Merge branch 'master' of rdmp:/mnt/web-sites/dmbcs/guile into rdmp
This commit is contained in:
commit
92a77da732
12 changed files with 1724 additions and 221 deletions
|
|
@ -90,6 +90,7 @@ guile_TEXINFOS = preface.texi \
|
|||
libguile-extensions.texi \
|
||||
api-init.texi \
|
||||
mod-getopt-long.texi \
|
||||
mod-command-line-processor.texi \
|
||||
statprof.texi \
|
||||
sxml.texi \
|
||||
texinfo.texi \
|
||||
|
|
|
|||
|
|
@ -357,7 +357,7 @@ available through both Scheme and C interfaces.
|
|||
* SLIB:: Using the SLIB Scheme library.
|
||||
* POSIX:: POSIX system calls and networking.
|
||||
* Web:: HTTP, the web, and all that.
|
||||
* getopt-long:: Command line handling.
|
||||
* Command Line Processor:: Command line handling.
|
||||
* SRFI Support:: Support for various SRFIs.
|
||||
* R6RS Support:: Modules defined by the R6RS.
|
||||
* R7RS Support:: Modules defined by the R7RS.
|
||||
|
|
@ -381,7 +381,7 @@ available through both Scheme and C interfaces.
|
|||
@include slib.texi
|
||||
@include posix.texi
|
||||
@include web.texi
|
||||
@include mod-getopt-long.texi
|
||||
@include mod-command-line-processor.texi
|
||||
@include srfi-modules.texi
|
||||
@include r6rs.texi
|
||||
@include r7rs.texi
|
||||
|
|
|
|||
240
doc/ref/mod-command-line-processor.texi
Normal file
240
doc/ref/mod-command-line-processor.texi
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
@c -*-texinfo-*-
|
||||
@c This is part of the GNU Guile Reference Manual.
|
||||
@c Copyright (C) 2020
|
||||
@c Free Software Foundation, Inc.
|
||||
@c See the file guile.texi for copying conditions.
|
||||
|
||||
@node Command Line Processor, SRFI Support, Web, Guile Modules
|
||||
@section The (ice-9 command-line-processor) Module
|
||||
|
||||
As its name implies, the @code{(ice-9 command-line-processor)} facility
|
||||
is supposed to be a one-stop shop for dealing with the command line. It
|
||||
is inspired by the GNU libc's @code{argp} parser, and can be regarded as
|
||||
a high-level wrapper around the @xref{getopt-long} module. It is
|
||||
designed to provide two specific features.
|
||||
|
||||
@itemize @bullet
|
||||
@item
|
||||
Higher-level (easier to use) abstraction of the command-line user
|
||||
interface to this application, including available options and program
|
||||
meta-data.
|
||||
|
||||
@item
|
||||
Automatic handling of @code{--help}, @code{--version} and @code{--usage}
|
||||
flags. This means meeting GNU coding standards, and helping to
|
||||
‘regularize’ the output from these commands.
|
||||
@end itemize
|
||||
|
||||
The module provides a single syntax extension to the guile language:
|
||||
@code{process-command-line}.
|
||||
|
||||
@menu
|
||||
* Command Line Examples:: Examples of use.
|
||||
* Command Line Reference:: Detailed specification of the procedure.
|
||||
@end menu
|
||||
|
||||
Also see @xref{Command Line Format} for precise details of allowed
|
||||
command-line formats.
|
||||
|
||||
@node Command Line Examples, Command Line Reference, Command Line Processor, Command Line Processor
|
||||
@subsection A Simple Example
|
||||
|
||||
A (silly) program which takes two options, the second of which may
|
||||
provide a numerical value, might include the following lines.
|
||||
|
||||
@lisp
|
||||
(use-modules (ice-9 command-line-processor))
|
||||
|
||||
(process-command-line (command-line)
|
||||
application "my-app"
|
||||
option (--option -o "the first option")
|
||||
option (--test=3 -t "another option" string->number))
|
||||
|
||||
(when --option (do-something))
|
||||
(when --test (display --test) (newline))
|
||||
@end lisp
|
||||
|
||||
@noindent
|
||||
and then the program could be called with command lines like
|
||||
|
||||
@example
|
||||
$ ./my-app -o
|
||||
@end example
|
||||
|
||||
@noindent
|
||||
or
|
||||
|
||||
@example
|
||||
$ ./my-app --option -t 4 file-1 file-2
|
||||
@end example
|
||||
|
||||
@subsection GNU Mcron
|
||||
|
||||
For realistic code, here is the first line of executable code GNU's
|
||||
@code{mcron} program has (the @code{%} tokens are filled in by the build
|
||||
system).
|
||||
|
||||
@lisp
|
||||
(process-command-line (command-line)
|
||||
application "mcron"
|
||||
version "%VERSION%"
|
||||
usage "[OPTIONS ...] [FILES ...]"
|
||||
help-preamble
|
||||
"Run unattended jobs according to instructions in the FILES... "
|
||||
"(‘-’ for standard input), or use all the files in ~/.config/cron "
|
||||
"(or the deprecated ~/.cron) with .guile or .vixie extensions.\n"
|
||||
"Note that --daemon and --schedule are mutually exclusive."
|
||||
option (--daemon -d "run as a daemon process")
|
||||
option (--schedule=8 -s string->number
|
||||
"display the next N (or 8) jobs that will be run,"
|
||||
"and then exit")
|
||||
option (--stdin=guile short-i (λ (in) (or (string=? in "guile")
|
||||
(string=? in "vixie")))
|
||||
"format of data passed as standard input or file "
|
||||
"arguments, 'guile' or 'vixie' (default guile)")
|
||||
help-postamble
|
||||
"Mandatory or optional arguments to long options are also mandatory or "
|
||||
"optional for any corresponding short options."
|
||||
bug-address "%PACKAGE_BUGREPORT%"
|
||||
copyright "2003, 2006, 2014, 2020 Free Software Foundation, Inc."
|
||||
license GPLv3)
|
||||
@end lisp
|
||||
|
||||
@noindent
|
||||
after which there are four new variable bindings in the present
|
||||
namespace: literally, @code{--daemon}, @code{--stdin}, @code{--schedule}
|
||||
and @code{--!} (the latter holds all the command-line arguments that did
|
||||
not partake in option processing) whose values depend on the specific
|
||||
command-line options the end user furnished... except that if the user
|
||||
had typed
|
||||
|
||||
@example
|
||||
$ mcron --help
|
||||
@end example
|
||||
|
||||
@noindent
|
||||
they would be greeted with
|
||||
|
||||
@example
|
||||
Usage: mcron [OPTIONS ...] [FILES ...]
|
||||
Run unattended jobs according to instructions in the FILES...
|
||||
(`-' for standard input), or use all the files in ~/.config/cron
|
||||
(or the deprecated ~/.cron) with .guile or .vixie extensions.
|
||||
|
||||
Note that --daemon and --schedule are mutually exclusive.
|
||||
|
||||
-d, --daemon run as a daemon process
|
||||
-s[N], --schedule[=N] display the next N (or 8) jobs that will be run,
|
||||
and then exit
|
||||
-i[N], --stdin[=N] format of data passed as standard input or file
|
||||
arguments, 'guile' or 'vixie' (default guile)
|
||||
-h, --help display this help and exit
|
||||
-V, --version output version information and exit
|
||||
-u, --usage show brief usage summary
|
||||
|
||||
Mandatory or optional arguments to long options are also mandatory or
|
||||
optional for any corresponding short options.
|
||||
|
||||
Send bug reports to bug-mcron@@gnu.org.
|
||||
@end example
|
||||
|
||||
@noindent
|
||||
and the program would immediately have exited.
|
||||
|
||||
@node Command Line Reference, , Command Line Examples, Command Line Processor
|
||||
@subsection process-command-line
|
||||
|
||||
@deffn {Scheme Procedure} process-command-line COMMAND-LINE SPECIFICATION
|
||||
Process the @var{COMMAND-LINE} according to the application
|
||||
@var{SPECIFICATION}.
|
||||
|
||||
@var{COMMAND-LINE} is a list of strings, such as that returned from the
|
||||
core @code{(command-line)} function: the first string is the name of the
|
||||
command being run, and the rest are the space-separated tokens that
|
||||
followed the command on the command line.
|
||||
|
||||
@var{SPECIFICATION} is a form holding a space-separated mix of selection
|
||||
words followed by their respective declarations. The selection words
|
||||
are @code{application}, @code{author}, @code{bug-address},
|
||||
@code{copyright}, @code{help-preamble}, @code{help-postamble},
|
||||
@code{license}, @code{option}, @code{usage} and @code{version}, and can
|
||||
appear in any order.
|
||||
|
||||
@table @asis
|
||||
@item @code{application}
|
||||
should be followed by a string: the name of the application with
|
||||
possibly the package name in parentheses afterwards. This may appear
|
||||
zero or one times, but ideally should be present.
|
||||
@item @code{author}
|
||||
should be followed by a string giving the name of one of the packageʼs
|
||||
authors. This selection word can be repeated as many times as necessary
|
||||
to provide the names of all authors.
|
||||
@item @code{bug-address}
|
||||
should be followed by a string giving the URL of a contact-point for
|
||||
sending bug reports, such as an e-mail address or web address of
|
||||
bug-tracking system interface. This can appear zero or one times.
|
||||
@item @code{copyright}
|
||||
should be followed by a string containing a list of years and an entity
|
||||
to whom the copyright is assigned. This may be repeated to list other
|
||||
assignees.
|
||||
@item @code{help-preamble}
|
||||
should be followed by a number of strings which make up a short
|
||||
paragraph of text displayed before a full list of the available program
|
||||
options.
|
||||
@item @code{help-postamble}
|
||||
like the preamble, this is followed by strings which make up a paragraph
|
||||
of text, shown after the list of options.
|
||||
@item @code{license}
|
||||
can be followed by one of the words ‘GPLv3’ [this is currently the only
|
||||
standard choice implemented], or else a string which briefly gives out
|
||||
the terms of the license. Can appear zero or one times.
|
||||
@item @code{option}
|
||||
is followed by an option declaration, described below. You can specify
|
||||
any number of options.
|
||||
@item @code{usage}
|
||||
is followed by a string describing the usage of the application on one
|
||||
line. This can appear zero or one times, but ideally should be present.
|
||||
@item @code{version}
|
||||
is followed by a string providing the current version number of this
|
||||
program. This item may appear zero or one times.
|
||||
@end table
|
||||
|
||||
The ‘option’ declaration is followed by another form bracketed by
|
||||
parentheses and holding a space-separated mix of declarations (order
|
||||
irrelevant).
|
||||
|
||||
@itemize @bullet
|
||||
@item
|
||||
A word beginning with two hyphens, an optional exclamation point,
|
||||
alphabetic letters (intermixed with digits, underscore and hyphens), an
|
||||
optional equals sign, and an optional further word. There must be
|
||||
exactly one of these, and that determines the long name of the option.
|
||||
An exclamation point indicates that the option MUST appear on the
|
||||
command line, an equals indicates that the option MUST have a value
|
||||
unless it is followed in the specification by a value, in which case the
|
||||
value on the command-line is optional and the one in the specification
|
||||
will be taken as the default when not given on the command line.
|
||||
@item
|
||||
A word comprised of one hyphen and one letter. There can be exactly
|
||||
zero or one of these, and it declares that the option has this short
|
||||
form available on the command-line. As a very special exception: if you
|
||||
want to use @code{-i} as an option, it must be specified with the
|
||||
identifier @code{short-i} (a naked @emph{-i} is read as a complex
|
||||
number); ditto @code{short-I} for @code{-I}.
|
||||
@item
|
||||
A number of strings which are catenated together to provide a short,
|
||||
succinct description of the option. These strings should be
|
||||
approximately half the width of a page, i.e. about 40 characters.
|
||||
@item
|
||||
A function which will be used as a predicate to decide if a value is
|
||||
allowable for this option. There should be zero or one of these.
|
||||
@end itemize
|
||||
|
||||
For the precise presentation of options on the command-line, the reader
|
||||
should refer to the @xref{Command Line Format}, part of the description
|
||||
of the @xref{getopt-long} module, which underlies the present one.
|
||||
|
||||
@end deffn
|
||||
|
||||
|
||||
@include mod-getopt-long.texi
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
@c Free Software Foundation, Inc.
|
||||
@c See the file guile.texi for copying conditions.
|
||||
|
||||
@node getopt-long
|
||||
@node getopt-long, SRFI Support, Command Line Processor
|
||||
@section The (ice-9 getopt-long) Module
|
||||
|
||||
The @code{(ice-9 getopt-long)} facility is designed to help parse
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
@c Free Software Foundation, Inc.
|
||||
@c See the file guile.texi for copying conditions.
|
||||
|
||||
@node SRFI Support
|
||||
@node SRFI Support, R6RS Support, Command Line Processor, Guile Modules
|
||||
@section SRFI Support Modules
|
||||
@cindex SRFI
|
||||
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ SOURCES = \
|
|||
ice-9/futures.scm \
|
||||
ice-9/gap-buffer.scm \
|
||||
ice-9/getopt-long.scm \
|
||||
ice-9/command-line-processor.scm \
|
||||
ice-9/hash-table.scm \
|
||||
ice-9/hcons.scm \
|
||||
ice-9/history.scm \
|
||||
|
|
|
|||
646
module/ice-9/command-line-processor.scm
Normal file
646
module/ice-9/command-line-processor.scm
Normal file
|
|
@ -0,0 +1,646 @@
|
|||
;;;; command-line-processor.scm --- command-line options processing
|
||||
;;;; -*- scheme -*-
|
||||
;;;;
|
||||
;;;; Copyright (C) 1998, 2001, 2006, 2009, 2011, 2020
|
||||
;;;; Free Software Foundation, Inc.
|
||||
;;;;
|
||||
;;;; This library is free software; you can redistribute it and/or
|
||||
;;;; modify it under the terms of the GNU Lesser General Public
|
||||
;;;; License as published by the Free Software Foundation; either
|
||||
;;;; version 3 of the License, or (at your option) any later version.
|
||||
;;;;
|
||||
;;;; This library is distributed in the hope that it will be useful,
|
||||
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
;;;; Lesser General Public License for more details.
|
||||
;;;;
|
||||
;;;; You should have received a copy of the GNU Lesser General Public
|
||||
;;;; License along with this library; if not, write to the Free Software
|
||||
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
;;;; 02110-1301 USA
|
||||
|
||||
;;; Author: Dale Mellor <guile-qf1qmg@rdmp.org> May, 2020
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; Where the Guile (ice-9 getopt-long) module, modelled after the GNU C
|
||||
;;; libraryʼs ‘getopt_long’ function, allows an application to construct
|
||||
;;; a grammar prescribing the decomposition of the command-line options,
|
||||
;;; this module, inspired by the C libraryʼs ‘argp’ parser, gives the
|
||||
;;; application a higher-level paradigm in which the command-line
|
||||
;;; processing is specified declaratively. This includes enough of the
|
||||
;;; application meta-data and some fragmentary help strings for the
|
||||
;;; completely automatic generation of responses to GNU-standard
|
||||
;;; ‘--help’, ‘--version’ and ‘--usage’ options, thus alleviating the
|
||||
;;; need of the application itself to deal with these things.
|
||||
;;;
|
||||
;;; The module has three specific aims.
|
||||
;;;
|
||||
;;; 1) Provide higher-level declarative interface, easier to use.
|
||||
;;;
|
||||
;;; 2) Automatically respond to --help, --version and --usage
|
||||
;;; options.
|
||||
;;;
|
||||
;;; 3) Allow amalgamation of specifications, so that an application
|
||||
;;; can mix in requirements from modules into its own option
|
||||
;;; specification--THIS IS NOT CURRENTLY IMPLEMENTED.
|
||||
;;;
|
||||
;;; There is just one function which needs to be called to get all of
|
||||
;;; this functionality: it is ‘process-command-line’, and has the side
|
||||
;;; effect that new variable bindings appear in the current module
|
||||
;;; corresponding to all the options. For example, if a declared option
|
||||
;;; is ‘--do-this’, then a variable called, literally, ‘--do-this’ will
|
||||
;;; be injected in the current namespace and will have the value
|
||||
;;; provided on the command-line, or simply #t or #f to indicate whether
|
||||
;;; or not that option was present on the command line.
|
||||
;;;
|
||||
;;; Alternatively, it is possible to create and compose the
|
||||
;;; specification in separate steps, and then call the above method with
|
||||
;;; the results. The functions ‘command-line-specification’ and
|
||||
;;; ‘merge-command-line-specifications’ are provided to this end.
|
||||
|
||||
;;; (process-command-line COMMAND-LINE SPECIFICATION)
|
||||
;;; Process the COMMAND-LINE according to the application SPECIFICATION.
|
||||
;;;
|
||||
;;; COMMAND-LINE is a list of strings, such as that returned from the
|
||||
;;; core ‘command-line’ function.
|
||||
;;;
|
||||
;;; SPECIFICATION is a form holding a space-separated mix of selection
|
||||
;;; words followed by their respective declarations. The selection
|
||||
;;; words are ‘application’, ‘author’, ‘bug-address’, ‘copyright’,
|
||||
;;; ‘help-preamble’, ‘help-postamble’, ‘license’, ‘option’, ‘usage’ and
|
||||
;;; ‘version’, and can appear in any order.
|
||||
;;;
|
||||
;;; ‘application’ should be followed by a string: the name of the
|
||||
;;; application with possibly the package name in
|
||||
;;; parentheses afterwards
|
||||
;;; ‘author’ should be followed by a string giving the name of one of
|
||||
;;; the packageʼs authors. This selection word can be
|
||||
;;; repeated as many times as necessary to provide the names
|
||||
;;; of all authors.
|
||||
;;; ‘bug-address’ should be followed by a string giving the URL of a
|
||||
;;; contact-point for sending bug reports, such as an
|
||||
;;; e-mail address or web address of bug-tracking system
|
||||
;;; interface
|
||||
;;; ‘copyright’ should be followed by a string containing a list of
|
||||
;;; years and an entity to whom the copyright is assigned.
|
||||
;;; This may be repeated to list other assignees
|
||||
;;; ‘help-preamble’ should be followed by a number of strings which
|
||||
;;; make up a short paragraph of text displayed before
|
||||
;;; a full list of the available program options
|
||||
;;; ‘help-postamble’, like the preamble, is followed by strings which
|
||||
;;; make up a paragraph of text, shown after the list
|
||||
;;; of options
|
||||
;;; ‘license’ can be followed by one of the words ‘GPLv3’ [this is
|
||||
;;; currently the only standard choice implemented], or else
|
||||
;;; a string which briefly gives out the terms of the license
|
||||
;;; ‘option’ is followed by an option declaration, described below
|
||||
;;; ‘usage’ is followed by a string describing the usage of the
|
||||
;;; application on one line
|
||||
;;; ‘version’ is followed by a string providing the current version
|
||||
;;; number of this program
|
||||
;;;
|
||||
;;; The ‘option’ declaration is followed by another form bracketed by
|
||||
;;; parentheses and holding a space-separated mix of declarations (order
|
||||
;;; irrelevant).
|
||||
;;;
|
||||
;;; A word beginning with two hyphens, an optional exclamation point,
|
||||
;;; alphabetic letters, an optional equals sign, and an optional
|
||||
;;; further word. There must be exactly one of these, and they
|
||||
;;; determine the long name of the option. An exclamation point
|
||||
;;; indicates that the option MUST appear on the command line, an
|
||||
;;; equals indicates that the option MUST have a value unless it is
|
||||
;;; followed in the specification by a value, in which case the value
|
||||
;;; on the command-line is optional and the one in the specification
|
||||
;;; will be taken as the default when not given on the command line.
|
||||
;;;
|
||||
;;; A word comprised of one hyphen and one letter. There can be
|
||||
;;; exactly zero or one of these, and it declares that the option has
|
||||
;;; this short form available on the command-line. As a very special
|
||||
;;; exception: if you want to use ‘-i’ as an option, it must be
|
||||
;;; specified with the identifier ‘short-i’ (a naked /-i/ is read as
|
||||
;;; a complex number); ditto ‘short-I’ for ‘-I’.
|
||||
;;;
|
||||
;;; A number of strings which are catenated together to provide a
|
||||
;;; short, succinct description of the option. These strings should
|
||||
;;; be approximately half the width of a page, i.e. about 40
|
||||
;;; characters.
|
||||
;;;
|
||||
;;; A function which will be used as a predicate to decide if a value
|
||||
;;; is allowable for this option. There should be zero or one of
|
||||
;;; these.
|
||||
;;;
|
||||
;;; For the precise presentation of options on the command-line, the
|
||||
;;; reader should refer to the description of the ‘getopt-long’ module,
|
||||
;;; which underlies the present one.
|
||||
;;;
|
||||
;;; At this point a short example is in order. The main entry point for
|
||||
;;; the GNU Mcron program has as its first clause
|
||||
;;;
|
||||
;;; (process-command-line (command-line)
|
||||
;;; application "mcron"
|
||||
;;; version "1.4"
|
||||
;;; usage "[OPTIONS]... [FILES]..."
|
||||
;;; help-preamble
|
||||
;;; "Run an mcron process according to the specifications in the FILE... "
|
||||
;;; "(`-' for standard input), or use all the files in ~/.config/cron "
|
||||
;;; "(or the deprecated ~/.cron) with .guile or .vixie extensions.\n"
|
||||
;;; "Note that --daemon and --schedule are mutually exclusive."
|
||||
;;; option (--daemon -d
|
||||
;;; "run as a daemon process")
|
||||
;;; option (--stdin=guile -i (λ (in) (or (string=? in "guile")
|
||||
;;; (string=? in "vixie")))
|
||||
;;; "format of data passed as standard input or file "
|
||||
;;; "arguments, 'guile' or 'vixie' (default guile)")
|
||||
;;; option (--schedule=8 -s string->number
|
||||
;;; "display the next N (or 8) jobs that will be run")
|
||||
;;; help-postamble
|
||||
;;; "Mandatory or optional arguments to long options are also mandatory or "
|
||||
;;; "optional for any corresponding short options."
|
||||
;;; bug-address "bug-mcron@gnu.org"
|
||||
;;; copyright "2003, 2006, 2014, 2020 Free Software Foundation, Inc."
|
||||
;;; license GPLv3)
|
||||
;;;
|
||||
;;; after which there are four new variable bindings in the present
|
||||
;;; namespace: --daemon, --stdin, --schedule and --! (the latter holds
|
||||
;;; all the command-line arguments that did not partake in option
|
||||
;;; processing) whose values depend on the specific command-line options
|
||||
;;; the end user furnished.
|
||||
|
||||
;;; (command-line-specification SPECIFICATION)
|
||||
;;; Compiles an object which encapsulates the given SPECIFICATION.
|
||||
;;;
|
||||
;;; For details of how to give a SPECIFICATION, see the description of
|
||||
;;; the full ‘process-command-line’ function above. The return from
|
||||
;;; this method can be used in the partial version of
|
||||
;;; ‘process-command-line’ described below, and in the following
|
||||
;;; ‘merge-command-line-specifications’ function.
|
||||
|
||||
;;; (merge-command-line-specifications SPECIFICATION_OBJECT ...) Make a
|
||||
;;; single specification object which embodies the amalgamation of all
|
||||
;;; of the specification objects given as arguments.
|
||||
;;;
|
||||
;;; Order is important: if two option items specify the same short form
|
||||
;;; for the option (a single letter), then only the first option will
|
||||
;;; actually have that short form available at the command-line.
|
||||
;;; Similarly, if two options have exactly the same name, the second (or
|
||||
;;; later) ones will have a numerical digit appended to their name.
|
||||
|
||||
;;; (process-command-line COMMAND-LINE SPECIFICATION-OBJECT) Perform
|
||||
;;; exactly the same function as the full ‘process-command-line’
|
||||
;;; function described above, but takes a pre-made specification object
|
||||
;;; produced using the two functions above.
|
||||
|
||||
;;; Bugs/To do
|
||||
;;;
|
||||
;;; 1) This stuff currently only works in the top-level module.
|
||||
;;;
|
||||
;;; 2) Want to be able to amalgamate command-line specifications from
|
||||
;;; different modules. Will need to get to the bottom of the first
|
||||
;;; issue before we can tackle this one (somehow need to put the
|
||||
;;; --option variable bindings into the right places, or at least
|
||||
;;; replicate them all in all modules which want to do some processing
|
||||
;;; of the command line).
|
||||
;;;
|
||||
;;; 3) Want more license boilerplate text; currently we only have GPLv3.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(define-module (ice-9 command-line-processor)
|
||||
#:use-module (srfi srfi-1) ;; fold
|
||||
#:use-module (srfi srfi-9) ;; Records
|
||||
#:use-module (srfi srfi-9 gnu) ;; set/get-fields
|
||||
#:use-module (ice-9 getopt-long)
|
||||
#:use-module (ice-9 regex)
|
||||
#:export (specific option item
|
||||
obtain-getopt-long-results
|
||||
process-getopt-long-results
|
||||
|
||||
;; These are the real public exports.
|
||||
process-command-line
|
||||
command-line-specification
|
||||
merge-command-line-specifications))
|
||||
|
||||
|
||||
|
||||
(define-record-type <<specification>>
|
||||
(make-specification- preamble postamble copyright authors options)
|
||||
specification?
|
||||
(name spec:name spec:set-name!)
|
||||
(version spec:version spec:set-version!)
|
||||
(usage spec:usage spec:set-usage!)
|
||||
(preamble spec:preamble spec:set-preamble!)
|
||||
(postamble spec:postamble spec:set-postamble!)
|
||||
(bug-address spec:bugs spec:set-bugs!)
|
||||
(copyright spec:copyright spec:set-copyright!)
|
||||
(license spec:license spec:set-license!)
|
||||
(authors spec:authors spec:set-authors!)
|
||||
(options spec:options spec:set-all-options!))
|
||||
|
||||
(define (make-specification) (make-specification- '() '() '() '() '()))
|
||||
|
||||
|
||||
|
||||
(define-record-type <<option>>
|
||||
(make-option- description)
|
||||
option?
|
||||
(name option:name option:set-name!)
|
||||
(required? option:required?)
|
||||
(short-letter option:short option:set-short!)
|
||||
(value? option:value?)
|
||||
(default option:default)
|
||||
(description option:description option:set-description!)
|
||||
(predicate option:predicate option:set-predicate!))
|
||||
|
||||
(define (make-option) (make-option- '()))
|
||||
|
||||
|
||||
|
||||
(define (has-option-short-form spec letter)
|
||||
(if (not letter)
|
||||
#f
|
||||
(let loop ((o (spec:options spec)))
|
||||
(cond ((null? o) #f)
|
||||
((eq? letter (option:short (car o))) #t)
|
||||
(else (loop (cdr o)))))))
|
||||
|
||||
(define (has-option-name spec name)
|
||||
(let loop ((o (spec:options spec)))
|
||||
(cond ((null? o) #f)
|
||||
((string=? (option:name (car o)) name) #t)
|
||||
(else (loop (cdr o))))))
|
||||
|
||||
(define (merge-command-line-specifications A . B)
|
||||
"- Scheme Procedure: merge-command-line-specifications A B Append the
|
||||
list of options in A with those in B, but drop any short-forms in B
|
||||
which clash with existing ones, and if a long option name clashes then
|
||||
append a number to make it unique. A and B will be mutilated in the
|
||||
process, a new specification object will be returned."
|
||||
(for-each (λ (b-spec)
|
||||
(for-each (λ (b-option)
|
||||
(when (has-option-short-form A (option:short b-option))
|
||||
(option:set-short! b-option #f))
|
||||
(when (has-option-name A (option:name b-option))
|
||||
(let ((base-name (option:name b-option)))
|
||||
(let loop ((count 1))
|
||||
(let ((new-name (string-append base-name "-"
|
||||
(number->string count))))
|
||||
(if (has-option-name A new-name)
|
||||
(loop (1+ count))
|
||||
(option:set-name! b-option new-name))))))
|
||||
(spec:set-all-options! A (append (spec:options A)
|
||||
(list b-option))))
|
||||
(spec:options b-spec)))
|
||||
B)
|
||||
A)
|
||||
|
||||
|
||||
|
||||
(define long-re (make-regexp "^--(!)?([a-zA-Z][-_0-9a-zA-Z]*)(=(.+)?)?$"))
|
||||
(define short-re (make-regexp "^-[a-zA-Z]$"))
|
||||
|
||||
|
||||
(define-syntax item ;; As in, an option item (long name, short form...).
|
||||
(λ (x) (syntax-case x (short-i short-I)
|
||||
|
||||
;; No more work to do.
|
||||
((item O) #'#t)
|
||||
|
||||
;; Next option is a string: take as description.
|
||||
((item O desc args ...)
|
||||
(string? (syntax->datum #'desc))
|
||||
#'(begin (option:set-description! O (append (option:description O)
|
||||
(list desc)))
|
||||
(item O args ...)))
|
||||
|
||||
;; Next option is short-form.
|
||||
((item O short-i args ...)
|
||||
#`(begin (option:set-short! O #\i)
|
||||
(item O args ...)))
|
||||
|
||||
((item O short-I args ...)
|
||||
#`(begin (option:set-short! O #\I)
|
||||
(item O args ...)))
|
||||
|
||||
((item O short args ...)
|
||||
(and (identifier? #'short)
|
||||
(regexp-exec short-re (symbol->string (syntax->datum #'short))))
|
||||
#`(begin (option:set-short! O (string-ref (symbol->string 'short) 1))
|
||||
(item O args ...)))
|
||||
|
||||
;; Next option is long-form.
|
||||
((item O long args ...)
|
||||
(and (identifier? #'long)
|
||||
(regexp-exec long-re (symbol->string (syntax->datum #'long))))
|
||||
#`(begin (let ((match (regexp-exec long-re
|
||||
(symbol->string (syntax->datum #'long)))))
|
||||
(set! O
|
||||
(set-fields O
|
||||
((option:name) (match:substring match 2))
|
||||
((option:required?) (if (match:substring match 1) #t #f))
|
||||
((option:value?)
|
||||
(cond ((not (match:substring match 3)) #f)
|
||||
((match:substring match 4) 'optional)
|
||||
(else #t)))
|
||||
((option:default) (match:substring match 4)))))
|
||||
(item O args ...)))
|
||||
|
||||
;; Next option is a procedure: take as predicate.
|
||||
|
||||
((item O (lambda args ...) Args ...)
|
||||
#'(begin (option:set-predicate! O (lambda args ...))
|
||||
(item O Args ...)))
|
||||
|
||||
((item O pred args ...)
|
||||
(and (identifier? #'pred)
|
||||
;; (procedure? (primitive-eval (syntax->datum #'pred)))
|
||||
)
|
||||
#'(begin (option:set-predicate! O pred)
|
||||
(item O args ...))))))
|
||||
|
||||
|
||||
|
||||
(define-syntax-rule (option args ...)
|
||||
(let ((O (make-option))) (item O args ...) O))
|
||||
|
||||
|
||||
|
||||
(define-syntax specific
|
||||
(λ (x) (syntax-case x (application author bug-address
|
||||
copyright help-preamble help-postamble
|
||||
license option usage
|
||||
version)
|
||||
((specific spec application A args ...)
|
||||
(string? (syntax->datum #'A))
|
||||
#'(begin (spec:set-name! spec A)
|
||||
(specific spec args ...)))
|
||||
((specific spec author A args ...)
|
||||
(string? (syntax->datum #'A))
|
||||
#'(begin (spec:set-author! spec (append (spec:authors spec)
|
||||
(list A)))
|
||||
(specific spec args ...)))
|
||||
((specific spec bug-address B args ...)
|
||||
(string? (syntax->datum #'B))
|
||||
#'(begin (spec:set-bugs! spec B)
|
||||
(specific spec args ...)))
|
||||
((specific spec copyright C args ...)
|
||||
(string? (syntax->datum #'C))
|
||||
#'(begin (spec:set-copyright! spec (append (spec:copyright spec)
|
||||
(list C)))
|
||||
(specific spec args ...)))
|
||||
((specific spec help-preamble id args ...)
|
||||
(identifier? #'id)
|
||||
#'(specific spec id args ...))
|
||||
((specific spec help-preamble quotation args ...)
|
||||
(string? (syntax->datum #'quotation))
|
||||
#'(begin (spec:set-preamble! spec (append (spec:preamble spec)
|
||||
(list quotation)))
|
||||
(specific spec help-preamble args ...)))
|
||||
((specific spec help-postamble id args ...)
|
||||
(identifier? #'id)
|
||||
#'(specific spec id args ...))
|
||||
((specific spec help-postamble quotation args ...)
|
||||
(string? (syntax->datum #'quotation))
|
||||
#'(begin (spec:set-postamble! spec (append (spec:postamble spec)
|
||||
(list quotation)))
|
||||
(specific spec help-postamble args ...)))
|
||||
((specific spec license L args ...)
|
||||
(identifier? #'L)
|
||||
#'(begin (spec:set-license! spec 'L)
|
||||
(specific spec args ...)))
|
||||
((specific spec license L args ...)
|
||||
(string? (syntax->datum #'L))
|
||||
#'(begin (spec:set-license! spec L)
|
||||
(specific spec args ...)))
|
||||
((specific spec option (args ...) Args ...)
|
||||
#'(begin (spec:set-all-options! spec
|
||||
(append (spec:options spec)
|
||||
(list (option args ...))))
|
||||
(specific spec Args ...)))
|
||||
((specific spec usage U args ...)
|
||||
(string? (syntax->datum #'U))
|
||||
#'(begin (spec:set-usage! spec U)
|
||||
(specific spec args ...)))
|
||||
((specific spec version V args ...)
|
||||
(string? (syntax->datum #'V))
|
||||
#'(begin (spec:set-version! spec V)
|
||||
(specific spec args ...)))
|
||||
((specific spec) #'#t))))
|
||||
|
||||
|
||||
|
||||
(define-syntax-rule (command-line-specification args ...)
|
||||
;; " - Scheme Procedure: command-line-specification ARGS ...
|
||||
|
||||
;; Furnish an application specification object with attributes specified in
|
||||
;; ARGS followed by a number of values for the attribute. Please refer to
|
||||
;; full documentation for a proper description of a specification object.
|
||||
|
||||
;; The attributes are
|
||||
|
||||
;; application: string: the formal name of this application. Must appear
|
||||
;; exactly once.
|
||||
;; author: string: the name of an author. May appear any number of times.
|
||||
;; bug-address: string: The URI to which bug reports should be addressed.
|
||||
;; May appear zero or one times.
|
||||
;; copyright: string: list of years and owning entity. May appear any
|
||||
;; number of times.
|
||||
;; help-preamble: string: text to precede the list of options in a
|
||||
;; response to the --help option. This attribute may appear any
|
||||
;; number of times, and each occurrence can be followed by one or
|
||||
;; more strings which will be assembled together into paragraphs.
|
||||
;; help-postamble: string: text to succeed the list of options in a help
|
||||
;; message. Same considerations apply as to ‘help-preamble’.
|
||||
;; license: identifier or string: either the identifier ‘GPLv3’ or a
|
||||
;; string describing the terms of the license.
|
||||
;; option: (sub-form): the sub-form must contain one identifier composed
|
||||
;; of two hyphens, an optional exclamation point, a token of
|
||||
;; letters, numbers, underscore and hyphen, an optional equals
|
||||
;; sign, and an optional word; the sub-form may have zero or one
|
||||
;; identifiers composed of a hyphen and a single letter; any
|
||||
;; number of strings which will be composed into a paragraph of
|
||||
;; help for the option (these should be sized to half-line
|
||||
;; lengths); and zero or one procedures which will be applied as a
|
||||
;; predicate on allowable option values. Any number of these
|
||||
;; option attributes may appear in the specification.
|
||||
;; usage: string: a single line of text prototyping the command line.
|
||||
;; Zero or one of these may appear.
|
||||
;; version: string: the version number of this application. Zero or one
|
||||
;; of these attributes may appear."
|
||||
|
||||
(let ((spec (make-specification)))
|
||||
(specific spec args ...)
|
||||
spec))
|
||||
|
||||
|
||||
|
||||
(define (version-string spec)
|
||||
(with-output-to-string (λ ()
|
||||
(display (if (string? (spec:name spec))
|
||||
(spec:name spec)
|
||||
(car (command-line))))
|
||||
(when (string? (spec:version spec))
|
||||
(display " ") (display (spec:version spec)) (newline))
|
||||
(unless (null? (spec:copyright spec))
|
||||
(display "Copyright © ")
|
||||
(display (string-join (spec:copyright spec) "\n "))
|
||||
(newline))
|
||||
(cond ((eq? (spec:license spec) 'GPLv3)
|
||||
(display (string-append
|
||||
"License GPLv3+: GNU GPL version 3 or later "
|
||||
"<https://gnu.org/licenses/gpl.html>.\nThis is "
|
||||
"free software: you are free to change and "
|
||||
"redistribute it.\nThere is NO WARRANTY, to the "
|
||||
"extent permitted by law.\n")))
|
||||
((string? (spec:license spec))
|
||||
(display (spec:license spec)) (newline)))
|
||||
(unless (null? (spec:authors spec))
|
||||
(display (string-append "Written by "
|
||||
(case (length (spec:authors spec))
|
||||
((1 2) (string-join (spec:authors spec) " and "))
|
||||
(else
|
||||
(let loop ((a (cdr (spec:authors spec)))
|
||||
(ret (car (spec:authors spec))))
|
||||
(if (null? (cdr a))
|
||||
(string-append ret " and " (car a))
|
||||
(loop (cdr a) (string-append ret ", "
|
||||
(car a)))))))
|
||||
".\n"))))))
|
||||
|
||||
|
||||
|
||||
(define (usage-string spec)
|
||||
(string-append "Usage: " (spec:name spec) " " (spec:usage spec) "\n"))
|
||||
|
||||
|
||||
|
||||
(define (help-string spec)
|
||||
(with-output-to-string (λ ()
|
||||
(display (usage-string spec))
|
||||
(display (string-join (spec:preamble spec) "\n"))
|
||||
(display "\n\n")
|
||||
(let ((max-length (fold (λ (o r) (max r (string-length (option:name o))))
|
||||
0
|
||||
(spec:options spec))))
|
||||
(for-each (λ (o)
|
||||
(display " ")
|
||||
(cond ((option:short o)
|
||||
(display "-") (display (option:short o))
|
||||
(case (option:value? o) ((#t) (display "N, "))
|
||||
((optional) (display "[N], "))
|
||||
(else (display ", "))))
|
||||
(else (display " ")))
|
||||
(display "--")
|
||||
(display (option:name o))
|
||||
(case (option:value? o) ((#t) (display "=N "))
|
||||
((optional) (display "[=N]"))
|
||||
(else (display " ")))
|
||||
(display (make-string (- max-length
|
||||
(string-length (option:name o)))
|
||||
#\space))
|
||||
(display " ")
|
||||
(when (option:required? o) (display "*REQUIRED*: "))
|
||||
(display (string-join (option:description o)
|
||||
(string-append "\n"
|
||||
(make-string max-length
|
||||
#\space)
|
||||
" ")))
|
||||
(newline))
|
||||
(spec:options spec)))
|
||||
(newline)
|
||||
(display (string-join (spec:postamble spec) "\n"))
|
||||
(when (spec:bugs spec)
|
||||
(display "\nSend bug reports to ")
|
||||
(display (spec:bugs spec))
|
||||
(display ".\n")))))
|
||||
|
||||
|
||||
|
||||
(define (make-getopt-long-input spec)
|
||||
(map (λ (o)
|
||||
(append (list (string->symbol (option:name o)))
|
||||
(cond ((option:short o)
|
||||
=> (λ (x) (list (list 'single-char x))))
|
||||
(else '()))
|
||||
(list (list 'required? (option:required? o)))
|
||||
(list (list 'value (if (option:default o)
|
||||
'optional
|
||||
(option:value? o))))
|
||||
(cond ((option:predicate o)
|
||||
=> (λ (x) (list (list 'predicate x))))
|
||||
(else '()))))
|
||||
(spec:options spec)))
|
||||
|
||||
|
||||
|
||||
(define (obtain-getopt-long-results args spec)
|
||||
(getopt-long args (make-getopt-long-input spec)))
|
||||
|
||||
|
||||
|
||||
(define (distill-getopt-long-results go-l spec)
|
||||
(cons (cons "!" (option-ref go-l '() '()))
|
||||
(map (λ (o)
|
||||
(let ((g (option-ref go-l
|
||||
(string->symbol (option:name o))
|
||||
#f)))
|
||||
(when (eq? g #t)
|
||||
(case (string->symbol (option:name o))
|
||||
((help) (display (help-string spec)) (exit 0))
|
||||
((version) (display (version-string spec)) (exit 0))
|
||||
((usage) (display (usage-string spec)) (exit 0))))
|
||||
(cons (option:name o)
|
||||
(if (and (eq? #t g)
|
||||
(not (eq? #f (option:default o))))
|
||||
(option:default o)
|
||||
g))))
|
||||
(spec:options spec))))
|
||||
|
||||
|
||||
|
||||
(define (process-getopt-long-results go-l spec)
|
||||
(for-each (λ (option)
|
||||
(module-define!
|
||||
(current-module)
|
||||
(string->symbol (string-append "--" (car option)))
|
||||
(cdr option)))
|
||||
(distill-getopt-long-results go-l spec)))
|
||||
|
||||
|
||||
|
||||
(define-syntax process-command-line
|
||||
;; "- Scheme Procedure: process-command-line COMMAND-LINE SPECS [...]
|
||||
|
||||
;; Process the COMMAND-LINE according to the SPECS, extracting options and
|
||||
;; their values, dealing with --help, --version and --usage requests. The
|
||||
;; procedure has no return values, but has the side effect of creating
|
||||
;; variable bindings in the current module corresponding to the long form
|
||||
;; of the options, plus a variable called ‘--!’ which gets a list of all
|
||||
;; the arguments on the command-line which did not participate in option
|
||||
;; processing.
|
||||
|
||||
;; The COMMAND-LINE is a list of strings, starting with the name of the
|
||||
;; program and containing all the tokens passed to the program on the
|
||||
;; command line, such as returned from the core ‘command-line’ procedure.
|
||||
|
||||
;; The SPECS should have been obtained with the
|
||||
;; ‘command-line-specification’ procedure, or, as a short-cut, can be
|
||||
;; supplied directly as arguments to this procedure."
|
||||
|
||||
(syntax-rules (GO)
|
||||
((_ command-line specs GO)
|
||||
(let ((S (merge-command-line-specifications
|
||||
specs
|
||||
(command-line-specification
|
||||
option (-h --help "display this help and exit")
|
||||
option (-V --version "output version information and exit")
|
||||
option (-u --usage "show brief usage summary")))))
|
||||
(process-getopt-long-results
|
||||
(obtain-getopt-long-results command-line S)
|
||||
S)))
|
||||
((_ command-line specs)
|
||||
(process-command-line command-line specs GO))
|
||||
((_ command-line item ...)
|
||||
(process-command-line
|
||||
command-line
|
||||
(command-line-specification item ...)))))
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
;;; Copyright (C) 1998, 2001, 2006, 2009, 2011 Free Software Foundation, Inc.
|
||||
;;;
|
||||
;;;; getopt-long.scm --- long options processing -*- scheme -*-
|
||||
;;;;
|
||||
;;;; Copyright (C) 1998, 2001, 2006, 2009, 2011, 2020
|
||||
;;;; Free Software Foundation, Inc.
|
||||
;;;;
|
||||
;;;; This library is free software; you can redistribute it and/or
|
||||
;;;; modify it under the terms of the GNU Lesser General Public
|
||||
;;;; License as published by the Free Software Foundation; either
|
||||
|
|
@ -12,54 +15,59 @@
|
|||
;;;;
|
||||
;;;; You should have received a copy of the GNU Lesser General Public
|
||||
;;;; License along with this library; if not, write to the Free Software
|
||||
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
;;;; 02110-1301 USA
|
||||
|
||||
;;; Author: Russ McManus (rewritten by Thien-Thi Nguyen)
|
||||
;;; Author: Russ McManus
|
||||
;;; Rewritten by Thien-Thi Nguyen
|
||||
;;; Rewritten by Dale Mellor 2020-04-14
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;; This module implements some complex command line option parsing, in
|
||||
;;; the spirit of the GNU C library function `getopt_long'. Both long
|
||||
;;; the spirit of the GNU C library function ‘getopt_long’. Both long
|
||||
;;; and short options are supported.
|
||||
;;;
|
||||
;;; The theory is that people should be able to constrain the set of
|
||||
;;; options they want to process using a grammar, rather than some arbitrary
|
||||
;;; structure. The grammar makes the option descriptions easy to read.
|
||||
;;; options they want to process using a grammar, rather than some ad
|
||||
;;; hoc procedure. The grammar makes the option descriptions easy to
|
||||
;;; read.
|
||||
;;;
|
||||
;;; `getopt-long' is a procedure for parsing command-line arguments in a
|
||||
;;; manner consistent with other GNU programs. `option-ref' is a procedure
|
||||
;;; that facilitates processing of the `getopt-long' return value.
|
||||
;;; ‘getopt-long’ is a procedure for parsing command-line arguments in a
|
||||
;;; manner consistent with other GNU programs. ‘option-ref’ is a procedure
|
||||
;;; that facilitates processing of the ‘getopt-long’ return value.
|
||||
|
||||
;;; (getopt-long ARGS GRAMMAR)
|
||||
;;; Parse the arguments ARGS according to the argument list grammar GRAMMAR.
|
||||
;;;
|
||||
;;; ARGS should be a list of strings. Its first element should be the
|
||||
;;; name of the program; subsequent elements should be the arguments
|
||||
;;; name of the program, and subsequent elements should be the arguments
|
||||
;;; that were passed to the program on the command line. The
|
||||
;;; `program-arguments' procedure returns a list of this form.
|
||||
;;; ‘program-arguments’ procedure returns a list of this form.
|
||||
;;;
|
||||
;;; GRAMMAR is a list of the form:
|
||||
;;; ((OPTION (PROPERTY VALUE) ...) ...)
|
||||
;;;
|
||||
;;; Each OPTION should be a symbol. `getopt-long' will accept a
|
||||
;;; command-line option named `--OPTION'.
|
||||
;;; Each OPTION should be a symbol. ‘getopt-long’ will accept a
|
||||
;;; command-line option named ‘--OPTION’.
|
||||
;;; Each option can have the following (PROPERTY VALUE) pairs:
|
||||
;;;
|
||||
;;; (single-char CHAR) --- Accept `-CHAR' as a single-character
|
||||
;;; equivalent to `--OPTION'. This is how to specify traditional
|
||||
;;; (single-char CHAR) --- Accept ‘-CHAR’ as a single-character
|
||||
;;; equivalent to ‘--OPTION’. This is how to specify traditional
|
||||
;;; Unix-style flags.
|
||||
;;; (required? BOOL) --- If BOOL is true, the option is required.
|
||||
;;; getopt-long will raise an error if it is not found in ARGS.
|
||||
;;; (value BOOL) --- If BOOL is #t, the option accepts a value; if
|
||||
;;; it is #f, it does not; and if it is the symbol
|
||||
;;; `optional', the option may appear in ARGS with or
|
||||
;;; ‘optional’, the option may appear in ARGS with or
|
||||
;;; without a value.
|
||||
;;; (predicate FUNC) --- If the option accepts a value (i.e. you
|
||||
;;; specified `(value #t)' for this option), then getopt
|
||||
;;; will apply FUNC to the value, and throw an exception
|
||||
;;; if it returns #f. FUNC should be a procedure which
|
||||
;;; accepts a string and returns a boolean value; you may
|
||||
;;; need to use quasiquotes to get it into GRAMMAR.
|
||||
;;; specified ‘(value #t)’ or ‘(value 'optional)’ for this
|
||||
;;; option), then getopt will apply FUNC to the value, and
|
||||
;;; will not take the value if it returns #f. FUNC should
|
||||
;;; be a procedure which accepts a string and returns a
|
||||
;;; boolean value; you may need to use quasiquotes to get it
|
||||
;;; into GRAMMAR.
|
||||
;;;
|
||||
;;; The (PROPERTY VALUE) pairs may occur in any order, but each
|
||||
;;; property may occur only once. By default, options do not have
|
||||
|
|
@ -79,16 +87,22 @@
|
|||
;;; for "blimps" and "catalexis")
|
||||
;;; ("-ab" "bang" "-c" "couth") (same)
|
||||
;;; ("-ac" "couth" "-b" "bang") (same)
|
||||
;;; ("-abc" "couth" "bang") (an error, since `-b' is not the
|
||||
;;; last option in its combination)
|
||||
;;;
|
||||
;;; If an option's value is optional, then `getopt-long' decides
|
||||
;;; whether it has a value by looking at what follows it in ARGS. If
|
||||
;;; the next element is does not appear to be an option itself, then
|
||||
;;; that element is the option's value.
|
||||
;;; If an option's value is optional, then ‘getopt-long’ decides whether
|
||||
;;; it has a value by looking at what follows it in ARGS. If the next
|
||||
;;; element does not appear to be an option itself, and passes a
|
||||
;;; predicate if given, then that element is taken to be the option's
|
||||
;;; value. Note that predicate functions are invaluable in this respect
|
||||
;;; for differentiating options and option values, and in the case of
|
||||
;;; options with optional values, PREDICATES REALLY SHOULD BE GIVEN. If
|
||||
;;; an option is supposed to take a numerical value, then
|
||||
;;; ‘string->number’ can be used as the predicate; this will also allow
|
||||
;;; negative values to be used, which would ordinarily be regarded as
|
||||
;;; bad options causing the module, and the application consuming it, to
|
||||
;;; bail out with an immediate exit to the operating system.
|
||||
;;;
|
||||
;;; The value of a long option can appear as the next element in ARGS,
|
||||
;;; or it can follow the option name, separated by an `=' character.
|
||||
;;; or it can follow the option name, separated by an ‘=’ character.
|
||||
;;; Thus, using the same grammar as above, the following argument lists
|
||||
;;; are equivalent:
|
||||
;;; ("--apples" "Braeburn" "--blimps" "Goodyear")
|
||||
|
|
@ -99,27 +113,30 @@
|
|||
;;; subsequent arguments are returned as ordinary arguments, even if
|
||||
;;; they resemble options. So, in the argument list:
|
||||
;;; ("--apples" "Granny Smith" "--" "--blimp" "Goodyear")
|
||||
;;; `getopt-long' will recognize the `apples' option as having the
|
||||
;;; value "Granny Smith", but it will not recognize the `blimp'
|
||||
;;; option; it will return the strings "--blimp" and "Goodyear" as
|
||||
;;; ordinary argument strings.
|
||||
;;; ‘getopt-long’ will recognize the ‘apples’ option as having the value
|
||||
;;; "Granny Smith", but it will not recognize the ‘blimp’ option; it
|
||||
;;; will return the strings "--blimp" and "Goodyear" as ordinary
|
||||
;;; argument strings. The first "--" argument itself will *not* appear
|
||||
;;; in the ordinary arguments list, although the occurrence of
|
||||
;;; subsequent ones will.
|
||||
;;;
|
||||
;;; The `getopt-long' function returns the parsed argument list as an
|
||||
;;; The ‘getopt-long’ function returns the parsed argument list as an
|
||||
;;; assocation list, mapping option names --- the symbols from GRAMMAR
|
||||
;;; --- onto their values, or #t if the option does not accept a value.
|
||||
;;; Unused options do not appear in the alist.
|
||||
;;;
|
||||
;;; All arguments that are not the value of any option are returned
|
||||
;;; as a list, associated with the empty list.
|
||||
;;; All arguments that are not the value of any option are returned as a
|
||||
;;; list, associated with the empty list in the above returned
|
||||
;;; association.
|
||||
;;;
|
||||
;;; `getopt-long' throws an exception if:
|
||||
;;; ‘getopt-long’ throws an exception if:
|
||||
;;; - it finds an unrecognized property in GRAMMAR
|
||||
;;; - the value of the `single-char' property is not a character
|
||||
;;; - the value of the ‘single-char’ property is not a character
|
||||
;;; - it finds an unrecognized option in ARGS
|
||||
;;; - a required option is omitted
|
||||
;;; - an option that requires an argument doesn't get one
|
||||
;;; - an option that doesn't accept an argument does get one (this can
|
||||
;;; only happen using the long option `--opt=value' syntax)
|
||||
;;; only happen using the long option ‘--opt=value’ syntax)
|
||||
;;; - an option predicate fails
|
||||
;;;
|
||||
;;; So, for example:
|
||||
|
|
@ -147,9 +164,10 @@
|
|||
|
||||
;;; (option-ref OPTIONS KEY DEFAULT)
|
||||
;;; Return value in alist OPTIONS using KEY, a symbol; or DEFAULT if not
|
||||
;;; found. The value is either a string or `#t'.
|
||||
;;; found. The return is either a string or ‘#t’, or whatever DEFAULT
|
||||
;;; is.
|
||||
;;;
|
||||
;;; For example, using the `getopt-long' return value from above:
|
||||
;;; For example, using the ‘getopt-long’ return value from above:
|
||||
;;;
|
||||
;;; (option-ref (getopt-long ...) 'x-includes 42) => "/usr/include"
|
||||
;;; (option-ref (getopt-long ...) 'not-a-key! 31) => 31
|
||||
|
|
@ -158,12 +176,17 @@
|
|||
|
||||
(define-module (ice-9 getopt-long)
|
||||
#:use-module ((ice-9 common-list) #:select (remove-if-not))
|
||||
#:use-module (ice-9 control)
|
||||
#:use-module (srfi srfi-9)
|
||||
#:use-module (ice-9 match)
|
||||
#:use-module (ice-9 regex)
|
||||
#:use-module (ice-9 optargs)
|
||||
#:use-module (ice-9 receive)
|
||||
#:export (getopt-long option-ref))
|
||||
|
||||
;; Code makes more sense to human beings with this.
|
||||
(define return values)
|
||||
|
||||
(define %program-name (make-fluid "guile"))
|
||||
(define (program-name)
|
||||
(fluid-ref %program-name))
|
||||
|
|
@ -175,18 +198,13 @@
|
|||
(exit 1))
|
||||
|
||||
(define-record-type option-spec
|
||||
(%make-option-spec name required? option-spec->single-char predicate value-policy)
|
||||
(%make-option-spec name required? single-char predicate value-policy)
|
||||
option-spec?
|
||||
(name
|
||||
option-spec->name set-option-spec-name!)
|
||||
(required?
|
||||
option-spec->required? set-option-spec-required?!)
|
||||
(option-spec->single-char
|
||||
option-spec->single-char set-option-spec-single-char!)
|
||||
(predicate
|
||||
option-spec->predicate set-option-spec-predicate!)
|
||||
(value-policy
|
||||
option-spec->value-policy set-option-spec-value-policy!))
|
||||
(name option-spec->name)
|
||||
(required? option-spec->required? set-option-spec-required?!)
|
||||
(single-char option-spec->single-char set-option-spec-single-char!)
|
||||
(predicate option-spec->predicate set-option-spec-predicate!)
|
||||
(value-policy option-spec->value-policy set-option-spec-value-policy!))
|
||||
|
||||
(define (make-option-spec name)
|
||||
(%make-option-spec name #f #f #f #f))
|
||||
|
|
@ -195,120 +213,338 @@
|
|||
(let ((spec (make-option-spec (symbol->string (car desc)))))
|
||||
(for-each (match-lambda
|
||||
(('required? val)
|
||||
(set-option-spec-required?! spec val))
|
||||
(set-option-spec-required?! spec val))
|
||||
(('value val)
|
||||
(set-option-spec-value-policy! spec val))
|
||||
(set-option-spec-value-policy! spec val))
|
||||
(('single-char val)
|
||||
(or (char? val)
|
||||
(error "`single-char' value must be a char!"))
|
||||
(set-option-spec-single-char! spec val))
|
||||
(unless (char? val)
|
||||
(fatal-error "‘single-char’ value must be a char!"))
|
||||
(set-option-spec-single-char! spec val))
|
||||
(('predicate pred)
|
||||
(set-option-spec-predicate!
|
||||
spec (lambda (name val)
|
||||
(or (not val)
|
||||
(pred val)
|
||||
(fatal-error "option predicate failed: --~a"
|
||||
name)))))
|
||||
(set-option-spec-predicate! spec pred))
|
||||
((prop val)
|
||||
(error "invalid getopt-long option property:" prop)))
|
||||
(fatal-error "invalid getopt-long option property:" prop)))
|
||||
(cdr desc))
|
||||
spec))
|
||||
|
||||
(define (split-arg-list argument-list)
|
||||
;; Scan ARGUMENT-LIST for "--" and return (BEFORE-LS . AFTER-LS).
|
||||
;; Discard the "--". If no "--" is found, AFTER-LS is empty.
|
||||
(let loop ((yes '()) (no argument-list))
|
||||
(cond ((null? no) (cons (reverse yes) no))
|
||||
((string=? "--" (car no)) (cons (reverse yes) (cdr no)))
|
||||
(else (loop (cons (car no) yes) (cdr no))))))
|
||||
|
||||
(define short-opt-rx (make-regexp "^-([a-zA-Z]+)(.*)"))
|
||||
(define long-opt-no-value-rx (make-regexp "^--([^=]+)$"))
|
||||
(define long-opt-with-value-rx (make-regexp "^--([^=]+)=(.*)"))
|
||||
;; Extract the name of a long option given that it may or may not be
|
||||
;; surrounded by '--' and '=...'.
|
||||
(define isolate-long-name-re (make-regexp "^-*([^=]+)"))
|
||||
|
||||
(define (isolate-long-name name)
|
||||
(cond ((regexp-exec isolate-long-name-re name)
|
||||
=> (λ (match) (match:substring match 1)))
|
||||
(else #f)))
|
||||
|
||||
|
||||
;; Whatever the presentation of the long option, make sure it is in a
|
||||
;; clean, normalized form (but this does NOT account for any value the
|
||||
;; option might have).
|
||||
(define (re-present option)
|
||||
(string-append "--" (isolate-long-name option) "="))
|
||||
|
||||
|
||||
;; The /name/ passed in here must be a string with just the characters
|
||||
;; of the option name in it. The return is the spec with that name, or
|
||||
;; #f if such cannot be found.
|
||||
(define (find-spec-long-name-clear specs name)
|
||||
(cond ((null? specs) #f)
|
||||
((string=? (option-spec->name (car specs)) name) (car specs))
|
||||
(else (find-spec-long-name-clear (cdr specs) name))))
|
||||
|
||||
|
||||
;; The /name/ can take the form of a long option entry on the command
|
||||
;; line, with whatever decoration that entails. Will return #f if a
|
||||
;; spec does not exist for this named option.
|
||||
(define (find-spec-long specs name)
|
||||
(cond ((isolate-long-name name)
|
||||
=> (λ (name) (find-spec-long-name-clear specs name)))
|
||||
(else #f)))
|
||||
|
||||
|
||||
;; Return #f if a spec with the short /letter/ name does not exist.
|
||||
(define (find-spec-short specs letter)
|
||||
(cond ((null? specs) #f)
|
||||
((eq? (option-spec->single-char (car specs)) letter) (car specs))
|
||||
(else (find-spec-short (cdr specs) letter))))
|
||||
|
||||
|
||||
;; Return the long name (string) of a short option (char).
|
||||
(define (short->long specs letter)
|
||||
(cond ((find-spec-short specs letter) => option-spec->name)
|
||||
(else (string letter))))
|
||||
|
||||
|
||||
;; Take, for example, /short/='-h' to '--help='.
|
||||
(define (double-up short specs)
|
||||
(string-append "--" (short->long specs (string-ref short 1)) "="))
|
||||
|
||||
|
||||
;; Can't believe this is not already in Guile, but return a boolean
|
||||
;; indicating if /a/ is a character of the English alphabet. This
|
||||
;; should probably be more locale-specific.
|
||||
(define char-rx (make-regexp "[a-zA-Z]"))
|
||||
(define (is-alpha a) (regexp-exec char-rx (string a)))
|
||||
|
||||
|
||||
;; This procedure does whatever is necessary to put the (ostensibly)
|
||||
;; first item on the command-line into the canonical (normal) form
|
||||
;; '--item=value'; this may mean consuming the next item of the
|
||||
;; command-line (the first item of /rest/) to get the value. Note that
|
||||
;; the value may be missing, but the '=' sign will always be there in
|
||||
;; the return. The first item (/A/) will always be more than two
|
||||
;; characters, and the first two characters will be "--", i.e. we are
|
||||
;; processing a long option.
|
||||
;;
|
||||
;; A IN string The first argument on the command-line
|
||||
;; rest IN list of strings The remaining items of the command-line
|
||||
;; specs IN list of option-spec Options specification
|
||||
;; remnant OUT list of strings The unprocessed command line
|
||||
;; processed OUT string New command-line argument
|
||||
(define (normalize-long-option A rest specs)
|
||||
(define (return-empty-arg) (return rest (re-present A)))
|
||||
(define (return-arg-with-value)
|
||||
(return (cdr rest) (string-append (re-present A) (car rest))))
|
||||
(cond
|
||||
((string-index A #\=)
|
||||
;; The argument is already in the canonical form.
|
||||
(return rest A))
|
||||
((null? rest)
|
||||
;; There are no more arguments to be had, so present an empty
|
||||
;; value.
|
||||
(return-empty-arg))
|
||||
((find-spec-long specs A)
|
||||
;; There is an option spec for this argument; we must use the
|
||||
;; /value-policy/ and /predicate/ members to decide whether or
|
||||
;; not to take the following argument from the command-line as
|
||||
;; the value of the option.
|
||||
=> (λ (spec)
|
||||
(cond
|
||||
((option-spec->predicate spec)
|
||||
=> (λ (pred) (if (pred (car rest))
|
||||
(return-arg-with-value)
|
||||
(return-empty-arg))))
|
||||
(else (cond ((eq? (option-spec->value-policy spec) 'optional)
|
||||
(if (eq? (string-ref (car rest) 0) #\-)
|
||||
(return-empty-arg)
|
||||
(return-arg-with-value)))
|
||||
((and (eq? (option-spec->value-policy spec) #t)
|
||||
(or (string->number (car rest))
|
||||
(not (eq? (string-ref (car rest) 0) #\-))))
|
||||
(return-arg-with-value))
|
||||
(else (return-empty-arg)))))))
|
||||
(else
|
||||
;; We know nothing about this option, abort operations.
|
||||
(fatal-error "no such option: --~a" (isolate-long-name A)))))
|
||||
|
||||
|
||||
|
||||
;; This procedure does whatever is necessary to put the (ostensibly)
|
||||
;; first item on the command-line into the canonical form
|
||||
;; '--item=value'; this may mean consuming the next item of the
|
||||
;; command-line (the first item of /rest/) to get the value. Note that
|
||||
;; the value may be missing, but the '=' sign will always be there in
|
||||
;; the return. The first item (/A/) will always be exactly two
|
||||
;; characters, and the first character will be "-", i.e. we are
|
||||
;; processing an isolated short option.
|
||||
;;
|
||||
;; A IN string The first argument on the command-line
|
||||
;; rest IN list of strings The remaining items of the command-line
|
||||
;; specs IN list of option-spec Options specification
|
||||
;; remnant OUT list of strings The unprocessed command line
|
||||
;; processed OUT string New command-line argument
|
||||
(define (normalize-free-short-option A rest specs)
|
||||
(define (return-empty-arg) (return rest (double-up A specs)))
|
||||
(define (return-arg-with-next-value)
|
||||
(return (cdr rest)
|
||||
(string-append (double-up A specs) (car rest))))
|
||||
(let* ((name (string-ref A 1))
|
||||
(spec (find-spec-short specs name)))
|
||||
(unless (is-alpha name) (return rest A))
|
||||
(unless spec (fatal-error "no such option: -~a" name))
|
||||
(cond ((null? rest) (return-empty-arg))
|
||||
((option-spec->predicate spec)
|
||||
=> (λ (pred) (if (pred (car rest))
|
||||
(return-arg-with-next-value)
|
||||
(return-empty-arg))))
|
||||
((eq? (option-spec->value-policy spec) #f)
|
||||
(return-empty-arg))
|
||||
((eq? (option-spec->value-policy spec) 'optional)
|
||||
(if (eq? (string-ref (car rest) 0) #\-)
|
||||
(return-empty-arg)
|
||||
(return-arg-with-next-value)))
|
||||
(else (return-arg-with-next-value)))))
|
||||
|
||||
|
||||
|
||||
;; The /sequence/ is a string of characters from the command line, and
|
||||
;; the task is to decide if those characters, after a '-' sign, are a
|
||||
;; viable clumped option sequence, possibly using some of the trailing
|
||||
;; characters as option values, or not.
|
||||
(define (viable-short sequence specs)
|
||||
(cond ((eq? 0 (string-length sequence)) #t)
|
||||
((find-spec-short specs (string-ref sequence 0))
|
||||
;; If this optionʼs /value-policy/ allows the option to
|
||||
;; take a value then this string is viable as the
|
||||
;; remainder can be taken as that value. Otherwise we
|
||||
;; must assert the viability of the rest of the line by
|
||||
;; recursion.
|
||||
=> (λ (spec) (or (not (eq? #f (option-spec->value-policy spec)))
|
||||
(viable-short (substring sequence 1) specs))))
|
||||
(else #f)))
|
||||
|
||||
|
||||
|
||||
;; This procedure does whatever is necessary to put the (ostensibly)
|
||||
;; first item on the command-line into the canonical form
|
||||
;; '--item=value'. Note that the value may be missing, but the '='
|
||||
;; sign will always be there in the return. The first item (/A/) will
|
||||
;; always be *more* than two characters, and the first character will
|
||||
;; be "-", i.e. we are processing a short option which is either
|
||||
;; clumped with other short options, or is clumped with its value.
|
||||
;;
|
||||
;; A IN string The first argument on the command-line
|
||||
;; rest IN list of strings The remaining items of the command-line
|
||||
;; specs IN list of option-spec Options specification
|
||||
;; remnant OUT list of strings The unprocessed command line
|
||||
;; processed OUT string New command-line argument
|
||||
(define (normalize-clumped-short-option A rest specs)
|
||||
(define (declump-arg) (return (cons* (string-append "-" (substring A 1 2))
|
||||
(string-append "-" (substring A 2))
|
||||
rest)
|
||||
#f))
|
||||
(define (construct-arg-from-clumped-value)
|
||||
(return rest (string-append (double-up A specs)
|
||||
(substring A 2))))
|
||||
(unless (is-alpha (string-ref A 1)) (return rest A))
|
||||
(let ((spec (find-spec-short specs (string-ref A 1))))
|
||||
(unless spec (fatal-error "no such option: -~a" (string-ref A 1)))
|
||||
(cond ((option-spec->predicate spec)
|
||||
=> (λ (pred) (if (pred (substring A 2))
|
||||
(construct-arg-from-clumped-value)
|
||||
(declump-arg))))
|
||||
((eq? (option-spec->value-policy spec) 'optional)
|
||||
(if (viable-short (substring A 2) specs)
|
||||
(declump-arg)
|
||||
(construct-arg-from-clumped-value)))
|
||||
((eq? (option-spec->value-policy spec) #f) (declump-arg))
|
||||
(else (construct-arg-from-clumped-value)))))
|
||||
|
||||
|
||||
|
||||
;; Return a version of the command-line /args/ in which all options are
|
||||
;; represented in long form with an equals sign (whether they have a
|
||||
;; value or not).
|
||||
(define (normalize args specs stop-at-first-non-option)
|
||||
(call/ec (λ (return)
|
||||
(let loop ((args args) (processed '()))
|
||||
(when (null? args) (return (reverse processed)))
|
||||
(apply loop (call/ec (λ (loop)
|
||||
(define A (car args))
|
||||
(define (when-loop cond normalizer)
|
||||
(when cond
|
||||
(receive (remainder-args processed-arg)
|
||||
(normalizer A (cdr args) specs)
|
||||
(loop (list remainder-args
|
||||
(if processed-arg
|
||||
(cons processed-arg processed)
|
||||
processed))))))
|
||||
(when (string=? "--" A)
|
||||
(return (append (reverse processed) args)))
|
||||
(when-loop (and (> (string-length A) 2)
|
||||
(string=? (substring A 0 2) "--"))
|
||||
normalize-long-option)
|
||||
(when-loop (and (eq? (string-length A) 2)
|
||||
(eq? (string-ref A 0) #\-))
|
||||
normalize-free-short-option)
|
||||
(when-loop (and (> (string-length A) 1)
|
||||
(eq? (string-ref A 0) #\-))
|
||||
normalize-clumped-short-option)
|
||||
(when stop-at-first-non-option
|
||||
(return (append (reverse processed) args)))
|
||||
;else
|
||||
(loop (list (cdr args) (cons A processed))))))))))
|
||||
|
||||
|
||||
|
||||
;; Check that all the rules inherent in the /specs/ are fulfilled by
|
||||
;; the /options/.
|
||||
(define (verify-specs-fullfilled specs options)
|
||||
(for-each
|
||||
(λ (spec)
|
||||
(let* ((name (option-spec->name spec))
|
||||
(value (assq-ref options (string->symbol name))))
|
||||
(when (and (option-spec->required? spec) (not value))
|
||||
(fatal-error "option must be specified: --~a" name))
|
||||
(let ((policy (option-spec->value-policy spec)))
|
||||
(when (and (eq? policy #t) (eq? value #t))
|
||||
(fatal-error "option must be specified with argument: --~a" name))
|
||||
(when (and (eq? policy #f) (string? value))
|
||||
(fatal-error "option does not support argument: --~a" name)))
|
||||
(let ((pred (option-spec->predicate spec)))
|
||||
(when (and pred (string? value) (not (pred value)))
|
||||
(fatal-error "option predicate failed: --~a" name)))))
|
||||
specs))
|
||||
|
||||
|
||||
|
||||
;; Check that all the options are matched by a specification.
|
||||
(define (verify-options options specs)
|
||||
(for-each
|
||||
(λ (value)
|
||||
(unless (or (null? (car value))
|
||||
(find-spec-long specs (symbol->string (car value))))
|
||||
(fatal-error "no such option: --~a" (car value))))
|
||||
options))
|
||||
|
||||
|
||||
|
||||
;; This procedure will simply return if the options and the specs
|
||||
;; conform with each other, or else will bail out with an error
|
||||
;; message.
|
||||
(define (check-compliance options specs)
|
||||
(verify-specs-fullfilled specs options)
|
||||
(verify-options options specs))
|
||||
|
||||
|
||||
|
||||
(define full-option-re (make-regexp "^--([^=]+)=(.+)?$"))
|
||||
|
||||
;; The /normal-args/ are a normalized command line in which all
|
||||
;; options are expressed long-form, and the task here is to construct an
|
||||
;; /options/ object: an associative array of option names onto values
|
||||
;; (or #t if there is no value).
|
||||
(define (extract-options normal-args stop-at-first-non-option)
|
||||
(let loop ((args normal-args)
|
||||
(options '())
|
||||
(non-options '()))
|
||||
(cond
|
||||
((null? args) (acons '() (reverse non-options) options))
|
||||
(else
|
||||
(cond
|
||||
((string=? (car args) "--")
|
||||
(acons '() (append (reverse non-options) (cdr args)) options))
|
||||
((regexp-exec full-option-re (car args))
|
||||
=> (λ (match)
|
||||
(loop (cdr args)
|
||||
(acons (string->symbol (match:substring match 1))
|
||||
(or (match:substring match 2) #t)
|
||||
options)
|
||||
non-options)))
|
||||
(stop-at-first-non-option
|
||||
(acons '() (append (reverse non-options) args) options))
|
||||
(else
|
||||
(loop (cdr args) options (cons (car args) non-options))))))))
|
||||
|
||||
(define (looks-like-an-option string)
|
||||
(or (regexp-exec short-opt-rx string)
|
||||
(regexp-exec long-opt-with-value-rx string)
|
||||
(regexp-exec long-opt-no-value-rx string)))
|
||||
|
||||
(define (process-options specs argument-ls stop-at-first-non-option)
|
||||
;; Use SPECS to scan ARGUMENT-LS; return (FOUND . ETC).
|
||||
;; FOUND is an unordered list of option specs for found options, while ETC
|
||||
;; is an order-maintained list of elements in ARGUMENT-LS that are neither
|
||||
;; options nor their values.
|
||||
(let ((idx (map (lambda (spec)
|
||||
(cons (option-spec->name spec) spec))
|
||||
specs))
|
||||
(sc-idx (map (lambda (spec)
|
||||
(cons (make-string 1 (option-spec->single-char spec))
|
||||
spec))
|
||||
(remove-if-not option-spec->single-char specs))))
|
||||
(let loop ((unclumped 0) (argument-ls argument-ls) (found '()) (etc '()))
|
||||
(define (eat! spec ls)
|
||||
(cond
|
||||
((eq? 'optional (option-spec->value-policy spec))
|
||||
(if (or (null? ls)
|
||||
(looks-like-an-option (car ls)))
|
||||
(loop (- unclumped 1) ls (acons spec #t found) etc)
|
||||
(loop (- unclumped 2) (cdr ls) (acons spec (car ls) found) etc)))
|
||||
((eq? #t (option-spec->value-policy spec))
|
||||
(if (or (null? ls)
|
||||
(looks-like-an-option (car ls)))
|
||||
(fatal-error "option must be specified with argument: --~a"
|
||||
(option-spec->name spec))
|
||||
(loop (- unclumped 2) (cdr ls) (acons spec (car ls) found) etc)))
|
||||
(else
|
||||
(loop (- unclumped 1) ls (acons spec #t found) etc))))
|
||||
|
||||
(match argument-ls
|
||||
(()
|
||||
(cons found (reverse etc)))
|
||||
((opt . rest)
|
||||
(cond
|
||||
((regexp-exec short-opt-rx opt)
|
||||
=> (lambda (match)
|
||||
(if (> unclumped 0)
|
||||
;; Next option is known not to be clumped.
|
||||
(let* ((c (match:substring match 1))
|
||||
(spec (or (assoc-ref sc-idx c)
|
||||
(fatal-error "no such option: -~a" c))))
|
||||
(eat! spec rest))
|
||||
;; Expand a clumped group of short options.
|
||||
(let* ((extra (match:substring match 2))
|
||||
(unclumped-opts
|
||||
(append (map (lambda (c)
|
||||
(string-append "-" (make-string 1 c)))
|
||||
(string->list
|
||||
(match:substring match 1)))
|
||||
(if (string=? "" extra) '() (list extra)))))
|
||||
(loop (length unclumped-opts)
|
||||
(append unclumped-opts rest)
|
||||
found
|
||||
etc)))))
|
||||
((regexp-exec long-opt-no-value-rx opt)
|
||||
=> (lambda (match)
|
||||
(let* ((opt (match:substring match 1))
|
||||
(spec (or (assoc-ref idx opt)
|
||||
(fatal-error "no such option: --~a" opt))))
|
||||
(eat! spec rest))))
|
||||
((regexp-exec long-opt-with-value-rx opt)
|
||||
=> (lambda (match)
|
||||
(let* ((opt (match:substring match 1))
|
||||
(spec (or (assoc-ref idx opt)
|
||||
(fatal-error "no such option: --~a" opt))))
|
||||
(if (option-spec->value-policy spec)
|
||||
(eat! spec (cons (match:substring match 2) rest))
|
||||
(fatal-error "option does not support argument: --~a"
|
||||
opt)))))
|
||||
((and stop-at-first-non-option
|
||||
(<= unclumped 0))
|
||||
(cons found (append (reverse etc) argument-ls)))
|
||||
(else
|
||||
(loop (- unclumped 1) rest found (cons opt etc)))))))))
|
||||
|
||||
(define* (getopt-long program-arguments option-desc-list
|
||||
#:key stop-at-first-non-option)
|
||||
"Process options, handling both long and short options, similar to
|
||||
"- Scheme Procedure: getopt-long PROGRAM-ARGUMENTS OPTION-DESC-LIST
|
||||
[#:stop-at-first-non-option]
|
||||
|
||||
Process options, handling both long and short options, similar to
|
||||
the glibc function 'getopt_long'. PROGRAM-ARGUMENTS should be a value
|
||||
similar to what (program-arguments) returns. OPTION-DESC-LIST is a
|
||||
list of option descriptions. Each option description must satisfy the
|
||||
|
|
@ -337,35 +573,26 @@ or option values.
|
|||
By default, options are not required, and option values are not
|
||||
required. By default, single character equivalents are not supported;
|
||||
if you want to allow the user to use single character options, you need
|
||||
to add a `single-char' clause to the option description."
|
||||
to add a ‘single-char’ clause to the option description."
|
||||
|
||||
(with-fluids ((%program-name (car program-arguments)))
|
||||
(let* ((specifications (map parse-option-spec option-desc-list))
|
||||
(pair (split-arg-list (cdr program-arguments)))
|
||||
(split-ls (car pair))
|
||||
(non-split-ls (cdr pair))
|
||||
(found/etc (process-options specifications split-ls
|
||||
stop-at-first-non-option))
|
||||
(found (car found/etc))
|
||||
(rest-ls (append (cdr found/etc) non-split-ls)))
|
||||
(for-each (lambda (spec)
|
||||
(let ((name (option-spec->name spec))
|
||||
(val (assq-ref found spec)))
|
||||
(and (option-spec->required? spec)
|
||||
(or val
|
||||
(fatal-error "option must be specified: --~a"
|
||||
name)))
|
||||
(let ((pred (option-spec->predicate spec)))
|
||||
(and pred (pred name val)))))
|
||||
specifications)
|
||||
(for-each (lambda (spec+val)
|
||||
(set-car! spec+val
|
||||
(string->symbol (option-spec->name (car spec+val)))))
|
||||
found)
|
||||
(cons (cons '() rest-ls) found))))
|
||||
(let* ((specs (map parse-option-spec option-desc-list))
|
||||
(options (extract-options
|
||||
(normalize (cdr program-arguments)
|
||||
specs
|
||||
stop-at-first-non-option)
|
||||
stop-at-first-non-option)))
|
||||
(check-compliance options specs)
|
||||
options)))
|
||||
|
||||
|
||||
|
||||
(define (option-ref options key default)
|
||||
"Return value in alist OPTIONS using KEY, a symbol; or DEFAULT if not found.
|
||||
The value is either a string or `#t'."
|
||||
"- Scheme Procedure: option-ref OPTIONS KEY DEFAULT
|
||||
Return value in alist OPTIONS (as returned from getopt-long),
|
||||
using KEY, a symbol; or DEFAULT if not found. The value is either
|
||||
a string or ‘#t’, or whatever DEFAULT is."
|
||||
(or (assq-ref options key) default))
|
||||
|
||||
|
||||
;;; getopt-long.scm ends here
|
||||
|
|
|
|||
|
|
@ -31,19 +31,25 @@
|
|||
set-fields))
|
||||
|
||||
(define (set-record-type-printer! type proc)
|
||||
"Set PROC as the custom printer for TYPE."
|
||||
"- Scheme Procedure: set-record-type-printer! TYPE PROC
|
||||
Set PROC as the custom printer for TYPE."
|
||||
(struct-set! type vtable-index-printer proc))
|
||||
|
||||
(define-syntax-rule (define-immutable-record-type name ctor pred fields ...)
|
||||
"- Scheme Procedure: define-immutable-record-type NAME CTOR PRED (FIELD GETTER [SETTER]) ..."
|
||||
((@@ (srfi srfi-9) %define-record-type)
|
||||
#t (define-immutable-record-type name ctor pred fields ...)
|
||||
name ctor pred fields ...))
|
||||
|
||||
(define-syntax-rule (set-field s (getter ...) expr)
|
||||
"- Scheme Procedure: set-field RECORD (GETTER ...) EXPR
|
||||
Set the field in RECORD with the GETTER, to the value of EXPR."
|
||||
(%set-fields #t (set-field s (getter ...) expr) ()
|
||||
s ((getter ...) expr)))
|
||||
|
||||
(define-syntax-rule (set-fields s . rest)
|
||||
"- Scheme Procedure: set-fields RECORD ((GETTER ...) EXPR) ...
|
||||
Set the fields in the RECORD with the given GETTERs to the corresponding EXPRessions."
|
||||
(%set-fields #t (set-fields s . rest) ()
|
||||
s . rest))
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ SCM_TESTS = tests/00-initial-env.test \
|
|||
tests/c-api.test \
|
||||
tests/chars.test \
|
||||
tests/coding.test \
|
||||
tests/command-line-processor.test \
|
||||
tests/common-list.test \
|
||||
tests/compiler.test \
|
||||
tests/control.test \
|
||||
|
|
|
|||
155
test-suite/tests/command-line-processor.test
Normal file
155
test-suite/tests/command-line-processor.test
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
;;;; command-line-processor.test --- long options processing -*- scheme -*-
|
||||
;;;;
|
||||
;;;; Copyright (C) 2001, 2006, 2011 Free Software Foundation, Inc.
|
||||
;;;;
|
||||
;;;; This library is free software; you can redistribute it and/or
|
||||
;;;; modify it under the terms of the GNU Lesser General Public
|
||||
;;;; License as published by the Free Software Foundation; either
|
||||
;;;; version 3 of the License, or (at your option) any later version.
|
||||
;;;;
|
||||
;;;; This library is distributed in the hope that it will be useful,
|
||||
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
;;;; Lesser General Public License for more details.
|
||||
;;;;
|
||||
;;;; You should have received a copy of the GNU Lesser General Public
|
||||
;;;; License along with this library; if not, write to the Free Software
|
||||
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
;;;; MA 02110-1301 USA
|
||||
|
||||
;;; Author: Dale Mellor <guile-qf1qmg@rdmp.org> --- May 2020
|
||||
|
||||
|
||||
(use-modules (test-suite lib)
|
||||
(ice-9 command-line-processor)
|
||||
(ice-9 regex))
|
||||
|
||||
(define-syntax-rule (pass-if-fatal-exception name exn exp)
|
||||
(let ((port (open-output-string)))
|
||||
(with-error-to-port port
|
||||
(λ ()
|
||||
(run-test
|
||||
name #t
|
||||
(λ ()
|
||||
(catch (car exn)
|
||||
(λ () exp #f)
|
||||
(λ (k . args)
|
||||
(let ((output (get-output-string port)))
|
||||
(close-port port)
|
||||
(if (string-match (cdr exn) output)
|
||||
#t
|
||||
(error "Unexpected output" output)))))))))))
|
||||
|
||||
(defmacro deferr (name-frag re)
|
||||
(let ((name (symbol-append 'exception: name-frag)))
|
||||
`(define ,name (cons 'quit ,re))))
|
||||
|
||||
(deferr no-such-option "no such option")
|
||||
(deferr option-predicate-failed "option predicate failed")
|
||||
(deferr option-does-not-support-arg "option does not support argument")
|
||||
(deferr option-must-be-specified "option must be specified")
|
||||
(deferr option-must-have-arg "option must be specified with argument")
|
||||
|
||||
|
||||
|
||||
(with-test-prefix "exported procs"
|
||||
(pass-if "‘option’ defined" (defined? 'process-command-line)))
|
||||
|
||||
|
||||
(with-test-prefix "extended mcron options"
|
||||
|
||||
(define stdin-predicate (λ (in) (or (string=? in "guile")
|
||||
(string=? in "vixie"))))
|
||||
|
||||
(define app (command-line-specification
|
||||
application "mcron"
|
||||
version "1.4"
|
||||
usage "[OPTIONS]... [FILES]..."
|
||||
help-preamble
|
||||
"Run an mcron process according to the specifications in the FILE... "
|
||||
"(`-' for standard input), or use all the files in ~/.config/cron "
|
||||
"(or the deprecated ~/.cron) with .guile or .vixie extensions.\n"
|
||||
"Note that --daemon and --schedule are mutually exclusive."
|
||||
option (--daemon -d
|
||||
"run as a daemon process")
|
||||
option (--stdin=guile -j (λ (in) (or (string=? in "guile")
|
||||
(string=? in "vixie")))
|
||||
"format of data passed as standard input or file "
|
||||
"arguments, 'guile' or 'vixie' (default guile)")
|
||||
option (--stdin-2=guile -k (lambda (in) (or (string=? in "guile")
|
||||
(string=? in "vixie")))
|
||||
"format of data passed as standard input or file "
|
||||
"arguments, 'guile' or 'vixie' (default guile)")
|
||||
option (--stdin-3=guile -l stdin-predicate
|
||||
"format of data passed as standard input or file "
|
||||
"arguments, 'guile' or 'vixie' (default guile)")
|
||||
option (--schedule=8 -s string->number
|
||||
"display the next N (or 8) jobs that will be run")
|
||||
option (--not-complex short-i "this should just work")
|
||||
help-postamble
|
||||
"Mandatory or optional arguments to long options are also mandatory or "
|
||||
"optional for any corresponding short options."
|
||||
bug-address "bug-mcron@gnu.org"
|
||||
copyright "2003, 2006, 2014, 2020 Free Software Foundation, Inc."
|
||||
license GPLv3))
|
||||
|
||||
(pass-if "specification transformed" #t)
|
||||
|
||||
(process-command-line '("test" "-d" "leftover0" "--schedule=3" "leftover1"
|
||||
"--stdin=vixie" "--stdin-2=guile" "--stdin-3" "vixie"
|
||||
"-i")
|
||||
app)
|
||||
(pass-if "process-command-line completed" #t)
|
||||
|
||||
(pass-if "-d" (eq? --daemon #t))
|
||||
(pass-if "--schedule=3" (string=? --schedule "3"))
|
||||
(pass-if "--!" (equal? --! '("leftover0" "leftover1")))
|
||||
(pass-if "-j" (string=? --stdin "vixie"))
|
||||
(pass-if "-k" (string=? --stdin-2 "guile"))
|
||||
(pass-if "-l" (string=? --stdin-3 "vixie"))
|
||||
(pass-if "-i" (eq? --not-complex #t))
|
||||
|
||||
(process-command-line '("test" "-s9") app)
|
||||
|
||||
(pass-if "! -d" (eq? --daemon #f))
|
||||
(pass-if "-s9" (string=? --schedule "9"))
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
(with-test-prefix "option merging"
|
||||
|
||||
(process-command-line (string-split "prog -a -b -d -h" #\space)
|
||||
(merge-command-line-specifications
|
||||
(command-line-specification
|
||||
application "test-1"
|
||||
option (-a --alpha "option alpha")
|
||||
option (-b --beta "option beta")
|
||||
option (-h --eta "option eta"))
|
||||
(command-line-specification
|
||||
application "test-2"
|
||||
option (-b --beta "option beta")
|
||||
option (-d --delta "option delta"))))
|
||||
|
||||
(pass-if "--alpha" (eq? #t --alpha))
|
||||
(pass-if "--beta" (eq? #t --beta))
|
||||
(pass-if "--delta" (eq? #t --delta))
|
||||
(pass-if "--eta" (eq? #t --eta))
|
||||
(pass-if "--beta-1" (eq? #f --beta-1))
|
||||
(pass-if "--help" (eq? #f --help))
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
(with-test-prefix "all option combinations"
|
||||
|
||||
(define app (command-line-specification option (--alpha -a)))
|
||||
|
||||
(pass-if "specification transformed" #t)
|
||||
|
||||
)
|
||||
|
||||
|
||||
;;; command-line-processor.test ends here
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
;;;; getopt-long.test --- long options processing -*- scheme -*-
|
||||
;;;; Thien-Thi Nguyen <ttn@gnu.org> --- August 2001
|
||||
;;;;
|
||||
;;;; Copyright (C) 2001, 2006, 2011 Free Software Foundation, Inc.
|
||||
;;;; Copyright (C) 2001, 2006, 2011 Free Software Foundation, Inc.
|
||||
;;;;
|
||||
;;;; This library is free software; you can redistribute it and/or
|
||||
;;;; modify it under the terms of the GNU Lesser General Public
|
||||
|
|
@ -17,6 +16,10 @@
|
|||
;;;; License along with this library; if not, write to the Free Software
|
||||
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
;;; Author: Thien-Thi Nguyen <ttn@gnu.org> --- August 2001
|
||||
;;; Dale Mellor <> --- April 2020
|
||||
|
||||
|
||||
(use-modules (test-suite lib)
|
||||
(ice-9 getopt-long)
|
||||
(ice-9 regex))
|
||||
|
|
@ -49,9 +52,34 @@
|
|||
(deferr option-must-be-specified "option must be specified")
|
||||
(deferr option-must-have-arg "option must be specified with argument")
|
||||
|
||||
|
||||
|
||||
(define (symbol/>string a)
|
||||
(if (symbol? a) (symbol->string a) ""))
|
||||
|
||||
(define (output-sort out)
|
||||
(sort out (lambda (a b) (string<? (symbol/>string (car a))
|
||||
(symbol/>string (car b))))))
|
||||
|
||||
(define* (A-TEST args option-specs expectation
|
||||
#:key stop-at-first-non-option)
|
||||
(let ((answer
|
||||
(output-sort
|
||||
(getopt-long
|
||||
(cons "foo" (string-split args #\space))
|
||||
option-specs
|
||||
#:stop-at-first-non-option stop-at-first-non-option))))
|
||||
(cond ((equal? answer (output-sort expectation)) #t)
|
||||
(else (format (current-output-port)
|
||||
"Test result was \n‘~s’ --VS-- \n‘~s’.\n"
|
||||
answer (output-sort expectation))
|
||||
#f))))
|
||||
|
||||
|
||||
|
||||
(with-test-prefix "exported procs"
|
||||
(pass-if "`option-ref' defined" (defined? 'option-ref))
|
||||
(pass-if "`getopt-long' defined" (defined? 'getopt-long)))
|
||||
(pass-if "‘option-ref’ defined" (defined? 'option-ref))
|
||||
(pass-if "‘getopt-long’ defined" (defined? 'getopt-long)))
|
||||
|
||||
(with-test-prefix "specifying predicate"
|
||||
|
||||
|
|
@ -92,33 +120,48 @@
|
|||
|
||||
(with-test-prefix "value optional"
|
||||
|
||||
(define (test3 . args)
|
||||
(getopt-long args '((foo (value optional) (single-char #\f))
|
||||
(bar))))
|
||||
(define (test args expect)
|
||||
(A-TEST args
|
||||
'((foo (value optional) (single-char #\f))
|
||||
(bar))
|
||||
expect))
|
||||
|
||||
(pass-if "long option `foo' w/ arg, long option `bar'"
|
||||
(equal? (test3 "prg" "--foo" "fooval" "--bar")
|
||||
'((()) (bar . #t) (foo . "fooval"))))
|
||||
(pass-if "long option ‘foo’ w/ arg, long option ‘bar’"
|
||||
(test "--foo fooval --bar"
|
||||
'((()) (bar . #t) (foo . "fooval"))))
|
||||
|
||||
(pass-if "short option `foo' w/ arg, long option `bar'"
|
||||
(equal? (test3 "prg" "-f" "fooval" "--bar")
|
||||
'((()) (bar . #t) (foo . "fooval"))))
|
||||
(pass-if "short option ‘foo’ w/ arg, long option ‘bar’"
|
||||
(test "-f fooval --bar"
|
||||
'((()) (bar . #t) (foo . "fooval"))))
|
||||
|
||||
(pass-if "short option `foo', long option `bar', no args"
|
||||
(equal? (test3 "prg" "-f" "--bar")
|
||||
'((()) (bar . #t) (foo . #t))))
|
||||
(pass-if "short option ‘foo’, long option ‘bar’, no args"
|
||||
(test "-f --bar"
|
||||
'((()) (bar . #t) (foo . #t))))
|
||||
|
||||
(pass-if "long option `foo', long option `bar', no args"
|
||||
(equal? (test3 "prg" "--foo" "--bar")
|
||||
'((()) (bar . #t) (foo . #t))))
|
||||
(pass-if "long option ‘foo’, long option ‘bar’, no args"
|
||||
(test "--foo --bar"
|
||||
'((()) (bar . #t) (foo . #t))))
|
||||
|
||||
(pass-if "long option `bar', short option `foo', no args"
|
||||
(equal? (test3 "prg" "--bar" "-f")
|
||||
'((()) (foo . #t) (bar . #t))))
|
||||
(pass-if "long option ‘bar’, short option ‘foo’, no args"
|
||||
(test "--bar -f"
|
||||
'((()) (foo . #t) (bar . #t))))
|
||||
|
||||
(pass-if "long option `bar', long option `foo', no args"
|
||||
(equal? (test3 "prg" "--bar" "--foo")
|
||||
'((()) (foo . #t) (bar . #t))))
|
||||
(pass-if "long option ‘bar’, long option ‘foo’, no args"
|
||||
(test "--bar --foo"
|
||||
'((()) (foo . #t) (bar . #t))))
|
||||
|
||||
(pass-if "long option with equals and space"
|
||||
(test "--foo= test"
|
||||
'((() "test") (foo . #t))))
|
||||
|
||||
(pass-if "long option with equals and space, not allowed a value"
|
||||
(A-TEST "--foo= test"
|
||||
'((foo (value #f)))
|
||||
'((() "test") (foo . #t))))
|
||||
|
||||
(pass-if "--="
|
||||
(test "--="
|
||||
'((() "--="))))
|
||||
|
||||
)
|
||||
|
||||
|
|
@ -133,16 +176,16 @@
|
|||
(bar)))
|
||||
'foo #f)))
|
||||
|
||||
(pass-if "option-ref `--foo 4'"
|
||||
(pass-if "option-ref ‘--foo 4’"
|
||||
(test4 "4" "--foo" "4"))
|
||||
|
||||
(pass-if "option-ref `-f 4'"
|
||||
(pass-if "option-ref ‘-f 4’"
|
||||
(test4 "4" "-f" "4"))
|
||||
|
||||
(pass-if "option-ref `-f4'"
|
||||
(pass-if "option-ref ‘-f4’"
|
||||
(test4 "4" "-f4"))
|
||||
|
||||
(pass-if "option-ref `--foo=4'"
|
||||
(pass-if "option-ref ‘--foo=4’"
|
||||
(test4 "4" "--foo=4"))
|
||||
|
||||
)
|
||||
|
|
@ -227,11 +270,12 @@
|
|||
|
||||
(with-test-prefix "apples-blimps-catalexis example"
|
||||
|
||||
(define spec '((apples (single-char #\a))
|
||||
(blimps (single-char #\b) (value #t))
|
||||
(catalexis (single-char #\c) (value #t))))
|
||||
|
||||
(define (test8 . args)
|
||||
(equal? (sort (getopt-long (cons "foo" args)
|
||||
'((apples (single-char #\a))
|
||||
(blimps (single-char #\b) (value #t))
|
||||
(catalexis (single-char #\c) (value #t))))
|
||||
(equal? (sort (getopt-long (cons "foo" args) spec)
|
||||
(lambda (a b)
|
||||
(cond ((null? (car a)) #t)
|
||||
((null? (car b)) #f)
|
||||
|
|
@ -246,9 +290,38 @@
|
|||
(pass-if "normal 2" (test8 "-ab" "bang" "-c" "couth"))
|
||||
(pass-if "normal 3" (test8 "-ac" "couth" "-b" "bang"))
|
||||
|
||||
(pass-if-fatal-exception "bad ordering causes missing option"
|
||||
exception:option-must-have-arg
|
||||
(test8 "-abc" "couth" "bang"))
|
||||
|
||||
;;;; Dale Mellor 2020-04-14
|
||||
;;;;
|
||||
;;;; I disagree with this test: to my mind 'c' is 'b's argument, and
|
||||
;;;; the other two arguments are non-options which get passed
|
||||
;;;; through; there should not be an exception.
|
||||
|
||||
;; (pass-if-fatal-exception "bad ordering causes missing option"
|
||||
;; exception:option-must-have-arg
|
||||
;; (test8 "-abc" "couth" "bang"))
|
||||
|
||||
(pass-if "clumped options with trailing mandatory value"
|
||||
(A-TEST "-abc couth bang"
|
||||
spec
|
||||
'((() "couth" "bang") (apples . #t) (blimps . "c"))))
|
||||
|
||||
(pass-if "clumped options with trailing optional value"
|
||||
(A-TEST "-abc couth bang"
|
||||
'((apples (single-char #\a))
|
||||
(blimps (single-char #\b)
|
||||
(value optional)))
|
||||
'((() "couth" "bang") (apples . #t) (blimps . "c"))))
|
||||
|
||||
(pass-if "clumped options with trailing optional value"
|
||||
(A-TEST "-abc couth bang"
|
||||
'((apples (single-char #\a))
|
||||
(blimps (single-char #\b)
|
||||
(value optional))
|
||||
(catalexis (single-char #\c)
|
||||
(value #t)))
|
||||
'((() "bang")
|
||||
(apples . #t) (blimps . #t) (catalexis . "couth"))))
|
||||
|
||||
)
|
||||
|
||||
|
|
@ -291,12 +364,165 @@
|
|||
(with-test-prefix "stop-at-first-non-option"
|
||||
|
||||
(pass-if "guile-tools compile example"
|
||||
(equal? (getopt-long '("guile-tools" "compile" "-Wformat" "eval.scm" "-o" "eval.go")
|
||||
(equal? (getopt-long '("guile-tools" "compile" "-Wformat"
|
||||
"eval.scm" "-o" "eval.go")
|
||||
'((help (single-char #\h))
|
||||
(version (single-char #\v)))
|
||||
#:stop-at-first-non-option #t)
|
||||
'((() "compile" "-Wformat" "eval.scm" "-o" "eval.go"))))
|
||||
|
||||
(pass-if "stop after option"
|
||||
(equal? (getopt-long '("foo" "-a" "3" "4" "-b" "4")
|
||||
'((about (single-char #\a) (value #t))
|
||||
(breathe (single-char #\b) (value #t)))
|
||||
#:stop-at-first-non-option #t)
|
||||
'((() "4" "-b" "4") (about . "3"))))
|
||||
)
|
||||
|
||||
|
||||
(with-test-prefix "stop at end-of-options marker"
|
||||
|
||||
(define* (test args expectation #:key stop-at-first-non-option)
|
||||
(A-TEST args
|
||||
'((abby) (ben) (charles))
|
||||
expectation
|
||||
#:stop-at-first-non-option stop-at-first-non-option))
|
||||
|
||||
(pass-if "stop at start" (test "-- --abby" '((() "--abby"))))
|
||||
|
||||
(pass-if "stop in middle" (test "--abby dave -- --ben"
|
||||
'((() "dave" "--ben") (abby . #t))))
|
||||
|
||||
(pass-if "stop at end" (test "--abby dave --ben --"
|
||||
'((() "dave") (abby . #t) (ben . #t))))
|
||||
|
||||
(pass-if "marker before first non-option"
|
||||
(test "--abby -- --ben dave --charles"
|
||||
'((() "--ben" "dave" "--charles") (abby . #t))
|
||||
#:stop-at-first-non-option #t))
|
||||
|
||||
(pass-if "first non-option before marker"
|
||||
(test "--abby dave --ben -- --charles"
|
||||
'((() "dave" "--ben" "--" "--charles") (abby . #t))
|
||||
#:stop-at-first-non-option #t))
|
||||
|
||||
(pass-if "double end marker"
|
||||
(test "--abby -- -- --ben"
|
||||
'((() "--" "--ben") (abby . #t))))
|
||||
|
||||
(pass-if "separated double end markers"
|
||||
(test "--abby dave -- --ben -- --charles"
|
||||
'((() "dave" "--ben" "--" "--charles")
|
||||
(abby . #t))))
|
||||
)
|
||||
|
||||
|
||||
(with-test-prefix "negative numbers for option values"
|
||||
|
||||
(define (test args expectation)
|
||||
(A-TEST args
|
||||
`((arthur (single-char #\a) (value optional)
|
||||
(predicate ,string->number))
|
||||
(beth (single-char #\b) (value #t)
|
||||
(predicate ,string->number))
|
||||
(charles (single-char #\c) (value optional))
|
||||
(dave (single-char #\d) (value #t)))
|
||||
expectation))
|
||||
|
||||
(pass-if "predicated --optional=-1"
|
||||
(test "--arthur=-1" '((()) (arthur . "-1"))))
|
||||
|
||||
(pass-if "predicated -o-1"
|
||||
(test "-a-1" '((()) (arthur . "-1"))))
|
||||
|
||||
(pass-if "predicated --optional -1"
|
||||
(test "--arthur -1" '((()) (arthur . "-1"))))
|
||||
|
||||
(pass-if "predicated -o -1"
|
||||
(test "-a -1" '((()) (arthur . "-1"))))
|
||||
|
||||
(pass-if "predicated --mandatory=-1"
|
||||
(test "--beth=-1" '((()) (beth . "-1"))))
|
||||
|
||||
(pass-if "predicated -m-1"
|
||||
(test "-b-1" '((()) (beth . "-1"))))
|
||||
|
||||
(pass-if "predicated --mandatory -1"
|
||||
(test "--beth -1" '((()) (beth . "-1"))))
|
||||
|
||||
(pass-if "predicated -m -1"
|
||||
(test "-b -1" '((()) (beth . "-1"))))
|
||||
|
||||
(pass-if "non-predicated --optional=-1"
|
||||
(test "--charles=-1" '((()) (charles . "-1"))))
|
||||
|
||||
(pass-if "non-predicated -o-1"
|
||||
(test "-c-1" '((()) (charles . "-1"))))
|
||||
|
||||
(pass-if-fatal-exception "non-predicated --optional -1"
|
||||
exception:no-such-option
|
||||
(test "--charles -1" '((()) (charles . "-1"))))
|
||||
|
||||
(pass-if-fatal-exception "non-predicated -o -1"
|
||||
exception:no-such-option
|
||||
(test "-c -1" '((()) (charles . "-1"))))
|
||||
|
||||
(pass-if "non-predicated --mandatory=-1"
|
||||
(test "--dave=-1" '((()) (dave . "-1"))))
|
||||
|
||||
(pass-if "non-predicated -m-1"
|
||||
(test "-d-1" '((()) (dave . "-1"))))
|
||||
|
||||
(pass-if "non-predicated --mandatory -1"
|
||||
(test "--dave -1" '((()) (dave . "-1"))))
|
||||
|
||||
(pass-if "non-predicated -m -1"
|
||||
(test "-d -1" '((()) (dave . "-1"))))
|
||||
|
||||
)
|
||||
|
||||
|
||||
(with-test-prefix "mcron backwards compatibility"
|
||||
|
||||
(define (test args expectation)
|
||||
(A-TEST args
|
||||
`((daemon (single-char #\d) (value #f))
|
||||
(stdin (single-char #\i) (value #t)
|
||||
(predicate ,(λ (in) (or (string=? in "guile")
|
||||
(string=? in "vixie")))))
|
||||
(schedule (single-char #\s) (value optional)
|
||||
(predicate ,(λ (in) (or (eq? in #t)
|
||||
(and (string? in)
|
||||
(string->number in))))))
|
||||
(help (single-char #\?))
|
||||
(version (single-char #\V)))
|
||||
expectation))
|
||||
|
||||
(pass-if "-s8" (test "-s8 file" '((() "file") (schedule . "8"))))
|
||||
|
||||
(pass-if "-s 8" (test "-s 8 file" '((() "file") (schedule . "8"))))
|
||||
|
||||
(pass-if "-s file"
|
||||
(test "-s file" '((() "file") (schedule . #t))))
|
||||
|
||||
(pass-if "-sd file"
|
||||
(test "-sd file" '((() "file") (daemon . #t) (schedule . #t))))
|
||||
|
||||
(pass-if "-ds file"
|
||||
(test "-ds file" '((() "file") (daemon . #t) (schedule . #t))))
|
||||
|
||||
(pass-if "--schedule=8" (test "--schedule=8 file"
|
||||
'((() "file") (schedule . "8"))))
|
||||
|
||||
(pass-if "--schedule 8" (test "--schedule 8 file"
|
||||
'((() "file") (schedule . "8"))))
|
||||
|
||||
(pass-if "-ds8" (test "-ds8 file"
|
||||
'((() "file") (daemon . #t) (schedule . "8"))))
|
||||
|
||||
(pass-if "-ds 8" (test "-ds 8 file"
|
||||
'((() "file") (daemon . #t) (schedule . "8"))))
|
||||
|
||||
)
|
||||
|
||||
;;; getopt-long.test ends here
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue