13

I often have to make several substitution of the same string:

(format "%s %s %s" "a" "a" "a") ;; gives: "a a a"

(it's just a dummy example, in this case it's better to glue "a" with a whitespace, but in general I deal with more complicated situations)

Is there a way to make a named substitution? For example in python one would write:

"{0} {0} {0}".format("a") # or:
"{name} {name} {name}".format(name="a")
16

Re-writing this answer gives another solution:

(format-spec "%a %a %a %b %b %b" (format-spec-make ?a "a" ?b "b"))

Edit: Another format-spec solution

As Malabarba gives another solution in comments:

(format-spec "%a %a %a %b %b %b" '((?a . "a") (?b . "b")))

Edit 2: Evaluation before substitution:

Here are examples with evaluation before substitution:

(let ( (a 1)
       (b 2) )
  (message (format-spec "a = %a; b = %b" (format-spec-make ?a a ?b b))) )
;; ⇒ a = 1; b = 1

(let ( (a 1)
       (b 2) )
  (message (format-spec "a = %a; b = %b" `((?a . ,a) (?b . ,b)))) )
;; ⇒ a = 1; b = 2
| improve this answer | |
15

Magnar Sveen's string manipulation library s.el provides a variety of ways to do this. For example:

(require 's)
(s-format "${name} ${name} ${name}" 'aget '(("name" . "test")))
;; ==> "test test test"

Note that s-format can take any replacer function, but provides special handling for aget, elt, and gethash. So you could use a list of tokens and reference them by index, like so:

(s-format "$0 $0 $0 $1 $1 $1" 'elt '("a" "b"))
;; ==> "a a a b b b"

You can also replace using in-scope variables, like this:

(let ((name "test"))
  (s-lex-format "${name} ${name} ${name}"))
;; ==> "test test test"
| improve this answer | |
  • 2
    Excellent, I didn't know about this feature! I've used s.el most of the time to just peek at how to do common string manipulation tasks in Emacs, but this is truly more than just a one-line wrapper of an existing function. – wasamasa Jan 14 '15 at 20:18
3

s.el's s-lex-format is really what you want, but if you want to actually be able to put code inside the substitution blocks and not just variable names, I wrote this as a proof of concept.

(defmacro fmt (str)
  "Elisp string interpolation for any expression."
  (let ((exprs nil))
    (with-temp-buffer
      (insert str)
      (goto-char 1)
      (while (re-search-forward "#{" nil t 1)
        (let ((here (point))
              (emptyp (eql (char-after) ?})))
          (unless  emptyp (push (read (buffer-substring (point) (progn (forward-sexp 1) (point)))) exprs))
          (delete-region (- here 2) (progn (search-forward "}") (point)))
          (unless emptyp (insert "%s"))
          (ignore-errors (forward-char 1))))
      (append (list 'format (buffer-string)) (reverse exprs)))))

;; demo with variable and code substitution 
(fmt "My name is #{user-full-name}, I am running Emacs #{(if (display-graphic-p) \"with a GUI\" \"in a terminal\")}.")
;; results in
"My name is Jordon Biondo, I am running Emacs with a GUI."

You can even embed an fmt call inside another fmt if you're crazy

(fmt "#{(fmt\"#{(fmt\\\"#{user-full-name}\\\")}\")}")
;; =>
"Jordon Biondo"

The code just expands to a format call so all the substitutions are done in order and evaluated at run time.

(cl-prettyexpand '(fmt "Hello, I'm running Emacs #{emacs-version} on a #{system-type} machine with #{(length (window-list))} open windows."))

;; expands to

(format "Hello, I'm running Emacs %s on a %s machine with %s open windows."
        emacs-version
        system-type
        (length (window-list)))

Improvements could be made with what format type is used instead of always using %s, but that would have to be done at runtime and would add overhead but could be done by surrounding all the format args in a function call that nicely formats things nicely based on type but really the only scenario where you would want that is probably floats and you could even do a (format "%f" float) in the substitution is you were desperate.

If I work on it more, I'm more likely to update this gist instead of this answer. https://gist.github.com/jordonbiondo/c4e22b4289be130bc59b

| improve this answer | |
3

Not a general-purpose, but will solve your case:

(apply 'format "%s %s %s" (make-list 3 'a))

Using provided example:

(apply 'format (concat " * - :raw-html:`<img width=\"100%%\" "
                       "src=\"http://xxx.xxx/images/languages/"
                       "staff/%s.jpg\" alt=\"%s.jpg\"/>` - .. _%s:")
       (make-list 3 'some-image))

gives:

" * - :raw-html:`<img width=\"100%\" src=\"http://xxx.xxx/images/languages/staff/some-image.jpg\" alt=\"some-image.jpg\"/>` - .. _some-image:"
| improve this answer | |
  • Here's a sample string I'm dealing with: " * - :raw-html:`<img width=\"100%%\" src=\"http://xxx.xxx/images/languages/staff/%s.jpg\" alt=\"%s.jpg\"/>` - .. _%s:" -- all %s are the same. – Adobe Jan 14 '15 at 21:25
  • @Adobe I've updated the answer with your example. – wvxvw Jan 15 '15 at 7:34
1

Since search engines lead me to this page...

Reading the documentation of format, I find the following:

A %-sequence other than %% may contain optional field number, flag,
width, and precision specifiers, as follows:

  %<field><flags><width><precision>character

where field is [0-9]+ followed by a literal dollar "$", flags is
[+ #0-]+, width is [0-9]+, and precision is a literal period "."
followed by [0-9]+.

If a %-sequence is numbered with a field with positive value N, the
Nth argument is substituted instead of the next one.  A format can
contain either numbered or unnumbered %-sequences but not both, except
that %% can be mixed with numbered %-sequences.

Which means the following will work (well.. it works for me, on Emacs 27.1)

(format "%1$s %1$s %1$s" "a") ;; gives: "a a a"
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.