12

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
13

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"
  • 1
    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

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:"
  • 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

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.