From 0247fc4187db5c15668deff1ca67a0566031014d Mon Sep 17 00:00:00 2001 From: Jim Porter Date: Sat, 25 Jun 2022 18:19:01 -0700 Subject: [PATCH 2/2] 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). --- etc/NEWS | 6 +++++ lisp/eshell/em-glob.el | 45 +++++++++++++++++++++---------- test/lisp/eshell/em-glob-tests.el | 15 +++++++++-- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index 40658559d7..32a780129c 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1817,6 +1817,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 8acdaee233..58b7a83c09 100644 --- a/lisp/eshell/em-glob.el +++ b/lisp/eshell/em-glob.el @@ -273,17 +273,23 @@ eshell-glob-convert-1 (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 @@ eshell-glob-convert (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 @@ eshell-extended-glob 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 @@ eshell-glob-entries (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 @@ eshell-glob-entries (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 65f340a8da..b733be35d9 100644 --- a/test/lisp/eshell/em-glob-tests.el +++ b/test/lisp/eshell/em-glob-tests.el @@ -60,6 +60,12 @@ em-glob-test/match-any-string (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 @@ em-glob-test/match-recursive (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 @@ em-glob-test/match-recursive-follow-symlinks "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 \"***/\"." -- 2.25.1