ob-ref: Properly resolve references in ":post" arguments

* lisp/ob-core.el (org-babel-exp-reference-buffer): New variable, as
  a replacement for `org-current-export-file'.
(org-babel-check-confirm-evaluate): Use new variable.
* lisp/ob-exp.el (org-babel-exp-in-export-file): Use new variable.
(org-babel-exp-get-export-buffer): Remove function.
(org-babel-exp-process-buffer): Change signature.
* lisp/ob-ref.el (org-babel-ref-resolve): Use new variable during
  export in order to properly resolve references.
* lisp/ox.el (org-export-execute-babel-code): Use new variable.

* contrib/lisp/org-wikinodes.el (org-wikinodes-process-links-for-export):
  Remove a cond branch as it is always
  false (`org-current-export-file' couldn't be a string).

* testing/lisp/test-ob-lob.el (test-ob-lob/export-lob-lines): Update
  test.
* testing/lisp/test-ob.el (test-ob/eval-header-argument): Update test.
* testing/lisp/test-ob-exp.el (ob-export/reference-in-post-header):
  New test.

During export, Babel executes sequentially all blocks in the buffer
being exported.  This can lead to modifications preventing some
references from being resolved.  As a workaround, Babel stores
a pristine copy of the buffer in a variable so it can always find
needed references.

