Org Mode: Including portions of files between two regular expressions
| org, emacs
2023-09-10: Use consult-line
instead of consult--line
.
I'd like to refer to snippets of code, but lines are too fragile to
use as references for code and posts that I want to easily update. I'd
like to specify a from-regexp
and a to-regexp
instead in order to
collect the lines between those regexps (including the ones with the
regexps themselves). org-export-expand-include-keyword
looked a bit
hairy to extend since it uses regular expressions to match parameter
values. For this quick experiment, I decided to make a custom link
type instead. This allows me to refer to parts of code with a link like this:
[[my-include:~/proj/static-blog/assets/css/style.css::from-regexp=Start of copy code&to-regexp=End of copy code&wrap=src js]]
which will turn into this snippet from my stylesheet:
/* Start of copy code */ pre.src { margin: 0 } .org-src-container { position: relative; margin: 0 0; padding: 1.75rem 0 1.75rem 1rem; } div > details { padding: 20px; border: 1px solid lightgray } .org-src-container > details { padding: 0; border: none } summary { position: relative; } summary .org-src-container { padding: 0 } summary .org-src-container pre.src { margin: 0 } .org-src-container button.copy-code, summary button.copy-code { position: absolute; top: 0px; right: 0px; } /* End of copy code */
Here's the Emacs Lisp code to do that. my-include-complete
function
reuses my-include-open
to narrow to the file, and
my-include-complete
uses consult-line
so that we can specify the
prompt.
(org-link-set-parameters "my-include" :follow #'my-include-open :export #'my-include-export :complete #'my-include-complete) (defun my-include-open (path &optional _) "Narrow to the region specified in PATH." (let (params start end) (if (string-match "^\\(.*+?\\)::\\(.*+\\)" path) (setq params (save-match-data (org-protocol-convert-query-to-plist (match-string 2 path))) path (match-string 1 path))) (find-file path) (setq start (or (and (plist-get params :from-regexp) (progn (goto-char (point-min)) (when (re-search-forward (url-unhex-string (plist-get params :from-regexp))) (line-beginning-position)))) (progn (goto-char (point-min)) (point)))) (setq end (or (and (plist-get params :to-regexp) (progn (when (re-search-forward (url-unhex-string (plist-get params :to-regexp))) (line-end-position)))) (progn (goto-char (point-max)) (point)))) (when (or (not (= start (point-min))) (not (= end (point-max)))) (narrow-to-region start end)))) (defun my-include-export (path _ format _) "Export PATH to FORMAT using the specified wrap parameter." (let (params body start end) (when (string-match "^\\(.*+?\\)::\\(.*+\\)" path) (setq params (save-match-data (org-protocol-convert-query-to-plist (match-string 2 path))))) (save-window-excursion (my-include-open path) (setq body (buffer-substring (point-min) (point-max)))) (with-temp-buffer (when (plist-get params :wrap) (let* ((wrap (plist-get params :wrap)) block args) (when (string-match "\\<\\(\\S-+\\)\\( +.*\\)?" wrap) (setq block (match-string 1 wrap)) (setq args (match-string 2 wrap)) (setq body (format "#+BEGIN_%s%s\n%s\n#+END_%s\n" block (or args "") body block))))) (insert body) (org-export-as format nil nil t)))) (defun my-include-complete () "Include a section of a file from one line to another, specified with regexps." (interactive) (require 'consult) (let ((file (read-file-name "File: "))) (save-window-excursion (find-file file) (concat "my-include:" file "::from-regexp=" (let ((curr-line (line-number-at-pos (point) consult-line-numbers-widen)) (prompt "From line: ")) (goto-char (point-min)) (consult-line) (url-hexify-string (regexp-quote (buffer-substring (line-beginning-position) (line-end-position))))) "&to-regexp=" (let ((curr-line (line-number-at-pos (point) consult-line-numbers-widen)) (prompt "To line: ")) (goto-char (point-min)) (consult-line nil (point)) (url-hexify-string (regexp-quote (buffer-substring (line-beginning-position) (line-end-position))))) "&wrap=src " (replace-regexp-in-string "-mode$" "" (symbol-name major-mode))))))