; Copyright (c) 2004, James Bailie <jimmy@mammothcheese.ca>.
; All rights reserved.
; 
; Redistribution and use in source form, with or without
; modification, are permitted provided that the following conditions are met:
; 
;     * Redistributions of source code must retain the above copyright
; notice, this list of conditions and the following disclaimer.
;     * The name of James Bailie may not be used to endorse or promote 
; products derived from this software without specific prior written permission.
; 
; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS"
; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
; ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
; LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
; SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
; CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
; ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
; POSSIBILITY OF SUCH DAMAGE.

; This module extracts single-character command-line options and maps the
; options to their values in a table named "options", leaving the argument
; pointer pointing at the appropriate position for a call to the foreach_line or
; foreach_line_callback macros.  No type conversion is performed on values
; extracted from the command line.  They are stored in the "options" table as
; strings.  Boolean options, however, which have no values, will be mapped to
; the number 1, if found.  Option characters must be in the set [A-Za-z0-9].

; A single function named "getopt" intializes the "options" table.  All
; arguments to the function are optional, and if present, each one must be a
; string containing an option character that the user wishes to recognize as a
; boolean option which is either present or not present, and has no
; corresponding value setting.  Boolean options will be mapped to 1 in the
; "options" table if they are found, otherwise there will be no mapping for the
; boolean option.  This holds true for all options.  The absence of a mapping
; indicates the option's absence from the command line.

; Options are recognized in either -ovalue or -o value formats.  To pass an
; initial command-line argument which looks like an option to a program using
; this module, the option list must be terminated with the end of option flag
; "--".  If the argument is a filename, one could also fully-qualify it.  For
; example:  

; % booger.munger -n10 -f 11 -- -weird-filename

; or

; % booger.munger -n10 -f 11 ./-weird-filename

; An example of usage:
;
; (setq len 75)
; (setq prefix 0)
; (setq tabstop 8)
;
; (load (join "/" (libdir) "options.munger"))
; (getopt "p")
; 
; (let ((arg ()))
; 
;  (when (setq arg (lookup options "l"))
;     (setq len (or (abs (digitize arg)) 75)))
; 
;  (when (setq arg (lookup options "p"))
;     (setq prefix arg))
; 
;  (when (setq arg (lookup options "t"))
;     (setq tabstop (or (abs (digitize arg)) 8))))

; Create the table.

(setq options (table))

; Advance the argument pointer past the name of the interpreter.

(next)

(defun getopt ((boolean))

   ; Check for options beyond the name of the script.

   (when (next)

      (let ((arg (current))
            (rx (regcomp "^-([A-Za-z0-9])?(.*)"))
            (m ()))

         (while arg

            (if (setq m (matches rx arg))
               (progn

                  ; We found an option.

                  (if (eq (car (cddr m)) "")

                     ; If not a collapsed option, check to see
                     ; if it is a boolean.

                     (if (member (cadr m) boolean)

                        ; If it is boolean map it to 1 in options.

                        (hash options (cadr m) 1)

                        ; Otherwise, map it to the next argument.

                        (hash options (cadr m) (next)))

                     ; If it is a collapsed option, check to see
                     ; if it is the end of options flag.

                     (if (not (eq (car (cddr m)) "-"))

                        ; If it isn't the end of options flag,
                        ; store the mapping.

                        (hash options (cadr m) (car (cddr m)))

                        ; Otherwise, reset the argument pointer,
                        ; and invoke continue to short-circuit
                        ; rest of loop.

                        (prev)
                        (setq arg 0)
                        (continue)))

                  ; Advance argument pointer.

                  (setq arg (next)))

               ; No more option args, reset argument pointer.

               (prev)
               (setq arg 0))))))
