GNU bug report logs - #40720
[PATCH 3/4] (ice-9 getopt-long): substantially re-written to pass all the new tests

Please note: This is a static page, with minimal formatting, updated once a day.
Click here to see this page with the latest information and nicer formatting.

Package: guile; Reported by: Dale Mellor <guile-qf1qmg@HIDDEN>; Keywords: patch; merged with #40719, #40721, #40722, #40723; Done: Dale Mellor <guile-qf1qmg@HIDDEN>; Maintainer for guile is bug-guile@HIDDEN.
bug closed, send any further explanations to 40719 <at> debbugs.gnu.org and Dale Mellor <guile-qf1qmg@HIDDEN> Request was from Dale Mellor <guile-qf1qmg@HIDDEN> to control <at> debbugs.gnu.org. Full text available.
Merged 40719 40720 40721 40722 40723. Request was from Ludovic Courtès <ludo@HIDDEN> to control <at> debbugs.gnu.org. Full text available.
Merged 40719 40720 40721 40722. Request was from Ludovic Courtès <ludo@HIDDEN> to control <at> debbugs.gnu.org. Full text available.
Merged 40719 40720 40721. Request was from Ludovic Courtès <ludo@HIDDEN> to control <at> debbugs.gnu.org. Full text available.
Merged 40719 40720. Request was from Ludovic Courtès <ludo@HIDDEN> to control <at> debbugs.gnu.org. Full text available.

Message received at submit <at> debbugs.gnu.org:


Received: (at submit) by debbugs.gnu.org; 19 Apr 2020 19:01:12 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Apr 19 15:01:12 2020
Received: from localhost ([127.0.0.1]:45966 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1jQFBn-0005xG-6n
	for submit <at> debbugs.gnu.org; Sun, 19 Apr 2020 15:01:12 -0400
Received: from lists.gnu.org ([209.51.188.17]:54057)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <guile-qf1qmg@HIDDEN>) id 1jQEVZ-0004oX-Ux
 for submit <at> debbugs.gnu.org; Sun, 19 Apr 2020 14:17:34 -0400
Received: from eggs.gnu.org ([2001:470:142:3::10]:34058 helo=eggs1p.gnu.org)
 by lists.gnu.org with esmtp (Exim 4.90_1)
 (envelope-from <guile-qf1qmg@HIDDEN>) id 1jQEVW-0005qX-4r
 for bug-guile@HIDDEN; Sun, 19 Apr 2020 14:17:33 -0400
X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on eggs.gnu.org
X-Spam-Level: **
X-Spam-Status: No, score=2.2 required=5.0 tests=RDNS_DYNAMIC, SPF_HELO_SOFTFAIL,
 SPF_SOFTFAIL,URIBL_BLOCKED autolearn=no autolearn_force=no
 version=3.4.2
Received: from Debian-exim by eggs1p.gnu.org with spam-scanned (Exim 4.90_1)
 (envelope-from <guile-qf1qmg@HIDDEN>) id 1jQEVU-0006Re-SU
 for bug-guile@HIDDEN; Sun, 19 Apr 2020 14:17:29 -0400
Received: from ec2-52-19-174-175.eu-west-1.compute.amazonaws.com
 ([52.19.174.175]:45786 helo=rdmp.org)
 by eggs1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <guile-qf1qmg@HIDDEN>)
 id 1jQEVU-0006MR-68
 for bug-guile@HIDDEN; Sun, 19 Apr 2020 14:17:28 -0400
Received: from [127.0.0.1] (helo=localhost) by rdmp.org with esmtp (Exim 4.92)
 (envelope-from <guile-qf1qmg@HIDDEN>) id 1jQE2i-0002Xh-RT
 for bug-guile@HIDDEN; Sun, 19 Apr 2020 17:47:44 +0000
Message-ID: <667a7c8b9c36092a78446d1bee85a8d37edf90aa.camel@HIDDEN>
Subject: [PATCH 3/4] (ice-9 getopt-long):  substantially re-written to pass
 all the new tests
