aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars Ingebrigtsen2019-07-13 03:50:43 +0200
committerLars Ingebrigtsen2019-07-13 03:50:50 +0200
commit936d074d7c58bd4504b89a0b739b370312ae141a (patch)
tree254c2d3e4e2ae15aff37479bc7451e6fb9a54282
parent74579d3d2bb82f300a6f2d81b7b559f0a24061db (diff)
downloademacs-936d074d7c58bd4504b89a0b739b370312ae141a.tar.gz
emacs-936d074d7c58bd4504b89a0b739b370312ae141a.zip
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).
-rw-r--r--doc/lispref/text.texi67
-rw-r--r--etc/NEWS7
-rw-r--r--lisp/format-spec.el72
-rw-r--r--test/lisp/format-spec-tests.el18
4 files changed, 157 insertions, 7 deletions
diff --git a/doc/lispref/text.texi b/doc/lispref/text.texi
index 94b94eaba7e..df9fce066f0 100644
--- a/doc/lispref/text.texi
+++ b/doc/lispref/text.texi
@@ -58,6 +58,7 @@ the character after point.
58 of another buffer. 58 of another buffer.
59* Decompression:: Dealing with compressed data. 59* Decompression:: Dealing with compressed data.
60* Base 64:: Conversion to or from base 64 encoding. 60* Base 64:: Conversion to or from base 64 encoding.
61* Interpolated Strings:: Formatting Customizable Strings.
61* Checksum/Hash:: Computing cryptographic hashes. 62* Checksum/Hash:: Computing cryptographic hashes.
62* GnuTLS Cryptography:: Cryptographic algorithms imported from GnuTLS. 63* GnuTLS Cryptography:: Cryptographic algorithms imported from GnuTLS.
63* Parsing HTML/XML:: Parsing HTML and XML. 64* Parsing HTML/XML:: Parsing HTML and XML.
@@ -4626,6 +4627,72 @@ If optional argument @var{base64url} is is non-@code{nil}, then padding
4626is optional, and the URL variant of base 64 encoding is used. 4627is optional, and the URL variant of base 64 encoding is used.
4627@end defun 4628@end defun
4628 4629
4630
4631@node Interpolated Strings
4632@section Formatting Customizable Strings
4633
4634It is, in some circumstances, useful to present users with a string to
4635be customized that can then be expanded programmatically. For
4636instance, @code{erc-header-line-format} is @code{"%n on %t (%m,%l)
4637%o"}, and each of those characters after the percent signs are
4638expanded when the header line is computed. To do this, the
4639@code{format-spec} function is used:
4640
4641@defun format-spec format specification &optional only-present
4642@var{format} is the format specification string as in the example
4643above. @var{specification} is an alist that has elements where the
4644@code{car} is a character and the @code{cdr} is the substitution.
4645
4646If @code{ONLY-PRESENT} is @code{nil}, errors will be signalled if a
4647format character has been used that's not present in
4648@var{specification}. If it's non-@code{nil}, that format
4649specification is left verbatim in the result.
4650@end defun
4651
4652Here's a trivial example:
4653
4654@example
4655(format-spec "su - %u %l"
4656 `((?u . ,(user-login-name))
4657 (?l . "ls")))
4658=> "su - foo ls"
4659@end example
4660
4661In addition to allowing padding/limiting to a certain length, the
4662following modifiers are can be used:
4663
4664@table @asis
4665@item @samp{0}
4666Use zero padding.
4667
4668@item @samp{@ }
4669User space padding.
4670
4671@item @samp{-}
4672Pad to the right.
4673
4674@item @samp{^}
4675Use upper case.
4676
4677@item @samp{_}
4678Use lower case.
4679
4680@item @samp{<}
4681If the length needs to limited, remove characters from the left.
4682
4683@item @samp{>}
4684Same as previous, but remove characters from the right.
4685@end table
4686
4687If contradictory modifiers are used (for instance, both upper- and
4688lower case), then what happens is undefined.
4689
4690As an example, @samp{"%<010b"} means ``insert the @samp{b} expansion,
4691but pad with leading zeroes if it's less than ten characters, and if
4692it's more than ten characters, shorten by removing characters from the
4693left''.
4694
4695
4629@node Checksum/Hash 4696@node Checksum/Hash
4630@section Checksum/Hash 4697@section Checksum/Hash
4631@cindex MD5 checksum 4698@cindex MD5 checksum
diff --git a/etc/NEWS b/etc/NEWS
index 7e10d132dbe..902203f0c33 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2293,6 +2293,13 @@ argument is 'iec' and the empty string otherwise. We recomment a
2293space or non-breaking space as third argument, and "B" as fourth 2293space or non-breaking space as third argument, and "B" as fourth
2294argument, circumstances allowing. 2294argument, circumstances allowing.
2295 2295
2296+++
2297** `format-spec' has been expanded with several modifiers to allow
2298greater flexibility when customizing variables. The modifiers include
2299zero-padding, upper- and lower-casing, and limiting the length of the
2300interpolated strings. The function has now also been documented in
2301the Emacs Lisp manual.
2302
2296 2303
2297* Changes in Emacs 27.1 on Non-Free Operating Systems 2304* Changes in Emacs 27.1 on Non-Free Operating Systems
2298 2305
diff --git a/lisp/format-spec.el b/lisp/format-spec.el
index cf2d364bb28..220cecd9b05 100644
--- a/lisp/format-spec.el
+++ b/lisp/format-spec.el
@@ -24,6 +24,8 @@
24 24
25;;; Code: 25;;; Code:
26 26
27(require 'subr-x)
28
27(defun format-spec (format specification &optional only-present) 29(defun format-spec (format specification &optional only-present)
28 "Return a string based on FORMAT and SPECIFICATION. 30 "Return a string based on FORMAT and SPECIFICATION.
29FORMAT is a string containing `format'-like specs like \"su - %u %k\", 31FORMAT is a string containing `format'-like specs like \"su - %u %k\",
@@ -32,9 +34,22 @@ to values.
32 34
33For instance: 35For instance:
34 36
35 (format-spec \"su - %u %k\" 37 (format-spec \"su - %u %l\"
36 `((?u . ,(user-login-name)) 38 `((?u . ,(user-login-name))
37 (?k . \"ls\"))) 39 (?l . \"ls\")))
40
41Each format spec can have modifiers, where \"%<010b\" means \"if
42the expansion is shorter than ten characters, zero-pad it, and if
43it's longer, chop off characters from the left size\".
44
45The following modifiers are allowed:
46
47* 0: Use zero-padding.
48* -: Pad to the right.
49* ^: Upper-case the expansion.
50* _: Lower-case the expansion.
51* <: Limit the length by removing chars from the left.
52* >: Limit the length by removing chars from the right.
38 53
39Any text properties on a %-spec itself are propagated to the text 54Any text properties on a %-spec itself are propagated to the text
40that it generates. 55that it generates.
@@ -52,16 +67,31 @@ where they are, including \"%%\" strings."
52 (unless only-present 67 (unless only-present
53 (delete-char 1))) 68 (delete-char 1)))
54 ;; Valid format spec. 69 ;; Valid format spec.
55 ((looking-at "\\([-0-9.]*\\)\\([a-zA-Z]\\)") 70 ((looking-at "\\([-0 _^<>]*\\)\\([0-9.]*\\)\\([a-zA-Z]\\)")
56 (let* ((num (match-string 1)) 71 (let* ((modifiers (match-string 1))
57 (spec (string-to-char (match-string 2))) 72 (num (match-string 2))
73 (spec (string-to-char (match-string 3)))
58 (val (assq spec specification))) 74 (val (assq spec specification)))
59 (if (not val) 75 (if (not val)
60 (unless only-present 76 (unless only-present
61 (error "Invalid format character: `%%%c'" spec)) 77 (error "Invalid format character: `%%%c'" spec))
62 (setq val (cdr val)) 78 (setq val (cdr val)
79 modifiers (format-spec--parse-modifiers modifiers))
63 ;; Pad result to desired length. 80 ;; Pad result to desired length.
64 (let ((text (format (concat "%" num "s") val))) 81 (let ((text (format "%s" val)))
82 (when num
83 (setq num (string-to-number num))
84 (setq text (format-spec--pad text num modifiers))
85 (when (> (length text) num)
86 (cond
87 ((memq :chop-left modifiers)
88 (setq text (substring text (- (length text) num))))
89 ((memq :chop-right modifiers)
90 (setq text (substring text 0 num))))))
91 (when (memq :uppercase modifiers)
92 (setq text (upcase text)))
93 (when (memq :lowercase modifiers)
94 (setq text (downcase text)))
65 ;; Insert first, to preserve text properties. 95 ;; Insert first, to preserve text properties.
66 (insert-and-inherit text) 96 (insert-and-inherit text)
67 ;; Delete the specifier body. 97 ;; Delete the specifier body.
@@ -75,6 +105,34 @@ where they are, including \"%%\" strings."
75 (error "Invalid format string"))))) 105 (error "Invalid format string")))))
76 (buffer-string))) 106 (buffer-string)))
77 107
108(defun format-spec--pad (text total-length modifiers)
109 (if (> (length text) total-length)
110 ;; The text is longer than the specified length; do nothing.
111 text
112 (let ((padding (make-string (- total-length (length text))
113 (if (memq :zero-pad modifiers)
114 ?0
115 ?\s))))
116 (if (memq :right-pad modifiers)
117 (concat text padding)
118 (concat padding text)))))
119
120(defun format-spec--parse-modifiers (modifiers)
121 (let ((elems nil))
122 (mapc (lambda (char)
123 (when-let ((modifier
124 (pcase char
125 (?0 :zero-pad)
126 (?\s :space-pad)
127 (?^ :uppercase)
128 (?_ :lowercase)
129 (?- :right-pad)
130 (?< :chop-left)
131 (?> :chop-right))))
132 (push modifier elems)))
133 modifiers)
134 elems))
135
78(defun format-spec-make (&rest pairs) 136(defun format-spec-make (&rest pairs)
79 "Return an alist suitable for use in `format-spec' based on PAIRS. 137 "Return an alist suitable for use in `format-spec' based on PAIRS.
80PAIRS is a list where every other element is a character and a value, 138PAIRS is a list where every other element is a character and a value,
diff --git a/test/lisp/format-spec-tests.el b/test/lisp/format-spec-tests.el
index 6fbfaaad83a..a386e9da8ff 100644
--- a/test/lisp/format-spec-tests.el
+++ b/test/lisp/format-spec-tests.el
@@ -37,4 +37,22 @@
37 (should (equal (format-spec "foo %b %z %% zot" '((?b . "bar")) t) 37 (should (equal (format-spec "foo %b %z %% zot" '((?b . "bar")) t)
38 "foo bar %z %% zot"))) 38 "foo bar %z %% zot")))
39 39
40(ert-deftest test-format-modifiers ()
41 (should (equal (format-spec "foo %10b zot" '((?b . "bar")))
42 "foo bar zot"))
43 (should (equal (format-spec "foo % 10b zot" '((?b . "bar")))
44 "foo bar zot"))
45 (should (equal (format-spec "foo %-010b zot" '((?b . "bar")))
46 "foo bar0000000 zot"))
47 (should (equal (format-spec "foo %0-10b zot" '((?b . "bar")))
48 "foo bar0000000 zot"))
49 (should (equal (format-spec "foo %^10b zot" '((?b . "bar")))
50 "foo BAR zot"))
51 (should (equal (format-spec "foo %_10b zot" '((?b . "BAR")))
52 "foo bar zot"))
53 (should (equal (format-spec "foo %<4b zot" '((?b . "longbar")))
54 "foo gbar zot"))
55 (should (equal (format-spec "foo %>4b zot" '((?b . "longbar")))
56 "foo long zot")))
57
40;;; format-spec-tests.el ends here 58;;; format-spec-tests.el ends here