use guile eval for elisp tree-il
[bpt/guile.git] / module / ice-9 / getopt-long.scm
index a81d61c..14eaf8e 100644 (file)
 ;;; Code:
 
 (define-module (ice-9 getopt-long)
-  #:use-module ((ice-9 common-list) #:select (some remove-if-not))
+  #:use-module ((ice-9 common-list) #:select (remove-if-not))
   #:use-module (srfi srfi-9)
   #:use-module (ice-9 match)
+  #:use-module (ice-9 regex)
+  #:use-module (ice-9 optargs)
   #:export (getopt-long option-ref))
 
+(define %program-name (make-fluid "guile"))
+(define (program-name)
+  (fluid-ref %program-name))
+
+(define (fatal-error fmt . args)
+  (format (current-error-port) "~a: " (program-name))
+  (apply format (current-error-port) fmt args)
+  (newline (current-error-port))
+  (exit 1))
+
 (define-record-type option-spec
-  (%make-option-spec name value required? single-char predicate
-                     value-policy)
+  (%make-option-spec name required? option-spec->single-char predicate value-policy)
   option-spec?
   (name
    option-spec->name set-option-spec-name!)
-  (value 
-   option-spec->value set-option-spec-value!)
   (required?
    option-spec->required? set-option-spec-required?!)
   (option-spec->single-char
    option-spec->value-policy set-option-spec-value-policy!))
 
 (define (make-option-spec name)
-  (%make-option-spec name #f #f #f #f #f))
+  (%make-option-spec name #f #f #f #f))
 
 (define (parse-option-spec desc)
   (let ((spec (make-option-spec (symbol->string (car desc)))))
                  spec (lambda (name val)
                         (or (not val)
                             (pred val)
-                            (error "option predicate failed:" name)))))
+                            (fatal-error "option predicate failed: --~a"
+                                         name)))))
                ((prop val)
                 (error "invalid getopt-long option property:" prop)))
               (cdr desc))
 (define long-opt-no-value-rx   (make-regexp "^--([^=]+)$"))
 (define long-opt-with-value-rx (make-regexp "^--([^=]+)=(.*)"))
 
-(define (match-substring match which)
-  ;; condensed from (ice-9 regex) `match:{substring,start,end}'
-  (let ((sel (vector-ref match (1+ which))))
-    (substring (vector-ref match 0) (car sel) (cdr sel))))
-
-(define (expand-clumped-singles opt-ls)
-  ;; example: ("--xyz" "-abc5d") => ("--xyz" "-a" "-b" "-c" "5d")
-  (let loop ((opt-ls opt-ls) (ret-ls '()))
-    (cond ((null? opt-ls)
-           (reverse ret-ls))                                    ;;; retval
-          ((regexp-exec short-opt-rx (car opt-ls))
-           => (lambda (match)
-                (let ((singles (reverse
-                                (map (lambda (c)
-                                       (string-append "-" (make-string 1 c)))
-                                     (string->list
-                                      (match-substring match 1)))))
-                      (extra (match-substring match 2)))
-                  (loop (cdr opt-ls)
-                        (append (if (string=? "" extra)
-                                    singles
-                                    (cons extra singles))
-                                ret-ls)))))
-          (else (loop (cdr opt-ls)
-                      (cons (car opt-ls) ret-ls))))))
-
 (define (looks-like-an-option string)
-  (some (lambda (rx)
-          (regexp-exec rx string))
-        `(,short-opt-rx
-          ,long-opt-with-value-rx
-          ,long-opt-no-value-rx)))
+  (or (regexp-exec short-opt-rx string)
+      (regexp-exec long-opt-with-value-rx string)
+      (regexp-exec long-opt-no-value-rx string)))
 
-(define (process-options specs argument-ls)
+(define (process-options specs argument-ls stop-at-first-non-option)
   ;; Use SPECS to scan ARGUMENT-LS; return (FOUND . ETC).
   ;; FOUND is an unordered list of option specs for found options, while ETC
   ;; is an order-maintained list of elements in ARGUMENT-LS that are neither
                        (cons (make-string 1 (option-spec->single-char spec))
                              spec))
                      (remove-if-not option-spec->single-char specs))))
