Revision: lcourtes@laas.fr--2006-libre/guile-core--cvs-head--0--patch-81
[bpt/guile.git] / srfi / srfi-37.scm
CommitLineData
d4c38221
LC
1;;; srfi-37.scm --- args-fold
2
3;; Copyright (C) 2007 Free Software Foundation, Inc.
4;;
5;; This library is free software; you can redistribute it and/or
6;; modify it under the terms of the GNU Lesser General Public
7;; License as published by the Free Software Foundation; either
8;; version 2.1 of the License, or (at your option) any later version.
9;;
10;; This library is distributed in the hope that it will be useful,
11;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13;; Lesser General Public License for more details.
14;;
15;; You should have received a copy of the GNU Lesser General Public
16;; License along with this library; if not, write to the Free Software
17;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18\f
19#! Commentary
20
21To use this module with Guile, use (cdr (program-arguments)) as
22the ARGS argument to `args-fold'. Here is a short example:
23
24 (args-fold (cdr (program-arguments))
25 (let ((display-and-exit-proc
26 (lambda (msg)
27 (lambda (opt name arg)
28 (display msg) (quit) (values)))))
29 (list (option '(#\v "version") #f #f
30 (display-and-exit-proc "Foo version 42.0\n"))
31 (option '(#\h "help") #f #f
32 (display-and-exit-proc
33 "Usage: foo scheme-file ..."))))
34 (lambda (opt name arg)
35 (error "Unrecognized option `~A'" name))
36 (lambda (op) (load op) (values)))
37!#
38\f
39;;;; Module definition & exports
40(define-module (srfi srfi-37)
41 #:use-module (srfi srfi-9)
42 #:export (option option-names option-required-arg?
43 option-optional-arg? option-processor
44 args-fold))
45
46(cond-expand-provide (current-module) '(srfi-37))
47\f
48;;;; args-fold and periphery procedures
49
50;;; An option as answered by `option'. `names' is a list of
51;;; characters and strings, representing associated short-options and
52;;; long-options respectively that should use this option's
53;;; `processor' in an `args-fold' call.
54;;;
55;;; `required-arg?' and `optional-arg?' are mutually exclusive
56;;; booleans and indicate whether an argument must be or may be
57;;; provided. Besides the obvious, this affects semantics of
58;;; short-options, as short-options with a required or optional
59;;; argument cannot be followed by other short options in the same
60;;; program-arguments string, as they will be interpreted collectively
61;;; as the option's argument.
62;;;
63;;; `processor' is called when this option is encountered. It should
64;;; accept the containing option, the element of `names' (by `equal?')
65;;; encountered, the option's argument (or #f if none), and the seeds
66;;; as variadic arguments, answering the new seeds as values.
67(define-record-type srfi-37:option
68 (option names required-arg? optional-arg? processor)
69 option?
70 (names option-names)
71 (required-arg? option-required-arg?)
72 (optional-arg? option-optional-arg?)
73 (processor option-processor))
74
75(define (error-duplicate-option option-name)
76 (scm-error 'program-error "args-fold"
77 "Duplicate option name `~A~A'"
78 (list (if (char? option-name) #\- "--")
79 option-name)
80 #f))
81
82(define (build-options-lookup options)
83 "Answer an `equal?' Guile hash-table that maps OPTIONS' names back
84to the containing options, signalling an error if a name is
85encountered more than once."
86 (let ((lookup (make-hash-table (* 2 (length options)))))
87 (for-each
88 (lambda (opt)
89 (for-each (lambda (name)
90 (let ((assoc (hash-create-handle!
91 lookup name #f)))
92 (if (cdr assoc)
93 (error-duplicate-option (car assoc))
94 (set-cdr! assoc opt))))
95 (option-names opt)))
96 options)
97 lookup))
98
99(define (args-fold args options unrecognized-option-proc
100 operand-proc . seeds)
101 "Answer the results of folding SEEDS as multiple values against the
102program-arguments in ARGS, as decided by the OPTIONS'
103`option-processor's, UNRECOGNIZED-OPTION-PROC, and OPERAND-PROC."
104 (let ((lookup (build-options-lookup options)))
105 ;; I don't like Guile's `error' here
106 (define (error msg . args)
107 (scm-error 'misc-error "args-fold" msg args #f))
108
109 (define (mutate-seeds! procedure . params)
110 (set! seeds (call-with-values
111 (lambda ()
112 (apply procedure (append params seeds)))
113 list)))
114
115 ;; Clean up the rest of ARGS, assuming they're all operands.
116 (define (rest-operands)
117 (for-each (lambda (arg) (mutate-seeds! operand-proc arg))
118 args)
119 (set! args '()))
120
121 ;; Call OPT's processor with OPT, NAME, an argument to be decided,
122 ;; and the seeds. Depending on OPT's *-arg? specification, get
123 ;; the parameter by calling REQ-ARG-PROC or OPT-ARG-PROC thunks;
124 ;; if no argument is allowed, call NO-ARG-PROC thunk.
125 (define (invoke-option-processor
126 opt name req-arg-proc opt-arg-proc no-arg-proc)
127 (mutate-seeds!
128 (option-processor opt) opt name
129 (cond ((option-required-arg? opt) (req-arg-proc))
130 ((option-optional-arg? opt) (opt-arg-proc))
131 (else (no-arg-proc) #f))))
132
133 ;; Compute and answer a short option argument, advancing ARGS as
134 ;; necessary, for the short option whose character is at POSITION
135 ;; in the current ARG.
136 (define (short-option-argument position)
137 (cond ((< (1+ position) (string-length (car args)))
138 (let ((result (substring (car args) (1+ position))))
139 (set! args (cdr args))
140 result))
141 ((pair? (cdr args))
142 (let ((result (cadr args)))
143 (set! args (cddr args))
144 result))
145 (else #f)))
146
147 ;; Interpret the short-option at index POSITION in (car ARGS),
148 ;; followed by the remaining short options in (car ARGS).
149 (define (short-option position)
150 (if (>= position (string-length (car args)))
151 (next-arg)
152 (let* ((opt-name (string-ref (car args) position))
153 (option-here (hash-ref lookup opt-name)))
154 (cond ((not option-here)
155 (mutate-seeds! unrecognized-option-proc
156 (option (list opt-name) #f #f
157 unrecognized-option-proc)
158 opt-name #f)
159 (short-option (1+ position)))
160 (else
161 (invoke-option-processor
162 option-here opt-name
163 (lambda ()
164 (or (short-option-argument position)
165 (error "Missing required argument after `-~A'" opt-name)))
166 (lambda ()
167 ;; edge case: -xo -zf or -xo -- where opt-name=#\o
168 ;; GNU getopt_long resolves these like I do
169 (short-option-argument position))
170 (lambda () #f))
171 (if (not (or (option-required-arg? option-here)
172 (option-optional-arg? option-here)))
173 (short-option (1+ position))))))))
174
175 ;; Process the long option in (car ARGS). We make the
176 ;; interesting, possibly non-standard assumption that long option
177 ;; names might contain #\=, so keep looking for more #\= in (car
178 ;; ARGS) until we find a named option in lookup.
179 (define (long-option)
180 (let ((arg (car args)))
181 (let place-=-after ((start-pos 2))
182 (let* ((index (string-index arg #\= start-pos))
183 (opt-name (substring arg 2 (or index (string-length arg))))
184 (option-here (hash-ref lookup opt-name)))
185 (if (not option-here)
186 ;; look for a later #\=, unless there can't be one
187 (if index
188 (place-=-after (1+ index))
189 (mutate-seeds!
190 unrecognized-option-proc
191 (option (list opt-name) #f #f unrecognized-option-proc)
192 opt-name #f))
193 (invoke-option-processor
194 option-here opt-name
195 (lambda ()
196 (if index
197 (substring arg (1+ index))
198 (error "Missing required argument after `--~A'" opt-name)))
199 (lambda () (and index (substring arg (1+ index))))
200 (lambda ()
201 (if index
202 (error "Extraneous argument after `--~A'" opt-name))))))))
203 (set! args (cdr args)))
204
205 ;; Process the remaining in ARGS. Basically like calling
206 ;; `args-fold', but without having to regenerate `lookup' and the
207 ;; funcs above.
208 (define (next-arg)
209 (if (null? args)
210 (apply values seeds)
211 (let ((arg (car args)))
212 (cond ((or (not (char=? #\- (string-ref arg 0)))
213 (= 1 (string-length arg))) ;"-"
214 (mutate-seeds! operand-proc arg)
215 (set! args (cdr args)))
216 ((char=? #\- (string-ref arg 1))
217 (if (= 2 (string-length arg)) ;"--"
218 (begin (set! args (cdr args)) (rest-operands))
219 (long-option)))
220 (else (short-option 1)))
221 (next-arg))))
222
223 (next-arg)))
224
225;;; srfi-37.scm ends here