emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
From: Daniel Kraus <daniel@kraus.my>
To: emacs-orgmode@gnu.org
Subject: [patch] ob-clojure: Fix results output
Date: Thu, 09 Mar 2023 16:40:04 +0100	[thread overview]
Message-ID: <87mt4m53qj.fsf@kraus.my> (raw)

[-- Attachment #1: Type: text/plain, Size: 418 bytes --]


attached is a bigger patch that fixes the result output of ob-clojure.
The commit message contains examples what's currently wrong
and what's fixed.
This is a breaking change though.
So if someone before relied on the fact that, e.g. cider, returns
the result of every expression in every line instead of only the
last one, they get a different result now.

Is it ok to install?
Other feedback?


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-ob-clojure.el-Fix-results-output-support-clojure-cli.patch --]
[-- Type: text/x-patch, Size: 11377 bytes --]

From 77783d864d81ef1d962c302523d7c588f248c088 Mon Sep 17 00:00:00 2001
From: Daniel Kraus <daniel@kraus.my>
Date: Thu, 9 Mar 2023 16:11:27 +0100
Subject: [PATCH] ob-clojure.el: Fix results output, support clojure-cli

* lisp/ob-clojure.el (org-babel-clojure-backend): Add support for
* lisp/ob-clojure.el (org-babel-clojurescript-backend): Move nbb to
* lisp/ob-clojure.el (org-babel-expand-body:clojure)
* lisp/ob-clojure.el (ob-clojure-eval-with-cider): Return only the
last expression when :results is not set or value, and return only
stdout when :results is set to output.
* lisp/ob-clojure.el (ob-clojure-eval-with-cmd): Rename function as
it is not only for babashka.
* lisp/ob-clojure.el (org-babel-execute:clojure): Differentiate
between Clojure and ClojureScript source blocks.

The problem was that the ob-clojure results where not correctly
taking the results parameter into account.
E.g. with the cider backend, you would get all printed or returned
values for each line in your block:

(def small-map {:a 2 :b 4 :c 8})
{:some :map}
(prn :xx)
(:b small-map)

| #'user/small-map |
| {:some :map}     |
| 4                |

or for babashka you would only get the printed values but not the
last return value:
(def small-map {:a 2 :b 4 :c 8})
{:some :map}
(prn :xx)
(:b small-map)

: :xx

Now when you specify :results value, the result is only the last
returned value, and with :results output you get all values
printed to stdout.
So the examples above would all result in the same:
(def small-map {:a 2 :b 4 :c 8})
{:some :map}
(prn :xx)
(:b small-map)

: 4
 lisp/ob-clojure.el | 120 +++++++++++++++++++++++++++++----------------
 1 file changed, 77 insertions(+), 43 deletions(-)

diff --git a/lisp/ob-clojure.el b/lisp/ob-clojure.el
index 5f589db00..6345927d6 100644
--- a/lisp/ob-clojure.el
+++ b/lisp/ob-clojure.el
@@ -25,20 +25,21 @@
 ;;; Commentary:
-;; Support for evaluating Clojure code
+;; Support for evaluating Clojure / ClojureScript code.
 ;; Requirements:
 ;; - Clojure (at least 1.2.0)
 ;; - clojure-mode
-;; - inf-clojure, Cider, SLIME, babashka or nbb
+;; - babashka, nbb, Clojure CLI tools, Cider, inf-clojure or SLIME
 ;; For clojure-mode, see https://github.com/clojure-emacs/clojure-mode
-;; For inf-clojure, see https://github.com/clojure-emacs/inf-clojure
-;; For Cider, see https://github.com/clojure-emacs/cider
-;; For SLIME, see https://slime.common-lisp.dev
 ;; For babashka, see https://github.com/babashka/babashka
 ;; For nbb, see https://github.com/babashka/nbb
+;; For Clojure CLI tools, see https://clojure.org/guides/deps_and_cli
+;; For Cider, see https://github.com/clojure-emacs/cider
+;; For inf-clojure, see https://github.com/clojure-emacs/inf-clojure
+;; For SLIME, see https://slime.common-lisp.dev
 ;; For SLIME, the best way to install its components is by following
 ;; the directions as set out by Phil Hagelberg (Technomancy) on the
