Browse Source

org-export: Include title, author, date and email macros

* contrib/lisp/org-export.el (org-export-expand-macro): New function.
(org-export-as): Use new function.
* lisp/org.el (org-macro-expand, org-macro-replace-all): Change
  signature.  The function now accepts an alist of templates so it
  doesn't have to rely only on `org-macro-templates'.
(org-macro-initialize-templates): {{{date}}} is not anymore an alias
for {{{time}}}.  During export, it will provide the value stored in
DATE keyword instead.
* testing/lisp/test-org-export.el: Add tests.
* testing/lisp/test-org.el: Update tests.
Nicolas Goaziou 5 years ago
parent
commit
58b42debb1
4 changed files with 98 additions and 44 deletions
  1. 47 28
      contrib/lisp/org-export.el
  2. 21 12
      lisp/org.el
  3. 26 0
      testing/lisp/test-org-export.el
  4. 4 4
      testing/lisp/test-org.el

+ 47 - 28
contrib/lisp/org-export.el

@@ -2498,6 +2498,8 @@ Return the updated communication channel."
 ;; was within an item, the item should contain the headline.  That's
 ;; why file inclusion should be done before any structure can be
 ;; associated to the file, that is before parsing.
+;;
+;; Macro are expanded with `org-export-expand-macro'.
 
 (defun org-export-as
   (backend &optional subtreep visible-only body-only ext-plist noexpand)
@@ -2542,34 +2544,34 @@ Return code as a string."
 	     (narrow-to-region (point) (point-max))))
       ;; 1. Get export environment from original buffer.  Also install
       ;;    user's and developer's filters.
-      (let ((info (org-export-install-filters
-		   (org-export-get-environment backend subtreep ext-plist)))
-	    ;; 2. Get parse tree.  Buffer isn't parsed directly.
-	    ;;    Instead, a temporary copy is created, where macros
-	    ;;    and include keywords are expanded and code blocks
-	    ;;    are evaluated.
-	    (tree (let ((buf (or (buffer-file-name (buffer-base-buffer))
-				 (current-buffer))))
-		    (org-export-with-current-buffer-copy
-		     (unless noexpand
-		       (org-macro-replace-all)
-		       (org-export-expand-include-keyword)
-		       ;; TODO: Setting `org-current-export-file' is
-		       ;; required by Org Babel to properly resolve
-		       ;; noweb references.  Once "org-exp.el" is
-		       ;; removed, modify
-		       ;; `org-export-blocks-preprocess' so it accepts
-		       ;; the value as an argument instead.
-		       (let ((org-current-export-file buf))
-			 (org-export-blocks-preprocess)))
-		     (goto-char (point-min))
-		     ;; Run hook
-		     ;; `org-export-before-parsing-hook'. with current
-		     ;; back-end as argument.
-		     (run-hook-with-args
-		      'org-export-before-parsing-hook backend)
-		     ;; Eventually parse buffer.
-		     (org-element-parse-buffer nil visible-only)))))
+      (let* ((info (org-export-install-filters
+		    (org-export-get-environment backend subtreep ext-plist)))
+	     ;; 2. Get parse tree.  Buffer isn't parsed directly.
+	     ;;    Instead, a temporary copy is created, where macros
+	     ;;    and include keywords are expanded and code blocks
+	     ;;    are evaluated.
+	     (tree (let ((buf (or (buffer-file-name (buffer-base-buffer))
+				  (current-buffer))))
+		     (org-export-with-current-buffer-copy
+		      (unless noexpand
+			(org-export-expand-macro info)
+			(org-export-expand-include-keyword)
+			;; TODO: Setting `org-current-export-file' is
+			;; required by Org Babel to properly resolve
+			;; noweb references.  Once "org-exp.el" is
+			;; removed, modify
+			;; `org-export-blocks-preprocess' so it
+			;; accepts the value as an argument instead.
+			(let ((org-current-export-file buf))
+			  (org-export-blocks-preprocess)))
+		      (goto-char (point-min))
+		      ;; Run hook
+		      ;; `org-export-before-parsing-hook'. with current
+		      ;; back-end as argument.
+		      (run-hook-with-args
+		       'org-export-before-parsing-hook backend)
+		      ;; Eventually parse buffer.
+		      (org-element-parse-buffer nil visible-only)))))
 	;; 3. Call parse-tree filters to get the final tree.
 	(setq tree
 	      (org-export-filter-apply-functions
@@ -2721,6 +2723,23 @@ Point is at buffer's beginning when BODY is applied."
 	   (progn ,@body))))))
 (def-edebug-spec org-export-with-current-buffer-copy (body))
 
+(defun org-export-expand-macro (info)
+  "Expand every macro in buffer.
+INFO is a plist containing export options and buffer properties."
+  (org-macro-replace-all
+   ;; Before expanding macros, install {{{author}}}, {{{date}}},
+   ;; {{{email}}} and {{{title}}} templates.
+   (nconc
+    (list (cons "author"
+		(org-element-interpret-data (plist-get info :author)))
+	  (cons "date"
+		(org-element-interpret-data (plist-get info :date)))
+	  ;; EMAIL is not a parsed keyword: store it as-is.
+	  (cons "email" (or (plist-get info :email) ""))
+	  (cons "title"
+		(org-element-interpret-data (plist-get info :title))))
+    org-macro-templates)))
+
 (defun org-export-expand-include-keyword (&optional included dir)
   "Expand every include keyword in buffer.
 Optional argument INCLUDED is a list of included file names along

+ 21 - 12
lisp/org.el

@@ -20825,10 +20825,18 @@ hierarchy of headlines by UP levels before marking the subtree."
 ;; Macros are expanded with `org-macro-replace-all', which relies
 ;; internally on `org-macro-expand'.
 
-;; Templates for expansion are stored in the buffer-local variable
-;; `org-macro-templates'.  This variable is updated by
+;; Default templates for expansion are stored in the buffer-local
+;; variable `org-macro-templates'.  This variable is updated by
 ;; `org-macro-initialize-templates'.
 
+;; Along with macros defined through #+MACRO: keyword, default
+;; templates include the following hard-coded macros:
+;; {{{time(format-string)}}}, {{{property(node-property)}}},
+;; {{{input-file}}} and {{{modification-time(format-string)}}}.
+
+;; During export, {{{author}}}, {{{date}}}, {{{email}}} and
+;; {{{title}}} will also be provided.
+
 
 (defvar org-macro-templates nil
   "Alist containing all macro templates in current buffer.
@@ -20840,15 +20848,15 @@ directly, use instead:
   #+MACRO: name template")
 (make-variable-buffer-local 'org-macro-templates)
 
-(defun org-macro-expand (macro)
+(defun org-macro-expand (macro templates)
   "Return expanded MACRO, as a string.
 MACRO is an object, obtained, for example, with
-`org-element-context'.  Return nil if no template was found."
+`org-element-context'.  TEMPLATES is an alist of templates used
+for expansion.  See `org-macro-templates' for a buffer-local
+default value.  Return nil if no template was found."
   (let ((template
-	 (cdr (assoc-string (org-element-property :key macro)
-			    org-macro-templates
-			    ;; Macro names are case-insensitive.
-			    t))))
+	 ;; Macro names are case-insensitive.
+	 (cdr (assoc-string (org-element-property :key macro) templates t))))
     (when template
       (let ((value (replace-regexp-in-string
                     "\\$[0-9]+"
@@ -20865,14 +20873,16 @@ MACRO is an object, obtained, for example, with
         ;; Return string.
         (format "%s" (or value ""))))))
 
-(defun org-macro-replace-all ()
-  "Replace all macros in current buffer by their expansion."
+(defun org-macro-replace-all (templates)
+  "Replace all macros in current buffer by their expansion.
+TEMPLATES is an alist of templates used for expansion.  See
+`org-macro-templates' for a buffer-local default value."
   (save-excursion
     (goto-char (point-min))
     (while (re-search-forward "{{{[-A-Za-z0-9_]" nil t)
       (let ((object (org-element-context)))
         (when (eq (org-element-type object) 'macro)
-          (let ((value (org-macro-expand object)))
+          (let ((value (org-macro-expand object templates)))
             (when value
               (delete-region
                (org-element-property :begin object)
@@ -20916,7 +20926,6 @@ function installs the following ones: \"property\", \"date\",
     (mapc (lambda (cell) (funcall set-template cell))
 	  (list
 	   (cons "property" "(eval (org-entry-get nil \"$1\" 'selective))")
-	   (cons "date" "(eval (format-time-string \"$1\"))")
 	   (cons "time" "(eval (format-time-string \"$1\"))")))
     (let ((visited-file (buffer-file-name (buffer-base-buffer))))
       (when (and visited-file (file-exists-p visited-file))

+ 26 - 0
testing/lisp/test-org-export.el

@@ -429,6 +429,32 @@ body\n")))
     (should (equal (buffer-string)
 		   "#+BEGIN_SRC emacs-lisp\n(+ 2 1)\n#+END_SRC\n"))))
 
+(ert-deftest test-org-export/expand-macro ()
+  "Test macro expansion in an Org buffer."
+  ;; Standard macro expansion.
+  (should
+   (equal "#+MACRO: macro1 value\nvalue"
+	  (org-test-with-temp-text "#+MACRO: macro1 value\n{{{macro1}}}"
+	    (let (info)
+	      (org-macro-initialize-templates)
+	      (org-export-expand-macro info) (buffer-string)))))
+  ;; Export specific macros.
+  (should
+   (equal "me 2012-03-29 me@here Title"
+	  (org-test-with-temp-text
+	      "
+#+TITLE: Title
+#+DATE: 2012-03-29
+#+AUTHOR: me
+#+EMAIL: me@here
+{{{author}}} {{{date}}} {{{email}}} {{{title}}}"
+	    (let ((info (org-export-get-environment)))
+	      (org-macro-initialize-templates)
+	      (org-export-expand-macro info)
+	      (goto-char (point-max))
+	      (buffer-substring (line-beginning-position)
+				(line-end-position)))))))
+
 (ert-deftest test-org-export/user-ignore-list ()
   "Test if `:ignore-list' accepts user input."
   (org-test-with-backend test

+ 4 - 4
testing/lisp/test-org.el

@@ -140,7 +140,7 @@ http://article.gmane.org/gmane.emacs.orgmode/21459/"
     "#+MACRO: A B\n1 B 3"
     (org-test-with-temp-text "#+MACRO: A B\n1 {{{A}}} 3"
       (progn (org-macro-initialize-templates)
-	     (org-macro-replace-all)
+	     (org-macro-replace-all org-macro-templates)
 	     (buffer-string)))))
   ;; Macro with arguments.
   (should
@@ -148,7 +148,7 @@ http://article.gmane.org/gmane.emacs.orgmode/21459/"
     "#+MACRO: macro $1 $2\nsome text"
     (org-test-with-temp-text "#+MACRO: macro $1 $2\n{{{macro(some,text)}}}"
       (progn (org-macro-initialize-templates)
-	     (org-macro-replace-all)
+	     (org-macro-replace-all org-macro-templates)
 	     (buffer-string)))))
   ;; Macro with "eval".
   (should
@@ -156,7 +156,7 @@ http://article.gmane.org/gmane.emacs.orgmode/21459/"
     "#+MACRO: add (eval (+ $1 $2))\n3"
     (org-test-with-temp-text "#+MACRO: add (eval (+ $1 $2))\n{{{add(1,2)}}}"
       (progn (org-macro-initialize-templates)
-	     (org-macro-replace-all)
+	     (org-macro-replace-all org-macro-templates)
 	     (buffer-string)))))
   ;; Nested macros.
   (should
@@ -165,7 +165,7 @@ http://article.gmane.org/gmane.emacs.orgmode/21459/"
     (org-test-with-temp-text
 	"#+MACRO: in inner\n#+MACRO: out {{{in}}} outer\n{{{out}}}"
       (progn (org-macro-initialize-templates)
-	     (org-macro-replace-all)
+	     (org-macro-replace-all org-macro-templates)
 	     (buffer-string))))))