ob-ref.el (9637B)
1 ;;; ob-ref.el --- Babel Functions for Referencing External Data -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2009-2024 Free Software Foundation, Inc. 4 5 ;; Authors: Eric Schulte 6 ;; Dan Davison 7 ;; Keywords: literate programming, reproducible research 8 ;; URL: https://orgmode.org 9 10 ;; This file is part of GNU Emacs. 11 12 ;; GNU Emacs is free software: you can redistribute it and/or modify 13 ;; it under the terms of the GNU General Public License as published by 14 ;; the Free Software Foundation, either version 3 of the License, or 15 ;; (at your option) any later version. 16 17 ;; GNU Emacs is distributed in the hope that it will be useful, 18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 ;; GNU General Public License for more details. 21 22 ;; You should have received a copy of the GNU General Public License 23 ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. 24 25 ;;; Commentary: 26 27 ;; Functions for referencing data from the header arguments of a 28 ;; org-babel block. The syntax of such a reference should be 29 30 ;; #+VAR: variable-name=file:resource-id 31 32 ;; - variable-name :: the name of the variable to which the value 33 ;; will be assigned 34 35 ;; - file :: path to the file containing the resource, or omitted if 36 ;; resource is in the current file 37 38 ;; - resource-id :: the id or name of the resource 39 40 ;; So an example of a simple source block referencing table data in 41 ;; the same file would be 42 43 ;; #+NAME: sandbox 44 ;; | 1 | 2 | 3 | 45 ;; | 4 | org-babel | 6 | 46 ;; 47 ;; #+begin_src emacs-lisp :var table=sandbox 48 ;; (message table) 49 ;; #+end_src 50 51 ;;; Code: 52 53 (require 'org-macs) 54 (org-assert-version) 55 56 (require 'ob-core) 57 (require 'org-macs) 58 (require 'cl-lib) 59 60 (declare-function org-babel-lob-get-info "ob-lob" (&optional datum no-eval)) 61 (declare-function org-element-at-point "org-element" (&optional pom cached-only)) 62 (declare-function org-element-property "org-element-ast" (property node)) 63 (declare-function org-element-post-affiliated "org-element" (node)) 64 (declare-function org-element-type "org-element-ast" (node &optional anonymous)) 65 (declare-function org-end-of-meta-data "org" (&optional full)) 66 (declare-function org-find-property "org" (property &optional value)) 67 (declare-function org-id-find-id-file "org-id" (id)) 68 (declare-function org-id-find-id-in-file "org-id" (id file &optional markerp)) 69 (declare-function org-in-commented-heading-p "org" (&optional no-inheritance)) 70 (declare-function org-narrow-to-subtree "org" (&optional element)) 71 (declare-function org-fold-show-context "org-fold" (&optional key)) 72 73 (defvar org-babel-update-intermediate nil 74 "Update the in-buffer results of code blocks executed to resolve references.") 75 76 (defun org-babel-ref-parse (assignment) 77 "Parse a variable ASSIGNMENT in a header argument. 78 79 If the right hand side of the assignment has a literal value 80 return that value, otherwise interpret it as a reference to an 81 external resource and find its value using `org-babel-ref-resolve'. 82 83 Return a list with two elements: the name of the variable, and an 84 Emacs Lisp representation of the value of the variable." 85 (when (string-match "\\(.+?\\)=" assignment) 86 (let ((var (org-trim (match-string 1 assignment))) 87 (ref (org-trim (substring assignment (match-end 0))))) 88 (cons (intern var) 89 (let ((out (save-excursion 90 (when org-babel-current-src-block-location 91 (goto-char (if (markerp org-babel-current-src-block-location) 92 (marker-position org-babel-current-src-block-location) 93 org-babel-current-src-block-location))) 94 (org-babel-read ref)))) 95 (if (equal out ref) 96 (if (and (string-prefix-p "\"" ref) 97 (string-suffix-p "\"" ref)) 98 (read ref) 99 (org-babel-ref-resolve ref)) 100 out)))))) 101 102 (defun org-babel-ref-goto-headline-id (id) 103 (or (let ((h (org-find-property "CUSTOM_ID" id))) 104 (when h (goto-char h))) 105 (let* ((file (org-id-find-id-file id)) 106 (m (when file (org-id-find-id-in-file id file 'marker)))) 107 (when (and file m) 108 (message "file:%S" file) 109 (pop-to-buffer-same-window (marker-buffer m)) 110 (goto-char m) 111 (move-marker m nil) 112 (org-fold-show-context) 113 t)))) 114 115 (defun org-babel-ref-headline-body () 116 (save-restriction 117 (org-narrow-to-subtree) 118 (buffer-substring 119 (save-excursion (goto-char (point-min)) 120 (org-end-of-meta-data) 121 (point)) 122 (point-max)))) 123 124 (defvar org-babel-library-of-babel) 125 (defun org-babel-ref-resolve (ref) 126 "Resolve the reference REF and return its value." 127 (save-window-excursion 128 (with-current-buffer (or org-babel-exp-reference-buffer (current-buffer)) 129 (save-excursion 130 (let ((case-fold-search t) 131 args new-refere new-header-args new-referent split-file split-ref 132 index contents) 133 ;; if ref is indexed grab the indices -- beware nested indices 134 (when (and (string-match "\\[\\([^\\[]*\\)\\]$" ref) 135 (let ((str (substring ref 0 (match-beginning 0)))) 136 (= (cl-count ?\( str) (cl-count ?\) str)))) 137 (if (> (length (match-string 1 ref)) 0) 138 (setq index (match-string 1 ref)) 139 (setq contents t)) 140 (setq ref (substring ref 0 (match-beginning 0)))) 141 ;; assign any arguments to pass to source block 142 (when (string-match 143 "^\\(.+?\\)\\(\\[\\(.*\\)\\]\\|\\(\\)\\)(\\(.*\\))$" ref) 144 (setq new-refere (match-string 1 ref)) 145 (setq new-header-args (match-string 3 ref)) 146 (setq new-referent (match-string 5 ref)) 147 (when (> (length new-refere) 0) 148 (when (> (length new-referent) 0) 149 (setq args (mapcar (lambda (ref) (cons :var ref)) 150 (org-babel-ref-split-args new-referent)))) 151 (when (> (length new-header-args) 0) 152 (setq args (append (org-babel-parse-header-arguments 153 new-header-args) 154 args))) 155 (setq ref new-refere))) 156 (when (string-match "^\\(.+\\):\\(.+\\)$" ref) 157 (setq split-file (match-string 1 ref)) 158 (setq split-ref (match-string 2 ref)) 159 (when (file-exists-p split-file) 160 (find-file split-file) 161 (setq ref split-ref))) 162 (org-with-wide-buffer 163 (goto-char (point-min)) 164 (let* ((params (append args '((:results . "none")))) 165 (regexp (org-babel-named-data-regexp-for-name ref)) 166 (result 167 (catch :found 168 ;; Check for code blocks or named data. 169 (while (re-search-forward regexp nil t) 170 ;; Ignore COMMENTed headings and orphaned 171 ;; affiliated keywords. 172 (unless (org-in-commented-heading-p) 173 (let ((e (org-element-at-point))) 174 (when (equal (org-element-property :name e) ref) 175 (goto-char 176 (org-element-post-affiliated e)) 177 (pcase (org-element-type e) 178 (`babel-call 179 (throw :found 180 (org-babel-execute-src-block 181 nil (org-babel-lob-get-info e) params))) 182 ((and `src-block (guard (not contents))) 183 (throw :found 184 (org-babel-execute-src-block 185 nil nil 186 (and 187 (not org-babel-update-intermediate) 188 params)))) 189 ((and (let v (org-babel-read-element e)) 190 (guard v)) 191 (throw :found v)) 192 (_ (error "Reference not found"))))))) 193 ;; Check for local or global headlines by ID. 194 (when (org-babel-ref-goto-headline-id ref) 195 (throw :found (org-babel-ref-headline-body))) 196 ;; Check the Library of Babel. 197 (let ((info (cdr (assq (intern ref) 198 org-babel-library-of-babel)))) 199 (when info 200 (throw :found 201 (org-babel-execute-src-block nil info params)))) 202 (error "Reference `%s' not found in this buffer" ref)))) 203 (cond 204 ((and result (symbolp result)) (format "%S" result)) 205 ((and index (listp result)) 206 (org-babel-ref-index-list index result)) 207 (t result))))))))) 208 209 (defun org-babel-ref-index-list (index lis) 210 "Return the subset of LIS indexed by INDEX. 211 212 Indices are 0 based and negative indices count from the end of 213 LIS, so 0 references the first element of LIS and -1 references 214 the last. If INDEX is separated by \",\"s then each \"portion\" 215 is assumed to index into the next deepest nesting or dimension. 216 217 A valid \"portion\" can consist of either an integer index, two 218 integers separated by a \":\" in which case the entire range is 219 returned, or an empty string or \"*\" both of which are 220 interpreted to mean the entire range and as such are equivalent 221 to \"0:-1\"." 222 (if (and (> (length index) 0) (string-match "^\\([^,]*\\),?" index)) 223 (let* ((ind-re "\\(\\([-[:digit:]]+\\):\\([-[:digit:]]+\\)\\|\\*\\)") 224 (lgth (length lis)) 225 (portion (match-string 1 index)) 226 (remainder (substring index (match-end 0))) 227 (wrap (lambda (num) (if (< num 0) (+ lgth num) num))) 228 (open (lambda (ls) (if (and (listp ls) (= (length ls) 1)) (car ls) ls)))) 229 (funcall 230 open 231 (mapcar 232 (lambda (sub-lis) 233 (if (listp sub-lis) 234 (org-babel-ref-index-list remainder sub-lis) 235 sub-lis)) 236 (if (or (= 0 (length portion)) (string-match ind-re portion)) 237 (mapcar 238 (lambda (n) (nth n lis)) 239 (apply 'org-number-sequence 240 (if (and (> (length portion) 0) (match-string 2 portion)) 241 (list 242 (funcall wrap (string-to-number (match-string 2 portion))) 243 (funcall wrap (string-to-number (match-string 3 portion)))) 244 (list (funcall wrap 0) (funcall wrap -1))))) 245 (list (nth (funcall wrap (string-to-number portion)) lis)))))) 246 lis)) 247 248 (defun org-babel-ref-split-args (arg-string) 249 "Split ARG-STRING into top-level arguments of balanced parenthesis." 250 (mapcar #'org-trim (org-babel-balanced-split arg-string 44))) 251 252 (provide 'ob-ref) 253 254 ;;; ob-ref.el ends here