From: Dale Mellor <guile-qf1qmg@HIDDEN>
To: bug-guile@HIDDEN
Date: Sun, 19 Apr 2020 18:47:44 +0100
Organization: DM Bespoke Computer Solutions Ltd
Content-Type: text/plain; charset="UTF-8"
User-Agent: Evolution 3.30.5-1.1 
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Received-SPF: softfail client-ip=52.19.174.175;
 envelope-from=guile-qf1qmg@HIDDEN; helo=rdmp.org
X-detected-operating-system: by eggs1p.gnu.org: Genre and OS details not
 recognized.
X-Received-From: 52.19.174.175
X-Spam-Score: 0.3 (/)
X-Debbugs-Envelope-To: submit
X-Mailman-Approved-At: Sun, 19 Apr 2020 15:01:09 -0400
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -0.7 (/)

From 7d169c24c0fdbbaa56c646985dd2861b12e2bca5 Mon Sep 17 00:00:00 2001
From: Dale Mellor <guile-qf1qmg@HIDDEN>
Date: Sun, 19 Apr 2020 18:00:48 +0100
Subject: [PATCH 3/4] (ice-9 getopt-long):  substantially re-written to pass all the new tests

All of the original tests also still pass.  Also the entire guile build
actually depends on the correct functioning of this module, so we can be
quite confident that nothing has been broken.

* module/ice-9/getopt-long.scm: Substantially re-written.
---
 module/ice-9/getopt-long.scm | 476 +++++++++++++++++++++++++----------
 1 file changed, 339 insertions(+), 137 deletions(-)

diff --git a/module/ice-9/getopt-long.scm b/module/ice-9/getopt-long.scm
index 14eaf8e23..4c920cbe5 100644
--- a/module/ice-9/getopt-long.scm
+++ b/module/ice-9/getopt-long.scm
@@ -158,12 +158,17 @@
 
 (define-module (ice-9 getopt-long)
   #:use-module ((ice-9 common-list) #:select (remove-if-not))
+  #:use-module (ice-9 control)
   #:use-module (srfi srfi-9)
   #:use-module (ice-9 match)
   #:use-module (ice-9 regex)
   #:use-module (ice-9 optargs)
+  #:use-module (ice-9 receive)
   #:export (getopt-long option-ref))
 
+;;  Code makes more sense to human beings with this.
+(define  return  values)
+
 (define %program-name (make-fluid "guile"))
 (define (program-name)
   (fluid-ref %program-name))
@@ -175,18 +180,13 @@
   (exit 1))
 
 (define-record-type option-spec
-  (%make-option-spec name required? option-spec->single-char predicate value-policy)
+  (%make-option-spec name required? single-char predicate value-policy)
   option-spec?
-  (name
-   option-spec->name set-option-spec-name!)
-  (required?
-   option-spec->required? set-option-spec-required?!)
-  (option-spec->single-char
-   option-spec->single-char set-option-spec-single-char!)
-  (predicate
-   option-spec->predicate set-option-spec-predicate!)
-  (value-policy
-   option-spec->value-policy set-option-spec-value-policy!))
+  (name         option-spec->name)
+  (required?    option-spec->required?    set-option-spec-required?!)
+  (single-char  option-spec->single-char  set-option-spec-single-char!)
+  (predicate    option-spec->predicate    set-option-spec-predicate!)
+  (value-policy option-spec->value-policy set-option-spec-value-policy!))
 
 (define (make-option-spec name)
   (%make-option-spec name #f #f #f #f))
@@ -195,116 +195,331 @@
   (let ((spec (make-option-spec (symbol->string (car desc)))))
     (for-each (match-lambda
                (('required? val)
-                (set-option-spec-required?! spec val))
+                    (set-option-spec-required?! spec val))
                (('value val)
-                (set-option-spec-value-policy! spec val))
+                    (set-option-spec-value-policy! spec val))
                (('single-char val)
-                (or (char? val)
-                    (error "`single-char' value must be a char!"))
-                (set-option-spec-single-char! spec val))
+                    (unless (char? val)
+                        (fatal-error "‘single-char’ value must be a char!"))
+                    (set-option-spec-single-char! spec val))
                (('predicate pred)
-                (set-option-spec-predicate!
-                 spec (lambda (name val)
-                        (or (not val)
-                            (pred val)
-                            (fatal-error "option predicate failed: --~a"
-                                         name)))))
+                    (set-option-spec-predicate! spec pred))
                ((prop val)
-                (error "invalid getopt-long option property:" prop)))
+                    (fatal-error "invalid getopt-long option property:" prop)))
               (cdr desc))
     spec))
 
