commit faaea200a6af5cbfb46fe09c1c3daa347ec4a66b
parent efe868a939ca8d32e4eead237481d3b93a126320
Author: Tomas Hlavaty <tom@logand.com>
Date:   Sat,  1 Nov 2014 10:06:01 +0100
with-program-io added
Diffstat:
| M | os.lisp |  |  | 271 | ++++++++++++++++++++++++++++++++++++++++++------------------------------------- | 
1 file changed, 143 insertions(+), 128 deletions(-)
diff --git a/os.lisp b/os.lisp
@@ -29,141 +29,156 @@
            :md5sum
            :run-command
            :sha1sum
+           :with-program-io
            :with-program-output
            :with-temporary-file))
 
 (in-package :rw.os)
 
-(defun make-program (input output cmd args)
-  #-(or ccl ecl sbcl cmu #+nil clisp)
-  (error "RW.OS:MAKE-PROGRAM not ported")
-  #+ccl
-  (let ((p (ccl:run-program cmd
-                            args
-                            :input input
-                            :output output
-                            :error nil
-                            :sharing :external
-                            :wait nil
-                            ;; TODO make bivalent
-                            ;;:character-p t
-                            ;;:element-type '(unsigned-byte 8)
-                            )))
-    (let ((status (ccl:external-process-status p)))
-      (if input
-          (assert (eq :running status))
-          (assert (member status '(:running :exited)))))
-    (lambda (msg)
-      (ecase msg
-        (:status-and-code (ccl:external-process-status p))
-        (:input-stream (ccl:external-process-input-stream p))
-        (:output-stream (ccl:external-process-output-stream p))
-        (:wait (ccl::external-process-wait p))
-        (:close (flet ((finish (x) (when x (close x))))
-                 (finish (ccl:external-process-output-stream p))
-                 (finish (ccl:external-process-input-stream p))
-                 (finish (ccl:external-process-error-stream p)))))))
-  #+ecl
-  (multiple-value-bind (io x p)
-      (ext:run-program cmd
-                       args
-                       :input input
-                       :output output
-                       :error nil
-                       :wait (not (or input output))) ;; TODO why wait=nil + wait call doesnt work?
-    (declare (ignore x))
-    (let ((status (ext:external-process-status p)))
-      (if input
-          (assert (eq :running status))
-          (assert (member status '(:running :exited)))))
-    (lambda (msg)
-      (ecase msg
-        (:status-and-code (ext:external-process-status p))
-        (:input-stream io)
-        (:output-stream io)
-        (:wait (ext:external-process-wait p))
-        (:close (when io (close io)))))) ;; TODO is this the right thing to close process?
-  #+sbcl
-  (let ((p (sb-ext:run-program cmd
-                               args
-                               :input input
-                               :output output
-                               :error nil
-                               :wait nil
-                               :search t)))
-    (let ((status (sb-ext:process-status p)))
-      (if input
-          (assert (eq :running status))
-          (assert (member status '(:running :exited)))))
-    (lambda (msg)
-      (ecase msg
-        (:status-and-code (sb-ext:process-status p))
-        (:input-stream (sb-ext:process-input p))
-        (:output-stream (sb-ext:process-output p))
-        (:wait (sb-ext:process-wait p))
-        (:close (sb-ext:process-close p)))))
-  #+cmu
-  (let ((p (ext:run-program cmd
-                            args
-                            :input input
-                            :output output
-                            :error nil
-                            :wait nil)))
-    (let ((status (sb-ext:process-status p)))
-      (if input
-          (assert (eq :running status))
-          (assert (member status '(:running :exited)))))
-    (lambda (msg)
-      (ecase msg
-        (:status-and-code (sb-ext:process-status p))
-        (:input-stream (sb-ext:process-input p))
-        (:output-stream (sb-ext:process-output p))
-        (:wait (sb-ext:process-wait p))
-        (:close (ext:process-close p)))))
-  ;;#+clisp
-  #+nil
-  (let ((p (ext:run-program cmd
-                            :arguments args
-                            :input input
-                            :output output
-                            :error nil
-                            :wait nil)))
-    (let ((status :running)) ;; TODO
-      (if input
-          (assert (eq :running status))
-          (assert (member status '(:running :exited)))))
-    (lambda (msg)
-      (ecase msg
-        (:status-and-code (values :running 0)) ;; TODO
-        (:input-stream p)
-        (:output-stream p)
-        (:wait (ext:process-wait p)) ;; TODO
-        (:close (close p))))))
-
-(defun call-with-program-output (output cmd args error-plist fn)
-  (let ((p (make-program nil output cmd args)))
-    (unwind-protect
-         (let ((z (if output
-                      (funcall fn (funcall p :output-stream))
-                      t)))
-           (funcall p :wait)
-           (multiple-value-bind (status code) (funcall p :status-and-code)
-             (assert (member status '(:running :exited)))
-             (if (member code '(nil 0))
-                 z
-                 (unless (eq t error-plist)
-                   (error (format nil "~a error ~d: ~a ~s" cmd code
-                                  (when error-plist
-                                    (or (cdr (assoc code error-plist)) ""))
-                                  args))))))
-      (funcall p :close))))
-
-(defun run-command (cmd &optional args error-plist)
-  (call-with-program-output nil cmd args error-plist nil))
+(defun make-program (input output cmd args error-plist)
+  (flet ((fail (code)
+           (unless (eq t error-plist)
+             (error (format nil "~a error ~d: ~a ~s" cmd code
+                            (when error-plist
+                              (or (cdr (assoc code error-plist)) ""))
+                            args)))))
+    #-(or ccl ecl sbcl cmu #+nil clisp)
+    (error "RW.OS:MAKE-PROGRAM not ported")
+    #+ccl
+    (let ((p (ccl:run-program cmd
+                              args
+                              :input input
+                              :output output
+                              :error nil
+                              :sharing :external
+                              :wait nil
+                              ;; TODO make bivalent
+                              ;;:character-p t
+                              ;;:element-type '(unsigned-byte 8)
+                              )))
+      (let ((status (ccl:external-process-status p)))
+        (if input
+            (assert (eq :running status))
+            (assert (member status '(:running :exited)))))
+      (lambda (msg)
+        (ecase msg
+          (:fail (fail (nth-value 2 (ccl:external-process-status p))))
+          (:status-and-code (ccl:external-process-status p))
+          (:streams (values (ccl:external-process-input-stream p)
+                            (ccl:external-process-output-stream p)))
+          (:wait (ccl::external-process-wait p))
+          (:close (flet ((finish (x) (when x (close x))))
+                    (finish (ccl:external-process-output-stream p))
+                    (finish (ccl:external-process-input-stream p))
+                    (finish (ccl:external-process-error-stream p)))))))
+    #+ecl
+    (multiple-value-bind (io x p)
+        (ext:run-program cmd
+                         args
+                         :input input
+                         :output output
+                         :error nil
+                         :wait (not (or input output))) ;; TODO why wait=nil + wait call doesnt work?
+      (declare (ignore x))
+      (let ((status (ext:external-process-status p)))
+        (if input
+            (assert (eq :running status))
+            (assert (member status '(:running :exited)))))
+      (lambda (msg)
+        (ecase msg
+          (:fail (fail (nth-value 2 (ext:external-process-status p))))
+          (:status-and-code (ext:external-process-status p))
+          (:streams (values io io))
+          (:wait (ext:external-process-wait p))
+          (:close (when io (close io)))))) ;; TODO is this the right thing to close process?
+    #+sbcl
+    (let ((p (sb-ext:run-program cmd
+                                 args
+                                 :input input
+                                 :output output
+                                 :error nil
+                                 :wait nil
+                                 :search t)))
+      (let ((status (sb-ext:process-status p)))
+        (if input
+            (assert (eq :running status))
+            (assert (member status '(:running :exited)))))
+      (lambda (msg)
+        (ecase msg
+          (:fail (fail (sb-ext:process-exit-code p)))
+          (:status-and-code (values (sb-ext:process-status p)
+                                    (sb-ext:process-exit-code p)))
+          (:streams (values (sb-ext:process-input p)
+                            (sb-ext:process-output p)))
+          (:wait (sb-ext:process-wait p))
+          (:close (sb-ext:process-close p)))))
+    #+cmu
+    (let ((p (ext:run-program cmd
+                              args
+                              :input input
+                              :output output
+                              :error nil
+                              :wait nil)))
+      (let ((status (sb-ext:process-status p)))
+        (if input
+            (assert (eq :running status))
+            (assert (member status '(:running :exited)))))
+      (lambda (msg)
+        (ecase msg
+          (:fail (fail (nth-value 2 (sb-ext:process-status p))))
+          (:status-and-code (sb-ext:process-status p))
+          (:streams (values (sb-ext:process-input p)
+                            (sb-ext:process-output p)))
+          (:wait (sb-ext:process-wait p))
+          (:close (ext:process-close p)))))
+    ;;#+clisp
+    #+nil
+    (let ((p (ext:run-program cmd
+                              :arguments args
+                              :input input
+                              :output output
+                              :error nil
+                              :wait nil)))
+      (let ((status :running)) ;; TODO
+        (if input
+            (assert (eq :running status))
+            (assert (member status '(:running :exited)))))
+      (lambda (msg)
+        (ecase msg
+          (:fail (fail 0))                       ;; TODO
+          (:status-and-code (values :running 0)) ;; TODO
+          (:streams (values p p))
+          (:wait (ext:process-wait p)) ;; TODO
+          (:close (close p)))))))
+
+(defun call-with-program (program fn)
+  (unwind-protect
+       (let ((z (multiple-value-bind (input output)
+                    (funcall program :streams)
+                  (cond
+                    ((and input output)
+                     (funcall fn input output))
+                    ((or input output)
+                     (funcall fn (or input output)))
+                    (t t)))))
+         (funcall program :wait)
+         (multiple-value-bind (status code)
+             (funcall program :status-and-code)
+           (assert (member status '(:running :exited)))
+           (if (member code '(nil 0))
+               z
+               (funcall program :fail))))
+    (funcall program :close)))
+
+(defmacro with-program-io ((ivar ovar program) &body body)
+  `(call-with-program ,program (lambda (,ivar ,ovar) ,@body)))
 
 (defmacro with-program-output ((var cmd &optional args error-plist) &body body)
-  `(call-with-program-output :stream ,cmd ,args ,error-plist
-                             (lambda (,var) ,@body)))
+  `(call-with-program (make-program nil :stream ,cmd ,args ,error-plist)
+                      (lambda (,var) ,@body)))
+
+(defun run-command (cmd &optional args error-plist)
+  (call-with-program (make-program nil nil cmd args error-plist) nil))
 
 (defun %namestring (x) ;; TODO why not NAMESTRING directly usable?
   (with-output-to-string (*standard-output*)