Subject: Re: bug#41571: 27.0.91; "(elisp) Interpolated Strings" is under "(elisp) Text"

From: "Basil L. Contovounesios" <[email-masked]>
Date: Tue, 02 Jun 2020 15:03:36 +0100

Eli Zaretskii <[email-masked]> writes:

>> From: "Basil L. Contovounesios" <[email-masked]>
>> Cc: [email-masked]
>> Date: Sun, 31 May 2020 10:24:46 +0100
>> 
>> > (Btw, why are you attachments appear before the text?  It makes
>> > responding harder, because I need to bring the citations to the
>> > front.)
>> 
>> Sorry, I got into the habit of doing that because I wasn't sure whether
>> attachments should go before or after the email signature.  I'm guessing
>> after?
>
> Yes, after is better if you include text that is worded as a preamble
> to the patch (which is usually the case).

OK.

>> This wording is much clearer, but the description of the output is
>> wrong: 'format-spec' and 'format' both produce the same result - a
>> formatted string, not a format string.
>
> Well, that just reflects on how the original text could lead to a
> grave misunderstanding, doesn't it? ;-)

I thought truth is in the eye of the beholder. ;)

>> > Here you use "key" without first explaining what it is.
>> 
>> I was relying on the preceding xref to the node on alists, which defines
>> the terms "alist", "key", and "associated value".
>
> I don't recommend to rely on that, not in general.  Cross-references
> are there for readers who want to see additional details, but the text
> should be self-explanatory even if the cross-references are not
> followed.  IOW, the cross-references are optional reading.

OK.

>> > How is what's described in the last sentence "an exception"?  Format
>> > strings used by 'format' also behave like that, right?
>> 
>> It's an exception to "beginning with % and ending with a letter".
>
> Ah!  But the text was worded in a way that led me to believe the
> exception was from the similarity between 'format' and 'format-spec'.
>
>> Would it be clearer if I said "the only specification that does not end
>> in a letter is %%, which is replaced with a single % in the output"?
>
> Not sure.  Perhaps that sentence should simply be removed, because it
> looks like the differences between these two APIs are described in the
> following paragraph.  And %% is supported the same by both of them, so
> mentioning it here is just a distraction.

Done.

>> The main use case for format-spec I've seen is where one part of the
>> program produces an alist with all the information that could ever be
>> needed, and another part of the program formats an often
>> user-customisable format string using this data.
>> 
>> An example of such a use case is in battery.el, where the alist produced
>> by battery-status-function is used to format battery-echo-area-format
>> and battery-mode-line-format (battery.el doesn't currently use
>> format-spec, but it could and my WIP patch for master changes that).
>
> How about saying this in the text?  In fact, "user-customizable format
> string" is never mentioned in the text, it is only hinted upon in the
> menu entry leading to this node.

Done.

> If the main use case is to let users customize string-valued variables
> conveniently, that use case should be described and explained in more
> detail, including why it makes customization more convenient.

I already tried doing that in the two opening paragraphs, which is why
the original text said something like "allow the user to control..."
rather than "allow Lisp programs to control...".  I've now mentioned
both in the opening and added a new paragraph at the end of the section.

How's the attached?

Thanks,

-- 
Basil

>From b07e3b1d97e73c5cf0cd60edf4838b555530bbf0 Mon Sep 17 00:00:00 2001
From: "Basil L. Contovounesios" <[email-masked]>
Date: Thu, 28 May 2020 00:53:42 +0100
Subject: [PATCH] Improve format-spec documentation (bug#41571)

* doc/lispref/text.texi (Interpolated Strings): Move from here...
* doc/lispref/strings.texi (Custom Format Strings): ...to here,
renaming the node and clarifying the documentation.
(Formatting Strings): End node with sentence referring to the next
one.
* lisp/format-spec.el (format-spec): Clarify docstring.
---
 doc/lispref/strings.texi | 176 +++++++++++++++++++++++++++++++++++++++
 doc/lispref/text.texi    |  64 --------------
 lisp/format-spec.el      |  49 ++++++-----
 3 files changed, 206 insertions(+), 83 deletions(-)

diff --git a/doc/lispref/strings.texi b/doc/lispref/strings.texi
index 70c3b3cf4b..4a7bda57c4 100644
--- a/doc/lispref/strings.texi
+++ b/doc/lispref/strings.texi
@@ -28,6 +28,7 @@ Strings and Characters
 * Text Comparison::           Comparing characters or strings.
 * String Conversion::         Converting to and from characters and strings.
 * Formatting Strings::        @code{format}: Emacs's analogue of @code{printf}.
+* Custom Format Strings::     Formatting custom @code{format} specifications.
 * Case Conversion::           Case conversion functions.
 * Case Tables::               Customizing case conversion.
 @end menu
@@ -1122,6 +1123,181 @@ Formatting Strings
 NaNs and can lose precision and type, and @samp{#x%x} and @samp{#o%o}
 can mishandle negative integers.  @xref{Input Functions}.
 
+The functions described in this section accept a fixed set of
+specification characters.  The next section describes a function
+@code{format-spec} which can accept custom specification characters,
+such as @samp{%a} or @samp{%z}.
+
+@node Custom Format Strings
+@section Custom Format Strings
+@cindex custom format string
+@cindex custom @samp{%}-sequence in format
+
+Sometimes it is useful to allow users and Lisp programs alike to
+control how certain text is generated via custom format control
+strings.  For example, a format string could control how to display
+someone's forename, surname, and email address.  Using the function
+@code{format} described in the previous section, the format string
+could be something like @w{@code{"%s %s <%s>"}}.  This approach
+quickly becomes impractical, however, as it can be unclear which
+specification character corresponds to which piece of information.
+
+A more convenient format string for such cases would be something like
+@w{@code{"%f %l <%e>"}}, where each specification character carries
+more semantic information and can easily be rearranged relative to
+other specification characters, making such format strings more easily
+customizable by the user.
+
+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 only-present
+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
+@w{@code{(@var{letter} . @var{replacement})}}.  Each specification
+@code{%@var{letter}} in @var{template} will be replaced by
+@var{replacement} when formatting the resulting string.
+
+The characters in @var{template}, other than the format
+specifications, are copied directly into the output, including their
+text properties, if any.  Any text properties of the format
+specifications are copied to their replacements.
+
+Using an alist to specify conversions gives rise to some useful
+properties:
+
+@itemize @bullet
+@item
+If @var{spec-alist} contains more unique @var{letter} keys than there
+are unique specification characters in @var{template}, the unused keys
+are simply ignored.
+@item
+If @var{spec-alist} contains more than one association with the same
+@var{letter}, the closest one to the start of the list is used.
+@item
+If @var{template} contains the same specification character more than
+once, then the same @var{replacement} found in @var{spec-alist} is
+used as a basis for all of that character's substitutions.
+@item
+The order of specifications in @var{template} need not correspond to
+the order of associations in @var{spec-alist}.
+@end itemize
+
+The optional argument @var{only-present} 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
+signals an error.  Otherwise, those format specifications and any
+occurrences of @samp{%%} in @var{template} are left verbatim in the
+output, including their text properties, if any.
+@end defun
+
+The syntax of format specifications accepted by @code{format-spec} is
+similar, but not identical, to that accepted by @code{format}.  In
+both cases, a format specification is a sequence of characters
+beginning with @samp{%} and ending with an alphabetic letter such as
+@samp{s}.
+
+Unlike @code{format}, which assigns specific meanings to a fixed set
+of specification characters, @code{format-spec} accepts arbitrary
+specification characters and treats them all equally.  For example:
+
+@example
+@group
+(setq my-site-info
+      (list (cons ?s system-name)
+            (cons ?t (symbol-name system-type))
+            (cons ?c system-configuration)
+            (cons ?v emacs-version)
+            (cons ?e invocation-name)
+            (cons ?p (number-to-string (emacs-pid)))
+            (cons ?a user-mail-address)
+            (cons ?n user-full-name)))
+
+(format-spec "%e %v (%c)" my-site-info)
+     @result{} "emacs 27.1 (x86_64-pc-linux-gnu)"
+
+(format-spec "%n <%a>" my-site-info)
+     @result{} "Emacs Developers <emacs-devel@@gnu.org>"
+@end group
+@end example
+
+A format specification can include any number of the following flag
+characters immediately after the @samp{%} to modify aspects of the
+substitution.
+
+@table @samp
+@item 0
+This flag causes any padding specified by the width to consist of
+@samp{0} characters instead of spaces.
+
+@item -
+This flag causes any padding specified by the width to be inserted on
+the right rather than the left.
+
+@item <
+This flag causes the substitution to be truncated on the left to the
+given width, if specified.
+
+@item >
+This flag causes the substitution to be truncated on the right to the
+given width, if specified.
+
+@item ^
+This flag converts the substituted text to upper case (@pxref{Case
+Conversion}).
+
+@item _
+This flag converts the substituted text to lower case (@pxref{Case
+Conversion}).
+@end table
+
+The result of using contradictory flags (for instance, both upper and
+lower case) is undefined.
+
+As is the case with @code{format}, a format specification can include
+a width, which is a decimal number that appears after any flags.  If a
+substitution contains fewer characters than its specified width, it is
+padded on the left:
+
+@example
+@group
+(format-spec "%8a is padded on the left with spaces"
+             '((?a . "alpha")))
+     @result{} "   alpha is padded on the left with spaces"
+@end group
+@end example
+
+Here is a more complicated example that combines several
+aforementioned features:
+
+@example
+@group
+(setq my-battery-info
+      (list (cons ?p "73")      ; Percentage
+            (cons ?L "Battery") ; Status
+            (cons ?t "2:23")    ; Remaining time
+            (cons ?c "24330")   ; Capacity
+            (cons ?r "10.6")))  ; Rate of discharge
+
+(format-spec "%>^-3L : %3p%% (%05t left)" my-battery-info)
+     @result{} "BAT :  73% (02:23 left)"
+
+(format-spec "%>^-3L : %3p%% (%05t left)"
+             (cons (cons ?L "AC")
+                   my-battery-info))
+     @result{} "AC  :  73% (02:23 left)"
+@end group
+@end example
+
+As the examples in this section illustrate, @code{format-spec} is
+often used for selectively formatting an assortment of different
+pieces of information.  This is useful in programs that provide
+user-customizable format strings, as the user can choose to format
+with a regular syntax and in any desired order only a subset of the
+information that the program makes available.
+
 @node Case Conversion
 @section Case Conversion in Lisp
 @cindex upper case
diff --git a/doc/lispref/text.texi b/doc/lispref/text.texi
index de436fa9e6..a14867e1d1 100644
--- a/doc/lispref/text.texi
+++ b/doc/lispref/text.texi
@@ -58,7 +58,6 @@ Text
                        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.
@@ -4662,69 +4661,6 @@ Base 64
 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 @var{only-present} is @code{nil}, errors will be signaled 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")))
-     @result{} "su - foo ls"
-@end example
-
-In addition to allowing padding/limiting to a certain length, the
-following modifiers can be used:
-
-@table @asis
-@item @samp{0}
-Pad with zeros instead of the default spaces.
-
-@item @samp{-}
-Pad to the right.
-
-@item @samp{^}
-Use upper case.
-
-@item @samp{_}
-Use lower case.
-
-@item @samp{<}
-If the length needs to be 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 zeros 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/lisp/format-spec.el b/lisp/format-spec.el
index f418cea425..9278bd74c4 100644
--- a/lisp/format-spec.el
+++ b/lisp/format-spec.el
@@ -29,35 +29,46 @@
 
 (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\",
-while SPECIFICATION is an alist mapping from format spec characters
-to values.
+FORMAT is a string containing `format'-like specs like \"su - %u %k\".
+SPECIFICATION is an alist mapping format specification characters
+to their substitutions.
 
 For instance:
 
   (format-spec \"su - %u %l\"
-               `((?u . ,(user-login-name))
+               \\=`((?u . ,(user-login-name))
                  (?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 side\".
+Each %-spec may contain optional flag and width modifiers, as
+follows:
 
-The following modifiers are allowed:
+  %<flags><width>character
 
-* 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.
+The following flags are allowed:
 
-Any text properties on a %-spec itself are propagated to the text
-that it generates.
+* 0: Pad to the width, if given, with zeros instead of spaces.
+* -: Pad to the width, if given, on the right instead of the left.
+* <: Truncate to the width, if given, on the left.
+* >: Truncate to the width, if given, on the right.
+* ^: Convert to upper case.
+* _: Convert to lower case.
 
-If ONLY-PRESENT, format spec characters not present in
-SPECIFICATION are ignored, and the \"%\" characters are left
-where they are, including \"%%\" strings."
+The width modifier behaves like the corresponding one in `format'
+when applied to %s.
+
+For example, \"%<010b\" means \"substitute into the output the
+value associated with ?b in SPECIFICATION, either padding it with
+leading zeros or truncating leading characters until it's ten
+characters wide\".
+
+Any text properties of FORMAT are copied to the result, with any
+text properties of a %-spec itself copied to its substitution.
+
+ONLY-PRESENT indicates how to handle %-spec characters not
+present in SPECIFICATION.  If it is nil or omitted, emit an
+error; otherwise leave those %-specs and any occurrences of
+\"%%\" in FORMAT verbatim in the result, including their text
+properties, if any."
   (with-temp-buffer
     (insert format)
     (goto-char (point-min))
-- 
2.26.2