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
25static struct loption *pendopt;
26public int plusoption = FALSE;
27
28static char *optstring(char *s, char **p_str, char *printopt, char *validchars);
29static int flip_triple(int val, int lc);
30
31extern int screen_trashed;
32extern int less_is_more;
33extern int quit_at_eof;
34extern char *every_first_cmd;
35extern int opt_use_backslash;
36
37/*
38 * Return a printable description of an option.
39 */
40static 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 */
54public 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 */
66public 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 */
300public 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 */
483static 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 */
494public 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 */
507public 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 */
518public 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 */
537public int isoptpending(void)
538{
539	return (pendopt != NULL);
540}
541
542/*
543 * Print error message about missing string.
544 */
545static 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 */
555public 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 */
565static 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 */
600static 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 */
625public 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 */
657public 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 */
686public 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