diff options
Diffstat (limited to 'lisp/format-spec.el')
-rw-r--r-- | lisp/format-spec.el | 126 |
1 files changed, 98 insertions, 28 deletions
diff --git a/lisp/format-spec.el b/lisp/format-spec.el index db6b8768088..fec93ce83d9 100644 --- a/lisp/format-spec.el +++ b/lisp/format-spec.el @@ -24,44 +24,114 @@ ;;; Code: -(eval-when-compile (require 'cl)) +(eval-when-compile + (require 'subr-x)) -(defun format-spec (format specification) +(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 \"bash %u %k\", +FORMAT is a string containing `format'-like specs like \"su - %u %k\", while SPECIFICATION is an alist mapping from format spec characters -to values. Any text properties on a %-spec itself are propagated to -the text that it generates." +to values. + +For instance: + + (format-spec \"su - %u %l\" + `((?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 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. + +If ONLY-PRESENT, format spec characters not present in +SPECIFICATION are ignored, and the \"%\" characters are left +where they are, including \"%%\" strings." (with-temp-buffer (insert format) (goto-char (point-min)) (while (search-forward "%" nil t) (cond - ;; Quoted percent sign. - ((eq (char-after) ?%) - (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))) - (val (assq spec specification))) - (unless val - (error "Invalid format character: `%%%c'" spec)) - (setq val (cdr val)) - ;; Pad result to desired length. - (let ((text (format (concat "%" num "s") val))) - ;; 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 - (error "Invalid format string")))) + ;; 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"))))) (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) + (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)) + (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, |