Adding (chibi app) docs.

This commit is contained in:
Alex Shinn 2015-04-30 17:21:36 +09:00
parent 315d87174c
commit 93730efceb
4 changed files with 193 additions and 30 deletions

View file

@ -40,7 +40,7 @@ COMPILED_LIBS = $(CHIBI_COMPILED_LIBS) $(CHIBI_IO_COMPILED_LIBS) \
BASE_INCLUDES = include/chibi/sexp.h include/chibi/features.h include/chibi/install.h include/chibi/bignum.h BASE_INCLUDES = include/chibi/sexp.h include/chibi/features.h include/chibi/install.h include/chibi/bignum.h
INCLUDES = $(BASE_INCLUDES) include/chibi/eval.h INCLUDES = $(BASE_INCLUDES) include/chibi/eval.h
MODULE_DOCS := ast config disasm equiv filesystem generic heap-stats io \ MODULE_DOCS := app ast config disasm equiv filesystem generic heap-stats io \
loop match mime modules net pathname process repl scribble stty \ loop match mime modules net pathname process repl scribble stty \
system test time trace type-inference uri weak monad/environment \ system test time trace type-inference uri weak monad/environment \
show show/base crypto/sha2 show show/base crypto/sha2

View file

@ -1192,6 +1192,8 @@ namespace.
\itemlist[ \itemlist[
\item{\hyperlink["lib/chibi/app.html"]{(chibi app) - Unified option parsing and config}}
\item{\hyperlink["lib/chibi/ast.html"]{(chibi ast) - Abstract Syntax Tree and other internal data types}} \item{\hyperlink["lib/chibi/ast.html"]{(chibi ast) - Abstract Syntax Tree and other internal data types}}
\item{\hyperlink["lib/chibi/config.html"]{(chibi config) - General configuration management}} \item{\hyperlink["lib/chibi/config.html"]{(chibi config) - General configuration management}}

View file

@ -2,7 +2,152 @@
;; Copyright (c) 2012-2015 Alex Shinn. All rights reserved. ;; Copyright (c) 2012-2015 Alex Shinn. All rights reserved.
;; BSD-style license: http://synthcode.com/license.txt ;; BSD-style license: http://synthcode.com/license.txt
;;> Parses command-line options into a config object. ;;> The high-level interface. Given an application spec \var{spec},
;;> parses the given command-line arguments \var{args} into a config
;;> object, prepended to the existing object \var{config} if given.
;;> Then runs the corresponding command (or sub-command) procedure
;;> from \var{spec}.
;;>
;;> The app spec should be a list of the form:
;;>
;;> \scheme{(<command> [<doc-string>] <clauses> ...)}
;;>
;;> where clauses can be any of:
;;>
;;> \itemlist[
;;> \item[\scheme{(@ <opt-spec>)} - option spec, described below]
;;> \item[\scheme{(begin: <begin-proc>)} - procedure to run before main]
;;> \item[\scheme{(end: <end-proc>)} - procedure to run after main]
;;> \item[\scheme{(<proc> args ...)} - main procedure (args only for documentation)]
;;> \item[\scheme{<app-spec>} - a subcommand described by the nested spec]
;;> \item[\scheme{(or <app-spec> ...)} - an alternate list of subcommands]
;;> ]
;;>
;;> For subcommands the symbolic command name must match, though it is
;;> ignored for the initial spec (i.e. the application name is not
;;> checked). The \scheme{begin} and \scheme{end} procedures can be
;;> useful for loading and saving state common to all subcommands.
;;>
;;> The \scheme{opt-spec} describes command-line options, and is a
;;> simple list with each opt of the form:
;;>
;;> \scheme{(<name> <type> [(<aliases> ...)] [<doc-string>])}
;;>
;;> where \scheme{<name>} is a symbol name, \scheme{<aliases>} is an
;;> optional list of strings (for long options) or characters (for
;;> short options) to serve as aliases in addition to the exact name.
;;> \scheme{type} can be any of:
;;>
;;> \itemlist[
;;> \item{\scheme{boolean} - boolean, associated value optional, allowing \scheme{--noname} to indicate \scheme{#false}}
;;> \item{[\scheme{char} - a single character}
;;> \item{\scheme{integer} - an exact integer}
;;> \item{\scheme{real} - any real number}
;;> \item{\scheme{number} - any real or complex number}
;;> \item{\scheme{symbol} - a symbol}
;;> \item{\scheme{string} - a string}
;;> \item{\scheme{sexp} - a sexp parsed with \scheme{read}}
;;> \item{\scheme{(list <type>)} - a comma-delimited list of types}
;;> ]
;;>
;;> Note that the options specs are composed entirely of objects that
;;> can be read and written, thus for example optionally loaded from
;;> files, whereas the app specs include embedded procedure objects so
;;> are typically written with \scheme{quasiquote}.
;;>
;;> Complete Example:
;;>
;;> \schemeblock{
;;> (run-application
;;> `(zoo
;;> "Zookeeper Application"
;;> (@
;;> (animals (list symbol) "list of animals to act on (default all)")
;;> (lions boolean (#\l) "also apply the action to lions"))
;;> (or
;;> (feed "feed the animals" () (,feed animals ...))
;;> (wash "wash the animals" (@ (soap boolean)) (,wash animals ...))
;;> (help "print help" (,app-help-command)))
;;> (command-line)
;;> (conf-load (string-append (get-environment-variable "HOME") "/.zoo")))
;;> }
;;>
;;> The second and third arguments here are optional, provided to show
;;> the common pattern of allowing the same options to be specified
;;> either in a file and/or on the command-line. The above app can be
;;> run as:
;;>
;;> Feed all animals, including lions:
;;> \command{zoo -l feed}
;;>
;;> Wash the elephants with soap:
;;> \command{zoo --animals=elephant wash --soap}
;;>
;;> Print help:
;;> \command{zoo help}
;;>
;;> The application procedures themselves are of the form:
;;>
;;> \scheme{(proc cfg spec args ...)}
;;>
;;> where \var{cfg} is a config object from \scheme{(chibi config)}
;;> holding the parsed option info, \var{spec} is the original app
;;> spec, and \var{args} are the remaining non-option command-line
;;> arguments.
;;>
;;> To retrieve the options for the above example you can use:
;;>
;;> \itemlist[
;;> \item{\scheme{(conf-get cfg 'animals)}}
;;> \item{\scheme{(conf-get cfg 'lions)}}
;;> \item{\scheme{(conf-get cfg '(command wash soap))}}
;;> ]
;;>
;;> Notice that options for subcommands are nested under the
;;> \scheme{(command <name>)} prefix, so that you can use the same
;;> name for different subcommands without conflict. This also means
;;> the subcommand options are distinct from the top-level options, so
;;> when using subcommands users must always write the command line
;;> as:
;;>
;;> \command{app [<general options>] <subcommand> [<sub options>]}
;;>
;;> The ~/.zoo file could then hold an sexp of the form:
;;>
;;> \schemeblock{
;;> ((animals (camel elephant rhinocerous))
;;> (command
;;> (wash
;;> (soap #t))))
;;> }
(define (run-application spec . o)
(let ((args (or (and (pair? o) (car o)) (command-line)))
(config (and (pair? o) (pair? (cdr o)) (cadr o))))
(cond
((parse-app '() (cdr spec) '() (cdr args) config #f #f)
=> (lambda (v)
(let ((proc (vector-ref v 0))
(cfg (vector-ref v 1))
(args (vector-ref v 2))
(init (vector-ref v 3))
(end (vector-ref v 4)))
(if init (init cfg))
(apply proc cfg spec args)
(if end (end cfg)))))
((null? (cdr args))
(app-help spec args)
(error "Expected a command"))
(else
(error "Unknown command" (cdr args))))))
;;> Parse a single command-line argument from \var{args} according to
;;> \var{conf-spec}, and returns a list of two values: the
;;> \scheme{(name value)} for the option, and a list of remaining
;;> unparsed args. \scheme{name} will have the current \var{prefix}
;;> prepended. If a parse error or unknown option is found, calls
;;> \var{fail} with a single string argument describing the error,
;;> returning that result.
(define (parse-option prefix conf-spec args fail) (define (parse-option prefix conf-spec args fail)
(define (parse-value type str) (define (parse-value type str)
@ -94,7 +239,8 @@
(let ((res (parse-long-option (substring str 2) args (lambda args #f)))) (let ((res (parse-long-option (substring str 2) args (lambda args #f))))
(cond (cond
((not res) ((not res)
(fail prefix conf-spec (car fail-args) fail-args "unknown option")) (fail prefix conf-spec (car fail-args) fail-args
"unknown option"))
((not (boolean? (cdar res))) ((not (boolean? (cdar res)))
(error "'no' prefix only valid on boolean options")) (error "'no' prefix only valid on boolean options"))
(else (else
@ -104,7 +250,8 @@
((and (eq? 'boolean (cadr spec)) (null? (cdr str+val))) ((and (eq? 'boolean (cadr spec)) (null? (cdr str+val)))
(cons (cons (append prefix (list (car spec))) #t) args)) (cons (cons (append prefix (list (car spec))) #t) args))
((null? args) ((null? args)
(fail prefix conf-spec (car fail-args) fail-args "missing argument to option")) (fail prefix conf-spec (car fail-args) fail-args
"missing argument to option"))
(else (else
(let ((val+err (parse-value (cadr spec) (car args)))) (let ((val+err (parse-value (cadr spec) (car args))))
(if (cadr val+err) (if (cadr val+err)
@ -135,13 +282,19 @@
(cons (cons (append prefix (list (car x))) (car val+err)) (cons (cons (append prefix (list (car x))) (car val+err))
args)))) args))))
((null? args) ((null? args)
(fail prefix conf-spec (car fail-args) fail-args "missing argument to option")) (fail prefix conf-spec (car fail-args) fail-args
"missing argument to option"))
(else (else
(cons (cons (append prefix (list (car x))) (car args)) (cdr args)))))) (cons (cons (append prefix (list (car x))) (car args)) (cdr args))))))
(if (eqv? #\- (string-ref (car args) 1)) (if (eqv? #\- (string-ref (car args) 1))
(parse-long-option (substring (car args) 2) (cdr args) fail) (parse-long-option (substring (car args) 2) (cdr args) fail)
(parse-short-option (substring (car args) 1) (cdr args) fail))) (parse-short-option (substring (car args) 1) (cdr args) fail)))
;;> Parse a list of command-line arguments into a config object.
;;> Returns a list whose head is the resulting config object, and tail
;;> is the list of remaining non-option arguments. Calls fail on
;;> error and tries to continue processing from the result.
(define (parse-options prefix conf-spec orig-args fail) (define (parse-options prefix conf-spec orig-args fail)
(let lp ((args orig-args) (let lp ((args orig-args)
(opts (make-conf '() #f (cons 'options orig-args) #f))) (opts (make-conf '() #f (cons 'options orig-args) #f)))
@ -156,6 +309,22 @@
(lp (cdr val+args) (lp (cdr val+args)
(conf-set opts (caar val+args) (cdar val+args)))))))) (conf-set opts (caar val+args) (cdar val+args))))))))
;;> Parses a list of command-line arguments \var{args} according to
;;> the application spec \var{opt-spec}. Returns a vector of five
;;> elements:
;;>
;;> \itemlist[
;;> \item{\scheme{proc} - procedure to run the application}
;;> \item{\scheme{config} - a config object containing all parsed options}
;;> \item{\scheme{args} - a list of remaining unparsed command-line arguments}
;;> \item{\scheme{init} - an optional procedure to call before \scheme{proc}}
;;> \item{\scheme{end} - an optional procedure to call after \scheme{proc}}
;;> ]
;;>
;;> The config object is prepended to \var{config}, with option names
;;> all prefixed by \var{prefix}. The original \var{spec} is used for
;;> \scheme{app-help}.
(define (parse-app prefix spec opt-spec args config init end . o) (define (parse-app prefix spec opt-spec args config init end . o)
(define (next-prefix prefix name) (define (next-prefix prefix name)
(append (if (null? prefix) '(command) prefix) (list name))) (append (if (null? prefix) '(command) prefix) (list name)))
@ -181,23 +350,28 @@
(cfg+args (parse-options prefix new-opt-spec args new-fail)) (cfg+args (parse-options prefix new-opt-spec args new-fail))
(config (conf-append (car cfg+args) config)) (config (conf-append (car cfg+args) config))
(args (cdr cfg+args))) (args (cdr cfg+args)))
(parse-app prefix (cdr spec) new-opt-spec args config init end new-fail))) (parse-app prefix (cdr spec) new-opt-spec args config
init end new-fail)))
((or) ((or)
(any (lambda (x) (parse-app prefix x opt-spec args config init end)) (any (lambda (x) (parse-app prefix x opt-spec args config init end))
(cdar spec))) (cdar spec)))
((begin:) ((begin:)
(parse-app prefix (cdr spec) opt-spec args config (cadr (car spec)) end fail)) (parse-app prefix (cdr spec) opt-spec args config
(cadr (car spec)) end fail))
((end:) ((end:)
(parse-app prefix (cdr spec) opt-spec args config init (cadr (car spec)) fail)) (parse-app prefix (cdr spec) opt-spec args config
init (cadr (car spec)) fail))
(else (else
(if (procedure? (caar spec)) (if (procedure? (caar spec))
(vector (caar spec) config args init end) ; TODO: verify (vector (caar spec) config args init end) ; TODO: verify
(parse-app prefix (car spec) opt-spec args config init end fail))))) (parse-app prefix (car spec) opt-spec args config
init end fail)))))
((symbol? (car spec)) ((symbol? (car spec))
(and (pair? args) (and (pair? args)
(eq? (car spec) (string->symbol (car args))) (eq? (car spec) (string->symbol (car args)))
(let ((prefix (next-prefix prefix (car spec)))) (let ((prefix (next-prefix prefix (car spec))))
(parse-app prefix (cdr spec) opt-spec (cdr args) config init end fail)))) (parse-app prefix (cdr spec) opt-spec (cdr args) config
init end fail))))
((procedure? (car spec)) ((procedure? (car spec))
(vector (car spec) config args init end)) (vector (car spec) config args init end))
(else (else
@ -262,6 +436,8 @@
(if (pair? options) (display "Options:\n" out)) (if (pair? options) (display "Options:\n" out))
(for-each (lambda (o) (print-option-help o out)) options))) (for-each (lambda (o) (print-option-help o out)) options)))
;;> Print a help summary for the given application spec \var{spec}.
(define (app-help spec args . o) (define (app-help spec args . o)
(let ((out (if (pair? o) (car o) (current-output-port)))) (let ((out (if (pair? o) (car o) (current-output-port))))
(let lp ((ls (cdr spec)) (let lp ((ls (cdr spec))
@ -286,25 +462,9 @@
(else (else
(lp (cdr ls) docs commands options)))))) (lp (cdr ls) docs commands options))))))
;;> The subcommand form of \scheme{app-help}. You can use this as a
;;> subcommand in an application spec, for example as:
;;> \schemeblock{(help "print help" (,app-help-command args ...))}
(define (app-help-command config spec . args) (define (app-help-command config spec . args)
(app-help spec args (current-output-port))) (app-help spec args (current-output-port)))
(define (run-application spec . o)
(let ((args (or (and (pair? o) (car o)) (command-line)))
(config (and (pair? o) (pair? (cdr o)) (cadr o))))
(cond
((parse-app '() (cdr spec) '() (cdr args) config #f #f)
=> (lambda (v)
(let ((proc (vector-ref v 0))
(cfg (vector-ref v 1))
(args (vector-ref v 2))
(init (vector-ref v 3))
(end (vector-ref v 4)))
(if init (init cfg))
(apply proc cfg spec args)
(if end (end cfg)))))
((null? (cdr args))
(apply app-help-command config spec args)
(error "Expected a command"))
(else
(error "Unknown command: " (cdr args))))))

View file

@ -1,3 +1,4 @@
;;> Unified command-line option parsing and config management.
(define-library (chibi app) (define-library (chibi app)
(export parse-option parse-options parse-app run-application (export parse-option parse-options parse-app run-application