I want to automate little things on my computer so that I don’t have to look up command lines or stitch together different applications. Many of these things make sense to turn into shell scripts. That way, I can call them from other programs and assign keyboard shortcuts to them. Still, I spend most of my computer time in Emacs, and I don’t want to think about whether I’ve defined a command in Emacs Lisp or in a shell script. Besides, I like the way Helm lets me type parts of commands in order to select and call them.
Emacs Lisp allows you to define a macro that results in Emacs Lisp code. In this case, I want to define interactive functions so I can call them with
M-x. In case I decide to call them from Emacs Lisp, such as
(my/shell/rotate-screen "left"), I want to be able to pass arguments. I’m also using dash.el to provide functions like
-not, although I could rewrite this to just use the standard Emacs Lisp functions.
Here’s the code that scans a given directory for executable files and creates interactive functions, and some code that calls it for my ~/bin directory.
(defmacro my/convert-shell-scripts-to-interactive-commands (directory) "Make the shell scripts in DIRECTORY available as interactive commands." (cons 'progn (-map (lambda (filename) (let ((function-name (intern (concat "my/shell/" (file-name-nondirectory filename))))) `(defun ,function-name (&rest args) (interactive) (apply 'call-process ,filename nil nil nil args)))) (-filter (-not #'file-directory-p) (-filter #'file-executable-p (directory-files directory t)))))) (my/convert-shell-scripts-to-interactive-commands "~/bin")
Let’s see how that goes!