The plan:

  • Do some research into existing emacs lisp code
    • Figure out how to get the contents of a named block and use it as the stdin to a babel block.
  • Extend babel so that I can chain scripts of multiple languages together in babel

Here are some named blocks I’ll use for testing

1
(org-babel-goto-named-src-block "mycode")
1
2
(org-babel-goto-named-result)
(org-babel-goto-named-src-block)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
(defun org-copy-thing-here ()
  (interactive)
  (if (or (org-in-src-block-p)
          (org-in-block-p '("src" "example" "verbatim" "clocktable")))
      (org-copy-src-block)
    (self-insert-command 1)))

(defun org-copy-src-block ()
  (interactive)
  (shut-up (my-copy org-get-src-block-here)))

(defun org-get-src-block-here ()
  (interactive)
  (org-edit-src-code)
  (mark-whole-buffer)
  (let ((contents (sh/chomp (selection))))
    ;; (easy-kill 1)
    (org-edit-src-abort)
    contents))

Hi there

Existing emacs lisp babel code

Don’t bother with org-babel-goto-named-result. A result block can be a src block too.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(defun org-babel-goto-named-result (name)
  "Go to a named result."
  (interactive
   (let ((completion-ignore-case t))
     (list (completing-read "Source-block name: "
  			  (org-babel-result-names) nil t))))
  (let ((point (org-babel-find-named-result name)))
    (if point
        ;; taken from `org-open-at-point'
        (progn (goto-char point) (org-show-context))
      (message "result `%s' not found in this buffer" name))))

Use org-babel-goto-named-src-block.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
(defun org-babel-goto-named-src-block (name)
  "Go to a named source-code block."
  (interactive
   (let ((completion-ignore-case t)
       (case-fold-search t)
       (all-block-names (org-babel-src-block-names)))
     (list (completing-read
          "source-block name: " all-block-names nil t
          (let* ((context (org-element-context))
                 (type (org-element-type context))
                 (noweb-ref
                  (and (memq type '(inline-src-block src-block))
                       (org-in-regexp (org-babel-noweb-wrap)))))
            (cond
             (noweb-ref
              (buffer-substring
               (+ (car noweb-ref) (length org-babel-noweb-wrap-start))
               (- (cdr noweb-ref) (length org-babel-noweb-wrap-end))))
             ((memq type '(babel-call inline-babel-call)) ;#+CALL:
              (org-element-property :call context))
             ((car (org-element-property :results context))) ;#+RESULTS:
             ((let ((symbol (thing-at-point 'symbol))) ;Symbol.
                (and symbol
                     (member-ignore-case symbol all-block-names)
                     symbol)))
             (t "")))))))
  (let ((point (org-babel-find-named-block name)))
    (if point
        ;; Taken from `org-open-at-point'.
        (progn (org-mark-ring-push) (goto-char point) (org-show-context))
      (message "source-code block `%s' not found in this buffer" name))))

(defun org-babel-find-named-block (name)
  "Find a named source-code block.
Return the location of the source block identified by source
NAME, or nil if no such block exists.  Set match data according
to `org-babel-named-src-block-regexp'."
  (save-excursion
    (goto-char (point-min))
    (let ((regexp (org-babel-named-src-block-regexp-for-name name)))
      (or (and (looking-at regexp)
             (progn (goto-char (match-beginning 1))
                    (line-beginning-position)))
          (ignore-errors (org-next-block 1 nil regexp))))))
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(defun org-babel-get-named-block-contents (name)
  "Find a named source-code block.
Return the contents of the source block identified by source
NAME, or nil if no such block exists.  Set match data according
to `org-babel-named-src-block-regexp'."
  (save-excursion
    (goto-char (point-min))
    (let ((regexp (org-babel-named-src-block-regexp-for-name name)))
      (or (and (looking-at regexp)
               (progn (goto-char (match-beginning 1))
                      (line-beginning-position)))
          (if (ignore-errors (org-next-block 1 nil regexp))
              (org-get-src-block-here))))))

Create some functions to get the contents of src and result blocks by name

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
(defun org-babel-get-named-block-contents (name)
  "Find a named source-code block.
Return the contents of the source block identified by source
NAME, or nil if no such block exists.  Set match data according
to `org-babel-named-src-block-regexp'."
  (save-excursion
    (goto-char (point-min))
    (goto-char (org-babel-find-named-block name))
    (org-get-src-block-here)
    ))

(defun org-babel-get-named-result-contents (name)
  "Find a named result block.
Return the contents of the source block identified by source
NAME, or nil if no such block exists.  Set match data according
to `org-babel-named-src-block-regexp'."
  (save-excursion
    (goto-char (point-min))
    (goto-char (org-babel-find-named-result name))
    (org-get-src-block-here)
    ))

Great, the commands work

1
(org-babel-get-named-block-contents "mycode")
"\"(defun org-copy-thing-here ()\\n  (interactive)\\n  (if (or (org-in-src-block-p)\\n          (org-in-block-p '(\\\"src\\\" \\\"example\\\" \\\"verbatim\\\" \\\"clocktable\\\")))\\n      (org-copy-src-block)\\n    (self-insert-command 1)))\\n\\n(defun org-copy-src-block ()\\n  (interactive)\\n  (shut-up (my-copy org-get-src-block-here)))\\n\\n(defun org-get-src-block-here ()\\n  (interactive)\\n  (org-edit-src-code)\\n  (mark-whole-buffer)\\n  (let ((contents (sh/chomp (selection))))\\n    ;; (easy-kill 1)\\n    (org-edit-src-abort)\\n    contents))\""
1
(org-babel-get-named-result-contents "mycode")
"\"Hi there\""

Now, extend org-babel-execute:generic to use these functions

  • Provide stdin for custom interpreters when.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
(defun org-babel-execute:generic (body params)
  ":interpreter executes body like 'interpreter file'
:interpreter filters the source like 'cat source | filter'"
  (let ((stdinblock (or (cdr (assoc :in params))
                        (cdr (assoc :inb params))
                        (cdr (assoc :ins params))
                        (cdr (assoc :insrc params))
                        ""))
        (stdinresult (or (cdr (assoc :inr params))
                         (cdr (assoc :inresult params))
                         ""))
        (stdincmd (or (cdr (assoc :input params))
                      (cdr (assoc :stdin params))
                      ))
        (interpreter (or (cdr (assoc :interpreter params))
                         (cdr (assoc :i params))
                         (cdr (assoc :sps params))
                         (cdr (assoc :sph params))
                         (cdr (assoc :spv params))
                         (cdr (assoc :comint params))
                         (cdr (assoc :nw params))
                         (cdr (assoc :esph params))
                         (cdr (assoc :espv params))
                         (cdr (assoc :enw params))))
        (tmp (or (cdr (assoc :t params))
                 (cdr (assoc :fp params))
                 (cdr (assoc :file params))
                 (org-babel-temp-file "generic-")))
        (args (or (cdr (assoc :args params))
                  (cdr (assoc :a params))))
        (filter (or (if (cdr (assq :spsf params))
                        (concat "sps -E " (q (cdr (assq :spsf params))))
                      nil)
                    (cdr (assoc :filter params))
                    (cdr (assoc :f params)))))

    (if (and (string-empty-p (or stdincmd ""))
             (not (string-empty-p (or stdinblock ""))))
        ;; Save the contents of stdinblock to a file
        ;; and overwrite stdincmd
        (let ((fp (org-babel-temp-file "stdin" "txt")))
          (write-string-to-file (org-babel-get-named-block-contents stdinblock) fp)
          (setq stdincmd (concat "cat " fp))))

    (if (and (string-empty-p (or stdincmd ""))
             (not (string-empty-p (or stdinresult ""))))
        (let ((fp (org-babel-temp-file "stdin" "txt")))
          (write-string-to-file (org-babel-get-named-result-contents stdinresult) fp)
          (setq stdincmd (concat "cat " fp))))


    (if (and (boundp 'stdincmd) stdincmd)
        (setq stdincmd (concat stdincmd " | "))
      (setq stdincmd ""))

    ;; (tv stdincmd)
    ;; (tv "hi")

    (if (not (and (boundp 'interpreter) interpreter))
        (setq interpreter "cat"))
    (write-string-to-file body tmp)
    (if (and (boundp 'args) args)
        (setq args (concat " " args))
      (setq args ""))
    (if (and (boundp 'filter) filter)
        (setq filter (concat "| " filter))
      (setq filter ""))

    ;; The str here are not superfluous. They check for nil
    (let* ((stdincmd (str stdincmd))
           (interpreter (str interpreter))
           (tmp (str tmp))
           (args (str args))
           (filter (str filter))
           (cmd (format
                 "PATH=\"/home/shane/scripts:$PATH\"; %s%s%s %s"
                 stdincmd
                 (if (string-match-p "{}" interpreter)
                     (s-replace "{}" tmp interpreter)
                   (concat
                    interpreter
                    " "
                    tmp))
                 args
                 filter)))
      (if (assoc :pak params)
          (setq cmd (concat cmd "; pak")))
      (cond ((assoc :comint params) (comint-quick cmd))
            ((assoc :nw params) (nw cmd))
            ((assoc :sps params) (sps cmd))
            ((assoc :sph params) (sph cmd))
            ((assoc :spv params) (spv cmd))
            ((assoc :esph params) (sph-term cmd))
            ((assoc :espv params) (spv-term cmd))
            ((assoc :enw params) (nw-term cmd))
            (t (shell-command-to-string cmd))))))

Test it out

It works.

1
ferry

1
tr r z

fezzy
1
2
3
4
import sys
data = sys.stdin.read()
print(len(data))
print(data)
5
fezzy

Demonstration of chaining scripts of multiple languages together

asciinema recording