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

  1. #!/usr/local/bin/gawk -f
  2. #!/usr/bin/awk -f
  3. # @(#) libupdate.awk 1.0 96/11/04
  4. # 96/11/04 john h. dubois iii (john@armory.com)
  5.  
  6. # Use of @(\#) is to prevent these strings from being recognized by programs
  7. # run on this text file
  8. BEGIN {
  9.     Name = "libupdate"
  10.     Usage = \
  11. "Usage: " Name \
  12. " [-hdSqBrpPNRxn] [-s<string>] [-b<begin-line>] [-e<end-line>]\n"\
  13. "       [-E<ext>] [-V<version-string>] [-[cCM]<compare-libs>] newlib [file ...]"
  14.     rcFile = ".libupdate"
  15.     # Used: bBcCdefFEhmMNpPqrRsSuUvVWx
  16.     ARGC = Opts(Name,Usage,"BE;s:SdrqRhxb;e;V;c:C;pPNM;u;U;",0,
  17.     "~/" rcFile ":$UHOME/" rcFile,"NOBACKUP,EXTENSION,STARTSTRING,"\
  18.     "NOSTARTSTRING,NOCHECK,REQVERSION,QUIET,REGEX",
  19.     0,"n",0,"",
  20.     "s,S;d,Vr;c,C;p,VrCd;P,EVrp;N,cCEVr;M,cCpsSbeRVr;uU,cCMBEsSbeRdVrpPN;u,U")
  21.     startString = "### "
  22.     if ("h" in Options) {
  23.     printf \
  24. "%s: Update libraries included in the text of interpreted programs.\n"\
  25. "%s\n"\
  26. "     newlib is the filename of a library to substitute into each of the\n"\
  27. "other named files as a replacement of an older version of the library.\n"\
  28. "The substitution is done by getting the first and last lines of newlib,\n"\
  29. "finding the identical copies of those lines in the file being processed,\n"\
  30. "and replacing the lines and everything between them with contents of the\n"\
  31. "new library.  The first and last lines must be different and there must be\n"\
  32. "only one instance of each in each processed file.  Both the first and last\n"\
  33. "lines of the library are normally required to begin with the string\n"\
  34. "\"%s\" as a sanity check; this can be changed with the -s option.\n"\
  35. "     If newlib contains an SCCS string of the form\n"\
  36. "\"@(\#) <libname> <version-#> <date><end-of-line>\", then each replaced\n"\
  37. "library will be searched for an SCCS string that begins with\n"\
  38. "\"@(\#) <libname>\".  If such a string is found and the version number or\n"\
  39. "date is later than that in the replacement library, a warning is issued\n"\
  40. "and the file is not updated.  This can be turned off with the -d option.\n"\
  41. "<date> should have the form year/month/day.  <version-#> should contain a\n"\
  42. "sequence of numeric strings separated by '.' characters.  Each pair of\n"\
  43. "numeric strings is compared (as integers) separately, with strings on the\n"\
  44. "left having higher value.\n"\
  45. "     A copy of each file is put in filename- when the file is processed.\n"\
  46. "If no filenames are given, a single file is read from the standard input\n"\
  47. "and the modified version is written to the standard output.\n"\
  48. "Options:\n"\
  49. "Some of the following options can also be set by assigning values to\n"\
  50. "variables in a configuration file named %s.  This file is searched for\n"\
  51. "first in the home directory of the invoking user and then, if the\n"\
  52. "environment variable UHOME is set, in the directory specified by that\n"\
  53. "variable.  If both files exist, variables assignments in the first file\n"\
  54. "override those in the second file.  Variables are assigned to with the\n"\
  55. "syntax:  varname=value  or in the case of flags, by simply putting the\n"\
  56. "indicated variable name in the file without a value.  To turn off a flag\n"\
  57. "option in the first file and prevent it from being set in the second file,\n"\
  58. "assign it a value of 0.  e.g. if FOO is set in the first file, FOO=0 in\n"\
  59. "the second file will override it.  Flag options can be turned off on the\n"\
  60. "command line by following them immediately with \"-\", e.g. -v- to turn\n"\
  61. "off the v option in such a way that it cannot be turned on in a config\n"\
  62. "file.  Variable names appear in parentheses in the option descriptions.\n"\
  63. "-h: Print this help.\n"\
  64. "-c<compare-libs>: Compare the library found in each file line-for-line to\n"\
  65. "    the the contents of the library files given in the colon- or\n"\
  66. "    whitespace-separated list <compare-libs>.  If the library in a file is\n"\
  67. "    not exactly the same as that in one of the compare-libs, a complaint\n"\
  68. "    is issued and no update is done.\n"\
  69. "-C<compare-libs>: Like -c, except that the only result of a lack of match\n"\
  70. "    is the complaint; the update is still done.\n"\
  71. "-M<compare-libs>: Like -c, except that no checks for matching first or\n"\
  72. "    last line are done.  Instead, each file to be processed is searched\n"\
  73. "    until a sequence of lines matching that in one of the given files is\n"\
  74. "    found.  That set of lines is then replaced with the new library.  The\n"\
  75. "    search done by this option may take a long time.\n"\
  76. "-B: Do not makes backup copies of processed files.  (NOBACKUP)\n"\
  77. "-E<ext>: Instead of making backup copies, write updated files to the same\n"\
  78. "    filename with <ext> appended.  (EXTENSION)\n"\
  79. "-x: Turn on debugging.\n"\
  80. "-s<string>: Require the first and last lines of newlib to start with\n"\
  81. "    <string>.  (STARTSTRING)\n"\
  82. "-S: Do not require the first and last lines of newlib to start with any\n"\
  83. "    special string; equivalent to -s \"\".  (NOSTARTSTRING)\n"\
  84. "-b<begin-line>: Use <begin-line> as the starting line of the library to\n"\
  85. "    search for in each file.\n"\
  86. "-e<end-line>: Use <end-line> as the ending line of the library to search\n"\
  87. "    for in each file.\n"\
  88. "If multiple -b or -e options are given, each will be searched for.\n"\
  89. "-R: Treat the strings given with -b and -e as unanchored regular\n"\
  90. "    expressions style of egrep rather than as fixed strings.  (REGEX)\n"\
  91. "-d: Do not check library version numbers or dates.  (NOCHECK)\n"\
  92. "-V<version-string>: Use <version-string> as the source of the new library\n"\
  93. "    version number and date instead of reading it from the new library.\n"\
  94. "    It should have three words: <libname> <version-#> <date>\n"\
  95. "-r: Require replaced libraries to have a version string for comparison.\n"\
  96. "    This also requires the new library to have a version string, unless\n"\
  97. "    -V is given.  (REQVERSION)\n"\
  98. "-n: Do not read any configuration files.\n"\
  99. "-q: Quiet.  Turns off some messages.  (QUIET)\n"\
  100. "The following options cause %s to generate output based on the\n"\
  101. "contents of each file rather than updating them.  If any is given with\n"\
  102. "both -b and -e, then no <newlib> should be given; i.e. the first filename\n"\
  103. "argument to %s should be the first file to be processed rather than a\n"\
  104. "library file.  The same is true if -M is given with -P or -N.\n"\
  105. "-p: Print the library contained in a file.  If -E is also given, the\n"\
  106. "    library is written to a filename as described for the -E option.  If\n"\
  107. "    not, the library is written to the standard output; in this case, only\n"\
  108. "    one source filename should be given.  The -d option is always turned\n"\
  109. "    on.  If -c is given with -p, the meaning of the -c option is inverted:\n"\
  110. "    output will be produced only if the library contained in a file is NOT\n"\
  111. "    identical to any of the named libraries.  Additionally, each library\n"\
  112. "    read from a file will be added to the set of libraries for comparison,\n"\
  113. "    so only one instance of each unique library will be output.  An empty\n"\
  114. "    string may be given to -c to turn on this behaviour without having to\n"\
  115. "    name any already-existing libraries for comparison.\n"\
  116. "-P: Print the version string found in each file.\n"\
  117. "-N: Print the names of files that no library is found in.\n"\
  118. "-u<name>: Search the entirity of each file for an SCCS string with the\n"\
  119. "    format described above and the name <name>.  For the first such string\n"\
  120. "    found in each file, a line with this format is printed:\n"\
  121. "    filename version date line-number\n"\
  122. "-U<name>: Like -u, except that only the first instance of each version\n"\
  123. "    found is printed.\n",
  124.     Name,Usage,startString,rcFile,Name,Name
  125.     exit 0
  126.     }
  127.     if ("U" in Options) {
  128.     vName = Options["U"]
  129.     uniqueVersion = 1
  130.     }
  131.     if ("u" in Options)
  132.     vName = Options["u"]
  133.     if (Debug = "x" in Options)
  134.     printf "Debugging is on.\n"
  135.     numFiles = ARGC - 1
  136.     argNum = 1
  137.     if ((regEx = "R" in Options) && Options["R","num",1] != 0 && \
  138.     !("b" in Options || "e" in Options)) {
  139.     # If -R given on command line w/o -b or -e...
  140.     printf "%s: Must give -b or -e if -R is given.  Exiting.\n",
  141.     Name > "/dev/stderr"
  142.     exit 1
  143.     }
  144.     matchLibs = "M" in Options
  145.     printLib = "p" in Options
  146.     printLibVersion = "P" in Options
  147.     noLib = "N" in Options
  148.     printOnly = printLib || printLibVersion || noLib
  149.     if (!(printOnly && ("b" in Options && "e" in Options || "M" in Options) \
  150.     || vName != "")) {
  151.     if (!numFiles) {
  152.         printf "%s: No library named.  Use -h for help.\n",
  153.         Name > "/dev/stderr"
  154.         exit 1
  155.     }
  156.     libFile = ARGV[1]
  157.     libLines = readLib(libFile,library)
  158.  
  159.     numFiles--
  160.     argNum++
  161.     }
  162.     backup = !("B" in Options)
  163.     versionRequired = "r" in Options
  164.     quiet = "q" in Options || (!Debug && printOnly)
  165.     if (compareWarning = ("C" in Options)) {
  166.     libFiles = Options["C"]
  167.     compareLibs = 1
  168.     }
  169.     else if ("c" in Options) {
  170.     compareLibs = 1
  171.     libFiles = Options["c"]
  172.     if (printLib)
  173.         addLib = 1
  174.     }
  175.     else if (matchLibs) {
  176.     compareLibs = 1
  177.     libFiles = Options["M"]
  178.     }
  179.     checkVersion = !(("d" in Options) || printOnly || compareLibs)
  180.     if ("E" in Options) {
  181.     if (!numFiles) {
  182.         printf \
  183.     "%s: Cannot use -E option when reading from standard input.  Exiting.\n",
  184.         Name > "/dev/stderr"
  185.         exit 1
  186.     }
  187.     newExt = Options["E"]
  188.     backup = 0
  189.     }
  190.     else if (printLib && numFiles > 1) {
  191.     printf "%s: may only give one filename if -p is used without -E.\n",
  192.     Name > "/dev/stderr"
  193.     exit 1
  194.     }
  195.     if ("s" in Options)
  196.     startString = Options["s"]
  197.     else if ("S" in Options || "M" in Options)
  198.     startString = ""
  199.  
  200.     if (!matchLibs && vName == "") {
  201.     getLibMarker("first",startString,libFile,Options,"b",library[1],
  202.     firstLines)
  203.     getLibMarker("last",startString,libFile,Options,"e",library[libLines],
  204.     lastLines)
  205.     if (Intersection(firstLines,lastLines,Common)) {
  206.         printf "%s: Got identical first and last lines (exiting):\n",
  207.         Name > "/dev/stderr"
  208.         for (line in Common)
  209.         print line > "/dev/stderr"
  210.         exit 1
  211.     }
  212.     }
  213.  
  214.     if (libLines) {
  215.     if ("V" in Options) {
  216.         if (!getVersion(Options["V"],versionInfo,1)) {
  217.         printf "%s: Bad version string: %s\n",Name,
  218.         Options["V"] > "/dev/stderr"
  219.         exit 1
  220.         }
  221.     }
  222.     else if (\
  223.     !getLibVersion(library,1,0,versionInfo,quiet,libFile,versionRequired))
  224.         checkVersion = 0
  225.     }
  226.     
  227.     if (!numFiles) {
  228.     numFiles++
  229.     ARGV[argNum] = "/dev/stdin"
  230.     }
  231.     if (!matchLibs && Debug) {
  232.     printf "%d library starting %s(s):\n%s\n"\
  233.     "  %d library ending %s(s):\n%s\n",NumElem(firstLines),
  234.     regEx ? "pattern" : "line",conglom(firstLines,"\n"),NumElem(lastLines),
  235.     regEx ? "pattern" : "line",conglom(lastLines,"\n") > "/dev/stderr"
  236.     printf "%d file(s) to be processed.\n",numFiles > "/dev/stderr"
  237.     }
  238.     if (compareLibs)
  239.     if (!readLibs(libFiles,firstLines,lastLines,libs,versionRequired,quiet,
  240.     (name in versionInfo) ? versionInfo["name"] : "",regEx,"b" in Options,
  241.     "e" in Options,matchLibs) && !(compareWarning || printLib)) {
  242.         printf "%s: No libraries given.\n",Name > "/dev/stderr"
  243.         exit 1
  244.     }
  245.     for (; numFiles; numFiles--)
  246.     procFile(ARGV[argNum++],library,firstLines,lastLines,backup,
  247.     Debug,checkVersion,quiet,versionInfo,newExt,versionRequired,printLib,
  248.     printLibVersion,noLib,compareLibs,compareWarning,libs,regEx,matchLibs,
  249.     addLib,vName,uniqueVersion)
  250.     exit 0
  251. }
  252.  
  253. function conglom(set,sep,  s,l) {
  254.     for (l in set)
  255.     s = s sep l
  256.     return substr(s,1+length(sep))
  257. }
  258.  
  259. # Reads each file in libFiles into libs.
  260. # The lines of each file are placed in libs[filenum,1..numlines]
  261. # where filenum is an integer starting with 1.
  262. # The number of files is stored in libs["numfiles"].
  263. # The number of lines read from each file is stored in libs[filenum,"numlines"]
  264. # The number of chars read from each file is stored in libs[filenum,"numchars"]
  265. # The name of each file is stored in libs[filenum,"name"].
  266. # The version string of each file (if any) is stored in libs[filenum,"version"]
  267. # If matchLibs is true, the first & last lines of each lib are added to
  268. # firstLines[] and lastLines[]
  269. function readLibs(libFiles,firstLines,lastLines,libs,versionRequired,quiet,
  270. libName,regEx,b,e,matchLibs,  nFiles,i,file,libNum,numLines,versionInfo) {
  271.     nFiles = split(libFiles,files,"[ \t:]+")
  272.     for (i = 1; i <= nFiles; i++) {
  273.     file = files[i]
  274.     if (file == "")
  275.         continue
  276.     ++libNum
  277.     libs[libNum,"name"] = file
  278.     numLines = readLib(file,libs,libNum SUBSEP)
  279.     if (matchLibs) {
  280.         firstLines[f = libs[libNum,1]]
  281.         lastLines[l = libs[libNum,numLines]]
  282.         if (Debug)
  283.         printf \
  284.         "Adding to firstLines[]: %s\nAdding to lastLines[]: %s\n",
  285.         f,l > "/dev/stderr"
  286.     }
  287.     else if (\
  288.     (!matchLines(libs[libNum,1],firstLines,regEx,"first",b,file,1) ||
  289.     !matchLines(libs[libNum,numLines],lastLines,regEx,"last",e,file,1)))
  290.         exit 1
  291.     if (getLibVersion(libs,1,0,versionInfo,quiet,file,versionRequired,
  292.     libNum SUBSEP)) {
  293.         libs[libNum,"version"] = versionString(versionInfo)
  294.         if (versionRequired && versionInfo["name"] != libName) {
  295.         printf \
  296. "%s: name in version string of library in file %s\n"\
  297. "  (%s) does not match name in replacement library (%s).  Exiting.\n",Name,
  298.         file,versionInfo["name"],libName > "/dev/stderr"
  299.         exit 1
  300.         }
  301.     }
  302.     }
  303.     libs["numfiles"] = libNum
  304.     return libNum
  305. }
  306.  
  307. function matchLines(line,compareLines,regEx,lineName,lineGiven,file,complain,
  308. compareLine) {
  309.     if (regEx) {
  310.     for (compareLine in compareLines)
  311.         if (line ~ compareLine)
  312.         return 1
  313.     }
  314.     else if (line in compareLines) {
  315.     if (Debug)
  316.         printf "Found: %s\n",line > "/dev/stderr"
  317.     return 1
  318.     }
  319.     if (complain)
  320.     printf \
  321. "%s: %s line of comparison library file %s\n"\
  322. "  does not match %s line %s.  Exiting.\n",Name,
  323.     lineName,file,lineName,regEx ? "pattern(s)" : (lineGiven ? \
  324.     "comparison string(s)" : "of replacement library(s)") > "/dev/stderr"
  325.     return 0
  326. }
  327.  
  328. function readLib(libFile,library,prefix,  libLines) {
  329.     if (Debug)
  330.     printf "Reading library file: \"%s\"\n",libFile > "/dev/stderr"
  331.     if ((libLines = readFile(libFile,library,prefix)) == -1) {
  332.     printf "%s: Error reading library file \"%s\": %s\n",Name,libFile,
  333.     ERRNO > "/dev/stderr"
  334.     exit 1
  335.     }
  336.     if (!libLines) {
  337.     printf "%s: Library file \"%s\" is empty.  Exiting.\n",Name,
  338.     libFile > "/dev/stderr"
  339.     exit 1
  340.     }
  341.     return libLines
  342. }
  343.  
  344. function getLibMarker(lineName,startString,libFile,Options,Opt,libLine,lines,
  345. i) {
  346.     if (Opt in Options) {
  347.     lines[Options[Opt]]
  348.     if (Debug)
  349.         printf "Got %s-line comparison line: %s\n",lineName,
  350.         Options[Opt] > "/dev/stderr"
  351.     for (i = Options[Opt,"count"]; i > 1; i--) {
  352.         lines[Options[Opt,i]]
  353.         if (Debug)
  354.         printf "Got %s-line comparison line: %s\n",lineName,
  355.         Options[Opt,i] > "/dev/stderr"
  356.     }
  357.     }
  358.     else if (substr(libLine,1,length(startString)) != startString) {
  359.     printf \
  360. "%s: %s line of library file \"%s\"\n"\
  361. "  does not begin with string \"%s\".  Use -s or -S to override.  Exiting.\n",
  362.     Name,lineName,libFile,
  363.     startString > "/dev/stderr"
  364.     exit 1
  365.     }
  366.     else {
  367.     if (Debug)
  368.         printf "%s line of new library begins with \"%s\"\n",lineName,
  369.         startString > "/dev/stderr"
  370.     lines[libLine]
  371.     }
  372.     if (Debug)
  373.     printf "Got %d %s-line comparison line(s).\n",NumElem(lines),
  374.     lineName > "/dev/stderr"
  375. }
  376.  
  377. # library[] is a library to search for a version string.
  378. #     It contains lines with integer indices.
  379. # first is the first line number.
  380. # If last is non-0, it is the last line number.
  381. # If quiet is false, the library version is printed.
  382. #     libFile is the file that library came from, for messages.
  383. # If versionRequired is true, not finding a version string is fatal.
  384. # If Prefix is non-null, it is used as a prefix for the indices used to
  385. #     access library[].
  386. # If libname is non-null, any version string that has a name that doesn't
  387. #     match it is ignored.
  388. # If a line is found, the version components are put in versionInfo[].
  389. # Return value:
  390. # Line number version string was found on if version line is found, 0 if not.
  391. function getLibVersion(library,first,last,versionInfo,quiet,libFile,
  392. versionRequired,Prefix,libname,
  393. i,ret) {
  394.     ret = 0
  395.     if (Debug)
  396.     printf \
  397. "getLibVersion: searching for version on lines %d through %s; prefix is: %s\n",
  398.     first,last ? last : "end",Prefix > "/dev/stderr"
  399.     for (i = first; last ? (i <= last) : ((Prefix i) in library); i++) {
  400.     if (Debug)
  401.         printf "%d\r",i > "/dev/stderr"
  402.     if (library[Prefix i] ~ /@\(#\)/) {
  403.         if (Debug)
  404.         printf "Found SCCS line in library file %s:\n%s\n",libFile,
  405.         library[Prefix i] > "/dev/stderr"
  406.         if (getVersion(library[Prefix i],versionInfo)) {
  407.         if (libname != "" && versionInfo["name"] != libname) {
  408.             if (Debug)
  409.             printf \
  410.     "In %s, found version string with name \"%s\";\n"\
  411.     "  doesn't match name being searched for (%s); ignoring...\n",libFile,
  412.             versionInfo["name"],libname > "/dev/stderr"
  413.         # Empty versionInfo[] since the existance of indexes in it
  414.         # may be used as a test of whether a good version line was read
  415.             split("",versionInfo)
  416.             continue
  417.         }
  418.         ret = i
  419.         break
  420.         }
  421.     }
  422.     }
  423.     if (ret) {
  424.     if (!quiet)
  425.         printf "Library in file %s is: %s\n",libFile,
  426.         versionString(versionInfo) > "/dev/stderr"
  427.     }
  428.     else {
  429.     if (Debug)
  430.         printf "No version line in library in file %s\n",
  431.         libFile > "/dev/stderr"
  432.     if (versionRequired) {
  433.         printf \
  434.     "%s: No version string found in library in file \"%s\".  Exiting.\n",
  435.         Name,libFile > "/dev/stderr"
  436.         exit 1
  437.     }
  438.     }
  439.     return ret
  440. }
  441.  
  442. # If line ends with a string of the form:
  443. # @( #) <string> <version-#> <date><end-of-line>
  444. #   ^ avoid confusing programs run on this text file
  445. # then the string is put in versionInfo["name"], version-# is put in
  446. # versionInfo["ver"], and date is put in versionInfo["date"].
  447. # <date> must have the form num/num/num.  <version-#> must consist of a
  448. # sequence of numeric strings separated by '.' characters.
  449. # If noSCCS is true, the string should contain only the name, version, and
  450. # date words.
  451. # If line does not contain a string of this form, 0 is returned and no
  452. # indexes are created in versionInfo; if it does, 1 is returned.
  453. function getVersion(line,versionInfo,noSCCS,
  454. pos,elem,name,version,date,nElem) {
  455.     if (!noSCCS) {
  456.     if (!(pos = index(line,"@(\#)")))
  457.         return 0
  458.     line = substr(line,pos+4)
  459.     }
  460.     sub("^[ \t]+","",line)    # get rid of leading & trailing whitespace
  461.     sub("[ \t]+$","",line)
  462.     if ((nElem = split(line,elem,"[ \t]+")) != 3) {
  463.     if (Debug)
  464.         printf \
  465.         "Found %d words after @(\#) on following line; wanted 3.\n%s\n",
  466.         nElem,line > "/dev/stderr"
  467.     return 0
  468.     }
  469.     name = elem[1]
  470.     version = elem[2]
  471.     date = elem[3]
  472.     if (version !~ /^[0-9]+(\.[0-9]+)*$/) {
  473.     if (Debug)
  474.         printf \
  475.     "Version word '%s' not of recognized format in following line:\n%s\n",
  476.         version,line > "/dev/stderr"
  477.     return 0
  478.     }
  479.     if (date !~ "^[0-9]+/[0-9]+/[0-9]+$") {
  480.     if (Debug)
  481.         printf \
  482.     "Date word '%s' not of recognized format in following line:\n%s\n",
  483.         date,line > "/dev/stderr"
  484.     return 0
  485.     }
  486.     versionInfo["name"] = name
  487.     versionInfo["ver"] = version
  488.     versionInfo["date"] = date
  489.     return 1
  490. }
  491.  
  492. # v1 and v2 are sequences of integers separated by the pattern Sep.
  493. # Each pair of integers (from v1 and v2) is compared in turn, starting from
  494. # the left.  If an int from v1 is < int from v2, -1 is returned.
  495. # If v1 and v2 compare the same, 1 is returned.  If an int from v1 is > int
  496. # from v2, 1 is returned.  If one of v1 or v2 runs out of integers before the
  497. # other, the one that runs out is taken to be < the one that still has
  498. # integers, even if the one that still has integers has a 0 as the next value.
  499. function compareVersion(v1,v2,Sep,  v1e,v2e,i,i1,i2) {
  500.     split(v1,v1e,Sep)
  501.     split(v2,v2e,Sep)
  502.     for (i = 1; i in v1e; i++)
  503.     if (!(i in v2e))    # ran out of ints for v2, so v1 > v2
  504.         return 1
  505.     else {
  506.         i1 = v1e[i]+0
  507.         i2 = v2e[i]+0
  508.         if (i1 > i2)
  509.         return 1
  510.         else if (i2 > i1)
  511.         return -1
  512.     }
  513.     if (i in v2e)    # ran out of ints for v1, so v2 > v1
  514.     return -1
  515.     return 0
  516. }
  517.  
  518. function versionString(version) {
  519.     return version["name"] " " version["ver"] " " version["date"]
  520. }
  521.  
  522. # Read file into array lines, one line per element, with integer indices
  523. # starting at 1.
  524. # The index "numchars" is set to the total number of characters read.
  525. # The index "numlines" is set to the total number of lines read.
  526. # If prefix is set, all array indices start with it.
  527. # Return value:
  528. # -1 on error; the number of lines read on success.
  529. function readFile(file,lines,prefix,  numLines,ret,numChars) {
  530.     while ((ret = (getline < file)) == 1) {
  531.     numLines++
  532.     lines[prefix numLines] = $0
  533.     numChars += length($0)+1
  534.     }
  535.     if (Debug)
  536.     printf "Read %d lines from file %s.  Last getline returned %d.\n",
  537.     numLines,file,ret > "/dev/stderr"
  538.     close(file)
  539.     if (!ret) {
  540.     lines[prefix "numchars"] = numChars
  541.     lines[prefix "numlines"] = numLines
  542.     }
  543.     return ret ? -1 : numLines
  544. }
  545.  
  546. function writeLines(outFile,Lines,first,last,  i) {
  547.     for (i = first; i <= last || (!last && (i in Lines)); i++)
  548.     print Lines[i] > outFile
  549.     return 1
  550. }
  551.  
  552. # Compare the library in lines[] to each libary in libs[].
  553. # The lines of each library are in libs[libnum,1..numlines]
  554. # where libnum is an integer starting with 1.
  555. # The number of libraries is in libs["numfiles"].
  556. # The number of lines in each library is in libs[libnum,"numlines"]
  557. # The number of chars in each library is in libs[libnum,"numchars"]
  558. # The name of the file each library came from is in libs[libnum,"name"].
  559. # The version string of each library (if any) is in libs[libnum,"version"]
  560. # Return value: Library number if a match is found, else 0.
  561. # If a match is not found and addLib is true, the library being searched for
  562. # is added to libs[] (the version string is not added).
  563. function doCompareLibs(lines,libs,fileName,addLib,firstLineNum,Debug,
  564. lastLineNum,numChars,numLibs,numCompareLibLines,lineNum,libNum,fLineNum,
  565. maxIncLibLines,numLines) {
  566.     numLibs = libs["numfiles"]
  567.     if (firstLineNum) {
  568.     maxIncLibLines = lines["numlines"] - firstLineNum + 1
  569.     if (Debug)
  570.         printf "Maximum included lib length: %d lines\n",
  571.         maxIncLibLines > "/dev/stderr"
  572.     }
  573.     else {
  574.     firstLineNum = lines["libstart"]
  575.     lastLineNum = lines["libend"]
  576.     numChars = lines["libnumchars"]
  577.     numLines = lastLineNum - firstLineNum + 1
  578.     if (Debug)
  579.         printf \
  580.         "Included library has %d lines, %d chars.  Comparing to %d libs.\n",
  581.         numLines, numChars, numLibs > "/dev/stderr"
  582.     }
  583.     for (libNum = 1; libNum <= numLibs; libNum++) {
  584.     numCompareLibLines = libs[libNum,"numlines"]
  585.     if (numLines) {
  586.         if (numLines != numCompareLibLines) {
  587.         if (Debug)
  588.             printf "Included library is not \"%s\"; it has %d lines.\n",
  589.             libs[libNum,"name"],numCompareLibLines > "/dev/stderr"
  590.         continue
  591.         }
  592.         if (libs[libNum,"numchars"] != numChars) {
  593.         if (Debug)
  594.             printf "Included library is not same as \"%s\";\n"\
  595.             "  # of lines match but it has %d characters.\n",
  596.             libs[libNum,"name"],libs[libNum,"numchars"] > "/dev/stderr"
  597.         continue
  598.         }
  599.     }
  600.     else if (numCompareLibLines > maxIncLibLines) {
  601.         if (Debug)
  602.         printf \
  603.         "Included library is not \"%s\"; it has too many lines (%d).\n",
  604.         libs[libNum,"name"],numCompareLibLines > "/dev/stderr"
  605.         continue
  606.     }
  607.     fLineNum = firstLineNum
  608.     for (lineNum = 1; lineNum <= numCompareLibLines; lineNum++) {
  609.         if (libs[libNum,lineNum] != lines[fLineNum]) {
  610.         if (Debug)
  611.             printf \
  612.             "Included library is not \"%s\"; mismatch at line %d:\n"\
  613.             "  Included lib: %s\n"\
  614.             "  Compared lib: %s\n",
  615.             libs[libNum,"name"],lineNum,lines[fLineNum],
  616.             libs[libNum,lineNum] > "/dev/stderr"
  617.         break
  618.         }
  619.         fLineNum++
  620.     }
  621.     if (lineNum > numCompareLibLines) {
  622.         if (!quiet)
  623.         printf \
  624.         "%s: Included library in file \"%s\" is identical to \"%s\"\n",
  625.         Name,fileName,libs[libNum,"name"] > "/dev/stderr"
  626.         return libNum
  627.     }
  628.     }
  629.     if (addLib) {
  630.     libs["numfiles"] = ++numLibs
  631.     if (Debug)
  632.         printf "Adding library from \"%s\" to library set as lib #%d...\n",
  633.         fileName,numLibs > "/dev/stderr"
  634.     libs[numLibs,"numchars"] = numChars
  635.     libs[numLibs,"numlines"] = numLines
  636.     libs[numLibs,"name"] = fileName
  637.     fLineNum = firstLineNum
  638.     for (lineNum = 1; lineNum <= numLines; lineNum++)
  639.         libs[numLibs,lineNum] = lines[fLineNum++]
  640.     }
  641.     return 0
  642. }
  643.  
  644. function checkByMatch(lines,inFileName,firstLines,libs,Name,Debug,  libNum,
  645. numLines,lineNum,line) {
  646.     numLines = lines["numlines"]
  647.     for (lineNum = 1; lineNum <= numLines; lineNum++) {
  648.     if (Debug)
  649.         printf "%d\r",lineNum > "/dev/stderr"
  650.     line = lines[lineNum]
  651.     if (line in firstLines &&
  652.     (libNum = doCompareLibs(lines,libs,inFileName,0,lineNum,Debug)))
  653.         break
  654.     }
  655.     if (libNum) {
  656.     lines["libstart"] = lineNum
  657.     lines["libend"] = lineNum + libs[libNum,"numlines"] - 1
  658.     return 1
  659.     }
  660.     else {
  661.     printf "%s: None of %d comparison libraries found in file %s\n",
  662.     Name,libs["numfiles"],inFileName > "/dev/stderr"
  663.     return 0
  664.     }
  665. }
  666.  
  667. # Find a section of a file by searching for a match from among a set of first
  668. # and last lines or patterns.
  669. # Input variables:
  670. # lines[]: the array of lines to search.  The lines have integer indexes
  671. # starting at 1.
  672. # firstLines[], lastLines[]: Sets of lines that may mark the start and end
  673. #    of the library being searched for.
  674. # regEx: whether to treat the above sets as fixed lines or patterns.
  675. # inFileName: The name of the file, for use in messages.
  676. # Name: name of program, for error messages.
  677. # Output variables:
  678. # On success, lines["libstart"] and lines["libend"] are set to the first and
  679. # last lines of the found library, and lines["libnumchars"] is set to the
  680. # total number of characters in the library.
  681. # Return value:
  682. # On error (last line found before first, multiple instances of lines
  683. # found, or first but not last line found), -1.
  684. # On success, 1.
  685. # On failure (first and last lines not found), 0.
  686. function findFirstLast(lines,firstLines,lastLines,regEx,inFileName,Name,
  687. firstLineNum,lastLineNum,lineNum,numLines,numChars,line) {
  688.     if (Debug)
  689.     printf \
  690.     "findFirstLast(): searching for any of %d start line(s), %d end line(s).\n",
  691.     NumElem(firstLines),NumElem(lastLines) > "/dev/stderr"
  692.     numLines = lines["numlines"]
  693.     for (lineNum = 1; lineNum <= numLines; lineNum++) {
  694.     if (Debug)
  695.         printf "%d\r",lineNum > "/dev/stderr"
  696.     line = lines[lineNum]
  697.     # If found multiple instances of 1st line, fail.
  698.     if (matchLines(line,firstLines,regEx)) { # if found match for 1st line
  699.         if (!(firstLineNum = checkFoundLine(firstLineNum,lineNum,"first",
  700.         inFileName,lines,line)))
  701.         return -1
  702.     }
  703.     if (firstLineNum && !lastLineNum)
  704.         numChars += length(line)+1
  705.     if (matchLines(line,lastLines,regEx)) {
  706.         if (!firstLineNum) {
  707.         printf \
  708.         "%s: Found match for last line of library in file\n"\
  709.         "  \"%s\" (at line %d)\n"\
  710.         "  before match for first line of library.  Skipping this file.\n",
  711.         Name,inFileName,lineNum > "/dev/stderr"
  712.         return -1
  713.         }
  714.         # If found multiple instances of last line, fail.
  715.         if (!(lastLineNum = checkFoundLine(lastLineNum,lineNum,"last",
  716.         inFileName,lines,line)))
  717.         return -1
  718.     }
  719.     }
  720.     # Test old library
  721.     if (!firstLineNum)
  722.     return 0
  723.     if (!lastLineNum) {
  724.     printf "%s: Did not find end of library in file (skipping): %s\n",
  725.     Name,inFileName > "/dev/stderr"
  726.     return -1
  727.     }
  728.     lines["libstart"] = firstLineNum
  729.     lines["libend"] = lastLineNum
  730.     lines["libnumchars"] = numChars
  731.     return 1
  732. }
  733.  
  734. # Search a set of lines for a library.  The library section is recognized by
  735. # its first and last lines and possibly its version string.
  736. # Input variables:
  737. # lines[]: the array of lines to search.  The lines have integer indexes
  738. #    starting at 1.
  739. # inFileName: The name of the file, for use in messages.
  740. # firstLines[], lastLines[]: Sets of lines that may mark the start and end
  741. #    of the library being searched for.
  742. # regEx: whether to treat the above sets as fixed lines or patterns.
  743. # compareLibs: true if the lines of a found library must match one of a given
  744. #    set of libraries.
  745. # libs[]: The lines of the libraries for the above comparison.
  746. # addLib: True if a non-matching library should be added to the library set.
  747. # checkVersion: If true and library section contains a version string, it
  748. #     must be equale to or later than the version in libVersion[].
  749. # versionRequired: If true, library section must contain a version string.
  750. # getVersion: True if version information should be returned in
  751. #     versionInfo[], but not necessarily checked.
  752. # libVersion[]: Version information from replacement library.
  753. # Name: Program name, for messages.
  754. # Debug: Print debugging info.
  755. # Output variables:
  756. # If version information is gotten, it is put in versionInfo[].
  757. # If a good library is found in lines[], the first and last line numbers of
  758. # the library are put in lines["libstart"] and lines["libend"], and the
  759. # number of characters in the library is put in lines["libnumchars"]
  760. # Return value: 
  761. # 1 on success.  
  762. # 0 if library not found.
  763. # -1 if a bad library was found.
  764. # -2 if library contents comparison fails.
  765. function checkByFirstLast(lines,inFileName,firstLines,lastLines,regEx,
  766. compareLibs,libs,addLib,Name,Debug,
  767. ret,numCompared) {
  768.     ret = findFirstLast(lines,firstLines,lastLines,regEx,inFileName,Name)
  769.     if (ret != 1)
  770.     return ret
  771.     if (compareLibs) {
  772.     # Get number of current libs first, since doCompareLibs() may change it
  773.     numCompared = libs["numfiles"]
  774.     if (!doCompareLibs(lines,libs,inFileName,addLib,0,Debug)) {
  775.         printf "%s: No match for library in file %s\n"\
  776.         "  found in any of %d comparison library(s).\n",Name,
  777.         inFileName,numCompared > "/dev/stderr"
  778.         return -2
  779.     }
  780.     }
  781.     return 1
  782. }
  783.  
  784. # Globals: versions[]
  785. function procFile(inFile,library,firstLines,lastLines,backup,Debug,
  786. checkVersion,quiet,libVersion,newExt,versionRequired,printLib,printLibVersion,
  787. noLib,compareLibs,compareWarning,libs,regEx,matchLibs,addLib,vName,
  788. uniqueVersion,
  789. inFileName,contentsRet,versionInfo,lines,outFile,backupFileName,vString) {
  790.  
  791.     inFileName = (inFile == "/dev/stdin" ? "(standard input)" : inFile)
  792.     if (Debug)
  793.     printf "\nProcessing file: \"%s\"\n",inFile > "/dev/stderr"
  794.     if (readFile(inFile,lines) == -1) {
  795.     printf "%s: Error reading file %s.  Skipping.\n",Name,
  796.     inFileName > "/dev/stderr"
  797.     return 0
  798.     }
  799.     if (vName != "") {
  800.     if (ret = getLibVersion(lines,1,0,versionInfo,1,inFileName,0,"",vName))
  801.     {
  802.         vString = versionInfo["ver"] " " versionInfo["date"]
  803.         if (uniqueVersion) {
  804.         if (vString in versions) {
  805.             if (Debug)
  806.             printf "Already saw version <%s>\n",
  807.             vString > "/dev/stderr"
  808.             return 1
  809.         }
  810.         else {
  811.             if (Debug)
  812.             printf "New version <%s>\n",vString > "/dev/stderr"
  813.             versions[vString]
  814.         }
  815.         }
  816.         printf "%s %s %d\n",inFileName,vString,ret
  817.         return 1
  818.     }
  819.     else {
  820.         if (!uniqueVersion)
  821.         printf "%s no-version\n",inFileName
  822.         return 0
  823.     }
  824.     }
  825.     if (matchLibs)
  826.     contentsRet = \
  827.     checkByMatch(lines,inFileName,firstLines,libs,Name,Debug)
  828.     else {
  829.     contentsRet = checkByFirstLast(lines,inFileName,firstLines,lastLines,
  830.     regEx,compareLibs,libs,addLib,Name,Debug)
  831.     if (Debug)
  832.         printf "checkByFirstLast() returned %d\n",
  833.         contentsRet > "/dev/stderr"
  834.     }
  835.     if (!contentsRet) {    # no lib found
  836.     if (noLib)
  837.         print inFileName
  838.     else
  839.         printf \
  840.     "%s: Did not find start or end of library in file (skipping): %s\n",
  841.         Name,inFileName > "/dev/stderr"
  842.     return 0
  843.     }
  844.     if (contentsRet == -1)    # bad lib
  845.     return 0
  846.     if (printLib || printLibVersion || checkVersion || versionRequired) {
  847.     if (getLibVersion(lines,lines["libstart"],lines["libend"],versionInfo,
  848.     1,inFileName,0,"",libVersion["name"])) {
  849.         if (checkVersion && \
  850.         (compareLibVersions("version","\\.",versionInfo["ver"],
  851.         libVersion["ver"],inFileName) == 1 ||
  852.         compareLibVersions("date","/",versionInfo["date"],
  853.         libVersion["date"],inFileName) == 1))
  854.         return 0
  855.     }
  856.     else if (versionRequired) {
  857.         printf \
  858.     "%s: Did not find version string in library in file (skipping): %s\n",
  859.         Name,inFileName > "/dev/stderr"
  860.         return 0
  861.     }
  862.     }
  863.     if (printLibVersion) {
  864.     if ("name" in versionInfo)
  865.         printf "%-12s %s\n",inFileName,versionString(versionInfo)
  866.     else
  867.         printf "%s: no version string.\n",inFileName > "/dev/stderr"
  868.     return ("name" in versionInfo)
  869.     }
  870.     if (printLib) {
  871.     if ("name" in versionInfo)
  872.         printf "Library version in %s is %s\n",inFileName,
  873.         versionString(versionInfo) > "/dev/stderr"
  874.     # If either we are not doing a contents comparision or we are and it
  875.     # failed, write lib contents
  876.     if (!compareLibs || contentsRet == -2) {
  877.         if (newExt != "")
  878.         outFile = inFile newExt
  879.         else
  880.         outFile = "/dev/stdout"
  881.         printf "Writing library from %s (%d lines) to %s\n",inFileName,
  882.         lines["libend"] - lines["libstart"] + 1,outFile > "/dev/stderr"
  883.         if (!writeLines(outFile,lines,lines["libstart"],lines["libend"]))
  884.         return -1
  885.         close(outFile)
  886.         return 1
  887.     }
  888.     else
  889.         return 0
  890.     }
  891.     if (noLib)
  892.     return 1
  893.     # If contents comparisons didn't succeed and this should be fatal, return
  894.     if (contentsRet != 1 && !compareWarning)
  895.     return 0
  896.  
  897.     # Back up old file
  898.     if (backup && (inFile != "/dev/stdin")) {
  899.     backupFileName = inFile "-"
  900.     if (Debug)
  901.         printf "Backing up \"%s\" to \"%s\"\n",inFile,
  902.         backupFileName > "/dev/stderr"
  903.     if (copy(inFile,backupFileName)) {
  904.         printf "%s: Could not make backup copy of file \"%s\" in \"%s\".\n"\
  905.         "  Skipping this file.\n",Name,inFile,backupFileName > "/dev/stderr"
  906.         return 0
  907.     }
  908.     }
  909.     # Write updated file
  910.     if (newExt != "")
  911.     outFile = inFile newExt
  912.     else if (inFile == "/dev/stdin")
  913.     outFile = "/dev/stdout"
  914.     else
  915.     outFile = inFile
  916.     if (Debug)
  917.     printf \
  918.     "Writing output (replacing lines %d through %d) to file: \"%s\"\n",
  919.     lines["libstart"],lines["libend"],outFile > "/dev/stderr"
  920.     if (!writeLines(outFile,lines,1,lines["libstart"]-1) ||
  921.     !writeLines(outFile,library,1,0) ||
  922.     !writeLines(outFile,lines,lines["libend"]+1,0)) {
  923.     printf "File \"%s\" may be corrupted!\n",outFile > "/dev/stderr"
  924.     if (backup)
  925.         printf "Original file contents are in file \"%s\"\n",
  926.         backupFileName > "/dev/stderr"
  927.     return -1
  928.     }
  929.     close(outFile)
  930.     # Print status
  931.     if (!quiet) {
  932.     printf "%s: Updated file \"%s\"",Name,inFileName > "/dev/stderr"
  933.     if ("name" in versionInfo)
  934.         printf " (had library %s)",
  935.         versionString(versionInfo) > "/dev/stderr"
  936.     print "" > "/dev/stderr"
  937.     }
  938.     return 1
  939. }
  940.  
  941. # Compare version element in oldVersion to that in libVersion.
  942. # See compareVersion() for a description of the format of the version strings
  943. # and Sep.
  944. # verName is the version component being compared.
  945. # inFile is the name of the file that oldVersion came from.
  946. # Return value: 
  947. # -1 if oldVersion is earlier/lower than libVersion
  948. # 0 if oldVersion is equale to libVersion
  949. # 1 if oldVersion is later/higher than libVersion
  950. function compareLibVersions(verName,Sep,oldVersion,libVersion,inFile,  rel) {
  951.     rel = compareVersion(oldVersion,libVersion,Sep)
  952.     if (rel == 1) {
  953.     printf "%s: Library %s in file \"%s\" (%s)\n"\
  954.     "  appears to be later than %s of new library %s (%s).\n"\
  955.     "  Skipping this file.\n",
  956.     Name,verName,inFile,oldVersion,verName,verName,
  957.     libVersion > "/dev/stderr"
  958.     }
  959.     else if (!rel)
  960.     printf "%s: Note: Library %s in file \"%s\" (%s)\n"\
  961.     "  appears to be the same as %s of new library %s (%s).  Continuing.\n",
  962.     Name,verName,inFile,oldVersion,verName,verName,
  963.     libVersion > "/dev/stderr"
  964.     if (rel == 1 && Debug)
  965.     printf "File \"%s\": Old library %s was %s.\n",inFile,libVersion,
  966.     verName > "/dev/stderr"
  967.     return rel
  968. }
  969.  
  970. function checkFoundLine(oldLineNum,lineNum,lineName,inFileName,lines,
  971. line2)
  972. {
  973.     if (oldLineNum) {
  974.     printf \
  975. "%s: Found multiple instances of %s line of library in file\n"\
  976. "  \"%s\".  Skipping this file.\n"\
  977. "  1st (line %d): %s\n"\
  978. "  2nd (line %d): %s\n",
  979.     Name,lineName,inFileName,oldLineNum,
  980.     lines[oldLineNum],lineNum,line2 > "/dev/stderr"
  981.     return 0
  982.     }
  983.     else {
  984.     if (Debug)
  985.         printf "File \"%s\": Found %s line of library at line %d.\n",
  986.         inFileName,lineName,lineNum > "/dev/stderr"
  987.     return lineNum
  988.     }
  989. }
  990.  
  991. ### Begin copy,append routines
  992. # append: append file Source to file Dest.
  993. # The final return value from the read is returned.
  994. # It will be 0 if the file was read successfully; -1 if not.
  995. function append(Source,Dest,  Line,ret) {
  996.     while ((ret = (getline Line < Source)) == 1)
  997.     print Line >> Dest
  998.     close(Source)
  999.     close(Dest)
  1000.     return ret
  1001. }
  1002.  
  1003. # copy: append file Source to file Dest.
  1004. # The final return value from the read is returned.
  1005. # It will be 0 if the file was read successfully; -1 if not.
  1006. function copy(Source,Dest,  Line,ret) {
  1007.     while ((ret = (getline Line < Source)) == 1)
  1008.     print Line > Dest
  1009.     close(Source)
  1010.     close(Dest)
  1011.     return ret
  1012. }
  1013.  
  1014. ### End copy,append routines
  1015. ### Start of ProcArgs library
  1016. # @(#) ProcArgs 1.11 96/12/08
  1017. # 92/02/29 john h. dubois iii (john@armory.com)
  1018. # 93/07/18 Added "#" arg type
  1019. # 93/09/26 Do not count -h against MinArgs
  1020. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  1021. #          Removed meaning of "+" or "-" by itself.
  1022. # 94/03/08 Added & option and *()< option types.
  1023. # 94/04/02 Added NoRCopt to Opts()
  1024. # 94/06/11 Mark numeric variables as such.
  1025. # 94/07/08 Opts(): Do not require any args if h option is given.
  1026. # 95/01/22 Record options given more than once.  Record option num in argv.
  1027. # 95/06/08 Added ExclusiveOptions().
  1028. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  1029. #          Expand $VARNAME at the start of its filenames.
  1030. #          Let varname=0 and -option- turn off an option.
  1031. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  1032. #          of the vars should be searched for in the environment.
  1033. #          Check for duplicate rcfiles.
  1034. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  1035. #          now return various negatives values on error, not just -1, and
  1036. #          Opts() may set Err to various positive values, not just 1.
  1037. #          Added AllowUnrecOpt.
  1038. # 96/05/23 Check type given for & option
  1039. # 96/06/15 Re-port to awk
  1040. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  1041. #          used by other functions.
  1042. # 96/10/15 Added OptChars
  1043. # 96/11/01 Added exOpts arg to Opts()
  1044. # 96/11/16 Added ; type
  1045. # 96/12/08 Added Opt2Set() & Opt2Sets()
  1046. # 96/12/27 Added CmdLineOpt()
  1047.  
  1048. # optlist is a string which contains all of the possible command line options.
  1049. # A character followed by certain characters indicates that the option takes
  1050. # an argument, with type as follows:
  1051. # :    String argument
  1052. # ;    Non-empty string argument
  1053. # *    Floating point argument
  1054. # (    Non-negative floating point argument
  1055. # )    Positive floating point argument
  1056. # #    Integer argument
  1057. # <    Non-negative integer argument
  1058. # >    Positive integer argument
  1059. # The only difference the type of argument makes is in the runtime argument
  1060. # error checking that is done.
  1061.  
  1062. # The & option is a special case used to get numeric options without the
  1063. # user having to give an option character.  It is shorthand for [-+.0-9].
  1064. # If & is included in optlist and an option string that begins with one of
  1065. # these characters is seen, the value given to "&" will include the first
  1066. # char of the option.  & must be followed by a type character other than ":"
  1067. # or ";".
  1068. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  1069.  
  1070. # Strings in argv[] which begin with "-" or "+" are taken to be
  1071. # strings of options, except that a string which consists solely of "-"
  1072. # or "+" is taken to be a non-option string; like other non-option strings,
  1073. # it stops the scanning of argv and is left in argv[].
  1074. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  1075. # If an option takes an argument, the argument may either immediately
  1076. # follow it or be given separately.
  1077. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  1078. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  1079. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  1080. # this feature had a flaw that caused problems in some cases.  See the OptChars
  1081. # parameter to explicitly set the option-specifier characters.
  1082.  
  1083. # If an option that does not take an argument is given,
  1084. # an index with its name is created in Options and its value is set to the
  1085. # number of times it occurs in argv[].
  1086.  
  1087. # If an option that does take an argument is given, an index with its name is
  1088. # created in Options and its value is set to the value of the argument given
  1089. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  1090. # If an option that takes an argument is given more than once,
  1091. # Options[option-name,"count"] is incremented, and the value is assigned to
  1092. # the index (option-name,instance) where instance is 2 for the second occurance
  1093. # of the option, etc.
  1094. # In other words, the first time an option with a value is encountered, the
  1095. # value is assigned to an index consisting only of its name; for any further
  1096. # occurances of the option, the value index has an extra (count) dimension.
  1097.  
  1098. # The sequence number for each option found in argv[] is stored in
  1099. # Options[option-name,"num",instance], where instance is 1 for the first
  1100. # occurance of the option, etc.  The sequence number starts at 1 and is
  1101. # incremented for each option, both those that have a value and those that
  1102. # do not.  Options set from a config file have a value of 0 assigned to this.
  1103.  
  1104. # Options and their arguments are deleted from argv.
  1105. # Note that this means that there may be gaps left in the indices of argv[].
  1106. # If compress is nonzero, argv[] is packed by moving its elements so that
  1107. # they have contiguous integer indices starting with 0.
  1108. # Option processing will stop with the first unrecognized option, just as
  1109. # though -- was given except that unlike -- the unrecognized option will not be
  1110. # removed from ARGV[].  Normally, an error value is returned in this case.
  1111. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  1112. # be found, so the number of remaining arguments is returned instead.
  1113. # If OptChars is not a null string, it is the set of characters that indicate
  1114. # that an argument is an option string if the string begins with one of the
  1115. # characters.  A string consisting solely of two of the same option-indicator
  1116. # characters stops the scanning of argv[].  The default is "-+".
  1117. # argv[0] is not examined.
  1118. # The number of arguments left in argc is returned.
  1119. # If an error occurs, the global string OptErr is set to an error message
  1120. # and a negative value is returned.
  1121. # Current error values:
  1122. # -1: option that required an argument did not get it.
  1123. # -2: argument of incorrect type supplied for an option.
  1124. # -3: unrecognized (invalid) option.
  1125. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  1126. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  1127. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  1128. {
  1129. # ArgNum is the index of the argument being processed.
  1130. # ArgsLeft is the number of arguments left in argv.
  1131. # Arg is the argument being processed.
  1132. # ArgLen is the length of the argument being processed.
  1133. # ArgInd is the position of the character in Arg being processed.
  1134. # Option is the character in Arg being processed.
  1135. # Pos is the position in OptList of the option being processed.
  1136. # NumOpt is true if a numeric option may be given.
  1137.     ArgsLeft = argc
  1138.     NumOpt = index(OptList,"&")
  1139.     OptionNum = 0
  1140.     if (OptChars == "")
  1141.     OptChars = "-+"
  1142.     while (OptChars != "") {
  1143.     c = substr(OptChars,1,1)
  1144.     OptChars = substr(OptChars,2)
  1145.     OptCharSet[c]
  1146.     OptTerm[c c]
  1147.     }
  1148.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  1149.     Arg = argv[ArgNum]
  1150.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  1151.         break    # Not an option; quit
  1152.     if (Arg in OptTerm) {
  1153.         delete argv[ArgNum]
  1154.         ArgsLeft--
  1155.         break
  1156.     }
  1157.     ArgLen = length(Arg)
  1158.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  1159.         Option = substr(Arg,ArgInd,1)
  1160.         if (NumOpt && Option ~ /[-+.0-9]/) {
  1161.         # If this option is a numeric option, make its flag be & and
  1162.         # its option string flag position be the position of & in
  1163.         # the option string.
  1164.         Option = "&"
  1165.         Pos = NumOpt
  1166.         # Prefix Arg with a char so that ArgInd will point to the
  1167.         # first char of the numeric option.
  1168.         Arg = "&" Arg
  1169.         ArgLen++
  1170.         }
  1171.         # Find position of flag in option string, to get its type (if any).
  1172.         # Disallow & as literal flag.
  1173.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  1174.         if (AllowUnrecOpt) {
  1175.             Escape = 1
  1176.             break
  1177.         }
  1178.         else {
  1179.             OptErr = "Invalid option: " specGiven Option
  1180.             return -3
  1181.         }
  1182.         }
  1183.  
  1184.         # Find what the value of the option will be if it takes one.
  1185.         # NeedNextOpt is true if the option specifier is the last char of
  1186.         # this arg, which means that if the option requires a value it is
  1187.         # the next arg.
  1188.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  1189.         if (GotValue = ArgNum + 1 < argc)
  1190.             Value = argv[ArgNum+1]
  1191.         }
  1192.         else {    # Value is included with option
  1193.         Value = substr(Arg,ArgInd + 1)
  1194.         GotValue = 1
  1195.         }
  1196.  
  1197.         if (HadValue = AssignVal(Option,Value,Options,
  1198.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  1199.         specGiven)) {
  1200.         if (HadValue < 0)    # error occured
  1201.             return HadValue
  1202.         if (HadValue == 2)
  1203.             ArgInd++    # Account for the single-char value we used.
  1204.         else {
  1205.             if (NeedNextOpt) {    # option took next arg as value
  1206.             delete argv[++ArgNum]
  1207.             ArgsLeft--
  1208.             }
  1209.             break    # This option has been used up
  1210.         }
  1211.         }
  1212.     }
  1213.     if (Escape)
  1214.         break
  1215.     # Do not delete arg until after processing of it, so that if it is not
  1216.     # recognized it can be left in ARGV[].
  1217.     delete argv[ArgNum]
  1218.     ArgsLeft--
  1219.     }
  1220.     if (compress != 0) {
  1221.     dest = 1
  1222.     src = argc - ArgsLeft + 1
  1223.     for (count = ArgsLeft - 1; count; count--) {
  1224.         ARGV[dest] = ARGV[src]
  1225.         dest++
  1226.         src++
  1227.     }
  1228.     }
  1229.     return ArgsLeft
  1230. }
  1231.  
  1232. # Assignment to values in Options[] occurs only in this function.
  1233. # Option: Option specifier character.
  1234. # Value: Value to be assigned to option, if it takes a value.
  1235. # Options[]: Options array to return values in.
  1236. # ArgType: Argument type specifier character.
  1237. # GotValue: Whether any value is available to be assigned to this option.
  1238. # Name: Name of option being processed.
  1239. # OptionNum: Number of this option (starting with 1) if set in argv[],
  1240. #     or 0 if it was given in a config file or in the environment.
  1241. # SingleOpt: true if the value (if any) that is available for this option was
  1242. #     given as part of the same command line arg as the option.  Used only for
  1243. #     options from the command line.
  1244. # specGiven is the option specifier character use, if any (e.g. - or +),
  1245. # for use in error messages.
  1246. # Global variables: OptErr
  1247. # Return value: negative value on error, 0 if option did not require an
  1248. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  1249. # the arg.
  1250. # Current error values:
  1251. # -1: Option that required an argument did not get it.
  1252. # -2: Value of incorrect type supplied for option.
  1253. # -3: Bad type given for option &
  1254. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  1255. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  1256.     # If option takes a value...    [
  1257.     NumTypes = "*()#<>]"
  1258.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  1259.     OptErr = "Bad type given for & option"
  1260.     return -3
  1261.     }
  1262.  
  1263.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  1264.     if (!GotValue) {
  1265.         if (Name != "")
  1266.         OptErr = "Variable requires a value -- " Name
  1267.         else
  1268.         OptErr = "option requires an argument -- " Option
  1269.         return -1
  1270.     }
  1271.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  1272.         OptErr = Err
  1273.         return -2
  1274.     }
  1275.     # Mark this as a numeric variable; will be propogated to Options[] val.
  1276.     if (ArgType != ":" && ArgType != ";")
  1277.         Value += 0
  1278.     if ((Instance = ++Options[Option,"count"]) > 1)
  1279.         Options[Option,Instance] = Value
  1280.     else
  1281.         Options[Option] = Value
  1282.     }
  1283.     # If this is an environ or rcfile assignment & it was given a value...
  1284.     else if (!OptionNum && Value != "") {
  1285.     UsedValue = 1
  1286.     # If the value is "0" or "-" and this is the first instance of it,
  1287.     # do not set Options[Option]; this allows an assignment in an rcfile to
  1288.     # turn off an option (for the simple "Option in Options" test) in such
  1289.     # a way that it cannot be turned on in a later file.
  1290.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  1291.         Instance = 1
  1292.     else
  1293.         Instance = ++Options[Option]
  1294.     # Save the value even though this is a flag
  1295.     Options[Option,Instance] = Value
  1296.     }
  1297.     # If this is a command line flag and has a - following it in the same arg,
  1298.     # it is being turned off.
  1299.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  1300.     UsedValue = 2
  1301.     if (Option in Options)
  1302.         Instance = ++Options[Option]
  1303.     else
  1304.         Instance = 1
  1305.     Options[Option,Instance]
  1306.     }
  1307.     # If this is a flag assignment without a value, increment the count for the
  1308.     # flag unless it was turned off.  The indicator for a flag being turned off
  1309.     # is that the flag index has not been set in Options[] but it has an
  1310.     # instance count.
  1311.     else if (Option in Options || !((Option,1) in Options))
  1312.     # Increment number of times this flag seen; will inc null value to 1
  1313.     Instance = ++Options[Option]
  1314.     Options[Option,"num",Instance] = OptionNum
  1315.     return UsedValue
  1316. }
  1317.  
  1318. # Option is the option letter
  1319. # Value is the value being assigned
  1320. # Name is the var name of the option, if any
  1321. # ArgType is one of:
  1322. # :    String argument
  1323. # ;    Non-null string argument
  1324. # *    Floating point argument
  1325. # (    Non-negative floating point argument
  1326. # )    Positive floating point argument
  1327. # #    Integer argument
  1328. # <    Non-negative integer argument
  1329. # >    Positive integer argument
  1330. # specGiven is the option specifier character use, if any (e.g. - or +),
  1331. # for use in error messages.
  1332. # Returns null on success, err string on error
  1333. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  1334.     if (ArgType == ":")
  1335.     return ""
  1336.     if (ArgType == ";") {
  1337.     if (Value == "")
  1338.         Err = "must be a non-empty string"
  1339.     }
  1340.     # A number begins with optional + or -, and is followed by a string of
  1341.     # digits or a decimal with digits before it, after it, or both
  1342.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  1343.     Err = "must be a number"
  1344.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  1345.     Err = "may not include a fraction"
  1346.     else if (ArgType ~ "[()<>]" && Value < 0)
  1347.     Err = "may not be negative"
  1348.     # (
  1349.     else if (ArgType ~ "[)>]" && Value == 0)
  1350.     Err = "must be a positive number"
  1351.     if (Err != "") {
  1352.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  1353.     if (Name != "")
  1354.         return ErrStr "variable " substr(Name,1,1) " " Err
  1355.     else {
  1356.         if (Option == "&")
  1357.         Option = Value
  1358.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  1359.     }
  1360.     }
  1361.     else
  1362.     return ""
  1363. }
  1364.  
  1365. # Note: only the above functions are needed by ProcArgs.
  1366. # The rest of these functions call ProcArgs() and also do other
  1367. # option-processing stuff.
  1368.  
  1369. # Opts: Process command line arguments.
  1370. # Opts processes command line arguments using ProcArgs()
  1371. # and checks for errors.  If an error occurs, a message is printed
  1372. # and the program is exited.
  1373. #
  1374. # Input variables:
  1375. # Name is the name of the program, for error messages.
  1376. # Usage is a usage message, for error messages.
  1377. # OptList the option description string, as used by ProcArgs().
  1378. # MinArgs is the minimum number of non-option arguments that this
  1379. # program should have, non including ARGV[0] and +h.
  1380. # If the program does not require any non-option arguments,
  1381. # MinArgs should be omitted or given as 0.
  1382. # rcFiles, if given, is a colon-seprated list of filenames to read for
  1383. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  1384. # by the value of the environment variable HOME.  If a filename begins with
  1385. # $, the part from the character after the $ up until (but not including)
  1386. # the first character not in [a-zA-Z0-9_] will be searched for in the
  1387. # environment; if found its value will be substituted, if not the filename will
  1388. # be discarded.
  1389. # rcfiles are read in the order given.
  1390. # Values given in them will not override values given on the command line,
  1391. # and values given in later files will not override those set in earlier
  1392. # files, because AssignVal() will store each with a different instance index.
  1393. # The first instance of each variable, either on the command line or in an
  1394. # rcfile, will be stored with no instance index, and this is the value
  1395. # normally used by programs that call this function.
  1396. # VarNames is a comma-separated list of variable names to map to options,
  1397. # in the same order as the options are given in OptList.
  1398. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  1399. # searched for in the environment.  If set to -1, all values will be searched
  1400. # for in the environment.  Values given in the environment will override
  1401. # those given in the rcfiles but not those given on the command line.
  1402. # NoRCopt, if given, is an additional letter option that if given on the
  1403. # command line prevents the rcfiles from being read.
  1404. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  1405. # ExclusiveOptions() for a description of exOpts.
  1406. # Special options:
  1407. # If x is made an option and is given, some debugging info is output.
  1408. # h is assumed to be the help option.
  1409.  
  1410. # Global variables:
  1411. # The command line arguments are taken from ARGV[].
  1412. # The arguments that are option specifiers and values are removed from
  1413. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  1414. # The number of elements in ARGV[] should be in ARGC.
  1415. # After processing, ARGC is set to the number of elements left in ARGV[].
  1416. # The option values are put in Options[].
  1417. # On error, Err is set to a positive integer value so it can be checked for in
  1418. # an END block.
  1419. # Return value: The number of elements left in ARGV is returned.
  1420. # Must keep OptErr global since it may be set by InitOpts().
  1421. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  1422. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  1423.     if (MinArgs == "")
  1424.     MinArgs = 0
  1425.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  1426.     optChars)
  1427.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  1428.     if (ArgsLeft >= 0) {
  1429.         OptErr = "Not enough arguments"
  1430.         Err = 4
  1431.     }
  1432.     else
  1433.         Err = -ArgsLeft
  1434.     printf "%s: %s.\nUse -h for help.\n%s\n",
  1435.     Name,OptErr,Usage > "/dev/stderr"
  1436.     exit 1
  1437.     }
  1438.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  1439.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  1440.     {
  1441.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  1442.     Err = -e
  1443.     exit 1
  1444.     }
  1445.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  1446.     {
  1447.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  1448.     Err = 1
  1449.     exit 1
  1450.     }
  1451.     return ArgsLeft
  1452. }
  1453.  
  1454. # ReadConfFile(): Read a file containing var/value assignments, in the form
  1455. # <variable-name><assignment-char><value>.
  1456. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  1457. # line and whitespace between the variable name and the assignment character) 
  1458. # is stripped.  Lines that do not contain an assignment operator or which
  1459. # contain a null variable name are ignored, other than possibly being noted in
  1460. # the return value.  If more than one assignment is made to a variable, the
  1461. # first assignment is used.
  1462. # Input variables:
  1463. # File is the file to read.
  1464. # Comment is the line-comment character.  If it is found as the first non-
  1465. #     whitespace character on a line, the line is ignored.
  1466. # Assign is the assignment string.  The first instance of Assign on a line
  1467. #     separates the variable name from its value.
  1468. # If StripWhite is true, whitespace around the value (whitespace between the
  1469. #     assignment char and trailing whitespace on the line) is stripped.
  1470. # VarPat is a pattern that variable names must match.  
  1471. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  1472. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  1473. #     a line; no assignment operator is needed.  These variables are set in
  1474. #     the output array with a null value.  Lines containing nothing but
  1475. #     whitespace are still ignored.
  1476. # Output variables:
  1477. # Values[] contains the assignments, with the indexes being the variable names
  1478. #     and the values being the assigned values.
  1479. # Lines[] contains the line number that each variable occured on.  A flag set
  1480. #     is record by giving it an index in Lines[] but not in Values[].
  1481. # Return value:
  1482. # If any errors occur, a string consisting of descriptions of the errors
  1483. # separated by newlines is returned.  In no case will the string start with a
  1484. # numeric value.  If no errors occur,  the number of lines read is returned.
  1485. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  1486. FlagsOK,
  1487. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  1488.     if (Comment != "")
  1489.     Comment = "^" Comment
  1490.     AssignLen = length(Assign)
  1491.     if (VarPat == "")
  1492.     VarPat = "."    # null varname not allowed
  1493.     while ((Status = (getline Line < File)) == 1) {
  1494.     LineNum++
  1495.     sub("^[ \t]+","",Line)
  1496.     if (Line == "")        # blank line
  1497.         continue
  1498.     if (Comment != "" && Line ~ Comment)
  1499.         continue
  1500.     if (Pos = index(Line,Assign)) {
  1501.         Var = substr(Line,1,Pos-1)
  1502.         Val = substr(Line,Pos+AssignLen)
  1503.         if (StripWhite) {
  1504.         sub("^[ \t]+","",Val)
  1505.         sub("[ \t]+$","",Val)
  1506.         }
  1507.     }
  1508.     else {
  1509.         Var = Line    # If no value, var is entire line
  1510.         Val = ""
  1511.     }
  1512.     if (!FlagsOK && Val == "") {
  1513.         Errs = Errs \
  1514.         sprintf("\nBad assignment on line %d of file %s: %s",
  1515.         LineNum,File,Line)
  1516.         continue
  1517.     }
  1518.     sub("[ \t]+$","",Var)
  1519.     if (Var !~ VarPat) {
  1520.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  1521.         LineNum,File,Var)
  1522.         continue
  1523.     }
  1524.     if (!(Var in Lines)) {
  1525.         Lines[Var] = LineNum
  1526.         if (Pos)
  1527.         Values[Var] = Val
  1528.     }
  1529.     }
  1530.     if (Status)
  1531.     Errs = Errs "\nCould not read file " File
  1532.     close(File)
  1533.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  1534. }
  1535.  
  1536. # Variables:
  1537. # Data is stored in Options[].
  1538. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  1539. # Global vars:
  1540. # Sets OptErr.  Uses ENVIRON[].
  1541. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  1542. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  1543. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  1544. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  1545.     split("",filesRead,"")    # make awk know this is an array
  1546.     NumVars = split(VarNames,Vars,",")
  1547.     TypesInd = Ret = 0
  1548.     if (EnvSearch == -1)
  1549.     EnvSearch = NumVars
  1550.     for (i = 1; i <= NumVars; i++) {
  1551.     Var = Vars[i]
  1552.     CharOpt = substr(OptList,++TypesInd,1)
  1553.     if (CharOpt ~ "^[:;*()#<>&]$")
  1554.         CharOpt = substr(OptList,++TypesInd,1)
  1555.     Map[Var] = CharOpt
  1556.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1557.     # Do not overwrite entries from environment
  1558.     if (i <= EnvSearch && Var in ENVIRON &&
  1559.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  1560.         return Err
  1561.     }
  1562.  
  1563.     numrcFiles = split(rcFiles,fNames,":")
  1564.     for (i = 1; i <= numrcFiles; i++) {
  1565.     rcFile = fNames[i]
  1566.     if (rcFile ~ "^~/")
  1567.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1568.     else if (rcFile ~ /^\$/) {
  1569.         rcFile = substr(rcFile,2)
  1570.         match(rcFile,"^[a-zA-Z0-9_]*")
  1571.         envvar = substr(rcFile,1,RLENGTH)
  1572.         if (envvar in ENVIRON)
  1573.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1574.         else
  1575.         continue
  1576.     }
  1577.     if (rcFile in filesRead)
  1578.         continue
  1579.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1580.     # may be the same
  1581.     filesRead[rcFile]
  1582.     if ("x" in Options)
  1583.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1584.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1585.     if (retStr > 0)
  1586.         READ_RCFILE = 1
  1587.     else if (ret != "") {
  1588.         OptErr = retStr
  1589.         Ret = -1
  1590.     }
  1591.     for (Var in Lines)
  1592.         if (Var in Map) {
  1593.         if ((Err = AssignVal(Map[Var],
  1594.         Var in Values ? Values[Var] : "",Options,Types[Var],
  1595.         Var in Values,Var,0)) < 0)
  1596.             return Err
  1597.         }
  1598.         else {
  1599.         OptErr = sprintf(\
  1600.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1601.         Lines[Var],rcFile)
  1602.         Ret = -1
  1603.         }
  1604.     }
  1605.  
  1606.     if ("x" in Options)
  1607.     for (Var in Map)
  1608.         if (Map[Var] in Options)
  1609.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1610.         "/dev/stderr"
  1611.         else
  1612.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1613.     return Ret
  1614. }
  1615.  
  1616. # OptSets is a semicolon-separated list of sets of option sets.
  1617. # Within a list of option sets, the option sets are separated by commas.  For
  1618. # each set of sets, if any option in one of the sets is in Options[] AND any
  1619. # option in one of the other sets is in Options[], an error string is returned.
  1620. # If no conflicts are found, nothing is returned.
  1621. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1622. # the exclusions presented by the first set of sets (ab,def,g) if:
  1623. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1624. # (a or b is in Options[]) AND (g is in Options) OR
  1625. # (d, e, or f is in Options[]) AND (g is in Options)
  1626. # An error will be returned due to the exclusions presented by the second set
  1627. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1628. # todo: make options given on command line unset options given in config file
  1629. # todo: that they conflict with.
  1630. function ExclusiveOptions(OptSets,Options,
  1631. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1632. SetNum,OSetNum) {
  1633.     NumSetSets = split(OptSets,SetSets,";")
  1634.     # For each set of sets...
  1635.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1636.     # NumSets is the number of sets in this set of sets.
  1637.     NumSets = split(SetSets[SetSet],Sets,",")
  1638.     # For each set in a set of sets except the last...
  1639.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1640.         s1 = Sets[SetNum]
  1641.         L1 = length(s1)
  1642.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1643.         # If any of the options in this set was given, check whether
  1644.         # any of the options in the other sets was given.  Only check
  1645.         # later sets since earlier sets will have already been checked
  1646.         # against this set.
  1647.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1648.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1649.             s2 = Sets[OSetNum]
  1650.             L2 = length(s2)
  1651.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1652.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1653.                 ErrStr = ErrStr "\n"\
  1654.                 sprintf("Cannot give both %s and %s options.",
  1655.                 c1,c2)
  1656.             }
  1657.     }
  1658.     }
  1659.     if (ErrStr != "")
  1660.     return substr(ErrStr,2)
  1661.     return ""
  1662. }
  1663.  
  1664. # The value of each instance of option Opt that occurs in Options[] is made an
  1665. # index of Set[].
  1666. # The return value is the number of instances of Opt in Options.
  1667. function Opt2Set(Options,Opt,Set,  count) {
  1668.     if (!(Opt in Options))
  1669.     return 0
  1670.     Set[Options[Opt]]
  1671.     count = Options[Opt,"count"]
  1672.     for (; count > 1; count--)
  1673.     Set[Options[Opt,count]]
  1674.     return count
  1675. }
  1676.  
  1677. # The value of each instance of option Opt that occurs in Options[] that
  1678. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1679. # Other values are made indexes of Set[].
  1680. # The return value is the number of instances of Opt in Options.
  1681. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1682.     ret = Opt2Set(Options,Opt,aSet)
  1683.     for (value in aSet)
  1684.     if (substr(value,1,1) == "!")
  1685.         nSet[substr(value,2)]
  1686.     else
  1687.         Set[value]
  1688.     return ret
  1689. }
  1690.  
  1691. # Returns true if option Opt was given on the command line.
  1692. function CmdLineOpt(Options,Opt,  i) {
  1693.     for (i = 1; (Opt,"num",i) in Options; i++)
  1694.     if (Options[Opt,"num",i] != 0)
  1695.         return 1
  1696.     return 0
  1697. }
  1698. ### End of ProcArgs library
  1699. ### Begin array routines
  1700.  
  1701. # InitArr: Initialize an array with values.
  1702. # Ind and Vals are separated into lists on Sep.
  1703. # For each item in Ind, an index with that name is created in Arr[],
  1704. # and the value with the same position in Vals is stored in it.
  1705. # Global variables: none.
  1706. function InitArr(Arr,Ind,Vals,sep,  numind,indnames,values) {
  1707.     split(Ind,indnames,sep)
  1708.     split(Vals,values,sep)
  1709.     for (numind in indnames)
  1710.     Arr[indnames[numind]] = values[numind]
  1711. }
  1712.  
  1713. function ClearArr(Arr,  Elem) {
  1714.     for (Elem in Arr)
  1715.     delete Arr[Elem]
  1716. }
  1717.  
  1718. function CopyArr(From,To,  Elem) {
  1719.     for (Elem in From)
  1720.     To[Elem] = From[Elem]
  1721. }
  1722.  
  1723. # Subtract the values in Subtrahend from those in Minuend
  1724. function SubtractArr(Minuend,Subtrahend,  Elem) {
  1725.     for (Elem in Subtrahend)
  1726.     Minuend[Elem] -= Subtrahend[Elem]
  1727. }
  1728. # For each element of the array In, an element is created in Out having
  1729. # an index equal to the value of the element in In and a value equal to
  1730. # the index of the element in In.
  1731. function Invert(In,Out,  Index) {
  1732.     for (Index in In)
  1733.     Out[In[Index]] = Index
  1734. }
  1735.  
  1736. # Assign: make an array from a list of assignments.
  1737. # An index with the name of each variable in the list is created in the array.
  1738. # Its value is set to the value given for it.
  1739. # Input variables:
  1740. # Elements is a string containing the list of variable-value pairs.
  1741. # Sep is the string that separates the pairs in the list.
  1742. # AssignOp is the string that separates variables from values.
  1743. # Output variables:
  1744. # Arr is the array.
  1745. # Return value: the number of elements added to the set.
  1746. # Example:
  1747. # Assign(Arr,"foo=blot bar=blat baz=blit"," ","=")
  1748. function Assign(Arr,Elements,Sep,AssignOp,
  1749. Num,Names,Elem,Assignments,Assignment,i) {
  1750.     Num = split(Elements,Assignments,Sep)
  1751.     for (i = 1; i <= Num; i++) {
  1752.     Assignment = Assignments[i]
  1753.     Ind = index(Assignment,AssignOp)
  1754.     Arr[substr(Assignment,1,Ind - 1)] = substr(Assignment,Ind + 1)
  1755.     }
  1756.     return Num
  1757. }
  1758.  
  1759. # Packs Arr[], which should have integer indices starting at or above n, to
  1760. # contiguous integer indices starting with n.
  1761. # If n is not given it defaults to 0.
  1762. # Num should be the number of elements in Arr.
  1763. function PackArr(Arr,Num,n,  NewInd,OldInd) {
  1764.     NewInd = OldInd = n+0
  1765.     for (; Num; Num--) {
  1766.     while (!(OldInd in Arr))
  1767.         OldInd++
  1768.     if (NewInd != OldInd) {
  1769.         Arr[NewInd] = Arr[OldInd]
  1770.         delete Arr[OldInd]
  1771.     }
  1772.     OldInd++
  1773.     NewInd++
  1774.     }
  1775. }
  1776. ### End array routines
  1777. ### Begin set library
  1778. # 96/05/23 added return values  jhdiii
  1779. # 96/05/25 added set2list()
  1780.  
  1781. # Return value: the number of new elements added to Inter
  1782. function Intersection(A,B,Inter,  Elem,Count) {
  1783.     for (Elem in A)
  1784.     if (Elem in B && !(Elem in Inter)) {
  1785.         Inter[Elem]
  1786.         Count++
  1787.     }
  1788.     return Count
  1789. }
  1790.  
  1791. # Return value: the number of new elements added to Both
  1792. function Union(A,B,Both) {
  1793.     return CopySet(A,Both) + CopySet(B,Both)
  1794. }
  1795.  
  1796. # Deletes any elements that are in both Minuend and Subtrahend from Minuend.
  1797. # Return value: the number of elements deleted.
  1798. function SubtractSet(Minuend,Subtrahend,  Elem,nDel) {
  1799.     for (Elem in Subtrahend)
  1800.     if (Elem in Minuend) {
  1801.         delete Minuend[Elem]
  1802.         nDel++
  1803.     }
  1804.     return nDel
  1805. }
  1806.  
  1807. # Return value: the number of new elements added to To
  1808. function CopySet(From,To,  Elem,n) {
  1809.     for (Elem in From)
  1810.     if (!(Elem in To)) {
  1811.         To[Elem]
  1812.         n++
  1813.     }
  1814.     return n
  1815. }
  1816.  
  1817. # Returns 1 if Set is empty, 0 if not.
  1818. function IsEmpty(Set,  i) {
  1819.     for (i in Set)
  1820.     return 0
  1821.     return 1
  1822. }
  1823.  
  1824. # MakeSet: make a set from a list.
  1825. # An index with the name of each element of the list is created in the given
  1826. # array.
  1827. # Input variables:
  1828. # Elements is a string containing the list of elements.
  1829. # Sep is the character that separates the elements of the list.
  1830. # Output variables:
  1831. # Set is the array.
  1832. # Return value: the number of new elements added to the set.
  1833. function MakeSet(Set,Elements,Sep,  i,Num,Names,nFound,ind) {
  1834.     nFound = 0
  1835.     Num = split(Elements,Names,Sep)
  1836.     for (i = 1; i <= Num; i++) {
  1837.     ind = Names[i]
  1838.     if (!(ind in Set)) {
  1839.         Set[ind]
  1840.         nFound++
  1841.     }
  1842.     }
  1843.     return nFound
  1844. }
  1845.  
  1846. # Returns the number of elements in set Set
  1847. function NumElem(Set,  elem,Num) {
  1848.     for (elem in Set)
  1849.     Num++
  1850.     return Num
  1851. }
  1852.  
  1853. # Remove all elements from Set
  1854. function DeleteAll(Set,  i) {
  1855.     split("",Set,",")
  1856. }
  1857.  
  1858. # Returns a list of all of the elements in Set[], with each pair of elements
  1859. # separated by Sep.
  1860. function set2list(Set,Sep,  list,elem) {
  1861.     for (elem in Set)
  1862.     list = list Sep elem
  1863.     return substr(list,2)    # skip 1st separator
  1864. }
  1865. ### End set library
  1866.