Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Usage: $ shly script [options] scriptfile argv Options are like those of ruby's.
(ql:quickload :unix-options)
(defvar *argv* nil)
(defvar *argf* *standard-input*)
(defvar *-i* nil)
(defvar *-F*)
(defvar *_*)
(defvar *?*)
(defmacro $ (sym &optional arg)
(etypecase sym
(fixnum (if (zerop sym)
`"shelly"
`(nth (1- sym) *argv*)))
(symbol
(ecase sym
((*) `*argv*)
((|#|) `(length *argv*))
((|:|) `asdf:*central-registry*)
((<) `*argf*)
((>) `(if *-i*
(unless (path:= (handler-case (pathname *standard-output*) (condition (error) nil)) (without-ext *argf*))
(close *standard-output*)
(setf *standard-output* (open (without-ext *argf*) :direction :output :if-exists :supersede)))
*standard-output*))
((_) `*_*)
((F) (if arg `(nth ,arg *-F*) `*-F*))
((?) `*?*)
(($) `(swank-backend:getpid))
((PATH) `(mapcar #'(lambda (x) (make-pathname :defaults (format nil "~A/" x)))
(cl-ppcre:split ":" (asdf::getenv "PATH"))))))))
(defun remove-shebang (in)
(let* ((first-char (read-char in))
(second-char (read-char in)))
(cond
((and (char= first-char #\#) (char= second-char #\!))
(read-line in))
(t (unread-char second-char in)
(unread-char first-char in)))
in))
(defun open-files (files)
(apply #'make-concatenated-stream
(mapcar (lambda (file) (remove-shebang (open file :if-does-not-exist :error)))
files)))
(defun shift ()
(pop *argv*))
(defun which (bin &key a)
(let* ((paths ($ PATH))
found)
(setq found (flet ((test (x y)
(cl-fad:file-exists-p (merge-pathnames x y))))
(if a
(remove bin paths :test (complement #'test))
(find bin paths :test #'test))))
(when found
(if a
(mapcar #'(lambda (x) (merge-pathnames bin x)) found)
(merge-pathnames bin found)))))
(defun without-ext (stream)
(let ((stream (if (typep stream 'concatenated-stream)
(car (concatenated-stream-streams stream))
stream)))
(make-pathname :name (pathname-name stream) :directory (pathname-directory stream))))
(setf (symbol-function ' mv) #'rename-file)
(defun script (&rest args)
"Execute a file as script ignoring shebang."
(unix-options:with-cli-options ((mapcar #'namestring args) t)
((autosplit ((#\a) nil "autosplit mode with -n or -p (splits ($ _) into ($ F))"))
(cd ((#\C) "DIR" "set *default-pathname-defaults* DIR, before executing your script"))
(debug ((#\d) nil "set debugging flags (push :debug into *features*)"))
(sexp ((#\e) "SCRIPT" "one line of script. Several -e's allowed. Omit [programfile]"))
(F ((#\F) "PATTERN" "split pattern for autosplit (-a)"))
(*-i* ((#\i) "EXT" "edit *argv* files in place and make backup with extension .EXT"))
(load-path ((#\I) "DIRS" "push DIRS asdf:*central-registry* directories separated by \":\""))
(ending ((#\l) nil "enable line ending processing"))
(splitline ((#\n) nil "assume '(loop while (setf ($ _) (readline)) do ...)' around your script"))
(printline ((#\p) nil "assume loop like -n but print line also like sed"))
(scriptpath ((#\S) nil "look for the script using PATH environment variable"))
unix-options:&free *argv*)
(let ((scriptfile (unless sexp (if scriptpath (which (shift)) (shift)))))
(symbol-macrolet ((in (if sexp
(make-string-input-stream sexp)
(open scriptfile :if-does-not-exist :error))))
(when debug (push :debug *features*))
(when load-path (setf ($ |:|) (nconc (mapcar #'truename (cl-ppcre:split ":" load-path)) ($ |:|))))
(when cd (setf *default-pathname-defaults* (truename cd)))
(when *argv* (setf ($ <)
(open-files (mapcar (lambda (filename)
(mv filename (format nil "~a~@[.~a~]" filename *-i*)))
*argv*))))
(if (or splitline printline)
(loop while (setf ($ _) (read-line ($ <) nil nil t)) do
(progn
(if autosplit (setf ($ F) (cl-ppcre:split (or F "\\s+") ($ _))))
(unless ending (setf ($ _) (format nil "~a~%" ($ _))))
(load in)
(if ending (setf ($ _) (format nil "~a~%" ($ _))))
(if printline (princ ($ _) ($ >)))))
(load in))))
(values)))
(defun ! (command)
(with-output-to-string (s)
(setf ($ ?) (uiop/run-program:run-program command :output s))))
(defun exec (&rest commands)
(swank-backend::execv (namestring (which (first commands)))
commands))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment