Fix and extend format-spec (bug#41758)

* lisp/format-spec.el: Use lexical-binding.  Remove dependence on
subr-x.el.
(format-spec-make): Clarify docstring.
(format-spec--parse-modifiers): Rename to...
(format-spec--parse-flags): ...this and simplify.  In particular,
don't bother parsing :space-pad which is redundant and unused.
(format-spec--pad): Remove, replacing with...
(format-spec--do-flags): ...this new helper function which performs
more of format-spec's supported text manipulation.
(format-spec): Autoload.  Allow optional argument to take on special
values 'ignore' and 'delete' for more control over what happens when
a replacement for a format specification isn't provided.  Bring back
proper support for a precision modifier similar to that of 'format'.

* lisp/battery.el (battery-format): Rewrite in terms of format-spec.
(battery-echo-area-format, battery-mode-line-format): Mention
support of format-spec syntax in docstrings.

* doc/lispref/strings.texi (Custom Format Strings):
* etc/NEWS: Document and announce these changes.

* lisp/dired-aux.el (dired-do-compress-to):
* lisp/erc/erc-match.el (erc-log-matches):
* lisp/erc/erc.el (erc-update-mode-line-buffer):
* lisp/gnus/gnus-sieve.el (gnus-sieve-update):
* lisp/gnus/gssapi.el (open-gssapi-stream):
* lisp/gnus/mail-source.el (mail-source-fetch-file)
(mail-source-fetch-directory, mail-source-fetch-pop)
(mail-source-fetch-imap):
* lisp/gnus/message.el (message-insert-formatted-citation-line):
* lisp/image-dired.el:
* lisp/net/eww.el:
* lisp/net/imap.el (imap-kerberos4-open, imap-gssapi-open)
(imap-shell-open):
* lisp/net/network-stream.el (network-stream-open-shell):
* lisp/obsolete/tls.el (open-tls-stream):
* lisp/textmodes/tex-mode.el:
Remove extraneous loads and autoloads of format-spec now that it is
autoloaded and simplify its uses where possible.

* test/lisp/battery-tests.el (battery-format): Test new format-spec
support.
* test/lisp/format-spec-tests.el (test-format-spec): Rename to...
(format-spec) ...this, extending test cases.
(test-format-unknown): Rename to...
(format-spec-unknown): ...this, extending test cases.
(test-format-modifiers): Rename to...
(format-spec-flags): ...this.
(format-spec-make, format-spec-parse-flags, format-spec-do-flags)
(format-spec-do-flags-truncate, format-spec-do-flags-pad)
(format-spec-do-flags-chop, format-spec-do-flags-case): New tests.
parent 97d1f672
......@@ -1152,7 +1152,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 only-present
@defun format-spec template spec-alist &optional ignore-missing
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
......@@ -1185,12 +1185,15 @@ 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
The optional argument @var{ignore-missing} 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.
signals an error; if it is @code{ignore}, those format specifications
are left verbatim in the output, including their text properties, if
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.
@end defun
The syntax of format specifications accepted by @code{format-spec} is
......@@ -1238,7 +1241,7 @@ the right rather than the left.
@item <
This flag causes the substitution to be truncated on the left to the
given width, if specified.
given width and precision, if specified.
@item >
This flag causes the substitution to be truncated on the right to the
......@@ -1257,9 +1260,12 @@ 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:
a width, which is a decimal number that appears after any flags, and a
precision, which is a decimal-point @samp{.} followed by a decimal
number that appears after any flags and width.
If a substitution contains fewer characters than its specified width,
it is padded on the left:
@example
@group
......@@ -1269,6 +1275,17 @@ padded on the left:
@end group
@end example
If a substitution contains more characters than its specified
precision, it is truncated on the right:
@example
@group
(format-spec "%.2a is truncated on the right"
'((?a . "alpha")))
@result{} "al is truncated on the right"
@end group
@end example
Here is a more complicated example that combines several
aforementioned features:
......
......@@ -461,6 +461,16 @@ In Emacs 24.3, the variable 'dbus-event-error-hooks' was renamed to
The old names, which were kept as obsolete aliases of the new names,
have now been removed.
** Battery
---
*** A richer syntax can be used to format battery status information.
The user options 'battery-mode-line-format' and
'battery-echo-area-format' now support the full formatting syntax of
the function 'format-spec' documented under '(elisp) Custom Format
Strings'. The new syntax includes specifiers for padding and
truncation, amongst other things.
* New Modes and Packages in Emacs 28.1
......@@ -578,6 +588,13 @@ for encoding and decoding without having to bind
It controls, whether 'process-file' returns a string when a remote
process is interrupted by a signal.
+++
** The behavior of 'format-spec' is now closer to that of 'format'.
In order for the two functions to behave more consistently,
'format-spec' now pads and truncates based on string width rather than
length, and also supports format specifications that include a
truncating precision field, such as '%.2a'.
* Changes in Emacs 28.1 on Non-Free Operating Systems
......
......@@ -121,7 +121,10 @@ string are substituted as defined by the current value of the variable
%p Battery load percentage
%m Remaining time (to charge or discharge) in minutes
%h Remaining time (to charge or discharge) in hours
%t Remaining time (to charge or discharge) in the form `h:min'"
%t Remaining time (to charge or discharge) in the form `h:min'
The full `format-spec' formatting syntax is supported."
:link '(info-link "(elisp) Custom Format Strings")
:type '(choice string (const nil)))
(defvar battery-mode-line-string nil
......@@ -153,7 +156,10 @@ string are substituted as defined by the current value of the variable
%p Battery load percentage
%m Remaining time (to charge or discharge) in minutes
%h Remaining time (to charge or discharge) in hours
%t Remaining time (to charge or discharge) in the form `h:min'"
%t Remaining time (to charge or discharge) in the form `h:min'
The full `format-spec' formatting syntax is supported."
:link '(info-link "(elisp) Custom Format Strings")
:type '(choice string (const nil)))
(defcustom battery-update-interval 60
......@@ -823,13 +829,7 @@ The following %-sequences are provided:
(defun battery-format (format alist)
"Substitute %-sequences in FORMAT."
(replace-regexp-in-string
"%."
(lambda (str)
(let ((char (aref str 1)))
(if (eq char ?%) "%"
(or (cdr (assoc char alist)) ""))))
format t t))
(format-spec format alist 'delete))
(defun battery-search-for-one-match-in-files (files regexp match-num)
"Search REGEXP in the content of the files listed in FILES.
......
......@@ -1064,8 +1064,6 @@ corresponding command.
Within CMD, %i denotes the input file(s), and %o denotes the
output file. %i path(s) are relative, while %o is absolute.")
(declare-function format-spec "format-spec.el" (format specification))
;;;###autoload
(defun dired-do-compress-to ()
"Compress selected files and directories to an archive.
......@@ -1073,7 +1071,6 @@ Prompt for the archive file name.
Choose the archiving command based on the archive file-name extension
and `dired-compress-files-alist'."
(interactive)
(require 'format-spec)
(let* ((in-files (dired-get-marked-files nil nil nil nil t))
(out-file (expand-file-name (read-file-name "Compress to: ")))
(rule (cl-find-if
......@@ -1093,12 +1090,12 @@ and `dired-compress-files-alist'."
(when (zerop
(dired-shell-command
(format-spec (cdr rule)
`((?\o . ,(shell-quote-argument out-file))
(?\i . ,(mapconcat
(lambda (file-desc)
(shell-quote-argument (file-name-nondirectory
file-desc)))
in-files " "))))))
`((?o . ,(shell-quote-argument out-file))
(?i . ,(mapconcat
(lambda (in-file)
(shell-quote-argument
(file-name-nondirectory in-file)))
in-files " "))))))
(message (ngettext "Compressed %d file to %s"
"Compressed %d files to %s"
(length in-files))
......
......@@ -555,16 +555,15 @@ See `erc-log-match-format'."
(and (eq erc-log-matches-flag 'away)
(erc-away-time)))
match-buffer-name)
(let ((line (format-spec erc-log-match-format
(format-spec-make
?n nick
?t (format-time-string
(or (and (boundp 'erc-timestamp-format)
erc-timestamp-format)
"[%Y-%m-%d %H:%M] "))
?c (or (erc-default-target) "")
?m message
?u nickuserhost))))
(let ((line (format-spec
erc-log-match-format
`((?n . ,nick)
(?t . ,(format-time-string
(or (bound-and-true-p erc-timestamp-format)
"[%Y-%m-%d %H:%M] ")))
(?c . ,(or (erc-default-target) ""))
(?m . ,message)
(?u . ,nickuserhost)))))
(with-current-buffer (erc-log-matches-make-buffer match-buffer-name)
(let ((inhibit-read-only t))
(goto-char (point-max))
......
......@@ -6391,17 +6391,16 @@ if `erc-away' is non-nil."
(defun erc-update-mode-line-buffer (buffer)
"Update the mode line in a single ERC buffer BUFFER."
(with-current-buffer buffer
(let ((spec (format-spec-make
?a (erc-format-away-status)
?l (erc-format-lag-time)
?m (erc-format-channel-modes)
?n (or (erc-current-nick) "")
?N (erc-format-network)
?o (or (erc-controls-strip erc-channel-topic) "")
?p (erc-port-to-string erc-session-port)
?s (erc-format-target-and/or-server)
?S (erc-format-target-and/or-network)
?t (erc-format-target)))
(let ((spec `((?a . ,(erc-format-away-status))
(?l . ,(erc-format-lag-time))
(?m . ,(erc-format-channel-modes))
(?n . ,(or (erc-current-nick) ""))
(?N . ,(erc-format-network))
(?o . ,(or (erc-controls-strip erc-channel-topic) ""))
(?p . ,(erc-port-to-string erc-session-port))
(?s . ,(erc-format-target-and/or-server))
(?S . ,(erc-format-target-and/or-network))
(?t . ,(erc-format-target))))
(process-status (cond ((and (erc-server-process-alive)
(not erc-server-connected))
":connecting")
......
;;; format-spec.el --- functions for formatting arbitrary formatting strings
;;; format-spec.el --- format arbitrary formatting strings -*- lexical-binding: t -*-
;; Copyright (C) 1999-2020 Free Software Foundation, Inc.
......@@ -24,10 +24,8 @@
;;; Code:
(eval-when-compile
(require 'subr-x))
(defun format-spec (format specification &optional only-present)
;;;###autoload
(defun format-spec (format specification &optional ignore-missing)
"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
......@@ -39,22 +37,22 @@ For instance:
\\=`((?u . ,(user-login-name))
(?l . \"ls\")))
Each %-spec may contain optional flag and width modifiers, as
follows:
Each %-spec may contain optional flag, width, and precision
modifiers, as follows:
%<flags><width>character
%<flags><width><precision>character
The following flags are allowed:
* 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.
* <: Truncate to the width and precision, if given, on the left.
* >: Truncate to the width and precision, if given, on the right.
* ^: Convert to upper case.
* _: Convert to lower case.
The width modifier behaves like the corresponding one in `format'
when applied to %s.
The width and truncation modifiers behave like the corresponding
ones 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
......@@ -64,89 +62,108 @@ 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
IGNORE-MISSING 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."
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."
(with-temp-buffer
(insert format)
(goto-char (point-min))
(while (search-forward "%" nil t)
(cond
;; Quoted percent sign.
((eq (char-after) ?%)
(unless only-present
(delete-char 1)))
;; Valid format spec.
((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)
modifiers (format-spec--parse-modifiers modifiers))
;; Pad result to desired length.
(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.
(delete-region (+ (match-beginning 0) (length text))
(+ (match-end 0) (length text)))
;; Delete the percent sign.
(delete-region (1- (match-beginning 0)) (match-beginning 0))))))
;; Signal an error on bogus format strings.
(t
(unless only-present
(error "Invalid format string")))))
;; 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)))
(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)
(defun format-spec--do-flags (str flags width trunc)
"Return STR formatted according to FLAGS, WIDTH, and TRUNC.
FLAGS is a list of keywords as returned by
`format-spec--parse-flags'. WIDTH and TRUNC are either nil or
string widths corresponding to `format-spec' modifiers."
(let (diff str-width)
;; Truncate original string first, like `format' does.
(when trunc
(setq str-width (string-width str))
(when (> (setq diff (- str-width trunc)) 0)
(setq str (if (memq :chop-left flags)
(truncate-string-to-width str str-width diff)
(format (format "%%.%ds" trunc) str))
;; We know the new width so save it for later.
str-width trunc)))
;; Pad or chop to width.
(when width
(setq str-width (or str-width (string-width str))
diff (- width str-width))
(cond ((zerop diff))
((> diff 0)
(let ((pad (make-string diff (if (memq :pad-zero flags) ?0 ?\s))))
(setq str (if (memq :pad-right flags)
(concat str pad)
(concat pad str)))))
((memq :chop-left flags)
(setq str (truncate-string-to-width str str-width (- diff))))
((memq :chop-right flags)
(setq str (format (format "%%.%ds" width) str))))))
;; Fiddle case.
(cond ((memq :upcase flags)
(upcase str))
((memq :downcase flags)
(downcase str))
(str)))
(defun format-spec--parse-flags (flags)
"Convert sequence of FLAGS to list of human-readable keywords."
(mapcan (lambda (char)
(when-let ((modifier
(pcase char
(?0 :zero-pad)
(?\s :space-pad)
(?^ :uppercase)
(?_ :lowercase)
(?- :right-pad)
(?< :chop-left)
(?> :chop-right))))
(list modifier)))
modifiers))
(pcase char
(?0 (list :pad-zero))
(?- (list :pad-right))
(?< (list :chop-left))
(?> (list :chop-right))
(?^ (list :upcase))
(?_ (list :downcase))))
flags))
(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,
starting with a character."
PAIRS is a property list with characters as keys."
(let (alist)
(while pairs
(unless (cdr pairs)
......
......@@ -29,8 +29,6 @@
(require 'gnus)
(require 'gnus-sum)
(require 'format-spec)
(autoload 'sieve-mode "sieve-mode")
(eval-when-compile
(require 'sieve))
......@@ -88,10 +86,10 @@ See the documentation for these variables and functions for details."
(save-buffer)
(shell-command
(format-spec gnus-sieve-update-shell-command
(format-spec-make ?f gnus-sieve-file
?s (or (cadr (gnus-server-get-method
nil gnus-sieve-select-method))
"")))))
`((?f . ,gnus-sieve-file)
(?s . ,(or (cadr (gnus-server-get-method
nil gnus-sieve-select-method))
""))))))
;;;###autoload
(defun gnus-sieve-generate ()
......
......@@ -25,8 +25,6 @@
;;; Code:
(require 'format-spec)
(defcustom gssapi-program (list
(concat "gsasl %s %p "
"--mechanism GSSAPI "
......@@ -53,12 +51,9 @@ tried until a successful connection is made."
(coding-system-for-write 'binary)
(process (start-process
name buffer shell-file-name shell-command-switch
(format-spec
cmd
(format-spec-make
?s server
?p (number-to-string port)
?l user))))
(format-spec cmd `((?s . ,server)
(?p . ,(number-to-string port))
(?l . ,user)))))
response)
(when process
(while (and (memq (process-status process) '(open run))
......
......@@ -24,7 +24,6 @@
;;; Code:
(require 'format-spec)
(eval-when-compile
(require 'cl-lib)
(require 'imap))
......@@ -769,14 +768,14 @@ Deleting old (> %s day(s)) incoming mail file `%s'." diff bfile)
"Fetcher for single-file sources."
(mail-source-bind (file source)
(mail-source-run-script
prescript (format-spec-make ?t mail-source-crash-box)
prescript `((?t . ,mail-source-crash-box))
prescript-delay)
(let ((mail-source-string (format "file:%s" path)))
(if (mail-source-movemail path mail-source-crash-box)
(prog1
(mail-source-callback callback path)
(mail-source-run-script
postscript (format-spec-make ?t mail-source-crash-box))
postscript `((?t . ,mail-source-crash-box)))
(mail-source-delete-crash-box))
0))))
......@@ -784,7 +783,7 @@ Deleting old (> %s day(s)) incoming mail file `%s'." diff bfile)
"Fetcher for directory sources."
(mail-source-bind (directory source)
(mail-source-run-script
prescript (format-spec-make ?t path) prescript-delay)
prescript `((?t . ,path)) prescript-delay)
(let ((found 0)
(mail-source-string (format "directory:%s" path)))
(dolist (file (directory-files
......@@ -793,7 +792,7 @@ Deleting old (> %s day(s)) incoming mail file `%s'." diff bfile)
(funcall predicate file)
(mail-source-movemail file mail-source-crash-box))
(cl-incf found (mail-source-callback callback file))
(mail-source-run-script postscript (format-spec-make ?t path))
(mail-source-run-script postscript `((?t . ,path)))
(mail-source-delete-crash-box)))
found)))
......@@ -803,8 +802,8 @@ Deleting old (> %s day(s)) incoming mail file `%s'." diff bfile)
;; fixme: deal with stream type in format specs
(mail-source-run-script
prescript
(format-spec-make ?p password ?t mail-source-crash-box
?s server ?P port ?u user)
`((?p . ,password) (?t . ,mail-source-crash-box)
(?s . ,server) (?P . ,port) (?u . ,user))
prescript-delay)
(let ((from (format "%s:%s:%s" server user port))
(mail-source-string (format "pop:%s@%s" user server))
......@@ -825,8 +824,8 @@ Deleting old (> %s day(s)) incoming mail file `%s'." diff bfile)
(mail-source-fetch-with-program
(format-spec
program
(format-spec-make ?p password ?t mail-source-crash-box
?s server ?P port ?u user))))
`((?p . ,password) (?t . ,mail-source-crash-box)
(?s . ,server) (?P . ,port) (?u . ,user)))))
(function
(funcall function mail-source-crash-box))
;; The default is to use pop3.el.
......@@ -863,8 +862,8 @@ Deleting old (> %s day(s)) incoming mail file `%s'." diff bfile)
(setq mail-source-new-mail-available nil))
(mail-source-run-script
postscript
(format-spec-make ?p password ?t mail-source-crash-box
?s server ?P port ?u user))
`((?p . ,password) (?t . ,mail-source-crash-box)
(?s . ,server) (?P . ,port) (?u . ,user)))
(mail-source-delete-crash-box)))
;; We nix out the password in case the error
;; was because of a wrong password being given.
......@@ -1077,8 +1076,9 @@ This only works when `display-time' is enabled."
"Fetcher for imap sources."
(mail-source-bind (imap source)
(mail-source-run-script
prescript (format-spec-make ?p password ?t mail-source-crash-box
?s server ?P port ?u user)
prescript
`((?p . ,password) (?t . ,mail-source-crash-box)
(?s . ,server) (?P . ,port) (?u . ,user))
prescript-delay)
(let ((from (format "%s:%s:%s" server user port))
(found 0)
......@@ -1143,8 +1143,8 @@ This only works when `display-time' is enabled."
(kill-buffer buf)
(mail-source-run-script
postscript
(format-spec-make ?p password ?t mail-source-crash-box
?s server ?P port ?u user))
`((?p . ,password) (?t . ,mail-source-crash-box)
(?s . ,server) (?P . ,port) (?u . ,user)))
found)))
(provide 'mail-source)
......
......@@ -42,13 +42,12 @@
(require 'mail-parse)
(require 'mml)
(require 'rfc822)
(require 'format-spec)
(require 'dired)
(require 'mm-util)
(require 'rfc2047)
(require 'puny)
(require 'rmc) ; read-multiple-choice
(eval-when-compile (require 'subr-x)) ; when-let*
(require 'rmc) ; read-multiple-choice
(eval-when-compile (require 'subr-x))
(autoload 'mailclient-send-it "mailclient")
......@@ -440,8 +439,8 @@ whitespace)."
(defcustom message-elide-ellipsis "\n[...]\n\n"
"The string which is inserted for elided text.
This is a format-spec string, and you can use %l to say how many
lines were removed, and %c to say how many characters were
This is a `format-spec' string, and you can use %l to say how
many lines were removed, and %c to say how many characters were
removed."
:type 'string
:link '(custom-manual "(message)Various Commands")
......@@ -3977,7 +3976,6 @@ This function uses `mail-citation-hook' if that is non-nil."
"Cite function in the standard Message manner."
(message-cite-original-1 nil))
(autoload 'format-spec "format-spec")
(autoload 'gnus-date-get-time "gnus-util")
(defun message-insert-formatted-citation-line (&optional from date tz)
......@@ -4002,20 +4000,18 @@ See `message-citation-line-format'."
(when (or message-reply-headers (and from date))
(unless from
(setq from (mail-header-from message-reply-headers)))
(let* ((data (condition-case ()
(funcall (if (boundp 'gnus-extract-address-components)
gnus-extract-address-components
'mail-extract-address-components)
from)
(error nil)))
(let* ((data (ignore-errors
(funcall (or (bound-and-true-p
gnus-extract-address-components)
#'mail-extract-address-components)
from)))
(name (car data))
(fname name)
(lname name)
(net (car (cdr data)))
(name-or-net (or (car data)
(car (cdr data)) from))
(net (cadr data))
(name-or-net (or name net from))
(time
(when (string-match "%[^fnNFL]" message-citation-line-format)
(when (string-match-p "%[^FLNfn]" message-citation-line-format)
(cond ((numberp (car-safe date)) date) ;; backward compatibility
(date (gnus-date-get-time date))
(t
......@@ -4024,68 +4020,53 @@ See `message-citation-line-format'."
(tz (or tz
(when (stringp date)
(nth 8 (parse-time-string date)))))
(flist
(let ((i ?A) lst)
(when (stringp name)
;; Guess first name and last name:
(let* ((names (delq
nil
(mapcar
(lambda (x)
(if (string-match "\\`\\(\\w\\|[-.]\\)+\\'"
x)
x
nil))
(split-string name "[ \t]+"))))
(count (length names)))
(cond ((= count 1)
(setq fname (car names)
lname ""))
((or (= count 2) (= count 3))
(setq fname (car names)
lname (mapconcat 'identity (cdr names) " ")))
((> count 3)
(setq fname (mapconcat 'identity
(butlast names (- count 2))