>> For others, if we are at it, I think it is much better to type >> (princ default-directory) than (eval default-directory), if you gonna have >> that example in a doc string or manual. > > That example is already in the docstring and the manual. > As for princ, it has a side effect of putting things into standard > output, which may be unexpected. So, I do not think that it is better > than eval. > Maybe simply identity? (although it feels long)
I also realized princ was bad, after I sent the mail, but frankly didn't want to bother more with that. Both eval and princ are bad. Both work in this case only when value is of string type. One could use prin1-to-string, but that is so long. I think you could relax there the requirement, and use princ under the hood, instead of insert in org-capture-expand-embedded-elisp, without introducing backward incompatibility. Since org-capture-fill-template is always printing to a buffer, and return buffer string, it should be OK to do. There is no reason to enforce string in %(). Just a suggestion. >> Also, while the example gives an example of how to use a variable, what >> I said from the beginning, would be good to state that explicitly in the >> text: >> >> "If you need to use variables, you have to wrap them in some function call, >> i.e. (princ var)", > > Good idea. Let's first decide whether we want to replace eval with > something else and then add explicit clarifications about variables. > >>> I am open to your patches, as long as some more people find them useful. >> >> We have seen it was more interesting to point out which doc string I >> refer to, than to reflect over anything else I said, so I guess it >> is not very popular demand, which is of course ok as well :). > > This is not true. I just wanted to focus on something we can improve > quickly. Your patches need to be discussed in more details, and there is > a reason I pointed your to doct package. More on this below. It is OK man. 10k people viewed posts on Reddit, 2 answered :). I would say it is a low demand :). About the doct package, I am not sure it does the same thing I suggested, but I haven't study it yet. >>>> There are also two other problems. org-capture-fill-template calls >>>> unconditionally untabify. There is no note why the original author calls >>>> this, but that seems unfortunate, since it does respect the users >>>> choice. A user might wish to have a tab in their expanded template. For >>>> my part for example, consider a templatized Makefile, something like >>>> this: ... >>> >>> Can you please open a new thread for this? >>> >>>> Finally, for some reason, each expansion ends up with a new line? Is >>>> that some necessary feature that org-capture templates rely on? >>>> >>>> Consider if I would to generate a file with a name expanded from a >>>> tempalte: >>>> >>>> %(project-name).asd >>>> %(project-name).asd >>>> >>>> I have to call (string-trim ...) everywhere where I call >>>> org-capture-fill-template. Can we remove that ending new line, or is >>>> that expected in expanded templates? >>> >>> Maybe. Again, could you please open a new email thread? > >> About opening other threads: nobody has answered on the other one, so I >> guess opening more threads is not best use of the time and energy, >> neither mine nor yours. > > I asked you to open new threads for a reason. Mixing too many things in > the discussion will just create a mess. For context, I actually believe I understand that; I am just not interested in long discussions on Emacs mailing list. Forgive me, been there done that. If it wasn't of the interest to add this extra patch, and org-capture would continue to be used just for shorter capture templates as it is used by now, than it is not a problem. Even if I agree with what you say later on that it is a bug, and that was my intention to say too, it is probably not important, so lets not engage in long discussions 🙂. That was the reasoning. > that unconditional untabification is simply a bug -- consider code > blocks in template that are in python. Stripping tabs will be an error > there. Similar for a new line. Regardless what we decide about changing > template syntax for Elisp expansion, these bugs need to be fixed. That's > why I asked to open new threads. That is my stand about untabify. As you say Python, and for example as I tried to illustrate Makefiles. I am not sure it is worth much discussions there tbh, and it is easier for you to just comment away that line than to apply a separate pacth if I or someone else would to send it in. For the newline at the end; the author explicitly say they want it, in a comment, so I am not equally sure if it is a bug or not. I don't understand though why they need it. Might be due to how they use expansions elsewhere in org. >>> Have you seen https://github.com/progfolio/doct ? >> >> I did a web-search after you mentioned it previously and found Nicholas >> package. But I haven't had time to study it, just glanced over the >> readme. What is with it? As I understand he introduces different syntax, >> at least what README says. I have no idea if it adds more capabilities, >> if he has a different implementation completely or if it runs on the >> existing org-capture. > > There is a good reason why I mentioned doct. It has somewhat similar > ideas. Moreover, at some point, we asked to include that package into > Org. > > Let me provide a more accurate link: > https://github.com/progfolio/doct?tab=readme-ov-file#keyword-expansion > The %{keyword} syntax from doct is pretty close to what you are talking > about. Please check. It may be a better way to address your request. Aha. Ok, thanks. As said I haven't study it, just glanced over it. The reason I suggested my patch was because I use org-capture to implement a little "project capture" framework, so I am already using it. It would be a minimal addition since org-capture already does something similar and I would not need to use a third party library. But it is not a whole world. I have already implemented somewhat minimal framework to do something similar. >> I don't introduce any new syntax, I am just trying to fix a fundamental >> flow, which shouldn't be there from the very beginning to make the thing >> easier to work with. In like ~50 sloc, no additional libraries needed. >> That would make writing bigger templates, like entire files easier >> and less clunky, as shown with attached example. I can buy it is not >> interesting to other people, so it is fine :). > > In my book, changing the meaning of the existing syntax is much worse > than adding new. The fact that it is opt-in, does not change this fact. > Several possible interpretations of template syntax will lead to > confusion for some people. > > If we were to consider the feature you are asking for, I'd go for > something closer to doct keyword, maybe allowing %{keyword} to refer to > %{elisp-variable} is :keyword is not defined in the template. WDYT? I agree. As mentioned in the above paragraph, I implemented something similar already. I saw the glass as half full rather than half empty, and took the opportunity to scale away anything I didn't need. For the same reason, to avoid possible confusion I have chosen to use "$" as the placeholder symbol. Basically I need just $() to eval lisp, but I have kept $[], $<> and introduced also ${} as a shortcut for environment variables. I haven't had time to test extensively yet, due to hollidays and family time. I have attached my new little framework as a curiosa; still WIP. I am happy to get critique and suggestions if anyone is interesting to take a look at it. I am still thinking if I want to keep interactive templates or not; but I wanted something to use in programmatic expansions for file generators, so I am not sure if I want interactivey at all. Best regards and happy New Year to all.
;;; template-fill.el --- Template expansion in files -*- lexical-binding: t; -*- ;; Copyright (C) 2025 Arthur Miller ;; Author: Arthur Miller ;; Keywords: ;; URL: ;; ;; This file is not part of GNU Emacs. ;; ;; GNU Emacs 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. ;; GNU Emacs 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 GNU Emacs. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;;; Code: (require 'cl-lib) (defvar template-fil-syntax (copy-syntax-table emacs-lisp-mode-syntax-table)) (aset template-fil-syntax ?< '(4 . 60)) (aset template-fil-syntax ?\[ '(4 . 93)) (aset template-fil-syntax ?{ '(4 . 125)) (defun template-fill (template &rest properties) "Fill a TEMPLATE and return the filled template as a string. TEMPLATE is a string in which following placeholders are allowed: $[filename] - insert content of FILENAME $(expression) - evaluate lisp EXPRESSION and insert the result $<timeformat> - insert time formatted according to TIMEFORMAT ${ENV} - insert value of environment variable ENV PROPERTIES is a property list with properties that can be passed to the template parser. Following properties are recognized: :timespec - use insted of current-time in TIMEFORMAT template. :target-file - save expanded template into a file, otherwise return as a string" (cl-macrolet ((with-template-string (&rest body) (with-syntax-table template-fil-syntax `(progn (delete-char -1) ;$ (let* ((beg (point)) (open-char (char-after)) (end (progn (forward-sexp) (point))) (close-char (char-before)) (it (buffer-substring-no-properties (1+ beg) (1- end)))) (delete-region beg end) (condition-case error (princ ,@body (current-buffer)) (error (insert (format "$!%ccould not insert %s: %s%c" open-char close-char it error))))))))) (cl-labels ((escaped () (when (and (> (- (point) (point-min)) 1) (= (char-before (1- (point))) ?\\)) (forward-char -1) (delete-char -1) (forward-char) t))) (with-temp-buffer (setq buffer-file-name nil) (insert template) (goto-char (point-min)) (while (search-forward "$" nil t) (pcase (char-after) (?\[ (unless (escaped) (with-template-string (insert-file-contents-literally (expand-file-name it))))) (?\( (unless (escaped) (with-template-string (eval (read it))))) (?< (unless (escaped) (with-template-string (format-time-string it (or (plist-get properties :timestamp) (current-time)))))) (?{ (unless (escaped) (with-template-string (or (getenv it) "")))))) (let ((file (plist-get properties :target-file))) (if file (write-region nil nil file 0) (buffer-substring-no-properties (point-min) (point-max)))))))) (provide 'template-fill) ;;; org-capture.el ends here
