Collect buffer-specific built-in macros in setup files

* lisp/org-macro.el (org-macro--set-template): New function.
(org-macro--collect-macros): Also collect "author", "email", "title"
and "date" macros.
(org-macro-initialize-templates): Do not collect previous macros here,
when it is too late.
(org-macro--find-date): New function.
* testing/lisp/test-org-macro.el (test-org/macro-replace-all): Remove
  test, since we cannot guarantee anymore that user-defined macros can
  take precedence over built-in ones.

Reported-by: emsenn <emsenn@emsenn.net>
<http://lists.gnu.org/r/emacs-orgmode/2019-04/msg00234.html>
This commit is contained in:
Nicolas Goaziou 2019-04-29 20:35:23 +02:00
parent 42abf5c695
commit 14132a356a
2 changed files with 72 additions and 73 deletions

View File

@ -83,51 +83,61 @@ directly, use instead:
;;; Functions
(defun org-macro--collect-macros ()
(defun org-macro--set-template (name value templates)
"Set template for the macro NAME.
VALUE is the template of the macro. The new value override the
previous one, unless VALUE is nil. TEMPLATES is the list of
templates. Return the updated list."
(when value
(let ((old-definition (assoc name templates)))
(if old-definition
(setcdr old-definition value)
(push (cons name value) templates))))
templates)
(defun org-macro--collect-macros (&optional files templates)
"Collect macro definitions in current buffer and setup files.
Return an alist containing all macro templates found."
(letrec ((collect-macros
(lambda (files templates)
;; Return an alist of macro templates. FILES is a list
;; of setup files names read so far, used to avoid
;; circular dependencies. TEMPLATES is the alist
;; collected so far.
(let ((case-fold-search t))
(org-with-wide-buffer
(goto-char (point-min))
(while (re-search-forward
"^[ \t]*#\\+\\(MACRO\\|SETUPFILE\\):" nil t)
(let ((element (org-element-at-point)))
(when (eq (org-element-type element) 'keyword)
(let ((val (org-element-property :value element)))
(if (equal (org-element-property :key element) "MACRO")
;; Install macro in TEMPLATES.
(when (string-match
"^\\(.*?\\)\\(?:\\s-+\\(.*\\)\\)?\\s-*$" val)
(let* ((name (match-string 1 val))
(template (or (match-string 2 val) ""))
(old-cell (assoc name templates)))
(if old-cell (setcdr old-cell template)
(push (cons name template) templates))))
;; Enter setup file.
(let* ((uri (org-strip-quotes (org-trim val)))
(uri-is-url (org-file-url-p uri))
(uri (if uri-is-url
uri
(expand-file-name uri))))
;; Avoid circular dependencies.
(unless (member uri files)
(with-temp-buffer
(unless uri-is-url
(setq default-directory
(file-name-directory uri)))
(org-mode)
(insert (org-file-contents uri 'noerror))
(setq templates
(funcall collect-macros (cons uri files)
templates)))))))))))
templates))))
(funcall collect-macros nil nil)))
Return an alist containing all macro templates found.
FILES is a list of setup files names read so far, used to avoid
circular dependencies. TEMPLATES is the alist collected so far.
The two arguments are used in recursive calls."
(let ((case-fold-search t))
(org-with-point-at 1
(while (re-search-forward "^[ \t]*#\\+\\(MACRO\\|SETUPFILE\\):" nil t)
(let ((element (org-element-at-point)))
(when (eq (org-element-type element) 'keyword)
(let ((val (org-element-property :value element)))
(if (equal "MACRO" (org-element-property :key element))
;; Install macro in TEMPLATES.
(when (string-match "^\\(\\S-+\\)[ \t]*" val)
(let ((name (match-string 1 val))
(value (substring val (match-end 0))))
(setq templates
(org-macro--set-template name value templates))))
;; Enter setup file.
(let* ((uri (org-strip-quotes val))
(uri-is-url (org-file-url-p uri))
(uri (if uri-is-url
uri
(expand-file-name uri))))
;; Avoid circular dependencies.
(unless (member uri files)
(with-temp-buffer
(unless uri-is-url
(setq default-directory (file-name-directory uri)))
(org-mode)
(insert (org-file-contents uri 'noerror))
(setq templates
(org-macro--collect-macros
(cons uri files) templates)))))))))))
(let ((macros `(("author" . ,(org-macro--find-keyword-value "AUTHOR"))
("email" . ,(org-macro--find-keyword-value "EMAIL"))
("title" . ,(org-macro--find-keyword-value "TITLE"))
("date" . ,(org-macro--find-date)))))
(pcase-dolist (`(,name . ,value) macros)
(setq templates (org-macro--set-template name value templates))))
templates))
(defun org-macro-initialize-templates ()
"Collect macro templates defined in current buffer.
@ -160,27 +170,12 @@ a file, \"input-file\" and \"modification-time\"."
(prin1-to-string
(file-attribute-modification-time
(file-attributes visited-file))))))))
;; Install built-in macros.
;; Install generic macros.
(list
'("n" . "(eval (org-macro--counter-increment $1 $2))")
`("author" . ,(org-macro--find-keyword-value "AUTHOR"))
`("email" . ,(org-macro--find-keyword-value "EMAIL"))
'("keyword" . "(eval (org-macro--find-keyword-value $1))")
'("time" . "(eval (format-time-string $1))")
`("title" . ,(org-macro--find-keyword-value "TITLE"))
'("property" . "(eval (org-macro--get-property $1 $2))")
`("date" .
,(let* ((value (org-macro--find-keyword-value "DATE"))
(date (org-element-parse-secondary-string
value (org-element-restriction 'keyword))))
(if (and (consp date)
(not (cdr date))
(eq 'timestamp (org-element-type (car date))))
(format "(eval (if (org-string-nw-p $1) %s %S))"
(format "(org-timestamp-format '%S $1)"
(org-element-copy (car date)))
value)
value)))))))
'("property" . "(eval (org-macro--get-property $1 $2))")))))
(defun org-macro-expand (macro templates)
"Return expanded MACRO, as a string.
@ -347,6 +342,21 @@ named after NAME, as a string, or nil."
(org-element-property :value element))))))
(and result (org-trim result)))))
(defun org-macro--find-date ()
"Find value for DATE in current buffer.
Return value as a string."
(let* ((value (org-macro--find-keyword-value "DATE"))
(date (org-element-parse-secondary-string
value (org-element-restriction 'keyword))))
(if (and (consp date)
(not (cdr date))
(eq 'timestamp (org-element-type (car date))))
(format "(eval (if (org-string-nw-p $1) %s %S))"
(format "(org-timestamp-format '%S $1)"
(org-element-copy (car date)))
value)
value)))
(defun org-macro--vc-modified-time (file)
(save-window-excursion
(when (vc-backend file)

View File

@ -103,18 +103,7 @@
"#+MACRO: macro expansion\n* COMMENT H1\n** H2\n<point>{{{macro}}}"
(org-macro-initialize-templates)
(org-macro-replace-all org-macro-templates)
(org-with-wide-buffer (buffer-string)))))
;; User-defined macros take precedence over built-in macros.
(should
(equal
"foo"
(org-test-with-temp-text
"#+MACRO: title foo\n#+TITLE: bar\n<point>{{{title}}}"
(org-macro-initialize-templates)
(org-macro-replace-all org-macro-templates)
(goto-char (point-max))
(buffer-substring-no-properties (line-beginning-position)
(line-end-position))))))
(org-with-wide-buffer (buffer-string))))))
(ert-deftest test-org-macro/property ()
"Test {{{property}}} macro."