¿Cómo calcular automáticamente las líneas de inicio y fin cuando se incluyen archivos fuente en modo org?

10

Tengo lo siguiente en mi documentación:

#+INCLUDE: "code/basic.sv" :src systemverilog :lines "14-117"

Aquí la línea 14 es donde tengo class basic extends ..y la línea 116 es donde tengo endclass.

¿Hay alguna manera de insertar automáticamente los números 14 y 117 (= 116 + 1) para que no tenga que actualizarlos manualmente cada vez que modifique el code/basic.sv?

Kaushal Modi
fuente
¿Entonces siempre quieres que pase de clase a clase final?
Malabarba
1
No. Ese fue un ejemplo. Estoy pensando en una solución donde pueda proporcionar expresiones regulares para las líneas de inicio y fin. Algo evaluaría una funciónorg-include-src(FILE, LANGUAGE, REGEX_BEGIN, REGEX_END)
Kaushal Modi
Una forma es, colocando algún tipo de marcadores únicos (comienzo final) en el archivo incluido y encuéntrelos con una función que se enganche org-export-before-processing-hookal preprocesamiento de los números de línea. Otra forma es simplemente enviar un correo de solicitud de función a la lista de correo org :)
kindahero

Respuestas:

8

Aquí hay otra opción. Este es le permite personalizar las expresiones regulares en función de cada inclusión. Debería encajar mejor con algunos flujos de trabajo, ya que no está limitado a definiciones basadas en extensiones.

Usar

Haga algo como lo siguiente en su archivo org. (La :linespalabra clave es opcional)

#+INCLUDE: "code/my-class.sv" :src systemverilog :range-begin "^class" :range-end "^endclass" :lines "14-80"

La función visitará "my-class.sv" y buscará esas dos expresiones regulares, y luego actualizará la :linespalabra clave de acuerdo con el resultado de la coincidencia.

Si :range-beginfalta, el rango será "-80".
Si :range-endfalta, el rango será "14-".

El código

(add-hook 'before-save-hook #'endless/update-includes)

