Substitution de chaîne nommée?

13

Je dois souvent faire plusieurs substitutions de la même chaîne:

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

(c'est juste un exemple factice, dans ce cas, il vaut mieux coller "a" avec un espace, mais en général je traite des situations plus compliquées)

Existe-t-il un moyen de faire une substitution nommée? Par exemple en python on écrirait:

"{0} {0} {0}".format("a") # or:
"{name} {name} {name}".format(name="a")
Adobe
la source
@Malabarba: J'ai posté une version modifiée d'une réponse de ce fil ici comme réponse .
Adobe

Réponses:

16

Réécrire cette réponse donne une autre solution:

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

Edit : Une autre format-specsolution

Comme Malabarba donne une autre solution dans les commentaires:

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

Edit 2 : Évaluation avant substitution:

Voici des exemples avec évaluation avant 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
Adobe
la source
3
Notez également que ce format-spec-maken'est qu'une liste:'((?a . "a") (?b . "b"))
Malabarba
1
"ne semble pas fonctionner pour les nombres" - voir emacs.stackexchange.com/questions/7481/…
npostavs
@npostavs: Bon à savoir! J'ai édité la réponse.
Adobe
14

La bibliothèque de manipulation de chaînes de Magnar Sveen s.el offre une variété de façons de le faire. Par exemple:

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

Notez que s-formatpeut prendre une fonction de succédané, mais fournit un traitement spécial pour aget, eltet gethash. Vous pouvez donc utiliser une liste de jetons et les référencer par index, comme ceci:

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

Vous pouvez également remplacer à l'aide de variables dans la portée, comme ceci:

(let ((name "test"))
  (s-lex-format "${name} ${name} ${name}"))
;; ==> "test test test"
glucas
la source
1
Excellent, je ne connaissais pas cette fonctionnalité! J'ai utilisé s.el la plupart du temps pour simplement voir comment effectuer des tâches de manipulation de chaînes courantes dans Emacs, mais c'est vraiment plus qu'un simple wrapper d'une ligne d'une fonction existante.
wasamasa
3

Le format s-lex de s.el est vraiment ce que vous voulez, mais si vous voulez réellement pouvoir mettre du code à l'intérieur des blocs de substitution et pas seulement des noms de variables, j'ai écrit cela comme une preuve de 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."

Vous pouvez même intégrer un fmtappel dans un autre fmtsi vous êtes fou

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

Le code se développe simplement en un formatappel afin que toutes les substitutions soient effectuées dans l'ordre et évaluées au moment de l'exécution.

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

Des améliorations pourraient être apportées au type de format utilisé au lieu de toujours utiliser% s, mais cela devrait être fait au moment de l'exécution et ajouterait des frais généraux, mais cela pourrait être fait en entourant tous les arguments de format dans un appel de fonction qui formate joliment les choses en fonction de la base sur le type mais vraiment le seul scénario où vous voudriez que ce soit probablement des flottants et vous pourriez même faire un (format "% f" flottant) dans la substitution est que vous étiez désespéré.

Si j'y travaille plus, je suis plus susceptible de mettre à jour cet essentiel au lieu de cette réponse. https://gist.github.com/jordonbiondo/c4e22b4289be130bc59b

Jordon Biondo
la source
3

Pas un usage général, mais résoudra votre cas:

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

En utilisant l'exemple fourni:

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

donne:

" * - :raw-html:`<img width=\"100%\" src=\"http://xxx.xxx/images/languages/staff/some-image.jpg\" alt=\"some-image.jpg\"/>` - .. _some-image:"
wvxvw
la source
Voici un exemple de chaîne que je traite: " * - :raw-html:`<img width=\"100%%\" src=\"http://xxx.xxx/images/languages/staff/%s.jpg\" alt=\"%s.jpg\"/>` - .. _%s:"- tous %ssont les mêmes.
Adobe
@Adobe J'ai mis à jour la réponse avec votre exemple.
wvxvw