This is the RCD Template Interpolation System for Emacs, the templating engine for Emacs and Emacs Lisp. Kind of. It is akin to text preprocessors.

It expands parts of the template into the evaluated Emacs Lisp code. If template is a string as "[[[ (+ 2 2) ]]]" then the system may expand that string into "4".

There is no need to pass to it any set of variables, it will expand whatever variables it can see. It uses dynamical bindings. But if somebody finds a way to use lexical bindings is welcome to tell me. As this is separate package I don’t think it would ever influence your other code under lexical-binding being TRUE.

Development thanks to contributors on Users list for the GNU Emacs where we discussed various issues, especially about eval and security of evaluation of Emacs Lisp code.

Example usage of this Emacs package may be reviewed on Emacs Lisp interpolation of embedded PostgreSQL queries within plain text.

;;; rcd-template.el --- RCD Template Interpolation System for Emacs

;; Copyright (C) 2021 by Jean Louis

;; Author: Jean Louis <bugs@gnu.support>
;; Version: 1.13
;; Package-Requires:
;; Keywords: 
;; URL: https://hyperscope.link/3/7/1/3/3/RCD-Template-Interpolation-System-for-Emacs.html

;; This file is not part of GNU Emacs.

;; This program is free software: you can redistribute it and/or
;; modify it under the terms of the GNU Affero 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 Affero General Public
;; License along with this program. If not, see
;; <http://www.gnu.org/licenses/>.

;;; Commentary:

