[functional-tests] Code up an equivalent of get_opt_long()
This commit is contained in:
		
							
								
								
									
										167
									
								
								functional-tests/command-line/getopt.scm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								functional-tests/command-line/getopt.scm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,167 @@ | ||||
| (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)))))) | ||||
		Reference in New Issue
	
	Block a user