¿Sustitución de cadena con nombre?

13

A menudo tengo que hacer varias sustituciones de la misma cadena:

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

(es solo un ejemplo ficticio, en este caso es mejor pegar "a" con un espacio en blanco, pero en general trato situaciones más complicadas)

¿Hay alguna manera de hacer una sustitución con nombre? Por ejemplo, en python se escribiría:

"{0} {0} {0}".format("a") # or:
"{name} {name} {name}".format(name="a")
Adobe
fuente
@Malabarba: publiqué una versión modificada de alguna respuesta de ese hilo aquí como respuesta .
Adobe

Respuestas:

16

Reescribir esta respuesta da otra solución:

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

Editar : otra format-specsolución

Como Malabarba da otra solución en los comentarios:

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

Edición 2 : Evaluación antes de la sustitución:

Aquí hay ejemplos con evaluación antes de la sustitución:

(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
fuente
3
También tenga en cuenta que format-spec-makees solo una lista:'((?a . "a") (?b . "b"))
Malabarba
1
"no parece estar funcionando para números" - vea emacs.stackexchange.com/questions/7481/…
npostavs
@npostavs: ¡Genial saberlo! Edité la respuesta.
Adobe
14

Biblioteca de manipulación de cadenas de magnar Sveen s.el ofrece una variedad de maneras de hacer esto. Por ejemplo:

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

Tenga en cuenta que s-formatpuede tomar cualquier función sustituto, sino que proporciona un manejo especial para aget, elty gethash. Por lo tanto, podría usar una lista de tokens y hacer referencia a ellos por índice, así:

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

También puede reemplazar el uso de variables dentro del alcance, como este:

(let ((name "test"))
  (s-lex-format "${name} ${name} ${name}"))
;; ==> "test test test"
glucas
fuente
1
Excelente, no sabía sobre esta característica! La mayor parte del tiempo la he usado para echar un vistazo a cómo hacer tareas comunes de manipulación de cadenas en Emacs, pero esto es realmente más que un contenedor de una línea de una función existente.
wasamasa
3

El formato s-lex de s.el es realmente lo que quieres, pero si realmente quieres poner código dentro de los bloques de sustitución y no solo nombres de variables, escribí esto como una prueba de concepto.

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

Incluso puedes insertar una fmtllamada dentro de otra fmtsi estás loco

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

El código simplemente se expande a una formatllamada para que todas las sustituciones se realicen en orden y se evalúen en tiempo de ejecución.

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

Se podrían hacer mejoras con qué tipo de formato se usa en lugar de usar siempre% s, pero eso tendría que hacerse en tiempo de ejecución y agregaría una sobrecarga, pero podría hacerse rodeando todos los argumentos de formato en una llamada de función que formatea bien las cosas muy bien en tipo, pero en realidad el único escenario en el que desearía que probablemente sea flotante e incluso podría hacer un (formato "% f" flotante) en la sustitución es que estaba desesperado.

Si trabajo más en ello, es más probable que actualice esta esencia en lugar de esta respuesta. https://gist.github.com/jordonbiondo/c4e22b4289be130bc59b

Jordon Biondo
fuente
3

No es un propósito general, pero resolverá su caso:

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

Usando el ejemplo proporcionado:

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

da:

" * - :raw-html:`<img width=\"100%\" src=\"http://xxx.xxx/images/languages/staff/some-image.jpg\" alt=\"some-image.jpg\"/>` - .. _some-image:"
wvxvw
fuente
Aquí hay una cadena de muestra con la que estoy tratando: " * - :raw-html:`<img width=\"100%%\" src=\"http://xxx.xxx/images/languages/staff/%s.jpg\" alt=\"%s.jpg\"/>` - .. _%s:"todos %sson iguales.
Adobe
@ Adobe He actualizado la respuesta con tu ejemplo.
wvxvw