home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / fmtawksh < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  38.2 KB  |  1,053 lines

  1. #!/usr/local/bin/gawk -f
  2. #!/u/johnd/bin/gawk -f
  3. # @(#) fmtawksh.gawk 1.2 96/04/16
  4. # 95/04/25 john h. dubois iii (john@armory.com)
  5. # 95/05/22 Use stack library.  Try to keep comment/block pairs together.
  6. #          Added all options.
  7. # 96/04/16 Read from /dev/tty for interactive tty input if no filenames.
  8. #          Make filename in footer work correctly again.
  9. #          Changed p option to n; added new p option.
  10. #          Exit if q it typed.
  11.  
  12. # Break awk programs up into blocks, inter-block comments, and any whitespace
  13. # that comes between them.  Try to avoid splitting these; try to keep comments
  14. # with the block that follows it.
  15. # Leading empty blocks (blank lines at the top of the page) are not printed.
  16.  
  17. BEGIN {
  18.     Debug = 0
  19.     PagePos = 0
  20.     PageLen = 66
  21.     FormFeed = sprintf("%c",12)    # formfeed
  22.     Name = "fmtawksh"
  23.     Usage = \
  24.     "Usage: " Name " [-whinsax] [-l<lines>] [-p<page-ranges>] [file ...]"
  25.     ARGC = Opts(Name,Usage,"whinsaxtl>",0)
  26.     if ("h" in Options) {
  27.     printf \
  28. "%s: Format awk, sh, and ksh scripts for printing.\n"\
  29. Usage "\n"\
  30. "%s tries to avoid splitting modules between pages.  Function and\n"\
  31. "interfunction blocks are pushed to the start of a new page if it will\n"\
  32. "prevent them from being split.  If an interfunction block is followed by a\n"\
  33. "function block without any intervening whitespace, %s will try to keep\n"\
  34. "them together, splitting them between pages only if they will not fit on a\n"\
  35. "page together.  Thus, if a function is preceded by comments regarding it,\n"\
  36. "there should be no blank lines within the comments, so that %s will treat\n"\
  37. "the entire comment section as being associated with the function block. \n"\
  38. "If there are blank lines within the comments, put a pound sign on each of\n"\
  39. "the blank lines so that they will not be empty.\n"\
  40. "The start and end of a function block are recognized by simple pattern\n"\
  41. "tests which will fail in some circumstances.  A function block begins with\n"\
  42. "a line that consists of any amount of whitespace, followed by either: \n"\
  43. "* The word 'function' followed by end of line or whitespace (ksh & awk\n"\
  44. "  functions).\n"\
  45. "* A legal identifier followed by a pair of parentheses (sh functions).\n"\
  46. "* A non-commented line that includes an open-brace followed by\n"\
  47. "  end-of-line or the beginning of a comment (awk pattern block).\n"\
  48. "A function block ends when a close-brace is found at the start of a line.\n"\
  49. "An interfunction block is anything that is outside of a function block.\n"\
  50. "Multiple blank lines are squeezed to one and trailing spaces are removed.\n"\
  51. "Options:\n"\
  52. "-h: Print this help.\n"\
  53. "-i: Wait for return to be pressed after printing each page.  This option\n"\
  54. "    requires that filenames be given, rather than letting %s read the\n"\
  55. "    standard input.\n"\
  56. "-n: Print a footer that includes the page number and filename.\n"\
  57. "-l: Set the number of lines per page (default: %d)\n"\
  58. "-w: Complain about lines in an awk program if their type cannot be\n"\
  59. "    determined (specifically, lines that do not appear to be comments nor\n"\
  60. "    part of an awk-block).\n"\
  61. "-a: Recognize only awk function blocks.\n"\
  62. "-s: Recognize only sh/ksh function blocks.\n"\
  63. "-x: Print debugging information.\n",
  64. Name,Name,Name,Name,Name,PageLen
  65.     exit(0)
  66.     }
  67.     Debug = "x" in Options
  68.     if ("l" in Options)
  69.     PageLen = Options["l"]
  70.     # Test exercises most of the code but does not add any formfeeds or
  71.     # delete blank lines, etc., so the output can be compared to the input to
  72.     # test this program.
  73.     Test = "t" in Options
  74.     Quiet = !("w" in Options)
  75.     if (Interactive = ("i" in Options))
  76.     ttyfile = ARGC < 2 ? "/dev/tty" : "/dev/stdin"
  77.     if (Footer = "n" in Options)
  78.     PageLen -= 2
  79.  
  80.     FPat = "^[ \t]*(function($|[ \t])"
  81.     if (!("a" in Options))
  82.     # Add sh function pattern
  83.     FPat = FPat "|[a-zA-Z_][a-zA-Z0-9_]*[ \t]*\\([ \t]*\\)"
  84.     if (!("s" in Options))
  85.     # Add awk pattern block pattern
  86.     FPat = FPat "|[^#].*{[ \t]*(#|$)" #}pair match for preceding open-brace
  87.     FPat = FPat ")"
  88.     if (Debug)
  89.     printf "Function start pattern: %s\n",FPat > "/dev/stderr"
  90.     if (ARGC < 2)
  91.     ProcFile("/dev/stdin")
  92.     else
  93.     for (i = 1; i < ARGC; i++)
  94.         ProcFile(ARGV[i])
  95.     if (PagePos != 0)
  96.     ff(PagePos)
  97. }
  98.  
  99. # Sets globals: FILENAME
  100. function ProcFile(InFile) {
  101.     FILENAME = InFile
  102.     if (Debug)
  103.     print "Processing file: " InFile > "/dev/stderr"
  104.     while (NumLines = GetBlock(InFile)) {
  105.     if (!Test && PagePos == 0 && Block == "") # Discard leading blank lines
  106.         continue
  107.     if (NumLines < 0) {
  108.         # This is a comment block and the next block is a function.
  109.         # Read next block to determine if we can keep the function together
  110.         # with the comments that precede it.
  111.         NumLines = -NumLines    # Determine real number of lines
  112.         CurBlock = Block        # Save current block
  113.         # Since this block is followed by a function block, we are
  114.         # guaranteed that another block can be gotten, and that it will
  115.         # have a positive return value.
  116.         NextLines = GetBlock(InFile)
  117.         if ((NumLines + NextLines) <= PageLen) {
  118.         # This block and the next will fit on a page; conglomerate
  119.         # them so that the rest of the code will have to treat them
  120.         # as a single unit.
  121.         Block = CurBlock "\n" Block
  122.         NumLines += NextLines
  123.         if (Debug)
  124.             print "Conglomerated comment+function block." \
  125.             > "/dev/stderr"
  126.         }
  127.         else {
  128.         # This block and the next won't fit on a single page, so don't
  129.         # try to keep them together.  Push the next block back so it
  130.         # will be read next time.
  131.         if (Debug)
  132.             printf \
  133.             "Comment+function won't fit on page (%d+%d > %d);\n"\
  134.             "pushing function to next page.\n",
  135.             NumLines,NextLines,PageLen > "/dev/stderr"
  136.         PushBlock(NextLines,Block)
  137.         Block = CurBlock
  138.         }
  139.     }
  140.     CurPagePos = PagePos    # Save page pos for use by footer function
  141.     PagePos += NumLines    # Where will we be?
  142.     if (PagePos <= PageLen) {    # Block will fit on this page
  143.         print Block
  144.         # If block exactly reached the end of page...
  145. #        if (PagePos == PageLen) {
  146. #        PagePos = 0
  147. #        ff(PagePos)
  148. #        }
  149.     }
  150.     else {
  151.         # If block won't fit on this page, but will fit on one page,
  152.         # print a formfeed first to push out the current page, so that
  153.         # the block will be printed on the next page.
  154.         if (NumLines <= PageLen) {
  155.         ff(CurPagePos)
  156.         PagePos = NumLines
  157.         }
  158.         if (Block == "")
  159.         # If blank block, we know we did a formfeed, since a single
  160.         # line will always fit on a page.
  161.         # So, don't print the block, and set page pos to 0.
  162.         PagePos = 0
  163.         else {
  164.         if ((Interactive || Footer) && NumLines > PageLen)
  165.             SplitBlock(Block,CurPagePos,PageLen)
  166.         else
  167.             print Block
  168.         PagePos %= PageLen
  169.         }
  170.     }
  171.     }
  172. }
  173.  
  174. # Split a block that would span multiple pages so that we can put footers at
  175. # the bottom of each page.
  176. function SplitBlock(Block,PagePos,PageLen,  Num,i) {
  177.     Num = split(Block,Lines,"\n")
  178.     for (i = 1; i <= Num; i++) {
  179.     if (PagePos == PageLen) {
  180.         ff(PagePos)
  181.         PagePos = 0
  182.     }
  183.     print Lines[i]
  184.     PagePos++
  185.     }
  186. }
  187.  
  188. # Print string s centered on page.
  189. function Center(s) {
  190.     return sprintf("%" int((80-length(s))/2) "s%s","",s)
  191. }
  192.  
  193. # Print a formfeed.  If printing page numbers, print newlines to get to the
  194. # bottom of the page and then print the filename/page number, then the
  195. # formfeed.
  196. function ff(PagePos) {
  197.     if (!Test) {
  198.     if (Footer) {
  199.         while (PagePos++ < PageLen)
  200.         print ""
  201.         # Don't print trailing newline, so that we can print a formfeed
  202.         # even when doing footer printing.  This will hopefully limit the
  203.         # extent of erroneous printing if page position gets out of sync.
  204.         printf "\n%s",Center(sprintf("Page %d of %s",++PageNum,
  205.         FILENAME == "/dev/stdin" ? "stdin" : FILENAME))
  206.     }
  207.     if (Interactive) {
  208.         if ((getline < ttyfile) != 1) {
  209.         printf "Bad read from %s.  Exiting.\n",ttyfile > "/dev/stderr"
  210.         exit 1
  211.         }
  212.         if ($0 ~ /^[qQ]/) {
  213.         print ""
  214.         exit 0
  215.         }
  216.     }
  217.     printf "%s",FormFeed
  218.     }
  219. }
  220.  
  221. # Get next block.  A block is a function block (which may contain blank lines),
  222. # or a text section (generally comments) between blocks, or a blank line
  223. # representing any blank lines between the above types of blocks.
  224. # Text sections are terminated by blank lines or by the start of a function.
  225. # Functions are terminated by a } at the start of a line.
  226. # Multiple contiguous blank lines are squeezed to one newline.
  227. # Trailing whitespace (at the end of a line) is not returned.
  228. # The block is put in global Block.  Every line except the last is followed
  229. # by a newline.  A blank block is thus represented as an empty value of Block.
  230. # The number of lines in the block is returned.  0 is returned at EOF.
  231. # If the block being returned is a comment block, and it was terminated by the
  232. # start of a function (without any intervening whitespace), the line count
  233. # returned is negated.
  234. # Globals used: FPat
  235. function GetBlock(InFile, \
  236. Line,LastBlank,FirstLine,IsBlock,Whitespace,NumLines,Term,Ind) {
  237.     if (Ind = Pop(_PushedBlocks)) {
  238.      Block = _PushedBlocks[Ind]
  239.      Ind = Pop(_BlockLines)
  240.      return _BlockLines[Ind]
  241.     }
  242.     Block = ""
  243.     LastBlank = NumLines = Term = 0
  244.     FirstLine = 1
  245.     while ((Line = ReadLine(InFile)) != "\n") {    # Read until EOF or error
  246.     if (!Test)
  247.         sub("[ \t]+$","",Line)    # Discard trailing whitespace on line
  248.     if (Line == "") {
  249.         if (!Test && LastBlank)    # Discard 2nd & later blank lines
  250.         continue
  251.         else
  252.         LastBlank = 1
  253.     }
  254.     else
  255.         LastBlank = 0
  256.     if (FirstLine) {
  257.         # Determine if this block is whitespace, or is inside or outside
  258.         # of a function block
  259.         if (Line == "")
  260.         Whitespace = 1
  261.         else
  262.         IsBlock = Line ~ FPat
  263.         FirstLine = 0
  264.     }
  265.     else {    # After first line
  266.         if (Whitespace) {
  267.         # All blank lines except the first are discarded, so if we get
  268.         # here, we have found a non-blank line, which means the end
  269.         # of a whitespace block.
  270.         PushLine(Line)
  271.         break
  272.         }
  273.         else if (IsBlock) {    #{pair for following close-brace
  274.         if (Line ~ /^}/) {    # Found end of function block
  275.             Block = Block "\n" Line
  276.             NumLines++
  277.             break
  278.         }
  279.         }
  280.         else { # Accumulating non-whitespace block outside function block
  281.         if (Line ~ FPat) {    # Found start of function?
  282.             # Indicate that block was terminated by the start of a 
  283.             # function.
  284.             if (Debug)
  285.             print \
  286.             "Found comment block followed by function block." \
  287.             > "/dev/stderr"
  288.             Term = 1
  289.             PushLine(Line)
  290.             break
  291.         }
  292.         else if (Line == "") {
  293.             PushLine(Line)
  294.             break
  295.         }
  296.         if (!Quiet && Line !~ /^[ \t]*#/)
  297.             printf "Warning: at line %d: non-comment outside of "\
  298.             "block:\n%s\n",NR,Line > "/dev/stderr"
  299.         }
  300.     }
  301.     if (Block != "")
  302.         Block = Block "\n" Line
  303.     else
  304.         Block = Line
  305.     NumLines++
  306.     }
  307.     if (Debug)
  308.     printf "Returning %d-line block.\n",NumLines > "/dev/stderr"
  309.     if (Term)
  310.     return -NumLines
  311.     else
  312.     return NumLines
  313. }
  314.  
  315. # Return the next line read from InFile (without newline).
  316. # A single newline is returned at EOF or on error.
  317. function ReadLine(InFile,  Line,Ind) {
  318.     if (Ind = Pop(_PushedLines))
  319.     return _PushedLines[Ind]
  320.     else {
  321.     if ((getline Line < InFile) != 1)
  322.         return "\n"
  323.     else {
  324.         if (Debug)
  325.         printf "Returning line: %s\n",Line > "/dev/stderr"
  326.         return Line
  327.     }
  328.     }
  329. }
  330.  
  331. # Push a line back onto the input.
  332. # Uses global stack _PushedLines[].
  333. function PushLine(Line) {
  334.     Push(_PushedLines,Line)
  335. }
  336.  
  337. # Push a block back onto the input.
  338. # Uses global stack _PushedBlocks[] (block text) and _BlockLines[] (number
  339. # of lines in the block).
  340. function PushBlock(Lines,Block) {
  341.     Push(_PushedBlocks,Block)
  342.     Push(_BlockLines,Lines)
  343. }
  344.  
  345. # Push element Elem onto stack Stack
  346. # Pushed data is saved in Stack[] starting at index 1.
  347. # The stack pointer is stored at index "ptr"; it points to top of the
  348. # stack (the last element used).
  349. function Push(Stack,Elem) {
  350.     Stack[++Stack["ptr"]] = Elem
  351.     if (Debug)
  352.     printf "Pushed element %d: %s\n",Stack["ptr"],
  353.     Stack[Stack["ptr"]] > "/dev/stderr"
  354. }
  355.  
  356. # Pops the top element off of the stack and returns the index of it in Stack.
  357. # If the stack is empty, returns 0.
  358. # NOTE: The data stored at a particular position on the stack is lost when
  359. # either a value is pushed onto its position or the NEXT value is popped.
  360. function Pop(Stack,  Pointer) {
  361.     if ((Pointer = Stack["ptr"]+0) > 0) {
  362.     # If there was data in the last position, delete it.
  363.     delete Stack[Pointer+1]
  364.     Stack["ptr"]--
  365.     }
  366.     return Pointer
  367. }
  368.  
  369. ### Start of ProcArgs library
  370. # @(#) ProcArgs 1.11 96/12/08
  371. # 92/02/29 john h. dubois iii (john@armory.com)
  372. # 93/07/18 Added "#" arg type
  373. # 93/09/26 Do not count -h against MinArgs
  374. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  375. #          Removed meaning of "+" or "-" by itself.
  376. # 94/03/08 Added & option and *()< option types.
  377. # 94/04/02 Added NoRCopt to Opts()
  378. # 94/06/11 Mark numeric variables as such.
  379. # 94/07/08 Opts(): Do not require any args if h option is given.
  380. # 95/01/22 Record options given more than once.  Record option num in argv.
  381. # 95/06/08 Added ExclusiveOptions().
  382. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  383. #          Expand $VARNAME at the start of its filenames.
  384. #          Let varname=0 and -option- turn off an option.
  385. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  386. #          of the vars should be searched for in the environment.
  387. #          Check for duplicate rcfiles.
  388. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  389. #          now return various negatives values on error, not just -1, and
  390. #          Opts() may set Err to various positive values, not just 1.
  391. #          Added AllowUnrecOpt.
  392. # 96/05/23 Check type given for & option
  393. # 96/06/15 Re-port to awk
  394. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  395. #          used by other functions.
  396. # 96/10/15 Added OptChars
  397. # 96/11/01 Added exOpts arg to Opts()
  398. # 96/11/16 Added ; type
  399. # 96/12/08 Added Opt2Set() & Opt2Sets()
  400. # 96/12/27 Added CmdLineOpt()
  401.  
  402. # optlist is a string which contains all of the possible command line options.
  403. # A character followed by certain characters indicates that the option takes
  404. # an argument, with type as follows:
  405. # :    String argument
  406. # ;    Non-empty string argument
  407. # *    Floating point argument
  408. # (    Non-negative floating point argument
  409. # )    Positive floating point argument
  410. # #    Integer argument
  411. # <    Non-negative integer argument
  412. # >    Positive integer argument
  413. # The only difference the type of argument makes is in the runtime argument
  414. # error checking that is done.
  415.  
  416. # The & option is a special case used to get numeric options without the
  417. # user having to give an option character.  It is shorthand for [-+.0-9].
  418. # If & is included in optlist and an option string that begins with one of
  419. # these characters is seen, the value given to "&" will include the first
  420. # char of the option.  & must be followed by a type character other than ":"
  421. # or ";".
  422. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  423.  
  424. # Strings in argv[] which begin with "-" or "+" are taken to be
  425. # strings of options, except that a string which consists solely of "-"
  426. # or "+" is taken to be a non-option string; like other non-option strings,
  427. # it stops the scanning of argv and is left in argv[].
  428. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  429. # If an option takes an argument, the argument may either immediately
  430. # follow it or be given separately.
  431. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  432. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  433. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  434. # this feature had a flaw that caused problems in some cases.  See the OptChars
  435. # parameter to explicitly set the option-specifier characters.
  436.  
  437. # If an option that does not take an argument is given,
  438. # an index with its name is created in Options and its value is set to the
  439. # number of times it occurs in argv[].
  440.  
  441. # If an option that does take an argument is given, an index with its name is
  442. # created in Options and its value is set to the value of the argument given
  443. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  444. # If an option that takes an argument is given more than once,
  445. # Options[option-name,"count"] is incremented, and the value is assigned to
  446. # the index (option-name,instance) where instance is 2 for the second occurance
  447. # of the option, etc.
  448. # In other words, the first time an option with a value is encountered, the
  449. # value is assigned to an index consisting only of its name; for any further
  450. # occurances of the option, the value index has an extra (count) dimension.
  451.  
  452. # The sequence number for each option found in argv[] is stored in
  453. # Options[option-name,"num",instance], where instance is 1 for the first
  454. # occurance of the option, etc.  The sequence number starts at 1 and is
  455. # incremented for each option, both those that have a value and those that
  456. # do not.  Options set from a config file have a value of 0 assigned to this.
  457.  
  458. # Options and their arguments are deleted from argv.
  459. # Note that this means that there may be gaps left in the indices of argv[].
  460. # If compress is nonzero, argv[] is packed by moving its elements so that
  461. # they have contiguous integer indices starting with 0.
  462. # Option processing will stop with the first unrecognized option, just as
  463. # though -- was given except that unlike -- the unrecognized option will not be
  464. # removed from ARGV[].  Normally, an error value is returned in this case.
  465. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  466. # be found, so the number of remaining arguments is returned instead.
  467. # If OptChars is not a null string, it is the set of characters that indicate
  468. # that an argument is an option string if the string begins with one of the
  469. # characters.  A string consisting solely of two of the same option-indicator
  470. # characters stops the scanning of argv[].  The default is "-+".
  471. # argv[0] is not examined.
  472. # The number of arguments left in argc is returned.
  473. # If an error occurs, the global string OptErr is set to an error message
  474. # and a negative value is returned.
  475. # Current error values:
  476. # -1: option that required an argument did not get it.
  477. # -2: argument of incorrect type supplied for an option.
  478. # -3: unrecognized (invalid) option.
  479. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  480. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  481. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  482. {
  483. # ArgNum is the index of the argument being processed.
  484. # ArgsLeft is the number of arguments left in argv.
  485. # Arg is the argument being processed.
  486. # ArgLen is the length of the argument being processed.
  487. # ArgInd is the position of the character in Arg being processed.
  488. # Option is the character in Arg being processed.
  489. # Pos is the position in OptList of the option being processed.
  490. # NumOpt is true if a numeric option may be given.
  491.     ArgsLeft = argc
  492.     NumOpt = index(OptList,"&")
  493.     OptionNum = 0
  494.     if (OptChars == "")
  495.     OptChars = "-+"
  496.     while (OptChars != "") {
  497.     c = substr(OptChars,1,1)
  498.     OptChars = substr(OptChars,2)
  499.     OptCharSet[c]
  500.     OptTerm[c c]
  501.     }
  502.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  503.     Arg = argv[ArgNum]
  504.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  505.         break    # Not an option; quit
  506.     if (Arg in OptTerm) {
  507.         delete argv[ArgNum]
  508.         ArgsLeft--
  509.         break
  510.     }
  511.     ArgLen = length(Arg)
  512.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  513.         Option = substr(Arg,ArgInd,1)
  514.         if (NumOpt && Option ~ /[-+.0-9]/) {
  515.         # If this option is a numeric option, make its flag be & and
  516.         # its option string flag position be the position of & in
  517.         # the option string.
  518.         Option = "&"
  519.         Pos = NumOpt
  520.         # Prefix Arg with a char so that ArgInd will point to the
  521.         # first char of the numeric option.
  522.         Arg = "&" Arg
  523.         ArgLen++
  524.         }
  525.         # Find position of flag in option string, to get its type (if any).
  526.         # Disallow & as literal flag.
  527.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  528.         if (AllowUnrecOpt) {
  529.             Escape = 1
  530.             break
  531.         }
  532.         else {
  533.             OptErr = "Invalid option: " specGiven Option
  534.             return -3
  535.         }
  536.         }
  537.  
  538.         # Find what the value of the option will be if it takes one.
  539.         # NeedNextOpt is true if the option specifier is the last char of
  540.         # this arg, which means that if the option requires a value it is
  541.         # the next arg.
  542.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  543.         if (GotValue = ArgNum + 1 < argc)
  544.             Value = argv[ArgNum+1]
  545.         }
  546.         else {    # Value is included with option
  547.         Value = substr(Arg,ArgInd + 1)
  548.         GotValue = 1
  549.         }
  550.  
  551.         if (HadValue = AssignVal(Option,Value,Options,
  552.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  553.         specGiven)) {
  554.         if (HadValue < 0)    # error occured
  555.             return HadValue
  556.         if (HadValue == 2)
  557.             ArgInd++    # Account for the single-char value we used.
  558.         else {
  559.             if (NeedNextOpt) {    # option took next arg as value
  560.             delete argv[++ArgNum]
  561.             ArgsLeft--
  562.             }
  563.             break    # This option has been used up
  564.         }
  565.         }
  566.     }
  567.     if (Escape)
  568.         break
  569.     # Do not delete arg until after processing of it, so that if it is not
  570.     # recognized it can be left in ARGV[].
  571.     delete argv[ArgNum]
  572.     ArgsLeft--
  573.     }
  574.     if (compress != 0) {
  575.     dest = 1
  576.     src = argc - ArgsLeft + 1
  577.     for (count = ArgsLeft - 1; count; count--) {
  578.         ARGV[dest] = ARGV[src]
  579.         dest++
  580.         src++
  581.     }
  582.     }
  583.     return ArgsLeft
  584. }
  585.  
  586. # Assignment to values in Options[] occurs only in this function.
  587. # Option: Option specifier character.
  588. # Value: Value to be assigned to option, if it takes a value.
  589. # Options[]: Options array to return values in.
  590. # ArgType: Argument type specifier character.
  591. # GotValue: Whether any value is available to be assigned to this option.
  592. # Name: Name of option being processed.
  593. # OptionNum: Number of this option (starting with 1) if set in argv[],
  594. #     or 0 if it was given in a config file or in the environment.
  595. # SingleOpt: true if the value (if any) that is available for this option was
  596. #     given as part of the same command line arg as the option.  Used only for
  597. #     options from the command line.
  598. # specGiven is the option specifier character use, if any (e.g. - or +),
  599. # for use in error messages.
  600. # Global variables: OptErr
  601. # Return value: negative value on error, 0 if option did not require an
  602. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  603. # the arg.
  604. # Current error values:
  605. # -1: Option that required an argument did not get it.
  606. # -2: Value of incorrect type supplied for option.
  607. # -3: Bad type given for option &
  608. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  609. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  610.     # If option takes a value...    [
  611.     NumTypes = "*()#<>]"
  612.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  613.     OptErr = "Bad type given for & option"
  614.     return -3
  615.     }
  616.  
  617.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  618.     if (!GotValue) {
  619.         if (Name != "")
  620.         OptErr = "Variable requires a value -- " Name
  621.         else
  622.         OptErr = "option requires an argument -- " Option
  623.         return -1
  624.     }
  625.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  626.         OptErr = Err
  627.         return -2
  628.     }
  629.     # Mark this as a numeric variable; will be propogated to Options[] val.
  630.     if (ArgType != ":" && ArgType != ";")
  631.         Value += 0
  632.     if ((Instance = ++Options[Option,"count"]) > 1)
  633.         Options[Option,Instance] = Value
  634.     else
  635.         Options[Option] = Value
  636.     }
  637.     # If this is an environ or rcfile assignment & it was given a value...
  638.     else if (!OptionNum && Value != "") {
  639.     UsedValue = 1
  640.     # If the value is "0" or "-" and this is the first instance of it,
  641.     # do not set Options[Option]; this allows an assignment in an rcfile to
  642.     # turn off an option (for the simple "Option in Options" test) in such
  643.     # a way that it cannot be turned on in a later file.
  644.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  645.         Instance = 1
  646.     else
  647.         Instance = ++Options[Option]
  648.     # Save the value even though this is a flag
  649.     Options[Option,Instance] = Value
  650.     }
  651.     # If this is a command line flag and has a - following it in the same arg,
  652.     # it is being turned off.
  653.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  654.     UsedValue = 2
  655.     if (Option in Options)
  656.         Instance = ++Options[Option]
  657.     else
  658.         Instance = 1
  659.     Options[Option,Instance]
  660.     }
  661.     # If this is a flag assignment without a value, increment the count for the
  662.     # flag unless it was turned off.  The indicator for a flag being turned off
  663.     # is that the flag index has not been set in Options[] but it has an
  664.     # instance count.
  665.     else if (Option in Options || !((Option,1) in Options))
  666.     # Increment number of times this flag seen; will inc null value to 1
  667.     Instance = ++Options[Option]
  668.     Options[Option,"num",Instance] = OptionNum
  669.     return UsedValue
  670. }
  671.  
  672. # Option is the option letter
  673. # Value is the value being assigned
  674. # Name is the var name of the option, if any
  675. # ArgType is one of:
  676. # :    String argument
  677. # ;    Non-null string argument
  678. # *    Floating point argument
  679. # (    Non-negative floating point argument
  680. # )    Positive floating point argument
  681. # #    Integer argument
  682. # <    Non-negative integer argument
  683. # >    Positive integer argument
  684. # specGiven is the option specifier character use, if any (e.g. - or +),
  685. # for use in error messages.
  686. # Returns null on success, err string on error
  687. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  688.     if (ArgType == ":")
  689.     return ""
  690.     if (ArgType == ";") {
  691.     if (Value == "")
  692.         Err = "must be a non-empty string"
  693.     }
  694.     # A number begins with optional + or -, and is followed by a string of
  695.     # digits or a decimal with digits before it, after it, or both
  696.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  697.     Err = "must be a number"
  698.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  699.     Err = "may not include a fraction"
  700.     else if (ArgType ~ "[()<>]" && Value < 0)
  701.     Err = "may not be negative"
  702.     # (
  703.     else if (ArgType ~ "[)>]" && Value == 0)
  704.     Err = "must be a positive number"
  705.     if (Err != "") {
  706.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  707.     if (Name != "")
  708.         return ErrStr "variable " substr(Name,1,1) " " Err
  709.     else {
  710.         if (Option == "&")
  711.         Option = Value
  712.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  713.     }
  714.     }
  715.     else
  716.     return ""
  717. }
  718.  
  719. # Note: only the above functions are needed by ProcArgs.
  720. # The rest of these functions call ProcArgs() and also do other
  721. # option-processing stuff.
  722.  
  723. # Opts: Process command line arguments.
  724. # Opts processes command line arguments using ProcArgs()
  725. # and checks for errors.  If an error occurs, a message is printed
  726. # and the program is exited.
  727. #
  728. # Input variables:
  729. # Name is the name of the program, for error messages.
  730. # Usage is a usage message, for error messages.
  731. # OptList the option description string, as used by ProcArgs().
  732. # MinArgs is the minimum number of non-option arguments that this
  733. # program should have, non including ARGV[0] and +h.
  734. # If the program does not require any non-option arguments,
  735. # MinArgs should be omitted or given as 0.
  736. # rcFiles, if given, is a colon-seprated list of filenames to read for
  737. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  738. # by the value of the environment variable HOME.  If a filename begins with
  739. # $, the part from the character after the $ up until (but not including)
  740. # the first character not in [a-zA-Z0-9_] will be searched for in the
  741. # environment; if found its value will be substituted, if not the filename will
  742. # be discarded.
  743. # rcfiles are read in the order given.
  744. # Values given in them will not override values given on the command line,
  745. # and values given in later files will not override those set in earlier
  746. # files, because AssignVal() will store each with a different instance index.
  747. # The first instance of each variable, either on the command line or in an
  748. # rcfile, will be stored with no instance index, and this is the value
  749. # normally used by programs that call this function.
  750. # VarNames is a comma-separated list of variable names to map to options,
  751. # in the same order as the options are given in OptList.
  752. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  753. # searched for in the environment.  If set to -1, all values will be searched
  754. # for in the environment.  Values given in the environment will override
  755. # those given in the rcfiles but not those given on the command line.
  756. # NoRCopt, if given, is an additional letter option that if given on the
  757. # command line prevents the rcfiles from being read.
  758. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  759. # ExclusiveOptions() for a description of exOpts.
  760. # Special options:
  761. # If x is made an option and is given, some debugging info is output.
  762. # h is assumed to be the help option.
  763.  
  764. # Global variables:
  765. # The command line arguments are taken from ARGV[].
  766. # The arguments that are option specifiers and values are removed from
  767. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  768. # The number of elements in ARGV[] should be in ARGC.
  769. # After processing, ARGC is set to the number of elements left in ARGV[].
  770. # The option values are put in Options[].
  771. # On error, Err is set to a positive integer value so it can be checked for in
  772. # an END block.
  773. # Return value: The number of elements left in ARGV is returned.
  774. # Must keep OptErr global since it may be set by InitOpts().
  775. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  776. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  777.     if (MinArgs == "")
  778.     MinArgs = 0
  779.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  780.     optChars)
  781.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  782.     if (ArgsLeft >= 0) {
  783.         OptErr = "Not enough arguments"
  784.         Err = 4
  785.     }
  786.     else
  787.         Err = -ArgsLeft
  788.     printf "%s: %s.\nUse -h for help.\n%s\n",
  789.     Name,OptErr,Usage > "/dev/stderr"
  790.     exit 1
  791.     }
  792.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  793.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  794.     {
  795.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  796.     Err = -e
  797.     exit 1
  798.     }
  799.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  800.     {
  801.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  802.     Err = 1
  803.     exit 1
  804.     }
  805.     return ArgsLeft
  806. }
  807.  
  808. # ReadConfFile(): Read a file containing var/value assignments, in the form
  809. # <variable-name><assignment-char><value>.
  810. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  811. # line and whitespace between the variable name and the assignment character) 
  812. # is stripped.  Lines that do not contain an assignment operator or which
  813. # contain a null variable name are ignored, other than possibly being noted in
  814. # the return value.  If more than one assignment is made to a variable, the
  815. # first assignment is used.
  816. # Input variables:
  817. # File is the file to read.
  818. # Comment is the line-comment character.  If it is found as the first non-
  819. #     whitespace character on a line, the line is ignored.
  820. # Assign is the assignment string.  The first instance of Assign on a line
  821. #     separates the variable name from its value.
  822. # If StripWhite is true, whitespace around the value (whitespace between the
  823. #     assignment char and trailing whitespace on the line) is stripped.
  824. # VarPat is a pattern that variable names must match.  
  825. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  826. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  827. #     a line; no assignment operator is needed.  These variables are set in
  828. #     the output array with a null value.  Lines containing nothing but
  829. #     whitespace are still ignored.
  830. # Output variables:
  831. # Values[] contains the assignments, with the indexes being the variable names
  832. #     and the values being the assigned values.
  833. # Lines[] contains the line number that each variable occured on.  A flag set
  834. #     is record by giving it an index in Lines[] but not in Values[].
  835. # Return value:
  836. # If any errors occur, a string consisting of descriptions of the errors
  837. # separated by newlines is returned.  In no case will the string start with a
  838. # numeric value.  If no errors occur,  the number of lines read is returned.
  839. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  840. FlagsOK,
  841. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  842.     if (Comment != "")
  843.     Comment = "^" Comment
  844.     AssignLen = length(Assign)
  845.     if (VarPat == "")
  846.     VarPat = "."    # null varname not allowed
  847.     while ((Status = (getline Line < File)) == 1) {
  848.     LineNum++
  849.     sub("^[ \t]+","",Line)
  850.     if (Line == "")        # blank line
  851.         continue
  852.     if (Comment != "" && Line ~ Comment)
  853.         continue
  854.     if (Pos = index(Line,Assign)) {
  855.         Var = substr(Line,1,Pos-1)
  856.         Val = substr(Line,Pos+AssignLen)
  857.         if (StripWhite) {
  858.         sub("^[ \t]+","",Val)
  859.         sub("[ \t]+$","",Val)
  860.         }
  861.     }
  862.     else {
  863.         Var = Line    # If no value, var is entire line
  864.         Val = ""
  865.     }
  866.     if (!FlagsOK && Val == "") {
  867.         Errs = Errs \
  868.         sprintf("\nBad assignment on line %d of file %s: %s",
  869.         LineNum,File,Line)
  870.         continue
  871.     }
  872.     sub("[ \t]+$","",Var)
  873.     if (Var !~ VarPat) {
  874.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  875.         LineNum,File,Var)
  876.         continue
  877.     }
  878.     if (!(Var in Lines)) {
  879.         Lines[Var] = LineNum
  880.         if (Pos)
  881.         Values[Var] = Val
  882.     }
  883.     }
  884.     if (Status)
  885.     Errs = Errs "\nCould not read file " File
  886.     close(File)
  887.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  888. }
  889.  
  890. # Variables:
  891. # Data is stored in Options[].
  892. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  893. # Global vars:
  894. # Sets OptErr.  Uses ENVIRON[].
  895. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  896. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  897. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  898. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  899.     split("",filesRead,"")    # make awk know this is an array
  900.     NumVars = split(VarNames,Vars,",")
  901.     TypesInd = Ret = 0
  902.     if (EnvSearch == -1)
  903.     EnvSearch = NumVars
  904.     for (i = 1; i <= NumVars; i++) {
  905.     Var = Vars[i]
  906.     CharOpt = substr(OptList,++TypesInd,1)
  907.     if (CharOpt ~ "^[:;*()#<>&]$")
  908.         CharOpt = substr(OptList,++TypesInd,1)
  909.     Map[Var] = CharOpt
  910.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  911.     # Do not overwrite entries from environment
  912.     if (i <= EnvSearch && Var in ENVIRON &&
  913.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  914.         return Err
  915.     }
  916.  
  917.     numrcFiles = split(rcFiles,fNames,":")
  918.     for (i = 1; i <= numrcFiles; i++) {
  919.     rcFile = fNames[i]
  920.     if (rcFile ~ "^~/")
  921.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  922.     else if (rcFile ~ /^\$/) {
  923.         rcFile = substr(rcFile,2)
  924.         match(rcFile,"^[a-zA-Z0-9_]*")
  925.         envvar = substr(rcFile,1,RLENGTH)
  926.         if (envvar in ENVIRON)
  927.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  928.         else
  929.         continue
  930.     }
  931.     if (rcFile in filesRead)
  932.         continue
  933.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  934.     # may be the same
  935.     filesRead[rcFile]
  936.     if ("x" in Options)
  937.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  938.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  939.     if (retStr > 0)
  940.         READ_RCFILE = 1
  941.     else if (ret != "") {
  942.         OptErr = retStr
  943.         Ret = -1
  944.     }
  945.     for (Var in Lines)
  946.         if (Var in Map) {
  947.         if ((Err = AssignVal(Map[Var],
  948.         Var in Values ? Values[Var] : "",Options,Types[Var],
  949.         Var in Values,Var,0)) < 0)
  950.             return Err
  951.         }
  952.         else {
  953.         OptErr = sprintf(\
  954.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  955.         Lines[Var],rcFile)
  956.         Ret = -1
  957.         }
  958.     }
  959.  
  960.     if ("x" in Options)
  961.     for (Var in Map)
  962.         if (Map[Var] in Options)
  963.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  964.         "/dev/stderr"
  965.         else
  966.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  967.     return Ret
  968. }
  969.  
  970. # OptSets is a semicolon-separated list of sets of option sets.
  971. # Within a list of option sets, the option sets are separated by commas.  For
  972. # each set of sets, if any option in one of the sets is in Options[] AND any
  973. # option in one of the other sets is in Options[], an error string is returned.
  974. # If no conflicts are found, nothing is returned.
  975. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  976. # the exclusions presented by the first set of sets (ab,def,g) if:
  977. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  978. # (a or b is in Options[]) AND (g is in Options) OR
  979. # (d, e, or f is in Options[]) AND (g is in Options)
  980. # An error will be returned due to the exclusions presented by the second set
  981. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  982. # todo: make options given on command line unset options given in config file
  983. # todo: that they conflict with.
  984. function ExclusiveOptions(OptSets,Options,
  985. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  986. SetNum,OSetNum) {
  987.     NumSetSets = split(OptSets,SetSets,";")
  988.     # For each set of sets...
  989.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  990.     # NumSets is the number of sets in this set of sets.
  991.     NumSets = split(SetSets[SetSet],Sets,",")
  992.     # For each set in a set of sets except the last...
  993.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  994.         s1 = Sets[SetNum]
  995.         L1 = length(s1)
  996.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  997.         # If any of the options in this set was given, check whether
  998.         # any of the options in the other sets was given.  Only check
  999.         # later sets since earlier sets will have already been checked
  1000.         # against this set.
  1001.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1002.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1003.             s2 = Sets[OSetNum]
  1004.             L2 = length(s2)
  1005.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1006.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1007.                 ErrStr = ErrStr "\n"\
  1008.                 sprintf("Cannot give both %s and %s options.",
  1009.                 c1,c2)
  1010.             }
  1011.     }
  1012.     }
  1013.     if (ErrStr != "")
  1014.     return substr(ErrStr,2)
  1015.     return ""
  1016. }
  1017.  
  1018. # The value of each instance of option Opt that occurs in Options[] is made an
  1019. # index of Set[].
  1020. # The return value is the number of instances of Opt in Options.
  1021. function Opt2Set(Options,Opt,Set,  count) {
  1022.     if (!(Opt in Options))
  1023.     return 0
  1024.     Set[Options[Opt]]
  1025.     count = Options[Opt,"count"]
  1026.     for (; count > 1; count--)
  1027.     Set[Options[Opt,count]]
  1028.     return count
  1029. }
  1030.  
  1031. # The value of each instance of option Opt that occurs in Options[] that
  1032. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1033. # Other values are made indexes of Set[].
  1034. # The return value is the number of instances of Opt in Options.
  1035. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1036.     ret = Opt2Set(Options,Opt,aSet)
  1037.     for (value in aSet)
  1038.     if (substr(value,1,1) == "!")
  1039.         nSet[substr(value,2)]
  1040.     else
  1041.         Set[value]
  1042.     return ret
  1043. }
  1044.  
  1045. # Returns true if option Opt was given on the command line.
  1046. function CmdLineOpt(Options,Opt,  i) {
  1047.     for (i = 1; (Opt,"num",i) in Options; i++)
  1048.     if (Options[Opt,"num",i] != 0)
  1049.         return 1
  1050.     return 0
  1051. }
  1052. ### End of ProcArgs library
  1053.