unofficial mirror of guile-devel@gnu.org 
 help / color / mirror / Atom feed
* [patch] SRFI-37 support
@ 2007-07-14  8:31 Stephen Compall
  2007-07-18 20:45 ` Ludovic Courtès
  0 siblings, 1 reply; 2+ messages in thread
From: Stephen Compall @ 2007-07-14  8:31 UTC (permalink / raw)
  To: guile-devel


[-- Attachment #1.1: Type: text/plain, Size: 19330 bytes --]

Here is SRFI-37, args-fold, implemented by me from scratch in Scheme,
including a manual section and a few tests.  Patch does not include log
entries, listed immediately below instead:

srfi/ChangeLog:

2007-07-14  Stephen Compall  <s11@member.fsf.org>

	* srfi-37.scm: New file.
	* Makefile.am: Add it.

test-suite/ChangeLog:

2007-07-14  Stephen Compall  <s11@member.fsf.org>

	* tests/srfi-37.test: New file.
	* Makefile.am: Add it.

doc/ref/ChangeLog:

2007-07-14  Stephen Compall  <s11@member.fsf.org>

	* srfi-modules.texi: Describe SRFI-37 in a new subsection.

Index: doc/ref/srfi-modules.texi
===================================================================
RCS file: /sources/guile/guile/guile-core/doc/ref/srfi-modules.texi,v
retrieving revision 1.72
diff -u -d -u -r1.72 srfi-modules.texi
--- doc/ref/srfi-modules.texi	31 Jan 2007 20:58:20 -0000	1.72
+++ doc/ref/srfi-modules.texi	14 Jul 2007 08:23:01 -0000
@@ -37,6 +37,7 @@
 * SRFI-19::                     Time/Date library.
 * SRFI-26::                     Specializing parameters
 * SRFI-31::                     A special form `rec' for recursive evaluation
+* SRFI-37::                     args-fold program argument processor
 * SRFI-39::                     Parameter objects
 * SRFI-55::                     Requiring Features.
 * SRFI-60::                     Integers as bits.
@@ -2401,6 +2402,93 @@
 @end lisp
 
 
