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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 555 public lbool isoptpending(void) 556 { 557 return (pendopt != NULL); 558 } 559 560 /* 561 * Print error message about missing string. 562 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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