13

The format-time-string function takes a string, and replaces a set of special constructs in that string (characters preceeded by %) with some specific text.

I'd like to implement a functionality like that in a function of my own:

  • I have an alist between chars and symbols like: '((?n . name) (?r . reputation)).
  • The function should take a string like "My name is %n, and my rep is %r".
  • It should replace %n and %r with the value of the variables name and reputation, and return the result.
  • It should handle %% just like format-time-string (replace it with %).

What's the easiest way to implement this function?
Is there a library or function that will facilitate this? Handling %% correctly is important.

0

3 Answers 3

19

The default functions to be used are format-spec and format-spec-make:

(let* ((name "Malabarba") (reputation "good") (fs (format-spec-make ?n name ?r reputation))) (format-spec "My name is %n, with a %r reputation. %% is kept." fs)) 
1
  • 1
    Nice, didn't expect such a thing to exist in Emacs already. What's particularly good is that there's rudimentary error handling for invalid format codes included. Commented Jan 7, 2015 at 12:34
7

Since elisp has rather primitive facilities for dealing with strings, it is better to put your format string into a buffer and iterate over that:

(defvar malabarba-alist '((?n . "Malabarba") (?r . 7488) (?% . "%"))) (defun malabarba-format (string) (with-temp-buffer (insert string) (goto-char 1) (while (search-forward "%" nil t) (let ((s (cdr (assoc (char-after) malabarba-alist)))) (cond (s (delete-char -1) (delete-char 1) (insert (format "%s" (eval s)))) (t (unless (eobp) (forward-char)))))) (buffer-string))) 

A minor subtlety — this doesn't break when there's a lone % at the end of the string because char-after returns nil at the end of the buffer.

1
  • Yes, this approach, of progressing through the string once, used also by @AlanShutko, makes more sense. I've deleted my answer. Commented Jan 7, 2015 at 7:20
3

Here is code that I use for a format-time-string for a fake calendar:

(defun mystcal-format-time (format-string time) "Format time %Y is the year. %m is the numeric month. %B is the full name of the month. %d is the day of the month, zero-padded, %e is blank-padded. %u is the numeric day of week from 1 (Monday) to 7, %w from 0 (Sunday) to 6. %A is the locale's full name of the day of week. %H is the hour on a 24-hour clock, %I is on a 12-hour clock, %k is like %H only blank-padded, %l is like %I blank-padded. %p is the locale's equivalent of either AM or PM. %M is the minute. %S is the second." (let* (output (start 0) (decoded-time (if (listp time) time (mystcal-decode-time time))) (turn (nth 0 decoded-time)) (minute (nth 1 decoded-time)) (hour (nth 2 decoded-time)) (day (nth 3 decoded-time)) (month (mystcal-month decoded-time)) (year (nth 5 decoded-time)) (weekday (mystcal-weekday-of-day day)) (hour12 (mod hour 12))) (save-match-data (while (string-match "%" format-string start) (let ((index (match-beginning 0))) ;; First copy non-format text (setq output (concat output (substring format-string start index))) ;; Process format codes here (let (fmted) (setq output (concat output (case (aref format-string (1+ index)) (?Y (number-to-string year)) (?m (number-to-string month)) (?B (mystcal-month-name month)) (?d (format "%02d" day)) (?e (format "%2d" day)) (?u (number-to-string (if (zerop weekday) 7 weekday))) (?w (number-to-string weekday)) (?A (mystcal-weekday-name (mystcal-weekday-of-day day))) (?H (format "%02d" hour)) (?k (format "%2d" hour)) (?I (format "%02d" (if (zerop hour12) 12 hour12))) (?l (format "%2d" (if (zerop hour12) 12 hour12))) (?p (if (< hour 12) "AM" "PM")) (?M (format "%02d" minute)) (?S "00"))))) (setq start (+ 2 index))))) (setq output (concat output (substring format-string start))) output)) 

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.