summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKaz Kylheku <kaz@kylheku.com>2016-11-03 06:26:42 -0700
committerKaz Kylheku <kaz@kylheku.com>2016-11-03 06:26:42 -0700
commit935f58e8941f03590bbd5c8482a31b50cf233802 (patch)
tree60e7aba15bda7669cf005af9ce5d4e058e52348f
parentd01a12405fbffb6a68345f72a510bf9e25e8ef95 (diff)
downloadtxr-935f58e8941f03590bbd5c8482a31b50cf233802.tar.gz
txr-935f58e8941f03590bbd5c8482a31b50cf233802.tar.bz2
txr-935f58e8941f03590bbd5c8482a31b50cf233802.zip
Introducing command line option processing system.
* lisplib.c (getopts_set_entries, getopts_instantiate): New functions. (lisplib_init): Register auto-loading for getopt.tl via new functions. * share/txr/stdlib/getopts.tl: New file. * txr.1: Documented new library area.
-rw-r--r--lisplib.c19
-rw-r--r--share/txr/stdlib/getopts.tl320
-rw-r--r--txr.1386
3 files changed, 725 insertions, 0 deletions
diff --git a/lisplib.c b/lisplib.c
index 8b87d976..46514d3b 100644
--- a/lisplib.c
+++ b/lisplib.c
@@ -387,6 +387,24 @@ static val trace_instantiate(val set_fun)
return nil;
}
+static val getopts_set_entries(val dlt, val fun)
+{
+ val name[] = {
+ lit("opt-desc"), lit("opts"),
+ lit("opt"), lit("getopts"), lit("opthelp"),
+ nil
+ };
+ set_dlt_entries(dlt, name, fun);
+ return nil;
+}
+
+static val getopts_instantiate(val set_fun)
+{
+ funcall1(set_fun, nil);
+ load(format(nil, lit("~agetopts.tl"), stdlib_path, nao));
+ return nil;
+}
+
val dlt_register(val dlt,
val (*instantiate)(val),
val (*set_entries)(val, val))
@@ -419,6 +437,7 @@ void lisplib_init(void)
dlt_register(dl_table, awk_instantiate, awk_set_entries);
dlt_register(dl_table, build_instantiate, build_set_entries);
dlt_register(dl_table, trace_instantiate, trace_set_entries);
+ dlt_register(dl_table, getopts_instantiate, getopts_set_entries);
}
val lisplib_try_load(val sym)
diff --git a/share/txr/stdlib/getopts.tl b/share/txr/stdlib/getopts.tl
new file mode 100644
index 00000000..f5909873
--- /dev/null
+++ b/share/txr/stdlib/getopts.tl
@@ -0,0 +1,320 @@
+;; Copyright 2016
+;; Kaz Kylheku <kaz@kylheku.com>
+;; Vancouver, Canada
+;; All rights reserved.
+;;
+;; Redistribution and use in source and binary forms, with or without
+;; modification, are permitted provided that the following conditions are met:
+;;
+;; 1. Redistributions of source code must retain the above copyright notice, this
+;; list of conditions and the following disclaimer.
+;;
+;; 2. Redistributions in binary form must reproduce the above copyright notice,
+;; this list of conditions and the following disclaimer in the documentation
+;; and/or other materials provided with the distribution.
+;;
+;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+;; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+;; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+;; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+;; CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+;; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+(defex opt-error error)
+
+(defstruct opt-desc nil
+ short
+ long
+ helptext
+ arg-p
+ (type :bool)
+ (:static valid-types '(:bool :dec :hex :oct :cint :float :str))
+ (:postinit (me)
+ me.(check)
+ (set me.arg-p (neq me.type :bool))))
+
+(defstruct (sys:opt-parsed name arg desc) nil
+ name
+ arg ;; string, integer, real, ...
+ desc ;; opt-desc
+ (:postinit (me) me.(convert-type)))
+
+(defstruct opts nil
+ (opt-hash (hash :equal-based)) ;; string to sys:opt-parsed
+ in-args
+ out-args
+ opt-processor) ;; sys:opt-processor
+
+(defstruct sys:opt-processor nil
+ od-list
+ (od-hash (hash :equal-based)) ;; string to opt-desc
+ (:postinit (me)
+ me.(build-hash)))
+
+(defun sys:opt-err (. args)
+ (throwf 'opt-error . args))
+
+(defun sys:opt-dash (name)
+ `@(if (> (length name) 1) "-")-@name`)
+
+(defmeth opt-desc check (me)
+ (unless (or (functionp me.type)
+ (fboundp me.type)
+ (member me.type me.valid-types))
+ (error "getopts: type must be a function or valid keyword, not ~s"
+ me.type))
+ (when me.long
+ (when (< (length me.long) 2)
+ (error "getopts: long option ~a has a short name" me.long))
+ (when (eql [me.long 0] #\-)
+ (error "getopts: long option ~a starts with - character" me.long)))
+ (when me.short
+ (when (neq (length me.short) 1)
+ (error "getopts: short option ~a not one character long" me.short))
+ (when (eql [me.short 0] #\-)
+ (error "getopts: short option ~a starts with - character" me.short))))
+
+(defmeth sys:opt-parsed convert-type (me)
+ (let ((name (sys:opt-dash me.name)))
+ (when (and (neq me.desc.type :bool)
+ (eq me.arg :explicit-no))
+ (sys:opt-err "Non-Boolean option ~a explicitly specified as false" name))
+ (caseql me.desc.type
+ (:bool
+ (set me.arg (neq me.arg :explicit-no)))
+ (:dec (set me.arg
+ (or (and (r^$ #/[+\-]?\d+/ me.arg) (int-str me.arg))
+ (sys:opt-err "option ~a needs decimal integer arg, not ~a"
+ name me.arg))))
+ (:hex (set me.arg
+ (or (and (r^$ #/[+\-]?[\da-fA-F]+/ me.arg) (int-str me.arg 16))
+ (sys:opt-err "option ~a needs hexadecimal integer arg, not ~a"
+ name me.arg))))
+ (:oct (set me.arg
+ (or (and (r^$ #/[+\-]?[0-7]+/ me.arg) (int-str me.arg 8))
+ (sys:opt-err "option ~a needs octal integer arg, not ~a"
+ name me.arg))))
+ (:cint (set me.arg
+ (cond
+ ((r^$ #/[+\-]?0x[\da-fA-F]+/ me.arg)
+ (int-str (regsub #/0x/ "" me.arg) 16))
+ ((r^$ #/[+\-]?0[0-7]+/ me.arg)
+ (int-str me.arg 8))
+ ((r^$ #/[+\-]?0[\da-fA-F]+/ me.arg)
+ (sys:opt-err "option ~a argument ~a non octal, but leading 0"
+ name me.arg))
+ ((r^$ #/[+\-]?\d+/ me.arg)
+ (int-str me.arg))
+ (t (sys:opt-err "option ~a needs C style numeric arg, not ~a"
+ name me.arg)))))
+ (:float (set me.arg
+ (cond
+ ([[chand (orf (f^$ #/[+\-]?\d+[.]?([Ee][+\-]?\d+)?/)
+ (f^$ #/[+\-]?\d*[.]?\d+([Ee][+\-]?\d+)?/))
+ flo-str] me.arg])
+ (t (sys:opt-err "option ~a needs floating-point arg, not ~a"
+ name me.arg)))))
+ (:str (set me.arg
+ (or (ignerr (read `"@{me.arg}"`))
+ (sys:opt-err "option ~a needs string lit syntax, ~a given"
+ name me.arg))))
+ (t (set me.arg (call me.desc.type me.arg))))))
+
+(defmeth opts lambda (me key : dfl)
+ (iflet ((o [me.opt-hash key])) o.arg dfl))
+
+(defmeth opts lambda-set (me key val)
+ (iflet ((o [me.opt-hash key]))
+ (set o.arg val)
+ (error "opts: cannot set option ~s to ~s: no such option" key val)))
+
+(defmeth opts add (me opt)
+ (whenlet ((n opt.desc.short))
+ (set [me.opt-hash n] opt))
+ (whenlet ((n opt.desc.long))
+ (set [me.opt-hash n] opt)))
+
+(defmeth sys:opt-processor build-hash (me)
+ (each ((od me.od-list))
+ (each ((str (list od.long od.short)))
+ (when (and str [me.od-hash str])
+ (error "opt-processor: duplicate option ~s" str)))
+ (unless (or od.long od.short)
+ (error "opt-processor: no short or long name in option ~s" od))
+ (each ((str (list od.long od.short)))
+ (when str
+ (set [me.od-hash str] od)))))
+
+(defmeth sys:opt-processor parse-long (me out opt : arg)
+ (iflet ((ieq (unless (stringp arg) (break-str opt "="))))
+ (let ((oname [opt 0..ieq])
+ (arg [opt (succ ieq)..:]))
+ me.(parse-long out oname arg))
+ (let ((od [me.od-hash opt]))
+ (cond
+ ((null od)
+ (sys:opt-err "unrecognized option: --~a" opt))
+ ((and arg od.arg-p)
+ out.(add (new (sys:opt-parsed opt arg od))))
+ ((stringp arg)
+ (sys:opt-err "option --~a doesn't take an argument" opt))
+ (od.arg-p
+ (iflet ((arg (pop out.out-args)))
+ out.(add (new (sys:opt-parsed opt arg od)))
+ (sys:opt-err "option --~a requires an argument" opt)))
+ (out.(add (new (sys:opt-parsed opt arg od))))))))
+
+(defmeth sys:opt-processor parse-shorts (me out opts)
+ (each ((o (split-str opts #//)))
+ (iflet ((od [me.od-hash o]))
+ (let ((arg (when od.arg-p
+ (when (> (length opts) 1)
+ (sys:opt-err "argument -~a includes -~a, which does not clump"
+ opts o))
+ (unless out.out-args
+ (sys:opt-err "option -~a requires an argument" o))
+ (pop out.out-args))))
+ out.(add (new (sys:opt-parsed o arg od))))
+ (sys:opt-err "unrecognized option: -~a" o))))
+
+(defmeth sys:opt-processor parse-opts (me args)
+ (let ((out (new opts in-args args out-args args opt-processor me)))
+ (whilet ((arg (pop out.out-args)))
+ (cond
+ ((equal "--" arg) (return))
+ ((r^ #/--no-/ arg) me.(parse-long out [arg 5..:] :explicit-no))
+ ((r^ #/--/ arg) me.(parse-long out [arg 2..:]))
+ ((r^ #/-.+/ arg) me.(parse-shorts out [arg 1..:]))
+ (t (push arg out.out-args)
+ (return))))
+ out))
+
+(defun sys:wdwrap (string columns)
+ (let ((words (tok-str string #/\S+/))
+ line)
+ (build
+ (whilet ((word (pop words))
+ (wpart (cond
+ ((and word (r^$ #/\w+[\w\-]*\w[.,;:!?"]?/ word))
+ (split-str word #/-/))
+ (word (list word))))
+ (wpart-orig wpart))
+ (whilet ((wp0 (eq wpart wpart-orig))
+ (wp (pop wpart))
+ (w (if wp `@wp@(if wpart "-")`)))
+ (cond
+ ((not line)
+ (set line w))
+ ((> (+ (length line) (length w) 1) columns)
+ (add line)
+ (set line w))
+ (t (set line `@line@(if wp0 " ")@w`)))))
+ (if line
+ (add line)))))
+
+(defun opt (short long : (type :bool) helptext)
+ (new opt-desc short short long long helptext helptext type type))
+
+(defun getopts (opt-desc-list args)
+ (let ((opr (new sys:opt-processor od-list opt-desc-list)))
+ opr.(parse-opts args)))
+
+(defun opthelp (opt-desc-list : (stream *stdout*))
+ (let ((sorted [sort (copy-list (remove-if (op null @1.helptext)
+ opt-desc-list)) :
+ (do if @1.long @1.long @1.short)])
+ (undocumented (keep-if (op null @1.helptext) opt-desc-list)))
+ (put-line "\nOptions:\n")
+ (each ((od sorted))
+ (let* ((type (if (keywordp od.type)
+ (upcase-str (tostringp od.type))
+ "ARG"))
+ (long (if od.long
+ `--@{od.long}@(if od.arg-p `=@type`)`))
+ (short (if od.short
+ `-@{od.short}@(if od.arg-p ` @type`)`))
+ (ls (cond
+ ((and long short) `@{long 21} (@short)`)
+ (long long)
+ (short `@{"" 21} @short`)))
+ (lines (if od.helptext (sys:wdwrap od.helptext 43))))
+ (put-line ` @{ls 34}@(pop lines)`)
+ (while lines
+ (put-line ` @{"" 34}@(pop lines)`))))
+ (put-line)
+ (when undocumented
+ (put-line "Undocumented options:\n")
+ (let* ((undoc-str `@{[mapcar sys:opt-dash
+ (flatten (mappend (op list @1.short @1.long)
+ undocumented))] ", "}`))
+ (each ((line (sys:wdwrap undoc-str 75)))
+ (put-line ` @line`)))
+ (put-line))
+ (put-line "Notes:\n")
+ (let* ((have-short (some sorted (usl short)))
+ (have-long (some sorted (usl long)))
+ (have-arg-p (some sorted (usl arg-p)))
+ (have-bool (some sorted (op eq @1.type :bool)))
+ (texts (list (if have-short
+ "Short options can be invoked with long syntax: \
+ \ for example, --a can be used when -a exists.\
+ \ Short no-argument options can be clumped into\
+ \ one argument as exemplified by -xyz. ")
+ (if have-bool
+ (if have-arg-p
+ "Options that take no argument are Boolean:"
+ (if undocumented
+ "All documented options are Boolean:"
+ "All options are Boolean:")))
+ (if have-bool
+ "they are true when present, false when absent.")
+ (if have-bool
+ (if have-arg-p
+ " The --no- prefix can explicitly specify \
+ \ Boolean options as false: if a Boolean option\
+ \ X exists,\
+ \ --no-X specifies it as false. This is useful\
+ \ for making false those options which default\
+ \ to true."
+ " The --no- prefix can explicitly specify \
+ \ options as false: if an X option exists,\
+ \ --no-X specifies it as false. This is useful\
+ \ for making false those options which default\
+ \ to true."))
+ (if (not have-long)
+ "Note the double dash on --no.")
+ (if (and have-short have-long)
+ "The --no- prefix can be applied to a short\
+ \ or long option name.")
+ (if have-arg-p
+ "The argument to a long option can be given in one\
+ \ argument as --option=arg or as a separate\
+ \ argument using --option arg.")
+ "The special argument -- can be used where an option\
+ \ may appear. It means \"end of options\": the\
+ \ arguments which follow are not treated as options\
+ \ even if they look like options.")))
+ (mapdo (do put-line ` @1`)
+ (sys:wdwrap `@{(flatten texts)}` 77)))
+ (put-line)
+ (whenlet ((types (remq :bool (uniq (mapcar (usl type) sorted)))))
+ (put-line "Type legend:\n")
+ (each ((ty types))
+ (iflet ((ln (caseql ty
+ (:dec " DEC - Decimal integer: -123, 0, 5, +73")
+ (:hex " HEX - Hexadecimal integer -EF, 2D0, +9A")
+ (:oct " OCT - Octal integer: -773, 5677, +326")
+ (:cint " CINT - C-style integer: leading 0 octal,\
+ \ leading 0x hex, else decimal;\n\
+ \ leading sign allowed: -0777, 0xFDC, +123")
+ (:float " FLOAT - Floating-point: -1.3e+03, +5, 3.3,\
+ \ 3., .5, .12e9, 53.e-3, 3e-015")
+ (:str " STR - String with embedded escapes, valid\
+ \ as TXR Lisp string literals\n\
+ \ syntax: foo, foo\\tbar, abc\\nxyz"))))
+ (put-line ln)))
+ (put-line))))
diff --git a/txr.1 b/txr.1
index c6fffc4c..abdd2122 100644
--- a/txr.1
+++ b/txr.1
@@ -41322,6 +41322,392 @@ is restored by the
.code unwind-protect
cleanup form.
+.SS* Command Line Option Processing
+
+\*(TL provides a support for recognizing, extracting and validating
+the POSIX-style options from a list of command-line arguments.
+
+The supported options can be defined as a list of option descriptor
+objects each of which is constructed by a call to the
+.code opt
+function. Each option can have a long or short name, or both,
+a type, and a description.
+
+The
+.code getopts
+function takes a list of option descriptors, and a list of arguments,
+producing a parse, or else throwing an exception of type
+.code opt-error
+if an error is detected. The returned object, an instance of struct type
+.codn opts ,
+can then be queried for specific option values, or for the remaining non-option
+arguments.
+
+The
+.code opthelp
+function takes a list of option descriptors and an output stream,
+and generates help text on that stream. A program supporting a
+.code --help
+option can use this to generate that portion of its help text which
+describes the available options, as well as the conventions that they use.
+
+.NP* Command Line Option Conventions
+
+A command line option can have a short or long name. A short name is always
+one-character long, and treated specially in the command line syntax. Long
+options have names two or more characters long. An option can have both a long
+and short name. Options may not begin with the
+.code -
+(ASCII dash) character. A long option may not contain the
+.code =
+character.
+
+Short options are invoked by specifying an argument with a single leading
+.code -
+followed by the option character. Multiple short options which take
+no argument can be "clumped": combined into a single argument consisting of
+a single
+.code -
+followed by multiple short option characters.
+
+An option can take an argument, in which case the argument is required.
+An option which takes no argument is Boolean, and a Boolean option
+never takes an argument: "takes argument" and "Boolean" are synonymous.
+
+Long options are invoked as an argument which begins with a
+.code --
+(double dash)
+immediately followed by the name. When a long option takes an argument,
+it is mandatory. It must be specified in the same argument, separated
+from the name by the
+.code =
+character. If that is omitted, then the next command line argument
+is taken as the argument. That argument is removed, and not recognized as
+an option, even if it looks like one.
+
+A Boolean long option can be explicitly specified as false using the
+.code --no-
+prefix rather than the
+.code --
+prefix.
+
+Short options may be invoked using long name syntax; if
+.code a
+is a short option, then it may be referenced on the command line as
+.code --a
+and treated as a long option in all other ways, including the use
+of
+.code --no-
+to explicitly specify false for a Boolean option.
+
+If a short option takes an argument, it may not clump with other
+short option. The following command line argument is taken as the
+options argument. That argument is removed and is not recognized as
+an option even if it looks like one.
+
+If the command line argument
+.code --
+occurs in the command line where an option would otherwise be recognized,
+it signifies the end of the options. The subsequent arguments are the
+non-option arguments, even if they resemble options.
+
+.NP* Command Line Processing Example
+
+The following example illustrates a complete \*(TL program which
+parses command line options:
+
+.cblk
+ (defvarl options
+ (list (opt "v" "verbose" :dec
+ "Verbosity level. Higher values produce more chatter.")
+ (opt nil "help" :bool
+ "List this help text.")
+ (opt "x" nil :hex
+ "The X factor: a number with a mysterious\e \e
+ interpretation, affecting the program\e \e
+ behavior in strange ways.")
+ (opt "z" nil) ;; undocumented option
+ (opt nil "cee" :cint
+ "C style integer.")
+ (opt "g" "gravity" :float
+ "Gravitational constant. This gives\e \e
+ the gravitational field\e \e
+ strength at the Earth's surface.")
+ (opt "l" "lit" :str
+ "A character string given in TXR Lisp notation.")
+ (opt "c" nil 'upcase-str
+ "Custom treatment: ARG is converted to upper case.")
+ (opt "b" "bool" :bool
+ "A flag you can flip true.")))
+
+ (defvarl prog-name *load-path*)
+
+ (let ((o (getopts options *args*)))
+ (when [o "help"]
+ (put-line "Usage:\en")
+ (put-line ` @{prog-name} [options] arg*`)
+ (opthelp options)
+ (exit 0))
+ (put-line `args after opts are: @{o.out-args ", "}`))
+.cble
+
+.coNP Structure @ opt-desc
+.synb
+.mets (defstruct opt-desc
+.mets \ \ short long helptext type
+.mets \ \ ... < unspecified << slots )
+.syne
+.desc
+The
+.code opt-desc
+structure describes a single command line option.
+
+The
+.code short
+and
+.code long
+slots are either
+.code nil
+or else hold strings.
+The
+.code short
+slot gives the option's short name: a one-character-long
+string which may not be the ASCII dash character
+.codn - .
+The
+.code long
+slot gives the option's long name: a string two or more
+characters long which doesn't begin with a dash.
+An option must have at least one of these names.
+
+The
+.code helptext
+slot provides a descriptive string. This string may be long. The
+.code opthelp
+function displays this text, formatting into multiple lines as necessary.
+If
+.code helptext
+is
+.codn nil ,
+the option is considered undocumented.
+
+The
+.code type
+slot may be a symbol naming a global function which takes one argument,
+or it may be such a function object. Otherwise it must be one of the
+following keyword symbols:
+.RS
+.coIP :bool
+This indicates that the type of the option is Boolean. Such
+an option doesn't take any argument. Its value is
+.code t
+or
+.codn nil .
+.coIP :dec
+This indicates that the option requires an argument, which is a
+decimal integer with an optional positive or negative sign.
+This argument is converted to an integer object.
+.coIP :hex
+This type indicates that the option requires an argument consisting
+of a hexadecimal integer with an optional positive or negative sign.
+This is converted to an integer object.
+.coIP :oct
+This type indicates that the option requires an argument consisting
+of a octal integer with an optional positive or negative sign.
+This is converted to an integer object.
+.coIP :cint
+This type indicates that the option requires an integer argument
+whose format conforms to one of three C language conventions in most respects,
+other than that this integer may have an arbitrary range.
+All forms may carry an optional positive or negative leading sign
+at the very beginning.
+The first convention consists of decimal digits, which must not have
+a superfluous leading zero. The second convention consists of octal
+digits which are introduced by an extra leading zero.
+The third convention consists of hexadecimal digits introduced by the
+.code 0x
+prefix.
+.coIP :float
+This type indicates a decimal floating-point argument, which is converted to
+a floating-point number. Its basic form is: an optional leading plus or
+minus sign, followed by a sequence of one or more digits which may contain
+a single decimal point anywhere, including the very beginning of the
+sequence or at the end, optionally followed by the letter
+.code e
+or
+.code E
+followed by a decimal integer which may have a leading positive or negative
+sign, and include leading zeros.
+
+.coIP :str
+This type indicates that the argument consists of the interior notation of
+a TXR Lisp character string. It is processed by adding a double quote
+at the beginning or end, and parsed as a string literal. This parsing must
+successfully yield a string object, otherwise the argument is ill-formed.
+.RE
+.PP
+
+If
+.code type
+is a function, then the option requires an argument. The argument string
+is passed to the function, and the value is whatever the function returns.
+
+The
+.code opt-desc
+structure may have additional slots which are not specified.
+
+The
+.code opt
+convenience function is provided for constructing
+.code opt-desc
+objects.
+
+.coNP Structure @ opts
+.synb
+.mets (defstruct opts nil
+.mets \ \ in-args out-args
+.mets \ \ ... < unspecified << slots )
+.syne
+.desc
+The
+.code opts
+structure represents a parsed command line, containing decoded
+information obtained from the options, and an indication where
+the non-option arguments start.
+
+The
+.code opts
+structure supports direct indexing for option retrieval.
+That is the only documented interface for accessing the parsed
+options; the implementation of the information set describing
+the parsed options is unspecified.
+
+The
+.code in-args
+slot holds the original argument list.
+
+The
+.code out-args
+member holds the tail of the argument list consisting of the non-option
+arguments.
+
+The mechanism by means of which
+.code out-args
+is calculated, and by means of which the information about the
+options is populated, is unspecified. The only interface to that
+mechanism is the
+.code getopts
+function.
+
+The
+.code opts
+object supports indexing, including indexed assignment.
+
+If
+.code o
+is an instance of
+.code opts
+returned by
+.codn getopts ,
+then the expression
+.code "[o \(dqv\(dq]"
+tests whether the option
+.str v
+is available in
+.codn o ;
+that is, whether it has been specified in the command line.
+If so, then its associated value is returned, otherwise
+.code nil
+is returned. This
+.code nil
+is ambiguous: for a Boolean option it indicates that either
+the option was not specified, or that it was explicitly
+specified as false. For a Boolean option that was specified
+(positively), the value
+.code t
+is returned.
+
+The expression
+.code "[o \(dqv\(dq dfl]"
+yields the value of option
+.str v
+if that option has been specified. If the option hasn't
+been specified, then the expression yields the value
+.codn dfl .
+
+Assigning to
+.code "[o \(dqv\(dq]"
+is possible. This replaces the value associated with option
+.strn v .
+The assignment is erroneous if no such option was parsed
+from the command line, even if it is a valid option.
+
+.coNP Function @ getopts
+.synb
+.mets (getopts < option-desc-list << arg-list )
+.syne
+.desc
+The
+.code getopts
+function takes a list of
+.code opt-desc
+structures and a list of strings
+.meta arg-list
+representing command line arguments.
+
+The
+.meta arg-list
+is parsed. If the parse is unsuccessful, an exception of type
+.code opt-error
+is thrown, derived from
+.codn error .
+
+If there are problems in
+.code option-desc-list
+itself, then an exception of type
+.code error
+is thrown.
+
+If the parse is successful,
+.code getopts
+returns an instance of the
+.code opts
+structure describing the parsed opts, and listing the non-option
+arguments.
+
+.coNP Function @ opthelp
+.synb
+.mets (opthelp < opt-desc-list <> [ stream ])
+.syne
+.desc
+The
+.code opthelp
+function processes the list of
+.code opt-desc
+structures
+.meta opt-desc-list
+and compiles a customized body of help text describing all of the
+options, as well as general description of the command line option
+conventions to guide the user in in the correct use of command
+line options.
+
+The text is formatted to fit within 79 columns, and begins and ends with a
+blank line. Its format consists of headings which begin in the first column,
+and paragraphs and tables which feature a two space left margin.
+A blank line follows each section heading. The heading begins with a capital
+letter. Its remaining words are uncapitalized, and it ends with a colon.
+
+The text is sent to
+.metn stream ,
+if specified. This argument defaults to
+.codn *stdout* .
+
+If there are problems in
+.code option-desc-list
+itself, then an exception of type
+.code error
+is thrown.
+
.SS* System Programming
.coNP Accessor @ errno
.synb