-(define (split-arg-list argument-list)
-  ;; Scan ARGUMENT-LIST for "--" and return (BEFORE-LS . AFTER-LS).
-  ;; Discard the "--".  If no "--" is found, AFTER-LS is empty.
-  (let loop ((yes '()) (no argument-list))
-    (cond ((null? no)               (cons (reverse yes) no))
-	  ((string=? "--" (car no)) (cons (reverse yes) (cdr no)))
-	  (else (loop (cons (car no) yes) (cdr no))))))
-
-(define short-opt-rx           (make-regexp "^-([a-zA-Z]+)(.*)"))
-(define long-opt-no-value-rx   (make-regexp "^--([^=]+)$"))
-(define long-opt-with-value-rx (make-regexp "^--([^=]+)=(.*)"))
-
-(define (looks-like-an-option string)
-  (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 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
-  ;; options nor their values.
-  (let ((idx (map (lambda (spec)
-                    (cons (option-spec->name spec) spec))
-                  specs))
-        (sc-idx (map (lambda (spec)
-                       (cons (make-string 1 (option-spec->single-char spec))
-                             spec))
-                     (remove-if-not option-spec->single-char specs))))
-    (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)))))))))
+
+;; Extract the name of a long option given that it may or may not be
+;; surrounded by '--' and '=...'.
+(define isolate-long-name-re (make-regexp "^-*([^=]+)"))
+
+(define (isolate-long-name name)
+          (cond ((regexp-exec isolate-long-name-re name)
+                        => (λ (match)  (match:substring match 1)))
+                (else #f)))
+
+
+;;  Whatever the presentation of the long option, make sure it is in a
+;;  clean, normalized form (but this does NOT account for any value the
+;;  option might have).
+(define (re-present option)
+         (string-append "--" (isolate-long-name option) "="))
+
+
+;;  The /name/ passed in here must be a string with just the characters
+;;  of the option name in it.  The return is the spec with that name, or
+;;  #f if such cannot be found.
+(define (find-spec-long-name-clear  specs  name)
+          (cond ((null? specs) #f)
+                ((string=? (option-spec->name (car specs)) name) (car specs))
+                (else (find-spec-long-name-clear (cdr specs) name))))
+  
+
+;;  The /name/ can take the form of a long option entry on the command
+;;  line, with whatever decoration that entails.  Will return #f if a
+;;  spec does not exist for this named option.
+(define (find-spec-long  specs  name)
+  (cond ((isolate-long-name  name)
+                => (λ (name) (find-spec-long-name-clear  specs  name)))
+        (else #f)))
+
+
+;;  Return #f if a spec with the short /letter/ name does not exist.
+(define (find-spec-short  specs  letter)
+  (cond ((null? specs) #f)
+        ((eq? (option-spec->single-char (car specs)) letter) (car specs))
+        (else (find-spec-short (cdr specs) letter))))
+
+
+;;  Return the long name (string) of a short option (char).
+(define (short->long  specs  letter)
+  (cond ((find-spec-short  specs  letter)  =>  option-spec->name)
+        (else  (string letter))))
+
+
+;;  Take, for example, /short/='-h' to '--help='.
+(define (double-up  short  specs)
+  (string-append "--" (short->long specs (string-ref short 1)) "="))
+
+
+;;  Can't believe this is not already in Guile, but return a boolean
+;;  indicating if /a/ is a character of the English alphabet.  This
+;;  should probably be more locale-specific.
+(define  char-rx  (make-regexp "[a-zA-Z]"))
+(define  (is-alpha  a)  (regexp-exec  char-rx  (string a)))
+
+
+;;  This procedure does whatever is necessary to put the (ostensibly)
+;;  first item on the command-line into the canonical (normal) form
+;;  '--item=value'; this may mean consuming the next item of the
+;;  command-line (the first item of /rest/) to get the value.  Note that
+;;  the value may be missing, but the '=' sign will always be there in
+;;  the return.  The first item (/A/) will always be more than two
+;;  characters, and the first two characters will be "--", i.e. we are
+;;  processing a long option.
+;;
+;;  A          IN   string               The first argument on the command-line
+;;  rest       IN   list of strings      The remaining items of the command-line
+;;  specs      IN   list of option-spec  Options specification
+;;  remnant    OUT  list of strings      The unprocessed command line
+;;  processed  OUT  string               New command-line argument
+(define (normalize-long-option  A  rest  specs)
+  (define (return-empty-arg)  (return  rest  (re-present A)))
+  (define (return-arg-with-value)
+              (return  (cdr rest)  (string-append (re-present A) (car rest))))
+  (cond
+   ((string-index A #\=)
+         ;; The argument is already in the canonical form.
+         (return rest A))
+   ((null? rest)
+         ;; There are no more arguments to be had, so present an empty
+         ;; value.
+         (return-empty-arg))
+   ((find-spec-long specs A)
+         ;; There is an option spec for this argument; we must use the
+         ;; /value-policy/ and /predicate/ members to decide whether or
+         ;; not to take the following argument from the command-line as
+         ;; the value of the option.
+         => (λ (spec)
+              (cond
+               ((option-spec->predicate spec)
+                     => (λ (pred) (if (pred (car rest))
+                                           (return-arg-with-value)
+                                           (return-empty-arg))))
+               (else (cond ((eq? (option-spec->value-policy spec) 'optional)
+                                (if  (eq? (string-ref (car rest) 0) #\-)
+                                        (return-empty-arg)
+                                        (return-arg-with-value)))
+                           ((and (eq? (option-spec->value-policy spec) #t)
+                                 (or (string->number (car rest))
+                                     (not (eq? (string-ref (car rest) 0) #\-))))
+                                    (return-arg-with-value))
+                           (else    (return-empty-arg)))))))
+   (else
+         ;; We know nothing about this option, abort operations.
+         (fatal-error "no such option: --~a" (isolate-long-name A)))))
+
+
+
+;;  This procedure does whatever is necessary to put the (ostensibly)
+;;  first item on the command-line into the canonical form
+;;  '--item=value'; this may mean consuming the next item of the
+;;  command-line (the first item of /rest/) to get the value.  Note that
+;;  the value may be missing, but the '=' sign will always be there in
+;;  the return.  The first item (/A/) will always be exactly two
+;;  characters, and the first character will be "-", i.e. we are
+;;  processing an isolated short option.
+;;
+;;  A          IN   string               The first argument on the command-line
+;;  rest       IN   list of strings      The remaining items of the command-line
+;;  specs      IN   list of option-spec  Options specification
+;;  remnant    OUT  list of strings      The unprocessed command line
+;;  processed  OUT  string               New command-line argument
+(define (normalize-free-short-option  A  rest  specs)
+  (define  (return-empty-arg)  (return rest (double-up A specs)))
+  (define  (return-arg-with-next-value)
+                     (return (cdr rest)
+                             (string-append (double-up A specs) (car rest))))
+  (let*  ((name  (string-ref  A  1))
+          (spec  (find-spec-short  specs  name)))
+    (unless  (is-alpha name)  (return rest A))
+    (unless  spec  (fatal-error "no such option: -~a" name))
+    (cond ((null? rest)  (return-empty-arg))
+          ((option-spec->predicate spec)
+                         => (λ (pred)  (if  (pred (car rest))
+                                                 (return-arg-with-next-value)
+                                                 (return-empty-arg))))
+          ((eq? (option-spec->value-policy spec) #f)
+                        (return-empty-arg))
+          ((eq? (option-spec->value-policy spec) 'optional)
+                        (if (eq? (string-ref (car rest) 0) #\-)
+                            (return-empty-arg)
+                            (return-arg-with-next-value)))
+          (else         (return-arg-with-next-value)))))
+
+
+
+;; The /sequence/ is a string of characters from the command line, and
+;; the task is to decide if those characters, after a '-' sign, are a
+;; viable clumped option sequence, possibly using some of the trailing
+;; characters as option values, or not.
+(define  (viable-short  sequence  specs)
+  (cond ((eq?  0  (string-length sequence))  #t)
+        ((find-spec-short  specs  (string-ref sequence 0))
+              ;; If this optionʼs /value-policy/ allows the option to
+              ;; take a value then this string is viable as the
+              ;; remainder can be taken as that value.  Otherwise we
+              ;; must assert the viability of the rest of the line by
+              ;; recursion.
+              => (λ (spec)  (or (not (eq? #f (option-spec->value-policy spec)))
+                                (viable-short (substring sequence 1) specs))))
+        (else  #f)))
+
+
+
+;;  This procedure does whatever is necessary to put the (ostensibly)
+;;  first item on the command-line into the canonical form
+;;  '--item=value'.  Note that the value may be missing, but the '='
+;;  sign will always be there in the return.  The first item (/A/) will
+;;  always be *more* than two characters, and the first character will
+;;  be "-", i.e. we are processing a short option which is either
+;;  clumped with other short options, or is clumped with its value.
+;;
+;;  A          IN   string               The first argument on the command-line
+;;  rest       IN   list of strings      The remaining items of the command-line
+;;  specs      IN   list of option-spec  Options specification
+;;  remnant    OUT  list of strings      The unprocessed command line
+;;  processed  OUT  string               New command-line argument
+(define (normalize-clumped-short-option  A  rest  specs)
+  (define  (declump-arg)  (return (cons* (string-append "-" (substring A 1 2))
+                                         (string-append "-" (substring A 2))
+                                         rest)
+                                  #f))
+  (define  (construct-arg-from-clumped-value)
+                            (return rest  (string-append (double-up A specs)
+                                                         (substring A 2))))
+  (unless  (is-alpha (string-ref A 1))  (return rest A))
+  (let ((spec  (find-spec-short  specs  (string-ref  A  1))))
+    (unless  spec  (fatal-error "no such option: -~a" (string-ref A 1)))
+    (cond ((option-spec->predicate spec)
+                   => (λ (pred)  (if (pred (substring A 2))
+                                         (construct-arg-from-clumped-value)
+                                         (declump-arg))))
+          ((eq? (option-spec->value-policy spec) 'optional)
+                   (if (viable-short  (substring A 2)  specs)
+                       (declump-arg)
+                       (construct-arg-from-clumped-value)))
+          ((eq? (option-spec->value-policy spec) #f)   (declump-arg))
+          (else    (construct-arg-from-clumped-value)))))
+
+
+
+;;  Return a version of the command-line /args/ in which all options are
+;;  represented in long form with an equals sign (whether they have a
+;;  value or not).
+(define  (normalize  args  specs  stop-at-first-non-option)
+  (call/ec  (λ (return)
+    (let loop  ((args args) (processed '()))
+      (when  (null? args)   (return  (reverse processed)))
+      (apply  loop  (call/ec  (λ (loop)
+        (define A  (car args))
+        (define  (when-loop  cond  normalizer)
+               (when cond
+                 (receive (remainder-args processed-arg)
+                          (normalizer  A  (cdr args)  specs)
+                          (loop (list remainder-args
+                                      (if processed-arg
+                                          (cons processed-arg processed)
+                                          processed))))))
+        (when (string=? "--" A)
+            (return  (append  (reverse processed)  args)))
+        (when-loop  (and (> (string-length A) 2)
+                         (string=? (substring A 0 2) "--"))
+                    normalize-long-option)
+        (when-loop  (and (eq? (string-length A) 2)
+                         (eq? (string-ref A 0) #\-))
+                    normalize-free-short-option)
+        (when-loop  (and (> (string-length A) 1)
+                         (eq? (string-ref A 0) #\-))
+                    normalize-clumped-short-option)
+        (when stop-at-first-non-option
+            (return  (append (reverse processed) args)))
+        ;else
+          (loop  (list (cdr args)  (cons A processed))))))))))
+         
+
+
+;;  Check that all the rules inherent in the /specs/ are fulfilled by
+;;  the /options/.
+(define (verify-specs-fullfilled  specs  options)
+  (for-each
+     (λ (spec)
+       (let* ((name (option-spec->name spec))
+              (value (assq-ref options (string->symbol name))))
+         (when (and (option-spec->required? spec) (not value))
+           (fatal-error "option must be specified: --~a" name))
+         (let ((policy (option-spec->value-policy spec)))
+           (when (and (eq? policy #t) (eq? value  #t))
+             (fatal-error "option must be specified with argument: --~a" name))
+           (when (and (eq? policy #f) (string? value))
+             (fatal-error "option does not support argument: --~a" name)))
+         (let  ((pred (option-spec->predicate spec)))
+           (when (and pred (string? value) (not (pred value)))
+             (fatal-error "option predicate failed: --~a" name)))))
+     specs))
+
+
+
+;;  Check that all the options are matched by a specification.
+(define  (verify-options  options  specs)
+  (for-each
+   (λ (value)
+     (unless (or (null? (car value))
+                 (find-spec-long specs (symbol->string (car value))))
+       (fatal-error "no such option: --~a" (car value))))
+   options))
+
+
+
+;;  This procedure will simply return if the options and the specs
+;;  conform with each other, or else will bail out with an error
+;;  message.
+(define  (check-compliance  options  specs)
+  (verify-specs-fullfilled  specs  options)
+  (verify-options  options  specs))
+
+
+
+(define  full-option-re  (make-regexp "^--([^=]+)=(.+)?$"))
+
+;; The /normal-args/ are a normalized command line in which all
+;; options are expressed long-form, and the task here is to construct an
+;; /options/ object: an associative array of option names onto values
+;; (or #t if there is no value).
+(define  (extract-options  normal-args  stop-at-first-non-option)
+  (let  loop  ((args        normal-args)
+               (options     '())
+               (non-options '()))
+     (cond
+      ((null? args)  (acons '() (reverse non-options) options))
+      (else
+       (cond
+        ((string=? (car args) "--")
+            (acons '() (append (reverse non-options) (cdr args)) options))
+        ((regexp-exec  full-option-re  (car args))
+            => (λ (match)
+                   (loop (cdr args)
+                         (acons (string->symbol (match:substring match 1))
+                                (or (match:substring match 2) #t)
+                                options)
+                         non-options)))
+        (stop-at-first-non-option
+            (acons '() (append (reverse non-options) args) options))
+        (else
+         (loop  (cdr args)  options  (cons (car args) non-options))))))))
+
+
 
 (define* (getopt-long program-arguments option-desc-list
                       #:key stop-at-first-non-option)
@@ -339,29 +554,16 @@ 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."
   (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))))
+    (let* ((specs   (map parse-option-spec option-desc-list))
+           (options  (extract-options
+                          (normalize (cdr program-arguments)
+                                     specs
+                                     stop-at-first-non-option)
+                          stop-at-first-non-option)))
+      (check-compliance  options  specs)
+      options)))
+
+
 
 (define (option-ref options key default)
   "Return value in alist OPTIONS using KEY, a symbol; or DEFAULT if not found.
-- 
2.20.1






Acknowledgement sent to Dale Mellor <guile-qf1qmg@HIDDEN>:
New bug report received and forwarded. Copy sent to bug-guile@HIDDEN. Full text available.
Report forwarded to bug-guile@HIDDEN:
bug#40720; Package guile. Full text available.
Please note: This is a static page, with minimal formatting, updated once a day.
Click here to see this page with the latest information and nicer formatting.
Last modified: Sun, 2 Aug 2020 10:45:02 UTC

GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997 nCipher Corporation Ltd, 1994-97 Ian Jackson.