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