Obtenga todas las coincidencias regexp en el búfer como una lista

18

En el sitio de Code Golf Stack Exchange hoy, encontré esta respuesta en Clojure a la pregunta "Obtener todos los enlaces en una página web".

(->> (slurp "http://www.stroustrup.com")
     (re-seq #"(?:http://)?www(?:[./#\+-]\w*)+"))

Sin la macro elegante, es solo esto:

(re-seq #"(?:http://)?www(?:[./#\+-]\w*)+" (slurp "http://www.stroustrup.com"))

Esto devuelve la lista:

("http://www.morganstanley.com/" "http://www.cs.columbia.edu/" "http://www.cse.tamu.edu" ...)

¿Puedo hacer algo similar en Emacs Lisp?

Tal vez una función como (re-seq regexp (buffer-string))esa regresa '(firstmatch secondmatch thirdmatch ...)?

niñera
fuente
Esto es lo que M-x occurhace, pero buscaría más funciones de bajo nivel para hacerlo.
wvxvw
@wvxvw Ese es un buen punto, ni siquiera lo pensé occur. Tendré que mirar a través de su fuente.
niñera
Miré dentro, y ¡ay! Ese código hace demasiado y no es fácil reutilizarlo, para nada. Mi próximo candidato sería s.el, pero tal vez hay más por ahí. Aquí: github.com/magnars/s.el#s-match-strings-all-regex-string ¿qué tal esto?
wvxvw

Respuestas:

16

Así es como puede hacerlo en función de las cadenas, según lo solicitado.

(defun re-seq (regexp string)
  "Get a list of all regexp matches in a string"
  (save-match-data
    (let ((pos 0)
          matches)
      (while (string-match regexp string pos)
        (push (match-string 0 string) matches)
        (setq pos (match-end 0)))
      matches)))

; Sample URL
(setq urlreg "\\(?:http://\\)?www\\(?:[./#\+-]\\w*\\)+")
; Sample invocation
(re-seq urlreg (buffer-string))
Alan Shutko
fuente
Eso no parece del todo completo, ¿podría ampliar esto a una respuesta totalmente funcional?
wasamasa
1
El código estaba completo, pero también agregué un ejemplo de uso. ¿Qué más te gustaría ver?
Alan Shutko
1
Esta solución es demasiado simple, desafortunadamente. Tratar (re-seq "^.*$" ""). Regexp válido, cadena válida, pero nunca termina.
Phil Lord
8

Probablemente valga la pena señalar que invocar occurcon el argumento universal hace que llene el *Occur*búfer solo con coincidencias, sin nombres de archivo, números de línea o información de encabezado. Cuando se combina con un grupo de captura, esto permite extraer el patrón que se desee.

Por ejemplo, C-u M-x occurseguido de \"\(.*\)\"preguntará al usuario qué grupo de captura debe recopilar (predeterminado \1) y luego colocará el contenido de cada cadena citada en el *Occur*búfer.

Jack Rusher
fuente
5

Tengo una respuesta de emacs lisp a esa pregunta publicada: /codegolf//a/44319/18848

Usando la misma estructura (while (search) (print)) podría modificarla en una función para insertar coincidencias en un búfer en una lista y devolverla así:

(defun matches-in-buffer (regexp &optional buffer)
  "return a list of matches of REGEXP in BUFFER or the current buffer if not given."
  (let ((matches))
    (save-match-data
      (save-excursion
        (with-current-buffer (or buffer (current-buffer))
          (save-restriction
            (widen)
            (goto-char 1)
            (while (search-forward-regexp regexp nil t 1)
              (push (match-string 0) matches)))))
      matches)))
Jordon Biondo
fuente
Respuesta agradable, nota es posible que desee reemplazar match-stringcon match-string-no-propertieslo que el resaltado de sintaxis no se extrae. Es posible que desee pasar un regexp-group-indexuso para poder elegir qué texto se almacena. Además de invertir el orden de búsqueda (la lista actual es la última a la primera). Vea esta respuesta que incluye una versión modificada emacs.stackexchange.com/a/38752/2418
ideasman42
3

Usar s.elesto hubiera sido más corto, pero, desafortunadamente, da demasiadas coincidencias:

(defun all-urls-in-buffer ()
  (s-match-strings-all
   "\\(?:http://\\)?www\\(?:[./#+-]\\w*\\)+"
   (buffer-string)))

Si esto está bien (la expresión regular para las URL no es perfecta de todos modos), esto podría ser más corto, y si no, entonces no creo que pueda hacerlo más corto que la respuesta de Alan Shutko.

wvxvw
fuente
2

Permítanme mencionar por qué creo que esto no se implementa en el núcleo. Simplemente por razones de eficiencia: no hay necesidad de copiar, crear listas, pasarlas y recolectar basura. En su lugar, almacene la cadena completa como el búfer y opere con límites de coincidencia de enteros. Así es como occurfunciona, por ejemplo: coincide con una cadena a la vez e inserta la coincidencia *occur*. No coincide con todas las cadenas a la vez, hacerlas en la lista, hacer un bucle en la lista para insertar *occur*y recolectar basura de la lista y sus cadenas.

Al igual que no escribirías (do (def x 1) (def x (+ 2 x)))en Clojure, por defecto no deberías intentar que Elisp se comporte como un lenguaje funcional. Me encantaría si lo fuera, pero tenemos que cumplir con lo que tenemos en este momento.

abo-abo
fuente
1

Si se me permite un enchufe, eche un vistazo a mi biblioteca "m-buffer".

(m-buffer-match buffer "foo")

Devuelve una lista de marcadores con los que coincide foo.

Phil Lord
fuente