Commit | Line | Data |
---|---|---|
86fbb8ca CD |
1 | ;;; ob-clojure.el --- org-babel functions for clojure evaluation |
2 | ||
3 | ;; Copyright (C) 2009, 2010 Free Software Foundation, Inc. | |
4 | ||
5 | ;; Author: Joel Boehland | |
6 | ;; Keywords: literate programming, reproducible research | |
7 | ;; Homepage: http://orgmode.org | |
afe98dfa | 8 | ;; Version: 7.3 |
86fbb8ca CD |
9 | |
10 | ;; This file is part of GNU Emacs. | |
11 | ||
12 | ;; GNU Emacs is free software: you can redistribute it and/or modify | |
13 | ;; it under the terms of the GNU General Public License as published by | |
14 | ;; the Free Software Foundation, either version 3 of the License, or | |
15 | ;; (at your option) any later version. | |
16 | ||
17 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | ;; GNU General Public License for more details. | |
21 | ||
22 | ;; You should have received a copy of the GNU General Public License | |
23 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | |
24 | ||
25 | ;;; Commentary: | |
26 | ||
27 | ;;; ob support for evaluating clojure code | |
28 | ||
29 | ;;; Requirements: | |
30 | ||
31 | ;;; A working clojure install. This also implies a working java executable | |
32 | ;;; clojure-mode | |
33 | ;;; slime | |
34 | ;;; swank-clojure | |
35 | ||
36 | ;;; By far, the best way to install these components is by following | |
37 | ;;; the directions as set out by Phil Hagelberg (Technomancy) on the | |
38 | ;;; web page: http://technomancy.us/126 | |
39 | ||
40 | ;;; Code: | |
41 | (require 'ob) | |
42 | (require 'ob-eval) | |
43 | (eval-when-compile (require 'cl)) | |
44 | ||
45 | (declare-function slime-eval-async "ext:slime" (sexp &optional cont package)) | |
46 | (declare-function slime-eval "ext:slime" (sexp &optional package)) | |
47 | (declare-function swank-clojure-concat-paths "ext:slime" (paths)) | |
86fbb8ca CD |
48 | (declare-function slime "ext:slime" (&optional command coding-system)) |
49 | (declare-function slime-output-buffer "ext:slime" (&optional noprompt)) | |
50 | (declare-function slime-filter-buffers "ext:slime" (predicate)) | |
51 | ||
52 | (add-to-list 'org-babel-tangle-lang-exts '("clojure" . "clj")) | |
53 | ||
54 | (defvar org-babel-default-header-args:clojure '()) | |
55 | ||
56 | (defvar org-babel-clojure-wrapper-method | |
57 | " | |
58 | (defn spit | |
59 | [f content] | |
60 | (with-open [#^java.io.PrintWriter w | |
61 | (java.io.PrintWriter. | |
62 | (java.io.BufferedWriter. | |
63 | (java.io.OutputStreamWriter. | |
64 | (java.io.FileOutputStream. | |
65 | (java.io.File. f)))))] | |
66 | (.print w content))) | |
67 | ||
68 | (defn main | |
69 | [] | |
70 | %s) | |
71 | ||
72 | (spit \"%s\" (str (main)))") | |
73 | ;;";; <-- syntax highlighting is messed without this double quote | |
74 | ||
75 | ;;taken mostly from clojure-test-mode.el | |
76 | (defun org-babel-clojure-clojure-slime-eval (string &optional handler) | |
77 | "Evaluate a STRING of clojure code using `slime-eval-async'." | |
78 | (slime-eval-async `(swank:eval-and-grab-output ,string) | |
79 | (or handler #'identity))) | |
80 | ||
81 | (defun org-babel-clojure-slime-eval-sync (string) | |
82 | "Evaluate a STRING of clojure code using `slime-eval'." | |
83 | (slime-eval `(swank:eval-and-grab-output ,string))) | |
84 | ||
85 | ;;taken from swank-clojure.el | |
86 | (defvar swank-clojure-binary) | |
87 | (defvar swank-clojure-classpath) | |
88 | (defvar swank-clojure-java-path) | |
89 | (defvar swank-clojure-extra-vm-args) | |
90 | (defvar swank-clojure-library-paths) | |
91 | (defvar swank-clojure-extra-classpaths) | |
92 | (defun org-babel-clojure-babel-clojure-cmd () | |
93 | "Create the command to start clojure according to current settings." | |
afe98dfa CD |
94 | (or (when swank-clojure-binary |
95 | (if (listp swank-clojure-binary) | |
96 | swank-clojure-binary | |
97 | (list swank-clojure-binary))) | |
98 | (when swank-clojure-classpath | |
99 | (delq | |
100 | nil | |
101 | (append | |
102 | (list swank-clojure-java-path) | |
103 | swank-clojure-extra-vm-args | |
104 | (list | |
105 | (when swank-clojure-library-paths | |
106 | (concat "-Djava.library.path=" | |
107 | (swank-clojure-concat-paths swank-clojure-library-paths))) | |
108 | "-classpath" | |
109 | (swank-clojure-concat-paths | |
110 | (append | |
111 | swank-clojure-classpath | |
112 | swank-clojure-extra-classpaths)) | |
113 | "clojure.main")))) | |
86fbb8ca | 114 | (error "%s" (concat "You must specifiy either a `swank-clojure-binary' " |
afe98dfa | 115 | "or a `swank-clojure-classpath'")))) |
86fbb8ca CD |
116 | |
117 | (defun org-babel-clojure-table-or-string (results) | |
118 | "Convert RESULTS to an elisp value. | |
119 | If RESULTS looks like a table, then convert to an Emacs-lisp | |
120 | table, otherwise return the results as a string." | |
121 | (org-babel-read | |
122 | (if (string-match "^\\[.+\\]$" results) | |
123 | (org-babel-read | |
124 | (concat "'" | |
125 | (replace-regexp-in-string | |
126 | "\\[" "(" (replace-regexp-in-string | |
127 | "\\]" ")" (replace-regexp-in-string | |
128 | ", " " " (replace-regexp-in-string | |
129 | "'" "\"" results)))))) | |
130 | results))) | |
131 | ||
132 | (defun org-babel-clojure-var-to-clojure (var) | |
133 | "Convert an elisp value into a clojure variable. | |
134 | The elisp value VAR is converted into a string of clojure source | |
135 | code specifying a variable of the same value." | |
136 | (if (listp var) | |
137 | (format "'%s" var) | |
138 | (format "%S" var))) | |
139 | ||
140 | (defun org-babel-clojure-build-full-form (body vars) | |
141 | "Construct a clojure let form with VARS as the let variables." | |
142 | (let ((vars-forms | |
143 | (mapconcat ;; define any variables | |
144 | (lambda (pair) | |
145 | (format "%s %s" | |
146 | (car pair) (org-babel-clojure-var-to-clojure (cdr pair)))) | |
147 | vars "\n ")) | |
148 | (body (org-babel-trim body))) | |
149 | (if (> (length vars-forms) 0) | |
150 | (format "(let [%s]\n %s)" vars-forms body) | |
151 | body))) | |
152 | ||
153 | (defun org-babel-prep-session:clojure (session params) | |
154 | "Prepare SESSION according to the header arguments specified in PARAMS." | |
155 | (require 'slime) (require 'swank-clojure) | |
156 | (let* ((session-buf (org-babel-clojure-initiate-session session)) | |
afe98dfa | 157 | (vars (mapcar #'cdr (org-babel-get-header params :var))) |
86fbb8ca CD |
158 | (var-lines (mapcar ;; define any top level session variables |
159 | (lambda (pair) | |
160 | (format "(def %s %s)\n" (car pair) | |
161 | (org-babel-clojure-var-to-clojure (cdr pair)))) | |
162 | vars))) | |
163 | session-buf)) | |
164 | ||
165 | (defun org-babel-load-session:clojure (session body params) | |
166 | "Load BODY into SESSION." | |
167 | (require 'slime) (require 'swank-clojure) | |
168 | (save-window-excursion | |
169 | (let ((buffer (org-babel-prep-session:clojure session params))) | |
170 | (with-current-buffer buffer | |
171 | (goto-char (point-max)) | |
172 | (insert (org-babel-chomp body))) | |
173 | buffer))) | |
174 | ||
175 | (defvar org-babel-clojure-buffers '()) | |
176 | (defvar org-babel-clojure-pending-sessions '()) | |
177 | ||
178 | (defun org-babel-clojure-session-buffer (session) | |
179 | "Return the buffer associated with SESSION." | |
180 | (cdr (assoc session org-babel-clojure-buffers))) | |
181 | ||
182 | (defun org-babel-clojure-initiate-session-by-key (&optional session) | |
183 | "Initiate a clojure session in an inferior-process-buffer. | |
184 | If there is not a current inferior-process-buffer in SESSION | |
185 | then create one. Return the initialized session." | |
186 | (save-window-excursion | |
187 | (let* ((session (if session | |
188 | (if (stringp session) (intern session) | |
189 | session) | |
190 | :default)) | |
191 | (clojure-buffer (org-babel-clojure-session-buffer session))) | |
192 | (unless (and clojure-buffer (buffer-live-p clojure-buffer)) | |
193 | (setq org-babel-clojure-buffers | |
194 | (assq-delete-all session org-babel-clojure-buffers)) | |
195 | (push session org-babel-clojure-pending-sessions) | |
196 | (slime) | |
197 | ;; we are waiting to finish setting up which will be done in | |
198 | ;; org-babel-clojure-session-connected-hook below. | |
199 | (let ((timeout 9)) | |
200 | (while (and (not (org-babel-clojure-session-buffer session)) | |
201 | (< 0 timeout)) | |
202 | (message "Waiting for clojure repl for session: %s ... %i" | |
203 | session timeout) | |
204 | (sit-for 1) | |
205 | (decf timeout))) | |
206 | (setq org-babel-clojure-pending-sessions | |
207 | (remove session org-babel-clojure-pending-sessions)) | |
208 | (unless (org-babel-clojure-session-buffer session) | |
209 | (error "Couldn't create slime clojure process")) | |
210 | (setq clojure-buffer (org-babel-clojure-session-buffer session))) | |
211 | session))) | |
212 | ||
213 | (defun org-babel-clojure-initiate-session (&optional session params) | |
214 | "Return the slime-clojure repl buffer bound to SESSION. | |
215 | Returns nil if \"none\" is specified." | |
216 | (require 'slime) (require 'swank-clojure) | |
217 | (unless (and (stringp session) (string= session "none")) | |
218 | (org-babel-clojure-session-buffer | |
219 | (org-babel-clojure-initiate-session-by-key session)))) | |
220 | ||
221 | (defun org-babel-clojure-session-connected-hook () | |
222 | "Finish binding an org-babel session to a slime-clojure repl." | |
223 | (let ((pending-session (pop org-babel-clojure-pending-sessions))) | |
224 | (when pending-session | |
225 | (save-excursion | |
226 | (switch-to-buffer (slime-output-buffer)) | |
227 | (rename-buffer | |
228 | (if (stringp pending-session) | |
229 | pending-session (symbol-name pending-session))) | |
230 | (org-babel-clojure-bind-session-to-repl-buffer | |
231 | pending-session (slime-output-buffer)))))) | |
232 | ||
233 | (add-hook 'slime-connected-hook 'org-babel-clojure-session-connected-hook) | |
234 | ||
235 | (defun org-babel-clojure-bind-session-to-repl-buffer (session repl-buffer) | |
236 | "Associate SESSION with REPL-BUFFER." | |
237 | (when (stringp session) (setq session (intern session))) | |
238 | (setq org-babel-clojure-buffers | |
239 | (cons (cons session repl-buffer) | |
240 | (assq-delete-all session org-babel-clojure-buffers)))) | |
241 | ||
242 | (defun org-babel-clojure-repl-buffer-pred () | |
243 | "Test whether the current buffer is an active slime-clojure | |
244 | repl buffer." | |
245 | (and (buffer-live-p (current-buffer)) (eq major-mode 'slime-repl-mode))) | |
246 | ||
247 | (defun org-babel-clojure-bind-session-to-repl (session) | |
248 | "Bind SESSION to a clojure repl." | |
249 | (interactive "sEnter session name: ") | |
250 | (let ((repl-bufs (slime-filter-buffers 'org-babel-clojure-repl-buffer-pred))) | |
251 | (unless repl-bufs (error "No existing slime-clojure repl buffers exist")) | |
252 | (let ((repl-buf (read-buffer "Choose slime-clojure repl: " repl-bufs t))) | |
253 | (org-babel-clojure-bind-session-to-repl-buffer session repl-buf)))) | |
254 | ||
255 | (defun org-babel-clojure-evaluate-external-process | |
256 | (buffer body &optional result-type) | |
257 | "Evaluate the body in an external process." | |
258 | (let ((cmd (format "%s -" (mapconcat #'identity | |
259 | (org-babel-clojure-babel-clojure-cmd) | |
260 | " ")))) | |
261 | (case result-type | |
262 | (output (org-babel-eval cmd body)) | |
afe98dfa CD |
263 | (value (let* ((tmp-file (org-babel-temp-file "clojure-"))) |
264 | (org-babel-eval | |
265 | cmd | |
266 | (format | |
267 | org-babel-clojure-wrapper-method | |
268 | body | |
269 | (org-babel-process-file-name tmp-file 'noquote))) | |
86fbb8ca CD |
270 | (org-babel-clojure-table-or-string |
271 | (org-babel-eval-read-file tmp-file))))))) | |
272 | ||
273 | (defun org-babel-clojure-evaluate-session (buffer body &optional result-type) | |
274 | "Evaluate the body in the context of a clojure session." | |
275 | (require 'slime) (require 'swank-clojure) | |
276 | (let ((raw nil) | |
277 | (results nil)) | |
278 | (with-current-buffer buffer | |
279 | (setq raw (org-babel-clojure-slime-eval-sync body)) | |
280 | (setq results (reverse (mapcar #'org-babel-trim raw))) | |
281 | (cond | |
282 | ((equal result-type 'output) | |
283 | (mapconcat #'identity (reverse (cdr results)) "\n")) | |
284 | ((equal result-type 'value) | |
285 | (org-babel-clojure-table-or-string (car results))))))) | |
286 | ||
287 | (defun org-babel-clojure-evaluate (buffer body &optional result-type) | |
288 | "Pass BODY to the Clojure process in BUFFER. | |
289 | If RESULT-TYPE equals 'output then return a list of the outputs | |
290 | of the statements in BODY, if RESULT-TYPE equals 'value then | |
291 | return the value of the last statement in BODY as elisp." | |
292 | (if buffer | |
293 | (org-babel-clojure-evaluate-session buffer body result-type) | |
294 | (org-babel-clojure-evaluate-external-process buffer body result-type))) | |
295 | ||
afe98dfa | 296 | (defun org-babel-expand-body:clojure (body params) |
86fbb8ca CD |
297 | "Expand BODY according to PARAMS, return the expanded body." |
298 | (org-babel-clojure-build-full-form | |
afe98dfa | 299 | body (mapcar #'cdr (org-babel-get-header params :var)))) |
86fbb8ca CD |
300 | |
301 | (defun org-babel-execute:clojure (body params) | |
302 | "Execute a block of Clojure code." | |
303 | (require 'slime) (require 'swank-clojure) | |
afe98dfa | 304 | (let* ((body (org-babel-expand-body:clojure body params)) |
86fbb8ca | 305 | (session (org-babel-clojure-initiate-session |
afe98dfa | 306 | (cdr (assoc :session params))))) |
86fbb8ca | 307 | (org-babel-reassemble-table |
afe98dfa | 308 | (org-babel-clojure-evaluate session body (cdr (assoc :result-type params))) |
86fbb8ca | 309 | (org-babel-pick-name |
afe98dfa | 310 | (cdr (assoc :colname-names params)) (cdr (assoc :colnames params))) |
86fbb8ca | 311 | (org-babel-pick-name |
afe98dfa | 312 | (cdr (assoc :rowname-names params)) (cdr (assoc :rownames params)))))) |
86fbb8ca CD |
313 | |
314 | (provide 'ob-clojure) | |
315 | ||
316 | ;; arch-tag: a43b33f2-653e-46b1-ac56-2805cf05b7d1 | |
317 | ||
318 | ;;; ob-clojure.el ends here |