;;; This is the RCD Template Interpolation System for Emacs.
;;;
;;; It expands parts of the template into the evaluated Emacs Lisp
;;; code. If template is a string as "⟦ (+ 2 2) ⟧" then this system
;;; will expand the string into "4".
;;;
;;; Delimiters such as "⟦" and "⟧" may be defined by user and can be
;;; any strings:
;;;
;;; <% and %>
;;; >>> and <<<
;;; 〖 and 〗
;;; [[[ and ]]]
;;;
;;; You may install key bindings to easier insert the embedded snippet
;;; with delimiters, such as:
;;;
;;; (global-set-key (kbd "C-c i") 'rcd-template-insert-delimiters)
;;;
;;; This type of system is may be used to pre-process Markdown and
;;; other lightweight markup before its full expansion into the HTML
;;; pages. It will not know nothing about Markdown, it will
;;; pre-process any kind of text and interpolate Emacs Lisp values. It
;;; may be used to create any kind of dynamic reports. Thus any kind
;;; of text may have Emacs Lisp values that will be interpolated and
;;; expanded as a string.
;;;
;;; If an embedded variables does not exist such as:
;;;
;;; ⟦ non-existent-variable ⟧
;;;
;;; then such would be expanded into empty string "" and same would
;;; happen if the embedded Emacs Lisp fails to evaluate with success.
;;;
;;; Many similar templating systems are determined for online and real
;;; time interpolation. If a program receives any input from users
;;; that has to be used in the interpolation, such input could have
;;; malicious values and thus exploit the system. Programmer should
;;; thus understand the security risks involved:
;;; https://en.wikipedia.org/wiki/Eval#Security_risks
;;;
;;; Majority of templating systems also require programmer to provide
;;; a specific set of variables that should be expanded. This system
;;; will expand global variables whatever they may be and thus lessen
;;; the amount of programming efforts with full liberty.
;;;
;;; This package will work well for as long as lexical-binding is nil.
;;;
;;; You may invoke now `rcd-template-buffer-preview' to see how the
;;; following line expands ⟦ (+ 2 2) ⟧ into "4".
;;; 
;;; Two plus two equals ⟦ (+ 2 2) ⟧.
;;;
;;; Exit the preview by killing the buffer. 
;;;
;;; Example on how Markdown markup would be pre-processed:
;;;
;;; --- begin of Markdown markup ---
;;; # Today's prices
;;;
;;; Today's gold price is ⟦ (usd-eur (gold-price-kg 1)) ⟧
;;; --- end of Markdown markup ---
;;;
;;; Markdown pages cannot know what will be the result of evaluation
;;; such as (usd-eur (gold-price-kg 1)) thus after the processing with
;;; the RCD Template Interpolation System the markup will be expanded
;;; into following:
;;; 
;;; --- begin of pre-processed Markdown markup ---
;;; # Today's prices
;;;
;;; Today's gold price is € 47530.79
;;; --- end of pre-processed Markdown markup ---
;;;
;;; and after pre-processing, the page may be expanded into the HTML
;;; and get prepared for publishing.
;;;
;;; In similar way the RCD Template Interpolation System may be used
;;; to expand HTML templates.
;;;
;;; --- begin of HTML template ---
;;; <html>
;;;   <head>
;;;     <title>⟦ (xml-escape "Title is within <title>TITLE</title>") ⟧</title>
;;;   </head>
;;;   <body>
;;;     ⟦ markdown ⟧
;;;   </body>
;;; </html>
;;; --- end of HTML template ---
;;;
;;; when such HTML template is expanded, it would look as following:
;;;
;;; --- begin of HTML template ---
;;; <html>
;;;   <head>
;;;     <title>Title is within &lt;title&gt;TITLE&lt;/title&gt;</title>
;;;   </head>
;;;   <body>
;;;      <h1>Today's prices</h1>
;;;        <p>Today's gold price is € 47530.79</p>
;;;   </body>
;;; </html>
;;; --- end of HTML template ---
;;;
;;; Example of email template expansion:
;;;
;;; --- begin of email template ---
;;; Hello ⟦ hello-name ⟧,
;;;
;;; Last time I have sent my email to you on ⟦ (last-date-email-sent contact-id) ⟧
;;; and still have not heard from you. What happened?
;;;
;;; To unsubscribe click here: ⟦ (unsubscribe-url contact-id) ⟧
;;; --- end of email template ---
;;;
;;; such email template would then expand to following:
;;; 
;;; --- begin of email template ---
;;; Hello John,
;;;
;;; Last time I have sent my email to you on May 24th 2021,
;;; and still have not heard from you. What happened?
;;;
;;; To unsubscribe click here: https://www.example.com/unsubscribe?user=John
;;; --- end of email template ---
;;;
;;; You may supply HASH argument to this function, such as:
;;; 
;;; (rcd-template-eval text nil hash)
;;;
;;; which hash values will take precedence over global variables, when
;;; interpolating something like ⟦ hash-key ⟧
;;; 
;;; RCD is acronym for Reach, Connect, Deliver, my personal
;;; principle and formula for Wealth.
;;;
;;; The RCD Template Interpolation System for Emacs is used in mass
;;; communication systems such as bulk letter writing, mass SMS
;;; communication and the clients' follow-up mailing systems where it
;;; indirectly generates exchange of services and products for
;;; money. It is part of the RCD Notes suite.

;;; Change Log:

;;; Code:

(require 'subr-x)

(defcustom rcd-template-delimiter-open "⟦"
  "The opening delimiter for RCD Template Interpolation System."
  :group 'rcd
  :type 'string)

(defcustom rcd-template-delimiter-close "⟧"
  "The closing delimiter for RCD Template Interpolation System."
  :group 'rcd
  :type 'string)

(defun rcd-template-eval (string &optional delimiters hash)
  "Evaluates Emacs Lisp enclosed by `rcd-template-delimiter-open' and `rcd-template-delimiter-close'.

Optional DELIMITERS list may be provided to change default
delimiters, first list member has to be the opening delimiter and
second the closing delimiter.

HASH may be supplied to interpolate values from hash key ⟦ KEY
⟧. When HASH keys are strings, the hash table :test must be
'equal.

Space or new line has to follow `rcd-template-delimiter-open' and
precede `rcd-template-delimiter-close' for evaluation to get
invoked.

The function will evaluated to "" on any error.

Errors are reported on the standard error output."
  (let* ((delimiters (or delimiters (list rcd-template-delimiter-open rcd-template-delimiter-close)))
	 (open (car delimiters))
	 (close (cadr delimiters))
	 (add (length open))
	 (minus (length close))
	 (keys (if hash (hash-table-keys hash)))
	 (test (when hash (hash-table-test hash)))
	 (debug-on-error nil))
    (with-temp-buffer
      (insert string)
      (goto-char 0)
      (while (re-search-forward (rx (literal open)
				    (one-or-more (or blank "\n"))
				    (group (minimal-match (one-or-more anything)))
				    (one-or-more (or blank "\n"))
				    (literal close))
				nil t)
	(let* ((match-beginning (match-beginning 0))
	       (match-end (match-end 0))
	       (match (buffer-substring-no-properties
		       (+ add match-beginning) (- match-end minus)))
	       (lisp (condition-case error
			 (read-from-string match)
		       (error (princ (format "rcd-template-eval: `read-from-string' error: %s for match: '%s'" error match))
			      "")))
	       (lisp (if (listp lisp) (car lisp) lisp))
	       (match (string-trim (prin1-to-string lisp)))
	       (parenthesis (substring match 0 1))
	       (lisp (car (read-from-string match)))
	       (value (condition-case error
			  (if (and 
			       (not (string= parenthesis "("))
			      keys 
			    (member 
			     (cond ((eql test 'eql) lisp)
				   ((eql test 'equal) (symbol-name lisp))
				   (t lisp))
			     keys))
 
			      (gethash 
			       (cond ((eql test 'eql) lisp)
				     ((eql test 'equal) (symbol-name lisp))
				     (t lisp))
			       hash "")

			    (eval lisp))
			(error (princ (format "rcd-template-eval: `eval' error: %s for match: '%s'" error match))
			       "")))
	       (value (cond ((null value) "")
			    (t (format "%s" value)))))
	  (delete-region match-beginning match-end)
	  (insert (format "%s" value))))
      (buffer-string))))

(defun rcd-template-buffer-preview ()
  (interactive)
  (let* ((current (current-buffer))
	 (buffer (format "Preview of buffer %s" current))
	 (string (buffer-string))
	 (mode major-mode)
	 (point (point)))
    (pop-to-buffer-same-window buffer)
    (insert (rcd-template-eval string))
    (goto-char point)
    (funcall mode)
    (message buffer)))

(defun rcd-template-insert-delimiters ()
  "Quickly insert the template evaluation snippet such as ⟦ () ⟧
and move cursor into parenthesis."
  (interactive)
  (insert (format "%s () %s" rcd-template-delimiter-open rcd-template-delimiter-close))
  (backward-char (+ 2 (length rcd-template-delimiter-close))))

(provide 'rcd-template)

;;; rcd-template.el ends here

Use it with parenthesis only

Parenthesis in normal prose almost never stand alone like " ( word ) " and will rather be used as "(word)". The fact that parenthesis normally do not stand alone in the text may be used or abused to use parenthesis to embed the Emacs Lisp into any text.

Then following:

My cow is now ( (psql-age "2017-01-01") ) old.

would expand into following:

My cow is now 4 years 4 months 2 days old.

In my belief this Emacs Lisp templating engine supports most of features described on the following hyperlinks:

Tame Emacs compiler

If it happens that you are using local variables that appear not to be used in your Emacs Lisp, just use ignore.

Following code, if used with lexical-binding TRUE, would cause compiler warnings as variable MY-VARIABLE would apparently not be used, even though it is used.

(let ((template "[ MY-VARIABLE ]")
      (MY-VARIABLE "Leonard"))
  (rcd-template-eval template))

To solve the problem, simply use ignore:

(let ((template "[ MY-VARIABLE ]")
      (MY-VARIABLE "Leonard"))
  (ignore "Just to ignore warnings" MY-VARIABLE)
  (rcd-template-eval template))

For more information see the discussion on Users list for the GNU Emacs.

Comments from Help GNU Emacs mailing list

don’t abuse eval ;-)

— Stefan Monnier, Emacs Developer

I know I sound like a broken clock, but I think a better answer is to avoid eval: instead of taking expressions (that you’d pass to eval) arrange to receive functions (which you’d pass to funcall or apply). Then you can pass those functions the data they need (e.g. the value of things like unsubscribe-text).

— Stefan Monnier, Emacs Developer

If I would want to do something like that, I’d just write a lispy HTML DSL so that I could write

(foo/html
 (foo/body
  (foo/title (xml-escape "My new <title>"))))

which is shorter, easier to type, and doesn’t need `eval'.

— Tassilo Horn, Emacs User
Jean Louis wrote:
It is just one of useful functions.

Hahaha, let’s put this in the docstring :)

— Emmanuel Berg, Emacs User
Jean Louis wrote:
So I do not know how to use dynamical bindings

Even more reason not to do it…​

— Emmanuel Berg, Emacs User

Lexical is much better, so put ` -- lexical-binding: t; --` in the beginning (first line) of every file including ~/.emacs

— Emmanuel Berg, Emacs User
And I did not put it.

Oh, you could also use the second argument of `eval' to specify a lexical environment. That would also help to get rid of the warnings.

— Michael Heerdegen, Emacs Developer

Independent from the question whether your usage of `eval' is good or valid - there must be some real problem here: if the compiler tells that the lexical variables are unused, their values will not be available in you `eval' call - you would have to create dynamical bindings for that.

I recommend to take Stefan’s suggestions into consideration, he knows what he is talking about.

— Michael Heerdegen, Emacs Developer

I tried to offer an explanation which you chose to ignore . I think the problem is more subtle, and thus difficult to explain in just a few words.

If it were that easy to explain clearly, I think Stefan would have done that already.

It’s not as clear-cut as "should not be used". Rather something along the lines of "should be used with care". Template expansion is one of those borderline cases, but if you look at all those modern template expanders out there, you’ll realise that they all have some kind of "custom evaluator", where you explicitly provide an environment, instead of just punting to `eval' and saying "use…​ uh, whatever".

— Tomas, Emacs User

Similar tools I have used for WWW page generation

Since about 26 years 4 months 2 days I am creating web pages. Sometimes I had 40,000 pages online used for sales, marketing and finally wealth generation.

In my past I have been using various markup and templating systems in past such as following:

  1. In Perl, self-generated simple variable expansions;

  2. The GNU macro processor M4 to expand variables, codes in number of websites. I consider this one more powerful than others, but not well integrated. If I am working within Emacs, and editing text with Emacs, then it is best to preprocess text by using Emacs.

  3. I have been using Asciidoc if I am not mistaken even before Markdown came to world. This page is created with Asciidoctor. Today I am using Website Revision System that can create each page with a different markup how I wish and want it.

  4. Markdown was my favorite for long time, using it since its inception in 2004 if I remember well, while Discount Markdown appears to be the fastest one.

  5. In Perl I have tested all templating engines and found that Text::NeatTemplate is the fastest one. I have been using it to generate thousands of pages and even more money.

  6. When I have switched to Common Lisp, I have been heavily using CL-EMB templating engine.

  7. Since I have started managing all the database from Emacs itself, it became tiresome to always invoke external programs, that is why I have created this RCD Template Interpolation System for Emacs.

Comments are welcome

You may easily contact me by email to my current web comment email address: 2021-05-02@gnu.support.

Prepared with GNU Emacs 28.0.50 (build 1, x86_64-pc-linux-gnu, X toolkit, cairo version 1.17.4, Xaw3d scroll bars) of 2021-05-02.

GNU Affero General Public License Version 3

Copyright © 2021-05-01 22:01:28.792282+02 Jean Marc Louis

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License along with this program. If not, see https://www.gnu.org/licenses/.