@@ -78,7 +79,7 @@ defvar org-babel-header-args:clojurescript
 (defcustom org-babel-clojure-backend (cond
                                       ((executable-find "bb") 'babashka)
-                                      ((executable-find "nbb") 'nbb)
+                                      ((executable-find "clojure") 'clojure-cli)
                                       ((featurep 'cider) 'cider)
                                       ((featurep 'inf-clojure) 'inf-clojure)
                                       ((featurep 'slime) 'slime)
@@ -87,11 +88,24 @@ defcustom org-babel-clojure-backend
   :group 'org-babel
   :package-version '(Org . "9.6")
   :type '(choice
-	  (const :tag "inf-clojure" inf-clojure)
+	  (const :tag "babashka" babashka)
+          (const :tag "clojure-cli" clojure-cli)
 	  (const :tag "cider" cider)
+	  (const :tag "inf-clojure" inf-clojure)
 	  (const :tag "slime" slime)
-	  (const :tag "babashka" babashka)
+	  (const :tag "Not configured yet" nil)))
+(defcustom org-babel-clojurescript-backend
+  (cond
+   ((or (executable-find "nbb") (executable-find "npx")) 'nbb)
+   ((featurep 'cider) 'cider)
+   (t nil))
+  "Backend used to evaluate Clojure code blocks."
+  :group 'org-babel
+  :package-version '(Org . "9.6")
+  :type '(choice
 	  (const :tag "nbb" nbb)
+	  (const :tag "cider" cider)
 	  (const :tag "Not configured yet" nil)))
 (defcustom org-babel-clojure-default-ns "user"
@@ -105,15 +119,24 @@ defcustom ob-clojure-babashka-command
   :group 'org-babel
   :package-version '(Org . "9.6"))
-(defcustom ob-clojure-nbb-command (executable-find "nbb")
+(defcustom ob-clojure-nbb-command (or (executable-find "nbb")
+                                      (when-let (npx (executable-find "npx"))
+                                        (concat npx " nbb")))
   "Path to the nbb executable."
   :type '(choice file (const nil))
+  :group 'org-babel)
+(defcustom ob-clojure-cli-command (when-let (cmd (executable-find "clojure"))
+                                    (concat cmd " -M"))
+  "Clojure CLI command used to execute source blocks."
+  :type 'file
   :group 'org-babel
   :package-version '(Org . "9.6"))
 (defun org-babel-expand-body:clojure (body params)
   "Expand BODY according to PARAMS, return the expanded body."
   (let* ((vars (org-babel--get-vars params))
+         (cljs-p (string= (cdr (assq :target params)) "cljs"))
          (backend-override (cdr (assq :backend params)))
@@ -146,10 +169,26 @@ defun org-babel-expand-body:clojure
 			    "\n      ")
-    (if (or (member "code" result-params)
-	    (member "pp" result-params))
-	(format "(clojure.pprint/pprint (do %s))" body)
-      body)))
+    ;; If the result param is set to "output" we don't have to do
+    ;; anything special and just let the backend handle everything
+    (if (member "output" result-params)
+        body
+      ;; If the result is not "output" (i.e. it's "value"), disable
+      ;; stdout output and print the last returned value.  Use pprint
+      ;; instead of prn when results param is "pp" or "code".
+      (concat
+       (if (or (member "code" result-params)
+	       (member "pp" result-params))
+           (concat (if cljs-p
+                       "(require '[cljs.pprint :refer [pprint]])"
+                     "(require '[clojure.pprint :refer [pprint]])")
+                   " (pprint ")
+         "(prn ")
+       (if cljs-p
+           "(binding [cljs.core/*print-fn* (constantly nil)]"
+         "(binding [*out* (java.io.StringWriter.)]")
+       body "))"))))
 (defvar ob-clojure-inf-clojure-filter-out)
 (defvar ob-clojure-inf-clojure-tmp-output)
@@ -228,29 +267,15 @@ defun ob-clojure-eval-with-inf-clojure
 (defun ob-clojure-eval-with-cider (expanded params)
   "Evaluate EXPANDED code block with PARAMS using cider."
   (org-require-package 'cider "Cider")
-  (let ((connection (cider-current-connection (cdr (assq :target params))))
-	(result-params (cdr (assq :result-params params)))
-	result0)
+  (let ((connection (cider-current-connection (cdr (assq :target params)))))
     (unless connection (sesman-start-session 'CIDER))
     (if (not connection)
 	;; Display in the result instead of using `user-error'
-	(setq result0 "Please reevaluate when nREPL is connected")
-      (ob-clojure-with-temp-expanded expanded params
-	(let ((response (nrepl-sync-request:eval exp connection)))
-	  (push (or (nrepl-dict-get response "root-ex")
-		    (nrepl-dict-get response "ex")
-		    (nrepl-dict-get
-		     response (if (or (member "output" result-params)
-				      (member "pp" result-params))
-				  "out"
-				"value")))
-		result0)))
-      (ob-clojure-string-or-list
-       ;; Filter out s-expressions that return nil (string "nil"
-       ;; from nrepl eval) or comment forms (actual nil from nrepl)
-       (reverse (delete "" (mapcar (lambda (r)
-				     (replace-regexp-in-string "nil" "" (or r "")))
-				   result0)))))))
+        "Please reevaluate when nREPL is connected"
+      (let ((response (nrepl-sync-request:eval expanded connection)))
+        (or (nrepl-dict-get response "root-ex")
+	    (nrepl-dict-get response "ex")
+	    (nrepl-dict-get response "out"))))))
 (defun ob-clojure-eval-with-slime (expanded params)
   "Evaluate EXPANDED code block with PARAMS using slime."
@@ -262,24 +287,30 @@ defun ob-clojure-eval-with-slime
        ,(buffer-substring-no-properties (point-min) (point-max)))
      (cdr (assq :package params)))))
-(defun ob-clojure-eval-with-babashka (bb expanded)
-  "Evaluate EXPANDED code block using BB (babashka or nbb)."
-  (let ((script-file (org-babel-temp-file "clojure-bb-script-" ".clj")))
+(defun ob-clojure-eval-with-cmd (cmd expanded)
+  "Evaluate EXPANDED code block using CMD (babashka, clojure or nbb)."
+  (let ((script-file (org-babel-temp-file "clojure-cmd-script-" ".clj")))
     (with-temp-file script-file
       (insert expanded))
-     (format "%s %s" bb (org-babel-process-file-name script-file))
+     (format "%s %s" cmd (org-babel-process-file-name script-file))
 (defun org-babel-execute:clojure (body params)
   "Execute the BODY block of Clojure code with PARAMS using Babel."
   (let* ((backend-override (cdr (assq :backend params)))
+         (cljs-p (string= (cdr (assq :target params)) "cljs"))
            (backend-override (intern backend-override))
-           (org-babel-clojure-backend org-babel-clojure-backend)
-           (t (user-error "You need to customize `org-babel-clojure-backend'
-or set the `:backend' header argument")))))
+           (org-babel-clojure-backend (if cljs-p
+                                          org-babel-clojurescript-backend
+                                        org-babel-clojure-backend))
+           (t (user-error "You need to customize `%S'
+or set the `:backend' header argument"
+                          (if cljs-p
+                              org-babel-clojurescript-backend
+                            org-babel-clojure-backend))))))
     (let* ((expanded (org-babel-expand-body:clojure body params))
 	   (result-params (cdr (assq :result-params params)))
@@ -287,14 +318,17 @@ defun org-babel-execute:clojure
 	     ((eq org-babel-clojure-backend 'inf-clojure)
 	      (ob-clojure-eval-with-inf-clojure expanded params))
+             ((eq org-babel-clojure-backend 'clojure-cli)
+              (ob-clojure-eval-with-cmd ob-clojure-cli-command expanded))
              ((eq org-babel-clojure-backend 'babashka)
-	      (ob-clojure-eval-with-babashka ob-clojure-babashka-command expanded))
+	      (ob-clojure-eval-with-cmd ob-clojure-babashka-command expanded))
              ((eq org-babel-clojure-backend 'nbb)
-	      (ob-clojure-eval-with-babashka ob-clojure-nbb-command expanded))
+	      (ob-clojure-eval-with-cmd ob-clojure-nbb-command expanded))
 	     ((eq org-babel-clojure-backend 'cider)
 	      (ob-clojure-eval-with-cider expanded params))
 	     ((eq org-babel-clojure-backend 'slime)
-	      (ob-clojure-eval-with-slime expanded params))))
+	      (ob-clojure-eval-with-slime expanded params))
+             (t (user-error "Invalid backend"))))
       (org-babel-result-cond result-params
         (condition-case nil (org-babel-script-escape result)

             reply	other threads:[~2023-03-09 15:45 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-03-09 15:40 Daniel Kraus [this message]
2023-03-10 12:35 ` [patch] ob-clojure: Fix results output Ihor Radchenko
2023-03-13 14:01   ` Daniel Kraus
2023-03-14 12:35     ` Ihor Radchenko
2023-03-14 13:38       ` Daniel Kraus
2023-03-14 14:27         ` Daniel Kraus
2023-03-15 10:20           ` Ihor Radchenko
2023-03-15 11:22             ` Daniel Kraus
2023-03-16 10:19               ` Ihor Radchenko
2023-03-19  8:07                 ` Ihor Radchenko
2023-03-19 20:43                   ` Daniel Kraus
2023-03-22 10:48                     ` Ihor Radchenko
2023-03-23 11:31                       ` Daniel Kraus
2023-03-23 11:52                         ` Ihor Radchenko
2023-03-23 12:29                           ` Daniel Kraus

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:

  List information: https://www.orgmode.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87mt4m53qj.fsf@kraus.my \
    --to=daniel@kraus.my \
    --cc=emacs-orgmode@gnu.org \


* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).