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