1/*	$NetBSD: putshell.c,v 1.7 2020/05/25 20:47:35 christos Exp $	*/
2
3
4/**
5 * \file putshell.c
6 *
7 *  This module will interpret the options set in the tOptions
8 *  structure and print them to standard out in a fashion that
9 *  will allow them to be interpreted by the Bourne or Korn shells.
10 *
11 * @addtogroup autoopts
12 * @{
13 */
14/*
15 *  This file is part of AutoOpts, a companion to AutoGen.
16 *  AutoOpts is free software.
17 *  AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved
18 *
19 *  AutoOpts is available under any one of two licenses.  The license
20 *  in use must be one of these two and the choice is under the control
21 *  of the user of the license.
22 *
23 *   The GNU Lesser General Public License, version 3 or later
24 *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
25 *
26 *   The Modified Berkeley Software Distribution License
27 *      See the file "COPYING.mbsd"
28 *
29 *  These files have the following sha256 sums:
30 *
31 *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
32 *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
33 *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
34 */
35
36/* = = = START-STATIC-FORWARD = = = */
37static size_t
38string_size(char const * scan, size_t nl_len);
39
40static char const *
41print_quoted_apostrophes(char const * str);
42
43static void
44print_quot_str(char const * str);
45
46static void
47print_enumeration(tOptions * pOpts, tOptDesc * pOD);
48
49static void
50print_membership(tOptions * pOpts, tOptDesc * pOD);
51
52static void
53print_stacked_arg(tOptions * pOpts, tOptDesc * pOD);
54
55static void
56print_reordering(tOptions * opts);
57/* = = = END-STATIC-FORWARD = = = */
58
59/**
60 * Count the number of bytes required to represent a string as a
61 * compilable string.
62 *
63 * @param[in] scan    the text to be rewritten as a C program text string.
64 * @param[in] nl_len  the number of bytes used for each embedded newline.
65 *
66 * @returns the count, including the terminating NUL byte.
67 */
68static size_t
69string_size(char const * scan, size_t nl_len)
70{
71    /*
72     *  Start by counting the start and end quotes, plus the NUL.
73     */
74    size_t res_ln = 3;
75
76    for (;;) {
77        char ch = *(scan++);
78        if ((ch >= ' ') && (ch <= '~')) {
79
80            /*
81             * a backslash allowance for double quotes and baskslashes
82             */
83            res_ln += ((ch == '"') || (ch == '\\')) ? 2 : 1;
84        }
85
86        /*
87         *  When not a normal character, then count the characters
88         *  required to represent whatever it is.
89         */
90        else switch (ch) {
91        case NUL:
92            return res_ln;
93
94        case NL:
95            res_ln += nl_len;
96            break;
97
98        case HT:
99        case BEL:
100        case BS:
101        case FF:
102        case CR:
103        case VT:
104            res_ln += 2;
105            break;
106
107        default:
108            res_ln += 4; /* text len for \xNN */
109        }
110    }
111}
112
113/*=export_func  optionQuoteString
114 * private:
115 *
116 * what:  Print a string as quoted text suitable for a C compiler.
117 * arg:   + char const * + text  + a block of text to quote +
118 * arg:   + char const * + nl    + line splice text         +
119 *
120 * ret_type:  char const *
121 * ret_desc:  the allocated input string as a quoted string
122 *
123 * doc:
124 *  This is for internal use by autogen and autoopts.
125 *  It takes an input string and produces text the C compiler can process
126 *  to produce an exact copy of the original string.
127 *  The caller must deallocate the result.  Standard C strings and
128 *  K&R strings are distinguished by the "nl" string.
129=*/
130char const *
131optionQuoteString(char const * text, char const * nl)
132{
133    size_t   nl_len = strlen(nl);
134    char *   out;
135    char *   res = out = AGALOC(string_size(text, nl_len), "quot str");
136    *(out++) = '"';
137
138    for (;;) {
139        unsigned char ch = (unsigned char)*text;
140        if ((ch >= ' ') && (ch <= '~')) {
141            if ((ch == '"') || (ch == '\\'))
142                /*
143                 *  We must escape these characters in the output string
144                 */
145                *(out++) = '\\';
146            *(out++) = (char)ch;
147
148        } else switch (ch) {
149#       define   add_esc_ch(_ch)  { *(out++) = '\\'; *(out++) = (_ch); }
150        case BEL: add_esc_ch('a'); break;
151        case BS:  add_esc_ch('b'); break;
152        case HT:  add_esc_ch('t'); break;
153        case VT:  add_esc_ch('v'); break;
154        case FF:  add_esc_ch('f'); break;
155        case CR:  add_esc_ch('r'); break;
156
157        case LF:
158            /*
159             *  Place contiguous new-lines on a single line.
160             *  The current character is a NL, check the next one.
161             */
162            while (*++text == NL)
163                add_esc_ch('n');
164
165            /*
166             *  Insert a splice before starting next line
167             */
168            if (*text != NUL) {
169                memcpy(out, nl, nl_len);
170                out += nl_len;
171
172                continue; /* text is already at the next character */
173            }
174
175            add_esc_ch('n');
176            /* FALLTHROUGH */
177
178        case NUL:
179            /*
180             *  End of string.  Terminate the quoted output.  If necessary,
181             *  deallocate the text string.  Return the scan resumption point.
182             */
183            *(out++) = '"';
184            *out = NUL;
185            return res;
186
187        default:
188            /*
189             *  sprintf is safe here, because we already computed
190             *  the amount of space we will be using.
191             */
192            sprintf(out, MK_STR_OCT_FMT, ch);
193            out += 4;
194        }
195
196        text++;
197#       undef add_esc_ch
198    }
199}
200
201/**
202 *  Print out escaped apostorophes.
203 *
204 *  @param[in] str  the apostrophies to print
205 */
206static char const *
207print_quoted_apostrophes(char const * str)
208{
209    while (*str == APOSTROPHE) {
210        fputs(QUOT_APOS, stdout);
211        str++;
212    }
213    return str;
214}
215
216/**
217 *  Print a single quote (apostrophe quoted) string.
218 *  Other than somersaults for apostrophes, nothing else needs quoting.
219 *
220 *  @param[in] str  the string to print
221 */
222static void
223print_quot_str(char const * str)
224{
225    /*
226     *  Handle empty strings to make the rest of the logic simpler.
227     */
228    if ((str == NULL) || (*str == NUL)) {
229        fputs(EMPTY_ARG, stdout);
230        return;
231    }
232
233    /*
234     *  Emit any single quotes/apostrophes at the start of the string and
235     *  bail if that is all we need to do.
236     */
237    str = print_quoted_apostrophes(str);
238    if (*str == NUL)
239        return;
240
241    /*
242     *  Start the single quote string
243     */
244    fputc(APOSTROPHE, stdout);
245    for (;;) {
246        char const * pz = strchr(str, APOSTROPHE);
247        if (pz == NULL)
248            break;
249
250        /*
251         *  Emit the string up to the single quote (apostrophe) we just found.
252         */
253        (void)fwrite(str, (size_t)(pz - str), (size_t)1, stdout);
254
255        /*
256         * Close the current string, emit the apostrophes and re-open the
257         * string (IFF there is more text to print).
258         */
259        fputc(APOSTROPHE, stdout);
260        str = print_quoted_apostrophes(pz);
261        if (*str == NUL)
262            return;
263
264        fputc(APOSTROPHE, stdout);
265    }
266
267    /*
268     *  If we broke out of the loop, we must still emit the remaining text
269     *  and then close the single quote string.
270     */
271    fputs(str, stdout);
272    fputc(APOSTROPHE, stdout);
273}
274
275static void
276print_enumeration(tOptions * pOpts, tOptDesc * pOD)
277{
278    uintptr_t e_val = pOD->optArg.argEnum;
279    printf(OPT_VAL_FMT, pOpts->pzPROGNAME, pOD->pz_NAME);
280
281    /*
282     *  Convert value to string, print that and restore numeric value.
283     */
284    (*(pOD->pOptProc))(OPTPROC_RETURN_VALNAME, pOD);
285    printf(QUOT_ARG_FMT, pOD->optArg.argString);
286    if (pOD->fOptState & OPTST_ALLOC_ARG)
287        AGFREE(pOD->optArg.argString);
288    pOD->optArg.argEnum = e_val;
289
290    printf(OPT_END_FMT, pOpts->pzPROGNAME, pOD->pz_NAME);
291}
292
293static void
294print_membership(tOptions * pOpts, tOptDesc * pOD)
295{
296    char const * svstr = pOD->optArg.argString;
297    char const * pz;
298    uintptr_t val = 1;
299    printf(zOptNumFmt, pOpts->pzPROGNAME, pOD->pz_NAME,
300           (int)(uintptr_t)(pOD->optCookie));
301    pOD->optCookie = VOIDP(~0UL);
302    (*(pOD->pOptProc))(OPTPROC_RETURN_VALNAME, pOD);
303
304    pz = pOD->optArg.argString;
305    while (*pz != NUL) {
306        printf("readonly %s_", pOD->pz_NAME);
307        pz = SPN_PLUS_N_SPACE_CHARS(pz);
308
309        for (;;) {
310            int ch = *(pz++);
311            if (IS_LOWER_CASE_CHAR(ch))   fputc(toupper(ch), stdout);
312            else if (IS_UPPER_CASE_CHAR(ch))   fputc(ch, stdout);
313            else if (IS_PLUS_N_SPACE_CHAR(ch)) goto name_done;
314            else if (ch == NUL)        { pz--; goto name_done; }
315            else fputc('_', stdout);
316        } name_done:;
317        printf(SHOW_VAL_FMT, (unsigned long)val);
318        val <<= 1;
319    }
320
321    AGFREE(pOD->optArg.argString);
322    pOD->optArg.argString = svstr;
323}
324
325static void
326print_stacked_arg(tOptions * pOpts, tOptDesc * pOD)
327{
328    tArgList *      pAL = (tArgList *)pOD->optCookie;
329    char const **   ppz = pAL->apzArgs;
330    int             ct  = pAL->useCt;
331
332    printf(zOptCookieCt, pOpts->pzPROGNAME, pOD->pz_NAME, ct);
333
334    while (--ct >= 0) {
335        printf(ARG_BY_NUM_FMT, pOpts->pzPROGNAME, pOD->pz_NAME,
336               pAL->useCt - ct);
337        print_quot_str(*(ppz++));
338        printf(EXPORT_ARG_FMT, pOpts->pzPROGNAME, pOD->pz_NAME,
339               pAL->useCt - ct);
340    }
341}
342
343/**
344 * emit the arguments as readily parsed text.
345 * The program options are set by emitting the shell "set" command.
346 *
347 * @param[in] opts  the program options structure
348 */
349static void
350print_reordering(tOptions * opts)
351{
352    unsigned int ix;
353
354    fputs(set_dash, stdout);
355
356    for (ix = opts->curOptIdx;
357         ix < opts->origArgCt;
358         ix++) {
359        fputc(' ', stdout);
360        print_quot_str(opts->origArgVect[ ix ]);
361    }
362    fputs(init_optct, stdout);
363}
364
365/*=export_func  optionPutShell
366 * what:  write a portable shell script to parse options
367 * private:
368 * arg:   tOptions *, pOpts, the program options descriptor
369 * doc:   This routine will emit portable shell script text for parsing
370 *        the options described in the option definitions.
371=*/
372void
373optionPutShell(tOptions * pOpts)
374{
375    int  optIx = 0;
376
377    printf(zOptCtFmt, pOpts->curOptIdx-1);
378
379    do  {
380        tOptDesc * pOD = pOpts->pOptDesc + optIx;
381
382        if ((pOD->fOptState & OPTST_NO_OUTPUT_MASK) != 0)
383            continue;
384
385        /*
386         *  Equivalence classes are hard to deal with.  Where the
387         *  option data wind up kind of squishes around.  For the purposes
388         *  of emitting shell state, they are not recommended, but we'll
389         *  do something.  I guess we'll emit the equivalenced-to option
390         *  at the point in time when the base option is found.
391         */
392        if (pOD->optEquivIndex != NO_EQUIVALENT)
393            continue; /* equivalence to a different option */
394
395        /*
396         *  Equivalenced to a different option.  Process the current option
397         *  as the equivalenced-to option.  Keep the persistent state bits,
398         *  but copy over the set-state bits.
399         */
400        if (pOD->optActualIndex != optIx) {
401            tOptDesc * p  = pOpts->pOptDesc + pOD->optActualIndex;
402            p->optArg     = pOD->optArg;
403            p->fOptState &= OPTST_PERSISTENT_MASK;
404            p->fOptState |= pOD->fOptState & ~OPTST_PERSISTENT_MASK;
405            printf(zEquivMode, pOpts->pzPROGNAME, pOD->pz_NAME, p->pz_NAME);
406            pOD = p;
407        }
408
409        /*
410         *  If the argument type is a set membership bitmask, then we always
411         *  emit the thing.  We do this because it will always have some sort
412         *  of bitmask value and we need to emit the bit values.
413         */
414        if (OPTST_GET_ARGTYPE(pOD->fOptState) == OPARG_TYPE_MEMBERSHIP) {
415            print_membership(pOpts, pOD);
416            continue;
417        }
418
419        /*
420         *  IF the option was either specified or it wakes up enabled,
421         *  then we will emit information.  Otherwise, skip it.
422         *  The idea is that if someone defines an option to initialize
423         *  enabled, we should tell our shell script that it is enabled.
424         */
425        if (UNUSED_OPT(pOD) && DISABLED_OPT(pOD))
426            continue;
427
428        /*
429         *  Handle stacked arguments
430         */
431        if (  (pOD->fOptState & OPTST_STACKED)
432           && (pOD->optCookie != NULL) )  {
433            print_stacked_arg(pOpts, pOD);
434            continue;
435        }
436
437        /*
438         *  If the argument has been disabled,
439         *  Then set its value to the disablement string
440         */
441        if ((pOD->fOptState & OPTST_DISABLED) != 0) {
442            printf(zOptDisabl, pOpts->pzPROGNAME, pOD->pz_NAME,
443                   (pOD->pz_DisablePfx != NULL)
444                   ? pOD->pz_DisablePfx : "false");
445            continue;
446        }
447
448        /*
449         *  If the argument type is numeric, the last arg pointer
450         *  is really the VALUE of the string that was pointed to.
451         */
452        if (OPTST_GET_ARGTYPE(pOD->fOptState) == OPARG_TYPE_NUMERIC) {
453            printf(zOptNumFmt, pOpts->pzPROGNAME, pOD->pz_NAME,
454                   (int)pOD->optArg.argInt);
455            continue;
456        }
457
458        /*
459         *  If the argument type is an enumeration, then it is much
460         *  like a text value, except we call the callback function
461         *  to emit the value corresponding to the "optArg" number.
462         */
463        if (OPTST_GET_ARGTYPE(pOD->fOptState) == OPARG_TYPE_ENUMERATION) {
464            print_enumeration(pOpts, pOD);
465            continue;
466        }
467
468        /*
469         *  If the argument type is numeric, the last arg pointer
470         *  is really the VALUE of the string that was pointed to.
471         */
472        if (OPTST_GET_ARGTYPE(pOD->fOptState) == OPARG_TYPE_BOOLEAN) {
473            printf(zFullOptFmt, pOpts->pzPROGNAME, pOD->pz_NAME,
474                   (pOD->optArg.argBool == 0) ? "false" : "true");
475            continue;
476        }
477
478        /*
479         *  IF the option has an empty value,
480         *  THEN we set the argument to the occurrence count.
481         */
482        if (  (pOD->optArg.argString == NULL)
483           || (pOD->optArg.argString[0] == NUL) ) {
484
485            printf(zOptNumFmt, pOpts->pzPROGNAME, pOD->pz_NAME,
486                   pOD->optOccCt);
487            continue;
488        }
489
490        /*
491         *  This option has a text value
492         */
493        printf(OPT_VAL_FMT, pOpts->pzPROGNAME, pOD->pz_NAME);
494        print_quot_str(pOD->optArg.argString);
495        printf(OPT_END_FMT, pOpts->pzPROGNAME, pOD->pz_NAME);
496
497    } while (++optIx < pOpts->presetOptCt );
498
499    if (  ((pOpts->fOptSet & OPTPROC_REORDER) != 0)
500       && (pOpts->curOptIdx < pOpts->origArgCt))
501        print_reordering(pOpts);
502
503    fflush(stdout);
504}
505
506/** @}
507 *
508 * Local Variables:
509 * mode: C
510 * c-file-style: "stroustrup"
511 * indent-tabs-mode: nil
512 * End:
513 * end of autoopts/putshell.c */
514