Hello Org-Mode team, find attached the patch implementing org-babel-call. It contains the actual implementation within ob-core.el. Compared to the demo I sent on the RFC thread earlier this week, it now includes a docstring and a little fix/improvement regarding variable names as strings vs. symbols. It also contains documentation within the manual and a single test-case covering most of the expectations of the interface.
Kind regards, Daniel
From f1fe76c84fc681f80ec0d9b465173d7bb43088d0 Mon Sep 17 00:00:00 2001 From: Daniel Bausch <[email protected]> Date: Thu, 15 Oct 2026 12:52:58 +0200 Subject: [PATCH] Provide org-babel-call This new function lets users easily call other code blocks from within Emacs Lisp code blocks. It facilitates recursion, orchestration, and argument forwarding. This commit includes the implementation, including a docstring, documentation within the manual and a single test case that captures most expectations of the interface. --- doc/org-manual.org | 158 ++++++++++++++++++++++++++++++++++++++++ lisp/ob-core.el | 62 ++++++++++++++++ testing/lisp/test-ob.el | 14 ++++ 3 files changed, 234 insertions(+) diff --git a/doc/org-manual.org b/doc/org-manual.org index b308be24a..3109afae8 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -19729,6 +19729,164 @@ variable ~org-babel-inline-result-wrap~, which by default is set to For example, =:results html= wraps the results in a =#+BEGIN_EXPORT html= block before inserting the results in the Org buffer. +#+findex: org-babel-call +Similar to calling a code block using the =CALL= keyword or an inline +call, code blocks of any language can also be called from within Emacs +Lisp code blocks using ~org-babel-call~. This facilitates recursion, +orchestration, and argument forwarding. + +~org-babel-call~ takes the name of the code block to be called as its +first argument, followed by a mixed list of positional arguments, +named variable assignments, and header arguments. Positional +arguments are assigned to the declared variables of the called block +in the original order. Named variable assigments must be preceeded by +the keyword =:var= and must be given as a pair like ~("name" . value)~ +or ~('name . value)~. They can be also specified as a string like +~"name=value"~ in which case the value is resolved internally using +~org-babel-ref-resolve~. This is especially useful when refererring +to tables. Every other keyword plus its following argument is treated +as a header argument and is forwarded to the block as given. All +variable values are forwarded to the called block as native values, +i.e. numbers and lists are not coerced to strings. + +The following example demonstrates argument forwarding. The variable +~user~ of block ~full-name~ is forwarded to the block ~pydemo~. The +example also shows simple cases of orchestration by calling another +block twice in ~full-name~ and calling a block with the result of +another block in ~greet-user~. + +#+begin_example +,#+name: data +,#+begin_example +john: + first-name: John + last-name: Doe +jane: + first-name: Jane + last-name: Doe +,#+end_example + +,#+name: pydemo +,#+begin_src python :var user="john" :var field="first-name" :var data=data +import yaml +d=yaml.safe_load(data) +return d[user][field] +,#+end_src + +,#+results: pydemo +: John + +,#+name: full-name +,#+begin_src elisp :var user="john" +(format "%s %s" + (org-babel-call "pydemo" user "first-name") + (org-babel-call "pydemo" user "last-name")) +,#+end_src + +,#+results: full-name +: John Doe + +,#+call: full-name("jane") + +,#+results: +: Jane Doe + +,#+name: hello +,#+begin_src elisp :var name="World" :exports both +(format "Hello %s!" name) +,#+end_src + +,#+results: hello +: Hello World! + +,#+name: greet-user +,#+begin_src elisp :var user="john" +(org-babel-call "hello" (org-babel-call "full-name" user)) +,#+end_src + +,#+results: greet-user +: Hello John Doe! + +#+call: greet-user("jane") + +#+results: +: Hello Jane Doe! +#+end_example + +Next follows an example of calling a block recursively. Notice that +numbers stay numbers and are not coerced to strings during the +recursion. + +#+begin_example +,#+name: fib +,#+begin_src elisp :var x=0 +(if (< x 2) x (+ (org-babel-call "fib" (- x 2)) (org-babel-call "fib" (- x 1)))) +,#+end_src + +,#+results: fib +: 0 + +,#+call: fib(7) + +,#+results: +: 13 +#+end_example + +The next example shows how header arguments can be overriden on the +call and positional arguments can be flexibly put after keywords and +their associated arguments. + +#+begin_example +,#+name: find +,#+begin_src sh :results list :var name="*" :var maxdepth=1 :dir "/" +find -mindepth 1 -maxdepth "$maxdepth" -name "$name" -printf "%P\n" | sort +,#+end_src + +,#+begin_src elisp :results list +(seq-sort + (lambda (a b) + (version< (substring (car a) 6) (substring (car b) 6))) + (org-babel-call "find" :dir "/usr/lib" "python*")) +,#+end_src + +,#+results: +- python2.7 +- python3.7 +- python3.8 +- python3.11 +- python3.12 +- python3.13 +- python3.14 +#+end_example + +This final example shows how references to elements can be resolved by +using a named variable written as an assigment string or a positional +argument by resolving the reference explicitely using +~org-babel-ref-resolve~. + +#+begin_example +,#+name: tab +| HELLO | WORLD | +|-------+-------| +| ORG | MODE | + +,#+begin_src elisp +(org-babel-call "hello" :var "name=tab[0,1]") +,#+end_src + +,#+results: +: Hello WORLD! + +or + +,#+begin_src elisp +(org-babel-call "hello" (org-babel-ref-resolve "tab[2,0]")) +,#+end_src + +,#+results: +: Hello ORG! +#+end_example + *** Limit code block evaluation :PROPERTIES: :UNNUMBERED: notoc diff --git a/lisp/ob-core.el b/lisp/ob-core.el index e512f0ffc..903ffd976 100644 --- a/lisp/ob-core.el +++ b/lisp/ob-core.el @@ -826,6 +826,68 @@ retrieve backend-specific session buffer name." (when (org-babel-comint-buffer-livep buffer-name) buffer-name))) +(defun org-babel-call (name &rest args) + "Execute named source block and return the result. ARGS can contain +variable assigments and header arguments. Variable assigments can be +specified as unnamed positional arguments and named assigments. +Positional arguments are assigned to the declared variables of the +called block in the original order. Named variable assigments must be +preceeded by the the keyword :var and must be given as a pair +like (\"name\" . value) or ('name . value). They can be also specified +as a string like \"name=value\" in which case the value is resolved +internally using `org-babel-ref-resolve'. This is especially useful +when refererring to tables. Every other keyword plus its following +argument is treated as a header argument and is forwarded to the block +as given. All variable values are forwarded to the called block as +native values, i.e. numbers and lists are not coerced to strings. This +function is intended to be used in Emacs Lisp code blocks of user files. +For internal use `org-babel-execute-src-block' has the more flexible +interface." + (let ((arg args) + (pargs '()) ; positional args + (nargs '()) ; named args + (hargs '())) ; additional header args + (while arg + (let ((carg (car arg)) + (cadrg (cadr arg))) + (cond + ((eq carg :var) + (push cadrg nargs) + (setq arg (cddr arg))) + ((and (symbolp carg) + (string= (substring (symbol-name carg) 0 1) ":")) + (push (cons carg cadrg) hargs) + (setq arg (cddr arg))) + (t + (push carg pargs) + (setq arg (cdr arg))) + ) + ) + ) + (setq pargs (nreverse pargs)) + (setq nargs (nreverse nargs)) + (setq hargs (nreverse hargs)) + (save-excursion + (org-babel-goto-named-src-block name) + (let* ((info (org-babel-get-src-block-info)) + (vars (org-babel--get-vars (nth 2 info))) + (pvars (seq-mapn + (lambda (var parg) + (cons :var (cons (car var) parg))) + vars pargs)) + (nvars (seq-map + (lambda (narg) + (if (and (listp narg) (stringp (car narg))) + (setcar narg (intern (car narg)))) + (cons :var narg)) + nargs)) + (params (org-babel-merge-params + pvars + nvars + hargs + '((:results . "none"))))) + (org-babel-execute-src-block nil info params 'babel-call))))) + ;;;###autoload (defun org-babel-execute-src-block (&optional arg info params executor-type) "Execute the current source code block and return the result. diff --git a/testing/lisp/test-ob.el b/testing/lisp/test-ob.el index 5856b7994..beb7ddc87 100644 --- a/testing/lisp/test-ob.el +++ b/testing/lisp/test-ob.el @@ -605,6 +605,20 @@ at the beginning of a line." (org-babel-execute-maybe) (buffer-string)))))) +(ert-deftest test-ob/org-babel-call-mixed-variables-and-header-args () + (org-test-with-temp-text + " +#+name: test +#+begin_src sh :exports both :var var1=-1 :var var2=-2 :var var3=-3 :var var4=-4 :var var5=-5 +echo \"`pwd` $var1 $var2 $var3 $var4 $var5\" +#+end_src + +#+<point>begin_src emacs-lisp :var val1=1 :var val2=2 :var val3=3 :var val4=4 :var val5=5 +(org-babel-call \"test\" val1 :var (cons \"var4\" val4) val2 val3 :var (cons \"var5\" val5) :dir \"/tmp\") +#+end_src +" + (should (string= (org-babel-execute-src-block) "/tmp 1 2 3 4 5")))) + (ert-deftest test-ob/combining-scalar-and-raw-result-types () (skip-unless (featurep 'ob-shell)) (org-test-with-temp-text-in-file " -- 2.55.0
