(define-module (ttn-do zzz subprocess)
#:export (shell-quote-argument
system*
sysfmt
find-program
args->normalized-list
call-process
call-process->buffers
make-buffered-caller
shell-command->string
fshell-command->string
shell-command->list
port-lines
file-lines)
#:use-module ((srfi srfi-13) #:select (string-join
string-tokenize
string-concatenate
substring/shared))
#:use-module ((srfi srfi-14) #:select (char-set-complement char-set))
#:use-module ((ice-9 regex) #:select (match:start))
#:use-module ((ice-9 optargs-kw) #:select (lambda*
define*
let-optional*
let-keywords*))
#:use-module ((ice-9 rdelim) #:select (read-line))
#:use-module ((ice-9 popen) #:select (open-input-pipe
close-pipe))
#:use-module ((ttn-do mogrify) #:select (editing-buffer-manager
editing-buffer))
#:use-module ((ttn-do zzz filesystem) #:select (safe-rewind
temporary-file-port))
#:use-module ((ttn-do zzz personally) #:select (accumulator
fs)))
(define *not-space* (char-set-complement (char-set #\space)))
(define shell-quote-argument
(let ((need-backslash (make-regexp "[^-0-9a-zA-Z_./]")))
(lambda (arg)
(define (sub b e)
(substring/shared arg b e))
(let ((acc (accumulator)))
(let loop ((start 0))
(cond ((= start (string-length arg)))
((regexp-exec need-backslash arg start)
=> (lambda (m)
(let ((pos (match:start m)))
(acc (sub start pos) "\\" (sub pos (1+ pos)))
(loop (1+ pos)))))
(else
(acc (sub start (1+ start)))
(loop (1+ start)))))
(string-concatenate (acc))))))
(define (split/space s)
(string-tokenize s *not-space*))
(define (join/space ls)
(string-join ls " "))
(define *not-colon* (char-set-complement (char-set #\:)))
(define (split/colon s)
(string-tokenize s *not-colon*))
(define (join/colon ls)
(string-join ls ":"))
(define (system* . args)
(system (join/space args)))
(define (sysfmt . args)
(system (apply fs (car args) (cdr args))))
(define (find-program name)
(let ((full (search-path (split/colon (or (getenv "PATH") ".")) name)))
(and full (access? full X_OK)) full))
(define (args->normalized-list . args)
(split/space (join/space args)))
(define* (call-process program
#:key
(inp (current-input-port))
(outp (current-output-port))
(errp (current-error-port))
(norm #f)
(args ()))
(or (defined? 'close-all-fdes-except)
(defined? 'port-for-each)
(error "sorry, ‘call-process’ unusable"))
(let ((chk (lambda (p)
(or (not p)
(port? p)
(error "not a port:" p)))))
(chk inp)
(or (and (number? outp) (zero? outp)) (chk outp))
(chk errp))
(and norm
(let ((ls (apply args->normalized-list program args)))
(set! program (car ls))
(set! args (cdr ls))))
(let* ((null-input? (not inp))
(async? (equal? 0 outp))
(discard-output? (or async? (not outp)))
(discard-error-output? (or async? (not errp)))
(ensure-fdes (lambda (ignore? port mode)
(if ignore?
(open-fdes *null-device* mode)
(or (false-if-exception (fileno port))
(open-fdes *null-device* mode))))))
(let ((pid (primitive-fork)))
(flush-all-ports)
(cond ((zero? pid)
(set-batch-mode?! #t)
(let ((input-fdes (ensure-fdes null-input? inp O_RDONLY))
(output-fdes (ensure-fdes discard-output? outp O_WRONLY))
(error-fdes (ensure-fdes discard-error-output?
errp O_WRONLY)))
(let ((c-a-f-e (if (defined? 'close-all-fdes-except)
close-all-fdes-except
(lambda ok
(port-for-each
(lambda (pt-entry)
(false-if-exception
(let ((fd (fileno pt-entry)))
(or (memq fd ok)
(close-fdes fd))))))))))
(c-a-f-e input-fdes
output-fdes
error-fdes))
(cond ((not (= input-fdes 0))
(if (= output-fdes 0)
(set! output-fdes (dup->fdes 0)))
(if (= error-fdes 0)
(set! error-fdes (dup->fdes 0)))
(dup2 input-fdes 0)))
(cond ((not (= output-fdes 1))
(if (= error-fdes 1)
(set! error-fdes (dup->fdes 1)))
(dup2 output-fdes 1)))
(dup2 error-fdes 2)
(apply execlp program program args)
(alfksdlkajdlkfaldskjf) ))
(else
(if async?
#f
(cdr (waitpid pid))))))))
(define (read/cleanup-tmpfile port buf)
(safe-rewind port)
(editing-buffer buf
(insert port)))
(define* (call-process->buffers program
#:key
(inb #f) (outb #f) (errb #f)
(norm #f) (args ()))
(and norm
(let ((ls (apply args->normalized-list program args)))
(set! program (car ls))
(set! args (cdr ls))))
(let ((in-port (and inb (let ((p (temporary-file-port)))
(editing-buffer inb
(write-to-port p))
(safe-rewind p)
p)))
(out-port (and outb (temporary-file-port)))
(err-port (and errb (temporary-file-port))))
(let ((rv (call-process program
#:inp in-port
#:outp out-port
#:errp err-port
#:args args)))
(and outb (read/cleanup-tmpfile out-port outb))
(and errb (read/cleanup-tmpfile err-port errb))
rv)))
(define* (make-buffered-caller program #:key (inb #f) (args ()))
(let* ((outbuf (editing-buffer-manager))
(errbuf (editing-buffer-manager))
(not-yet #t)
(exit-val #f)
(do-it!-proc (lambda (program . args)
(set! not-yet #t)
(lambda ()
(set! not-yet #f)
(set! exit-val
(call-process->buffers
program
#:inb inb
#:outb (outbuf outbuf)
#:errb (errbuf errbuf)
#:norm #t #:args args))
exit-val)))
(do-it! (apply do-it!-proc program args)))
(lambda (command . args)
(case (cond ((keyword? command) command)
((symbol? command) (symbol->keyword command))
(else #f))
((#:redefine) (set! do-it! (apply do-it!-proc (car args) (cdr args))))
((#:execute) (outbuf 'erase-buffer) (errbuf 'erase-buffer) (do-it!))
((#:execute/no-init) (do-it!))
((#:outbuf) (outbuf outbuf))
((#:outbuf-string) (outbuf 'buffer-string))
((#:outbuf-lines) (outbuf 'buffer-lines))
((#:errbuf) (errbuf errbuf))
((#:errbuf-string) (errbuf 'buffer-string))
((#:errbuf-lines) (errbuf 'buffer-lines))
((#:exit-val) (if not-yet
(error "program not executed yet")
exit-val))
(else (error "bad command"))))))
(define (read-lines --open --close handle-delim)
(let* ((policy (if (null? handle-delim)
'trim
(let ((p (car handle-delim)))
(case p
((trim concat peek split) p)
(else 'trim)))))
(split? (eq? 'split policy))
(eof? (if split?
(lambda (pair) (eof-object? (cdr pair)))
eof-object?))
(p (--open))
(lines (accumulator)))
(define (next)
(read-line p policy))
(let loop ((line (next)))
(cond ((eof? line)
(and split? (lines line))
(--close p))
(else
(lines line)
(loop (next)))))
(lines)))
(define (shell-command->list cmd . handle-delim)
(read-lines (lambda () (open-input-pipe cmd))
close-pipe
handle-delim))
(define (shell-command->string cmd)
(apply string-append (shell-command->list cmd 'concat)))
(define (fshell-command->string s . args)
(shell-command->string (apply fs s args)))
(define (port-lines port . handle-delim)
(read-lines (lambda () port) identity handle-delim))
(define (file-lines filename . handle-delim)
(read-lines (lambda () (open-input-file filename))
close-port
handle-delim))