2

I have this in my init file

(use-package autoinsert
  :ensure t
  :init
  ;; Don't want to be prompted before insertion:
  (setq auto-insert-query nil)

  (setq auto-insert-directory (locate-user-emacs-file "templates"))
  (add-hook 'find-file-hook 'auto-insert)
  (auto-insert-mode 1)

  :config
  (define-auto-insert "poolchem.org?$" "poolchem.org"))

IOW, I have the template file poolchem.org in ~/.emacs.d/templates/, which contains six tables. I went this route because it seemed easier than hand-tooling such a big thing in (customize-variable 'auto-insert-alist). But I would like various bits of embedded initialization code to run upon creating poolchem.org, e.g.,

(org-read-date nil nil "++mon" nil (org-time-string-to-time "2020-03-31"))

should place the date of the next Monday after 2020-03-31 in the text upon file open. How can I do this? I've experimented with eval of org-sbe in # local variables: ... but that was handy for running code blocks upon file open, not individual embedded functions.

Drew
  • 75,699
  • 9
  • 109
  • 225
147pm
  • 2,907
  • 1
  • 18
  • 39

1 Answers1

3

You can specify a vector of actions for each entry in auto-insert-alist.
This way you can insert the contents of the template file with the first action and then use a function that searches the new file for special elisp forms that are to be replaced with their evaluated results.

Below you find the source code for one such function.
You just wrap your code in the template file with (auto-insert-init-form ...) and that form will be evaluated like progn.
The last form of the code must return a string that is used to replace the code in the target file.

(defcustom auto-insert-init-form 'auto-insert-init-form
  "Symbol identifying init forms in template files."
  :group 'auto-insert
  :type 'symbol)

(defun my-eval-auto-insert-init-form ()
  "Evaluate (AUTO-INSERT-INIT-FORM ...) in autoinsert templates.
Thereby, AUTO-INSERT-INIT-FORM stands for the symbol defined by
the customizable variable `auto-insert-init-form'.
\(auto-insert-init-form ...) works like `progn'.
Applied in the newly created file it should return the string
that replaces the form."
  (goto-char (point-min))
  (cl-letf (((symbol-function auto-insert-init-form) #'progn))
    (while (re-search-forward "(auto-insert-init-form[[:space:]]" nil t)
      (let* ((beg (goto-char (match-beginning 0)))
         (end (with-syntax-table emacs-lisp-mode-syntax-table
            (forward-sexp)
            (point)))
         (str (eval (read (buffer-substring beg end)))))
    (delete-region beg end)
    (insert str)))))

(define-auto-insert
  "poolchem.org?\\'"
  ["poolchem.org"
   my-eval-auto-insert-init-form])

Example of the contents of a template file:

| first | table |

#+DATE: (auto-insert-init-form (org-read-date nil nil "++mon" nil (org-time-string-to-time "2020-03-31")))

#+AUTHOR: (auto-insert-init-form (read-string "Author: "))

| SOME | OTHER | TABLE |
|      |       |       |
Tobias
  • 32,569
  • 1
  • 34
  • 75
  • Thanks. This worked straight out of the box. Curious, though. Why did you create the symbol `auto-insert-init-form` with a `defcustom` when there doesn't seem to be anything to customize about it? Would a `defvar` have sufficed? – 147pm Feb 26 '20 at 06:12
  • @147pm The `defcustom` allows you to use something else than `auto-insert-init-form` as form to be evaluated after `auto-insert`. Whether this makes sense or not is up to the user. – Tobias Feb 28 '20 at 04:57