I came across an issue with org-babel evaluation of Ruby blocks with
:results `output`
A minimal reproduction can be done like this:
#+begin_src emacs-lisp
(package-install 'inf-ruby)
(org-babel-do-load-languages
'org-babel-load-languages
'((ruby . t)))
#+end_src
#+RESULTS:
#+begin_src ruby :session test :results output
def moo
puts "goo"
end
moo
#+end_src
#+RESULTS:
: _org_babel_ruby_prompt _org_babel_ruby_prompt goo
The IRB process is adding extra prompts due to the method definition in
the block. The test examples in org-mode do not cover this scenario.
I have added a test, you can see the failure result here:
https://builds.sr.ht/~shoshin/job/1754555
I was trying to isolate my own local Emacs from the current org-mode
code, I'm on 9.7, so I set up this build to run from org-mode and
inf-ruby source. Ruby is at v3.3. IRB at v1.18 which is the most recent
release.
I see that Org 9.8 adds some filter-prompt behavior that might make this
patch moot.
Regarding the changes, inf-ruby dealt with some prompt issues several
years back, https://github.com/nonsequitur/inf-ruby/pull/175 and the
ruby-send-string function was updated to behave more cleanly. I replaced
some (insert <string>) calls with (ruby-send-string <string>). I'm not
sure this is the best way to interact with comint and babel, but it
reduces the amount of noise in the session buffer.
I changed how the session is initiated as well, because ruby-send-string
impacted the behavior of the prompt during initialization. I chose to
clear the comint buffer after the prompt is configured, again to remove
noise in the session buffer.
There's some side effects of the changes, like the contents of the
session buffer are different. I'm not sure if the contents of the
session are important or not, though it would be nice if the org source
block session could be interacted with like any other inferior-ruby.
I am sure there is a better way to cleanly send code to the IRB
inferior-ruby process, but I'm just beginning to understand how all the
parts interact. Any guidance in general on org bable session behavior
would be welcome.
I'd also like to improve the readability of ob-ruby.el, any guidance
there is welcome too.
>From eeaa66efb52c222800bdf3a73a74233c87d53e30 Mon Sep 17 00:00:00 2001
From: Grant Shangreaux <[email protected]>
Date: Sun, 10 May 2026 11:20:08 -0500
Subject: [PATCH 1/3] [ob-ruby] Clean: factor out session and external eval
functions
org-babel-ruby-evaluate is one large function, this extracts two
separate functions.
One for evaluating a code block in an external Ruby process, and
one for session based evaluation.
---
lisp/ob-ruby.el | 56 ++++++++++++++++++++++++++++---------------------
1 file changed, 32 insertions(+), 24 deletions(-)
diff --git a/lisp/ob-ruby.el b/lisp/ob-ruby.el
index 003fc809e..3151f5103 100644
--- a/lisp/ob-ruby.el
+++ b/lisp/ob-ruby.el
@@ -231,26 +231,24 @@ File.open('%s', 'w') do |f|
end
")
-(defun org-babel-ruby-evaluate
- (buffer body &optional result-type result-params)
- "Pass BODY to the Ruby process in BUFFER.
-If RESULT-TYPE equals `output' then return a list of the outputs
-of the statements in BODY, if RESULT-TYPE equals `value' then
-return the value of the last statement in BODY, as elisp."
- (if (not buffer)
- ;; external process evaluation
- (pcase result-type
- (`output (org-babel-eval org-babel-ruby-command body))
- (`value (let ((tmp-file (org-babel-temp-file "ruby-")))
- (org-babel-eval
- org-babel-ruby-command
- (format (if (member "pp" result-params)
- org-babel-ruby-pp-wrapper-method
- org-babel-ruby-wrapper-method)
- body (org-babel-process-file-name tmp-file 'noquote)))
- (org-babel-eval-read-file tmp-file))))
- ;; comint session evaluation
- (pcase result-type
+(defun org-babel-ruby-evaluate-external-process
+ (body &optional result-type result-params)
+ "Evaluate BODY in an external Ruby process."
+ (pcase result-type
+ (`output (org-babel-eval org-babel-ruby-command body))
+ (`value (let ((tmp-file (org-babel-temp-file "ruby-")))
+ (org-babel-eval
+ org-babel-ruby-command
+ (format (if (member "pp" result-params)
+ org-babel-ruby-pp-wrapper-method
+ org-babel-ruby-wrapper-method)
+ body (org-babel-process-file-name tmp-file 'noquote)))
+ (org-babel-eval-read-file tmp-file)))))
+
+(defun org-babel-ruby-evaluate-in-session
+ (session body &optional result-type result-params)
+ "Evaluate BODY in the running comint SESSION."
+ (pcase result-type
(`output
(let ((eoe-string (format "puts \"%s\"" org-babel-ruby-eoe-indicator)))
;; Force the session to be ready before the actual session
@@ -259,7 +257,7 @@ return the value of the last statement in BODY, as elisp."
;; been inserted and that throws off the extraction of the
;; result for Babel.
(org-babel-comint-with-output
- (buffer org-babel-ruby-eoe-indicator t eoe-string)
+ (session org-babel-ruby-eoe-indicator t eoe-string)
(insert eoe-string) (comint-send-input nil t))
(mapconcat
#'identity
@@ -268,7 +266,7 @@ return the value of the last statement in BODY, as elisp."
(mapconcat
#'org-trim
(org-babel-comint-with-output
- (buffer org-babel-ruby-eoe-indicator t body)
+ (session org-babel-ruby-eoe-indicator t body)
(insert (org-babel-chomp body) "\n" eoe-string)
(comint-send-input nil t))
"\n") "[\r\n]")) "\n")))
@@ -277,7 +275,7 @@ return the value of the last statement in BODY, as elisp."
(ppp (or (member "code" result-params)
(member "pp" result-params))))
(org-babel-comint-with-output
- (buffer org-babel-ruby-eoe-indicator t body)
+ (session org-babel-ruby-eoe-indicator t body)
(when ppp (insert "require 'pp';") (comint-send-input nil t))
(mapc
(lambda (line)
@@ -293,7 +291,17 @@ return the value of the last statement in BODY, as elisp."
(org-babel-process-file-name tmp-file 'noquote))))
(list (format "puts \"%s\"" org-babel-ruby-eoe-indicator))))
(comint-send-input nil t))
- (org-babel-eval-read-file tmp-file))))))
+ (org-babel-eval-read-file tmp-file)))))
+
+(defun org-babel-ruby-evaluate
+ (session body &optional result-type result-params)
+ "Pass BODY to the Ruby SESSION or external process.
+If RESULT-TYPE equals `output' then return a list of the outputs
+of the statements in BODY, if RESULT-TYPE equals `value' then
+return the value of the last statement in BODY, as elisp."
+ (if session
+ (org-babel-ruby-evaluate-in-session session body result-type result-params)
+ (org-babel-ruby-evaluate-external-process body result-type result-params)))
(provide 'ob-ruby)
--
2.54.0
>From 59798e3be23a5ea3738f30bb2bcd8a427c5ae2be Mon Sep 17 00:00:00 2001
From: Grant Shangreaux <[email protected]>
Date: Mon, 11 May 2026 18:37:51 -0500
Subject: [PATCH 2/3] Add: failing session output test
---
testing/lisp/test-ob-ruby.el | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/testing/lisp/test-ob-ruby.el b/testing/lisp/test-ob-ruby.el
index aecc05b40..e10e68b24 100644
--- a/testing/lisp/test-ob-ruby.el
+++ b/testing/lisp/test-ob-ruby.el
@@ -82,6 +82,35 @@ s = \"6\"
: 5
")))
+
+(ert-deftest test-ob-ruby/session-output-5 ()
+ (should (equal (org-test-with-temp-text "#+begin_src ruby :session org-test-ruby :results output
+class Foo
+ def moo
+ puts \"Goo\"
+ end
+end
+
+Foo.new.moo
+#+end_src"
+ (org-babel-execute-maybe)
+ (substring-no-properties
+ (buffer-string)))
+ "#+begin_src ruby :session org-test-ruby :results output
+class Foo
+ def moo
+ puts \"Goo\"
+ end
+end
+
+Foo.new.moo
+#+end_src
+
+#+RESULTS:
+: Goo
+")))
+
+
(ert-deftest test-ob-ruby/value ()
(should
(equal 3
--
2.54.0
>From 0d16d4036119f934f13ee112ffcb7f71f27c91f3 Mon Sep 17 00:00:00 2001
From: Grant Shangreaux <[email protected]>
Date: Tue, 12 May 2026 15:16:29 -0500
Subject: [PATCH 3/3] Fix: ruby babel session eval output results include
prompts
In the case where a user is defining methods or classes within a
session source block that has :results output, there are IRB prompts
not being filtered from the output.
---
lisp/ob-ruby.el | 24 ++++++++++--------------
1 file changed, 10 insertions(+), 14 deletions(-)
diff --git a/lisp/ob-ruby.el b/lisp/ob-ruby.el
index 3151f5103..50305c5f0 100644
--- a/lisp/ob-ruby.el
+++ b/lisp/ob-ruby.el
@@ -161,7 +161,9 @@ Emacs-lisp table, otherwise return the results as a string."
"String used for unique prompt.")
(defvar org-babel-ruby-define-prompt
- (format "IRB.conf[:PROMPT][:CUSTOM] = { :PROMPT_I => \"%s\" }" org-babel-ruby-prompt))
+ (format "IRB.conf[:PROMPT][:CUSTOM] = { PROMPT_I: \"%s\", PROMPT_C: nil, PROMPT_S: nil }"
+ org-babel-ruby-prompt)
+ "Ruby code to define the IRB prompt appropriate for babel evaluation.")
(defun org-babel-ruby-initiate-session (&optional session params)
"Initiate a ruby session.
@@ -197,9 +199,10 @@ Session settings (`:ruby' header arg value) are taken from PARAMS."
(insert org-babel-ruby-define-prompt ";")
(insert "_org_prompt_mode=conf.prompt_mode;conf.prompt_mode=:CUSTOM;")
(insert "conf.echo=false\n")
- (comint-send-input nil t)))
+ (comint-send-input nil t)
+ ;; None of this needs to remain in the comint session buffer.
+ (comint-clear-buffer)))
session-buffer)
- (sit-for .5)
(org-babel-ruby-initiate-session session)))))
(defvar org-babel-ruby-eoe-indicator ":org_babel_ruby_eoe"
@@ -251,23 +254,16 @@ end
(pcase result-type
(`output
(let ((eoe-string (format "puts \"%s\"" org-babel-ruby-eoe-indicator)))
- ;; Force the session to be ready before the actual session
- ;; code is run. There is some problem in comint that will
- ;; sometimes show the prompt after the input has already
- ;; been inserted and that throws off the extraction of the
- ;; result for Babel.
- (org-babel-comint-with-output
- (session org-babel-ruby-eoe-indicator t eoe-string)
- (insert eoe-string) (comint-send-input nil t))
- (mapconcat
+ (mapconcat
#'identity
(butlast
(split-string
(mapconcat
#'org-trim
(org-babel-comint-with-output
- (session org-babel-ruby-eoe-indicator t body)
- (insert (org-babel-chomp body) "\n" eoe-string)
+ (session org-babel-ruby-eoe-indicator t body)
+ (ruby-send-string (org-babel-chomp body))
+ (ruby-send-string eoe-string)
(comint-send-input nil t))
"\n") "[\r\n]")) "\n")))
(`value
--
2.54.0