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