Another alternative to string templates
Posted January 26, 2014 at 09:56 AM | categories: emacs-lisp | tags:
In the last post I explored a way to expand a string template that was more readable than the usual format. Today I look at another approach where I use sexp expansions to accomplish the same thing. The idea is to embed lisp expressions and replace them by what they evaluate to.
In emacs-lisp, if we have a command in a string, we can "read" it, and then eval it.
Here we get the user-full-name:
(eval (read "user-full-name"))
John Kitchin
We can use this on variables too.
(setq some-variable "test") (eval (read "some-variable"))
test
So, if we use a syntax to identify what to replace, we can substitute in the values. Let us try %() as the syntax.
(defun expand-template (s) "expand a template containing %() with the eval of its contents" (replace-regexp-in-string "%(\\([^)]+\\))" (lambda (arg) (format "%s" (eval (read (substring arg 2 -1))))) s)) (let ((key "kitchin-2014") (author "Kitchin, J. R.") (journal "HACS") (year "2014") (volume "1") (pages "1--10") (doi "10.1.1.109/hacs.1.10") (url "http://hacs.org/10.1.1.109/hacs.1.10") (pdf-dir "/home/jkitchin/pdfs") (template " :PROPERTIES: :Custom_ID: %(key) :AUTHOR: %(author :JOURNAL: %(journal) :YEAR: %(year) :VOLUME: %(volume) :PAGES: %(pages) :DOI: %(doi) :URL: %(url) :END: [[cite:%(key)]] [[file:%(pdf-dir)/%(key).pdf][pdf]]\n\n")) (expand-template template))
:PROPERTIES: :Custom_ID: kitchin-2014 :AUTHOR: Kitchin, J. R. :YEAR: 2014 :VOLUME: 1 :PAGES: 1--10 :DOI: 10.1.1.109/hacs.1.10 :URL: http://hacs.org/10.1.1.109/hacs.1.10 :END: [[cite:kitchin-2014]] [[file:/home/jkitchin/pdfs/kitchin-2014.pdf][pdf]]
That is pretty nice. I like it better than the plist expansion I used before. Presumably these variables would already be defined somewhere in your code.
I thought of trying this on a more complex expansion, and discovered a weakness in the regexp that finds the expansion values. It turns out to be simpler to use %{} as the delimiter than %(), because you may want nested parentheses. The regexp above does not correctly match sets of parentheses.
(defun expand-template (s) "expand a template containing %{} with the eval of its contents" (replace-regexp-in-string "%{\\([^}]+\\)}" (lambda (arg) (let ((sexp (substring arg 2 -1))) (format "%s" (eval (read sexp))))) s)) (expand-template "2 * 2 = %{(* 2 2)}")
2 * 2 = 4
I am not sure this is a desirable way to make a template, with multiline code to be expanded, but at least this works!
(defun expand-template (s) "expand a template containing %{} with the eval of its contents" (replace-regexp-in-string "%{\\([^}]+\\)}" (lambda (arg) (let ((sexp (substring arg 2 -1))) (format "%s" (eval (read sexp))))) s)) (expand-template "The result is %{(progn (if (> 4 3) 'true 'false))}")
The result is true
The regexp used in the expansion is not very robust. In particular if there is a } in the code, it will probably fail because the regexp does not match closing } correctly. Fixing that is beyond me right now!
Copyright (C) 2014 by John Kitchin. See the License for information about copying.
Org-mode version = 8.2.5g