xref: /dragonfly/contrib/less/option.c (revision e433da38)
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