--- 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
--- 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)))