# HG changeset patch # User Steve Losh # Date 1575592934 18000 # Node ID 1745fd2d70d6f8a4c9b24300f7c7f3f7bf22f0e4 # Parent 19519aba4cb546d3dd000c5bdec648a499af41f8# Parent a85855efde4af5cbd6d00682f8073679d54738ad Merge. diff -r 19519aba4cb5 -r 1745fd2d70d6 losh.asd --- a/losh.asd Thu Dec 05 19:41:57 2019 -0500 +++ b/losh.asd Thu Dec 05 19:42:14 2019 -0500 @@ -10,7 +10,7 @@ :in-order-to ((asdf:test-op (asdf:test-op :losh/test))) :depends-on (:iterate - :uiop + :external-program #+sbcl :sb-sprof) :serial t diff -r 19519aba4cb5 -r 1745fd2d70d6 src/shell.lisp --- a/src/shell.lisp Thu Dec 05 19:41:57 2019 -0500 +++ b/src/shell.lisp Thu Dec 05 19:42:14 2019 -0500 @@ -1,6 +1,6 @@ (in-package :losh.shell) -(defun sh (command &key input output (wait t)) +(defun sh (command &key input (wait t) (result-type 'null)) "Run `command`, piping `input` to it, optionally returning its output. `command` must be either a string (the program), or a list of the program and @@ -13,8 +13,15 @@ `input` must be a character input stream, a string, or `nil`. If non-`nil` its contents will be sent to the program as its standard input. - `output` must be one of `:string`, `:stream`, or `nil`. `:string` cannot be - used if `:wait` is `nil`. + `result-type` must be one of: + + * `null`: output will be sent to `/dev/null` and `nil` returned. + * `stream`: output will be returned as a character stream. + * `string`: all output will be gathered up and returned as a single string. + * `list`: all output will be gathered up and returned as a list of lines. + + If `wait` is `nil`, the only acceptable values for `result-type` are `null` + and `stream`. " (ctypecase command @@ -24,23 +31,34 @@ (string (setf input (make-string-input-stream input))) (stream) (null)) - (let ((result (funcall (if wait #'uiop:run-program #'uiop:launch-program) - command - :output (when output - (if wait - (ccase output - (:string :string) - (:stream :string)) ; hack because uiop doesn't support this - (ccase output - (:string (error "`output` cannot be `:string` when not `wait`ing.")) - (:stream :stream)))) - :input input))) - (ecase output - ((nil) (values)) - (:stream (if wait - (make-string-input-stream result) - (uiop:process-info-output result))) - (:string result)))) + (when (not wait) + (assert (member result-type '(null stream)) () + "`result-type` must be `stream` or `null` when not `wait`ing.")) + (let* ((out (if wait ; why is every external programming running facility a goddamn mess? + (ecase result-type + ((string stream list) (make-string-output-stream)) + (null nil)) + (ecase result-type + ((string list) (make-string-output-stream)) + (stream :stream) + (null nil)))) + (result (multiple-value-list + (funcall (if wait #'external-program:run #'external-program:start) + (first command) (rest command) + :output out + :input input)))) + (flet ((output-stream () ; jesus christ + (if wait + (make-string-input-stream (get-output-stream-string out)) + (external-program:process-output-stream (first result))))) + (values-list + (cons (ecase result-type + (null nil) + (stream (output-stream)) + (string (get-output-stream-string out)) + (list (iterate (for line :in-stream (output-stream) :using #'read-line) + (collect line)))) + result))))) (defun pbcopy (object) @@ -50,4 +68,4 @@ (defun pbpaste () "`pbpaste` the current clipboard as a string." - (values (sh "pbpaste" :output :string))) + (values (sh "pbpaste" :result-type 'string)))