emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Emacs-diffs] master 936d074: Document format-spec and expand the modifi


From: Lars Ingebrigtsen
Subject: [Emacs-diffs] master 936d074: Document format-spec and expand the modifiers it supports
Date: Fri, 12 Jul 2019 21:51:01 -0400 (EDT)

branch: master
commit 936d074d7c58bd4504b89a0b739b370312ae141a
Author: Lars Ingebrigtsen <address@hidden>
Commit: Lars Ingebrigtsen <address@hidden>

    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).
---
 doc/lispref/text.texi          | 67 +++++++++++++++++++++++++++++++++++++++
 etc/NEWS                       |  7 ++++
 lisp/format-spec.el            | 72 ++++++++++++++++++++++++++++++++++++++----
 test/lisp/format-spec-tests.el | 18 +++++++++++
 4 files changed, 157 insertions(+), 7 deletions(-)

diff --git a/doc/lispref/text.texi b/doc/lispref/text.texi
index 94b94ea..df9fce0 100644
--- a/doc/lispref/text.texi
+++ b/doc/lispref/text.texi
@@ -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
diff --git a/etc/NEWS b/etc/NEWS
index 7e10d13..902203f 100644
--- a/etc/NEWS
+++ b/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
 
diff --git a/lisp/format-spec.el b/lisp/format-spec.el
index cf2d364..220cecd 100644
--- a/lisp/format-spec.el
+++ b/lisp/format-spec.el
@@ -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,
diff --git a/test/lisp/format-spec-tests.el b/test/lisp/format-spec-tests.el
index 6fbfaaa..a386e9d 100644
--- a/test/lisp/format-spec-tests.el
+++ b/test/lisp/format-spec-tests.el
@@ -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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]