1 ;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Package: getopt -*-
2 ;;;; *************************************************************************
3 ;;;; FILE IDENTIFICATION
6 ;;;; Purpose: Command line option processing like GNU's getopt_long
7 ;;;; Programmer: Kevin M. Rosenberg
8 ;;;; Date Started: Sep 2003
10 ;;;; $Id: getopt.lisp,v 1.1 2009-07-13 15:25:24 rtoy Exp $
12 ;;;; *************************************************************************
14 ;;;; This file has been modified from the original to support the
15 ;;;; needs of maxima. Basically, we changed getopt so that:
17 ;;;; - "-ab" is recognized as two separate options: "-a" "-b"
19 ;;;; - Exact matches are treated as matches, even if the match is an
20 ;;;; ambiguous prefix. Hence, "--batch" will match the option
21 ;;;; "--batch", even though it is an ambiguous prefix for
22 ;;;; "--batch-lisp" and "--batch--string". But "--bat" is still an
23 ;;;; error since it is ambiguous and is not an exact match for any
26 ;;;; To comply with the license, we include the license here:
28 ;;;; *************************************************************************
29 ;;;; Copyright (C) 2003 by Kevin M. Rosenberg.
31 ;;;; All rights reserved.
33 ;;;; Redistribution and use in source and binary forms, with or without
34 ;;;; modification, are permitted provided that the following conditions
36 ;;;; 1. Redistributions of source code must retain the above copyright
37 ;;;; notice, this list of conditions and the following disclaimer.
38 ;;;; 2. Redistributions in binary form must reproduce the above copyright
39 ;;;; notice, this list of conditions and the following disclaimer in the
40 ;;;; documentation and/or other materials provided with the distribution.
41 ;;;; 3. Neither the name of the author nor the names of the contributors
42 ;;;; may be used to endorse or promote products derived from this software
43 ;;;; without specific prior written permission.
45 ;;;; THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
46 ;;;; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
47 ;;;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
48 ;;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
49 ;;;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
50 ;;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
51 ;;;; OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
52 ;;;; HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
53 ;;;; LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
54 ;;;; OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
56 ;;;; *************************************************************************
62 (defun is-short-option (arg)
63 (and (>= (length arg
) 2)
64 (char= #\-
(schar arg
0))
65 (char/= #\-
(schar arg
1))))
67 (defun is-option-terminator (arg)
68 (and (= 2 (length arg
))
69 (char= #\-
(schar arg
0))
70 (char= #\-
(schar arg
1))))
72 (defun is-long-option (arg)
73 (and (> (length arg
) 2)
74 (char= #\-
(schar arg
0))
75 (char= #\-
(schar arg
1))
76 (char/= #\-
(schar arg
2))))
78 (defun decompose-arg (arg option-type
)
79 "Returns base-name,argument"
80 (let ((start (ecase option-type
83 (name-end (position #\
= arg
)))
85 (values (subseq arg start name-end
)
86 (when name-end
(subseq arg
(1+ name-end
))))))
88 (defun analyze-arg (arg)
89 "Analyzes an argument. Returns option-type,base-name,argument"
90 (let* ((option-type (cond ((is-short-option arg
) :short
)
91 ((is-long-option arg
) :long
)
93 (if (or (eq option-type
:short
) (eq option-type
:long
))
94 (multiple-value-bind (base arg
) (decompose-arg arg option-type
)
95 (values option-type base arg
))
96 (values :arg arg nil
))))
99 (defun find-option (name options
&key allow-exact-match
)
100 "Find an option in option list. Handles using unique abbreviations"
101 (let* ((option-names (mapcar #'car options
))
102 (pos (match-unique-abbreviation name option-names
:allow-exact-match allow-exact-match
)))
106 (defun match-option (arg options
&key allow-exact-match
)
107 "Matches an argument to an option. Returns option-list,option-type,base-name,argument"
108 (multiple-value-bind (option-type base-name argument
) (analyze-arg arg
)
109 (let ((match (find-option base-name options
:allow-exact-match allow-exact-match
)))
110 (values match option-type
(when match
(car match
)) argument
))))
113 ;;; EXPORTED functions
115 (defun match-unique-abbreviation (abbr strings
&key
(allow-exact-match nil
))
116 "Returns position of ABBR in STRINGS. ABBR may be a unique abbreviation.
117 Returns NIL if no match found."
118 (let ((len (length abbr
))
120 (dotimes (i (length strings
))
121 (let* ((s (nth i strings
))
125 (when (string= abbr s
)
126 (if allow-exact-match
127 (return-from match-unique-abbreviation i
)
128 (push (cons s i
) matches
))))
130 (when (string= abbr
(subseq s
0 len
))
131 (push (cons s i
) matches
))))))
132 (when (= 1 (length matches
))
133 (cdr (first matches
)))))
135 (defun getopt (args options
&key allow-exact-match
)
136 "Processes a list of arguments and options. Returns three values:
137 - Non-option arguments
138 - An alist of options consisting of the option name and the value, if any
139 - A list of any option names that were not recognized
141 options is a list of option lists. The fields of the list are
142 - NAME name of the long option
143 - HAS-ARG with legal values of :NONE, :REQUIRED, :OPTIONAL
144 - VAL value to return for a option with no arguments"
145 (do ((pos args
(cdr pos
))
150 ((null pos
) (values (nreverse out-args
) (nreverse out-opts
) errors
))
153 (push (car pos
) out-args
))
154 ((is-option-terminator (car pos
))
155 (setq finished-options t
))
157 (let ((arg (car pos
)))
158 (multiple-value-bind (option-list option-type base-name argument
)
159 (match-option (car pos
) options
:allow-exact-match allow-exact-match
)
161 ((and option-list
(not (eq option-type
:arg
)))
164 (case (second option-list
)
166 (push base-name errors
))
168 (push (cons base-name argument
) out-opts
))))
170 (if (and (eq :required
(second option-list
)) (null (cdr pos
)))
171 (push base-name errors
)
172 (case (second option-list
)
174 (push (cons base-name
(third option-list
)) out-opts
))
176 ;; Next arg is the value.
177 (push (cons base-name
(second pos
)) out-opts
)
178 (setf pos
(cdr pos
)))
180 ;; Optional arg. If the next arg is an option
181 ;; arg, we use the default value. Otherwise we
182 ;; use the next arg as the value.
183 (if (or (is-short-option (second pos
))
184 (is-long-option (second pos
)))
185 (push (cons base-name
(third option-list
)) out-opts
)
187 (push (cons base-name
(second pos
)) out-opts
)
188 (setf pos
(cdr pos
))))))))))
190 (cond ((eq :long option-type
)
191 (push (nth-value 0 (decompose-arg arg option-type
)) errors
))
192 ((eq :short option-type
)
193 (cond ((<= (length (car pos
)) 2)
194 ;; Unrecognized short option (one character)
195 (push (nth-value 0 (decompose-arg arg option-type
)) errors
))
197 ;; We have option that's not matched, but
198 ;; looks like a short option like "-abc".
199 ;; Expand this to '("-a" "-b" "-c") and
200 ;; effectively replace "-abc" with the
201 ;; replacement. We setf the cdr because
202 ;; the do loop will remove "-abc" for us.
206 (concatenate 'string
"-" (string x
)))
207 (subseq (car pos
) 1))
210 (push arg out-args
)))))))))))