From: Jim Porter <jporterbugs@gmail.com>
To: 52999@debbugs.gnu.org
Subject: bug#52999: 29.0.50; [PATCH v2] `eshell-eval-using-options' should follow POSIX/GNU argument conventions
Date: Mon, 3 Jan 2022 21:33:28 -0800 [thread overview]
Message-ID: <8c7a7765-2d40-e685-1d93-c60496a4f626@gmail.com> (raw)
In-Reply-To: <c8c4ffa3-8e6f-38e2-8045-693a1d0cad2d@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 331 bytes --]
On 1/3/2022 5:36 PM, Jim Porter wrote:
> I've attached a patch with tests to fix this and use the POSIX/GNU
> argument conventions, supporting both the above cases.
One small addition here. I just noticed that `eshell-eval-using-options'
already supports "--" to terminate all the options, so I added a test
case for that too.
[-- Attachment #2: 0001-Follow-POSIX-GNU-argument-conventions-for-eshell-eva.patch --]
[-- Type: text/plain, Size: 13705 bytes --]
From de20c52f7669367479c3c4762cb8326f9a2058b3 Mon Sep 17 00:00:00 2001
From: Jim Porter <jporterbugs@gmail.com>
Date: Mon, 3 Jan 2022 21:30:59 -0800
Subject: [PATCH] Follow POSIX/GNU argument conventions for
'eshell-eval-using-options'
* lisp/eshell/esh-opt.el (eshell--split-switch): New function.
(eshell-set-option): Allow setting a supplied value instead of always
consuming from 'eshell--args'.
(eshell--process-option): Support consuming option values specified as
a single token.
(eshell--process-args): For short options, pass full switch token to
'eshell--process-option'.
* test/lisp/eshell/esh-opt-tests.el (esh-opt-process-args-test): Fix
test.
(test-eshell-eval-using-options): Add tests for various types of
options.
* etc/NEWS: Announce the change.
---
etc/NEWS | 3 +
lisp/eshell/esh-opt.el | 90 ++++++++++++------
test/lisp/eshell/esh-opt-tests.el | 149 ++++++++++++++++++++++--------
3 files changed, 174 insertions(+), 68 deletions(-)
diff --git a/etc/NEWS b/etc/NEWS
index ca6a716ccd..b595d99633 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1060,6 +1060,9 @@ dimensions.
Specifying a cons as the from argument allows to start measuring text
from a specified amount of pixels above or below a position.
+---
+** 'eshell-eval-using-options' now follows POSIX/GNU argument syntax conventions.
+
** XDG support
*** New function 'xdg-state-home' returns 'XDG_STATE_HOME' environment variable.
diff --git a/lisp/eshell/esh-opt.el b/lisp/eshell/esh-opt.el
index 7d31845528..fcc35780e9 100644
--- a/lisp/eshell/esh-opt.el
+++ b/lisp/eshell/esh-opt.el
@@ -187,49 +187,82 @@ eshell-show-usage
will be called instead." extcmd)))))
(throw 'eshell-usage usage)))
-(defun eshell--set-option (name ai opt options opt-vals)
+(defun eshell--split-switch (switch kind)
+ "Split SWITCH into its option name and potential value, if any.
+KIND should be the integer 0 if SWITCH is a short option, or 1 if it's
+a long option."
+ (if (eq kind 0)
+ ;; Short option
+ (cons (aref switch 0)
+ (and (> (length switch) 1) (substring switch 1)))
+ ;; Long option
+ (save-match-data
+ (string-match "\\([^=]*\\)\\(?:=\\(.*\\)\\)?" switch)
+ (cons (match-string 1 switch) (match-string 2 switch)))))
+
+(defun eshell--set-option (name ai opt value options opt-vals)
"Using NAME's remaining args (index AI), set the OPT within OPTIONS.
-If the option consumes an argument for its value, the argument list
-will be modified."
+VALUE is the potential value of the OPT, coming from args like
+\"-fVALUE\" or \"--foo=VALUE\", or nil if no value was supplied. If
+OPT doesn't consume a value, return VALUE unchanged so that it can be
+processed later; otherwsie, return nil.
+
+If the OPT consumes an argument for its value and VALUE is nil, the
+argument list will be modified."
(if (not (nth 3 opt))
(eshell-show-usage name options)
- (setcdr (assq (nth 3 opt) opt-vals)
- (if (eq (nth 2 opt) t)
- (if (> ai (length eshell--args))
- (error "%s: missing option argument" name)
- (pop (nthcdr ai eshell--args)))
- (or (nth 2 opt) t)))))
+ (if (eq (nth 2 opt) t)
+ (progn
+ (setcdr (assq (nth 3 opt) opt-vals)
+ (or value
+ (if (> ai (length eshell--args))
+ (error "%s: missing option argument" name)
+ (pop (nthcdr ai eshell--args)))))
+ nil)
+ (setcdr (assq (nth 3 opt) opt-vals)
+ (or (nth 2 opt) t))
+ value)))
(defun eshell--process-option (name switch kind ai options opt-vals)
"For NAME, process SWITCH (of type KIND), from args at index AI.
The SWITCH will be looked up in the set of OPTIONS.
-SWITCH should be either a string or character. KIND should be the
-integer 0 if it's a character, or 1 if it's a string.
-
-The SWITCH is then be matched against OPTIONS. If no matching handler
-is found, and an :external command is defined (and available), it will
-be called; otherwise, an error will be triggered to say that the
-switch is unrecognized."
- (let* ((opts options)
- found)
+SWITCH should be a string starting with the option to process,
+possibly followed by its value, e.g. \"u\" or \"uUSER\". KIND should
+be the integer 0 if it's a short option, or 1 if it's a long option.
+
+The SWITCH is then be matched against OPTIONS. If KIND is 0 and the
+SWITCH matches an option that doesn't take a value, return the
+remaining characters in SWITCH to be processed later as further short
+options.
+
+If no matching handler is found, and an :external command is defined
+(and available), it will be called; otherwise, an error will be
+triggered to say that the switch is unrecognized."
+ (let ((switch (eshell--split-switch switch kind))
+ (opts options)
+ found remaining)
(while opts
(if (and (listp (car opts))
- (nth kind (car opts))
- (equal switch (nth kind (car opts))))
+ (equal (car switch) (nth kind (car opts))))
(progn
- (eshell--set-option name ai (car opts) options opt-vals)
+ (setq remaining (eshell--set-option name ai (car opts)
+ (cdr switch) options opt-vals))
+ (when (and remaining (eq kind 1))
+ (error "%s: option --%s doesn't allow an argument"
+ name (car switch)))
(setq found t opts nil))
(setq opts (cdr opts))))
- (unless found
+ (if found
+ remaining
(let ((extcmd (memq ':external options)))
(when extcmd
(setq extcmd (eshell-search-path (cadr extcmd)))
(if extcmd
(throw 'eshell-ext-command extcmd)
- (error (if (characterp switch) "%s: unrecognized option -%c"
+ (error (if (characterp (car switch)) "%s: unrecognized option -%c"
"%s: unrecognized option --%s")
- name switch)))))))
+ name (car switch))))))))
(defun eshell--process-args (name args options)
"Process the given ARGS using OPTIONS."
@@ -262,12 +295,9 @@ eshell--process-args
(if (> (length switch) 0)
(eshell--process-option name switch 1 ai options opt-vals)
(setq ai (length eshell--args)))
- (let ((len (length switch))
- (index 0))
- (while (< index len)
- (eshell--process-option name (aref switch index)
- 0 ai options opt-vals)
- (setq index (1+ index))))))))
+ (while (> (length switch) 0)
+ (setq switch (eshell--process-option name switch 0
+ ai options opt-vals)))))))
(nconc (mapcar #'cdr opt-vals) eshell--args)))
(provide 'esh-opt)
diff --git a/test/lisp/eshell/esh-opt-tests.el b/test/lisp/eshell/esh-opt-tests.el
index e2a0ea59d1..235e03bced 100644
--- a/test/lisp/eshell/esh-opt-tests.el
+++ b/test/lisp/eshell/esh-opt-tests.el
@@ -57,7 +57,7 @@ esh-opt-process-args-test
'((?u "user" t user "execute a command as another USER")
:parse-leading-options-only))))
(should
- (equal '("world" "emerge")
+ (equal '("DN" "emerge" "world")
(eshell--process-args
"sudo"
'("-u" "root" "emerge" "-uDN" "world")
@@ -65,59 +65,132 @@ esh-opt-process-args-test
(ert-deftest test-eshell-eval-using-options ()
"Tests for `eshell-eval-using-options'."
+ ;; Test short options.
(eshell-eval-using-options
- "sudo" '("-u" "root" "whoami")
- '((?u "user" t user "execute a command as another USER")
- :parse-leading-options-only)
- (should (equal user "root")))
+ "ls" '("-a" "/dev/null")
+ '((?a "all" nil show-all
+ "do not ignore entries starting with ."))
+ (should (eq show-all t))
+ (should (equal args '("/dev/null"))))
(eshell-eval-using-options
- "sudo" '("--user" "root" "whoami")
- '((?u "user" t user "execute a command as another USER")
- :parse-leading-options-only)
- (should (equal user "root")))
+ "ls" '("/dev/null")
+ '((?a "all" nil show-all
+ "do not ignore entries starting with ."))
+ (should (eq show-all nil))
+ (should (equal args '("/dev/null"))))
+ ;; Test long options.
(eshell-eval-using-options
- "sudo" '("emerge" "-uDN" "world")
- '((?u "user" t user "execute a command as another USER"))
- (should (equal user "world")))
+ "ls" '("--all" "/dev/null")
+ '((?a "all" nil show-all
+ "do not ignore entries starting with ."))
+ (should (eq show-all t))
+ (should (equal args '("/dev/null"))))
+
+ ;; Test options with constant values.
(eshell-eval-using-options
- "sudo" '("emerge" "-uDN" "world")
- '((?u "user" t user "execute a command as another USER")
- :parse-leading-options-only)
- (should (eq user nil)))
+ "ls" '("/dev/null" "-h")
+ '((?h "human-readable" 1024 human-readable
+ "print sizes in human readable format"))
+ (should (eql human-readable 1024))
+ (should (equal args '("/dev/null"))))
+ (eshell-eval-using-options
+ "ls" '("/dev/null" "--human-readable")
+ '((?h "human-readable" 1024 human-readable
+ "print sizes in human readable format"))
+ (should (eql human-readable 1024))
+ (should (equal args '("/dev/null"))))
+ (eshell-eval-using-options
+ "ls" '("/dev/null")
+ '((?h "human-readable" 1024 human-readable
+ "print sizes in human readable format"))
+ (should (eq human-readable nil))
+ (should (equal args '("/dev/null"))))
+ ;; Test options with user-specified values.
(eshell-eval-using-options
"ls" '("-I" "*.txt" "/dev/null")
'((?I "ignore" t ignore-pattern
- "do not list implied entries matching pattern"))
- (should (equal ignore-pattern "*.txt")))
+ "do not list implied entries matching pattern"))
+ (should (equal ignore-pattern "*.txt"))
+ (should (equal args '("/dev/null"))))
+ (eshell-eval-using-options
+ "ls" '("-I*.txt" "/dev/null")
+ '((?I "ignore" t ignore-pattern
+ "do not list implied entries matching pattern"))
+ (should (equal ignore-pattern "*.txt"))
+ (should (equal args '("/dev/null"))))
+ (eshell-eval-using-options
+ "ls" '("--ignore" "*.txt" "/dev/null")
+ '((?I "ignore" t ignore-pattern
+ "do not list implied entries matching pattern"))
+ (should (equal ignore-pattern "*.txt"))
+ (should (equal args '("/dev/null"))))
+ (eshell-eval-using-options
+ "ls" '("--ignore=*.txt" "/dev/null")
+ '((?I "ignore" t ignore-pattern
+ "do not list implied entries matching pattern"))
+ (should (equal ignore-pattern "*.txt"))
+ (should (equal args '("/dev/null"))))
+ ;; Test multiple short options in a single token.
(eshell-eval-using-options
- "ls" '("-l" "/dev/null")
- '((?l nil long-listing listing-style
- "use a long listing format"))
- (should (eql listing-style 'long-listing)))
+ "ls" '("-al" "/dev/null")
+ '((?a "all" nil show-all
+ "do not ignore entries starting with .")
+ (?l nil long-listing listing-style
+ "use a long listing format"))
+ (should (eq t show-all))
+ (should (eql listing-style 'long-listing))
+ (should (equal args '("/dev/null"))))
(eshell-eval-using-options
- "ls" '("/dev/null")
- '((?l nil long-listing listing-style
- "use a long listing format"))
- (should (eq listing-style nil)))
+ "ls" '("-aI*.txt" "/dev/null")
+ '((?a "all" nil show-all
+ "do not ignore entries starting with .")
+ (?I "ignore" t ignore-pattern
+ "do not list implied entries matching pattern"))
+ (should (eq t show-all))
+ (should (equal ignore-pattern "*.txt"))
+ (should (equal args '("/dev/null"))))
+ ;; Test that "--" terminates options.
(eshell-eval-using-options
- "ls" '("/dev/null" "-h")
- '((?h "human-readable" 1024 human-readable
- "print sizes in human readable format"))
- (should (eql human-readable 1024)))
+ "ls" '("--" "-a")
+ '((?a "all" nil show-all
+ "do not ignore entries starting with ."))
+ (should (eq show-all nil))
+ (should (equal args '("-a"))))
(eshell-eval-using-options
- "ls" '("/dev/null" "--human-readable")
- '((?h "human-readable" 1024 human-readable
- "print sizes in human readable format"))
- (should (eql human-readable 1024)))
+ "ls" '("--" "--all")
+ '((?a "all" nil show-all
+ "do not ignore entries starting with ."))
+ (should (eq show-all nil))
+ (should (equal args '("--all"))))
+
+ ;; Test :parse-leading-options-only.
(eshell-eval-using-options
- "ls" '("/dev/null")
- '((?h "human-readable" 1024 human-readable
- "print sizes in human readable format"))
- (should (eq human-readable nil))))
+ "sudo" '("-u" "root" "whoami")
+ '((?u "user" t user "execute a command as another USER")
+ :parse-leading-options-only)
+ (should (equal user "root"))
+ (should (equal args '("whoami"))))
+ (eshell-eval-using-options
+ "sudo" '("--user" "root" "whoami")
+ '((?u "user" t user "execute a command as another USER")
+ :parse-leading-options-only)
+ (should (equal user "root"))
+ (should (equal args '("whoami"))))
+ (eshell-eval-using-options
+ "sudo" '("emerge" "-uDN" "world")
+ '((?u "user" t user "execute a command as another USER"))
+ (should (equal user "DN"))
+ (should (equal args '("emerge" "world"))))
+ (eshell-eval-using-options
+ "sudo" '("emerge" "-uDN" "world")
+ '((?u "user" t user "execute a command as another USER")
+ :parse-leading-options-only)
+ (should (eq user nil))
+ (should (equal args '("emerge" "-uDN" "world")))))
(provide 'esh-opt-tests)
--
2.25.1
next prev parent reply other threads:[~2022-01-04 5:33 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-01-04 1:36 bug#52999: 29.0.50; [PATCH] `eshell-eval-using-options' should follow POSIX/GNU argument conventions Jim Porter
2022-01-04 5:33 ` Jim Porter [this message]
2022-01-04 13:01 ` bug#52999: 29.0.50; [PATCH v2] " Eli Zaretskii
2022-01-04 21:09 ` bug#52999: 29.0.50; [PATCH v3] " Jim Porter
2022-01-05 14:50 ` Eli Zaretskii
2022-01-06 0:48 ` Jim Porter
2022-01-06 12:31 ` Eli Zaretskii
2022-01-08 19:58 ` Jim Porter
2022-01-08 20:10 ` Eli Zaretskii
2022-01-12 15:00 ` Eli Zaretskii
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.gnu.org/software/emacs/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=8c7a7765-2d40-e685-1d93-c60496a4f626@gmail.com \
--to=jporterbugs@gmail.com \
--cc=52999@debbugs.gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs.git
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).