aboutsummaryrefslogtreecommitdiffstats
path: root/lisp/eshell
diff options
context:
space:
mode:
authorJim Porter2022-03-27 22:28:40 -0700
committerLars Ingebrigtsen2022-05-01 20:41:18 +0200
commitade1424a975aabaa208010c6fdd3c8b7c51242ff (patch)
treef4c2e874ed3b1f5684f6e72e7f41401b9b3bd22c /lisp/eshell
parent788694d026b401715330576633a98542623978ff (diff)
downloademacs-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.
Diffstat (limited to 'lisp/eshell')
-rw-r--r--lisp/eshell/em-pred.el273
1 files changed, 128 insertions, 145 deletions
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:
219EXAMPLES: 219EXAMPLES:
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.
232Each element is of the form (OPEN . CLOSE), where OPEN and CLOSE
233are characters representing the opening and closing delimiter,
234respectively.")
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.
383These are the -/+ characters, corresponding to `<' and `>',
384respectively. If no comparison modifier is at point, return `='.
385
386FUNCTIONS, if non-nil, is a list of comparison functions,
387specified 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.
397If 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.
405If the character after point is a predicate/modifier
406delimiter (see `eshell-pred-delimiter-pairs', read the value of
407the argument and update point to be just after the closing
408delimiter.
409
410If CHAINED-P is true, then another delimited modifier argument
411will immediately follow this one. In this case, when the opening
412and closing delimiters are the same, update point to be just
413before 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) 542If 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)