srfi-37.scm 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. ;;; srfi-37.scm --- args-fold
  2. ;; Copyright (C) 2007, 2008 Free Software Foundation, Inc.
  3. ;;
  4. ;; This library is free software; you can redistribute it and/or
  5. ;; modify it under the terms of the GNU Lesser General Public
  6. ;; License as published by the Free Software Foundation; either
  7. ;; version 2.1 of the License, or (at your option) any later version.
  8. ;;
  9. ;; This library is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. ;; Lesser General Public License for more details.
  13. ;;
  14. ;; You should have received a copy of the GNU Lesser General Public
  15. ;; License along with this library; if not, write to the Free Software
  16. ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  17. ;;; Commentary:
  18. ;;
  19. ;; To use this module with Guile, use (cdr (program-arguments)) as
  20. ;; the ARGS argument to `args-fold'. Here is a short example:
  21. ;;
  22. ;; (args-fold (cdr (program-arguments))
  23. ;; (let ((display-and-exit-proc
  24. ;; (lambda (msg)
  25. ;; (lambda (opt name arg)
  26. ;; (display msg) (quit) (values)))))
  27. ;; (list (option '(#\v "version") #f #f
  28. ;; (display-and-exit-proc "Foo version 42.0\n"))
  29. ;; (option '(#\h "help") #f #f
  30. ;; (display-and-exit-proc
  31. ;; "Usage: foo scheme-file ..."))))
  32. ;; (lambda (opt name arg)
  33. ;; (error "Unrecognized option `~A'" name))
  34. ;; (lambda (op) (load op) (values)))
  35. ;;
  36. ;;; Code:
  37. ;;;; Module definition & exports
  38. (define-module (srfi srfi-37)
  39. #:use-module (srfi srfi-9)
  40. #:export (option option-names option-required-arg?
  41. option-optional-arg? option-processor
  42. args-fold))
  43. (cond-expand-provide (current-module) '(srfi-37))
  44. ;;;; args-fold and periphery procedures
  45. ;;; An option as answered by `option'. `names' is a list of
  46. ;;; characters and strings, representing associated short-options and
  47. ;;; long-options respectively that should use this option's
  48. ;;; `processor' in an `args-fold' call.
  49. ;;;
  50. ;;; `required-arg?' and `optional-arg?' are mutually exclusive
  51. ;;; booleans and indicate whether an argument must be or may be
  52. ;;; provided. Besides the obvious, this affects semantics of
  53. ;;; short-options, as short-options with a required or optional
  54. ;;; argument cannot be followed by other short options in the same
  55. ;;; program-arguments string, as they will be interpreted collectively
  56. ;;; as the option's argument.
  57. ;;;
  58. ;;; `processor' is called when this option is encountered. It should
  59. ;;; accept the containing option, the element of `names' (by `equal?')
  60. ;;; encountered, the option's argument (or #f if none), and the seeds
  61. ;;; as variadic arguments, answering the new seeds as values.
  62. (define-record-type srfi-37:option
  63. (option names required-arg? optional-arg? processor)
  64. option?
  65. (names option-names)
  66. (required-arg? option-required-arg?)
  67. (optional-arg? option-optional-arg?)
  68. (processor option-processor))
  69. (define (error-duplicate-option option-name)
  70. (scm-error 'program-error "args-fold"
  71. "Duplicate option name `~A~A'"
  72. (list (if (char? option-name) #\- "--")
  73. option-name)
  74. #f))
  75. (define (build-options-lookup options)
  76. "Answer an `equal?' Guile hash-table that maps OPTIONS' names back
  77. to the containing options, signalling an error if a name is
  78. encountered more than once."
  79. (let ((lookup (make-hash-table (* 2 (length options)))))
  80. (for-each
  81. (lambda (opt)
  82. (for-each (lambda (name)
  83. (let ((assoc (hash-create-handle!
  84. lookup name #f)))
  85. (if (cdr assoc)
  86. (error-duplicate-option (car assoc))
  87. (set-cdr! assoc opt))))
  88. (option-names opt)))
  89. options)
  90. lookup))
  91. (define (args-fold args options unrecognized-option-proc
  92. operand-proc . seeds)
  93. "Answer the results of folding SEEDS as multiple values against the
  94. program-arguments in ARGS, as decided by the OPTIONS'
  95. `option-processor's, UNRECOGNIZED-OPTION-PROC, and OPERAND-PROC."
  96. (let ((lookup (build-options-lookup options)))
  97. ;; I don't like Guile's `error' here
  98. (define (error msg . args)
  99. (scm-error 'misc-error "args-fold" msg args #f))
  100. (define (mutate-seeds! procedure . params)
  101. (set! seeds (call-with-values
  102. (lambda ()
  103. (apply procedure (append params seeds)))
  104. list)))
  105. ;; Clean up the rest of ARGS, assuming they're all operands.
  106. (define (rest-operands)
  107. (for-each (lambda (arg) (mutate-seeds! operand-proc arg))
  108. args)
  109. (set! args '()))
  110. ;; Call OPT's processor with OPT, NAME, an argument to be decided,
  111. ;; and the seeds. Depending on OPT's *-arg? specification, get
  112. ;; the parameter by calling REQ-ARG-PROC or OPT-ARG-PROC thunks;
  113. ;; if no argument is allowed, call NO-ARG-PROC thunk.
  114. (define (invoke-option-processor
  115. opt name req-arg-proc opt-arg-proc no-arg-proc)
  116. (mutate-seeds!
  117. (option-processor opt) opt name
  118. (cond ((option-required-arg? opt) (req-arg-proc))
  119. ((option-optional-arg? opt) (opt-arg-proc))
  120. (else (no-arg-proc) #f))))
  121. ;; Compute and answer a short option argument, advancing ARGS as
  122. ;; necessary, for the short option whose character is at POSITION
  123. ;; in the current ARG.
  124. (define (short-option-argument position)
  125. (cond ((< (1+ position) (string-length (car args)))
  126. (let ((result (substring (car args) (1+ position))))
  127. (set! args (cdr args))
  128. result))
  129. ((pair? (cdr args))
  130. (let ((result (cadr args)))
  131. (set! args (cddr args))
  132. result))
  133. (else #f)))
  134. ;; Interpret the short-option at index POSITION in (car ARGS),
  135. ;; followed by the remaining short options in (car ARGS).
  136. (define (short-option position)
  137. (if (>= position (string-length (car args)))
  138. (begin
  139. (set! args (cdr args))
  140. (next-arg))
  141. (let* ((opt-name (string-ref (car args) position))
  142. (option-here (hash-ref lookup opt-name)))
  143. (cond ((not option-here)
  144. (mutate-seeds! unrecognized-option-proc
  145. (option (list opt-name) #f #f
  146. unrecognized-option-proc)
  147. opt-name #f)
  148. (short-option (1+ position)))
  149. (else
  150. (invoke-option-processor
  151. option-here opt-name
  152. (lambda ()
  153. (or (short-option-argument position)
  154. (error "Missing required argument after `-~A'" opt-name)))
  155. (lambda ()
  156. ;; edge case: -xo -zf or -xo -- where opt-name=#\o
  157. ;; GNU getopt_long resolves these like I do
  158. (short-option-argument position))
  159. (lambda () #f))
  160. (if (not (or (option-required-arg? option-here)
  161. (option-optional-arg? option-here)))
  162. (short-option (1+ position))))))))
  163. ;; Process the long option in (car ARGS). We make the
  164. ;; interesting, possibly non-standard assumption that long option
  165. ;; names might contain #\=, so keep looking for more #\= in (car
  166. ;; ARGS) until we find a named option in lookup.
  167. (define (long-option)
  168. (let ((arg (car args)))
  169. (let place-=-after ((start-pos 2))
  170. (let* ((index (string-index arg #\= start-pos))
  171. (opt-name (substring arg 2 (or index (string-length arg))))
  172. (option-here (hash-ref lookup opt-name)))
  173. (if (not option-here)
  174. ;; look for a later #\=, unless there can't be one
  175. (if index
  176. (place-=-after (1+ index))
  177. (mutate-seeds!
  178. unrecognized-option-proc
  179. (option (list opt-name) #f #f unrecognized-option-proc)
  180. opt-name #f))
  181. (invoke-option-processor
  182. option-here opt-name
  183. (lambda ()
  184. (if index
  185. (substring arg (1+ index))
  186. (error "Missing required argument after `--~A'" opt-name)))
  187. (lambda () (and index (substring arg (1+ index))))
  188. (lambda ()
  189. (if index
  190. (error "Extraneous argument after `--~A'" opt-name))))))))
  191. (set! args (cdr args)))
  192. ;; Process the remaining in ARGS. Basically like calling
  193. ;; `args-fold', but without having to regenerate `lookup' and the
  194. ;; funcs above.
  195. (define (next-arg)
  196. (if (null? args)
  197. (apply values seeds)
  198. (let ((arg (car args)))
  199. (cond ((or (not (char=? #\- (string-ref arg 0)))
  200. (= 1 (string-length arg))) ;"-"
  201. (mutate-seeds! operand-proc arg)
  202. (set! args (cdr args)))
  203. ((char=? #\- (string-ref arg 1))
  204. (if (= 2 (string-length arg)) ;"--"
  205. (begin (set! args (cdr args)) (rest-operands))
  206. (long-option)))
  207. (else (short-option 1)))
  208. (next-arg))))
  209. (next-arg)))
  210. ;;; srfi-37.scm ends here