|
| 1 | +#+TITLE: Custom =org-babel= Extensions |
| 2 | +#+AUTHOR: Brandon T. Willard |
| 3 | +#+DATE: 2018-07-29 |
| 4 | +#+EMAIL: brandonwillard@gmail.com |
| 5 | +#+STARTUP: hideblocks |
| 6 | + |
| 7 | +* Introduction |
| 8 | + |
| 9 | + To get started, add a block like the following to an Org file that |
| 10 | + needs to reference the functions herein: |
| 11 | + #+BEGIN_SRC elisp :eval t :exports none :results none |
| 12 | + (org-babel-lob-ingest "org-babel-extensions.org") |
| 13 | + #+END_SRC |
| 14 | + |
| 15 | + Alternatively, run the same code within Emacs; both should add the named |
| 16 | + blocks below to your LOB (library-of-babel, i.e. =org-babel-library-of-babel=). |
| 17 | + |
| 18 | +* General Babel Functions |
| 19 | + |
| 20 | + #+NAME: babel_helper_functions |
| 21 | + #+BEGIN_SRC elisp :eval t :exports none :results none |
| 22 | + (defun org-babel-get-call-var-value (var-name) |
| 23 | + "Extract the value of a named variable from a CALL statement." |
| 24 | + ;; What about `org-element-context' and `org-babel-parse-header-arguments'? |
| 25 | + (when-let ((el-info (org-babel-lob-get-info))) |
| 26 | + (car-safe |
| 27 | + (seq-filter #'identity |
| 28 | + (map-values-apply |
| 29 | + (lambda (x) (if (string-match (format "^%s=\"\\(.*\\)\"$" var-name) x) |
| 30 | + (match-string 1 x))) |
| 31 | + (seq-filter (lambda (x) (eq (car x) :var)) |
| 32 | + (nth 2 el-info))))))) |
| 33 | + |
| 34 | + (defmacro org-babel-get-caller-var-value (var) |
| 35 | + `(or (org-with-point-at org-babel-current-src-block-location |
| 36 | + (org-babel-get-call-var-value ,(symbol-name var))) |
| 37 | + ,var)) |
| 38 | + #+END_SRC |
| 39 | + |
| 40 | +* Figure Generation |
| 41 | + |
| 42 | + Below, we create a babel function that nicely wraps the output of a filename |
| 43 | + to be displayed as a figure in org-mode and LaTeX. |
| 44 | + |
| 45 | + The code takes extra effort to extract variable information from the calling block. |
| 46 | + This is especially useful when blocks are called indirectly (e.g. from =:post=) and |
| 47 | + variables (i.e. =:var= assignments) for the callee need to be set. |
| 48 | + |
| 49 | + #+NAME: org_fig_wrap |
| 50 | + #+HEADER: :var org_attrs=":width 400" |
| 51 | + #+HEADER: :var latex_attrs=":width 1.0\\textwidth :height 1.0\\textwidth :float t :options [keepaspectratio] :placement [p!]" |
| 52 | + #+HEADER: :var data="" :var label="" :var caption="" :var label_var="" |
| 53 | + #+BEGIN_SRC elisp :exports none :results raw value :noweb yes |
| 54 | + |
| 55 | + <<babel_helper_functions>> |
| 56 | + |
| 57 | + (let* ((label (if (string-blank-p label) |
| 58 | + ;; There's no specified label. |
| 59 | + (org-with-point-at org-babel-current-src-block-location |
| 60 | + (let ((src-block-info (org-babel-get-src-block-info))) |
| 61 | + ;; First, use the calling block's name as the label. |
| 62 | + (if src-block-info |
| 63 | + (nth 4 (org-babel-get-src-block-info)) |
| 64 | + ;; The caller is not a SRC block; let's assume it's a |
| 65 | + ;; CALL. |
| 66 | + (or (org-babel-get-call-var-value "label") |
| 67 | + ;; If the CALL specifies no label value, try the |
| 68 | + ;; value assigned to the variable given by |
| 69 | + ;; label_var. |
| 70 | + (org-babel-get-call-var-value label_var)) |
| 71 | + ;; (error "No figure name!") |
| 72 | + ))) |
| 73 | + label)) |
| 74 | + (latex_attrs (org-babel-get-caller-var-value latex_attrs)) |
| 75 | + (org_attrs (org-babel-get-caller-var-value org_attrs)) |
| 76 | + (caption (org-babel-get-caller-var-value caption)) |
| 77 | + ;; TODO: Further customize filename output? |
| 78 | + ;; (filename (if (org-export-derived-backend-p org-export-current-backend 'latex) |
| 79 | + ;; (let ((pdf-name (concat (file-name-sans-extension data) ".pdf"))) |
| 80 | + ;; (or (and (file-exists-p pdf-name) pdf-name) |
| 81 | + ;; data)) |
| 82 | + ;; data)) |
| 83 | + ;; TODO: Could number figures using `org-export-get-ordinal'. |
| 84 | + ;; See https://github.com/kawabata/ox-pandoc/blob/master/ox-pandoc.el |
| 85 | + ) |
| 86 | + (mapconcat 'identity |
| 87 | + `(,(format "#+ATTR_ORG: %s" org_attrs) |
| 88 | + ,(format "#+ATTR_LATEX: %s" latex_attrs) |
| 89 | + ,(format "#+CAPTION: %s" caption) |
| 90 | + ,(format "#+NAME: fig:%s" label) |
| 91 | + ,(format "[[file:%s]]" data)) |
| 92 | + "\n")) |
| 93 | + #+END_SRC |
| 94 | + |
| 95 | +** Example Usage |
| 96 | + |
| 97 | + In this instance, we call =org_fig_wrap= as a block =:post= processing function. |
| 98 | + This is where the indirect variable gathering functionality is useful, since, |
| 99 | + without it, we would not be able to set =label= or =caption= for |
| 100 | + =org_fig_wrap= in the originating =CALL=. |
| 101 | + |
| 102 | + #+BEGIN_SRC org :eval never :exports code |
| 103 | + ,#+NAME: insert_pydot_figure |
| 104 | + ,#+HEADER: :var graph_obj_name="" |
| 105 | + ,#+HEADER: :post org_fig_wrap(data=*this*, label_var="graph_obj_name") |
| 106 | + ,#+BEGIN_SRC python :results raw value |
| 107 | + ... |
| 108 | + ,#+END_SRC |
| 109 | + #+END_SRC |
| 110 | + |
| 111 | + |
| 112 | + Then emit the results in a =CALL= statement. |
| 113 | + #+BEGIN_SRC org :eval never :exports code |
| 114 | + ,#+CALL: insert_pydot_figure[:results value](graph_obj_name="blah", label="a-label", caption="A caption") |
| 115 | + #+END_SRC |
0 commit comments