/* $OpenBSD: scanopt.c,v 1.7 2024/11/09 18:03:44 op Exp $ */ /* flex - tool to generate fast lexical analyzers */ /* Copyright (c) 1990 The Regents of the University of California. */ /* All rights reserved. */ /* This code is derived from software contributed to Berkeley by */ /* Vern Paxson. */ /* The United States Government has rights in this work pursuant */ /* to contract no. DE-AC03-76SF00098 between the United States */ /* Department of Energy and the University of California. */ /* This file is part of flex. */ /* Redistribution and use in source and binary forms, with or without */ /* modification, are permitted provided that the following conditions */ /* are met: */ /* 1. Redistributions of source code must retain the above copyright */ /* notice, this list of conditions and the following disclaimer. */ /* 2. Redistributions in binary form must reproduce the above copyright */ /* notice, this list of conditions and the following disclaimer in the */ /* documentation and/or other materials provided with the distribution. */ /* Neither the name of the University nor the names of its contributors */ /* may be used to endorse or promote products derived from this software */ /* without specific prior written permission. */ /* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR */ /* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED */ /* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR */ /* PURPOSE. */ #include "flexdef.h" #include "scanopt.h" /* Internal structures */ #ifdef HAVE_STRCASECMP #define STRCASECMP(a,b) strcasecmp(a,b) #else static int STRCASECMP PROTO ((const char *, const char *)); static int STRCASECMP (const char *a, const char *b) { while (tolower ((u_char)*a++) == tolower ((u_char)*b++)) ; return b - a; } #endif #define ARG_NONE 0x01 #define ARG_REQ 0x02 #define ARG_OPT 0x04 #define IS_LONG 0x08 struct _aux { int flags; /* The above hex flags. */ int namelen; /* Length of the actual option word, e.g., "--file[=foo]" is 4 */ int printlen; /* Length of entire string, e.g., "--file[=foo]" is 12 */ }; struct _scanopt_t { const optspec_t *options; /* List of options. */ struct _aux *aux; /* Auxiliary data about options. */ int optc; /* Number of options. */ int argc; /* Number of args. */ char **argv; /* Array of strings. */ int index; /* Used as: argv[index][subscript]. */ int subscript; char no_err_msg; /* If true, do not print errors. */ char has_long; char has_short; }; /* Accessor functions. These WOULD be one-liners, but portability calls. */ static const char *NAME PROTO ((struct _scanopt_t *, int)); static int PRINTLEN PROTO ((struct _scanopt_t *, int)); static int RVAL PROTO ((struct _scanopt_t *, int)); static int FLAGS PROTO ((struct _scanopt_t *, int)); static const char *DESC PROTO ((struct _scanopt_t *, int)); static int scanopt_err PROTO ((struct _scanopt_t *, int, int, int)); static int matchlongopt PROTO ((char *, char **, int *, char **, int *)); static int find_opt PROTO ((struct _scanopt_t *, int, char *, int, int *, int *opt_offset)); static const char *NAME (struct _scanopt_t *s, int i) { return s->options[i].opt_fmt + ((s->aux[i].flags & IS_LONG) ? 2 : 1); } static int PRINTLEN (struct _scanopt_t *s, int i) { return s->aux[i].printlen; } static int RVAL (struct _scanopt_t *s, int i) { return s->options[i].r_val; } static int FLAGS (struct _scanopt_t *s, int i) { return s->aux[i].flags; } static const char *DESC (struct _scanopt_t *s, int i) { return s->options[i].desc ? s->options[i].desc : ""; } #ifndef NO_SCANOPT_USAGE static int get_cols PROTO ((void)); static int get_cols (void) { char *env; int cols = 80; /* default */ #ifdef HAVE_NCURSES_H initscr (); endwin (); if (COLS > 0) return COLS; #endif if ((env = getenv ("COLUMNS")) != NULL) cols = atoi (env); return cols; } #endif /* Macro to check for NULL before assigning a value. */ #define SAFE_ASSIGN(ptr,val) \ do{ \ if((ptr)!=NULL) \ *(ptr) = val; \ }while(0) /* Macro to assure we reset subscript whenever we adjust s->index.*/ #define INC_INDEX(s,n) \ do{ \ (s)->index += (n); \ (s)->subscript= 0; \ }while(0) scanopt_t *scanopt_init (const optspec_t *options, int argc, char **argv, int flags) { int i; struct _scanopt_t *s; s = (struct _scanopt_t *) malloc (sizeof (struct _scanopt_t)); s->options = options; s->optc = 0; s->argc = argc; s->argv = (char **) argv; s->index = 1; s->subscript = 0; s->no_err_msg = (flags & SCANOPT_NO_ERR_MSG); s->has_long = 0; s->has_short = 0; /* Determine option count. (Find entry with all zeros). */ s->optc = 0; while (options[s->optc].opt_fmt || options[s->optc].r_val || options[s->optc].desc) s->optc++; /* Build auxiliary data */ s->aux = (struct _aux *) malloc (s->optc * sizeof (struct _aux)); for (i = 0; i < s->optc; i++) { const u_char *p, *pname; const struct optspec_t *opt; struct _aux *aux; opt = s->options + i; aux = s->aux + i; aux->flags = ARG_NONE; if (opt->opt_fmt[0] == '-' && opt->opt_fmt[1] == '-') { aux->flags |= IS_LONG; pname = (const u_char *)(opt->opt_fmt + 2); s->has_long = 1; } else { pname = (const u_char *)(opt->opt_fmt + 1); s->has_short = 1; } aux->printlen = strlen (opt->opt_fmt); aux->namelen = 0; for (p = pname + 1; *p; p++) { /* detect required arg */ if (*p == '=' || isspace (*p) || !(aux->flags & IS_LONG)) { if (aux->namelen == 0) aux->namelen = p - pname; aux->flags |= ARG_REQ; aux->flags &= ~ARG_NONE; } /* detect optional arg. This overrides required arg. */ if (*p == '[') { if (aux->namelen == 0) aux->namelen = p - pname; aux->flags &= ~(ARG_REQ | ARG_NONE); aux->flags |= ARG_OPT; break; } } if (aux->namelen == 0) aux->namelen = p - pname; } return (scanopt_t *) s; } #ifndef NO_SCANOPT_USAGE /* these structs are for scanopt_usage(). */ struct usg_elem { int idx; struct usg_elem *next; struct usg_elem *alias; }; typedef struct usg_elem usg_elem; /* Prints a usage message based on contents of optlist. * Parameters: * scanner - The scanner, already initialized with scanopt_init(). * fp - The file stream to write to. * usage - Text to be prepended to option list. * Return: Always returns 0 (zero). * The output looks something like this: [indent][option, alias1, alias2...][indent][description line1 description line2...] */ int scanopt_usage (scanopt_t *scanner, FILE *fp, const char *usage) { struct _scanopt_t *s; int i, columns, indent = 2; usg_elem *byr_val = NULL; /* option indices sorted by r_val */ usg_elem *store; /* array of preallocated elements. */ int store_idx = 0; usg_elem *ue; int maxlen[2]; int desccol = 0; int print_run = 0; maxlen[0] = 0; maxlen[1] = 0; s = (struct _scanopt_t *) scanner; if (usage) { fprintf (fp, "%s\n", usage); } else { /* Find the basename of argv[0] */ const char *p; p = s->argv[0] + strlen (s->argv[0]); while (p != s->argv[0] && *p != '/') --p; if (*p == '/') p++; fprintf (fp, _("Usage: %s [OPTIONS]...\n"), p); } fprintf (fp, "\n"); /* Sort by r_val and string. Yes, this is O(n*n), but n is small. */ store = (usg_elem *) malloc (s->optc * sizeof (usg_elem)); for (i = 0; i < s->optc; i++) { /* grab the next preallocate node. */ ue = store + store_idx++; ue->idx = i; ue->next = ue->alias = NULL; /* insert into list. */ if (!byr_val) byr_val = ue; else { int found_alias = 0; usg_elem **ue_curr, **ptr_if_no_alias = NULL; ue_curr = &byr_val; while (*ue_curr) { if (RVAL (s, (*ue_curr)->idx) == RVAL (s, ue->idx)) { /* push onto the alias list. */ ue_curr = &((*ue_curr)->alias); found_alias = 1; break; } if (!ptr_if_no_alias && STRCASECMP (NAME (s, (*ue_curr)->idx), NAME (s, ue->idx)) > 0) { ptr_if_no_alias = ue_curr; } ue_curr = &((*ue_curr)->next); } if (!found_alias && ptr_if_no_alias) ue_curr = ptr_if_no_alias; ue->next = *ue_curr; *ue_curr = ue; } } #if 0 if (1) { printf ("ORIGINAL:\n"); for (i = 0; i < s->optc; i++) printf ("%2d: %s\n", i, NAME (s, i)); printf ("SORTED:\n"); ue = byr_val; while (ue) { usg_elem *ue2; printf ("%2d: %s\n", ue->idx, NAME (s, ue->idx)); for (ue2 = ue->alias; ue2; ue2 = ue2->next) printf (" +---> %2d: %s\n", ue2->idx, NAME (s, ue2->idx)); ue = ue->next; } } #endif /* Now build each row of output. */ /* first pass calculate how much room we need. */ for (ue = byr_val; ue; ue = ue->next) { usg_elem *ap; int len = 0; int nshort = 0, nlong = 0; #define CALC_LEN(i) do {\ if(FLAGS(s,i) & IS_LONG) \ len += (nlong++||nshort) ? 2+PRINTLEN(s,i) : PRINTLEN(s,i);\ else\ len += (nshort++||nlong)? 2+PRINTLEN(s,i) : PRINTLEN(s,i);\ }while(0) if (!(FLAGS (s, ue->idx) & IS_LONG)) CALC_LEN (ue->idx); /* do short aliases first. */ for (ap = ue->alias; ap; ap = ap->next) { if (FLAGS (s, ap->idx) & IS_LONG) continue; CALC_LEN (ap->idx); } if (FLAGS (s, ue->idx) & IS_LONG) CALC_LEN (ue->idx); /* repeat the above loop, this time for long aliases. */ for (ap = ue->alias; ap; ap = ap->next) { if (!(FLAGS (s, ap->idx) & IS_LONG)) continue; CALC_LEN (ap->idx); } if (len > maxlen[0]) maxlen[0] = len; /* It's much easier to calculate length for description column! */ len = strlen (DESC (s, ue->idx)); if (len > maxlen[1]) maxlen[1] = len; } /* Determine how much room we have, and how much we will allocate to each col. * Do not address pathological cases. Output will just be ugly. */ columns = get_cols () - 1; if (maxlen[0] + maxlen[1] + indent * 2 > columns) { /* col 0 gets whatever it wants. we'll wrap the desc col. */ maxlen[1] = columns - (maxlen[0] + indent * 2); if (maxlen[1] < 14) /* 14 is arbitrary lower limit on desc width. */ maxlen[1] = INT_MAX; } desccol = maxlen[0] + indent * 2; #define PRINT_SPACES(fp,n)\ do{\ int _n;\ _n=(n);\ while(_n-- > 0)\ fputc(' ',(fp));\ }while(0) /* Second pass (same as above loop), this time we print. */ /* Sloppy hack: We iterate twice. The first time we print short and long options. The second time we print those lines that have ONLY long options. */ while (print_run++ < 2) { for (ue = byr_val; ue; ue = ue->next) { usg_elem *ap; int nwords = 0, nchars = 0, has_short = 0; /* TODO: get has_short schtick to work */ has_short = !(FLAGS (s, ue->idx) & IS_LONG); for (ap = ue->alias; ap; ap = ap->next) { if (!(FLAGS (s, ap->idx) & IS_LONG)) { has_short = 1; break; } } if ((print_run == 1 && !has_short) || (print_run == 2 && has_short)) continue; PRINT_SPACES (fp, indent); nchars += indent; /* Print, adding a ", " between aliases. */ #define PRINT_IT(i) do{\ if(nwords++)\ nchars+=fprintf(fp,", ");\ nchars+=fprintf(fp,"%s",s->options[i].opt_fmt);\ }while(0) if (!(FLAGS (s, ue->idx) & IS_LONG)) PRINT_IT (ue->idx); /* print short aliases first. */ for (ap = ue->alias; ap; ap = ap->next) { if (!(FLAGS (s, ap->idx) & IS_LONG)) PRINT_IT (ap->idx); } if (FLAGS (s, ue->idx) & IS_LONG) PRINT_IT (ue->idx); /* repeat the above loop, this time for long aliases. */ for (ap = ue->alias; ap; ap = ap->next) { if (FLAGS (s, ap->idx) & IS_LONG) PRINT_IT (ap->idx); } /* pad to desccol */ PRINT_SPACES (fp, desccol - nchars); /* Print description, wrapped to maxlen[1] columns. */ if (1) { const char *pstart; pstart = DESC (s, ue->idx); while (1) { int n = 0; const char *lastws = NULL, *p; p = pstart; while (*p && n < maxlen[1] && *p != '\n') { if (isspace ((u_char)(*p)) || *p == '-') lastws = p; n++; p++; } if (!*p) { /* hit end of desc. done. */ fprintf (fp, "%s\n", pstart); break; } else if (*p == '\n') { /* print everything up to here then wrap. */ fprintf (fp, "%.*s\n", n, pstart); PRINT_SPACES (fp, desccol); pstart = p + 1; continue; } else { /* we hit the edge of the screen. wrap at space if possible. */ if (lastws) { fprintf (fp, "%.*s\n", (int)(lastws - pstart), pstart); pstart = lastws + 1; } else { fprintf (fp, "%.*s\n", n, pstart); pstart = p + 1; } PRINT_SPACES (fp, desccol); continue; } } } } } /* end while */ free (store); return 0; } #endif /* no scanopt_usage */ static int scanopt_err (struct _scanopt_t *s, int opt_offset, int is_short, int err) { const char *optname = ""; char optchar[2]; if (!s->no_err_msg) { if (s->index > 0 && s->index < s->argc) { if (is_short) { optchar[0] = s->argv[s->index][s->subscript]; optchar[1] = '\0'; optname = optchar; } else { optname = s->argv[s->index]; } } fprintf (stderr, "%s: ", s->argv[0]); switch (err) { case SCANOPT_ERR_ARG_NOT_ALLOWED: fprintf (stderr, _ ("option `%s' doesn't allow an argument\n"), optname); break; case SCANOPT_ERR_ARG_NOT_FOUND: fprintf (stderr, _("option `%s' requires an argument\n"), optname); break; case SCANOPT_ERR_OPT_AMBIGUOUS: fprintf (stderr, _("option `%s' is ambiguous\n"), optname); break; case SCANOPT_ERR_OPT_UNRECOGNIZED: fprintf (stderr, _("Unrecognized option `%s'\n"), optname); break; default: fprintf (stderr, _("Unknown error=(%d)\n"), err); break; } } return err; } /* Internal. Match str against the regex ^--([^=]+)(=(.*))? * return 1 if *looks* like a long option. * 'str' is the only input argument, the rest of the arguments are output only. * optname will point to str + 2 * */ static int matchlongopt (char *str, char **optname, int *optlen, char **arg, int *arglen) { char *p; *optname = *arg = (char *) 0; *optlen = *arglen = 0; /* Match regex /--./ */ p = str; if (p[0] != '-' || p[1] != '-' || !p[2]) return 0; p += 2; *optname = (char *) p; /* find the end of optname */ while (*p && *p != '=') ++p; *optlen = p - *optname; if (!*p) /* an option with no '=...' part. */ return 1; /* We saw an '=' char. The rest of p is the arg. */ p++; *arg = p; while (*p) ++p; *arglen = p - *arg; return 1; } /* Internal. Look up long or short option by name. * Long options must match a non-ambiguous prefix, or exact match. * Short options must be exact. * Return boolean true if found and no error. * Error stored in err_code or zero if no error. */ static int find_opt (struct _scanopt_t *s, int lookup_long, char *optstart, int len, int *err_code, int *opt_offset) { int nmatch = 0, lastr_val = 0, i; *err_code = 0; *opt_offset = -1; if (!optstart) return 0; for (i = 0; i < s->optc; i++) { char *optname; optname = (char *) (s->options[i].opt_fmt + (lookup_long ? 2 : 1)); if (lookup_long && (s->aux[i].flags & IS_LONG)) { if (len > s->aux[i].namelen) continue; if (strncmp (optname, optstart, len) == 0) { nmatch++; *opt_offset = i; /* exact match overrides all. */ if (len == s->aux[i].namelen) { nmatch = 1; break; } /* ambiguity is ok between aliases. */ if (lastr_val && lastr_val == s->options[i].r_val) nmatch--; lastr_val = s->options[i].r_val; } } else if (!lookup_long && !(s->aux[i].flags & IS_LONG)) { if (optname[0] == optstart[0]) { nmatch++; *opt_offset = i; } } } if (nmatch == 0) { *err_code = SCANOPT_ERR_OPT_UNRECOGNIZED; *opt_offset = -1; } else if (nmatch > 1) { *err_code = SCANOPT_ERR_OPT_AMBIGUOUS; *opt_offset = -1; } return *err_code ? 0 : 1; } int scanopt (scanopt_t *svoid, char **arg, int *optindex) { char *optname = NULL, *optarg = NULL, *pstart; int namelen = 0, arglen = 0; int errcode = 0, has_next; const optspec_t *optp; struct _scanopt_t *s; struct _aux *auxp; int is_short; int opt_offset = -1; s = (struct _scanopt_t *) svoid; /* Normalize return-parameters. */ SAFE_ASSIGN (arg, NULL); SAFE_ASSIGN (optindex, s->index); if (s->index >= s->argc) return 0; /* pstart always points to the start of our current scan. */ pstart = s->argv[s->index] + s->subscript; if (!pstart) return 0; if (s->subscript == 0) { /* test for exact match of "--" */ if (pstart[0] == '-' && pstart[1] == '-' && !pstart[2]) { SAFE_ASSIGN (optindex, s->index + 1); INC_INDEX (s, 1); return 0; } /* Match an opt. */ if (matchlongopt (pstart, &optname, &namelen, &optarg, &arglen)) { /* it LOOKS like an opt, but is it one?! */ if (!find_opt (s, 1, optname, namelen, &errcode, &opt_offset)) { scanopt_err (s, opt_offset, 0, errcode); return errcode; } /* We handle this below. */ is_short = 0; /* Check for short opt. */ } else if (pstart[0] == '-' && pstart[1]) { /* Pass through to below. */ is_short = 1; s->subscript++; pstart++; } else { /* It's not an option. We're done. */ return 0; } } /* We have to re-check the subscript status because it * may have changed above. */ if (s->subscript != 0) { /* we are somewhere in a run of short opts, * e.g., at the 'z' in `tar -xzf` */ optname = pstart; namelen = 1; is_short = 1; if (!find_opt (s, 0, pstart, namelen, &errcode, &opt_offset)) { return scanopt_err (s, opt_offset, 1, errcode); } optarg = pstart + 1; if (!*optarg) { optarg = NULL; arglen = 0; } else arglen = strlen (optarg); } /* At this point, we have a long or short option matched at opt_offset into * the s->options array (and corresponding aux array). * A trailing argument is in {optarg,arglen}, if any. */ /* Look ahead in argv[] to see if there is something * that we can use as an argument (if needed). */ has_next = s->index + 1 < s->argc && strcmp ("--", s->argv[s->index + 1]) != 0; optp = s->options + opt_offset; auxp = s->aux + opt_offset; /* case: no args allowed */ if (auxp->flags & ARG_NONE) { if (optarg && !is_short) { scanopt_err (s, opt_offset, is_short, errcode = SCANOPT_ERR_ARG_NOT_ALLOWED); INC_INDEX (s, 1); return errcode; } else if (!optarg) INC_INDEX (s, 1); else s->subscript++; return optp->r_val; } /* case: required */ if (auxp->flags & ARG_REQ) { if (!optarg && !has_next) return scanopt_err (s, opt_offset, is_short, SCANOPT_ERR_ARG_NOT_FOUND); if (!optarg) { /* Let the next argv element become the argument. */ SAFE_ASSIGN (arg, s->argv[s->index + 1]); INC_INDEX (s, 2); } else { SAFE_ASSIGN (arg, (char *) optarg); INC_INDEX (s, 1); } return optp->r_val; } /* case: optional */ if (auxp->flags & ARG_OPT) { SAFE_ASSIGN (arg, optarg); INC_INDEX (s, 1); return optp->r_val; } /* Should not reach here. */ return 0; } void scanopt_destroy (scanopt_t *svoid) { struct _scanopt_t *s; s = (struct _scanopt_t *) svoid; if (s) { free(s->aux); free (s); } }