168 lines
4.3 KiB
Scheme
168 lines
4.3 KiB
Scheme
|
(library
|
||
|
(command-line get-opt)
|
||
|
(export get-opt)
|
||
|
(import (chezscheme)
|
||
|
(fmt fmt)
|
||
|
(only (srfi s1 lists) concatenate!)
|
||
|
(srfi s8 receive))
|
||
|
|
||
|
;;; FIXME: return an error rather than raising a condition
|
||
|
(define-record-type option (fields name long-forms short-forms arg?))
|
||
|
(define-record-type getopt-results (fields switches rest))
|
||
|
|
||
|
(define (end-of-switches? str)
|
||
|
(string=? "--" str))
|
||
|
|
||
|
(define (long-opt? str)
|
||
|
(string=? "--" (substring str 0 2)))
|
||
|
|
||
|
(define (short-opt? str)
|
||
|
(and (eq? (string-ref str 0) #\-)
|
||
|
(> (string-length str) 1)))
|
||
|
|
||
|
(define (for-each-form fn opts form-fn)
|
||
|
(for-each
|
||
|
(lambda (opt)
|
||
|
(for-each
|
||
|
(lambda (elt) (fn opt elt))
|
||
|
(form-fn opt)))
|
||
|
opts))
|
||
|
|
||
|
(define (build-short-opt-hash opts)
|
||
|
(let ((ht (make-eq-hashtable)))
|
||
|
(for-each-form
|
||
|
(lambda (opt c)
|
||
|
(hashtable-set! ht c opt))
|
||
|
opts
|
||
|
option-short-forms)
|
||
|
ht))
|
||
|
|
||
|
(define (build-long-opt-hash opts)
|
||
|
(let ((ht (make-hashtable string-hash string=?)))
|
||
|
(for-each-form
|
||
|
(lambda (opt str)
|
||
|
(hashtable-set! ht str opt))
|
||
|
opts
|
||
|
option-long-forms)
|
||
|
ht))
|
||
|
|
||
|
(define (make-err str)
|
||
|
(condition
|
||
|
(make-error)
|
||
|
(make-message-condition str)))
|
||
|
|
||
|
(define (unknown-option arg)
|
||
|
(make-err (fmt #f "unknown option: " arg)))
|
||
|
|
||
|
(define (missing-arg opt)
|
||
|
(make-err
|
||
|
(fmt #f "missing argument for option: " opt)))
|
||
|
|
||
|
(define (assert-len opt args len)
|
||
|
(unless (zero? len)
|
||
|
(when (null? args)
|
||
|
(raise (missing-arg opt)))))
|
||
|
|
||
|
(define opt-ref
|
||
|
(let ((sym (gensym)))
|
||
|
(lambda (ht key)
|
||
|
(let ((opt (hashtable-ref ht key sym)))
|
||
|
(if (eq? opt sym)
|
||
|
(raise (unknown-option key))
|
||
|
opt)))))
|
||
|
|
||
|
(define (extract-long-form str)
|
||
|
(substring str 2 (string-length str)))
|
||
|
|
||
|
(define (extract-short-form str)
|
||
|
(string-ref str 1))
|
||
|
|
||
|
;; f - returns (values elt new-args)
|
||
|
;; iteration terminates when args is null?
|
||
|
(define (unfold-args f args)
|
||
|
(let loop ((args args)
|
||
|
(acc '()))
|
||
|
(if (null? args)
|
||
|
(reverse acc)
|
||
|
(receive (elt new-args) (f args)
|
||
|
(loop new-args (cons elt acc))))))
|
||
|
|
||
|
;; It's easier if multiple short flags such as '-vft' are expanded to single
|
||
|
;; switches.
|
||
|
(define (expand-short-forms args)
|
||
|
(define (expand args)
|
||
|
(let ((arg (car args)))
|
||
|
(cond
|
||
|
((long-opt? arg)
|
||
|
(values (list arg) (cdr args)))
|
||
|
|
||
|
((short-opt? arg)
|
||
|
(values (map (lambda (c)
|
||
|
(fmt #f "-" c))
|
||
|
(cdr (string->list arg)))
|
||
|
(cdr args)))
|
||
|
|
||
|
(else
|
||
|
(values (list arg) (cdr args))))))
|
||
|
|
||
|
(concatenate!
|
||
|
(unfold-args expand args)))
|
||
|
|
||
|
;; Returns a list of elts of the form:
|
||
|
;; ('switch <opt>)
|
||
|
;; ('arg-switch <opt> <arg>)
|
||
|
;; ('positional <arg>)
|
||
|
|
||
|
(define (process-all-opts short-ht long-ht opts)
|
||
|
(define (match-opt opt args)
|
||
|
(if (option-arg? opt)
|
||
|
(begin
|
||
|
(assert-len opt args 2)
|
||
|
(values `((arg-switch ,opt ,(cadr args)))
|
||
|
(cddr args)))
|
||
|
(values `((switch ,opt))
|
||
|
(cdr args))))
|
||
|
|
||
|
(define (process-one-opt args)
|
||
|
;; We know args contains at least one entry
|
||
|
(let ((arg (car args)))
|
||
|
(cond
|
||
|
((end-of-switches? arg)
|
||
|
(values (map (lambda (a)
|
||
|
`(positional ,a))
|
||
|
(cdr args))
|
||
|
'()))
|
||
|
|
||
|
((long-opt? arg)
|
||
|
(let ((opt (opt-ref long-ht (extract-long-form arg))))
|
||
|
(match-opt opt args)))
|
||
|
|
||
|
((short-opt? arg)
|
||
|
(let ((opt (opt-ref short-ht (extract-short-form arg))))
|
||
|
(match-opt opt args)))
|
||
|
|
||
|
(else
|
||
|
(values `((positional ,arg))
|
||
|
(cdr args))))))
|
||
|
|
||
|
(concatenate!
|
||
|
(unfold-args process-one-opt opts)))
|
||
|
|
||
|
(define (build-results opts)
|
||
|
(let ((ht (make-eq-hashtable))
|
||
|
(positional '()))
|
||
|
(for-each (lambda (opt)
|
||
|
(case (car opt)
|
||
|
((switch) (hashtable-set! ht (cadr opt) #t))
|
||
|
((arg-switch) (hashtable-set! ht (cadr opt) (caddr opt)))
|
||
|
((positional) (set! positional (cons (cadr opt) positional)))))
|
||
|
opts)
|
||
|
(make-getopt-results ht (reverse positional))))
|
||
|
|
||
|
(define (get-opt opts)
|
||
|
(let ((short-ht (build-short-opt-hash opts))
|
||
|
(long-ht (build-long-opt-hash opts)))
|
||
|
(lambda (args)
|
||
|
(build-results
|
||
|
(process-all-opts short-ht long-ht (expand-short-forms args))))))
|