1 ;;; Copyright (C) 1998, 2001 Free Software Foundation, Inc.
3 ;;; This program is free software; you can redistribute it and/or modify
4 ;;; it under the terms of the GNU General Public License as published by
5 ;;; the Free Software Foundation; either version 2 of the License, or
6 ;;; (at your option) any later version.
8 ;;; This program is distributed in the hope that it will be useful,
9 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
10 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 ;;; GNU General Public License for more details.
13 ;;; You should have received a copy of the GNU General Public License
14 ;;; along with this program; if not, write to the Free Software
15 ;;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 ;;; As a special exception, the Free Software Foundation gives permission
18 ;;; for additional uses of the text contained in its release of GUILE.
20 ;;; The exception is that, if you link the GUILE library with other files
21 ;;; to produce an executable, this does not by itself cause the
22 ;;; resulting executable to be covered by the GNU General Public License.
23 ;;; Your use of that executable is in no way restricted on account of
24 ;;; linking the GUILE library code into it.
26 ;;; This exception does not however invalidate any other reasons why
27 ;;; the executable file might be covered by the GNU General Public License.
29 ;;; This exception applies only to the code released by the
30 ;;; Free Software Foundation under the name GUILE. If you copy
31 ;;; code from other Free Software Foundation releases into a copy of
32 ;;; GUILE, as the General Public License permits, the exception does
33 ;;; not apply to the code that you add in this way. To avoid misleading
34 ;;; anyone as to the status of such modified files, you must delete
35 ;;; this exception notice from them.
37 ;;; If you write modifications of your own for GUILE, it is your choice
38 ;;; whether to permit this exception to apply to your modifications.
39 ;;; If you do not wish that, delete this exception notice.
41 ;;; Author: Russ McManus (rewritten by Thien-Thi Nguyen)
45 ;;; This module implements some complex command line option parsing, in
46 ;;; the spirit of the GNU C library function `getopt_long'. Both long
47 ;;; and short options are supported.
49 ;;; The theory is that people should be able to constrain the set of
50 ;;; options they want to process using a grammar, rather than some arbitrary
51 ;;; structure. The grammar makes the option descriptions easy to read.
53 ;;; `getopt-long' is a procedure for parsing command-line arguments in a
54 ;;; manner consistent with other GNU programs. `option-ref' is a procedure
55 ;;; that facilitates processing of the `getopt-long' return value.
57 ;;; (getopt-long ARGS GRAMMAR)
58 ;;; Parse the arguments ARGS according to the argument list grammar GRAMMAR.
60 ;;; ARGS should be a list of strings. Its first element should be the
61 ;;; name of the program; subsequent elements should be the arguments
62 ;;; that were passed to the program on the command line. The
63 ;;; `program-arguments' procedure returns a list of this form.
65 ;;; GRAMMAR is a list of the form:
66 ;;; ((OPTION (PROPERTY VALUE) ...) ...)
68 ;;; Each OPTION should be a symbol. `getopt-long' will accept a
69 ;;; command-line option named `--OPTION'.
70 ;;; Each option can have the following (PROPERTY VALUE) pairs:
72 ;;; (single-char CHAR) --- Accept `-CHAR' as a single-character
73 ;;; equivalent to `--OPTION'. This is how to specify traditional
75 ;;; (required? BOOL) --- If BOOL is true, the option is required.
76 ;;; getopt-long will raise an error if it is not found in ARGS.
77 ;;; (value BOOL) --- If BOOL is #t, the option accepts a value; if
78 ;;; it is #f, it does not; and if it is the symbol
79 ;;; `optional', the option may appear in ARGS with or
81 ;;; (predicate FUNC) --- If the option accepts a value (i.e. you
82 ;;; specified `(value #t)' for this option), then getopt
83 ;;; will apply FUNC to the value, and throw an exception
84 ;;; if it returns #f. FUNC should be a procedure which
85 ;;; accepts a string and returns a boolean value; you may
86 ;;; need to use quasiquotes to get it into GRAMMAR.
88 ;;; The (PROPERTY VALUE) pairs may occur in any order, but each
89 ;;; property may occur only once. By default, options do not have
90 ;;; single-character equivalents, are not required, and do not take
93 ;;; In ARGS, single-character options may be combined, in the usual
94 ;;; Unix fashion: ("-x" "-y") is equivalent to ("-xy"). If an option
95 ;;; accepts values, then it must be the last option in the
96 ;;; combination; the value is the next argument. So, for example, using
97 ;;; the following grammar:
98 ;;; ((apples (single-char #\a))
99 ;;; (blimps (single-char #\b) (value #t))
100 ;;; (catalexis (single-char #\c) (value #t)))
101 ;;; the following argument lists would be acceptable:
102 ;;; ("-a" "-b" "bang" "-c" "couth") ("bang" and "couth" are the values
103 ;;; for "blimps" and "catalexis")
104 ;;; ("-ab" "bang" "-c" "couth") (same)
105 ;;; ("-ac" "couth" "-b" "bang") (same)
106 ;;; ("-abc" "couth" "bang") (an error, since `-b' is not the
107 ;;; last option in its combination)
109 ;;; If an option's value is optional, then `getopt-long' decides
110 ;;; whether it has a value by looking at what follows it in ARGS. If
111 ;;; the next element is does not appear to be an option itself, then
112 ;;; that element is the option's value.
114 ;;; The value of a long option can appear as the next element in ARGS,
115 ;;; or it can follow the option name, separated by an `=' character.
116 ;;; Thus, using the same grammar as above, the following argument lists
118 ;;; ("--apples" "Braeburn" "--blimps" "Goodyear")
119 ;;; ("--apples=Braeburn" "--blimps" "Goodyear")
120 ;;; ("--blimps" "Goodyear" "--apples=Braeburn")
122 ;;; If the option "--" appears in ARGS, argument parsing stops there;
123 ;;; subsequent arguments are returned as ordinary arguments, even if
124 ;;; they resemble options. So, in the argument list:
125 ;;; ("--apples" "Granny Smith" "--" "--blimp" "Goodyear")
126 ;;; `getopt-long' will recognize the `apples' option as having the
127 ;;; value "Granny Smith", but it will not recognize the `blimp'
128 ;;; option; it will return the strings "--blimp" and "Goodyear" as
129 ;;; ordinary argument strings.
131 ;;; The `getopt-long' function returns the parsed argument list as an
132 ;;; assocation list, mapping option names --- the symbols from GRAMMAR
133 ;;; --- onto their values, or #t if the option does not accept a value.
134 ;;; Unused options do not appear in the alist.
136 ;;; All arguments that are not the value of any option are returned
137 ;;; as a list, associated with the empty list.
139 ;;; `getopt-long' throws an exception if:
140 ;;; - it finds an unrecognized property in GRAMMAR
141 ;;; - the value of the `single-char' property is not a character
142 ;;; - it finds an unrecognized option in ARGS
143 ;;; - a required option is omitted
144 ;;; - an option that requires an argument doesn't get one
145 ;;; - an option that doesn't accept an argument does get one (this can
146 ;;; only happen using the long option `--opt=value' syntax)
147 ;;; - an option predicate fails
152 ;;; `((lockfile-dir (required? #t)
154 ;;; (single-char #\k)
155 ;;; (predicate ,file-is-directory?))
156 ;;; (verbose (required? #f)
157 ;;; (single-char #\v)
159 ;;; (x-includes (single-char #\x))
160 ;;; (rnet-server (single-char #\y)
161 ;;; (predicate ,string?))))
163 ;;; (getopt-long '("my-prog" "-vk" "/tmp" "foo1" "--x-includes=/usr/include"
164 ;;; "--rnet-server=lamprod" "--" "-fred" "foo2" "foo3")
166 ;;; => ((() "foo1" "-fred" "foo2" "foo3")
167 ;;; (rnet-server . "lamprod")
168 ;;; (x-includes . "/usr/include")
169 ;;; (lockfile-dir . "/tmp")
172 ;;; (option-ref OPTIONS KEY DEFAULT)
173 ;;; Return value in alist OPTIONS using KEY, a symbol; or DEFAULT if not
174 ;;; found. The value is either a string or `#t'.
176 ;;; For example, using the `getopt-long' return value from above:
178 ;;; (option-ref (getopt-long ...) 'x-includes 42) => "/usr/include"
179 ;;; (option-ref (getopt-long ...) 'not-a-key! 31) => 31
183 (define-module (ice-9 getopt-long)
184 :use-module ((ice-9 common-list) :select (some remove-if-not))
185 :export (getopt-long option-ref))
187 (define option-spec-fields '(name
194 (define option-spec (make-record-type 'option-spec option-spec-fields))
195 (define make-option-spec (record-constructor option-spec option-spec-fields))
197 (define (define-one-option-spec-field-accessor field)
198 `(define ,(symbol-append 'option-spec-> field) ;;; name slib-compat
199 (record-accessor option-spec ',field)))
201 (define (define-one-option-spec-field-modifier field)
202 `(define ,(symbol-append 'set-option-spec- field '!) ;;; name slib-compat
203 (record-modifier option-spec ',field)))
205 (defmacro define-all-option-spec-accessors/modifiers ()
207 ,@(map define-one-option-spec-field-accessor option-spec-fields)
208 ,@(map define-one-option-spec-field-modifier option-spec-fields)))
210 (define-all-option-spec-accessors/modifiers)
212 (define make-option-spec
213 (let ((ctor (record-constructor option-spec '(name))))
217 (define (parse-option-spec desc)
218 (let ((spec (make-option-spec (symbol->string (car desc)))))
219 (for-each (lambda (desc-elem)
220 (let ((given (lambda () (cadr desc-elem))))
221 (case (car desc-elem)
223 (set-option-spec-required?! spec (given)))
225 (set-option-spec-value-policy! spec (given)))
228 (error "`single-char' value must be a char!"))
229 (set-option-spec-single-char! spec (given)))
231 (set-option-spec-predicate!
236 (error "option predicate failed:" name))))
239 (error "invalid getopt-long option property:"
244 (define (split-arg-list argument-list)
245 ;; Scan ARGUMENT-LIST for "--" and return (BEFORE-LS . AFTER-LS).
246 ;; Discard the "--". If no "--" is found, AFTER-LS is empty.
247 (let loop ((yes '()) (no argument-list))
248 (cond ((null? no) (cons (reverse yes) no))
249 ((string=? "--" (car no)) (cons (reverse yes) (cdr no)))
250 (else (loop (cons (car no) yes) (cdr no))))))
252 (define short-opt-rx (make-regexp "^-([a-zA-Z]+)(.*)"))
253 (define long-opt-no-value-rx (make-regexp "^--([^=]+)$"))
254 (define long-opt-with-value-rx (make-regexp "^--([^=]+)=(.*)"))
256 (define (match-substring match which)
257 ;; condensed from (ice-9 regex) `match:{substring,start,end}'
258 (let ((sel (vector-ref match (1+ which))))
259 (substring (vector-ref match 0) (car sel) (cdr sel))))
261 (define (expand-clumped-singles opt-ls)
262 ;; example: ("--xyz" "-abc5d") => ("--xyz" "-a" "-b" "-c" "5d")
263 (let loop ((opt-ls opt-ls) (ret-ls '()))
264 (cond ((null? opt-ls)
265 (reverse ret-ls)) ;;; retval
266 ((regexp-exec short-opt-rx (car opt-ls))
268 (let ((singles (reverse
270 (string-append "-" (make-string 1 c)))
272 (match-substring match 1)))))
273 (extra (match-substring match 2)))
275 (append (if (string=? "" extra)
277 (cons extra singles))
279 (else (loop (cdr opt-ls)
280 (cons (car opt-ls) ret-ls))))))
282 (define (looks-like-an-option string)
284 (regexp-exec rx string))
286 ,long-opt-with-value-rx
287 ,long-opt-no-value-rx)))
289 (define (process-options specs argument-ls)
290 ;; Use SPECS to scan ARGUMENT-LS; return (FOUND . ETC).
291 ;; FOUND is an unordered list of option specs for found options, while ETC
292 ;; is an order-maintained list of elements in ARGUMENT-LS that are neither
293 ;; options nor their values.
294 (let ((idx (map (lambda (spec)
295 (cons (option-spec->name spec) spec))
297 (sc-idx (map (lambda (spec)
298 (cons (make-string 1 (option-spec->single-char spec))
300 (remove-if-not option-spec->single-char specs))))
301 (let loop ((argument-ls argument-ls) (found '()) (etc '()))
302 (let ((eat! (lambda (spec ls)
303 (let ((val!loop (lambda (val n-ls n-found n-etc)
304 (set-option-spec-value!
306 ;; handle multiple occurrances
307 (cond ((option-spec->value spec)
309 ((if (list? cur) cons list)
312 (loop n-ls n-found n-etc)))
313 (ERR:no-arg (lambda ()
314 (error (string-append
315 "option must be specified"
317 (option-spec->name spec)))))
319 ((eq? 'optional (option-spec->value-policy spec))
320 (if (or (null? (cdr ls))
321 (looks-like-an-option (cadr ls)))
330 ((eq? #t (option-spec->value-policy spec))
331 (if (or (null? (cdr ls))
332 (looks-like-an-option (cadr ls)))
343 (if (null? argument-ls)
344 (cons found (reverse etc)) ;;; retval
345 (cond ((regexp-exec short-opt-rx (car argument-ls))
347 (let* ((c (match-substring match 1))
348 (spec (or (assoc-ref sc-idx c)
349 (error "no such option:" c))))
350 (eat! spec argument-ls))))
351 ((regexp-exec long-opt-no-value-rx (car argument-ls))
353 (let* ((opt (match-substring match 1))
354 (spec (or (assoc-ref idx opt)
355 (error "no such option:" opt))))
356 (eat! spec argument-ls))))
357 ((regexp-exec long-opt-with-value-rx (car argument-ls))
359 (let* ((opt (match-substring match 1))
360 (spec (or (assoc-ref idx opt)
361 (error "no such option:" opt))))
362 (if (option-spec->value-policy spec)
365 (match-substring match 2))
367 (error "option does not support argument:"
370 (loop (cdr argument-ls)
372 (cons (car argument-ls) etc)))))))))
374 (define (getopt-long program-arguments option-desc-list)
375 "Process options, handling both long and short options, similar to
376 the glibc function 'getopt_long'. PROGRAM-ARGUMENTS should be a value
377 similar to what (program-arguments) returns. OPTION-DESC-LIST is a
378 list of option descriptions. Each option description must satisfy the
381 <option-spec> :: (<name> . <attribute-ls>)
382 <attribute-ls> :: (<attribute> . <attribute-ls>)
384 <attribute> :: <required-attribute>
385 | <arg-required-attribute>
386 | <single-char-attribute>
387 | <predicate-attribute>
389 <required-attribute> :: (required? <boolean>)
390 <single-char-attribute> :: (single-char <char>)
391 <value-attribute> :: (value #t)
394 <predicate-attribute> :: (predicate <1-ary-function>)
396 The procedure returns an alist of option names and values. Each
397 option name is a symbol. The option value will be '#t' if no value
398 was specified. There is a special item in the returned alist with a
399 key of the empty list, (): the list of arguments that are not options
401 By default, options are not required, and option values are not
402 required. By default, single character equivalents are not supported;
403 if you want to allow the user to use single character options, you need
404 to add a `single-char' clause to the option description."
405 (let* ((specifications (map parse-option-spec option-desc-list))
406 (pair (split-arg-list (cdr program-arguments)))
407 (split-ls (expand-clumped-singles (car pair)))
408 (non-split-ls (cdr pair))
409 (found/etc (process-options specifications split-ls))
410 (found (car found/etc))
411 (rest-ls (append (cdr found/etc) non-split-ls)))
412 (for-each (lambda (spec)
413 (let ((name (option-spec->name spec))
414 (val (option-spec->value spec)))
415 (and (option-spec->required? spec)
416 (or (memq spec found)
417 (error "option must be specified:" name)))
418 (and (memq spec found)
419 (eq? #t (option-spec->value-policy spec))
421 (error "option must be specified with argument:"
423 (let ((pred (option-spec->predicate spec)))
424 (and pred (pred name val)))))
426 (cons (cons '() rest-ls)
427 (let ((multi-count (map (lambda (desc)
431 (let ((name (string->symbol (option-spec->name spec))))
433 ;; handle multiple occurrances
434 (let ((maybe-ls (option-spec->value spec)))
436 (let* ((look (assq name multi-count))
438 (val (list-ref maybe-ls idx)))
439 (set-cdr! look (1+ idx)) ; ugh!
444 (define (option-ref options key default)
445 "Return value in alist OPTIONS using KEY, a symbol; or DEFAULT if not found.
446 The value is either a string or `#t'."
447 (or (assq-ref options key) default))
449 ;;; getopt-long.scm ends here