Подстановка именованных строк?

13

Мне часто приходится делать несколько замен одной и той же строки:

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

(это просто фиктивный пример, в этом случае лучше склеить «а» с пробелом, но в целом я имею дело с более сложными ситуациями)

Есть ли способ сделать именную замену? Например, в Python можно написать:

"{0} {0} {0}".format("a") # or:
"{name} {name} {name}".format(name="a")
саман
источник
@Malabarba: я разместил здесь измененный заголовок некоторого ответа из этой ветки в качестве ответа .
Adobe

Ответы:

16

Переписывание этого ответа дает другое решение:

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

Изменить : еще одно format-specрешение

Как Малабарба дает другое решение в комментариях:

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

Изменить 2 : Оценка до замены:

Вот примеры с оценкой перед заменой:

(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
саман
источник
3
Также обратите внимание, что format-spec-makeэто просто alist:'((?a . "a") (?b . "b"))
Малабарба
1
«похоже, не работает с числами» - см. emacs.stackexchange.com/questions/7481/…
npostavs
@npostavs: Приятно знать! Я отредактировал ответ.
Adobe
14

Библиотека манипуляций со строками Магнара Свена s.el предоставляет множество способов сделать это. Например:

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

Обратите внимание , что s-formatможно взять любую функцию заменителя, но обеспечивает специальную обработку для aget, eltи gethash. Таким образом, вы можете использовать список токенов и ссылаться на них по индексу, например так:

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

Вы также можете заменить, используя переменные в области видимости, например так:

(let ((name "test"))
  (s-lex-format "${name} ${name} ${name}"))
;; ==> "test test test"
glucas
источник
1
Отлично, я не знал об этой функции! Я использовал s.el большую часть времени, чтобы просто посмотреть, как выполнять обычные задачи по обработке строк в Emacs, но это действительно нечто большее, чем просто однострочная оболочка существующей функции.
васамаса
3

s-lex-формат s.el - это действительно то, что вам нужно, но если вы действительно хотите поместить код внутри блоков подстановки, а не просто в имена переменных, я написал это в качестве доказательства концепции.

(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."

Вы даже можете вставить fmtзвонок внутри другого, fmtесли вы сумасшедший

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

Код просто расширяется до formatвызова, поэтому все замены выполняются по порядку и оцениваются во время выполнения.

(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)))

Улучшения могут быть сделаны с тем, какой тип формата используется вместо того, чтобы всегда использовать% s, но это должно было бы быть сделано во время выполнения и добавило бы накладные расходы, но могло бы быть сделано, окружая все аргументы форматирования в вызове функции, которая хорошо форматирует вещи, хорошо основанные по типу, но на самом деле единственный сценарий, в котором вы захотите, это, вероятно, float, и вы могли бы даже сделать (формат "% f" float) в замене, если вы были в отчаянии.

Если я над этим больше работаю, я скорее обновлю эту суть, чем этот ответ. https://gist.github.com/jordonbiondo/c4e22b4289be130bc59b

Джордон Биондо
источник
3

Не универсальный, но решит ваше дело:

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

Используя приведенный пример:

(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))

дает:

" * - :raw-html:`<img width=\"100%\" src=\"http://xxx.xxx/images/languages/staff/some-image.jpg\" alt=\"some-image.jpg\"/>` - .. _some-image:"
wvxvw
источник
Вот пример строки, с которой я имею дело: " * - :raw-html:`<img width=\"100%%\" src=\"http://xxx.xxx/images/languages/staff/%s.jpg\" alt=\"%s.jpg\"/>` - .. _%s:"- все %sодинаковые.
Adobe
@ Adobe Я обновил ответ на вашем примере.
wvxvw