Document format-spec and expand the modifiers it supports
* doc/lispref/text.texi (Interpolated Strings): New section. * lisp/format-spec.el (format-spec--parse-modifiers) (format-spec--pad): New functions. (format-spec): Support more format modifiers (bug#32931).
This commit is contained in:
parent
74579d3d2b
commit
936d074d7c
4 changed files with 157 additions and 7 deletions
|
@ -58,6 +58,7 @@ the character after point.
|
|||
of another buffer.
|
||||
* Decompression:: Dealing with compressed data.
|
||||
* Base 64:: Conversion to or from base 64 encoding.
|
||||
* Interpolated Strings:: Formatting Customizable Strings.
|
||||
* Checksum/Hash:: Computing cryptographic hashes.
|
||||
* GnuTLS Cryptography:: Cryptographic algorithms imported from GnuTLS.
|
||||
* Parsing HTML/XML:: Parsing HTML and XML.
|
||||
|
@ -4626,6 +4627,72 @@ If optional argument @var{base64url} is is non-@code{nil}, then padding
|
|||
is optional, and the URL variant of base 64 encoding is used.
|
||||
@end defun
|
||||
|
||||
|
||||
@node Interpolated Strings
|
||||
@section Formatting Customizable Strings
|
||||
|
||||
It is, in some circumstances, useful to present users with a string to
|
||||
be customized that can then be expanded programmatically. For
|
||||
instance, @code{erc-header-line-format} is @code{"%n on %t (%m,%l)
|
||||
%o"}, and each of those characters after the percent signs are
|
||||
expanded when the header line is computed. To do this, the
|
||||
@code{format-spec} function is used:
|
||||
|
||||
@defun format-spec format specification &optional only-present
|
||||
@var{format} is the format specification string as in the example
|
||||
above. @var{specification} is an alist that has elements where the
|
||||
@code{car} is a character and the @code{cdr} is the substitution.
|
||||
|
||||
If @code{ONLY-PRESENT} is @code{nil}, errors will be signalled if a
|
||||
format character has been used that's not present in
|
||||
@var{specification}. If it's non-@code{nil}, that format
|
||||
specification is left verbatim in the result.
|
||||
@end defun
|
||||
|
||||
Here's a trivial example:
|
||||
|
||||
@example
|
||||
(format-spec "su - %u %l"
|
||||
`((?u . ,(user-login-name))
|
||||
(?l . "ls")))
|
||||
=> "su - foo ls"
|
||||
@end example
|
||||
|
||||
In addition to allowing padding/limiting to a certain length, the
|
||||
following modifiers are can be used:
|
||||
|
||||
@table @asis
|
||||
@item @samp{0}
|
||||
Use zero padding.
|
||||
|
||||
@item @samp{@ }
|
||||
User space padding.
|
||||
|
||||
@item @samp{-}
|
||||
Pad to the right.
|
||||
|
||||
@item @samp{^}
|
||||
Use upper case.
|
||||
|
||||
@item @samp{_}
|
||||
Use lower case.
|
||||
|
||||
@item @samp{<}
|
||||
If the length needs to limited, remove characters from the left.
|
||||
|
||||
@item @samp{>}
|
||||
Same as previous, but remove characters from the right.
|
||||
@end table
|
||||
|
||||
If contradictory modifiers are used (for instance, both upper- and
|
||||
lower case), then what happens is undefined.
|
||||
|
||||
As an example, @samp{"%<010b"} means ``insert the @samp{b} expansion,
|
||||
but pad with leading zeroes if it's less than ten characters, and if
|
||||
it's more than ten characters, shorten by removing characters from the
|
||||
left''.
|
||||
|
||||
|
||||
@node Checksum/Hash
|
||||
@section Checksum/Hash
|
||||
@cindex MD5 checksum
|
||||
|
|
7
etc/NEWS
7
etc/NEWS
|
@ -2293,6 +2293,13 @@ argument is 'iec' and the empty string otherwise. We recomment a
|
|||
space or non-breaking space as third argument, and "B" as fourth
|
||||
argument, circumstances allowing.
|
||||
|
||||
+++
|
||||
** `format-spec' has been expanded with several modifiers to allow
|
||||
greater flexibility when customizing variables. The modifiers include
|
||||
zero-padding, upper- and lower-casing, and limiting the length of the
|
||||
interpolated strings. The function has now also been documented in
|
||||
the Emacs Lisp manual.
|
||||
|
||||
|
||||
* Changes in Emacs 27.1 on Non-Free Operating Systems
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
|
||||
;;; Code:
|
||||
|
||||
(require 'subr-x)
|
||||
|
||||
(defun format-spec (format specification &optional only-present)
|
||||
"Return a string based on FORMAT and SPECIFICATION.
|
||||
FORMAT is a string containing `format'-like specs like \"su - %u %k\",
|
||||
|
@ -32,9 +34,22 @@ to values.
|
|||
|
||||
For instance:
|
||||
|
||||
(format-spec \"su - %u %k\"
|
||||
(format-spec \"su - %u %l\"
|
||||
`((?u . ,(user-login-name))
|
||||
(?k . \"ls\")))
|
||||
(?l . \"ls\")))
|
||||
|
||||
Each format spec can have modifiers, where \"%<010b\" means \"if
|
||||
the expansion is shorter than ten characters, zero-pad it, and if
|
||||
it's longer, chop off characters from the left size\".
|
||||
|
||||
The following modifiers are allowed:
|
||||
|
||||
* 0: Use zero-padding.
|
||||
* -: Pad to the right.
|
||||
* ^: Upper-case the expansion.
|
||||
* _: Lower-case the expansion.
|
||||
* <: Limit the length by removing chars from the left.
|
||||
* >: Limit the length by removing chars from the right.
|
||||
|
||||
Any text properties on a %-spec itself are propagated to the text
|
||||
that it generates.
|
||||
|
@ -52,16 +67,31 @@ where they are, including \"%%\" strings."
|
|||
(unless only-present
|
||||
(delete-char 1)))
|
||||
;; Valid format spec.
|
||||
((looking-at "\\([-0-9.]*\\)\\([a-zA-Z]\\)")
|
||||
(let* ((num (match-string 1))
|
||||
(spec (string-to-char (match-string 2)))
|
||||
((looking-at "\\([-0 _^<>]*\\)\\([0-9.]*\\)\\([a-zA-Z]\\)")
|
||||
(let* ((modifiers (match-string 1))
|
||||
(num (match-string 2))
|
||||
(spec (string-to-char (match-string 3)))
|
||||
(val (assq spec specification)))
|
||||
(if (not val)
|
||||
(unless only-present
|
||||
(error "Invalid format character: `%%%c'" spec))
|
||||
(setq val (cdr val))
|
||||
(setq val (cdr val)
|
||||
modifiers (format-spec--parse-modifiers modifiers))
|
||||
;; Pad result to desired length.
|
||||
(let ((text (format (concat "%" num "s") val)))
|
||||
(let ((text (format "%s" val)))
|
||||
(when num
|
||||
(setq num (string-to-number num))
|
||||
(setq text (format-spec--pad text num modifiers))
|
||||
(when (> (length text) num)
|
||||
(cond
|
||||
((memq :chop-left modifiers)
|
||||
(setq text (substring text (- (length text) num))))
|
||||
((memq :chop-right modifiers)
|
||||
(setq text (substring text 0 num))))))
|
||||
(when (memq :uppercase modifiers)
|
||||
(setq text (upcase text)))
|
||||
(when (memq :lowercase modifiers)
|
||||
(setq text (downcase text)))
|
||||
;; Insert first, to preserve text properties.
|
||||
(insert-and-inherit text)
|
||||
;; Delete the specifier body.
|
||||
|
@ -75,6 +105,34 @@ where they are, including \"%%\" strings."
|
|||
(error "Invalid format string")))))
|
||||
(buffer-string)))
|
||||
|
||||
(defun format-spec--pad (text total-length modifiers)
|
||||
(if (> (length text) total-length)
|
||||
;; The text is longer than the specified length; do nothing.
|
||||
text
|
||||
(let ((padding (make-string (- total-length (length text))
|
||||
(if (memq :zero-pad modifiers)
|
||||
?0
|
||||
?\s))))
|
||||
(if (memq :right-pad modifiers)
|
||||
(concat text padding)
|
||||
(concat padding text)))))
|
||||
|
||||
(defun format-spec--parse-modifiers (modifiers)
|
||||
(let ((elems nil))
|
||||
(mapc (lambda (char)
|
||||
(when-let ((modifier
|
||||
(pcase char
|
||||
(?0 :zero-pad)
|
||||
(?\s :space-pad)
|
||||
(?^ :uppercase)
|
||||
(?_ :lowercase)
|
||||
(?- :right-pad)
|
||||
(?< :chop-left)
|
||||
(?> :chop-right))))
|
||||
(push modifier elems)))
|
||||
modifiers)
|
||||
elems))
|
||||
|
||||
(defun format-spec-make (&rest pairs)
|
||||
"Return an alist suitable for use in `format-spec' based on PAIRS.
|
||||
PAIRS is a list where every other element is a character and a value,
|
||||
|
|
|
@ -37,4 +37,22 @@
|
|||
(should (equal (format-spec "foo %b %z %% zot" '((?b . "bar")) t)
|
||||
"foo bar %z %% zot")))
|
||||
|
||||
(ert-deftest test-format-modifiers ()
|
||||
(should (equal (format-spec "foo %10b zot" '((?b . "bar")))
|
||||
"foo bar zot"))
|
||||
(should (equal (format-spec "foo % 10b zot" '((?b . "bar")))
|
||||
"foo bar zot"))
|
||||
(should (equal (format-spec "foo %-010b zot" '((?b . "bar")))
|
||||
"foo bar0000000 zot"))
|
||||
(should (equal (format-spec "foo %0-10b zot" '((?b . "bar")))
|
||||
"foo bar0000000 zot"))
|
||||
(should (equal (format-spec "foo %^10b zot" '((?b . "bar")))
|
||||
"foo BAR zot"))
|
||||
(should (equal (format-spec "foo %_10b zot" '((?b . "BAR")))
|
||||
"foo bar zot"))
|
||||
(should (equal (format-spec "foo %<4b zot" '((?b . "longbar")))
|
||||
"foo gbar zot"))
|
||||
(should (equal (format-spec "foo %>4b zot" '((?b . "longbar")))
|
||||
"foo long zot")))
|
||||
|
||||
;;; format-spec-tests.el ends here
|
||||
|
|
Loading…
Add table
Reference in a new issue