mirror of
git://git.sv.gnu.org/emacs.git
synced 2026-06-14 12:31:25 +00:00
Add 'apply-lines' and 'map-lines' Eshell commands
* lisp/eshell/esh-worker.el (eshell-map-lines-worker) (eshell-apply-lines-worker): New structs. (eshell-map-lines-worker--apply): New generic function. (eshell-output-object-to-target, eshell-close-target): New methods. (eshell/map-lines, eshell/apply-lines): New functions. * test/lisp/eshell/esh-worker-tests.el (eshell-test-line-number): New defvar... (eshell-test-number): ... use it in this new function. (esh-worker-test/map-lines/eshell-one-line) (esh-worker-test/map-lines/eshell-multiple-lines) (esh-worker-test/map-lines/eshell-multiple-batches) (esh-worker-test/map-lines/external-one-line) (esh-worker-test/map-lines/external-multiple-lines) (esh-worker-test/map-lines/numbers) (esh-worker-test/map-lines/numeric-conversion) (esh-worker-test/apply-lines/eshell-one-line) (esh-worker-test/apply-lines/eshell-multiple-lines) (esh-worker-test/apply-lines/external-one-line) (esh-worker-test/apply-lines/external-multiple-lines) (esh-worker-test/apply-lines/numbers) (esh-worker-test/apply-lines/numeric-conversion): New tests.
This commit is contained in:
parent
751dccb17e
commit
a1cbfe27b1
2 changed files with 219 additions and 0 deletions
|
|
@ -33,6 +33,25 @@
|
|||
;; also express this explicitly, as in:
|
||||
;;
|
||||
;; echo hi | accumulate #'upcase
|
||||
;;
|
||||
;;;_* `map-lines' worker
|
||||
;;
|
||||
;; You can also map each line of output to a function, similar to how
|
||||
;; `mapcar' works. For example, you could "quote" some output for
|
||||
;; pasting into an email like this:
|
||||
;;
|
||||
;; cat some-file.txt | map-lines (lambda (i) (format "> %s" i))
|
||||
;;
|
||||
;; This also shows how you use lambda functions as pipe targets in
|
||||
;; Eshell.
|
||||
;;
|
||||
;;;_* `apply-lines' worker
|
||||
;;
|
||||
;; Finally, you can apply each line of output as successive arguments to
|
||||
;; a function. For example, to sum up a list of numbers written one per
|
||||
;; line:
|
||||
;;
|
||||
;; cat numbers.txt | apply-lines #'+
|
||||
|
||||
;;; Code:
|
||||
|
||||
|
|
@ -187,5 +206,90 @@ this structure only receives a single non-string value as input, it will
|
|||
pass the value unaltered to FUNCTION."
|
||||
(eshell-accumulate-worker-create :function function))
|
||||
|
||||
;; Map-lines worker
|
||||
|
||||
(cl-defstruct (eshell-map-lines-worker
|
||||
(:include eshell-worker)
|
||||
(:constructor eshell-map-lines-worker-create))
|
||||
"A worker that calls a Lisp function once for each line.
|
||||
When outputting string data to this worker, it will call a Lisp
|
||||
FUNCTION once per line of text. When outputting other data types to
|
||||
this worker (e.g. lists), it will call FUNCTION once with the
|
||||
specified value."
|
||||
(function nil :read-only t)
|
||||
(current-line nil))
|
||||
|
||||
(cl-defgeneric eshell-map-lines-worker--apply (line target)
|
||||
(let ((function (eshell-map-lines-worker-function target)))
|
||||
(eshell--apply-print function (list line))))
|
||||
|
||||
(cl-defmethod eshell-output-object-to-target
|
||||
(object (target eshell-map-lines-worker))
|
||||
"Send OBJECT to the map-lines worker TARGET.
|
||||
This calls the function associated with the worker.
|
||||
|
||||
The returned value is the OBJECT in the form that it was actually
|
||||
sent to TARGET (e.g. a string representing OBJECT)."
|
||||
(if (stringp object)
|
||||
(let (line-begin line-end line)
|
||||
;; Prepend any saved text from the current line to the new text.
|
||||
(setq object (concat (eshell-map-lines-worker-current-line target)
|
||||
object))
|
||||
;; Pass each full line of text to FUNCTION.
|
||||
(while (setq line-end (string-search "\n" object line-begin))
|
||||
(setq line (eshell-mark-numeric-string
|
||||
(substring object line-begin line-end))
|
||||
line-begin (1+ line-end))
|
||||
(eshell-map-lines-worker--apply line target))
|
||||
;; Save any remaining text after the last newline for next time.
|
||||
(setf (eshell-map-lines-worker-current-line target)
|
||||
(and (length> object (or line-begin 0))
|
||||
(substring object line-begin))))
|
||||
(eshell-map-lines-worker--apply object target)))
|
||||
|
||||
(cl-defmethod eshell-close-target ((target eshell-map-lines-worker) _status)
|
||||
(when-let* ((last-line (eshell-map-lines-worker-current-line target)))
|
||||
(eshell-map-lines-worker--apply (eshell-mark-numeric-string last-line)
|
||||
target)))
|
||||
|
||||
(defun eshell/map-lines (function)
|
||||
"Map each line of output of another command to FUNCTION.
|
||||
When outputting string data to this worker, it will call FUNCTION once
|
||||
per line of text. When outputting other data types to this
|
||||
worker (e.g. lists), it will call FUNCTION once with the specified
|
||||
value."
|
||||
(eshell-map-lines-worker-create :function function))
|
||||
|
||||
;; Apply-lines worker
|
||||
|
||||
(cl-defstruct
|
||||
(eshell-apply-lines-worker
|
||||
(:include eshell-map-lines-worker)
|
||||
(:constructor eshell-apply-lines-worker-create))
|
||||
"A worker that calls a Lisp function with each line as an argument.
|
||||
This worker calls a Lisp FUNCTION once, with each line of string data
|
||||
corresponding to one argument passed to the fuction. When outputting
|
||||
other data types to this worker (e.g. lists), each object is passed as a
|
||||
single argument to FUNCTION."
|
||||
args) ; Arguments stored in reverse order
|
||||
|
||||
(cl-defmethod eshell-map-lines-worker--apply
|
||||
(line (target eshell-apply-lines-worker))
|
||||
(push line (eshell-apply-lines-worker-args target)))
|
||||
|
||||
(cl-defmethod eshell-close-target ((target eshell-apply-lines-worker) _status)
|
||||
(cl-call-next-method)
|
||||
(let ((function (eshell-map-lines-worker-function target)))
|
||||
(eshell--apply-print
|
||||
function (nreverse (eshell-apply-lines-worker-args target)))))
|
||||
|
||||
(defun eshell/apply-lines (function)
|
||||
"Call a Lisp FUNCTION with each line of output as an argument.
|
||||
This worker calls a Lisp FUNCTION once, with each line of string data
|
||||
corresponding to one argument passed to the fuction. When outputting
|
||||
other data types to this worker (e.g. lists), each object is passed as a
|
||||
single argument to FUNCTION."
|
||||
(eshell-apply-lines-worker-create :function function))
|
||||
|
||||
(provide 'esh-worker)
|
||||
;;; esh-worker.el ends here
|
||||
|
|
|
|||
|
|
@ -26,8 +26,17 @@
|
|||
(require 'eshell-tests-helpers
|
||||
(ert-resource-file "eshell-tests-helpers"))
|
||||
|
||||
(defvar eshell-test-line-number nil)
|
||||
|
||||
(defun eshell-test-number (line)
|
||||
"Return LINE with a line number prepended."
|
||||
(format "%2d %s" (incf eshell-test-line-number) line))
|
||||
|
||||
;;; Tests:
|
||||
|
||||
|
||||
;; Basic worker pipelines
|
||||
|
||||
(ert-deftest esh-worker-test/pipe/eshell-to-function ()
|
||||
"Test that piping an Eshell command to a function works."
|
||||
(with-temp-eshell
|
||||
|
|
@ -81,4 +90,110 @@
|
|||
"echo hi | #'upcase | (lambda (i) (concat \"> \" i))"
|
||||
"\\`> HI\n\\'")))
|
||||
|
||||
|
||||
;; `map-lines' pipelines
|
||||
|
||||
(ert-deftest esh-worker-test/map-lines/eshell-one-line ()
|
||||
"Test that piping a single line to `map-lines' works."
|
||||
(let ((eshell-test-line-number 0))
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output
|
||||
"echo hi | map-lines #'eshell-test-number"
|
||||
"\\` 1 hi\n\\'"))))
|
||||
|
||||
(ert-deftest esh-worker-test/map-lines/eshell-multiple-lines ()
|
||||
"Test that piping multiple lines to `map-lines' works.
|
||||
It should call the mapped function once per line."
|
||||
(let ((eshell-test-line-number 0))
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output
|
||||
"echo 'hi\nbye' | map-lines #'eshell-test-number"
|
||||
"\\` 1 hi\n 2 bye\n\\'"))))
|
||||
|
||||
(ert-deftest esh-worker-test/map-lines/eshell-multiple-batches ()
|
||||
"Test that piping multiple batches of lines to `map-lines' works.
|
||||
It should call the mapped function once per line, reassembling lines as
|
||||
needed."
|
||||
(let ((eshell-test-line-number 0))
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output
|
||||
"{echo 'hi\nhel'; echo 'lo\nhey'} | map-lines #'eshell-test-number"
|
||||
"\\` 1 hi\n 2 hello\n 3 hey\n\\'"))))
|
||||
|
||||
(ert-deftest esh-worker-test/map-lines/external-one-line ()
|
||||
"Test that piping a single external line to `map-lines' works."
|
||||
(let ((eshell-test-line-number 0))
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output
|
||||
"*echo hi | map-lines #'eshell-test-number"
|
||||
"\\` 1 hi\n\\'"))))
|
||||
|
||||
(ert-deftest esh-worker-test/map-lines/external-multiple-lines ()
|
||||
"Test that piping multiple external lines to `map-lines' works.
|
||||
It should call the mapped function once per line."
|
||||
(let ((eshell-test-line-number 0))
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output
|
||||
"*echo 'hi\nbye' | map-lines #'eshell-test-number"
|
||||
"\\` 1 hi\n 2 bye\n\\'"))))
|
||||
|
||||
(ert-deftest esh-worker-test/map-lines/numbers ()
|
||||
"Test that piping numbers to `map-lines' passes them to the mapped function."
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output
|
||||
"{echo 10; echo 20} | map-lines #'1+"
|
||||
"\\`11\n21\n\\'")))
|
||||
|
||||
(ert-deftest esh-worker-test/map-lines/numeric-conversion ()
|
||||
"Test that `map-lines' converts numeric strings when possible."
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output
|
||||
"{echo '10\n1'; echo '5\n20'} | map-lines #'1+"
|
||||
"\\`11\n16\n21\n\\'")))
|
||||
|
||||
|
||||
;; `apply-lines' pipelines
|
||||
|
||||
(ert-deftest esh-worker-test/apply-lines/eshell-one-line ()
|
||||
"Test that piping a single line to `apply-lines' works."
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output "echo hi | apply-lines #'upcase"
|
||||
"\\`HI\n\\'")))
|
||||
|
||||
(ert-deftest esh-worker-test/apply-lines/eshell-multiple-lines ()
|
||||
"Test that piping multiple lines to `apply-lines' works.
|
||||
It should pass each line as an argument to the applied function."
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output
|
||||
"echo 'o\ni\nfoobar' | apply-lines #'string-replace"
|
||||
"\\`fiibar\n\\'")))
|
||||
|
||||
(ert-deftest esh-worker-test/apply-lines/external-one-line ()
|
||||
"Test that piping a single external line to `apply-lines' works."
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output "*echo hi | apply-lines #'upcase"
|
||||
"\\`HI\n\\'")))
|
||||
|
||||
(ert-deftest esh-worker-test/apply-lines/external-multiple-lines ()
|
||||
"Test that piping multiple external lines to `apply-lines' works.
|
||||
It should pass each line as an argument to the applied function."
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output
|
||||
"*echo 'o\ni\nfoobar' | apply-lines #'string-replace"
|
||||
"\\`fiibar\n\\'")))
|
||||
|
||||
(ert-deftest esh-worker-test/apply-lines/numbers ()
|
||||
"Test that piping numbers to `apply-lines' passes them to the function."
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output
|
||||
"{echo 5; echo 8; echo 13} | apply-lines #'+"
|
||||
"\\`26\n\\'")))
|
||||
|
||||
(ert-deftest esh-worker-test/apply-lines/numeric-conversion ()
|
||||
"Test that `apply-lines' converts numeric strings when possible."
|
||||
(with-temp-eshell
|
||||
(eshell-match-command-output
|
||||
"{echo '10\n1'; echo '5\n20'} | apply-lines #'+"
|
||||
"\\`45\n\\'")))
|
||||
|
||||
;;; esh-io-tests.el ends here
|
||||
|
|
|
|||
Loading…
Reference in a new issue