src/shell.lisp @ 322aefbbcb9f default tip

Add `(reductions ... :result-type ...)` argument
author Steve Losh <steve@stevelosh.com>
date Tue, 20 Feb 2024 11:36:38 -0500
parents a1be2be82e9d
children (none)
(in-package :losh.shell)

(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
  its arguments.

  `wait` must be a boolean.  If true, this function will block until the command
  completes.  If false, it will return immediately and allow the program to run
  asynchronously.

  `input` must be a character input stream, a string, a list of strings, or
  `nil`.  If non-`nil` its contents will be sent to the program as its standard
  input.  A list of strings will be sent separated by newlines.

  `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.
  * `vector`: all output will be gathered up and returned as a vector of octets.

  If `wait` is `nil`, the only acceptable values for `result-type` are `null`
  and `stream`.

  "
  (ctypecase command
    (string (setf command (list command)))
    ((cons string list)))
  (ctypecase input
    (string (setf input (make-string-input-stream input)))
    (vector (setf input (flexi-streams:make-in-memory-input-stream input)))
    (cons (setf input (make-string-input-stream (format nil "~{~A~^~%~}" input)))) ; todo make this not cons as much
    (stream)
    (null))
  (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))
                  (vector (flexi-streams:make-in-memory-output-stream))
                  (null nil))
                (ecase result-type
                  ((string list) (make-string-output-stream))
                  (vector (flexi-streams:make-in-memory-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))
                (vector (flexi-streams:get-output-stream-sequence out))
                (list (iterate (for line :in-stream (output-stream) :using #'read-line)
                               (collect line))))
              result)))))


(defparameter *pbcopy-command* "pbcopy"
  "The shell command to use for `pbcopy`.  When run, this command should set the clipboard contents to its standard input.")

(defparameter *pbpaste-command* "pbpaste"
  "The shell command to use for `pbpaste`.  When run, this command should print the clipboard contents on standard output.")

(defun pbcopy (&optional (object *))
  "`pbcopy` the `aesthetic-string` of `object`."
  (sh *pbcopy-command* :input (format nil "~A" object) :wait nil)
  (values))

(defun pbpaste ()
  "`pbpaste` the current clipboard as a string."
  (values (sh *pbpaste-command* :result-type 'string)))