Заміна іменних рядків?


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: Я відправив модифіковану VESTION деякого відповіді від цього потоку тут в якості відповіді .
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це просто '((?a . "a") (?b . "b"))
аліст

1
"Здається, не працює для чисел" - див. emacs.stackexchange.com/questions/7481/…
npostavs

@npostavs: Чудово знати! Я відредагував відповідь.
Adobe

14

Бібліотека струнних маніпуляцій Magnar Sveen пропонує різноманітні способи зробити це. Наприклад:

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

1
Чудово, я не знав про цю особливість! Я більшу частину часу використовував s.el, щоб просто заглянути до того, як виконувати загальні завдання маніпуляції з рядками в Emacs, але це справді більше, ніж просто однорядкова обгортка існуючої функції.
wasamasa

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, але це потрібно робити під час виконання та додавати накладні витрати, але це можна зробити, оточуючи всі аргументи формату у виклику функції, який гарно форматує речі на тип, але насправді єдиний сценарій, де ви хочете, що це, мабуть, плаває, і ви навіть можете зробити (формат "% f" з плаваючою підміною) - це ви відчайдушно.

Якщо я над цим працюю більше, швидше я оновлюю цю суть замість цієї відповіді. 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:"

Ось зразок рядка, з яким я маю справу: " * - :raw-html:`<img width=\"100%%\" src=\"http://xxx.xxx/images/languages/staff/%s.jpg\" alt=\"%s.jpg\"/>` - .. _%s:"- всі %sоднакові.
Adobe

@Adobe Я оновив відповідь вашим прикладом.
wvxvw
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.