1
2/*
3 * \file sort.c
4 *
5 *  This module implements argument sorting.
6 *
7 * @addtogroup autoopts
8 * @{
9 */
10/*
11 *  This file is part of AutoOpts, a companion to AutoGen.
12 *  AutoOpts is free software.
13 *  AutoOpts is Copyright (C) 1992-2018 by Bruce Korb - all rights reserved
14 *
15 *  AutoOpts is available under any one of two licenses.  The license
16 *  in use must be one of these two and the choice is under the control
17 *  of the user of the license.
18 *
19 *   The GNU Lesser General Public License, version 3 or later
20 *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
21 *
22 *   The Modified Berkeley Software Distribution License
23 *      See the file "COPYING.mbsd"
24 *
25 *  These files have the following sha256 sums:
26 *
27 *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
28 *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
29 *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
30 */
31
32/*
33 *  "must_arg" and "maybe_arg" are really similar.  The biggest
34 *  difference is that "may" will consume the next argument only if it
35 *  does not start with a hyphen and "must" will consume it, hyphen or not.
36 */
37static tSuccess
38must_arg(tOptions * opts, char * arg_txt, tOptState * pOS,
39         char ** opt_txt, uint32_t * opt_idx)
40{
41    /*
42     *  An option argument is required.  Long options can either have
43     *  a separate command line argument, or an argument attached by
44     *  the '=' character.  Figure out which.
45     */
46    switch (pOS->optType) {
47    case TOPT_SHORT:
48        /*
49         *  See if an arg string follows the flag character.  If not,
50         *  the next arg must be the option argument.
51         */
52        if (*arg_txt != NUL)
53            return SUCCESS;
54        break;
55
56    case TOPT_LONG:
57        /*
58         *  See if an arg string has already been assigned (glued on
59         *  with an `=' character).  If not, the next is the opt arg.
60         */
61        if (pOS->pzOptArg != NULL)
62            return SUCCESS;
63        break;
64
65    default:
66        return FAILURE;
67    }
68    if (opts->curOptIdx >= opts->origArgCt)
69        return FAILURE;
70
71    opt_txt[ (*opt_idx)++ ] = opts->origArgVect[ (opts->curOptIdx)++ ];
72    return SUCCESS;
73}
74
75static tSuccess
76maybe_arg(tOptions * opts, char * arg_txt, tOptState * pOS,
77          char ** opt_txt, uint32_t * opt_idx)
78{
79    /*
80     *  An option argument is optional.
81     */
82    switch (pOS->optType) {
83    case TOPT_SHORT:
84        /*
85         *  IF nothing is glued on after the current flag character,
86         *  THEN see if there is another argument.  If so and if it
87         *  does *NOT* start with a hyphen, then it is the option arg.
88         */
89        if (*arg_txt != NUL)
90            return SUCCESS;
91        break;
92
93    case TOPT_LONG:
94        /*
95         *  Look for an argument if we don't already have one (glued on
96         *  with a `=' character)
97         */
98        if (pOS->pzOptArg != NULL)
99            return SUCCESS;
100        break;
101
102    default:
103        return FAILURE;
104    }
105    if (opts->curOptIdx >= opts->origArgCt)
106        return PROBLEM;
107
108    arg_txt = opts->origArgVect[ opts->curOptIdx ];
109    if (*arg_txt != '-')
110        opt_txt[ (*opt_idx)++ ] = opts->origArgVect[ (opts->curOptIdx)++ ];
111    return SUCCESS;
112}
113
114/*
115 *  Process a string of short options glued together.  If the last one
116 *  does or may take an argument, the do the argument processing and leave.
117 */
118static tSuccess
119short_opt_ck(tOptions * opts, char * arg_txt, tOptState * pOS,
120             char ** opt_txt, uint32_t * opt_idx)
121{
122    while (*arg_txt != NUL) {
123        if (FAILED(opt_find_short(opts, (uint8_t)*arg_txt, pOS)))
124            return FAILURE;
125
126        /*
127         *  See if we can have an arg.
128         */
129        if (OPTST_GET_ARGTYPE(pOS->pOD->fOptState) == OPARG_TYPE_NONE) {
130            arg_txt++;
131
132        } else if (pOS->pOD->fOptState & OPTST_ARG_OPTIONAL) {
133            /*
134             *  Take an argument if it is not attached and it does not
135             *  start with a hyphen.
136             */
137            if (arg_txt[1] != NUL)
138                return SUCCESS;
139
140            arg_txt = opts->origArgVect[ opts->curOptIdx ];
141            if (*arg_txt != '-')
142                opt_txt[ (*opt_idx)++ ] =
143                    opts->origArgVect[ (opts->curOptIdx)++ ];
144            return SUCCESS;
145
146        } else {
147            /*
148             *  IF we need another argument, be sure it is there and
149             *  take it.
150             */
151            if (arg_txt[1] == NUL) {
152                if (opts->curOptIdx >= opts->origArgCt)
153                    return FAILURE;
154                opt_txt[ (*opt_idx)++ ] =
155                    opts->origArgVect[ (opts->curOptIdx)++ ];
156            }
157            return SUCCESS;
158        }
159    }
160    return SUCCESS;
161}
162
163/*
164 *  If the program wants sorted options (separated operands and options),
165 *  then this routine will to the trick.
166 */
167static void
168optionSort(tOptions * opts)
169{
170    char **  opt_txt;
171    char **  ppzOpds;
172    uint32_t optsIdx = 0;
173    uint32_t opdsIdx = 0;
174
175    tOptState os = OPTSTATE_INITIALIZER(DEFINED);
176
177    /*
178     *  Disable for POSIX conformance, or if there are no operands.
179     */
180    if (  (getenv("POSIXLY_CORRECT") != NULL)
181       || NAMED_OPTS(opts))
182        return;
183
184    /*
185     *  Make sure we can allocate two full-sized arg vectors.
186     */
187    opt_txt = malloc(opts->origArgCt * sizeof(char *));
188    if (opt_txt == NULL)
189        goto exit_no_mem;
190
191    ppzOpds = malloc(opts->origArgCt * sizeof(char *));
192    if (ppzOpds == NULL) {
193        free(opt_txt);
194        goto exit_no_mem;
195    }
196
197    opts->curOptIdx = 1;
198    opts->pzCurOpt  = NULL;
199
200    /*
201     *  Now, process all the options from our current position onward.
202     *  (This allows interspersed options and arguments for the few
203     *  non-standard programs that require it.)
204     */
205    for (;;) {
206        char * arg_txt;
207        tSuccess res;
208
209        /*
210         *  If we're out of arguments, we're done.  Join the option and
211         *  operand lists into the original argument vector.
212         */
213        if (opts->curOptIdx >= opts->origArgCt) {
214            errno = 0;
215            goto joinLists;
216        }
217
218        arg_txt = opts->origArgVect[ opts->curOptIdx ];
219        if (*arg_txt != '-') {
220            ppzOpds[ opdsIdx++ ] = opts->origArgVect[ (opts->curOptIdx)++ ];
221            continue;
222        }
223
224        switch (arg_txt[1]) {
225        case NUL:
226            /*
227             *  A single hyphen is an operand.
228             */
229            ppzOpds[ opdsIdx++ ] = opts->origArgVect[ (opts->curOptIdx)++ ];
230            continue;
231
232        case '-':
233            /*
234             *  Two consecutive hypens.  Put them on the options list and then
235             *  _always_ force the remainder of the arguments to be operands.
236             */
237            if (arg_txt[2] == NUL) {
238                opt_txt[ optsIdx++ ] =
239                    opts->origArgVect[ (opts->curOptIdx)++ ];
240                goto restOperands;
241            }
242            res = opt_find_long(opts, arg_txt+2, &os);
243            break;
244
245        default:
246            /*
247             *  If short options are not allowed, then do long
248             *  option processing.  Otherwise the character must be a
249             *  short (i.e. single character) option.
250             */
251            if ((opts->fOptSet & OPTPROC_SHORTOPT) == 0) {
252                res = opt_find_long(opts, arg_txt+1, &os);
253            } else {
254                res = opt_find_short(opts, (uint8_t)arg_txt[1], &os);
255            }
256            break;
257        }
258        if (FAILED(res)) {
259            errno = EINVAL;
260            goto freeTemps;
261        }
262
263        /*
264         *  We've found an option.  Add the argument to the option list.
265         *  Next, we have to see if we need to pull another argument to be
266         *  used as the option argument.
267         */
268        opt_txt[ optsIdx++ ] = opts->origArgVect[ (opts->curOptIdx)++ ];
269
270        if (OPTST_GET_ARGTYPE(os.pOD->fOptState) == OPARG_TYPE_NONE) {
271            /*
272             *  No option argument.  If we have a short option here,
273             *  then scan for short options until we get to the end
274             *  of the argument string.
275             */
276            if (  (os.optType == TOPT_SHORT)
277               && FAILED(short_opt_ck(opts, arg_txt+2, &os, opt_txt,
278                                      &optsIdx)) )  {
279                errno = EINVAL;
280                goto freeTemps;
281            }
282
283        } else if (os.pOD->fOptState & OPTST_ARG_OPTIONAL) {
284            switch (maybe_arg(opts, arg_txt+2, &os, opt_txt, &optsIdx)) {
285            case FAILURE: errno = EIO; goto freeTemps;
286            case PROBLEM: errno = 0;   goto joinLists;
287            }
288
289        } else {
290            switch (must_arg(opts, arg_txt+2, &os, opt_txt, &optsIdx)) {
291            case PROBLEM:
292            case FAILURE: errno = EIO; goto freeTemps;
293            }
294        }
295    } /* for (;;) */
296
297 restOperands:
298    while (opts->curOptIdx < opts->origArgCt)
299        ppzOpds[ opdsIdx++ ] = opts->origArgVect[ (opts->curOptIdx)++ ];
300
301 joinLists:
302    if (optsIdx > 0)
303        memcpy(opts->origArgVect + 1, opt_txt,
304               (size_t)optsIdx * sizeof(char *));
305    if (opdsIdx > 0)
306        memcpy(opts->origArgVect + 1 + optsIdx, ppzOpds,
307               (size_t)opdsIdx * sizeof(char *));
308
309 freeTemps:
310    free(opt_txt);
311    free(ppzOpds);
312    return;
313
314 exit_no_mem:
315    errno = ENOMEM;
316    return;
317}
318
319/** @}
320 *
321 * Local Variables:
322 * mode: C
323 * c-file-style: "stroustrup"
324 * indent-tabs-mode: nil
325 * End:
326 * end of autoopts/sort.c */
327