Use define-option to define command-line options and define-command for defining subcommands that control specific sub-functionality. Basic usage of a program that uses this facility is
PROGRAM [OPTION | ARGUMENT] ...
PROGRAM [COMMAND [OPTION | ARGUMENT] ...]
where PROGRAM is the program name, COMMAND is an optional subcommand and OPTION and ARGUMENT are command-line options and arguments to those, respectively. define-option defines global or subcommand-specific command-line options, define-command defines subcommands and specifies their behaviour. After you have finished declaring options and commands, invoke tool-main to process the remainingcommand-line arguments and invoke command and option processors.
The option -h / --help is predefined and invokes tool-usage.
This extension supports static linking.
[syntax] (define-option OPTION DOCUMENTATION [PROC])
Defines the command-line option OPTION which may be a character, string or SRFI-37 option value. The option expects a required argument. If an option, the option argument flag (required or optional) must be a string which is used in tool-usage to show a more informative option documentation. DOCUMENTATION should be a string describing the option.
PROC, if given, should be an option-processor as described in SRFI-37 or a symbol. If PROC is a symbol then giving this option on the command line will set the global variable with the same name to the option argument. If PROC is missing the name of option global variable is the same as OPTION which should be a string, symbol or character.
[syntax] (define-flag OPTIONNAME DOCUMENTATION [VARIABLE])
Defines an option that takes no arguments and sets VARIABLE to #t if the option is given. If VARIABLE is not specified, the name of the variable is the same as OPTIONNAME which should be a string, symbol or character.
[syntax] (define-command NAME DOCUMENTATION BODY ...)
Defines a subcommand. NAME should be a string or symbol. DOCUMENTATION should be a string describing the operation of this command. BODY ... is evaluated and should return a one-argument procedure that is invoked when the subcommand is specified on the command-line, with all non-option arguments collected in a list.
If a tool provides subcommands, then the command name must be the first program argument given on the command line.
Note that you can use define-option and define-flag in the body to declare command-specific options.
[syntax] (define-tool (NAME [INPUTVAR [COUNTVAR [INDEXVAR]]]) HELPSTRING BODY ...)
A convenience macro that wraps common uses of tool-name, tool-help and tool-main. Defines a tool named NAME with a help string HELP, and calls tool-main with the value of (command-line-arguments) and an argument processor that invokes body ... for each given command line argument, with INPUTVAR bound to the current argument. If no command line arguments were given, the value of (current-input-port) will be bound to INPUTVAR. When INPUTVAR is missing the variable named argument is in scope.
The number of arguments is bound to COUNTVAR. When COUNTVAR is missing the variable named arguments-count is in scope.
The current argument index is bound to INDEXVAR. When INDEXVAR is missing the variable named arguments-index is in scope.
define-tool can be defined like this:
(define-macro (define-tool head help . body) (let-optionals head ((name (string->symbol (tool-name))) (input 'argument) (args-cnt-var 'argument-count) (args-idx-var 'argument-index)) (let ((args-var (gensym))) `(begin (tool-name ,(symbol->string name)) (tool-help ,help) (tool-main (command-line-arguments) (lambda (,args-var) (let ((,args-cnt-var (length ,args-var)) (,args-idx-var 0)) (for-each (lambda (,input) ,@body (set! ,args-idx-var (+ ,args-idx-var 1))) (if (null? ,args-var) (list (current-input-port)) ,args-var) ) ) ) ) ) ) ) )
[procedure] (tool-main ARGUMENTS [PROC [THUNK]])
Starts command-line processing of ARGUMENTS. If no arguments are given THUNK is invoked (which defaults to a procedure that calls (tool-usage 1)). If non-option arguments are supplied, PROC is called with the argument list, if given.
[procedure] (tool-name [STRING])
Sets or returns the name of the program, which defaults to (car (argv)).
[procedure] (tool-help STRING)
Sets or returns the program description.
[procedure] (tool-usage STATUS [COMMAND])
Shows usage information collected from (tool-help) and the option and subcommand documentation and exits with status code STATUS. If COMMAND is given, the usage information shown lists global options and the description of the command. If no command is given, then global options and all available subcommands are shown. Finally (tool-exit STATUS) is invoked.
[procedure] (tool-exit [STATUS])
Terminates the program and returns STATUS (which defaults to 0). If invoked inside the dynamic scope of tool-main, then the call to tool-main returns with the given status code. If called outside of the dynamic scope of tool-main, (exit STATUS) is performed.
[procedure] (tool-error MESSAGE [STATUS [COMMAND]])
Displays an error MESSAGE and invokes tool-usage with the any optional arguments. Default value for STATUS is 1. Default value for COMMAND is the same as tool-usage.
A simple cat(1) tool:
;;;; cat.scm (use tool utils format-modular) (define-flag #\n "number output lines" numlines) (tool-name "cat") (tool-help "Concatenate input files (or standard input) to standard output") (tool-main (command-line-arguments) (lambda (args) (for-each (lambda (f) (if numlines (do ((i 1 (add1 i)) (lns (read-lines f) (cdr lns)) ) ((null? lns)) (format #t "~6d\t~a~%" i (car lns)) ) (display (read-all f)) ) ) (if (null? args) (list (current-input-port)) args) ) ) )
Copyright (c) 2007, Felix L. Winkelmann All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- added 'tool-error' procedure, "argument-index" to 'define-tool' [Kon Lovett]
- binding of 'tool-exit' in 'tool-main' should be "fluid" [Kon Lovett]
- added count of non-option arguments to 'define-tool' [Kon Lovett]
- didn't work with syntax-case at all
- uses args-doc for usage information [Thanks to Ivan Shmakov]
- toplevel help output is somewhat compressed
- fixed bug in handling of option processor in define-option
- order of non-option arguments was reversed
- added define-tool [suggested by Arto Bendiken]
- initial release