-    (let loop ((argument-ls argument-ls) (found '()) (etc '()))
-      (let ((eat! (lambda (spec ls)
-                    (let ((val!loop (lambda (val n-ls n-found n-etc)
-                                      (set-option-spec-value!
-                                       spec
-                                       ;; handle multiple occurrances
-                                       (cond ((option-spec->value spec)
-                                              => (lambda (cur)
-                                                   ((if (list? cur) cons list)
-                                                    val cur)))
-                                             (else val)))
-                                      (loop n-ls n-found n-etc)))
-                          (ERR:no-arg (lambda ()
-                                        (error (string-append
-                                                "option must be specified"
-                                                " with argument:")
-                                               (option-spec->name spec)))))
-                      (cond
-                       ((eq? 'optional (option-spec->value-policy spec))
-                        (if (or (null? (cdr ls))
-                                (looks-like-an-option (cadr ls)))
-                            (val!loop #t
-                                      (cdr ls)
-                                      (cons spec found)
-                                      etc)
-                            (val!loop (cadr ls)
-                                      (cddr ls)
-                                      (cons spec found)
-                                      etc)))
-                       ((eq? #t (option-spec->value-policy spec))
-                        (if (or (null? (cdr ls))
-                                (looks-like-an-option (cadr ls)))
-                            (ERR:no-arg)
-                            (val!loop (cadr ls)
-                                      (cddr ls)
-                                      (cons spec found)
-                                      etc)))
-                       (else
-                        (val!loop #t
-                                  (cdr ls)
-                                  (cons spec found)
-                                  etc)))))))
-        (if (null? argument-ls)
-            (cons found (reverse etc))                          ;;; retval
-            (cond ((regexp-exec short-opt-rx (car argument-ls))
-                   => (lambda (match)
-                        (let* ((c (match-substring match 1))
-                               (spec (or (assoc-ref sc-idx c)
-                                         (error "no such option:" c))))
-                          (eat! spec argument-ls))))
-                  ((regexp-exec long-opt-no-value-rx (car argument-ls))
-                   => (lambda (match)
-                        (let* ((opt (match-substring match 1))
-                               (spec (or (assoc-ref idx opt)
-                                         (error "no such option:" opt))))
-                          (eat! spec argument-ls))))
-                  ((regexp-exec long-opt-with-value-rx (car argument-ls))
-                   => (lambda (match)
-                        (let* ((opt (match-substring match 1))
-                               (spec (or (assoc-ref idx opt)
-                                         (error "no such option:" opt))))
-                          (if (option-spec->value-policy spec)
-                              (eat! spec (append
-                                          (list 'ignored
-                                                (match-substring match 2))
-                                          (cdr argument-ls)))
-                              (error "option does not support argument:"
-                                     opt)))))
-                  (else
-                   (loop (cdr argument-ls)
-                         found
-                         (cons (car argument-ls) etc)))))))))
+    (let loop ((unclumped 0) (argument-ls argument-ls) (found '()) (etc '()))
+      (define (eat! spec ls)
+        (cond
+         ((eq? 'optional (option-spec->value-policy spec))
+          (if (or (null? ls)
+                  (looks-like-an-option (car ls)))
+              (loop (- unclumped 1) ls (acons spec #t found) etc)
+              (loop (- unclumped 2) (cdr ls) (acons spec (car ls) found) etc)))
+         ((eq? #t (option-spec->value-policy spec))
+          (if (or (null? ls)
+                  (looks-like-an-option (car ls)))
+              (fatal-error "option must be specified with argument: --~a"
+                           (option-spec->name spec))
+              (loop (- unclumped 2) (cdr ls) (acons spec (car ls) found) etc)))
+         (else
+          (loop (- unclumped 1) ls (acons spec #t found) etc))))
+      
+      (match argument-ls
+        (()
+         (cons found (reverse etc)))
+        ((opt . rest)
+         (cond
+          ((regexp-exec short-opt-rx opt)
+           => (lambda (match)
+                (if (> unclumped 0)
+                    ;; Next option is known not to be clumped.
+                    (let* ((c (match:substring match 1))
+                           (spec (or (assoc-ref sc-idx c)
+                                     (fatal-error "no such option: -~a" c))))
+                      (eat! spec rest))
+                    ;; Expand a clumped group of short options.
+                    (let* ((extra (match:substring match 2))
+                           (unclumped-opts
+                            (append (map (lambda (c)
+                                           (string-append "-" (make-string 1 c)))
+                                         (string->list
+                                          (match:substring match 1)))
+                                    (if (string=? "" extra) '() (list extra)))))
+                      (loop (length unclumped-opts)
+                            (append unclumped-opts rest)
+                            found
+                            etc)))))
+          ((regexp-exec long-opt-no-value-rx opt)
+           => (lambda (match)
+                (let* ((opt (match:substring match 1))
+                       (spec (or (assoc-ref idx opt)
+                                 (fatal-error "no such option: --~a" opt))))
+                  (eat! spec rest))))
+          ((regexp-exec long-opt-with-value-rx opt)
+           => (lambda (match)
+                (let* ((opt (match:substring match 1))
+                       (spec (or (assoc-ref idx opt)
+                                 (fatal-error "no such option: --~a" opt))))
+                  (if (option-spec->value-policy spec)
+                      (eat! spec (cons (match:substring match 2) rest))
+                      (fatal-error "option does not support argument: --~a"
+                                   opt)))))
+          ((and stop-at-first-non-option
+                (<= unclumped 0))
+           (cons found (append (reverse etc) argument-ls)))
+          (else
+           (loop (- unclumped 1) rest found (cons opt etc)))))))))
 
-(define (getopt-long program-arguments option-desc-list)
+(define* (getopt-long program-arguments option-desc-list
+                      #:key stop-at-first-non-option)
   "Process options, handling both long and short options, similar to
 the glibc function 'getopt_long'.  PROGRAM-ARGUMENTS should be a value
 similar to what (program-arguments) returns.  OPTION-DESC-LIST is a
@@ -365,44 +338,30 @@ or option values.
 required.  By default, single character equivalents are not supported;
 if you want to allow the user to use single character options, you need
 to add a `single-char' clause to the option description."
-  (let* ((specifications (map parse-option-spec option-desc-list))
-        (pair (split-arg-list (cdr program-arguments)))
-        (split-ls (expand-clumped-singles (car pair)))
-        (non-split-ls (cdr pair))
-         (found/etc (process-options specifications split-ls))
-         (found (car found/etc))
-         (rest-ls (append (cdr found/etc) non-split-ls)))
-    (for-each (lambda (spec)
-                (let ((name (option-spec->name spec))
-                      (val (option-spec->value spec)))
-                  (and (option-spec->required? spec)
-                       (or (memq spec found)
-                           (error "option must be specified:" name)))
-                  (and (memq spec found)
-                       (eq? #t (option-spec->value-policy spec))
-                       (or val
-                           (error "option must be specified with argument:"
-                                  name)))
-                  (let ((pred (option-spec->predicate spec)))
-                    (and pred (pred name val)))))
-              specifications)
-    (cons (cons '() rest-ls)
-          (let ((multi-count (map (lambda (desc)
-                                    (cons (car desc) 0))
-                                  option-desc-list)))
-            (map (lambda (spec)
-                   (let ((name (string->symbol (option-spec->name spec))))
-                     (cons name
-                           ;; handle multiple occurrances
-                           (let ((maybe-ls (option-spec->value spec)))
-                             (if (list? maybe-ls)
-                                 (let* ((look (assq name multi-count))
-                                        (idx (cdr look))
-                                        (val (list-ref maybe-ls idx)))
-                                   (set-cdr! look (1+ idx)) ; ugh!
-                                   val)
-                                 maybe-ls)))))
-                 found)))))
+  (with-fluids ((%program-name (car program-arguments)))
+    (let* ((specifications (map parse-option-spec option-desc-list))
+           (pair (split-arg-list (cdr program-arguments)))
+           (split-ls (car pair))
+           (non-split-ls (cdr pair))
+           (found/etc (process-options specifications split-ls
+                                       stop-at-first-non-option))
+           (found (car found/etc))
+           (rest-ls (append (cdr found/etc) non-split-ls)))
+      (for-each (lambda (spec)
+                  (let ((name (option-spec->name spec))
+                        (val (assq-ref found spec)))
+                    (and (option-spec->required? spec)
+                         (or val
+                             (fatal-error "option must be specified: --~a"
+                                          name)))
+                    (let ((pred (option-spec->predicate spec)))
+                      (and pred (pred name val)))))
+                specifications)
+      (for-each (lambda (spec+val)
+                  (set-car! spec+val
+                            (string->symbol (option-spec->name (car spec+val)))))
+                found)
+      (cons (cons '() rest-ls) found))))
 
 (define (option-ref options key default)
   "Return value in alist OPTIONS using KEY, a symbol; or DEFAULT if not found.