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