diff options
-rw-r--r-- | doc/lispref/strings.texi | 35 | ||||
-rw-r--r-- | etc/NEWS | 17 | ||||
-rw-r--r-- | lisp/battery.el | 18 | ||||
-rw-r--r-- | lisp/dired-aux.el | 15 | ||||
-rw-r--r-- | lisp/erc/erc-match.el | 19 | ||||
-rw-r--r-- | lisp/erc/erc.el | 21 | ||||
-rw-r--r-- | lisp/format-spec.el | 183 | ||||
-rw-r--r-- | lisp/gnus/gnus-sieve.el | 10 | ||||
-rw-r--r-- | lisp/gnus/gssapi.el | 11 | ||||
-rw-r--r-- | lisp/gnus/mail-source.el | 30 | ||||
-rw-r--r-- | lisp/gnus/message.el | 137 | ||||
-rw-r--r-- | lisp/image-dired.el | 1 | ||||
-rw-r--r-- | lisp/net/eww.el | 1 | ||||
-rw-r--r-- | lisp/net/imap.el | 30 | ||||
-rw-r--r-- | lisp/net/network-stream.el | 13 | ||||
-rw-r--r-- | lisp/obsolete/tls.el | 16 | ||||
-rw-r--r-- | lisp/textmodes/tex-mode.el | 3 | ||||
-rw-r--r-- | test/lisp/battery-tests.el | 4 | ||||
-rw-r--r-- | test/lisp/format-spec-tests.el | 135 |
19 files changed, 408 insertions, 291 deletions
diff --git a/doc/lispref/strings.texi b/doc/lispref/strings.texi index 4a7bda57c4e..2ef88b90254 100644 --- a/doc/lispref/strings.texi +++ b/doc/lispref/strings.texi @@ -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 diff --git a/lisp/battery.el b/lisp/battery.el index b8855a8ce37..38728196507 100644 --- a/lisp/battery.el +++ b/lisp/battery.el @@ -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. diff --git a/lisp/dired-aux.el b/lisp/dired-aux.el index 0d481f4ac19..efb214088d8 100644 --- a/lisp/dired-aux.el +++ b/lisp/dired-aux.el @@ -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)) diff --git a/lisp/erc/erc-match.el b/lisp/erc/erc-match.el index 3107ff2ccd1..0e98f2bc613 100644 --- a/lisp/erc/erc-match.el +++ b/lisp/erc/erc-match.el @@ -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)) diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el index cfde84e19aa..38807787945 100644 --- a/lisp/erc/erc.el +++ b/lisp/erc/erc.el @@ -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") diff --git a/lisp/format-spec.el b/lisp/format-spec.el index 9278bd74c42..6af79a44167 100644 --- a/lisp/format-spec.el +++ b/lisp/format-spec.el @@ -1,4 +1,4 @@ -;;; 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) diff --git a/lisp/gnus/gnus-sieve.el b/lisp/gnus/gnus-sieve.el index 278e3a5d6f3..5d8f9b55deb 100644 --- a/lisp/gnus/gnus-sieve.el +++ b/lisp/gnus/gnus-sieve.el @@ -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 () diff --git a/lisp/gnus/gssapi.el b/lisp/gnus/gssapi.el index 218a1542e3a..485d58ad94e 100644 --- a/lisp/gnus/gssapi.el +++ b/lisp/gnus/gssapi.el @@ -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)) diff --git a/lisp/gnus/mail-source.el b/lisp/gnus/mail-source.el index acf35a376a9..43180726c45 100644 --- a/lisp/gnus/mail-source.el +++ b/lisp/gnus/mail-source.el @@ -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) diff --git a/lisp/gnus/message.el b/lisp/gnus/message.el index 5a6827af762..fb560f0eab8 100644 --- a/lisp/gnus/message.el +++ b/lisp/gnus/message.el @@ -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)) - " ") - lname (mapconcat 'identity - (nthcdr 2 names) - " ")))) - (when (string-match "\\(.*\\),\\'" fname) - (let ((newlname (match-string 1 fname))) - (setq fname lname lname newlname))))) - ;; The following letters are not used in `format-time-string': - (push ?E lst) (push "<E>" lst) - (push ?F lst) (push (or fname name-or-net) lst) - ;; We might want to use "" instead of "<X>" later. - (push ?J lst) (push "<J>" lst) - (push ?K lst) (push "<K>" lst) - (push ?L lst) (push lname lst) - (push ?N lst) (push name-or-net lst) - (push ?O lst) (push "<O>" lst) - (push ?P lst) (push "<P>" lst) - (push ?Q lst) (push "<Q>" lst) - (push ?f lst) (push from lst) - (push ?i lst) (push "<i>" lst) - (push ?n lst) (push net lst) - (push ?o lst) (push "<o>" lst) - (push ?q lst) (push "<q>" lst) - (push ?t lst) (push "<t>" lst) - (push ?v lst) (push "<v>" lst) - ;; Delegate the rest to `format-time-string': - (while (<= i ?z) - (when (and (not (memq i lst)) - ;; Skip (Z,a) - (or (<= i ?Z) - (>= i ?a))) - (push i lst) - (push (condition-case nil - (format-time-string (format "%%%c" i) time tz) - (error (format ">%c<" i))) - lst)) - (setq i (1+ i))) - (reverse lst))) - (spec (apply 'format-spec-make flist))) + spec) + (when (stringp name) + ;; Guess first name and last name: + (let* ((names (seq-filter + (lambda (s) + (string-match-p (rx bos (+ (in word ?. ?-)) eos) s)) + (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 (string-join (cdr names) " "))) + ((> count 3) + (setq fname (string-join (butlast names (- count 2)) + " ") + lname (string-join (nthcdr 2 names) " ")))) + (when (string-match "\\(.*\\),\\'" fname) + (let ((newlname (match-string 1 fname))) + (setq fname lname lname newlname))))) + ;; The following letters are not used in `format-time-string': + (push (cons ?E "<E>") spec) + (push (cons ?F (or fname name-or-net)) spec) + ;; We might want to use "" instead of "<X>" later. + (push (cons ?J "<J>") spec) + (push (cons ?K "<K>") spec) + (push (cons ?L lname) spec) + (push (cons ?N name-or-net) spec) + (push (cons ?O "<O>") spec) + (push (cons ?P "<P>") spec) + (push (cons ?Q "<Q>") spec) + (push (cons ?f from) spec) + (push (cons ?i "<i>") spec) + (push (cons ?n net) spec) + (push (cons ?o "<o>") spec) + (push (cons ?q "<q>") spec) + (push (cons ?t "<t>") spec) + (push (cons ?v "<v>") spec) + ;; Delegate the rest to `format-time-string': + (dolist (c (nconc (number-sequence ?A ?Z) + (number-sequence ?a ?z))) + (unless (assq c spec) + (push (cons c (condition-case nil + (format-time-string (format "%%%c" c) time tz) + (error (format ">%c<" c)))) + spec))) (insert (format-spec message-citation-line-format spec))) (newline))) diff --git a/lisp/image-dired.el b/lisp/image-dired.el index 1cc38ba714b..6f297672caf 100644 --- a/lisp/image-dired.el +++ b/lisp/image-dired.el @@ -149,7 +149,6 @@ ;;; Code: (require 'dired) -(require 'format-spec) (require 'image-mode) (require 'widget) diff --git a/lisp/net/eww.el b/lisp/net/eww.el index 2a70560ca7b..cf31d37f072 100644 --- a/lisp/net/eww.el +++ b/lisp/net/eww.el @@ -25,7 +25,6 @@ ;;; Code: (require 'cl-lib) -(require 'format-spec) (require 'shr) (require 'url) (require 'url-queue) diff --git a/lisp/net/imap.el b/lisp/net/imap.el index aa10f0291fd..a492dc8c798 100644 --- a/lisp/net/imap.el +++ b/lisp/net/imap.el @@ -136,7 +136,6 @@ ;;; Code: (eval-when-compile (require 'cl-lib)) -(require 'format-spec) (require 'utf7) (require 'rfc2104) ;; Hmm... digest-md5 is not part of Emacs. @@ -517,12 +516,9 @@ sure of changing the value of `foo'." (process-connection-type imap-process-connection-type) (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 imap-default-user)))) + (format-spec cmd `((?s . ,server) + (?p . ,(number-to-string port)) + (?l . ,imap-default-user))))) response) (when process (with-current-buffer buffer @@ -583,12 +579,9 @@ sure of changing the value of `foo'." (process-connection-type imap-process-connection-type) (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 imap-default-user)))) + (format-spec cmd `((?s . ,server) + (?p . ,(number-to-string port)) + (?l . ,imap-default-user))))) response) (when process (with-current-buffer buffer @@ -701,13 +694,10 @@ sure of changing the value of `foo'." (process-connection-type imap-process-connection-type) (process (start-process name buffer shell-file-name shell-command-switch - (format-spec - cmd - (format-spec-make - ?s server - ?g imap-shell-host - ?p (number-to-string port) - ?l imap-default-user))))) + (format-spec cmd `((?s . ,server) + (?g . ,imap-shell-host) + (?p . ,(number-to-string port)) + (?l . ,imap-default-user)))))) (when process (while (and (memq (process-status process) '(open run)) (set-buffer buffer) ;; XXX "blue moon" nntp.el bug diff --git a/lisp/net/network-stream.el b/lisp/net/network-stream.el index 1d5cf382a84..1c371f59870 100644 --- a/lisp/net/network-stream.el +++ b/lisp/net/network-stream.el @@ -170,8 +170,8 @@ a greeting from the server. :nowait, if non-nil, says the connection should be made asynchronously, if possible. -:shell-command is a format-spec string that can be used if :type -is `shell'. It has two specs, %s for host and %p for port +:shell-command is a `format-spec' string that can be used if +:type is `shell'. It has two specs, %s for host and %p for port number. Example: \"ssh gateway nc %s %p\". :tls-parameters is a list that should be supplied if you're @@ -453,11 +453,7 @@ gnutls-boot (as returned by `gnutls-boot-parameters')." (network-stream-command stream capability-command eo-capa) 'tls))))))) -(declare-function format-spec "format-spec" (format spec)) -(declare-function format-spec-make "format-spec" (&rest pairs)) - (defun network-stream-open-shell (name buffer host service parameters) - (require 'format-spec) (let* ((capability-command (plist-get parameters :capability-command)) (eoc (plist-get parameters :end-of-command)) (start (with-current-buffer buffer (point))) @@ -467,9 +463,8 @@ gnutls-boot (as returned by `gnutls-boot-parameters')." shell-command-switch (format-spec (plist-get parameters :shell-command) - (format-spec-make - ?s host - ?p service)))))) + `((?s . ,host) + (?p . ,service))))))) (when coding (if (consp coding) (set-process-coding-system stream (car coding) diff --git a/lisp/obsolete/tls.el b/lisp/obsolete/tls.el index cd091c0108e..d1b215cbfb8 100644 --- a/lisp/obsolete/tls.el +++ b/lisp/obsolete/tls.el @@ -47,9 +47,6 @@ (require 'gnutls) -(autoload 'format-spec "format-spec") -(autoload 'format-spec-make "format-spec") - (defgroup tls nil "Transport Layer Security (TLS) parameters." :group 'comm) @@ -224,14 +221,11 @@ Fourth arg PORT is an integer specifying a port to connect to." (while (and (not done) (setq cmd (pop cmds))) (let ((process-connection-type tls-process-connection-type) (formatted-cmd - (format-spec - cmd - (format-spec-make - ?t (car (gnutls-trustfiles)) - ?h host - ?p (if (integerp port) - (int-to-string port) - port))))) + (format-spec cmd `((?t . ,(car (gnutls-trustfiles))) + (?h . ,host) + (?p . ,(if (integerp port) + (number-to-string port) + port)))))) (message "Opening TLS connection with `%s'..." formatted-cmd) (setq process (start-process name buffer shell-file-name shell-command-switch diff --git a/lisp/textmodes/tex-mode.el b/lisp/textmodes/tex-mode.el index 1b302e34a73..e3d5759579a 100644 --- a/lisp/textmodes/tex-mode.el +++ b/lisp/textmodes/tex-mode.el @@ -2295,9 +2295,6 @@ FILE is typically the output DVI or PDF file." (setq uptodate nil))))) uptodate))) - -(autoload 'format-spec "format-spec") - (defvar tex-executable-cache nil) (defun tex-executable-exists-p (name) "Like `executable-find' but with a cache." diff --git a/test/lisp/battery-tests.el b/test/lisp/battery-tests.el index 052ae49a800..4cb7470d884 100644 --- a/test/lisp/battery-tests.el +++ b/test/lisp/battery-tests.el @@ -52,7 +52,7 @@ "Test `battery-format'." (should (equal (battery-format "" ()) "")) (should (equal (battery-format "" '((?b . "-"))) "")) - (should (equal (battery-format "%a%b%p%%" '((?b . "-") (?p . "99"))) - "-99%"))) + (should (equal (battery-format "%2a%-3b%.1p%%" '((?b . "-") (?p . "99"))) + "- 9%"))) ;;; battery-tests.el ends here diff --git a/test/lisp/format-spec-tests.el b/test/lisp/format-spec-tests.el index 23ee88c5269..11882217afb 100644 --- a/test/lisp/format-spec-tests.el +++ b/test/lisp/format-spec-tests.el @@ -22,22 +22,145 @@ (require 'ert) (require 'format-spec) -(ert-deftest test-format-spec () +(ert-deftest format-spec-make () + "Test `format-spec-make'." + (should-not (format-spec-make)) + (should-error (format-spec-make ?b)) + (should (equal (format-spec-make ?b "b") '((?b . "b")))) + (should-error (format-spec-make ?b "b" ?a)) + (should (equal (format-spec-make ?b "b" ?a 'a) + '((?b . "b") + (?a . a))))) + +(ert-deftest format-spec-parse-flags () + "Test `format-spec--parse-flags'." + (should-not (format-spec--parse-flags nil)) + (should-not (format-spec--parse-flags "")) + (should (equal (format-spec--parse-flags "-") '(:pad-right))) + (should (equal (format-spec--parse-flags " 0") '(:pad-zero))) + (should (equal (format-spec--parse-flags " -x0y< >^_z ") + '(:pad-right :pad-zero :chop-left :chop-right + :upcase :downcase)))) + +(ert-deftest format-spec-do-flags () + "Test `format-spec--do-flags'." + (should (equal (format-spec--do-flags "" () nil nil) "")) + (dolist (flag '(:pad-zero :pad-right :upcase :downcase + :chop-left :chop-right)) + (should (equal (format-spec--do-flags "" (list flag) nil nil) ""))) + (should (equal (format-spec--do-flags "FOOBAR" '(:downcase :chop-right) 5 2) + " fo")) + (should (equal (format-spec--do-flags + "foobar" '(:pad-zero :pad-right :upcase :chop-left) 5 2) + "AR000"))) + +(ert-deftest format-spec-do-flags-truncate () + "Test `format-spec--do-flags' truncation." + (let (flags) + (should (equal (format-spec--do-flags "" flags nil 0) "")) + (should (equal (format-spec--do-flags "" flags nil 1) "")) + (should (equal (format-spec--do-flags "a" flags nil 0) "")) + (should (equal (format-spec--do-flags "a" flags nil 1) "a")) + (should (equal (format-spec--do-flags "a" flags nil 2) "a")) + (should (equal (format-spec--do-flags "asd" flags nil 0) "")) + (should (equal (format-spec--do-flags "asd" flags nil 1) "a"))) + (let ((flags '(:chop-left))) + (should (equal (format-spec--do-flags "" flags nil 0) "")) + (should (equal (format-spec--do-flags "" flags nil 1) "")) + (should (equal (format-spec--do-flags "a" flags nil 0) "")) + (should (equal (format-spec--do-flags "a" flags nil 1) "a")) + (should (equal (format-spec--do-flags "a" flags nil 2) "a")) + (should (equal (format-spec--do-flags "asd" flags nil 0) "")) + (should (equal (format-spec--do-flags "asd" flags nil 1) "d")))) + +(ert-deftest format-spec-do-flags-pad () + "Test `format-spec--do-flags' padding." + (let (flags) + (should (equal (format-spec--do-flags "" flags 0 nil) "")) + (should (equal (format-spec--do-flags "" flags 1 nil) " ")) + (should (equal (format-spec--do-flags "a" flags 0 nil) "a")) + (should (equal (format-spec--do-flags "a" flags 1 nil) "a")) + (should (equal (format-spec--do-flags "a" flags 2 nil) " a"))) + (let ((flags '(:pad-zero))) + (should (equal (format-spec--do-flags "" flags 0 nil) "")) + (should (equal (format-spec--do-flags "" flags 1 nil) "0")) + (should (equal (format-spec--do-flags "a" flags 0 nil) "a")) + (should (equal (format-spec--do-flags "a" flags 1 nil) "a")) + (should (equal (format-spec--do-flags "a" flags 2 nil) "0a"))) + (let ((flags '(:pad-right))) + (should (equal (format-spec--do-flags "" flags 0 nil) "")) + (should (equal (format-spec--do-flags "" flags 1 nil) " ")) + (should (equal (format-spec--do-flags "a" flags 0 nil) "a")) + (should (equal (format-spec--do-flags "a" flags 1 nil) "a")) + (should (equal (format-spec--do-flags "a" flags 2 nil) "a "))) + (let ((flags '(:pad-right :pad-zero))) + (should (equal (format-spec--do-flags "" flags 0 nil) "")) + (should (equal (format-spec--do-flags "" flags 1 nil) "0")) + (should (equal (format-spec--do-flags "a" flags 0 nil) "a")) + (should (equal (format-spec--do-flags "a" flags 1 nil) "a")) + (should (equal (format-spec--do-flags "a" flags 2 nil) "a0")))) + +(ert-deftest format-spec-do-flags-chop () + "Test `format-spec--do-flags' chopping." + (let ((flags '(:chop-left))) + (should (equal (format-spec--do-flags "a" flags 0 nil) "")) + (should (equal (format-spec--do-flags "a" flags 1 nil) "a")) + (should (equal (format-spec--do-flags "asd" flags 0 nil) "")) + (should (equal (format-spec--do-flags "asd" flags 1 nil) "d"))) + (let ((flags '(:chop-right))) + (should (equal (format-spec--do-flags "a" flags 0 nil) "")) + (should (equal (format-spec--do-flags "a" flags 1 nil) "a")) + (should (equal (format-spec--do-flags "asd" flags 0 nil) "")) + (should (equal (format-spec--do-flags "asd" flags 1 nil) "a")))) + +(ert-deftest format-spec-do-flags-case () + "Test `format-spec--do-flags' case fiddling." + (dolist (flag '(:pad-zero :pad-right :chop-left :chop-right)) + (let ((flags (list flag))) + (should (equal (format-spec--do-flags "a" flags nil nil) "a")) + (should (equal (format-spec--do-flags "A" flags nil nil) "A"))) + (let ((flags (list flag :downcase))) + (should (equal (format-spec--do-flags "a" flags nil nil) "a")) + (should (equal (format-spec--do-flags "A" flags nil nil) "a"))) + (let ((flags (list flag :upcase))) + (should (equal (format-spec--do-flags "a" flags nil nil) "A")) + (should (equal (format-spec--do-flags "A" flags nil nil) "A"))))) + +(ert-deftest format-spec () + (should (equal (format-spec "" ()) "")) + (should (equal (format-spec "a" ()) "a")) + (should (equal (format-spec "b" '((?b . "bar"))) "b")) + (should (equal (format-spec "%%%b%%b%b%%" '((?b . "bar"))) "%bar%bbar%")) (should (equal (format-spec "foo %b zot" `((?b . "bar"))) "foo bar 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"))) + "foo bar zot")) + (should (equal-including-properties + (format-spec (propertize "a" 'a 'b) '((?a . "foo"))) + #("a" 0 1 (a b)))) + (let ((fmt (concat (propertize "%a" 'a 'b) + (propertize "%%" 'c 'd) + "%b" + (propertize "%b" 'e 'f)))) + (should (equal-including-properties + (format-spec fmt '((?b . "asd") (?a . "fgh"))) + #("fgh%asdasd" 0 3 (a b) 3 4 (c d) 7 10 (e f)))))) -(ert-deftest test-format-unknown () +(ert-deftest format-spec-unknown () (should-error (format-spec "foo %b %z zot" '((?b . "bar")))) + (should-error (format-spec "foo %b %%%z zot" '((?b . "bar")))) (should (equal (format-spec "foo %b %z zot" '((?b . "bar")) t) "foo bar %z zot")) - (should (equal (format-spec "foo %b %z %% zot" '((?b . "bar")) t) - "foo bar %z %% zot"))) + (should (equal (format-spec "foo %4b %%%4z %%4 zot" '((?b . "bar")) t) + "foo bar %%%4z %%4 zot")) + (should (equal (format-spec "foo %4b %%%4z %%4 zot" '((?b . "bar")) 'ignore) + "foo bar %%4z %4 zot")) + (should (equal (format-spec "foo %4b %%%4z %%4 zot" '((?b . "bar")) 'delete) + "foo bar % %4 zot"))) -(ert-deftest test-format-modifiers () +(ert-deftest format-spec-flags () (should (equal (format-spec "foo %10b zot" '((?b . "bar"))) "foo bar zot")) (should (equal (format-spec "foo % 10b zot" '((?b . "bar"))) |