(defun endless/update-includes (&rest ignore)
  "Update the line numbers of #+INCLUDE:s in current buffer.
Only looks at INCLUDEs that have either :range-begin or :range-end.
This function does nothing if not in org-mode, so you can safely
add it to `before-save-hook'."
  (interactive)
  (when (derived-mode-p 'org-mode)
    (save-excursion
      (goto-char (point-min))
      (while (search-forward-regexp
              "^\\s-*#\\+INCLUDE: *\"\\([^\"]+\\)\".*:range-\\(begin\\|end\\)"
              nil 'noerror)
        (let* ((file (expand-file-name (match-string-no-properties 1)))
               lines begin end)
          (forward-line 0)
          (when (looking-at "^.*:range-begin *\"\\([^\"]+\\)\"")
            (setq begin (match-string-no-properties 1)))
          (when (looking-at "^.*:range-end *\"\\([^\"]+\\)\"")
            (setq end (match-string-no-properties 1)))
          (setq lines (endless/decide-line-range file begin end))
          (when lines
            (if (looking-at ".*:lines *\"\\([-0-9]+\\)\"")
                (replace-match lines :fixedcase :literal nil 1)
              (goto-char (line-end-position))
              (insert " :lines \"" lines "\""))))))))

(defun endless/decide-line-range (file begin end)
  "Visit FILE and decide which lines to include.
BEGIN and END are regexps which define the line range to use."
  (let (l r)
    (save-match-data
      (with-temp-buffer
        (insert-file file)
        (goto-char (point-min))
        (if (null begin)
            (setq l "")
          (search-forward-regexp begin)
          (setq l (line-number-at-pos (match-beginning 0))))
        (if (null end)
            (setq r "")
          (search-forward-regexp end)
          (setq r (1+ (line-number-at-pos (match-end 0)))))
        (format "%s-%s" l r)))))
Malabarba
fuente
2
¡Esto es genial! Ahora puedo usar esto para exportar varios fragmentos del mismo archivo. Fragmento 1: #+INCLUDE: "code/basic.sv" :src systemverilog :range-begin "// Example 1" :range-end "// End of Example 1". Fragmento 2: #+INCLUDE: "code/basic.sv" :src systemverilog :range-begin "// Example 2" :range-end "// End of Example 2". ¡La ejecución es perfecta! ¡Gracias por implementar esto tan rápido!
Kaushal Modi
5

La mejor manera en que puedo pensar es actualizar estos números inmediatamente antes de exportar o antes de evaluar.

El actualizador

Esta es la función que pasa por el búfer. Puede vincularlo a una clave o agregarlo a un gancho. El siguiente código actualiza las líneas cada vez que guarda el archivo , pero si su caso de uso es diferente, ¡descubra qué gancho necesita! (el modo org está lleno de ganchos)

(add-hook 'before-save-hook #'endless/update-includes)

(defun endless/update-includes (&rest ignore)
  "Update the line numbers of all #+INCLUDE:s in current buffer.
Only looks at INCLUDEs that already have a line number listed!
This function does nothing if not in org-mode, so you can safely
add it to `before-save-hook'."
  (interactive)
  (when (derived-mode-p 'org-mode)
    (save-excursion
      (goto-char (point-min))
      (while (search-forward-regexp
              "^\\s-*#\\+INCLUDE: *\"\\([^\"]+\\)\".*:lines *\"\\([-0-9]+\\)\""
              nil 'noerror)
        (let* ((file (expand-file-name (match-string-no-properties 1)))
               (lines (endless/decide-line-range file)))
          (when lines
            (replace-match lines :fixedcase :literal nil 2)))))))

Las expresiones regulares

Aquí es donde define las expresiones regulares que se utilizarán como la primera y la última línea que se incluirán. Puede dar una lista de expresiones regulares para cada extensión de archivo.

(defcustom endless/extension-regexp-map 
  '(("sv" ("^class\\b" . "^endclass\\b") ("^enum\\b" . "^endenum\\b")))
  "Alist of regexps to use for each file extension.
Each item should be
    (EXTENSION (REGEXP-BEGIN . REGEXP-END) (REGEXP-BEGIN . REGEXP-END))
See `endless/decide-line-range' for more information."
  :type '(repeat (cons string (repeat (cons regexp regexp)))))

El trabajador de fondo

Este es el tipo que hace la mayor parte del trabajo.

(defun endless/decide-line-range (file)
  "Visit FILE and decide which lines to include.
The FILE's extension is used to get a list of cons cells from
`endless/extension-regexp-map'. Each cons cell is a pair of
regexps, which determine the beginning and end of region to be
included. The first one which matches is used."
  (let ((regexps (cdr-safe (assoc (file-name-extension file)
                                  endless/extension-regexp-map)))
        it l r)
    (when regexps
      (save-match-data
        (with-temp-buffer
          (insert-file file)
          (while regexps
            (goto-char (point-min))
            (setq it (pop regexps))
            (when (search-forward-regexp (car it) nil 'noerror)
              (setq l (line-number-at-pos (match-beginning 0)))
              (when (search-forward-regexp (cdr it) nil 'noerror)
                (setq regexps nil
                      r (line-number-at-pos (match-end 0))))))
          (when r (format "%s-%s" l (+ r 1))))))))
Malabarba
fuente
1
Si puedo sugerir, edebug las dos funciones y luego invoque la primera con Mx. Eso debería ser muy informativo. :-)
Malabarba
La función en sí misma funciona bien. Pero el gancho necesita pasar un argumento a la función que está llamando. A partir de la documentación para org-export-before-processing-hook, Every function in this hook will be called with one argument: the back-end currently used, as a symbol. Como no estamos pasando ningún argumento, obtenemos el error run-hook-with-args: Wrong number of arguments. Ahora no estoy seguro de qué argumento agregar a endless/update-includes... (&optional dummy)?
Kaushal Modi
@kaushalmodi oops, mi mal. He actualizado la respuesta. Puedes usar lo que escribiste también.
Malabarba
OK .. agregar (&optional dummy)realmente funcionó! Pero un efecto secundario interesante de llamar a la función a través del gancho. Si llamo a la función usando M-x, modifica el .orgarchivo con los números de línea actualizados. Pero si simplemente exporto a html y dejo que el enlace llame a la función, los números de línea actualizados se reflejan solo en el archivo exportado, NO en el .orgarchivo.
Kaushal Modi
@kaushalmodi Sí, así es como funcionan los ganchos de organización. Puede agregarlo a before-save-hook en su lugar.
Malabarba