;;;; common-shell.lisp
#|
scm, ldbeth  and I were talking  about  lisp and shells  on our phlogs   and
mastodon.   A shell is a program  whose  main responsibility  is to fork and
exec.    Making  system(3)  calls  to the parent  shell  is a kind of  shell
scripting, but it would also be cool to be a shell ourselves. 

I figure  I need a reader macro that calls  strings  and gets their  output.
Using direct  control  of *read-table*  and read-time  macros  is one iconic
feature of lisp. #:uiop, which is wrapped in with #:asdf provides a portable
posix compatibility layer. 

BUT FIRST I feel like I have been a little too tech-blogger lately. Lisp and
falteringly  jamming soft synth is part of who I am, but I will try and make
at least a phlog a week that is not just C, lisp and systems.  But that is a
topic for later. |# 

(require 'asdf)
;; in order to get the portable #'uiop:run-program

(defun my-shell (s c m) "
(my-shell stream character number)
collects READs from stream until the next read-char  is #\], which is eaten.
maps #'uiop:run-program  to that list, except *standard-output* is collected
as      a      list     of      newline      delimited      strings.       "
 (declare (ignore c m))
`(let ((list ',(loop  for r = (read s nil nil)
for ch = (read-char  s nil nil)
 collect r
while (not (char= ch #\])) do (unread-char  ch s))))
 (mapcar (lambda (x)
(let ((response (with-output-to-string  (*standard-output*)
 (uiop:run-program x :output t))))
 (with-input-from-string (*standard-input* response)
(loop for line = (read-line *standard-input*  nil nil)
 while line
 nconcing (unless (string= "" line)
 `(,line))))))
 list)))

;;      And    let's    Make    that    the    reader    macro    for     #[
(set-dispatch-macro-character #\# #\[ #'my-shell)

#|
I think this is pretty powerful already.  To see it in action, we must evoke
the reader  (this file is finished  being read prior to being  evaluated  to
begin with, so we have to READ something else to see it in action.) |# 

(defun shell-reader-example-1 ()
 (let ((unread-string "
#[     \"date\"     \"printf    'floo\\\\nblah'      |     sed     s/o/e/\"]
 ")
 (output))
 (with-input-from-string (*standard-input* unread-string)
 (setf output (eval (read))))
 (values output)))

(shell-reader-example-1)

#|
$ rlwrap ecl --load common-shell.lisp
ECL (Embeddable Common-Lisp) 21.2.1 (git:UNKNOWN)
Copyright      (C)     1984    Taiichi    Yuasa    and     Masami     Hagiya
Copyright (C) 1993 Giuseppe Attardi
Copyright        (C)        2013        Juan        J.         Garcia-Ripoll
Copyright (C) 2018 Daniel Kochmanski
Copyright    (C)   2021   Daniel   Kochmanski    and   Marius   Gerbershagen
ECL   is  free  software,   and  you  are  welcome   to   redistribute    it
under   certain   conditions;     see  file   'Copyright'     for   details.
Type :h for Help.
Top       level      in:       #<process      TOP-LEVEL       0x1cf7126f80>.
> (shell-reader-example-1)

(("Mon     Nov     14    09:17:18    UTC    2022")     ("fleo"      "blah"))
>
Alright that seems to be working.
|#

(defun shell-reader-example-2 ()
 (let ((input "
#[ \"tmux new-session -sFOO -d\"
 \"tmux send-keys -tFOO:0 'date' C-m\"
 \"tmux capturep -tFOO:0 -p\"]
 ")
 (output))
 (with-input-from-string (*standard-input* input)
 (setf output (eval (read))))
 (values output)))

#|
> (shell-reader-example-2)

(NIL   NIL   ("$   date"   "Mon  Nov   14   09:22:26    UTC   2022"    "$"))
interesting.
|#

#|
Otherwise...  I still owe jns copying an org file from some laptops that are
sleeping  right now basically, which is incredibly  cool and trivial  to do.
And if darkness  didn't get figured out I should probably try to participate
in that. Once I post that org file, it would be cool if lots of people  used
it. The org file ended up very convenient (not pictured here). 

And I didn't use ronald's  gopher revision control yet, though  it will make
my life infinitely better once I get to it. 
|#