Before this patch, the variable storing this copy was
`org-current-export-file' and was dynamically bound in "ox.el".  It
was used to resolve noweb references (`org-babel-expand-noweb-references')
but not regular references (`org-babel-ref-resolve').

Now, the variable is `org-babel-exp-reference-buffer' and it is bound
from `org-babel-exp-process-buffer'.  It is used to resolve all
references.  In particular, this allows to use references in :post
header.

Thanks to Jarmo Hurri for reporting it.
This commit is contained in:
Nicolas Goaziou 2014-03-14 15:27:32 +01:00
parent 7527e3a80c
commit df10309489
8 changed files with 134 additions and 129 deletions

View File

@ -268,7 +268,6 @@ If there is no such wiki target, return nil."
(car org-export-target-aliases))))
(push (caar target-alist) (cdr a)))))
(defvar org-current-export-file)
(defun org-wikinodes-process-links-for-export ()
"Process Wiki links in the export preprocess buffer.
@ -294,12 +293,6 @@ with working links."
((eq org-wikinodes-scope 'file)
;; No match in file, and other files are not allowed
(insert (format "%s" link)))
((setq file
(and (org-string-nw-p org-current-export-file)
(org-wikinodes-which-file
link (file-name-directory org-current-export-file))))
;; Match in another file in the current directory
(insert (format "[[file:%s::%s][%s]]" file link link)))
(t ;; No match for this link
(insert (format "%s" link)))))))))

View File

@ -285,7 +285,11 @@ Returns a list
(setf (nth 2 info) (org-babel-process-params (nth 2 info))))
(when info (append info (list name indent head)))))
(defvar org-current-export-file) ; dynamically bound
(defvar org-babel-exp-reference-buffer nil
"Buffer containing original contents of the exported buffer.
This is used by Babel to resolve references in source blocks.
Its value is dynamically bound during export.")
(defmacro org-babel-check-confirm-evaluate (info &rest body)
"Evaluate BODY with special execution confirmation variables set.
@ -305,7 +309,7 @@ name of the code block."
(when (assoc :noeval ,headers) "no")))
(,eval-no (or (equal ,eval "no")
(equal ,eval "never")))
(,export (org-bound-and-true-p org-current-export-file))
(,export org-babel-exp-reference-buffer)
(,eval-no-export (and ,export (or (equal ,eval "no-export")
(equal ,eval "never-export"))))
(noeval (or ,eval-no ,eval-no-export))

View File

@ -27,7 +27,6 @@
(eval-when-compile
(require 'cl))
(defvar org-current-export-file)
(defvar org-babel-lob-one-liner-regexp)
(defvar org-babel-ref-split-regexp)
(defvar org-list-forbidden-blocks)
@ -61,27 +60,18 @@ be executed."
(const :tag "Always" t)))
(put 'org-export-babel-evaluate 'safe-local-variable (lambda (x) (eq x nil)))
(defun org-babel-exp-get-export-buffer ()
"Return the current export buffer if possible."
(cond
((bufferp org-current-export-file) org-current-export-file)
(org-current-export-file (get-file-buffer org-current-export-file))
('otherwise
(error "Requested export buffer when `org-current-export-file' is nil"))))
(defvar org-link-search-inhibit-query)
(defmacro org-babel-exp-in-export-file (lang &rest body)
(declare (indent 1))
`(let* ((lang-headers (intern (concat "org-babel-default-header-args:" ,lang)))
(heading (nth 4 (ignore-errors (org-heading-components))))
(export-buffer (current-buffer))
(original-buffer (org-babel-exp-get-export-buffer)) results)
(when original-buffer
;; resolve parameters in the original file so that
;; headline and file-wide parameters are included, attempt
;; to go to the same heading in the original file
(set-buffer original-buffer)
results)
(when org-babel-exp-reference-buffer
;; Resolve parameters in the original file so that headline and
;; file-wide parameters are included, attempt to go to the same
;; heading in the original file
(set-buffer org-babel-exp-reference-buffer)
(save-restriction
(when heading
(condition-case nil
@ -152,12 +142,17 @@ this template."
:type 'string)
(defvar org-babel-default-lob-header-args)
(defun org-babel-exp-process-buffer ()
"Execute all Babel blocks in current buffer."
(defun org-babel-exp-process-buffer (reference-buffer)
"Execute all Babel blocks in current buffer.
REFERENCE-BUFFER is the buffer containing a pristine copy of the
buffer being processed. It is used to properly resolve
references in source blocks, as modifications in current buffer
may make them unreachable."
(interactive)
(save-window-excursion
(save-excursion
(let ((case-fold-search t)
(org-babel-exp-reference-buffer reference-buffer)
(regexp (concat org-babel-inline-src-block-regexp "\\|"
org-babel-lob-one-liner-regexp "\\|"
"^[ \t]*#\\+BEGIN_SRC")))
@ -186,7 +181,7 @@ this template."
(if (and (cdr (assoc :noweb params))
(string= "yes" (cdr (assoc :noweb params))))
(org-babel-expand-noweb-references
info (org-babel-exp-get-export-buffer))
info org-babel-exp-reference-buffer)
(nth 1 info)))
(goto-char begin)
(let ((replacement (org-babel-exp-do-export info 'inline)))
@ -342,7 +337,7 @@ replaced with its value."
(org-babel-noweb-wrap) "" (nth 1 info))
(if (org-babel-noweb-p (nth 2 info) :export)
(org-babel-expand-noweb-references
info (org-babel-exp-get-export-buffer))
info org-babel-exp-reference-buffer)
(nth 1 info))))
(org-fill-template
org-babel-exp-code-template
@ -371,7 +366,7 @@ inhibit insertion of results into the buffer."
(let ((lang (nth 0 info))
(body (if (org-babel-noweb-p (nth 2 info) :eval)
(org-babel-expand-noweb-references
info (org-babel-exp-get-export-buffer))
info org-babel-exp-reference-buffer)
(nth 1 info)))
(info (copy-sequence info))
(org-babel-current-src-block-location (point-marker)))

View File

@ -129,98 +129,99 @@ the variable."
(defun org-babel-ref-resolve (ref)
"Resolve the reference REF and return its value."
(save-window-excursion
(save-excursion
(let ((case-fold-search t)
type args new-refere new-header-args new-referent result
lob-info split-file split-ref index index-row index-col id)
;; if ref is indexed grab the indices -- beware nested indices
(when (and (string-match "\\[\\([^\\[]+\\)\\]$" ref)
(let ((str (substring ref 0 (match-beginning 0))))
(= (org-count ?( str) (org-count ?) str))))
(setq index (match-string 1 ref))
(setq ref (substring ref 0 (match-beginning 0))))
;; assign any arguments to pass to source block
(when (string-match
"^\\(.+?\\)\\(\\[\\(.*\\)\\]\\|\\(\\)\\)\(\\(.*\\)\)$" ref)
(setq new-refere (match-string 1 ref))
(setq new-header-args (match-string 3 ref))
(setq new-referent (match-string 5 ref))
(when (> (length new-refere) 0)
(when (> (length new-referent) 0)
(setq args (mapcar (lambda (ref) (cons :var ref))
(org-babel-ref-split-args new-referent))))
(when (> (length new-header-args) 0)
(setq args (append (org-babel-parse-header-arguments
new-header-args) args)))
(setq ref new-refere)))
(when (string-match "^\\(.+\\):\\(.+\\)$" ref)
(setq split-file (match-string 1 ref))
(setq split-ref (match-string 2 ref))
(find-file split-file) (setq ref split-ref))
(save-restriction
(widen)
(goto-char (point-min))
(if (let ((src-rx (org-babel-named-src-block-regexp-for-name ref))
(res-rx (org-babel-named-data-regexp-for-name ref)))
;; goto ref in the current buffer
(or
;; check for code blocks
(re-search-forward src-rx nil t)
;; check for named data
(re-search-forward res-rx nil t)
;; check for local or global headlines by id
(setq id (org-babel-ref-goto-headline-id ref))
;; check the Library of Babel
(setq lob-info (cdr (assoc (intern ref)
org-babel-library-of-babel)))))
(unless (or lob-info id) (goto-char (match-beginning 0)))
;; ;; TODO: allow searching for names in other buffers
;; (setq id-loc (org-id-find ref 'marker)
;; buffer (marker-buffer id-loc)
;; loc (marker-position id-loc))
;; (move-marker id-loc nil)
(error "Reference '%s' not found in this buffer" ref))
(cond
(lob-info (setq type 'lob))
(id (setq type 'id))
((and (looking-at org-babel-src-name-regexp)
(save-excursion
(forward-line 1)
(or (looking-at org-babel-src-block-regexp)
(looking-at org-babel-multi-line-header-regexp))))
(setq type 'source-block))
((and (looking-at org-babel-src-name-regexp)
(save-excursion
(forward-line 1)
(looking-at org-babel-lob-one-liner-regexp)))
(setq type 'call-line))
(t (while (not (setq type (org-babel-ref-at-ref-p)))
(forward-line 1)
(beginning-of-line)
(if (or (= (point) (point-min)) (= (point) (point-max)))
(error "Reference not found")))))
(let ((params (append args '((:results . "silent")))))
(setq result
(case type
(results-line (org-babel-read-result))
(table (org-babel-read-table))
(list (org-babel-read-list))
(file (org-babel-read-link))
(source-block (org-babel-execute-src-block
nil nil (if org-babel-update-intermediate
nil params)))
(call-line (save-excursion
(forward-line 1)
(org-babel-lob-execute
(org-babel-lob-get-info))))
(lob (org-babel-execute-src-block
nil lob-info params))
(id (org-babel-ref-headline-body)))))
(if (symbolp result)
(format "%S" result)
(if (and index (listp result))
(org-babel-ref-index-list index result)
result)))))))
(with-current-buffer (or org-babel-exp-reference-buffer (current-buffer))
(save-excursion
(let ((case-fold-search t)
type args new-refere new-header-args new-referent result
lob-info split-file split-ref index index-row index-col id)
;; if ref is indexed grab the indices -- beware nested indices
(when (and (string-match "\\[\\([^\\[]+\\)\\]$" ref)
(let ((str (substring ref 0 (match-beginning 0))))
(= (org-count ?( str) (org-count ?) str))))
(setq index (match-string 1 ref))
(setq ref (substring ref 0 (match-beginning 0))))
;; assign any arguments to pass to source block
(when (string-match
"^\\(.+?\\)\\(\\[\\(.*\\)\\]\\|\\(\\)\\)\(\\(.*\\)\)$" ref)
(setq new-refere (match-string 1 ref))
(setq new-header-args (match-string 3 ref))
(setq new-referent (match-string 5 ref))
(when (> (length new-refere) 0)
(when (> (length new-referent) 0)
(setq args (mapcar (lambda (ref) (cons :var ref))
(org-babel-ref-split-args new-referent))))
(when (> (length new-header-args) 0)
(setq args (append (org-babel-parse-header-arguments
new-header-args) args)))
(setq ref new-refere)))
(when (string-match "^\\(.+\\):\\(.+\\)$" ref)
(setq split-file (match-string 1 ref))
(setq split-ref (match-string 2 ref))
(find-file split-file) (setq ref split-ref))
(save-restriction
(widen)
(goto-char (point-min))
(if (let ((src-rx (org-babel-named-src-block-regexp-for-name ref))
(res-rx (org-babel-named-data-regexp-for-name ref)))
;; goto ref in the current buffer
(or
;; check for code blocks
(re-search-forward src-rx nil t)
;; check for named data
(re-search-forward res-rx nil t)
;; check for local or global headlines by id
(setq id (org-babel-ref-goto-headline-id ref))
;; check the Library of Babel
(setq lob-info (cdr (assoc (intern ref)
org-babel-library-of-babel)))))
(unless (or lob-info id) (goto-char (match-beginning 0)))
;; ;; TODO: allow searching for names in other buffers
;; (setq id-loc (org-id-find ref 'marker)
;; buffer (marker-buffer id-loc)
;; loc (marker-position id-loc))
;; (move-marker id-loc nil)
(error "Reference '%s' not found in this buffer" ref))
(cond
(lob-info (setq type 'lob))
(id (setq type 'id))
((and (looking-at org-babel-src-name-regexp)
(save-excursion
(forward-line 1)
(or (looking-at org-babel-src-block-regexp)
(looking-at org-babel-multi-line-header-regexp))))
(setq type 'source-block))
((and (looking-at org-babel-src-name-regexp)
(save-excursion
(forward-line 1)
(looking-at org-babel-lob-one-liner-regexp)))
(setq type 'call-line))
(t (while (not (setq type (org-babel-ref-at-ref-p)))
(forward-line 1)
(beginning-of-line)
(if (or (= (point) (point-min)) (= (point) (point-max)))
(error "Reference not found")))))
(let ((params (append args '((:results . "silent")))))
(setq result
(case type
(results-line (org-babel-read-result))
(table (org-babel-read-table))
(list (org-babel-read-list))
(file (org-babel-read-link))
(source-block (org-babel-execute-src-block
nil nil (if org-babel-update-intermediate
nil params)))
(call-line (save-excursion
(forward-line 1)
(org-babel-lob-execute
(org-babel-lob-get-info))))
(lob (org-babel-execute-src-block
nil lob-info params))
(id (org-babel-ref-headline-body)))))
(if (symbolp result)
(format "%S" result)
(if (and index (listp result))
(org-babel-ref-index-list index result)
result))))))))
(defun org-babel-ref-index-list (index lis)
"Return the subset of LIS indexed by INDEX.

View File

@ -318,6 +318,7 @@ there is no export process in progress.
It can be used to teach Babel blocks how to act differently
according to the back-end used.")
;;; User-configurable Variables
;;
@ -3434,8 +3435,7 @@ file should have."
;; Get a pristine copy of current buffer so Babel references can be
;; properly resolved.
(let ((reference (org-export-copy-buffer)))
(unwind-protect (let ((org-current-export-file reference))
(org-babel-exp-process-buffer))
(unwind-protect (org-babel-exp-process-buffer reference)
(kill-buffer reference))))
(defun org-export--copy-to-kill-ring-p ()

View File

@ -32,8 +32,7 @@ Current buffer is a copy of the original buffer."
(with-temp-buffer
(org-mode)
(insert string)
(let ((org-current-export-file buf))
(org-babel-exp-process-buffer))
(org-babel-exp-process-buffer buf)
(goto-char (point-min))
(progn ,@body))))
@ -405,6 +404,20 @@ src_emacs-lisp{(+ 1 1)}"
(org-export-execute-babel-code)
(buffer-string)))))
(ert-deftest ob-export/reference-in-post-header ()
"Test references in :post header during export."
(should
(org-test-with-temp-text "
#+NAME: foo
#+BEGIN_SRC emacs-lisp :exports none :var bar=\"baz\"
(concat \"bar\" bar)
#+END_SRC
#+NAME: nofun
#+BEGIN_SRC emacs-lisp :exports results :post foo(\"nofun\")
#+END_SRC"
(org-export-execute-babel-code) t)))
(provide 'test-ob-exp)

View File

@ -83,8 +83,7 @@
(with-temp-buffer
(org-mode)
(insert string)
(let ((org-current-export-file buf))
(org-babel-exp-process-buffer))
(org-babel-exp-process-buffer buf)
(message (buffer-string))
(goto-char (point-min))
(should (re-search-forward "^: 0" nil t))

View File

@ -645,7 +645,7 @@ on two lines
(check-eval "no" nil)
(check-eval "never-export" t)
(check-eval "no-export" t)
(let ((org-current-export-file "something"))
(let ((org-babel-exp-reference-buffer (current-buffer)))
(check-eval "never" nil)
(check-eval "no" nil)
(check-eval "never-export" nil)