1745fd2d70d6

Merge.
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Thu, 05 Dec 2019 19:42:14 -0500
parents 19519aba4cb5 (current diff) a85855efde4a (diff)
children 18da0214d5a9
branches/tags (none)
files

Changes

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