diff options
| author | Jim Porter | 2022-03-27 22:28:40 -0700 |
|---|---|---|
| committer | Lars Ingebrigtsen | 2022-05-01 20:41:18 +0200 |
| commit | ade1424a975aabaa208010c6fdd3c8b7c51242ff (patch) | |
| tree | f4c2e874ed3b1f5684f6e72e7f41401b9b3bd22c | |
| parent | 788694d026b401715330576633a98542623978ff (diff) | |
| download | emacs-ade1424a975aabaa208010c6fdd3c8b7c51242ff.tar.gz emacs-ade1424a975aabaa208010c6fdd3c8b7c51242ff.zip | |
Use a common set of string delimiters for all Eshell predicates/modifiers
* lisp/eshell/em-pred.el (eshell-pred-delimiter-pairs): New variable.
(eshell-get-comparison-modifier-argument)
(eshell-get-numeric-modifier-argument)
(eshell-get-delimited-modifier-argument): New functions...
(eshell-pred-user-or-group, eshell-pred-file-time)
(eshell-pred-file-links, eshell-pred-file-size)
(eshell-pred-substitute, eshell-join-memebers, eshell-split-members):
... and use them here.
(eshell-include-members): Pass 'mod-char' and use
'eshell-get-delimited-modifier-argument'.
(eshell-pred-file-type, eshell-pred-file-mode): Use 'when-let'.
(eshell-modifier-alist): Pass modifier char to
'eshell-include-members'.
* test/lisp/eshell/em-pred-tests.el
(em-pred-test/predicate-delimiters): New test.
(em-pred-test/predicate-uid, em-pred-test/predicate-gid,
em-pred-test/modifier-include, em-pred-test/modifier-exclude): Remove
cases covered by 'em-pred-test/predicate-delimiters'.
(em-pred-test/modifier-substitute): Add test cases for new delimiter
styles.
* doc/misc/eshell.texi (Argument Predication and Modification):
Explain how string parameters are delimited.
(Argument Modifiers): Document some special delimiter behavior with
the 's/PATTERN/REPLACE/' modifier (bug#55204).
* etc/NEWS: Announce this change, and move the
'eshell-eval-using-options' entry to the Eshell section.
| -rw-r--r-- | doc/misc/eshell.texi | 12 | ||||
| -rw-r--r-- | etc/NEWS | 26 | ||||
| -rw-r--r-- | lisp/eshell/em-pred.el | 273 | ||||
| -rw-r--r-- | test/lisp/eshell/em-pred-tests.el | 33 |
4 files changed, 180 insertions, 164 deletions
diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi index e5392061665..a3ed922cf2c 100644 --- a/doc/misc/eshell.texi +++ b/doc/misc/eshell.texi | |||
| @@ -1191,6 +1191,13 @@ or modifiers. For example, @samp{*(.)} expands to all regular files | |||
| 1191 | in the current directory and @samp{*(^@@:U^u0)} expands to all | 1191 | in the current directory and @samp{*(^@@:U^u0)} expands to all |
| 1192 | non-symlinks not owned by @code{root}, upper-cased. | 1192 | non-symlinks not owned by @code{root}, upper-cased. |
| 1193 | 1193 | ||
| 1194 | Some predicates and modifiers accept string parameters, such as | ||
| 1195 | @samp{*(u'@var{user}')}, which matches all files owned by @var{user}. | ||
| 1196 | These parameters must be surrounded by delimiters; you can use any of | ||
| 1197 | the following pairs of delimiters: @code{" @dots{} "}, @code{' @dots{} | ||
| 1198 | '}, @code{/ @dots{} /}, @code{| @dots{} |}, @code{( @dots{} )}, | ||
| 1199 | @code{[ @dots{} ]}, @code{< @dots{} >}, or @code{@{ @dots{} @}}. | ||
| 1200 | |||
| 1194 | You can customize the syntax and behavior of predicates and modifiers | 1201 | You can customize the syntax and behavior of predicates and modifiers |
| 1195 | in Eshell via the Customize group ``eshell-pred'' (@pxref{Easy | 1202 | in Eshell via the Customize group ``eshell-pred'' (@pxref{Easy |
| 1196 | Customization, , , emacs, The GNU Emacs Manual}). | 1203 | Customization, , , emacs, The GNU Emacs Manual}). |
| @@ -1379,6 +1386,11 @@ meaning. | |||
| 1379 | Replaces the first instance of the regular expression @var{pattern} | 1386 | Replaces the first instance of the regular expression @var{pattern} |
| 1380 | with @var{replace}. Signals an error if no match is found. | 1387 | with @var{replace}. Signals an error if no match is found. |
| 1381 | 1388 | ||
| 1389 | As with other modifiers taking string parameters, you can use | ||
| 1390 | different delimiters to separate @var{pattern} and @var{replace}, such | ||
| 1391 | as @samp{s'@dots{}'@dots{}'}, @samp{s[@dots{}][@dots{}]}, or even | ||
| 1392 | @samp{s[@dots{}]/@dots{}/}. | ||
| 1393 | |||
| 1382 | @item gs/@var{pattern}/@var{replace}/ | 1394 | @item gs/@var{pattern}/@var{replace}/ |
| 1383 | Replaces all instances of the regular expression @var{pattern} with | 1395 | Replaces all instances of the regular expression @var{pattern} with |
| 1384 | @var{replace}. | 1396 | @var{replace}. |
| @@ -174,11 +174,22 @@ files that were compiled with an old EIEIO (Emacs<25). | |||
| 174 | ** 'C-x 8 .' has been moved to 'C-x 8 . .'. | 174 | ** 'C-x 8 .' has been moved to 'C-x 8 . .'. |
| 175 | This is to open up the 'C-x 8 .' map to bind further characters there. | 175 | This is to open up the 'C-x 8 .' map to bind further characters there. |
| 176 | 176 | ||
| 177 | ** Eshell | ||
| 178 | |||
| 177 | --- | 179 | --- |
| 178 | ** 'source' and '.' in Eshell no longer accept the '--help' option. | 180 | *** 'source' and '.' no longer accept the '--help' option. |
| 179 | This is for compatibility with the shell versions of these commands, | 181 | This is for compatibility with the shell versions of these commands, |
| 180 | which don't handle options like '--help' in any special way. | 182 | which don't handle options like '--help' in any special way. |
| 181 | 183 | ||
| 184 | +++ | ||
| 185 | *** String delimiters in argument predicates/modifiers are more restricted. | ||
| 186 | Previously, some argument predicates/modifiers allowed arbitrary | ||
| 187 | characters as string delimiters. To provide more unified behavior | ||
| 188 | across all predicates/modifiers, the list of allowed delimiters has | ||
| 189 | been restricted to "...", '...', /.../, |...|, (...), [...], <...>, | ||
| 190 | and {...}. See the "(eshell) Argument Predication and Modification" | ||
| 191 | node in the Eshell manual for more details. | ||
| 192 | |||
| 182 | --- | 193 | --- |
| 183 | ** The 'delete-forward-char' command now deletes by grapheme clusters. | 194 | ** The 'delete-forward-char' command now deletes by grapheme clusters. |
| 184 | This command is by default bound to the <Delete> function key | 195 | This command is by default bound to the <Delete> function key |
| @@ -1354,6 +1365,14 @@ Lisp function. This frees you from having to keep track of whether | |||
| 1354 | commands are Lisp function or external when supplying absolute file | 1365 | commands are Lisp function or external when supplying absolute file |
| 1355 | name arguments. See "Electric forward slash" in the Eshell manual. | 1366 | name arguments. See "Electric forward slash" in the Eshell manual. |
| 1356 | 1367 | ||
| 1368 | --- | ||
| 1369 | *** Built-in Eshell commands now follow POSIX/GNU argument syntax conventions. | ||
| 1370 | Built-in commands in Eshell now accept command-line options with | ||
| 1371 | values passed as a single token, such as '-oVALUE' or | ||
| 1372 | '--option=VALUE'. New commands can take advantage of this with the | ||
| 1373 | 'eshell-eval-using-options' macro. See "Defining new built-in | ||
| 1374 | commands" in the "(eshell) Built-ins" node of the Eshell manual. | ||
| 1375 | |||
| 1357 | ** Calc | 1376 | ** Calc |
| 1358 | 1377 | ||
| 1359 | +++ | 1378 | +++ |
| @@ -1937,11 +1956,6 @@ dimensions. | |||
| 1937 | Specifying a cons as the FROM argument allows to start measuring text | 1956 | Specifying a cons as the FROM argument allows to start measuring text |
| 1938 | from a specified amount of pixels above or below a position. | 1957 | from a specified amount of pixels above or below a position. |
| 1939 | 1958 | ||
| 1940 | --- | ||
| 1941 | ** 'eshell-eval-using-options' now follows argument syntax conventions. | ||
| 1942 | Built-in commands in Eshell now accept command-line options with | ||
| 1943 | values passed as a single token, such as '-oVALUE' or '--option=VALUE'. | ||
| 1944 | |||
| 1945 | ** XDG support | 1959 | ** XDG support |
| 1946 | 1960 | ||
| 1947 | --- | 1961 | --- |
diff --git a/lisp/eshell/em-pred.el b/lisp/eshell/em-pred.el index eb5109b82dc..594563554d2 100644 --- a/lisp/eshell/em-pred.el +++ b/lisp/eshell/em-pred.el | |||
| @@ -116,8 +116,8 @@ The format of each entry is | |||
| 116 | (?U . (lambda (lst) (mapcar #'upcase lst))) | 116 | (?U . (lambda (lst) (mapcar #'upcase lst))) |
| 117 | (?C . (lambda (lst) (mapcar #'capitalize lst))) | 117 | (?C . (lambda (lst) (mapcar #'capitalize lst))) |
| 118 | (?h . (lambda (lst) (mapcar #'file-name-directory lst))) | 118 | (?h . (lambda (lst) (mapcar #'file-name-directory lst))) |
| 119 | (?i . (eshell-include-members)) | 119 | (?i . (eshell-include-members ?i)) |
| 120 | (?x . (eshell-include-members t)) | 120 | (?x . (eshell-include-members ?x t)) |
| 121 | (?r . (lambda (lst) (mapcar #'file-name-sans-extension lst))) | 121 | (?r . (lambda (lst) (mapcar #'file-name-sans-extension lst))) |
| 122 | (?e . (lambda (lst) (mapcar #'file-name-extension lst))) | 122 | (?e . (lambda (lst) (mapcar #'file-name-extension lst))) |
| 123 | (?t . (lambda (lst) (mapcar #'file-name-nondirectory lst))) | 123 | (?t . (lambda (lst) (mapcar #'file-name-nondirectory lst))) |
| @@ -219,6 +219,20 @@ FOR LISTS OF ARGUMENTS: | |||
| 219 | EXAMPLES: | 219 | EXAMPLES: |
| 220 | *.c(:o) sorted list of .c files") | 220 | *.c(:o) sorted list of .c files") |
| 221 | 221 | ||
| 222 | (defvar eshell-pred-delimiter-pairs | ||
| 223 | '((?\( . ?\)) | ||
| 224 | (?\[ . ?\]) | ||
| 225 | (?\< . ?\>) | ||
| 226 | (?\{ . ?\}) | ||
| 227 | (?\' . ?\') | ||
| 228 | (?\" . ?\") | ||
| 229 | (?/ . ?/) | ||
| 230 | (?| . ?|)) | ||
| 231 | "A list of delimiter pairs that can be used in argument predicates/modifiers. | ||
| 232 | Each element is of the form (OPEN . CLOSE), where OPEN and CLOSE | ||
| 233 | are characters representing the opening and closing delimiter, | ||
| 234 | respectively.") | ||
| 235 | |||
| 222 | (defvar-keymap eshell-pred-mode-map | 236 | (defvar-keymap eshell-pred-mode-map |
| 223 | "C-c M-q" #'eshell-display-predicate-help | 237 | "C-c M-q" #'eshell-display-predicate-help |
| 224 | "C-c M-m" #'eshell-display-modifier-help) | 238 | "C-c M-m" #'eshell-display-modifier-help) |
| @@ -364,38 +378,68 @@ resultant list of strings." | |||
| 364 | (lambda (file) (funcall pred (file-truename file)))))) | 378 | (lambda (file) (funcall pred (file-truename file)))))) |
| 365 | (cons pred funcs)) | 379 | (cons pred funcs)) |
| 366 | 380 | ||
| 381 | (defun eshell-get-comparison-modifier-argument (&optional functions) | ||
| 382 | "Starting at point, get the comparison modifier argument, if any. | ||
| 383 | These are the -/+ characters, corresponding to `<' and `>', | ||
| 384 | respectively. If no comparison modifier is at point, return `='. | ||
| 385 | |||
| 386 | FUNCTIONS, if non-nil, is a list of comparison functions, | ||
| 387 | specified as (LESS-THAN GREATER-THAN EQUAL-TO)." | ||
| 388 | (let ((functions (or functions (list #'< #'> #'=)))) | ||
| 389 | (if (memq (char-after) '(?- ?+)) | ||
| 390 | (prog1 | ||
| 391 | (if (eq (char-after) ?-) (nth 0 functions) (nth 1 functions)) | ||
| 392 | (forward-char)) | ||
| 393 | (nth 2 functions)))) | ||
| 394 | |||
| 395 | (defun eshell-get-numeric-modifier-argument () | ||
| 396 | "Starting at point, get the numeric modifier argument, if any. | ||
| 397 | If a number is found, update point to just after the number." | ||
| 398 | (when (looking-at "[0-9]+") | ||
| 399 | (prog1 | ||
| 400 | (string-to-number (match-string 0)) | ||
| 401 | (goto-char (match-end 0))))) | ||
| 402 | |||
| 403 | (defun eshell-get-delimited-modifier-argument (&optional chained-p) | ||
| 404 | "Starting at point, get the delimited modifier argument, if any. | ||
| 405 | If the character after point is a predicate/modifier | ||
| 406 | delimiter (see `eshell-pred-delimiter-pairs', read the value of | ||
| 407 | the argument and update point to be just after the closing | ||
| 408 | delimiter. | ||
| 409 | |||
| 410 | If CHAINED-P is true, then another delimited modifier argument | ||
| 411 | will immediately follow this one. In this case, when the opening | ||
| 412 | and closing delimiters are the same, update point to be just | ||
| 413 | before the closing delimiter. This allows modifiers like | ||
| 414 | `:s/match/repl' to work as expected." | ||
| 415 | (when-let* ((open (char-after)) | ||
| 416 | (close (cdr (assoc open eshell-pred-delimiter-pairs))) | ||
| 417 | (end (eshell-find-delimiter open close nil nil t))) | ||
| 418 | (prog1 | ||
| 419 | (buffer-substring-no-properties (1+ (point)) end) | ||
| 420 | (goto-char (if (and chained-p (eq open close)) | ||
| 421 | end | ||
| 422 | (1+ end)))))) | ||
| 423 | |||
| 367 | (defun eshell-pred-user-or-group (mod-char mod-type attr-index get-id-func) | 424 | (defun eshell-pred-user-or-group (mod-char mod-type attr-index get-id-func) |
| 368 | "Return a predicate to test whether a file match a given user/group id." | 425 | "Return a predicate to test whether a file match a given user/group id." |
| 369 | (let (ugid open close end) | 426 | (let ((ugid (eshell-get-numeric-modifier-argument))) |
| 370 | (if (looking-at "[0-9]+") | 427 | (unless ugid |
| 371 | (progn | 428 | (let ((ugname (or (eshell-get-delimited-modifier-argument) |
| 372 | (setq ugid (string-to-number (match-string 0))) | 429 | (error "Malformed %s name string for modifier `%c'" |
| 373 | (goto-char (match-end 0))) | 430 | mod-type mod-char)))) |
| 374 | (setq open (char-after)) | 431 | (setq ugid (funcall get-id-func ugname)))) |
| 375 | (if (setq close (memq open '(?\( ?\[ ?\< ?\{))) | ||
| 376 | (setq close (car (last '(?\) ?\] ?\> ?\}) | ||
| 377 | (length close)))) | ||
| 378 | (setq close open)) | ||
| 379 | (forward-char) | ||
| 380 | (setq end (eshell-find-delimiter open close)) | ||
| 381 | (unless end | ||
| 382 | (error "Malformed %s name string for modifier `%c'" | ||
| 383 | mod-type mod-char)) | ||
| 384 | (setq ugid | ||
| 385 | (funcall get-id-func (buffer-substring (point) end))) | ||
| 386 | (goto-char (1+ end))) | ||
| 387 | (unless ugid | 432 | (unless ugid |
| 388 | (error "Unknown %s name specified for modifier `%c'" | 433 | (error "Unknown %s name specified for modifier `%c'" |
| 389 | mod-type mod-char)) | 434 | mod-type mod-char)) |
| 390 | (lambda (file) | 435 | (lambda (file) |
| 391 | (let ((attrs (file-attributes file))) | 436 | (when-let ((attrs (file-attributes file))) |
| 392 | (if attrs | 437 | (= (nth attr-index attrs) ugid))))) |
| 393 | (= (nth attr-index attrs) ugid)))))) | ||
| 394 | 438 | ||
| 395 | (defun eshell-pred-file-time (mod-char mod-type attr-index) | 439 | (defun eshell-pred-file-time (mod-char mod-type attr-index) |
| 396 | "Return a predicate to test whether a file matches a certain time." | 440 | "Return a predicate to test whether a file matches a certain time." |
| 397 | (let* ((quantum 86400) | 441 | (let* ((quantum 86400) |
| 398 | qual when open close end) | 442 | qual when) |
| 399 | (when (memq (char-after) '(?M ?w ?h ?m ?s)) | 443 | (when (memq (char-after) '(?M ?w ?h ?m ?s)) |
| 400 | (setq quantum (char-after)) | 444 | (setq quantum (char-after)) |
| 401 | (cond | 445 | (cond |
| @@ -410,36 +454,21 @@ resultant list of strings." | |||
| 410 | ((eq quantum ?s) | 454 | ((eq quantum ?s) |
| 411 | (setq quantum 1))) | 455 | (setq quantum 1))) |
| 412 | (forward-char)) | 456 | (forward-char)) |
| 413 | (when (memq (char-after) '(?+ ?-)) | 457 | (setq qual (eshell-get-comparison-modifier-argument |
| 414 | (setq qual (char-after)) | 458 | (list #'time-less-p |
| 415 | (forward-char)) | 459 | (lambda (a b) (time-less-p b a)) |
| 416 | (if (looking-at "[0-9]+") | 460 | #'time-equal-p))) |
| 417 | (progn | 461 | (if-let ((number (eshell-get-numeric-modifier-argument))) |
| 418 | (setq when (time-since (* (string-to-number (match-string 0)) | 462 | (setq when (time-since (* number quantum))) |
| 419 | quantum))) | 463 | (let* ((file (or (eshell-get-delimited-modifier-argument) |
| 420 | (goto-char (match-end 0))) | 464 | (error "Malformed %s time modifier `%c'" |
| 421 | (setq open (char-after)) | 465 | mod-type mod-char))) |
| 422 | (if (setq close (memq open '(?\( ?\[ ?\< ?\{))) | 466 | (attrs (or (file-attributes file) |
| 423 | (setq close (car (last '(?\) ?\] ?\> ?\}) | 467 | (error "Cannot stat file `%s'" file)))) |
| 424 | (length close)))) | 468 | (setq when (nth attr-index attrs)))) |
| 425 | (setq close open)) | 469 | (lambda (file) |
| 426 | (forward-char) | 470 | (when-let ((attrs (file-attributes file))) |
| 427 | (setq end (eshell-find-delimiter open close)) | 471 | (funcall qual when (nth attr-index attrs)))))) |
| 428 | (unless end | ||
| 429 | (error "Malformed %s time modifier `%c'" mod-type mod-char)) | ||
| 430 | (let* ((file (buffer-substring (point) end)) | ||
| 431 | (attrs (file-attributes file))) | ||
| 432 | (unless attrs | ||
| 433 | (error "Cannot stat file `%s'" file)) | ||
| 434 | (setq when (nth attr-index attrs))) | ||
| 435 | (goto-char (1+ end))) | ||
| 436 | (let ((f (cond ((eq qual ?-) #'time-less-p) | ||
| 437 | ((eq qual ?+) (lambda (a b) (time-less-p b a))) | ||
| 438 | (#'time-equal-p)))) | ||
| 439 | (lambda (file) | ||
| 440 | (let ((attrs (file-attributes file))) | ||
| 441 | (if attrs | ||
| 442 | (funcall f when (nth attr-index attrs)))))))) | ||
| 443 | 472 | ||
| 444 | (defun eshell-pred-file-type (type) | 473 | (defun eshell-pred-file-type (type) |
| 445 | "Return a test which tests that the file is of a certain TYPE. | 474 | "Return a test which tests that the file is of a certain TYPE. |
| @@ -454,36 +483,23 @@ that `ls -l' will show in the first column of its display." | |||
| 454 | '(?b ?c) | 483 | '(?b ?c) |
| 455 | (list type)))) | 484 | (list type)))) |
| 456 | (lambda (file) | 485 | (lambda (file) |
| 457 | (let ((attrs (eshell-file-attributes (directory-file-name file)))) | 486 | (when-let ((attrs (eshell-file-attributes (directory-file-name file)))) |
| 458 | (if attrs | 487 | (memq (aref (file-attribute-modes attrs) 0) set))))) |
| 459 | (memq (aref (file-attribute-modes attrs) 0) set)))))) | ||
| 460 | 488 | ||
| 461 | (defsubst eshell-pred-file-mode (mode) | 489 | (defsubst eshell-pred-file-mode (mode) |
| 462 | "Return a test which tests that MODE pertains to the file." | 490 | "Return a test which tests that MODE pertains to the file." |
| 463 | (lambda (file) | 491 | (lambda (file) |
| 464 | (let ((modes (file-modes file 'nofollow))) | 492 | (when-let ((modes (file-modes file 'nofollow))) |
| 465 | (if modes | 493 | (not (zerop (logand mode modes)))))) |
| 466 | (not (zerop (logand mode modes))))))) | ||
| 467 | 494 | ||
| 468 | (defun eshell-pred-file-links () | 495 | (defun eshell-pred-file-links () |
| 469 | "Return a predicate to test whether a file has a given number of links." | 496 | "Return a predicate to test whether a file has a given number of links." |
| 470 | (let (qual amount) | 497 | (let ((qual (eshell-get-comparison-modifier-argument)) |
| 471 | (when (memq (char-after) '(?- ?+)) | 498 | (amount (or (eshell-get-numeric-modifier-argument) |
| 472 | (setq qual (char-after)) | 499 | (error "Invalid file link count modifier `l'")))) |
| 473 | (forward-char)) | 500 | (lambda (file) |
| 474 | (unless (looking-at "[0-9]+") | 501 | (when-let ((attrs (eshell-file-attributes file))) |
| 475 | (error "Invalid file link count modifier `l'")) | 502 | (funcall qual (file-attribute-link-number attrs) amount))))) |
| 476 | (setq amount (string-to-number (match-string 0))) | ||
| 477 | (goto-char (match-end 0)) | ||
| 478 | (let ((f (if (eq qual ?-) | ||
| 479 | #'< | ||
| 480 | (if (eq qual ?+) | ||
| 481 | #'> | ||
| 482 | #'=)))) | ||
| 483 | (lambda (file) | ||
| 484 | (let ((attrs (eshell-file-attributes file))) | ||
| 485 | (if attrs | ||
| 486 | (funcall f (file-attribute-link-number attrs) amount))))))) | ||
| 487 | 503 | ||
| 488 | (defun eshell-pred-file-size () | 504 | (defun eshell-pred-file-size () |
| 489 | "Return a predicate to test whether a file is of a given size." | 505 | "Return a predicate to test whether a file is of a given size." |
| @@ -498,85 +514,52 @@ that `ls -l' will show in the first column of its display." | |||
| 498 | ((eq qual ?p) | 514 | ((eq qual ?p) |
| 499 | (setq quantum 512))) | 515 | (setq quantum 512))) |
| 500 | (forward-char)) | 516 | (forward-char)) |
| 501 | (when (memq (char-after) '(?- ?+)) | 517 | (setq qual (eshell-get-comparison-modifier-argument)) |
| 502 | (setq qual (char-after)) | 518 | (setq amount (* (or (eshell-get-numeric-modifier-argument) |
| 503 | (forward-char)) | 519 | (error "Invalid file size modifier `L'")) |
| 504 | (unless (looking-at "[0-9]+") | 520 | quantum)) |
| 505 | (error "Invalid file size modifier `L'")) | 521 | (lambda (file) |
| 506 | (setq amount (* (string-to-number (match-string 0)) quantum)) | 522 | (when-let ((attrs (eshell-file-attributes file))) |
| 507 | (goto-char (match-end 0)) | 523 | (funcall qual (file-attribute-size attrs) amount))))) |
| 508 | (let ((f (if (eq qual ?-) | ||
| 509 | #'< | ||
| 510 | (if (eq qual ?+) | ||
| 511 | #'> | ||
| 512 | #'=)))) | ||
| 513 | (lambda (file) | ||
| 514 | (let ((attrs (eshell-file-attributes file))) | ||
| 515 | (if attrs | ||
| 516 | (funcall f (file-attribute-size attrs) amount))))))) | ||
| 517 | 524 | ||
| 518 | (defun eshell-pred-substitute (&optional repeat) | 525 | (defun eshell-pred-substitute (&optional repeat) |
| 519 | "Return a modifier function that will substitute matches." | 526 | "Return a modifier function that will substitute matches." |
| 520 | (let ((delim (char-after)) | 527 | (let* ((match (or (eshell-get-delimited-modifier-argument t) |
| 521 | match replace end) | 528 | (error "Malformed pattern string for modifier `s'"))) |
| 522 | (forward-char) | 529 | (replace (or (eshell-get-delimited-modifier-argument) |
| 523 | (setq end (eshell-find-delimiter delim delim nil nil t) | 530 | (error "Malformed replace string for modifier `s'"))) |
| 524 | match (buffer-substring-no-properties (point) end)) | 531 | (function (if repeat |
| 525 | (goto-char (1+ end)) | 532 | (lambda (str) |
| 526 | (setq end (eshell-find-delimiter delim delim nil nil t) | 533 | (replace-regexp-in-string match replace str t)) |
| 527 | replace (buffer-substring-no-properties (point) end)) | 534 | (lambda (str) |
| 528 | (goto-char (1+ end)) | 535 | (if (string-match match str) |
| 529 | (if repeat | 536 | (replace-match replace t nil str) |
| 530 | (lambda (lst) | 537 | (error (concat str ": substitution failed"))))))) |
| 531 | (mapcar | 538 | (lambda (lst) (mapcar function lst)))) |
| 532 | (lambda (str) | 539 | |
| 533 | (replace-regexp-in-string match replace str t)) | 540 | (defun eshell-include-members (mod-char &optional invert-p) |
| 534 | lst)) | 541 | "Include only Lisp members matching a regexp. |
| 535 | (lambda (lst) | 542 | If INVERT-P is non-nil, include only members not matching a regexp." |
| 536 | (mapcar | 543 | (let* ((regexp (or (eshell-get-delimited-modifier-argument) |
| 537 | (lambda (str) | 544 | (error "Malformed pattern string for modifier `%c'" |
| 538 | (if (string-match match str) | 545 | mod-char))) |
| 539 | (replace-match replace t nil str) | 546 | (predicates |
| 540 | (error (concat str ": substitution failed")))) | 547 | (list (if invert-p |
| 541 | lst))))) | 548 | (lambda (elem) (not (string-match regexp elem))) |
| 542 | 549 | (lambda (elem) (string-match regexp elem)))))) | |
| 543 | (defun eshell-include-members (&optional invert-p) | 550 | (lambda (lst) |
| 544 | "Include only Lisp members matching a regexp." | 551 | (eshell-winnow-list lst nil predicates)))) |
| 545 | (let ((delim (char-after)) | ||
| 546 | regexp end) | ||
| 547 | (forward-char) | ||
| 548 | (setq end (eshell-find-delimiter delim delim nil nil t) | ||
| 549 | regexp (buffer-substring-no-properties (point) end)) | ||
| 550 | (goto-char (1+ end)) | ||
| 551 | (let ((predicates | ||
| 552 | (list (if invert-p | ||
| 553 | (lambda (elem) (not (string-match regexp elem))) | ||
| 554 | (lambda (elem) (string-match regexp elem)))))) | ||
| 555 | (lambda (lst) | ||
| 556 | (eshell-winnow-list lst nil predicates))))) | ||
| 557 | 552 | ||
| 558 | (defun eshell-join-members () | 553 | (defun eshell-join-members () |
| 559 | "Return a modifier function that join matches." | 554 | "Return a modifier function that join matches." |
| 560 | (let ((delim (char-after)) | 555 | (let ((str (or (eshell-get-delimited-modifier-argument) |
| 561 | str end) | 556 | " "))) |
| 562 | (if (not (memq delim '(?' ?/))) | ||
| 563 | (setq str " ") | ||
| 564 | (forward-char) | ||
| 565 | (setq end (eshell-find-delimiter delim delim nil nil t) | ||
| 566 | str (buffer-substring-no-properties (point) end)) | ||
| 567 | (goto-char (1+ end))) | ||
| 568 | (lambda (lst) | 557 | (lambda (lst) |
| 569 | (mapconcat #'identity lst str)))) | 558 | (mapconcat #'identity lst str)))) |
| 570 | 559 | ||
| 571 | (defun eshell-split-members () | 560 | (defun eshell-split-members () |
| 572 | "Return a modifier function that splits members." | 561 | "Return a modifier function that splits members." |
| 573 | (let ((delim (char-after)) | 562 | (let ((sep (eshell-get-delimited-modifier-argument))) |
| 574 | sep end) | ||
| 575 | (when (memq delim '(?' ?/)) | ||
| 576 | (forward-char) | ||
| 577 | (setq end (eshell-find-delimiter delim delim nil nil t) | ||
| 578 | sep (buffer-substring-no-properties (point) end)) | ||
| 579 | (goto-char (1+ end))) | ||
| 580 | (lambda (lst) | 563 | (lambda (lst) |
| 581 | (mapcar | 564 | (mapcar |
| 582 | (lambda (str) | 565 | (lambda (str) |
diff --git a/test/lisp/eshell/em-pred-tests.el b/test/lisp/eshell/em-pred-tests.el index 7f88ac44755..4d2af392923 100644 --- a/test/lisp/eshell/em-pred-tests.el +++ b/test/lisp/eshell/em-pred-tests.el | |||
| @@ -26,6 +26,7 @@ | |||
| 26 | (require 'ert) | 26 | (require 'ert) |
| 27 | (require 'esh-mode) | 27 | (require 'esh-mode) |
| 28 | (require 'eshell) | 28 | (require 'eshell) |
| 29 | (require 'em-pred) | ||
| 29 | 30 | ||
| 30 | (require 'eshell-tests-helpers | 31 | (require 'eshell-tests-helpers |
| 31 | (expand-file-name "eshell-tests-helpers" | 32 | (expand-file-name "eshell-tests-helpers" |
| @@ -254,8 +255,6 @@ read, write, and execute predicates to query the file's modes." | |||
| 254 | (cl-letf (((symbol-function 'eshell-user-id) | 255 | (cl-letf (((symbol-function 'eshell-user-id) |
| 255 | (lambda (name) (seq-position user-names name)))) | 256 | (lambda (name) (seq-position user-names name)))) |
| 256 | (should (equal (eshell-eval-predicate files "u'one'") | 257 | (should (equal (eshell-eval-predicate files "u'one'") |
| 257 | '("/fake/uid=1"))) | ||
| 258 | (should (equal (eshell-eval-predicate files "u{one}") | ||
| 259 | '("/fake/uid=1"))))))) | 258 | '("/fake/uid=1"))))))) |
| 260 | 259 | ||
| 261 | (ert-deftest em-pred-test/predicate-gid () | 260 | (ert-deftest em-pred-test/predicate-gid () |
| @@ -268,8 +267,6 @@ read, write, and execute predicates to query the file's modes." | |||
| 268 | (cl-letf (((symbol-function 'eshell-group-id) | 267 | (cl-letf (((symbol-function 'eshell-group-id) |
| 269 | (lambda (name) (seq-position group-names name)))) | 268 | (lambda (name) (seq-position group-names name)))) |
| 270 | (should (equal (eshell-eval-predicate files "g'one'") | 269 | (should (equal (eshell-eval-predicate files "g'one'") |
| 271 | '("/fake/gid=1"))) | ||
| 272 | (should (equal (eshell-eval-predicate files "g{one}") | ||
| 273 | '("/fake/gid=1"))))))) | 270 | '("/fake/gid=1"))))))) |
| 274 | 271 | ||
| 275 | (defmacro em-pred-test--time-deftest (name file-attribute predicate | 272 | (defmacro em-pred-test--time-deftest (name file-attribute predicate |
| @@ -430,6 +427,8 @@ PREDICATE is the predicate used to query that attribute." | |||
| 430 | "Test that \":s/PAT/REP/\" replaces PAT with REP once." | 427 | "Test that \":s/PAT/REP/\" replaces PAT with REP once." |
| 431 | (should (equal (eshell-eval-predicate "bar" ":s/a/*/") "b*r")) | 428 | (should (equal (eshell-eval-predicate "bar" ":s/a/*/") "b*r")) |
| 432 | (should (equal (eshell-eval-predicate "bar" ":s|a|*|") "b*r")) | 429 | (should (equal (eshell-eval-predicate "bar" ":s|a|*|") "b*r")) |
| 430 | (should (equal (eshell-eval-predicate "bar" ":s{a}{*}") "b*r")) | ||
| 431 | (should (equal (eshell-eval-predicate "bar" ":s{a}'*'") "b*r")) | ||
| 433 | (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":s/[ao]/*/") | 432 | (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":s/[ao]/*/") |
| 434 | '("f*o" "b*r" "b*z"))) | 433 | '("f*o" "b*r" "b*z"))) |
| 435 | (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":s|[ao]|*|") | 434 | (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":s|[ao]|*|") |
| @@ -450,23 +449,15 @@ PREDICATE is the predicate used to query that attribute." | |||
| 450 | (ert-deftest em-pred-test/modifier-include () | 449 | (ert-deftest em-pred-test/modifier-include () |
| 451 | "Test that \":i/PAT/\" filters elements to include only ones matching PAT." | 450 | "Test that \":i/PAT/\" filters elements to include only ones matching PAT." |
| 452 | (should (equal (eshell-eval-predicate "foo" ":i/a/") nil)) | 451 | (should (equal (eshell-eval-predicate "foo" ":i/a/") nil)) |
| 453 | (should (equal (eshell-eval-predicate "foo" ":i|a|") nil)) | ||
| 454 | (should (equal (eshell-eval-predicate "bar" ":i/a/") "bar")) | 452 | (should (equal (eshell-eval-predicate "bar" ":i/a/") "bar")) |
| 455 | (should (equal (eshell-eval-predicate "bar" ":i|a|") "bar")) | ||
| 456 | (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":i/a/") | 453 | (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":i/a/") |
| 457 | '("bar" "baz"))) | ||
| 458 | (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":i|a|") | ||
| 459 | '("bar" "baz")))) | 454 | '("bar" "baz")))) |
| 460 | 455 | ||
| 461 | (ert-deftest em-pred-test/modifier-exclude () | 456 | (ert-deftest em-pred-test/modifier-exclude () |
| 462 | "Test that \":x/PAT/\" filters elements to exclude any matching PAT." | 457 | "Test that \":x/PAT/\" filters elements to exclude any matching PAT." |
| 463 | (should (equal (eshell-eval-predicate "foo" ":x/a/") "foo")) | 458 | (should (equal (eshell-eval-predicate "foo" ":x/a/") "foo")) |
| 464 | (should (equal (eshell-eval-predicate "foo" ":x|a|") "foo")) | ||
| 465 | (should (equal (eshell-eval-predicate "bar" ":x/a/") nil)) | 459 | (should (equal (eshell-eval-predicate "bar" ":x/a/") nil)) |
| 466 | (should (equal (eshell-eval-predicate "bar" ":x|a|") nil)) | ||
| 467 | (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":x/a/") | 460 | (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":x/a/") |
| 468 | '("foo"))) | ||
| 469 | (should (equal (eshell-eval-predicate '("foo" "bar" "baz") ":x|a|") | ||
| 470 | '("foo")))) | 461 | '("foo")))) |
| 471 | 462 | ||
| 472 | (ert-deftest em-pred-test/modifier-split () | 463 | (ert-deftest em-pred-test/modifier-split () |
| @@ -516,7 +507,7 @@ PREDICATE is the predicate used to query that attribute." | |||
| 516 | '("baz" "bar" "foo")))) | 507 | '("baz" "bar" "foo")))) |
| 517 | 508 | ||
| 518 | 509 | ||
| 519 | ;; Combinations | 510 | ;; Miscellaneous |
| 520 | 511 | ||
| 521 | (ert-deftest em-pred-test/combine-predicate-and-modifier () | 512 | (ert-deftest em-pred-test/combine-predicate-and-modifier () |
| 522 | "Test combination of predicates and modifiers." | 513 | "Test combination of predicates and modifiers." |
| @@ -526,4 +517,20 @@ PREDICATE is the predicate used to query that attribute." | |||
| 526 | (should (equal (eshell-eval-predicate files ".:e:u") | 517 | (should (equal (eshell-eval-predicate files ".:e:u") |
| 527 | '("el" "txt")))))) | 518 | '("el" "txt")))))) |
| 528 | 519 | ||
| 520 | (ert-deftest em-pred-test/predicate-delimiters () | ||
| 521 | "Test various delimiter pairs with predicates and modifiers." | ||
| 522 | (dolist (delims eshell-pred-delimiter-pairs) | ||
| 523 | (eshell-with-file-attributes-from-name | ||
| 524 | (let ((files '("/fake/uid=1" "/fake/uid=2")) | ||
| 525 | (user-names '("root" "one" "two"))) | ||
| 526 | (cl-letf (((symbol-function 'eshell-user-id) | ||
| 527 | (lambda (name) (seq-position user-names name)))) | ||
| 528 | (should (equal (eshell-eval-predicate | ||
| 529 | files (format "u%cone%c" (car delims) (cdr delims))) | ||
| 530 | '("/fake/uid=1")))))) | ||
| 531 | (should (equal (eshell-eval-predicate | ||
| 532 | '("foo" "bar" "baz") | ||
| 533 | (format ":j%c-%c" (car delims) (cdr delims))) | ||
| 534 | "foo-bar-baz")))) | ||
| 535 | |||
| 529 | ;; em-pred-tests.el ends here | 536 | ;; em-pred-tests.el ends here |