Make format-spec accept function substitutions

* lisp/format-spec.el (format-spec): Accept a function producing the
substitution for a character.
* doc/lispref/strings.texi (Custom Format Strings): Document the
above change.
* test/lisp/format-spec-tests.el (format-spec/function): New test.
Ref. https://lists.gnu.org/r/emacs-devel/2022-09/msg01875.html
This commit is contained in:
Stefan Kangas 2022-09-27 18:16:51 +02:00
parent 423bdd5f7f
commit 5281946fbf
4 changed files with 37 additions and 2 deletions

View file

@ -1293,6 +1293,11 @@ The order of specifications in @var{template} need not correspond to
the order of associations in @var{spec-alist}.
@end itemize
REPLACEMENT can also be a function taking no arguments, and returning
a string to be used for the replacement. It will only be called when
the corresponding LETTER is used in the TEMPLATE. This is useful, for
example, to avoid prompting for input unless it is needed.
The optional argument @var{ignore-missing} indicates how to handle
specification characters in @var{template} that are not found in
@var{spec-alist}. If it is @code{nil} or omitted, the function

View file

@ -3926,6 +3926,12 @@ the same but works by modifying LIST destructively.
---
** 'string-split' is now an alias for 'split-string'.
+++
** 'format-spec' now accepts functions in the replacement.
The function is called only when used in the format string. This is
useful to avoid side-effects such as prompting, when the value is not
actually being used for anything.
+++
** The variable 'max-specpdl-size' has been made obsolete.
Now 'max-lisp-eval-depth' alone is used for limiting Lisp recursion

View file

@ -59,6 +59,18 @@ value associated with ?b in SPECIFICATION, either padding it with
leading zeros or truncating leading characters until it's ten
characters wide\".
the substitution for a specification character can also be a
function, taking no arguments and returning a string to be used
for the replacement. It will only be called if FORMAT uses that
character. For example:
(format-spec \"%n\"
\\=`((?n . ,(lambda ()
(read-number \"Number: \")))))
Note that it is best to make sure the function is not quoted,
like above, so that it is compiled by the byte-compiler.
Any text properties of FORMAT are copied to the result, with any
text properties of a %-spec itself copied to its substitution.
@ -94,14 +106,15 @@ is returned, where each format spec is its own element."
(width (match-string 2))
(trunc (match-string 3))
(char (string-to-char (match-string 4)))
(text (assq char specification)))
(text (let ((res (cdr (assq char specification))))
(if (functionp res) (funcall res) res))))
(when (and split
(not (= (1- beg) split-start)))
(push (buffer-substring split-start (1- beg)) split-result))
(cond (text
;; Handle flags.
(setq text (format-spec--do-flags
(format "%s" (cdr text))
(format "%s" text)
(format-spec--parse-flags flags)
(and width (string-to-number width))
(and trunc (car (read-from-string trunc 1)))))

View file

@ -148,6 +148,17 @@
(format-spec fmt '((?b . "asd") (?a . "fgh")))
#("fgh%asdasd" 0 3 (a b) 3 4 (c d) 7 10 (e f))))))
(ert-deftest format-spec/function ()
(let* (called
(spec `((?a . "foo")
(?f . ,(lambda ()
(setq called t)
"bar")))))
(should (equal (format-spec "%a" spec) "foo"))
(should-not called)
(should (equal (format-spec "%f" spec) "bar"))
(should called)))
(ert-deftest format-spec-unknown ()
(should-error (format-spec "foo %b %z zot" '((?b . "bar"))))
(should-error (format-spec "foo %b %%%z zot" '((?b . "bar"))))