123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- ;;; srfi-37.scm --- args-fold
- ;; Copyright (C) 2007, 2008, 2013 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
- ;;; Commentary:
- ;;
- ;; To use this module with Guile, use (cdr (program-arguments)) as
- ;; the ARGS argument to `args-fold'. Here is a short example:
- ;;
- ;; (args-fold (cdr (program-arguments))
- ;; (let ((display-and-exit-proc
- ;; (lambda (msg)
- ;; (lambda (opt name arg)
- ;; (display msg) (quit) (values)))))
- ;; (list (option '(#\v "version") #f #f
- ;; (display-and-exit-proc "Foo version 42.0\n"))
- ;; (option '(#\h "help") #f #f
- ;; (display-and-exit-proc
- ;; "Usage: foo scheme-file ..."))))
- ;; (lambda (opt name arg)
- ;; (error "Unrecognized option `~A'" name))
- ;; (lambda (op) (load op) (values)))
- ;;
- ;;; Code:
- ;;;; Module definition & exports
- (define-module (srfi srfi-37)
- #:use-module (srfi srfi-9)
- #:export (option option-names option-required-arg?
- option-optional-arg? option-processor
- args-fold))
- (cond-expand-provide (current-module) '(srfi-37))
- ;;;; args-fold and periphery procedures
- ;;; An option as answered by `option'. `names' is a list of
- ;;; characters and strings, representing associated short-options and
- ;;; long-options respectively that should use this option's
- ;;; `processor' in an `args-fold' call.
- ;;;
- ;;; `required-arg?' and `optional-arg?' are mutually exclusive
- ;;; booleans and indicate whether an argument must be or may be
- ;;; provided. Besides the obvious, this affects semantics of
- ;;; short-options, as short-options with a required or optional
- ;;; argument cannot be followed by other short options in the same
- ;;; program-arguments string, as they will be interpreted collectively
- ;;; as the option's argument.
- ;;;
- ;;; `processor' is called when this option is encountered. It should
- ;;; accept the containing option, the element of `names' (by `equal?')
- ;;; encountered, the option's argument (or #f if none), and the seeds
- ;;; as variadic arguments, answering the new seeds as values.
- (define-record-type srfi-37:option
- (option names required-arg? optional-arg? processor)
- option?
- (names option-names)
- (required-arg? option-required-arg?)
- (optional-arg? option-optional-arg?)
- (processor option-processor))
- (define (error-duplicate-option option-name)
- (scm-error 'program-error "args-fold"
- "Duplicate option name `~A~A'"
- (list (if (char? option-name) #\- "--")
- option-name)
- #f))
- (define (build-options-lookup options)
- "Answer an `equal?' Guile hash-table that maps OPTIONS' names back
- to the containing options, signalling an error if a name is
- encountered more than once."
- (let ((lookup (make-hash-table (* 2 (length options)))))
- (for-each
- (lambda (opt)
- (for-each (lambda (name)
- (let ((assoc (hash-create-handle!
- lookup name #f)))
- (if (cdr assoc)
- (error-duplicate-option (car assoc))
- (set-cdr! assoc opt))))
- (option-names opt)))
- options)
- lookup))
- (define (args-fold args options unrecognized-option-proc
- operand-proc . seeds)
- "Answer the results of folding SEEDS as multiple values against the
- program-arguments in ARGS, as decided by the OPTIONS'
- `option-processor's, UNRECOGNIZED-OPTION-PROC, and OPERAND-PROC."
- (let ((lookup (build-options-lookup options)))
- ;; I don't like Guile's `error' here
- (define (error msg . args)
- (scm-error 'misc-error "args-fold" msg args #f))
- (define (mutate-seeds! procedure . params)
- (set! seeds (call-with-values
- (lambda ()
- (apply procedure (append params seeds)))
- list)))
- ;; Clean up the rest of ARGS, assuming they're all operands.
- (define (rest-operands)
- (for-each (lambda (arg) (mutate-seeds! operand-proc arg))
- args)
- (set! args '()))
- ;; Call OPT's processor with OPT, NAME, an argument to be decided,
- ;; and the seeds. Depending on OPT's *-arg? specification, get
- ;; the parameter by calling REQ-ARG-PROC or OPT-ARG-PROC thunks;
- ;; if no argument is allowed, call NO-ARG-PROC thunk.
- (define (invoke-option-processor
- opt name req-arg-proc opt-arg-proc no-arg-proc)
- (mutate-seeds!
- (option-processor opt) opt name
- (cond ((option-required-arg? opt) (req-arg-proc))
- ((option-optional-arg? opt) (opt-arg-proc))
- (else (no-arg-proc) #f))))
- ;; Compute and answer a short option argument, advancing ARGS as
- ;; necessary, for the short option whose character is at POSITION
- ;; in the current ARG.
- (define (short-option-argument position)
- (cond ((< (1+ position) (string-length (car args)))
- (let ((result (substring (car args) (1+ position))))
- (set! args (cdr args))
- result))
- ((pair? (cdr args))
- (let ((result (cadr args)))
- (set! args (cddr args))
- result))
- ((pair? args)
- (set! args (cdr args))
- #f)
- (else #f)))
- ;; Interpret the short-option at index POSITION in (car ARGS),
- ;; followed by the remaining short options in (car ARGS).
- (define (short-option position)
- (if (>= position (string-length (car args)))
- (begin
- (set! args (cdr args))
- (next-arg))
- (let* ((opt-name (string-ref (car args) position))
- (option-here (hash-ref lookup opt-name)))
- (cond ((not option-here)
- (mutate-seeds! unrecognized-option-proc
- (option (list opt-name) #f #f
- unrecognized-option-proc)
- opt-name #f)
- (short-option (1+ position)))
- (else
- (invoke-option-processor
- option-here opt-name
- (lambda ()
- (or (short-option-argument position)
- (error "Missing required argument after `-~A'" opt-name)))
- (lambda ()
- ;; edge case: -xo -zf or -xo -- where opt-name=#\o
- ;; GNU getopt_long resolves these like I do
- (short-option-argument position))
- (lambda () #f))
- (if (not (or (option-required-arg? option-here)
- (option-optional-arg? option-here)))
- (short-option (1+ position))))))))
- ;; Process the long option in (car ARGS). We make the
- ;; interesting, possibly non-standard assumption that long option
- ;; names might contain #\=, so keep looking for more #\= in (car
- ;; ARGS) until we find a named option in lookup.
- (define (long-option)
- (let ((arg (car args)))
- (let place-=-after ((start-pos 2))
- (let* ((index (string-index arg #\= start-pos))
- (opt-name (substring arg 2 (or index (string-length arg))))
- (option-here (hash-ref lookup opt-name)))
- (if (not option-here)
- ;; look for a later #\=, unless there can't be one
- (if index
- (place-=-after (1+ index))
- (mutate-seeds!
- unrecognized-option-proc
- (option (list opt-name) #f #f unrecognized-option-proc)
- opt-name #f))
- (invoke-option-processor
- option-here opt-name
- (lambda ()
- (if index
- (substring arg (1+ index))
- (error "Missing required argument after `--~A'" opt-name)))
- (lambda () (and index (substring arg (1+ index))))
- (lambda ()
- (if index
- (error "Extraneous argument after `--~A'" opt-name))))))))
- (set! args (cdr args)))
- ;; Process the remaining in ARGS. Basically like calling
- ;; `args-fold', but without having to regenerate `lookup' and the
- ;; funcs above.
- (define (next-arg)
- (if (null? args)
- (apply values seeds)
- (let ((arg (car args)))
- (cond ((or (not (char=? #\- (string-ref arg 0)))
- (= 1 (string-length arg))) ;"-"
- (mutate-seeds! operand-proc arg)
- (set! args (cdr args)))
- ((char=? #\- (string-ref arg 1))
- (if (= 2 (string-length arg)) ;"--"
- (begin (set! args (cdr args)) (rest-operands))
- (long-option)))
- (else (short-option 1)))
- (next-arg))))
- (next-arg)))
- ;;; srfi-37.scm ends here
|