Last active
December 13, 2016 07:12
-
-
Save KeenS/7059301 to your computer and use it in GitHub Desktop.
Usage: $ shly script [options] scriptfile argv
Options are like those of ruby's.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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