1 ;;; Copyright (C) 1998, 2001, 2006 Free Software Foundation, Inc.
2 ;;; Copyright (C) 2017,2018 Jan Nieuwenhuizen <janneke@gnu.org>
4 ;; This library is free software; you can redistribute it and/or
5 ;; modify it under the terms of the GNU Lesser General Public
6 ;; License as published by the Free Software Foundation; either
7 ;; version 2.1 of the License, or (at your option) any later version.
9 ;; This library is distributed in the hope that it will be useful,
10 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 ;; Lesser General Public License for more details.
14 ;; You should have received a copy of the GNU Lesser General Public
15 ;; License along with this library; if not, write to the Free Software
16 ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 ;;; Author: Russ McManus (rewritten by Thien-Thi Nguyen)
19 ;;; (regexps removed by Jan Nieuwenhuizen)
20 ;;; (srfi-9 backport by Jan Nieuwenhuizen)
24 ;;; This module implements some complex command line option parsing, in
25 ;;; the spirit of the GNU C library function `getopt_long'. Both long
26 ;;; and short options are supported.
28 ;;; The theory is that people should be able to constrain the set of
29 ;;; options they want to process using a grammar, rather than some arbitrary
30 ;;; structure. The grammar makes the option descriptions easy to read.
32 ;;; `getopt-long' is a procedure for parsing command-line arguments in a
33 ;;; manner consistent with other GNU programs. `option-ref' is a procedure
34 ;;; that facilitates processing of the `getopt-long' return value.
36 ;;; (getopt-long ARGS GRAMMAR)
37 ;;; Parse the arguments ARGS according to the argument list grammar GRAMMAR.
39 ;;; ARGS should be a list of strings. Its first element should be the
40 ;;; name of the program; subsequent elements should be the arguments
41 ;;; that were passed to the program on the command line. The
42 ;;; `program-arguments' procedure returns a list of this form.
44 ;;; GRAMMAR is a list of the form:
45 ;;; ((OPTION (PROPERTY VALUE) ...) ...)
47 ;;; Each OPTION should be a symbol. `getopt-long' will accept a
48 ;;; command-line option named `--OPTION'.
49 ;;; Each option can have the following (PROPERTY VALUE) pairs:
51 ;;; (single-char CHAR) --- Accept `-CHAR' as a single-character
52 ;;; equivalent to `--OPTION'. This is how to specify traditional
54 ;;; (required? BOOL) --- If BOOL is true, the option is required.
55 ;;; getopt-long will raise an error if it is not found in ARGS.
56 ;;; (value BOOL) --- If BOOL is #t, the option accepts a value; if
57 ;;; it is #f, it does not; and if it is the symbol
58 ;;; `optional', the option may appear in ARGS with or
60 ;;; (predicate FUNC) --- If the option accepts a value (i.e. you
61 ;;; specified `(value #t)' for this option), then getopt
62 ;;; will apply FUNC to the value, and throw an exception
63 ;;; if it returns #f. FUNC should be a procedure which
64 ;;; accepts a string and returns a boolean value; you may
65 ;;; need to use quasiquotes to get it into GRAMMAR.
67 ;;; The (PROPERTY VALUE) pairs may occur in any order, but each
68 ;;; property may occur only once. By default, options do not have
69 ;;; single-character equivalents, are not required, and do not take
72 ;;; In ARGS, single-character options may be combined, in the usual
73 ;;; Unix fashion: ("-x" "-y") is equivalent to ("-xy"). If an option
74 ;;; accepts values, then it must be the last option in the
75 ;;; combination; the value is the next argument. So, for example, using
76 ;;; the following grammar:
77 ;;; ((apples (single-char #\a))
78 ;;; (blimps (single-char #\b) (value #t))
79 ;;; (catalexis (single-char #\c) (value #t)))
80 ;;; the following argument lists would be acceptable:
81 ;;; ("-a" "-b" "bang" "-c" "couth") ("bang" and "couth" are the values
82 ;;; for "blimps" and "catalexis")
83 ;;; ("-ab" "bang" "-c" "couth") (same)
84 ;;; ("-ac" "couth" "-b" "bang") (same)
85 ;;; ("-abc" "couth" "bang") (an error, since `-b' is not the
86 ;;; last option in its combination)
88 ;;; If an option's value is optional, then `getopt-long' decides
89 ;;; whether it has a value by looking at what follows it in ARGS. If
90 ;;; the next element is does not appear to be an option itself, then
91 ;;; that element is the option's value.
93 ;;; The value of a long option can appear as the next element in ARGS,
94 ;;; or it can follow the option name, separated by an `=' character.
95 ;;; Thus, using the same grammar as above, the following argument lists
97 ;;; ("--apples" "Braeburn" "--blimps" "Goodyear")
98 ;;; ("--apples=Braeburn" "--blimps" "Goodyear")
99 ;;; ("--blimps" "Goodyear" "--apples=Braeburn")
101 ;;; If the option "--" appears in ARGS, argument parsing stops there;
102 ;;; subsequent arguments are returned as ordinary arguments, even if
103 ;;; they resemble options. So, in the argument list:
104 ;;; ("--apples" "Granny Smith" "--" "--blimp" "Goodyear")
105 ;;; `getopt-long' will recognize the `apples' option as having the
106 ;;; value "Granny Smith", but it will not recognize the `blimp'
107 ;;; option; it will return the strings "--blimp" and "Goodyear" as
108 ;;; ordinary argument strings.
110 ;;; The `getopt-long' function returns the parsed argument list as an
111 ;;; assocation list, mapping option names --- the symbols from GRAMMAR
112 ;;; --- onto their values, or #t if the option does not accept a value.
113 ;;; Unused options do not appear in the alist.
115 ;;; All arguments that are not the value of any option are returned
116 ;;; as a list, associated with the empty list.
118 ;;; `getopt-long' throws an exception if:
119 ;;; - it finds an unrecognized property in GRAMMAR
120 ;;; - the value of the `single-char' property is not a character
121 ;;; - it finds an unrecognized option in ARGS
122 ;;; - a required option is omitted
123 ;;; - an option that requires an argument doesn't get one
124 ;;; - an option that doesn't accept an argument does get one (this can
125 ;;; only happen using the long option `--opt=value' syntax)
126 ;;; - an option predicate fails
131 ;;; `((lockfile-dir (required? #t)
133 ;;; (single-char #\k)
134 ;;; (predicate ,file-is-directory?))
135 ;;; (verbose (required? #f)
136 ;;; (single-char #\v)
138 ;;; (x-includes (single-char #\x))
139 ;;; (rnet-server (single-char #\y)
140 ;;; (predicate ,string?))))
142 ;;; (getopt-long '("my-prog" "-vk" "/tmp" "foo1" "--x-includes=/usr/include"
143 ;;; "--rnet-server=lamprod" "--" "-fred" "foo2" "foo3")
145 ;;; => ((() "foo1" "-fred" "foo2" "foo3")
146 ;;; (rnet-server . "lamprod")
147 ;;; (x-includes . "/usr/include")
148 ;;; (lockfile-dir . "/tmp")
151 ;;; (option-ref OPTIONS KEY DEFAULT)
152 ;;; Return value in alist OPTIONS using KEY, a symbol; or DEFAULT if not
153 ;;; found. The value is either a string or `#t'.
155 ;;; For example, using the `getopt-long' return value from above:
157 ;;; (option-ref (getopt-long ...) 'x-includes 42) => "/usr/include"
158 ;;; (option-ref (getopt-long ...) 'not-a-key! 31) => 31
162 (define-module (mes getopt-long)
163 #:use-module (srfi srfi-1)
164 #:use-module (srfi srfi-9)
165 #:export (getopt-long option-ref))
167 (define-record-type option-spec
168 (%make-option-spec name value required? option-spec->single-char predicate value-policy)
171 option-spec->name set-option-spec-name!)
173 option-spec->value set-option-spec-value!)
175 option-spec->required? set-option-spec-required?!)
176 (option-spec->single-char
177 option-spec->single-char set-option-spec-single-char!)
179 option-spec->predicate set-option-spec-predicate!)
181 option-spec->value-policy set-option-spec-value-policy!))
183 (define (make-option-spec name)
184 (%make-option-spec name #f #f #f #f #f))
186 (define (parse-option-spec desc)
187 (let ((spec (make-option-spec (symbol->string (car desc)))))
188 (for-each (lambda (desc-elem)
189 (let ((given (lambda () (cadr desc-elem))))
190 (case (car desc-elem)
192 (set-option-spec-required?! spec (given)))
194 (set-option-spec-value-policy! spec (given)))
197 (error "`single-char' value must be a char!"))
198 (set-option-spec-single-char! spec (given)))
200 (set-option-spec-predicate!
205 (error "option predicate failed:" name))))
208 (error "invalid getopt-long option property:"
213 (define (split-arg-list argument-list)
214 ;; Scan ARGUMENT-LIST for "--" and return (BEFORE-LS . AFTER-LS).
215 ;; Discard the "--". If no "--" is found, AFTER-LS is empty.
216 (let loop ((yes '()) (no argument-list))
217 (cond ((null? no) (cons (reverse yes) no))
218 ((string=? "--" (car no)) (cons (reverse yes) (cdr no)))
219 (else (loop (cons (car no) yes) (cdr no))))))
221 (define (expand-clumped-singles opt-ls)
222 ;; example: ("--xyz" "-abc5d") => ("--xyz" "-a" "-b" "-c" "5d")
223 (let loop ((opt-ls opt-ls) (ret-ls '()))
224 (cond ((null? opt-ls)
225 (reverse ret-ls)) ;;; retval
226 ((let ((opt (car opt-ls)))
227 (and (eq? (string-ref opt 0) #\-)
228 (> (string-length opt) 1)
229 (char-alphabetic? (string-ref opt 1))))
230 (let* ((opt (car opt-ls))
231 (n (char->integer (string-ref opt 1)))
232 (sub (substring opt 1 (string-length opt)))
233 (end (string-index (substring opt 1 (string-length opt)) (negate char-alphabetic?)))
234 (end (if end (1+ end) (string-length opt)))
235 (singles-string (substring opt 1 end))
238 (string-append "-" (make-string 1 c)))
239 (string->list singles-string))))
240 (extra (substring opt end)))
242 (append (if (string=? "" extra)
244 (cons extra singles))
246 (else (loop (cdr opt-ls)
247 (cons (car opt-ls) ret-ls))))))
249 (define (looks-like-an-option string)
250 (eq? (string-ref string 0) #\-))
252 (define (process-options specs argument-ls)
253 ;; Use SPECS to scan ARGUMENT-LS; return (FOUND . ETC).
254 ;; FOUND is an unordered list of option specs for found options, while ETC
255 ;; is an order-maintained list of elements in ARGUMENT-LS that are neither
256 ;; options nor their values.
257 (let ((idx (map (lambda (spec)
258 (cons (option-spec->name spec) spec))
260 (sc-idx (map (lambda (spec)
261 (cons (make-string 1 (option-spec->single-char spec))
263 (filter option-spec->single-char specs))))
264 (let loop ((argument-ls argument-ls) (found '()) (etc '()))
265 (let ((eat! (lambda (spec ls)
266 (let ((val!loop (lambda (val n-ls n-found n-etc)
267 (set-option-spec-value!
269 ;; handle multiple occurrances
270 (cond ((option-spec->value spec)
272 ((if (list? cur) cons list)
275 (loop n-ls n-found n-etc)))
276 (ERR:no-arg (lambda ()
277 (error (string-append
278 "option must be specified"
280 (option-spec->name spec)))))
282 ((eq? 'optional (option-spec->value-policy spec))
283 (if (or (null? (cdr ls))
284 (looks-like-an-option (cadr ls)))
293 ((eq? #t (option-spec->value-policy spec))
294 (if (or (null? (cdr ls))
295 (looks-like-an-option (cadr ls)))
307 (if (null? argument-ls)
308 (cons found (reverse etc)) ;;; retval
309 (cond ((let ((opt (car argument-ls)))
310 (and (eq? (string-ref opt 0) #\-)
311 (let ((n (char->integer (string-ref opt 1))))
312 (or (and (>= n (char->integer #\A)) (<= n (char->integer #\Z)))
313 (and (>= n (char->integer #\a)) (<= n (char->integer #\z)))))))
314 (let* ((c (substring (car argument-ls) 1 2))
315 (spec (or (assoc-ref sc-idx c)
316 (error "no such option:" (car argument-ls)))))
317 (eat! spec argument-ls)))
318 ((let ((opt (car argument-ls)))
319 (and (string-prefix? "--" opt)
320 (let ((n (char->integer (string-ref opt 2))))
321 (or (and (>= n (char->integer #\A)) (<= n (char->integer #\Z)))
322 (and (>= n (char->integer #\a)) (<= n (char->integer #\z)))))
323 (not (string-index opt #\space))
324 (not (string-index opt #\=))))
325 (let* ((opt (substring (car argument-ls) 2))
326 (spec (or (assoc-ref idx opt)
327 (error "no such option:" (car argument-ls)))))
328 (eat! spec argument-ls)))
329 ((let ((opt (car argument-ls)))
330 (and (string-prefix? "--" opt)
331 (let ((n (char->integer (string-ref opt 2))))
332 (or (and (>= n (char->integer #\A)) (<= n (char->integer #\Z)))
333 (and (>= n (char->integer #\a)) (<= n (char->integer #\z)))))
334 (or (string-index opt #\=)
335 (string-index opt #\space))))
336 (let* ((is (or (string-index (car argument-ls) #\=)
337 (string-index (car argument-ls) #\space)))
338 (opt (substring (car argument-ls) 2 is))
339 (spec (or (assoc-ref idx opt)
340 (error "no such option:" (substring opt is)))))
341 (if (option-spec->value-policy spec)
344 (substring (car argument-ls) (1+ is)))
346 (error "option does not support argument:"
349 (loop (cdr argument-ls)
351 (cons (car argument-ls) etc)))))))))
353 (define (getopt-long program-arguments option-desc-list)
354 "Process options, handling both long and short options, similar to
355 the glibc function 'getopt_long'. PROGRAM-ARGUMENTS should be a value
356 similar to what (program-arguments) returns. OPTION-DESC-LIST is a
357 list of option descriptions. Each option description must satisfy the
360 <option-spec> :: (<name> . <attribute-ls>)
361 <attribute-ls> :: (<attribute> . <attribute-ls>)
363 <attribute> :: <required-attribute>
364 | <arg-required-attribute>
365 | <single-char-attribute>
366 | <predicate-attribute>
368 <required-attribute> :: (required? <boolean>)
369 <single-char-attribute> :: (single-char <char>)
370 <value-attribute> :: (value #t)
373 <predicate-attribute> :: (predicate <1-ary-function>)
375 The procedure returns an alist of option names and values. Each
376 option name is a symbol. The option value will be '#t' if no value
377 was specified. There is a special item in the returned alist with a
378 key of the empty list, (): the list of arguments that are not options
380 By default, options are not required, and option values are not
381 required. By default, single character equivalents are not supported;
382 if you want to allow the user to use single character options, you need
383 to add a `single-char' clause to the option description."
384 (let* ((specifications (map parse-option-spec option-desc-list))
385 (pair (split-arg-list (cdr program-arguments)))
386 (split-ls (expand-clumped-singles (car pair)))
387 (non-split-ls (cdr pair))
388 (found/etc (process-options specifications split-ls))
389 (found (car found/etc))
390 (rest-ls (append (cdr found/etc) non-split-ls)))
391 (for-each (lambda (spec)
392 (let ((name (option-spec->name spec))
393 (val (option-spec->value spec)))
394 (and (option-spec->required? spec)
395 (or (memq spec found)
396 (error "option must be specified:" name)))
397 (and (memq spec found)
398 (eq? #t (option-spec->value-policy spec))
400 (error "option must be specified with argument:"
402 (let ((pred (option-spec->predicate spec)))
403 (and pred (pred name val)))))
405 (cons (cons '() rest-ls)
406 (let ((multi-count (map (lambda (desc)
410 (let ((name (string->symbol (option-spec->name spec))))
412 ;; handle multiple occurrances
413 (let ((maybe-ls (option-spec->value spec)))
415 (let* ((look (assq name multi-count))
417 (val (list-ref maybe-ls idx)))
418 (set-cdr! look (1+ idx)) ; ugh!
423 (define (option-ref options key default)
424 "Return value in alist OPTIONS using KEY, a symbol; or DEFAULT if not found.
425 The value is either a string or `#t'."
426 (or (assq-ref options key) default))
428 ;;; getopt-long.scm ends here