;;; ob-clojure.el --- Babel Functions for Clojure -*- lexical-binding: t; -*- ;; Copyright (C) 2009-2018 Free Software Foundation, Inc. ;; Author: Joel Boehland, Eric Schulte, Oleh Krehel, Frederick Giasson ;; ;; Keywords: literate programming, reproducible research ;; Homepage: https://orgmode.org ;; This file is part of GNU Emacs. ;; GNU Emacs is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs. If not, see . ;;; Commentary: ;; Support for evaluating clojure code ;; Requirements: ;; - clojure (at least 1.2.0) ;; - clojure-mode ;; - either cider or SLIME ;; For Cider, see https://github.com/clojure-emacs/cider ;; For SLIME, the best way to install these components is by following ;; the directions as set out by Phil Hagelberg (Technomancy) on the ;; web page: http://technomancy.us/126 ;;; Code: (require 'cl-lib) (require 'ob) (declare-function cider-current-connection "ext:cider-client" (&optional type)) (declare-function cider-current-ns "ext:cider-client" ()) (declare-function nrepl--merge "ext:nrepl-client" (dict1 dict2)) (declare-function nrepl-dict-get "ext:nrepl-client" (dict key)) (declare-function nrepl-dict-put "ext:nrepl-client" (dict key value)) (declare-function nrepl-request:eval "ext:nrepl-client" (input callback connection &optional ns line column additional-params tooling)) (declare-function nrepl-sync-request:eval "ext:nrepl-client" (input connection &optional ns tooling)) (declare-function org-trim "org" (s &optional keep-lead)) (declare-function slime-eval "ext:slime" (sexp &optional package)) (defvar nrepl-sync-request-timeout) (defvar cider-buffer-ns) (defvar org-babel-tangle-lang-exts) (add-to-list 'org-babel-tangle-lang-exts '("clojure" . "clj")) (add-to-list 'org-babel-tangle-lang-exts '("clojurescript" . "cljs")) (defvar org-babel-default-header-args:clojure '()) (defvar org-babel-header-args:clojure '((package . :any))) (defcustom org-babel-clojure-sync-nrepl-timeout 10 "Timeout value, in seconds, of a Clojure sync call. If the value is nil, timeout is disabled." :group 'org-babel :type 'integer :version "26.1" :package-version '(Org . "9.1") :safe #'wholenump) (defcustom org-babel-clojure-backend (cond ((featurep 'cider) 'cider) (t 'slime)) "Backend used to evaluate Clojure code blocks." :group 'org-babel :type '(choice (const :tag "cider" cider) (const :tag "SLIME" slime))) (defcustom org-babel-clojure-default-ns "user" "Default Clojure namespace for src block when all find ns ways failed." :type 'string :group 'org-babel) (defun org-babel-clojure-cider-current-ns () "Like `cider-current-ns' except `cider-find-ns'." (or cider-buffer-ns (let ((repl-buf (cider-current-connection))) (and repl-buf (buffer-local-value 'cider-buffer-ns repl-buf))) org-babel-clojure-default-ns)) (defun org-babel-expand-body:clojure (body params) "Expand BODY according to PARAMS, return the expanded body." (let* ((vars (org-babel--get-vars params)) (ns (or (cdr (assq :ns params)) (org-babel-clojure-cider-current-ns))) (result-params (cdr (assq :result-params params))) (print-level nil) (print-length nil) (body (org-trim (format "(ns %s)\n%s" ;; Source block specified namespace :ns. ns ;; Variables binding. (if (null vars) (org-trim body) (format "(let [%s]\n%s)" (mapconcat (lambda (var) (format "%S (quote %S)" (car var) (cdr var))) vars "\n ") body)))))) (if (or (member "code" result-params) (member "pp" result-params)) (format "(clojure.pprint/pprint (do %s))" body) body))) (defun org-babel-execute:clojure (body params) "Execute a block of Clojure code with Babel. The underlying process performed by the code block can be output using the :show-process parameter." (let* ((expanded (org-babel-expand-body:clojure body params)) (response (list 'dict)) result) (cl-case org-babel-clojure-backend (cider (require 'cider) (let ((result-params (cdr (assq :result-params params))) (show (cdr (assq :show-process params))) (connection (cider-current-connection (cdr (assq :target params))))) (if (member show '(nil "no")) ;; Run code without showing the process. (progn (setq response (let ((nrepl-sync-request-timeout org-babel-clojure-sync-nrepl-timeout)) (nrepl-sync-request:eval expanded connection))) (setq result (concat (nrepl-dict-get response (if (or (member "output" result-params) (member "pp" result-params)) "out" "value")) (nrepl-dict-get response "ex") (nrepl-dict-get response "root-ex") (nrepl-dict-get response "err")))) ;; Show the process in an output buffer/window. (let ((process-buffer (switch-to-buffer-other-window "*Clojure Show Process Sub Buffer*")) status) ;; Run the Clojure code in nREPL. (nrepl-request:eval expanded (lambda (resp) (when (member "out" resp) ;; Print the output of the nREPL in the output buffer. (princ (nrepl-dict-get resp "out") process-buffer)) (when (member "ex" resp) ;; In case there is an exception, then add it to the ;; output buffer as well. (princ (nrepl-dict-get resp "ex") process-buffer) (princ (nrepl-dict-get resp "root-ex") process-buffer)) (when (member "err" resp) ;; In case there is an error, then add it to the ;; output buffer as well. (princ (nrepl-dict-get resp "err") process-buffer)) (nrepl--merge response resp) ;; Update the status of the nREPL output session. (setq status (nrepl-dict-get response "status"))) connection) ;; Wait until the nREPL code finished to be processed. (while (not (member "done" status)) (nrepl-dict-put response "status" (remove "need-input" status)) (accept-process-output nil 0.01) (redisplay)) ;; Delete the show buffer & window when the processing is ;; finalized. (mapc #'delete-window (get-buffer-window-list process-buffer nil t)) (kill-buffer process-buffer) ;; Put the output or the value in the result section of ;; the code block. (setq result (concat (nrepl-dict-get response (if (or (member "output" result-params) (member "pp" result-params)) "out" "value")) (nrepl-dict-get response "ex") (nrepl-dict-get response "root-ex") (nrepl-dict-get response "err"))))))) (slime (require 'slime) (with-temp-buffer (insert expanded) (setq result (slime-eval `(swank:eval-and-grab-output ,(buffer-substring-no-properties (point-min) (point-max))) (cdr (assq :package params))))))) (org-babel-result-cond (cdr (assq :result-params params)) result (condition-case nil (org-babel-script-escape result) (error result))))) (defun org-babel-execute:clojurescript (body params) (org-babel-execute:clojure body (cons '(:target . "cljs") params))) (defun org-babel-edit-prep:clojure (babel-info) (if-let* ((namespace (cdr (assq :ns (nth 2 babel-info))))) (setq-local cider-buffer-ns namespace))) (defalias 'org-babel-edit-prep:clojurescript 'org-babel-edit-prep:clojure) (provide 'ob-clojure) ;;; ob-clojure.el ends here