+@node SRFI-37
+@subsection SRFI-37 - args-fold
+@cindex SRFI-37
+
+This is a processor for GNU @code{getopt_long}-style program
+arguments.  It provides an alternative, less declarative interface
+than @code{getopt-long} in @code{(ice-9 getopt-long)}
+(@pxref{getopt-long,,The (ice-9 getopt-long) Module}).  Unlike
+@code{getopt-long}, it supports repeated options and any number of
+short and long names per option.  Access it with:
+
+@lisp
+(use-modules (srfi srfi-37))
+@end lisp
+
+@acronym{SRFI}-37 principally provides an @code{option} type and the
+@code{args-fold} function.  To use the library, create a set of
+options with @code{option} and use it as a specification for invoking
+@code{args-fold}.
+
+Here is an example of a simple argument processor for the typical
+@samp{--version} and @samp{--help} options, which returns a backwards
+list of files given on the command line:
+
+@lisp
+(args-fold (cdr (program-arguments))
+           (let ((display-and-exit-proc
+                  (lambda (msg)
+                    (lambda (opt name arg loads)
+                      (display msg) (quit)))))
+             (list (option '(#\v "version") #f #f
+                           (display-and-exit-proc "Foo version 42.0\n"))
+                   (option '(#\h "help") #f #f
+                           (display-and-exit-proc
+                            "Usage: foo scheme-file ..."))))
+           (lambda (opt name arg loads)
+             (error "Unrecognized option `~A'" name))
+           (lambda (op loads) (cons op loads))
+           '())
+@end lisp
+
+@deffn {Scheme Procedure} option names required-arg? optional-arg? processor
+Return an object that specifies a single kind of program option.
+
+@var{names} is a list of command-line option names, and should consist of
+characters for traditional @code{getopt} short options and strings for
+@code{getopt_long}-style long options.
+
+@var{required-arg?} and @var{optional-arg?} are mutually exclusive;
+one or both must be @code{#f}.  If @var{required-arg?}, the option
+must be followed by an argument on the command line, such as
+@samp{--opt=value} for long options, or an error will be signalled.
+If @var{optional-arg?}, an argument will be taken if available.
+
+@var{processor} is a procedure that takes at least 3 arguments, called
+when @code{args-fold} encounters the option: the containing option
+object, the name used on the command line, and the argument given for
+the option (or @code{#f} if none).  The rest of the arguments are
+@code{args-fold} ``seeds'', and the @var{processor} should return
+seeds as well.
+@end deffn
+
+@deffn {Scheme Procedure} option-names opt
+@deffnx {Scheme Procedure} option-required-arg? opt
+@deffnx {Scheme Procedure} option-optional-arg? opt
+@deffnx {Scheme Procedure} option-processor opt
+Return the specified field of @var{opt}, an option object, as
+described above for @code{option}.
+@end deffn
+
+@deffn {Scheme Procedure} args-fold args options unrecognized-option-proc operand-proc seeds @dots{}
+Process @var{args}, a list of program arguments such as that returned
+by @code{(cdr (program-arguments))}, in order against @var{options}, a
+list of option objects as described above.  All functions called take
+the ``seeds'', or the last multiple-values as multiple arguments,
+starting with @var{seeds}, and must return the new seeds.  Return the
+final seeds.
+
+Call @code{unrecognized-option-proc}, which is like an option object's
+processor, for any options not found in @var{options}.
+
+Call @code{operand-proc} with any items on the command line that are
+not named options.  This includes arguments after @samp{--}.  It is
+called with the argument in question, as well as the seeds.
+@end deffn
+
+
 @node SRFI-39
 @subsection SRFI-39 - Parameters
 @cindex SRFI-39
Index: srfi/Makefile.am
===================================================================
RCS file: /sources/guile/guile/guile-core/srfi/Makefile.am,v
retrieving revision 1.33
diff -u -d -u -r1.33 Makefile.am
--- srfi/Makefile.am	16 Apr 2006 23:18:55 -0000	1.33
+++ srfi/Makefile.am	14 Jul 2007 08:23:01 -0000
@@ -74,6 +74,7 @@
             srfi-26.scm \
             srfi-31.scm \
             srfi-34.scm \
+            srfi-37.scm \
             srfi-39.scm \
             srfi-60.scm
 
Index: test-suite/Makefile.am
===================================================================
RCS file: /sources/guile/guile/guile-core/test-suite/Makefile.am,v
retrieving revision 1.40
diff -u -d -u -r1.40 Makefile.am
--- test-suite/Makefile.am	18 Nov 2006 18:14:55 -0000	1.40
+++ test-suite/Makefile.am	14 Jul 2007 08:23:01 -0000
@@ -76,6 +76,7 @@
 	    tests/srfi-26.test			\
 	    tests/srfi-31.test			\
 	    tests/srfi-34.test			\
+	    tests/srfi-37.test			\
 	    tests/srfi-39.test			\
 	    tests/srfi-60.test			\
 	    tests/srfi-4.test			\
--- /dev/null	2007-07-07 20:25:18.339081312 -0500
+++ srfi/srfi-37.scm	2007-07-13 22:30:41.000000000 -0500
@@ -0,0 +1,225 @@
+;;; srfi-37.scm --- args-fold
+
+;; 	Copyright (C) 2007 Free Software Foundation, Inc.
+;;
+;; This library is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU Lesser General Public
+;; License as published by the Free Software Foundation; either
+;; version 2.1 of the License, or (at your option) any later version.
+;; 
+;; This library is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;; Lesser General Public License for more details.
+;; 
+;; You should have received a copy of the GNU Lesser General Public
+;; License along with this library; if not, write to the Free Software
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+\f
+#! Commentary
+
+To use this module with Guile, use (cdr (program-arguments)) as
+the ARGS argument to `args-fold'.  Here is a short example:
+
+ (args-fold (cdr (program-arguments))
+	    (let ((display-and-exit-proc
+		   (lambda (msg)
+		     (lambda (opt name arg)
+		       (display msg) (quit) (values)))))
+	      (list (option '(#\v "version") #f #f
+			    (display-and-exit-proc "Foo version 42.0\n"))
+		    (option '(#\h "help") #f #f
+			    (display-and-exit-proc
+			     "Usage: foo scheme-file ..."))))
+	    (lambda (opt name arg)
+	      (error "Unrecognized option `~A'" name))
+	    (lambda (op) (load op) (values)))
+!#
+\f
+;;;; Module definition & exports
+(define-module (srfi srfi-37)
+  #:use-module (srfi srfi-9)
+  #:export (option option-names option-required-arg? 
+	    option-optional-arg? option-processor
+	    args-fold))
+
+(cond-expand-provide (current-module) '(srfi-37))
+\f
+;;;; args-fold and periphery procedures
+
+;;; An option as answered by `option'.  `names' is a list of
+;;; characters and strings, representing associated short-options and
+;;; long-options respectively that should use this option's
+;;; `processor' in an `args-fold' call.
+;;;
+;;; `required-arg?' and `optional-arg?' are mutually exclusive
+;;; booleans and indicate whether an argument must be or may be
+;;; provided.  Besides the obvious, this affects semantics of
+;;; short-options, as short-options with a required or optional
+;;; argument cannot be followed by other short options in the same
+;;; program-arguments string, as they will be interpreted collectively
+;;; as the option's argument.
+;;;
+;;; `processor' is called when this option is encountered.  It should
+;;; accept the containing option, the element of `names' (by `equal?')
+;;; encountered, the option's argument (or #f if none), and the seeds
+;;; as variadic arguments, answering the new seeds as values.
+(define-record-type srfi-37:option
+  (option names required-arg? optional-arg? processor)
+  option?
+  (names option-names)
+  (required-arg? option-required-arg?)
+  (optional-arg? option-optional-arg?)
+  (processor option-processor))
+
+(define (error-duplicate-option option-name)
+  (scm-error 'program-error "args-fold"
+	     "Duplicate option name `~A~A'"
+	     (list (if (char? option-name) #\- "--")
+		   option-name)
+	     #f))
+
+(define (build-options-lookup options)
+  "Answer an `equal?' Guile hash-table that maps OPTIONS' names back
+to the containing options, signalling an error if a name is
+encountered more than once."
+  (let ((lookup (make-hash-table (* 2 (length options)))))
+    (for-each
+     (lambda (opt)
+       (for-each (lambda (name)
+		   (let ((assoc (hash-create-handle!
+				 lookup name #f)))
+		     (if (cdr assoc)
+			 (error-duplicate-option (car assoc))
+			 (set-cdr! assoc opt))))
+		 (option-names opt)))
+     options)
+    lookup))
+
+(define (args-fold args options unrecognized-option-proc
+		   operand-proc . seeds)
+  "Answer the results of folding SEEDS as multiple values against the
+program-arguments in ARGS, as decided by the OPTIONS'
+`option-processor's, UNRECOGNIZED-OPTION-PROC, and OPERAND-PROC."
+  (let ((lookup (build-options-lookup options)))
+    ;; I don't like Guile's `error' here
+    (define (error msg . args)
+      (scm-error 'misc-error "args-fold" msg args #f))
+
+    (define (mutate-seeds! procedure . params)
+      (set! seeds (call-with-values
+		      (lambda ()
+			(apply procedure (append params seeds)))
+		    list)))
+
+    ;; Clean up the rest of ARGS, assuming they're all operands.
+    (define (rest-operands)
+      (for-each (lambda (arg) (mutate-seeds! operand-proc arg))
+		args)
+      (set! args '()))
+
+    ;; Call OPT's processor with OPT, NAME, an argument to be decided,
+    ;; and the seeds.  Depending on OPT's *-arg? specification, get
+    ;; the parameter by calling REQ-ARG-PROC or OPT-ARG-PROC thunks;
+    ;; if no argument is allowed, call NO-ARG-PROC thunk.
+    (define (invoke-option-processor
+	     opt name req-arg-proc opt-arg-proc no-arg-proc)
+      (mutate-seeds!
+       (option-processor opt) opt name
+       (cond ((option-required-arg? opt) (req-arg-proc))
+	     ((option-optional-arg? opt) (opt-arg-proc))
+	     (else (no-arg-proc) #f))))
+
+    ;; Compute and answer a short option argument, advancing ARGS as
+    ;; necessary, for the short option whose character is at POSITION
+    ;; in the current ARG.
+    (define (short-option-argument position)
+      (cond ((< (1+ position) (string-length (car args)))
+	     (let ((result (substring (car args) (1+ position))))
+	       (set! args (cdr args))
+	       result))
+	    ((pair? (cdr args))
+	     (let ((result (cadr args)))
+	       (set! args (cddr args))
+	       result))
+	    (else #f)))
+
+    ;; Interpret the short-option at index POSITION in (car ARGS),
+    ;; followed by the remaining short options in (car ARGS).
+    (define (short-option position)
+      (if (>= position (string-length (car args)))
+	  (next-arg)
+	  (let* ((opt-name (string-ref (car args) position))
+		 (option-here (hash-ref lookup opt-name)))
+	    (cond ((not option-here)
+		   (mutate-seeds! unrecognized-option-proc
+				  (option (list opt-name) #f #f
+					  unrecognized-option-proc)
+				  opt-name #f)
+		   (short-option (1+ position)))
+		  (else
+		   (invoke-option-processor
+		    option-here opt-name
+		    (lambda ()
+		      (or (short-option-argument position)
+			  (error "Missing required argument after `-~A'" opt-name)))
+		    (lambda ()
+		      ;; edge case: -xo -zf or -xo -- where opt-name=#\o
+		      ;; GNU getopt_long resolves these like I do
+		      (short-option-argument position))
+		    (lambda () #f))
+		   (if (not (or (option-required-arg? option-here)
+				(option-optional-arg? option-here)))
+		       (short-option (1+ position))))))))
+
+    ;; Process the long option in (car ARGS).  We make the
+    ;; interesting, possibly non-standard assumption that long option
+    ;; names might contain #\=, so keep looking for more #\= in (car
+    ;; ARGS) until we find a named option in lookup.
+    (define (long-option)
+      (let ((arg (car args)))
+	(let place-=-after ((start-pos 2))
+	  (let* ((index (string-index arg #\= start-pos))
+		 (opt-name (substring arg 2 (or index (string-length arg))))
+		 (option-here (hash-ref lookup opt-name)))
+	    (if (not option-here)
+		;; look for a later #\=, unless there can't be one
+		(if index
+		    (place-=-after (1+ index))
+		    (mutate-seeds!
+		     unrecognized-option-proc
+		     (option (list opt-name) #f #f unrecognized-option-proc)
+		     opt-name #f))
+		(invoke-option-processor
+		 option-here opt-name
+		 (lambda ()
+		   (if index
+		       (substring arg (1+ index))
+		       (error "Missing required argument after `--~A'" opt-name)))
+		 (lambda () (and index (substring arg (1+ index))))
+		 (lambda ()
+		   (if index
+		       (error "Extraneous argument after `--~A'" opt-name))))))))
+      (set! args (cdr args)))
+
+    ;; Process the remaining in ARGS.  Basically like calling
+    ;; `args-fold', but without having to regenerate `lookup' and the
+    ;; funcs above.
+    (define (next-arg)
+      (if (null? args)
+	  (apply values seeds)
+	  (let ((arg (car args)))
+	    (cond ((or (not (char=? #\- (string-ref arg 0)))
+		       (= 1 (string-length arg))) ;"-"
+		   (mutate-seeds! operand-proc arg)
+		   (set! args (cdr args)))
+		  ((char=? #\- (string-ref arg 1))
+		   (if (= 2 (string-length arg)) ;"--"
+		       (begin (set! args (cdr args)) (rest-operands))
+		       (long-option)))
+		  (else (short-option 1)))
+	    (next-arg))))
+
+    (next-arg)))
+
+;;; srfi-37.scm ends here
--- /dev/null	2007-07-07 20:25:18.339081312 -0500
+++ test-suite/tests/srfi-37.test	2007-07-13 22:59:42.000000000 -0500
@@ -0,0 +1,97 @@
+;;;; srfi-37.test --- Test suite for SRFI 37 -*- scheme -*-
+;;;;
+;;;; 	Copyright (C) 2007 Free Software Foundation, Inc.
+;;;;
+;;;; This program is free software; you can redistribute it and/or modify
+;;;; it under the terms of the GNU General Public License as published by
+;;;; the Free Software Foundation; either version 2, or (at your option)
+;;;; any later version.
+;;;;
+;;;; This program is distributed in the hope that it will be useful,
+;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;;; GNU General Public License for more details.
+;;;;
+;;;; You should have received a copy of the GNU General Public License
+;;;; along with this software; see the file COPYING.  If not, write to
+;;;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;;;; Boston, MA 02110-1301 USA
+
+(define-module (test-srfi-37)
+  #:use-module (test-suite lib)
+  #:use-module (srfi srfi-37))
+
+(with-test-prefix "SRFI-37"
+
+  (pass-if "empty calls with count-modified seeds"
+    (equal? (list 21 42)
+	    (call-with-values
+		(lambda ()
+		  (args-fold '("1" "3" "4") '()
+			     (lambda (opt name arg seed seed2)
+			       (values 1 2))
+			     (lambda (op seed seed2)
+			       (values (1+ seed) (+ 2 seed2)))
+			     18 36))
+	      list)))
+
+  (pass-if "short opt params"
+    (let ((a-set #f) (b-set #f) (c-val #f) (d-val #f) (no-fail #t) (no-operands #t))
+      (args-fold '("-abcdoit" "-ad" "whatev")
+		 (list (option '(#\a) #f #f (lambda (opt name arg)
+					      (set! a-set #t)
+					      (values)))
+		       (option '(#\b) #f #f (lambda (opt name arg)
+					      (set! b-set #t)
+					      (values)))
+		       (option '("cdoit" #\c) #f #t
+			       (lambda (opt name arg)
+				 (set! c-val arg)
+				 (values)))
+		       (option '(#\d) #f #t
+			       (lambda (opt name arg)
+				 (set! d-val arg)
+				 (values))))
+		 (lambda (opt name arg) (set! no-fail #f) (values))
+		 (lambda (oper) (set! no-operands #f) (values)))
+      (equal? '(#t #t "doit" "whatev" #t #t)
+	      (list a-set b-set c-val d-val no-fail no-operands))))
+
+  (pass-if "single unrecognized long-opt"
+    (equal? "fake"
+	    (args-fold '("--fake" "-i2")
+		       (list (option '(#\i) #t #f
+				     (lambda (opt name arg k) k)))
+		       (lambda (opt name arg k) name)
+		       (lambda (operand k) #f)
+		       #f)))
+
+  (pass-if "long req'd/optional"
+    (equal? '(#f "bsquare" "apple")
+	    (args-fold '("--x=pple" "--y=square" "--y")
+		       (list (option '("x") #t #f
+				     (lambda (opt name arg k)
+				       (cons (string-append "a" arg) k)))
+			     (option '("y") #f #t
+				     (lambda (opt name arg k)
+				       (cons (if arg
+						 (string-append "b" arg)
+						 #f) k))))
+		       (lambda (opt name arg k) #f)
+		       (lambda (opt name arg k) #f)
+		       '())))
+
+  ;; this matches behavior of getopt_long in libc 2.4
+  (pass-if "short options absorb special markers in the next arg"
+    (let ((arg-proc (lambda (opt name arg k)
+		      (acons name arg k))))
+      (equal? '((#\y . "-z") (#\x . "--") (#\z . #f))
+	      (args-fold '("-zx" "--" "-y" "-z" "--")
+			 (list (option '(#\x) #f #t arg-proc)
+			       (option '(#\z) #f #f arg-proc)
+			       (option '(#\y) #t #f arg-proc))
+			 (lambda (opt name arg k) #f)
+			 (lambda (opt name arg k) #f)
+			 '()))))
+
+)


-- 
;;; Stephen Compall ** http://scompall.nocandysw.com/blog **
"Peta" is Greek for fifth; a petabyte is 10 to the fifth power, as
well as fifth in line after kilo, mega, giga, and tera.
  -- Lee Gomes, performing every Wednesday in his tech column
     "Portals" on page B1 of The Wall Street Journal

[-- Attachment #1.2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 189 bytes --]

[-- Attachment #2: Type: text/plain, Size: 143 bytes --]

_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel

^ permalink raw reply	[flat|nested] 2+ messages in thread

* Re: [patch] SRFI-37 support
  2007-07-14  8:31 [patch] SRFI-37 support Stephen Compall
@ 2007-07-18 20:45 ` Ludovic Courtès
  0 siblings, 0 replies; 2+ messages in thread
From: Ludovic Courtès @ 2007-07-18 20:45 UTC (permalink / raw)
  To: guile-devel

Hi,

Stephen Compall <s11@member.fsf.org> writes:

> Here is SRFI-37, args-fold, implemented by me from scratch in Scheme,
> including a manual section and a few tests.  Patch does not include log
> entries, listed immediately below instead:

This is good news, thanks!

I committed it to HEAD (with slight edits to `srfi-37.scm' so that it
doesn't use the SCSH block comment syntax).  I'll apply it to the 1.8
branch as well since I think it's harmless.

> [...] Unlike
> +@code{getopt-long}, it supports repeated options and any number of
> +short and long names per option.

This, especially, has always been a source of annoyance to me.

Thanks,
Ludovic.



_______________________________________________
Guile-devel mailing list
Guile-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/guile-devel


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2007-07-18 20:45 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2007-07-14  8:31 [patch] SRFI-37 support Stephen Compall
2007-07-18 20:45 ` Ludovic Courtès

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).