;;AUTOLOAD: sh
;;AUTOLOAD: sh&
;;AUTODOC: sh "an easy way to execute shell commands synchronously"
;;AUTODOC: sh& "an easy way to execute shell commands asynchronously"
;;USAGE: (sh command arguments...)
;;
;;sh tries to provides a very user-friendly way to execute commands.
;;Arguments need not be quoted if they are strings, legal klone atoms or
;;numbers, other argument types will be coerced to strings.
;;No shell will be forked if not necessary.
;;Arguments prefixed by , or $ will be evaluated and must evaluate to strings
;;The call to sh is synchronous, command completion is awaited and () is 
;;returned in case of success, or the error code (number)
;;The call to sh& is asynchronous, the forked pid is returned immediately.
;;Example:
;;(dolist (file files) (sh cat $file >>/tmp/foo))
;;SEE ALSO: sh:open to get the command output
;;          system for the full functionality

(setq sh:re-special (regcomp "[^-a-zA-Z_0-9]"))
(setq sh:comma-symbol (intern ","))
(setq sh:re-dollar-prefixed (regcomp "^[$]([-a-zA-Z_0-9]*)$"))

(defun sh:exec (wait-command sh:args &aux
    sh:special 
    (sh:fixed-args (list))
  )
  (dolist (arg sh:args)
    (if (and (typep arg QuotedExpr) (= sh:comma-symbol (get arg 0)))
      (setq arg (eval (get arg 1)))
      (and (typep arg String) (regexec sh:re-dollar-prefixed arg))
      (setq arg (eval (intern (regsub sh:re-dollar-prefixed 1))))
    )
    (setq arg (String arg))
    (if (regexec sh:re-special arg) (setq sh:special t))
    (put sh:fixed-args -1 arg)
  )
  (if sh:special 
    (with (sh:command (copy ""))
      (dolist (arg sh:fixed-args) (nconc sh:command arg " "))
      (nconc sh:command "; exit $?")
      (setq sh:last-return-code (wait-command (system sh:command)))
    )
    (setq sh:last-return-code (wait-command (system sh:fixed-args)))
  )
  (if (/= 0 sh:last-return-code)
    sh:last-return-code
  )
)

(defunq sh (&rest sh:args) (sh:exec wait sh:args))
(defunq sh& (&rest sh:args) (sh:exec identity sh:args))

;;AUTOLOAD: sh:open
;;AUTODOC: sh:open "an easy way to execute shell commands and get its output"
;;(sh:open command arguments...)
;;
;;sh:open tries to provides a very user-friendly way to execute commands and
;;process their output
;;Arguments need not be quoted if they are strings, legal klone atoms or 
;;numbers other argument types will be coerced to strings.
;;No shell will be forked if not necessary.
;;Arguments prefixed by , or $ will be evaluated and must evaluate to strings
;;The call is asynchronous, and a stream plugged on the command standard output
;;is returned (or () if the command could not be executed)
;;Example:
;;(setq date-string (String (sh:open date))) ; will include terminating newline
;;SEE ALSO: sh, system

(defunq sh:open (&rest sh:args &aux
    sh:special sh:fd (sh:fixed-args (list)))
  (dolist (arg sh:args)
    (if (and (typep arg QuotedExpr) (= sh:comma-symbol (get arg 0)))
      (setq arg (eval (get arg 1)))
      (and (typep arg String) (regexec sh:re-dollar-prefixed arg))
      (setq arg (eval (intern (regsub sh:re-dollar-prefixed 1))))
    )
    (setq arg (String arg))
    (if (regexec sh:re-special arg) (setq sh:special t))
    (put sh:fixed-args -1 arg)
  )
  (if sh:special 
    (with (sh:command (copy ""))
      (dolist (arg sh:fixed-args) (nconc sh:command arg " "))
      (nconc sh:command "; exit $?")
      (system sh:command :output 'sh:fd)
    )
    (system sh:fixed-args :output 'sh:fd)
  )
  sh:fd
)
