summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJim Porter <jporterbugs@gmail.com>2022-06-25 18:19:01 -0700
committerLars Ingebrigtsen <larsi@gnus.org>2022-06-26 16:52:38 +0200
commit7fc3f1b0d14ad390ca361a40ecf02eaa9f1b202a (patch)
treec2e0e4641a6ead3b72a8f0d5534299833cf6d568
parentea3681575f24ab6766931d0c86f080c52f2ce2d7 (diff)
downloademacs-7fc3f1b0d14ad390ca361a40ecf02eaa9f1b202a.tar.gz
Make Eshell globs ending in "/" match directories only
* lisp/eshell/em-glob.el (eshell-glob-convert): Return whether to match directories only. (eshell-glob-entries): Add ONLY-DIRS argument. * test/lisp/eshell/em-glob-tests.el (em-glob-test/match-any-directory): New test. (em-glob-test/match-recursive) (em-glob-test/match-recursive-follow-symlinks): Add test cases for when "**/" or "***/" are the last components in a glob. * etc/NEWS: Announce this change (bug#56227).
-rw-r--r--etc/NEWS6
-rw-r--r--lisp/eshell/em-glob.el45
-rw-r--r--test/lisp/eshell/em-glob-tests.el15
3 files changed, 50 insertions, 16 deletions
diff --git a/etc/NEWS b/etc/NEWS
index 4e091e5a146..c85e8e02565 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1849,6 +1849,12 @@ values passed as a single token, such as '-oVALUE' or
'eshell-eval-using-options' macro. See "Defining new built-in
commands" in the "(eshell) Built-ins" node of the Eshell manual.
+---
+*** Eshell globs ending with '/' now match only directories.
+Additionally, globs ending with '**/' or '***/' no longer raise an
+error, and now expand to all directories recursively (following
+symlinks in the latter case).
+
** Shell
---
diff --git a/lisp/eshell/em-glob.el b/lisp/eshell/em-glob.el
index 8acdaee2331..58b7a83c091 100644
--- a/lisp/eshell/em-glob.el
+++ b/lisp/eshell/em-glob.el
@@ -273,17 +273,23 @@ include, and the second for ones to exclude."
(defun eshell-glob-convert (glob)
"Convert an Eshell glob-pattern GLOB to regexps.
-The result is a list, where the first element is the base
-directory to search in, and the second is a list containing
-elements of the following forms:
+The result is a list of three elements:
-* Regexp pairs as generated by `eshell-glob-convert-1'.
+1. The base directory to search in.
-* `recurse', indicating that searches should recurse into
- subdirectories.
+2. A list containing elements of the following forms:
-* `recurse-symlink', like `recurse', but also following symlinks."
+ * Regexp pairs as generated by `eshell-glob-convert-1'.
+
+ * `recurse', indicating that searches should recurse into
+ subdirectories.
+
+ * `recurse-symlink', like `recurse', but also following
+ symlinks.
+
+3. A boolean indicating whether to match directories only."
(let ((globs (eshell-split-path glob))
+ (isdir (eq (aref glob (1- (length glob))) ?/))
start-dir result last-saw-recursion)
(if (and (cdr globs)
(file-name-absolute-p (car globs)))
@@ -302,7 +308,8 @@ elements of the following forms:
(setq last-saw-recursion nil))
(setq globs (cdr globs)))
(list (file-name-as-directory start-dir)
- (nreverse result))))
+ (nreverse result)
+ isdir)))
(defun eshell-extended-glob (glob)
"Return a list of files matched by GLOB.
@@ -331,17 +338,21 @@ regular expressions, and these cannot support the above constructs."
glob))))
;; FIXME does this really need to abuse eshell-glob-matches, message-shown?
-(defun eshell-glob-entries (path globs)
+(defun eshell-glob-entries (path globs only-dirs)
"Match the entries in PATH against GLOBS.
GLOBS is a list of globs as converted by `eshell-glob-convert',
-which see."
+which see.
+
+If ONLY-DIRS is non-nil, only match directories; otherwise, match
+directories and files."
(let* ((entries (ignore-errors
(file-name-all-completions "" path)))
(case-fold-search eshell-glob-case-insensitive)
glob glob-remainder recurse-p)
(if (rassq (car globs) eshell-glob-recursive-alist)
(setq recurse-p (car globs)
- glob (cadr globs)
+ glob (or (cadr globs)
+ (eshell-glob-convert-1 "*" t))
glob-remainder (cddr globs))
(setq glob (car globs)
glob-remainder (cdr globs)))
@@ -363,7 +374,13 @@ which see."
(if glob-remainder
(when isdir
(push (concat path name) dirs))
- (push (concat path name) eshell-glob-matches)))
+ (when (or (not only-dirs)
+ (and isdir
+ (not (and (eq recurse-p 'recurse)
+ (file-symlink-p
+ (directory-file-name
+ (concat path name)))))))
+ (push (concat path name) eshell-glob-matches))))
(when (and recurse-p isdir
(not (member name '("./" "../")))
(setq pathname (concat path name))
@@ -372,9 +389,9 @@ which see."
(directory-file-name pathname)))))
(push pathname rdirs))))
(dolist (dir (nreverse dirs))
- (eshell-glob-entries dir glob-remainder))
+ (eshell-glob-entries dir glob-remainder only-dirs))
(dolist (rdir (nreverse rdirs))
- (eshell-glob-entries rdir globs)))))
+ (eshell-glob-entries rdir globs only-dirs)))))
(provide 'em-glob)
diff --git a/test/lisp/eshell/em-glob-tests.el b/test/lisp/eshell/em-glob-tests.el
index 65f340a8dad..b733be35d9a 100644
--- a/test/lisp/eshell/em-glob-tests.el
+++ b/test/lisp/eshell/em-glob-tests.el
@@ -60,6 +60,12 @@ component ending in \"symlink\" is treated as a symbolic link."
(should (equal (eshell-extended-glob "*.el")
'("a.el" "b.el")))))
+(ert-deftest em-glob-test/match-any-directory ()
+ "Test that \"*/\" pattern matches any directory."
+ (with-fake-files '("a.el" "b.el" "dir/a.el" "dir/sub/a.el" "symlink/")
+ (should (equal (eshell-extended-glob "*/")
+ '("dir/" "symlink/")))))
+
(ert-deftest em-glob-test/match-any-character ()
"Test that \"?\" pattern matches any character."
(with-fake-files '("a.el" "b.el" "ccc.el" "d.txt" "dir/a.el")
@@ -71,7 +77,9 @@ component ending in \"symlink\" is treated as a symbolic link."
(with-fake-files '("a.el" "b.el" "ccc.el" "d.txt" "dir/a.el" "dir/sub/a.el"
"dir/symlink/a.el" "symlink/a.el" "symlink/sub/a.el")
(should (equal (eshell-extended-glob "**/a.el")
- '("a.el" "dir/a.el" "dir/sub/a.el")))))
+ '("a.el" "dir/a.el" "dir/sub/a.el")))
+ (should (equal (eshell-extended-glob "**/")
+ '("dir/" "dir/sub/")))))
(ert-deftest em-glob-test/match-recursive-follow-symlinks ()
"Test that \"***/\" recursively matches directories, following symlinks."
@@ -79,7 +87,10 @@ component ending in \"symlink\" is treated as a symbolic link."
"dir/symlink/a.el" "symlink/a.el" "symlink/sub/a.el")
(should (equal (eshell-extended-glob "***/a.el")
'("a.el" "dir/a.el" "dir/sub/a.el" "dir/symlink/a.el"
- "symlink/a.el" "symlink/sub/a.el")))))
+ "symlink/a.el" "symlink/sub/a.el")))
+ (should (equal (eshell-extended-glob "***/")
+ '("dir/" "dir/sub/" "dir/symlink/" "symlink/"
+ "symlink/sub/")))))
(ert-deftest em-glob-test/match-recursive-mixed ()
"Test combination of \"**/\" and \"***/\"."