xref: /dragonfly/contrib/less/option.c (revision e0f238eda64c20d98364903e0c003825fd0b66dd)
1 /*
2  * Copyright (C) 1984-2024  Mark Nudelman
3  *
4  * You may distribute under the terms of either the GNU General Public
5  * License or the Less License, as specified in the README file.
6  *
7  * For more information, see the README file.
8  */
9 
10 
11 /*
12  * Process command line options.
13  *
14  * Each option is a single letter which controls a program variable.
15  * The options have defaults which may be changed via
16  * the command line option, toggled via the "-" command,
17  * or queried via the "_" command.
18  */
19 
20 #include "less.h"
21 #include "option.h"
22 
23 static struct loption *pendopt;
24 public lbool plusoption = FALSE;
25 
26 static constant char *optstring(constant char *s, char **p_str, constant char *printopt, constant char *validchars);
27 static int flip_triple(int val, int lc);
28 
29 extern int less_is_more;
30 extern int quit_at_eof;
31 extern char *every_first_cmd;
32 extern int opt_use_backslash;
33 
34 /*
35  * Return a printable description of an option.
36  */
opt_desc(struct loption * o)37 static constant char * opt_desc(struct loption *o)
38 {
39           static char buf[OPTNAME_MAX + 10];
40           if (o->oletter == OLETTER_NONE)
41                     SNPRINTF1(buf, sizeof(buf), "--%s", o->onames->oname);
42           else
43                     SNPRINTF2(buf, sizeof(buf), "-%c (--%s)", o->oletter, o->onames->oname);
44           return (buf);
45 }
46 
47 /*
48  * Return a string suitable for printing as the "name" of an option.
49  * For example, if the option letter is 'x', just return "-x".
50  */
propt(char c)51 public constant char * propt(char c)
52 {
53           static char buf[MAX_PRCHAR_LEN+2];
54 
55           sprintf(buf, "-%s", prchar((LWCHAR) c));
56           return (buf);
57 }
58 
59 /*
60  * Scan an argument (either from the command line or from the
61  * LESS environment variable) and process it.
62  */
scan_option(constant char * s)63 public void scan_option(constant char *s)
64 {
65           struct loption *o;
66           char optc;
67           constant char *optname;
68           constant char *printopt;
69           char *str;
70           lbool set_default;
71           int lc;
72           lbool ambig;
73           PARG parg;
74 
75           if (s == NULL)
76                     return;
77 
78           /*
79            * If we have a pending option which requires an argument,
80            * handle it now.
81            * This happens if the previous option was, for example, "-P"
82            * without a following string.  In that case, the current
83            * option is simply the argument for the previous option.
84            */
85           if (pendopt != NULL)
86           {
87                     if (!(pendopt->otype & UNSUPPORTED))
88                     {
89                               switch (pendopt->otype & OTYPE)
90                               {
91                               case STRING:
92                                         (*pendopt->ofunc)(INIT, s);
93                                         break;
94                               case NUMBER:
95                                         printopt = opt_desc(pendopt);
96                                         *(pendopt->ovar) = getnumc(&s, printopt, NULL);
97                                         break;
98                               }
99                     }
100                     pendopt = NULL;
101                     return;
102           }
103 
104           set_default = FALSE;
105           optname = NULL;
106 
107           while (*s != '\0')
108           {
109                     /*
110                      * Check some special cases first.
111                      */
112                     switch (optc = *s++)
113                     {
114                     case ' ':
115                     case '\t':
116                     case END_OPTION_STRING:
117                               continue;
118                     case '-':
119                               /*
120                                * "--" indicates an option name instead of a letter.
121                                */
122                               if (*s == '-')
123                                         optname = ++s;
124                               /*
125                                * "-+" or "--+" means set these options back to their defaults.
126                                * (They may have been set otherwise by previous options.)
127                                */
128                               set_default = (*s == '+');
129                               if (set_default)
130                                         s++;
131                               if (optname != NULL)
132                               {
133                                         optname = s;
134                                         break;
135                               }
136                               continue;
137                     case '+':
138                               /*
139                                * An option prefixed by a "+" is ungotten, so
140                                * that it is interpreted as less commands
141                                * processed at the start of the first input file.
142                                * "++" means process the commands at the start of
143                                * EVERY input file.
144                                */
145                               plusoption = TRUE;
146                               s = optstring(s, &str, propt('+'), NULL);
147                               if (s == NULL)
148                                         return;
149                               if (*str == '+')
150                               {
151                                         if (every_first_cmd != NULL)
152                                                   free(every_first_cmd);
153                                         every_first_cmd = save(str+1);
154                               } else
155                               {
156                                         ungetsc(str);
157                                         ungetcc_end_command();
158                               }
159                               free(str);
160                               continue;
161                     case '0':  case '1':  case '2':  case '3':  case '4':
162                     case '5':  case '6':  case '7':  case '8':  case '9':
163                               /*
164                                * Special "more" compatibility form "-<number>"
165                                * instead of -z<number> to set the scrolling
166                                * window size.
167                                */
168                               s--;
169                               optc = 'z';
170                               break;
171                     case 'n':
172                               if (less_is_more)
173                                         optc = 'z';
174                               break;
175                     }
176 
177                     /*
178                      * Not a special case.
179                      * Look up the option letter in the option table.
180                      */
181                     ambig = FALSE;
182                     if (optname == NULL)
183                     {
184                               printopt = propt(optc);
185                               lc = ASCII_IS_LOWER(optc);
186                               o = findopt(optc);
187                     } else
188                     {
189                               printopt = optname;
190                               lc = ASCII_IS_LOWER(optname[0]);
191                               o = findopt_name(&optname, NULL, &ambig);
192                               s = optname;
193                               optname = NULL;
194                               if (*s == '\0' || *s == ' ')
195                               {
196                                         /*
197                                          * The option name matches exactly.
198                                          */
199                                         ;
200                               } else if (*s == '=')
201                               {
202                                         /*
203                                          * The option name is followed by "=value".
204                                          */
205                                         if (o != NULL &&
206                                             (o->otype & OTYPE) != STRING &&
207                                             (o->otype & OTYPE) != NUMBER)
208                                         {
209                                                   parg.p_string = printopt;
210                                                   error("The %s option should not be followed by =",
211                                                             &parg);
212                                                   return;
213                                         }
214                                         s++;
215                               } else
216                               {
217                                         /*
218                                          * The specified name is longer than the
219                                          * real option name.
220                                          */
221                                         o = NULL;
222                               }
223                     }
224                     if (o == NULL)
225                     {
226                               parg.p_string = printopt;
227                               if (ambig)
228                                         error("%s is an ambiguous abbreviation (\"less --help\" for help)",
229                                                   &parg);
230                               else
231                                         error("There is no %s option (\"less --help\" for help)",
232                                                   &parg);
233                               return;
234                     }
235 
236                     str = NULL;
237                     switch (o->otype & OTYPE)
238                     {
239                     case BOOL:
240                               if (o->otype & UNSUPPORTED)
241                                         break;
242                               if (o->ovar != NULL)
243                               {
244                                         if (set_default)
245                                                   *(o->ovar) = o->odefault;
246                                         else
247                                                   *(o->ovar) = ! o->odefault;
248                               }
249                               break;
250                     case TRIPLE:
251                               if (o->otype & UNSUPPORTED)
252                                         break;
253                               if (o->ovar != NULL)
254                               {
255                                         if (set_default)
256                                                   *(o->ovar) = o->odefault;
257                                         else
258                                                   *(o->ovar) = flip_triple(o->odefault, lc);
259                               }
260                               break;
261                     case STRING:
262                               if (*s == '\0')
263                               {
264                                         /*
265                                          * Set pendopt and return.
266                                          * We will get the string next time
267                                          * scan_option is called.
268                                          */
269                                         pendopt = o;
270                                         return;
271                               }
272                               /*
273                                * Don't do anything here.
274                                * All processing of STRING options is done by
275                                * the handling function.
276                                */
277                               while (*s == ' ')
278                                         s++;
279                               s = optstring(s, &str, printopt, o->odesc[1]);
280                               if (s == NULL)
281                                         return;
282                               break;
283                     case NUMBER:
284                               if (*s == '\0')
285                               {
286                                         pendopt = o;
287                                         return;
288                               }
289                               if (o->otype & UNSUPPORTED)
290                                         break;
291                               *(o->ovar) = getnumc(&s, printopt, NULL);
292                               break;
293                     }
294                     /*
295                      * If the option has a handling function, call it.
296                      */
297                     if (o->ofunc != NULL && !(o->otype & UNSUPPORTED))
298                               (*o->ofunc)(INIT, str);
299                     if (str != NULL)
300                               free(str);
301           }
302 }
303 
304 /*
305  * Toggle command line flags from within the program.
306  * Used by the "-" and "_" commands.
307  * how_toggle may be:
308  *      OPT_NO_TOGGLE   just report the current setting, without changing it.
309  *      OPT_TOGGLE      invert the current setting
310  *      OPT_UNSET       set to the default value
311  *      OPT_SET         set to the inverse of the default value
312  */
toggle_option(struct loption * o,int lower,constant char * s,int how_toggle)313 public void toggle_option(struct loption *o, int lower, constant char *s, int how_toggle)
314 {
315           int num;
316           int no_prompt;
317           lbool err;
318           PARG parg;
319 
320           no_prompt = (how_toggle & OPT_NO_PROMPT);
321           how_toggle &= ~OPT_NO_PROMPT;
322 
323           if (o == NULL)
324           {
325                     error("No such option", NULL_PARG);
326                     return;
327           }
328 
329           if (how_toggle == OPT_TOGGLE && (o->otype & NO_TOGGLE))
330           {
331                     parg.p_string = opt_desc(o);
332                     error("Cannot change the %s option", &parg);
333                     return;
334           }
335 
336           if (how_toggle == OPT_NO_TOGGLE && (o->otype & NO_QUERY))
337           {
338                     parg.p_string = opt_desc(o);
339                     error("Cannot query the %s option", &parg);
340                     return;
341           }
342 
343           /*
344            * Check for something which appears to be a do_toggle
345            * (because the "-" command was used), but really is not.
346            * This could be a string option with no string, or
347            * a number option with no number.
348            */
349           switch (o->otype & OTYPE)
350           {
351           case STRING:
352           case NUMBER:
353                     if (how_toggle == OPT_TOGGLE && *s == '\0')
354                               how_toggle = OPT_NO_TOGGLE;
355                     break;
356           }
357 
358 #if HILITE_SEARCH
359           if (how_toggle != OPT_NO_TOGGLE && (o->otype & HL_REPAINT))
360                     repaint_hilite(FALSE);
361 #endif
362 
363           /*
364            * Now actually toggle (change) the variable.
365            */
366           if (how_toggle != OPT_NO_TOGGLE)
367           {
368                     switch (o->otype & OTYPE)
369                     {
370                     case BOOL:
371                               /*
372                                * Boolean.
373                                */
374                               if (o->ovar != NULL)
375                               {
376                                         switch (how_toggle)
377                                         {
378                                         case OPT_TOGGLE:
379                                                   *(o->ovar) = ! *(o->ovar);
380                                                   break;
381                                         case OPT_UNSET:
382                                                   *(o->ovar) = o->odefault;
383                                                   break;
384                                         case OPT_SET:
385                                                   *(o->ovar) = ! o->odefault;
386                                                   break;
387                                         }
388                               }
389                               break;
390                     case TRIPLE:
391                               /*
392                                * Triple:
393                                *      If user gave the lower case letter, then switch
394                                *      to 1 unless already 1, in which case make it 0.
395                                *      If user gave the upper case letter, then switch
396                                *      to 2 unless already 2, in which case make it 0.
397                                */
398                               if (o->ovar != NULL)
399                               {
400                                         switch (how_toggle)
401                                         {
402                                         case OPT_TOGGLE:
403                                                   *(o->ovar) = flip_triple(*(o->ovar), lower);
404                                                   break;
405                                         case OPT_UNSET:
406                                                   *(o->ovar) = o->odefault;
407                                                   break;
408                                         case OPT_SET:
409                                                   *(o->ovar) = flip_triple(o->odefault, lower);
410                                                   break;
411                                         }
412                               }
413                               break;
414                     case STRING:
415                               /*
416                                * String: don't do anything here.
417                                *      The handling function will do everything.
418                                */
419                               switch (how_toggle)
420                               {
421                               case OPT_SET:
422                               case OPT_UNSET:
423                                         error("Cannot use \"-+\" or \"--\" for a string option",
424                                                   NULL_PARG);
425                                         return;
426                               }
427                               break;
428                     case NUMBER:
429                               /*
430                                * Number: set the variable to the given number.
431                                */
432                               switch (how_toggle)
433                               {
434                               case OPT_TOGGLE:
435                                         num = getnumc(&s, NULL, &err);
436                                         if (!err)
437                                                   *(o->ovar) = num;
438                                         break;
439                               case OPT_UNSET:
440                                         *(o->ovar) = o->odefault;
441                                         break;
442                               case OPT_SET:
443                                         error("Can't use \"-!\" for a numeric option",
444                                                   NULL_PARG);
445                                         return;
446                               }
447                               break;
448                     }
449           }
450 
451           /*
452            * Call the handling function for any special action
453            * specific to this option.
454            */
455           if (o->ofunc != NULL)
456                     (*o->ofunc)((how_toggle==OPT_NO_TOGGLE) ? QUERY : TOGGLE, s);
457 
458 #if HILITE_SEARCH
459           if (how_toggle != OPT_NO_TOGGLE && (o->otype & HL_REPAINT))
460                     chg_hilite();
461 #endif
462 
463           if (!no_prompt)
464           {
465                     /*
466                      * Print a message describing the new setting.
467                      */
468                     switch (o->otype & OTYPE)
469                     {
470                     case BOOL:
471                     case TRIPLE:
472                               /*
473                                * Print the odesc message.
474                                */
475                               if (o->ovar != NULL)
476                                         error(o->odesc[*(o->ovar)], NULL_PARG);
477                               break;
478                     case NUMBER:
479                               /*
480                                * The message is in odesc[1] and has a %d for
481                                * the value of the variable.
482                                */
483                               parg.p_int = *(o->ovar);
484                               error(o->odesc[1], &parg);
485                               break;
486                     case STRING:
487                               /*
488                                * Message was already printed by the handling function.
489                                */
490                               break;
491                     }
492           }
493 
494           if (how_toggle != OPT_NO_TOGGLE && (o->otype & REPAINT))
495                     screen_trashed();
496 }
497 
498 /*
499  * "Toggle" a triple-valued option.
500  */
flip_triple(int val,int lc)501 static int flip_triple(int val, int lc)
502 {
503           if (lc)
504                     return ((val == OPT_ON) ? OPT_OFF : OPT_ON);
505           else
506                     return ((val == OPT_ONPLUS) ? OPT_OFF : OPT_ONPLUS);
507 }
508 
509 /*
510  * Determine if an option takes a parameter.
511  */
opt_has_param(struct loption * o)512 public int opt_has_param(struct loption *o)
513 {
514           if (o == NULL)
515                     return (0);
516           if (o->otype & (BOOL|TRIPLE|NOVAR|NO_TOGGLE))
517                     return (0);
518           return (1);
519 }
520 
521 /*
522  * Return the prompt to be used for a given option letter.
523  * Only string and number valued options have prompts.
524  */
opt_prompt(struct loption * o)525 public constant char * opt_prompt(struct loption *o)
526 {
527           if (o == NULL || (o->otype & (STRING|NUMBER)) == 0)
528                     return ("?");
529           return (o->odesc[0]);
530 }
531 
532 /*
533  * If the specified option can be toggled, return NULL.
534  * Otherwise return an appropriate error message.
535  */
opt_toggle_disallowed(int c)536 public constant char * opt_toggle_disallowed(int c)
537 {
538           switch (c)
539           {
540           case 'o':
541                     if (ch_getflags() & CH_CANSEEK)
542                               return "Input is not a pipe";
543                     break;
544           }
545           return NULL;
546 }
547 
548 /*
549  * Return whether or not there is a string option pending;
550  * that is, if the previous option was a string-valued option letter
551  * (like -P) without a following string.
552  * In that case, the current option is taken to be the string for
553  * the previous option.
554  */
isoptpending(void)555 public lbool isoptpending(void)
556 {
557           return (pendopt != NULL);
558 }
559 
560 /*
561  * Print error message about missing string.
562  */
nostring(constant char * printopt)563 static void nostring(constant char *printopt)
564 {
565           PARG parg;
566           parg.p_string = printopt;
567           error("Value is required after %s", &parg);
568 }
569 
570 /*
571  * Print error message if a STRING type option is not followed by a string.
572  */
nopendopt(void)573 public void nopendopt(void)
574 {
575           nostring(opt_desc(pendopt));
576 }
577 
578 /*
579  * Scan to end of string or to an END_OPTION_STRING character.
580  * In the latter case, replace the char with a null char.
581  * Return a pointer to the remainder of the string, if any.
582  * validchars is of the form "[-][.]d[,]".
583  *   "-" means an optional leading "-" is allowed
584  *   "." means an optional leading "." is allowed (after any "-")
585  *   "d" indicates a string of one or more digits (0-9)
586  *   "," indicates a comma-separated list of digit strings is allowed
587  *   "s" means a space char terminates the argument
588  */
optstring(constant char * s,char ** p_str,constant char * printopt,constant char * validchars)589 static constant char * optstring(constant char *s, char **p_str, constant char *printopt, constant char *validchars)
590 {
591           constant char *p;
592           char *out;
593 
594           if (*s == '\0')
595           {
596                     nostring(printopt);
597                     return (NULL);
598           }
599           /* Alloc could be more than needed, but not worth trimming. */
600           *p_str = (char *) ecalloc(strlen(s)+1, sizeof(char));
601           out = *p_str;
602 
603           for (p = s;  *p != '\0';  p++)
604           {
605                     if (opt_use_backslash && *p == '\\' && p[1] != '\0')
606                     {
607                               /* Take next char literally. */
608                               ++p;
609                     } else
610                     {
611                               if (validchars != NULL)
612                               {
613                                         if (validchars[0] == 's')
614                                         {
615                                                   if (*p == ' ')
616                                                             break;
617                                         } else if (*p == '-')
618                                         {
619                                                   if (validchars[0] != '-')
620                                                             break;
621                                                   ++validchars;
622                                         } else if (*p == '.')
623                                         {
624                                                   if (validchars[0] == '-')
625                                                             ++validchars;
626                                                   if (validchars[0] != '.')
627                                                             break;
628                                                   ++validchars;
629                                         } else if (*p == ',')
630                                         {
631                                                   if (validchars[0] == '\0' || validchars[1] != ',')
632                                                             break;
633                                         } else if (*p >= '0' && *p <= '9')
634                                         {
635                                                   while (validchars[0] == '-' || validchars[0] == '.')
636                                                             ++validchars;
637                                                   if (validchars[0] != 'd')
638                                                             break;
639                                         } else
640                                                   break;
641                               }
642                               if (*p == END_OPTION_STRING)
643                                         /* End of option string. */
644                                         break;
645                     }
646                     *out++ = *p;
647           }
648           *out = '\0';
649           return (p);
650 }
651 
652 /*
653  */
num_error(constant char * printopt,lbool * errp,lbool overflow)654 static int num_error(constant char *printopt, lbool *errp, lbool overflow)
655 {
656           PARG parg;
657 
658           if (errp != NULL)
659           {
660                     *errp = TRUE;
661                     return (-1);
662           }
663           if (printopt != NULL)
664           {
665                     parg.p_string = printopt;
666                     error((overflow
667                            ? "Number too large in '%s'"
668                            : "Number is required after %s"),
669                           &parg);
670           }
671           return (-1);
672 }
673 
674 /*
675  * Translate a string into a number.
676  * Like atoi(), but takes a pointer to a char *, and updates
677  * the char * to point after the translated number.
678  */
getnumc(constant char ** sp,constant char * printopt,lbool * errp)679 public int getnumc(constant char **sp, constant char *printopt, lbool *errp)
680 {
681           constant char *s = *sp;
682           int n;
683           lbool neg;
684 
685           s = skipspc(s);
686           neg = FALSE;
687           if (*s == '-')
688           {
689                     neg = TRUE;
690                     s++;
691           }
692           if (*s < '0' || *s > '9')
693                     return (num_error(printopt, errp, FALSE));
694 
695           n = lstrtoic(s, sp, 10);
696           if (n < 0)
697                     return (num_error(printopt, errp, TRUE));
698           if (errp != NULL)
699                     *errp = FALSE;
700           if (neg)
701                     n = -n;
702           return (n);
703 }
704 
getnum(char ** sp,constant char * printopt,lbool * errp)705 public int getnum(char **sp, constant char *printopt, lbool *errp)
706 {
707           constant char *cs = *sp;
708           int r = getnumc(&cs, printopt, errp);
709           *sp = (char *) cs;
710           return r;
711 }
712 
713 /*
714  * Translate a string into a fraction, represented by the part of a
715  * number which would follow a decimal point.
716  * The value of the fraction is returned as parts per NUM_FRAC_DENOM.
717  * That is, if "n" is returned, the fraction intended is n/NUM_FRAC_DENOM.
718  */
getfraction(constant char ** sp,constant char * printopt,lbool * errp)719 public long getfraction(constant char **sp, constant char *printopt, lbool *errp)
720 {
721           constant char *s;
722           long frac = 0;
723           int fraclen = 0;
724 
725           s = skipspc(*sp);
726           if (*s < '0' || *s > '9')
727                     return (num_error(printopt, errp, FALSE));
728 
729           for ( ;  *s >= '0' && *s <= '9';  s++)
730           {
731                     if (NUM_LOG_FRAC_DENOM <= fraclen)
732                               continue;
733                     frac = (frac * 10) + (*s - '0');
734                     fraclen++;
735           }
736           while (fraclen++ < NUM_LOG_FRAC_DENOM)
737                     frac *= 10;
738           *sp = s;
739           if (errp != NULL)
740                     *errp = FALSE;
741           return (frac);
742 }
743 
744 /*
745  * Set the UNSUPPORTED bit in every option listed
746  * in the LESS_UNSUPPORT environment variable.
747  */
init_unsupport(void)748 public void init_unsupport(void)
749 {
750           constant char *s = lgetenv("LESS_UNSUPPORT");
751           if (isnullenv(s))
752                     return;
753           for (;;)
754           {
755                     struct loption *opt;
756                     s = skipspc(s);
757                     if (*s == '\0') break;
758                     if (*s == '-' && *++s == '\0') break;
759                     if (*s == '-') /* long option name */
760                     {
761                               ++s;
762                               opt = findopt_name(&s, NULL, NULL);
763                     } else /* short (single-char) option */
764                     {
765                               opt = findopt(*s);
766                               if (opt != NULL) ++s;
767                     }
768                     if (opt != NULL)
769                               opt->otype |= UNSUPPORTED;
770           }
771 }
772 
773 /*
774  * Get the value of the -e flag.
775  */
get_quit_at_eof(void)776 public int get_quit_at_eof(void)
777 {
778           if (!less_is_more)
779                     return quit_at_eof;
780           /* When less_is_more is set, the -e flag semantics are different. */
781           return quit_at_eof ? OPT_ONPLUS : OPT_ON;
782 }
783