2

Following this suggestion, I have a function to automatically generate figure names:

#+begin_src emacs-lisp :exports none
(set (make-local-variable 'i) 0)
(fset (make-local-variable 'fig-file)
      #'(lambda () (concat "f" (number-to-string (incf i)) ".pdf")))
#+end_src

I apply it to R code as shown below.

#+begin_src R :file (fig-file) :results output graphics :exports results
(plot (rnorm(10))
#+end_src

This works well, except when I try org-babel-tangle on the org file, I get this error: eval: Symbol’s function definition is void: fig-file.

How can I get org-babel-tangle to work with functions in the header arguments?

2 Answers 2

3
+50

You need to execute at least the Elisp source block defining the function fig-file once before tangling the file. Otherwise that function is undefined as you see in the error message.

You can do so by placing point on the Elisp source block and by pressing C-c C-c.

Remark 1: Used Orgmode test file

Since I am not familiar with R i tested with a source block for gnuplot instead of the source block for R.

#+PROPERTY: header-args :tangle yes

#+begin_src emacs-lisp :exports none
(set (make-local-variable 'i) 0)
(fset (make-local-variable 'fig-file)
      #'(lambda () (concat "f" (number-to-string (cl-incf i)) ".pdf")))
#+end_src

#+begin_src gnuplot :file (fig-file) :results output graphics :exports results
plot sin(x)
#+end_src

#+RESULTS:
[[file:f6.pdf]]

The change from the R source block to the gnuplot source block does not matter much since the :file argument is resolved by org-babel-parse-header-arguments which is part of org-babel-get-src-block-info. And org-babel-get-src-block-info is called independently of the source block type very early in org-babel-tangle-single-block.

Remark 2: Orgmode property line for configuring tangling

I had to add at least the following line to the org test file before the first header line to enable tangling.

#+PROPERTY: header-args :tangle yes

Remark 3: make-local-variable and fset

It does not make sense to use make-local-variable in (fset (make-local-variable 'fig-file) ...). make-local-variable is only significant for the value cell of the symbol and not for its function cell.
Better use:

(defun fig-file () (format "f%d.pdf" (cl-incf i)))

Remark 4: Avoid source block evaluation

You can avoid the huddle with the source block evaluation if you define fig-file in your init file. You should use a buffer-local counter variable with an appropriate default value (e.g., -1 or 0) and an appropriate name prefix in that case.

Example (to be copied and pasted to your init file):

(defvar-local fig-file-i 0
  "Counter for `fig-file`")

(defun fig-file () (format "f%d.pdf" (cl-incf fig-file-i)))
2

The source block most certainly doesn't get executed when using org-babel-tangle. Consequently i and fig-file are void.

To get around this you can (starting with the cleanest solution):

  1. use the result of a named block
  2. execute the source block when opening the file
  3. use file local variables

Note: Newer emacs versions use cl-incf instead of incf.


1. Use the result of a named block

Change your first block into this to. Notice that I added #+name: fig-file to it.

#+name: fig-file
#+begin_src emacs-lisp :exports none
  (defvar-local i 0)
  (concat "f" (number-to-string (incf i)) ".pdf")
#+end_src

Use the result of that block as your file name by using org-sbe:

#+begin_src R :file (org-sbe "fig-file") :results output graphics :exports results
(plot (rnorm(10))
#+end_src

2. Execute the source block when opening the file

Add this to the top of the org file:

# -*- eval: (org-sbe "fig-file") -*-

Add #+name: fig-file to your first source block:

#+name: fig-file
#+begin_src emacs-lisp :exports none
(set (make-local-variable 'i) 0)
(fset (make-local-variable 'fig-file)
      #'(lambda () (concat "f" (number-to-string (cl-incf i)) ".pdf")))
#+end_src

This executes the source block when opening the file and then fig-file is defined.

As file local variables can be a security risk, you will be prompted when you open the file if the values should be applied.


3. Use file local variables

Add those lines at the top of your file to make file local variables:

# -*- i: 0; -*-
# -*- fig-file: nil; -*-
# -*- eval: (fset fig-file (lambda () (concat "f" (number-to-string (incf i)) ".pdf"))); -*-

or this at the end:

* COMMENT Footer 
# Local Variables:
# i: 0
# fig-file:
# eval: (fset fig-file (lambda () (concat "f" (number-to-string (incf i)) ".pdf")))
# End:

Your first source block is not needed anymore with this solution.

The security risk applies again.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.