Thu, Dec 14, 2017
#emacs #programming
Why Use Emacs?
The Beginning of an Obsession
Anyone who has been in my vicinity for than 5 minutes will know that I LOVE Emacs. More than any man should reasonably love any program, let alone a programm from the 70’s. I spend countless hours tweaking my config (and yes I use Spacemacs, which is still customizable), and I’m a frequent attendee of New York City Emacs. The thing that I love most about it is how customizable and programmable it is. Since, I spend most of my day interacting with text on a computer having a platform for automating text interaction allows me to remove a lot of pain from my day. Unfortunately many people I preach to don’t quite understand how this can be a benefit. Today, I’m going to document a use case where the power of Emacs saved me hours of programming.
Software Licensing is Hard
Anyone invovled with Free and Open Source Software (FOSS) will know what a quagmire software licensing is. We have entire organizations dedicated to interpreting and defending the FOSS licenses we use every day (btw check out the FSF and Software Freedom Conservancy and donate!). So any time I go to a Linux Fest or other similiar gathering and there is any talk about Software Licensing I make sure that I find a way into that talk. As a maintainer of a few Open Source projects getting licensing right is exteremely important to me to protect the spirit and rights of my code. Recently, at Ohio Linuxfest there was a talk by Mike Dolan from the Linux Foundation exactly about FOSS Communities and Licensing.
Whenever I visit a Linux Fest I make sure to take notes at talks I find interesting (of course, using Emacs and Org Mode). Under my “take aways” for that talk I listed:
- Enforce license headers in code files
Mike said that one of the first things the Linux Foundation does is get the project to start using SPDX license abbreviations in the headers of their source files. This is important for legal reasons since it makes it clear what files are licensed under what and gives you good ground to stand on if you have to go to battle over some of your code. There is no “I was given this file without the license” excuse available then.
So of course the first thing I thought is “Man that sounds like a lot of work”. Especially since none of my projects had been following this advice from the start and so had a ton of files with nothing which I would have to find and license.
Enter Text Automation with Emacs
So while pondering my predicament I realize pretty quickly that I can write a new Emacs command to license the file I’m in regardless of programming language.
It looks like this (Note: I’m primarily a Spacemacs user now so I follow their conventions):
(defun chasinglogic/license ()
"Insert the GPL license header."
(interactive)
(progn
(goto-char (point-min))
(insert (format (chasinglogic//get-license-template)
(format-time-string "%Y" (current-time))
user-full-name
user-mail-address))
(fill-region-as-paragraph (point-min) (point))
(comment-region (point-min) (point))
(insert "\n")
(goto-char (point-min))))
I’m going to explain this code line by line first then talk about alternative implementations (such as in vim). First for the uninitiated:
(defun chasinglogic/license ()
"Insert the GPL license header."
(interactive)
This defines a function with a docstring. The (interactive)
bit tells
Emacs that this is supposed to be a user command invokable via M-x
(like :
for Vim). Next:
(progn
progn
is a special macro which basically says “do these things in
order”. Lisp is a functional programming language so normally it would
“return” after the first function. progn
lets us imperatively do
multiple calls in sequence.
(goto-char (point-min))
(insert (format (chasinglogic//get-license-template)
(format-time-string "%Y" (current-time))
user-full-name
user-mail-address))
goto-char (point-min)
says “go to the beginning of the buffer”. Emacs
calls where the cursor can be a “point” so (point-min)
returns the
earliest place the cursor can go. Then insert
will simply insert a
string as though it was typed or pasted. format
formats a string like
printf
in other languages except it returns the formatted string as a
value (get used to that idea it’s a lisp thing). The variables
user-full-name
and user-mail-address
are simply my name “Mathew
Robinson” and email “[email protected]”. These are Emacs default
variables that it tries to set based on the environment, though I set
mine manually in my init.el
. For the curious the license template that
chasinglogic//get-license-template
returns looks like this:
(defvar chasinglogic/full-gpl-template
"Copyright %s %s <%s>. All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
")
So it subs in the current year, my name, and my email into this string
where the 3 %s
’s are in that order. Then it passes that formatted
string to insert with the cursor at the beginning of buffer (at the top
of the file).
(comment-region (point-min) (point))
(fill-region-as-paragraph (point-min) (point))
Next we’re commenting the newly inserted license header then “fill”-ing it. In Emacs the term fill is for making the text fit into the fill-column width. For me this is usually 80 columns which is something of a standard but it can be customized globally or per file type if you so choose.
Finally we simply insert a final line break to help seperate the license from the rest of the file and set the cursor to the beginning of the file:
(insert "\n")
(goto-char (point-min))))
But What about VimScript?
So after seeing that you may be thinking “I can do that in Vimscript” or <insert programming language here> and you’d be right! Which doesn’t sound like I’m making a great case for Emacs, but I promise it’s coming.
The first point I’d make about Vimscript or any other language, and this point is rather weak, is that generally the solution will be more complex. Vimscript in general is a less clean language (IMO) than elisp. With anything else you’d be writing a whole tool or script to do this one small thing that would require you to leave your text editor to… well… edit some text by inserting a license header. This seems to break my flow more than it’s worth to me but YMMV.
Now, the real argument and why I think Emacs is such a great choice for this is integration with other Emacs tools and libraries.
Solving the real problem
If you remember from earlier I was originally concerned with automating this for future projects but was unsure how I could easily automate fixing old projects. With the code above I can easily insert a nice license header into the top of my current buffer but I’d still have to find all the files in my projects that don’t have the license header and manually call my new function. Enter Projectile.
If you’ve been using Emacs for any amount of time you’ve probably heard
of Projectile and if you
haven’t the tl;dr is that it’s a code project interaction library for
Emacs that offers some nice commands like projectile-find-file
that I
use every day.
When you invoke projectile-find-file
it shows you a searchable list of
all the files in the current project. I got to thinking “hey if
projectile can get that list can I just get that same list and iterate
it calling my new command?”. Spoiler alert the answer is a resounding
yes.
One of the best parts of Emacs is the help system so the first thing I
do is hit C-h f
(or the describe-function
command), search for
projectile-find-file and see the following:
projectile-find-file is an interactive autoloaded compiled Lisp function in
‘projectile.el'.
It is bound to C-c p f, <menu-bar> <tools> <Projectile> <Find file>.
(projectile-find-file &optional ARG)
Jump to a project's file using completion.
With a prefix ARG invalidates the cache first.
[back]
If I click on the ‘projectile.el’ Emacs opens up the source code for that that function:
;;;###autoload
(defun projectile-find-file (&optional arg)
"Jump to a project's file using completion.
With a prefix ARG invalidates the cache first."
(interactive "P")
(projectile--find-file arg))
Which wasn’t super helpful, so I jump to the internal function it’s calling using imenu and see:
(defun projectile--find-file (invalidate-cache &optional ff-variant)
"Jump to a project's file using completion.
With INVALIDATE-CACHE invalidates the cache first. With FF-VARIANT set to a
defun, use that instead of `find-file'. A typical example of such a defun
would be `find-file-other-window' or `find-file-other-frame'"
(interactive "P")
(projectile-maybe-invalidate-cache invalidate-cache)
(let ((file (projectile-completing-read "Find file: "
(projectile-current-project-files)))
(ff (or ff-variant #'find-file)))
(funcall ff (expand-file-name file (projectile-project-root)))
(run-hooks 'projectile-find-file-hook)))
Which shows me that it’s calling projectile-current-project-files
to
get the list of project files. I go ahead and describe that function too
for good measure:
projectile-current-project-files is a compiled Lisp function in ‘projectile.el'.
(projectile-current-project-files)
Return a list of files for the current project.
[back]
Perfect. This will give us the list of files we want to license! So I write a much buggier version of this function:
(defun chasinglogic/license-project (&optional match)
"License the current Projectile project. Will skip certain classes of files
(.git .md etc.). Additionally, if match is given the filename will be checked
against it as a regex to determine if the license should be inserted or not."
(interactive)
(-map
(lambda (x)
(progn
(find-file (concat (projectile-project-root) x))
(chasinglogic/license)))
(-filter
(lambda (f) (chasinglogic//should-license f match))
(projectile-current-project-files))))
For those new to functional programming and lisps you can think of
-map
as a for loop over the list. -filter
simply finds the files
that return true from the function chasinglogic//should-license
so we
don’t try to license files that we don’t want to hit. (Originally I
destroyed the local git history for a project since I inserted the GPL
at the top of all of git’s internal files :D)
Just like that my small Emacs command went from a tiny solution for local files into a full blown project licensing machine! This is the real power of Emacs. It’s ability to discover and dig down into everything it can do via the describe commands allows you to compose your automations with those of others to build whatever you want.
And Beyond
For those curious you can steal all of this code in full context from my dotfiles as a Spacemacs layer. I’m currently refactoring out these functions and expanding on them as their own Emacs package that I hope to publish on MELPA with tests. But I wouldn’t hold your breath since I’m working on revamping open source project management right now which I consider a better value add for everyone.
If you’ve made it this far thanks for reading this lengthy post! I hope that on some level this at least helps you understand why some of us adore this 30+ year old program.
