From 40d16332597d3aa564c9950ae1831faf6867c71a Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Tue, 29 Dec 2020 03:04:51 +0100 Subject: [PATCH] Add a SPLIT parameter to `format-spec' * doc/lispref/strings.texi (Custom Format Strings): Document it. * lisp/format-spec.el (format-spec): Add an optional parameter to return a list of strings (bug#33740). --- doc/lispref/strings.texi | 12 +++- etc/NEWS | 5 ++ lisp/format-spec.el | 103 +++++++++++++++++++-------------- test/lisp/format-spec-tests.el | 10 ++++ 4 files changed, 85 insertions(+), 45 deletions(-) diff --git a/doc/lispref/strings.texi b/doc/lispref/strings.texi index a31e71d5260..4ac5057454f 100644 --- a/doc/lispref/strings.texi +++ b/doc/lispref/strings.texi @@ -1216,7 +1216,7 @@ The function @code{format-spec} described in this section performs a similar function to @code{format}, except it operates on format control strings that use arbitrary specification characters. -@defun format-spec template spec-alist &optional ignore-missing +@defun format-spec template spec-alist &optional ignore-missing split This function returns a string produced from the format string @var{template} according to conversions specified in @var{spec-alist}, which is an alist (@pxref{Association Lists}) of the form @@ -1258,6 +1258,16 @@ any; if it is @code{delete}, those format specifications are removed from the output; any other non-@code{nil} value is handled like @code{ignore}, but any occurrences of @samp{%%} are also left verbatim in the output. + +If the optional argument @var{split} is non-@code{nil}, instead of +returning a single string, @code{format-spec} will split the result +into a list of strings, based on where the substitutions were +performed. For instance: + +@example +(format-spec "foo %b bar" '((?b . "zot")) nil t) + @result{} ("foo " "zot" " bar") +@end example @end defun The syntax of format specifications accepted by @code{format-spec} is diff --git a/etc/NEWS b/etc/NEWS index f8282696e46..cd4006170d2 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2179,6 +2179,11 @@ In order for the two functions to behave more consistently, length, and also supports format specifications that include a truncating precision field, such as "%.2a". ++++ +** 'format-spec' now takes an optional SPLIT parameter. +If non-nil, 'format-spec' will split the resulting string into a list +of strings, based on where the format specs (and expansions) were. + --- ** New function 'color-values-from-color-spec'. This can be used to parse RGB color specs in several formats and diff --git a/lisp/format-spec.el b/lisp/format-spec.el index 6af79a44167..3abcd5183a3 100644 --- a/lisp/format-spec.el +++ b/lisp/format-spec.el @@ -25,7 +25,7 @@ ;;; Code: ;;;###autoload -(defun format-spec (format specification &optional ignore-missing) +(defun format-spec (format specification &optional ignore-missing split) "Return a string based on FORMAT and SPECIFICATION. FORMAT is a string containing `format'-like specs like \"su - %u %k\". SPECIFICATION is an alist mapping format specification characters @@ -68,50 +68,65 @@ error; if it is the symbol `ignore', leave those %-specs verbatim in the result, including their text properties, if any; if it is the symbol `delete', remove those %-specs from the result; otherwise do the same as for the symbol `ignore', but also leave -any occurrences of \"%%\" in FORMAT verbatim in the result." +any occurrences of \"%%\" in FORMAT verbatim in the result. + +If SPLIT, instead of returning a single string, a list of strings +is returned, where each format spec is its own element." (with-temp-buffer - (insert format) - (goto-char (point-min)) - (while (search-forward "%" nil t) - (cond - ;; Quoted percent sign. - ((= (following-char) ?%) - (when (memq ignore-missing '(nil ignore delete)) - (delete-char 1))) - ;; Valid format spec. - ((looking-at (rx (? (group (+ (in " 0<>^_-")))) - (? (group (+ digit))) - (? (group ?. (+ digit))) - (group alpha))) - (let* ((beg (point)) - (end (match-end 0)) - (flags (match-string 1)) - (width (match-string 2)) - (trunc (match-string 3)) - (char (string-to-char (match-string 4))) - (text (assq char specification))) - (cond (text - ;; Handle flags. - (setq text (format-spec--do-flags - (format "%s" (cdr text)) - (format-spec--parse-flags flags) - (and width (string-to-number width)) - (and trunc (car (read-from-string trunc 1))))) - ;; Insert first, to preserve text properties. - (insert-and-inherit text) - ;; Delete the specifier body. - (delete-region (point) (+ end (length text))) - ;; Delete the percent sign. - (delete-region (1- beg) beg)) - ((eq ignore-missing 'delete) - ;; Delete the whole format spec. - (delete-region (1- beg) end)) - ((not ignore-missing) - (error "Invalid format character: `%%%c'" char))))) - ;; Signal an error on bogus format strings. - ((not ignore-missing) - (error "Invalid format string")))) - (buffer-string))) + (let ((split-start (point-min)) + (split-result nil)) + (insert format) + (goto-char (point-min)) + (while (search-forward "%" nil t) + (cond + ;; Quoted percent sign. + ((= (following-char) ?%) + (when (memq ignore-missing '(nil ignore delete)) + (delete-char 1))) + ;; Valid format spec. + ((looking-at (rx (? (group (+ (in " 0<>^_-")))) + (? (group (+ digit))) + (? (group ?. (+ digit))) + (group alpha))) + (let* ((beg (point)) + (end (match-end 0)) + (flags (match-string 1)) + (width (match-string 2)) + (trunc (match-string 3)) + (char (string-to-char (match-string 4))) + (text (assq char specification))) + (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-spec--parse-flags flags) + (and width (string-to-number width)) + (and trunc (car (read-from-string trunc 1))))) + ;; Insert first, to preserve text properties. + (insert-and-inherit text) + ;; Delete the specifier body. + (delete-region (point) (+ end (length text))) + ;; Delete the percent sign. + (delete-region (1- beg) beg)) + ((eq ignore-missing 'delete) + ;; Delete the whole format spec. + (delete-region (1- beg) end)) + ((not ignore-missing) + (error "Invalid format character: `%%%c'" char))) + (when split + (push (buffer-substring (1- beg) (point)) split-result) + (setq split-start (point))))) + ;; Signal an error on bogus format strings. + ((not ignore-missing) + (error "Invalid format string")))) + (if (not split) + (buffer-string) + (unless (= split-start (point-max)) + (push (buffer-substring split-start (point-max)) split-result)) + (nreverse split-result))))) (defun format-spec--do-flags (str flags width trunc) "Return STR formatted according to FLAGS, WIDTH, and TRUNC. diff --git a/test/lisp/format-spec-tests.el b/test/lisp/format-spec-tests.el index 11882217afb..cced8623330 100644 --- a/test/lisp/format-spec-tests.el +++ b/test/lisp/format-spec-tests.el @@ -178,4 +178,14 @@ (should (equal (format-spec "foo %>4b zot" '((?b . "longbar"))) "foo long zot"))) +(ert-deftest format-spec-split () + (should (equal (format-spec "foo %b bar" '((?b . "zot")) nil t) + '("foo " "zot" " bar"))) + (should (equal (format-spec "%b bar" '((?b . "zot")) nil t) + '("zot" " bar"))) + (should (equal (format-spec "%b" '((?b . "zot")) nil t) + '("zot"))) + (should (equal (format-spec "foo %b" '((?b . "zot")) nil t) + '("foo " "zot")))) + ;;; format-spec-tests.el ends here -- 2.39.5