Nazwany ciąg znaków?

13

Często muszę dokonać kilku podstawień tego samego ciągu:

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

(to tylko fikcyjny przykład, w tym przypadku lepiej przykleić „a” spacją, ale ogólnie mam do czynienia z bardziej skomplikowanymi sytuacjami)

Czy istnieje sposób dokonania nazwanego podstawienia? Na przykład w pythonie można by napisać:

"{0} {0} {0}".format("a") # or:
"{name} {name} {name}".format(name="a")
Cegła suszona na słońcu
źródło
@Malabarba: Zamieściłem zmodyfikowaną vestion jakiejś odpowiedzi z tego wątku tutaj jako odpowiedź .
Adobe

Odpowiedzi:

16

Ponowne napisanie tej odpowiedzi daje inne rozwiązanie:

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

Edycja : inne format-specrozwiązanie

Ponieważ Malabarba podaje inne rozwiązanie w komentarzach:

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

Edycja 2 : Ocena przed zmianą:

Oto przykłady z oceną przed zamianą:

(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
Cegła suszona na słońcu
źródło
3
Zauważ też, że format-spec-maketo tylko alist:'((?a . "a") (?b . "b"))
Malabarba
1
„wydaje się, że nie działa na liczby” - patrz emacs.stackexchange.com/questions/7481/…
npostavs
@npostavs: Świetnie wiedzieć! Zredagowałem odpowiedź.
Adobe,
14

Biblioteka manipulacji ciągami Magnar Sveen s.el zapewnia wiele sposobów na zrobienie tego. Na przykład:

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

Należy pamiętać, że s-formatmoże przyjąć dowolną funkcję zamiennika, ale zapewnia specjalnej obsługi dla aget, elti gethash. Możesz więc użyć listy tokenów i odwoływać się do nich według indeksu, tak:

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

Można również zastąpić za pomocą zmiennych wewnątrz zakresu, takich jak to:

(let ((name "test"))
  (s-lex-format "${name} ${name} ${name}"))
;; ==> "test test test"
glucas
źródło
1
Doskonale, nie wiedziałem o tej funkcji! Przez większość czasu używałem s.el, aby po prostu zerknąć na to, jak wykonywać typowe zadania manipulacji ciągami w Emacsie, ale tak naprawdę jest to coś więcej niż tylko jednowierszowe opakowanie istniejącej funkcji.
wasamasa,
3

Format s-lex s.el jest naprawdę tym, czego chcesz, ale jeśli chcesz umieć kod wewnątrz bloków podstawienia, a nie tylko nazw zmiennych, napisałem to jako dowód koncepcji.

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

Możesz nawet osadzić fmtpołączenie w innym, fmtjeśli jesteś szalony

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

Kod po prostu rozwija się do formatwywołania, więc wszystkie podstawienia są wykonywane w kolejności i oceniane w czasie wykonywania.

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

Można wprowadzić ulepszenia w tym, który typ formatu jest używany zamiast zawsze używać% s, ale musiałoby to zostać wykonane w czasie wykonywania i dodałoby narzut, ale można by to zrobić, otaczając wszystkie argumenty formatu w wywołaniu funkcji, które ładnie formatuje rzeczy ładnie oparte na typie, ale tak naprawdę jedynym scenariuszem, w którym chciałbyś tego dokonać, jest prawdopodobnie liczba zmiennoprzecinkowa i możesz nawet zrobić zmienną zmiennoprzecinkową (format „% f”) w zamianie, ponieważ byłeś zdesperowany.

Jeśli popracuję nad tym bardziej, prawdopodobnie zaktualizuję tę treść zamiast tej odpowiedzi. https://gist.github.com/jordonbiondo/c4e22b4289be130bc59b

Jordon Biondo
źródło
3

Nie jest to cel ogólny, ale rozwiąże twoją sprawę:

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

Korzystając z podanego przykładu:

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

daje:

" * - :raw-html:`<img width=\"100%\" src=\"http://xxx.xxx/images/languages/staff/some-image.jpg\" alt=\"some-image.jpg\"/>` - .. _some-image:"
wvxvw
źródło
Oto przykładowy ciąg, z którym mam do czynienia: " * - :raw-html:`<img width=\"100%%\" src=\"http://xxx.xxx/images/languages/staff/%s.jpg\" alt=\"%s.jpg\"/>` - .. _%s:"- wszystkie %ssą takie same.
Adobe
@Adobe Zaktualizowałem odpowiedź twoim przykładem.
wvxvw