Snake Case Elisp
I’ve been using Ocaml for some fun stuff lately (including generating this site). However, it only came to my attention recently that the style guide recommends snake_case for functions and variables. After doing so much Java, my default is CamelCasing all the time so I had some work ahead of me to clean it up.
I did a few by hand via Emacs’ subword-mode (aside: subword-mode is awesome and you should definitely use it), but this got tedious, so I thought it would be a good time to practice some elisp.
I knew I’d want to be able to call my function from the middle of a word and have it transform the
whole thing, so I started by moving to the beginning of the word (backward-word
is insufficient
since if I’m already at the beginning of the word, it will take me to the word prior):
(defun aec/beginning-of-word ()
"Move point to the beginning of nearest word"
(interactive)
(forward-word)
(backward-word))
Easy enough; now I needed a way to know if I was done with the current word. I called this
end-of-word-p
, which is perhaps too general of a name, since it has some snake_case specific
logic. Regular expressions in elisp are apparently not as straightforward as they are in languages
I’m more familiar with, so I had to do some pretty crude expanded conditionals.
(defun aec/end-of-word-p (curpos)
"whether the point is at the end of a word. Treats numerical digits
as non-word characters"
(interactive "d")
(or
;; end of buffer is obviously the end of the word
(equal curpos (point-max))
;; word character followed by non-underscore non-word character
(and
(equal
(string-match "\\w" (substring (buffer-string) (- curpos 2)))
0)
(and
(equal
(string-match "\\W" (substring (buffer-string) (- curpos 1)))
0)
(not (equal
(string-match "_" (substring (buffer-string) (- curpos 1)))
0))))
;; underscore followed by non-word character
(and
(equal
(string-match "_" (substring (buffer-string) (- curpos 2)))
0)
(equal
(string-match "\\W" (substring (buffer-string) (- curpos 1)))
0))))
Now that we have those tools, actually doing the work of snake_casing is easy enough: just convert the subwords to lowercase and insert underscores between them (and remember to delete the last underscore).
(defun snake-case-ify ()
"Take a camelCased word and transform to snake_case"
(interactive)
(aec/beginning-of-word)
(while (not (aec/end-of-word-p (point)))
(call-interactively 'subword-downcase)
(insert "_"))
(delete-char -1))