From 78a3c5d57bdc4458a520d9abcc35d967fa29a3aa Mon Sep 17 00:00:00 2001 From: Joel Boehland Date: Mon, 9 Nov 2009 13:17:34 -0800 Subject: [PATCH] org-babel-clojure: Added clojure language support to org-babel --- contrib/babel/lisp/langs/org-babel-clojure.el | 205 ++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 contrib/babel/lisp/langs/org-babel-clojure.el diff --git a/contrib/babel/lisp/langs/org-babel-clojure.el b/contrib/babel/lisp/langs/org-babel-clojure.el new file mode 100644 index 000000000..183360f26 --- /dev/null +++ b/contrib/babel/lisp/langs/org-babel-clojure.el @@ -0,0 +1,205 @@ +;;; org-babel-clojure.el --- org-babel functions for clojure evaluation + +;; Copyright (C) 2009 Joel Boehland + +;; Author: Joel Boehland +;; Keywords: literate programming, reproducible research +;; Homepage: http://orgmode.org +;; Version: 0.01 + +;;; License: + +;; This program 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, or (at your option) +;; any later version. +;; +;; This program 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; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;;; Org-Babel support for evaluating clojure code + +;;; Requirements: + +;;; A working clojure install. This also implies a working java executable +;;; clojure-mode +;;; slime +;;; swank-clojure + +;;; By far, 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 'org-babel) +(require 'cl) +(require 'slime) +(require 'swank-clojure) + + +(org-babel-add-interpreter "clojure") + +(add-to-list 'org-babel-tangle-langs '("clojure" "clj")) + +(defvar org-babel-clojure-wrapper-method + " +(defn spit + [f content] + (with-open [#^java.io.PrintWriter w + (java.io.PrintWriter. + (java.io.BufferedWriter. + (java.io.OutputStreamWriter. + (java.io.FileOutputStream. + (java.io.File. f)))))] + (.print w content))) + +(defn main + [] + %s) + +(spit \"%s\" (str (main)))") ;;" <-- syntax highlighting is messed without this double quote + +;;taken mostly from clojure-test-mode.el +(defun org-babel-clojure-clojure-slime-eval (string &optional handler) + (slime-eval-async `(swank:eval-and-grab-output ,string) + (or handler #'identity))) + +(defun clojure-slime-eval-sync (string) + (slime-eval `(swank:eval-and-grab-output ,string))) + +;;taken from swank-clojure.el +(defun org-babel-clojure-babel-clojure-cmd () + "Create the command to start clojure according to current settings." + (if (and (not swank-clojure-binary) (not swank-clojure-jar-path)) + (error "You must specifiy either a `swank-clojure-binary' or a `swank-clojure-jar-path'") + (if swank-clojure-binary + (if (listp swank-clojure-binary) + swank-clojure-binary + (list swank-clojure-binary)) + (delete-if + 'null + (append + (list swank-clojure-java-path) + swank-clojure-extra-vm-args + (list + (when swank-clojure-library-paths + (concat "-Djava.library.path=" + (swank-clojure-concat-paths swank-clojure-library-paths))) + "-classpath" + (swank-clojure-concat-paths + (append (list swank-clojure-jar-path + (concat swank-clojure-path "src/main/clojure/")) + swank-clojure-extra-classpaths)) + "clojure.main")))))) + +(defun org-babel-clojure-table-or-string (results) + "If the results look like a table, then convert them into an +Emacs-lisp table, otherwise return the results as a string." + (org-babel-read + (if (string-match "^\\[.+\\]$" results) + (org-babel-read + (replace-regexp-in-string + "\\[" "(" (replace-regexp-in-string + "\\]" ")" (replace-regexp-in-string + ", " " " (replace-regexp-in-string + "'" "\"" results))))) + results))) + +(defun org-babel-clojure-var-to-clojure (var) + "Convert an elisp var into a string of clojure source code +specifying a var of the same value." + (if (listp var) + (format "'%s" var) + (format "%s" var))) + +(defun org-babel-clojure-build-full-form (body vars) + "Construct a clojure let form with vars as the let vars" + (let ((vars-forms (mapconcat ;; define any variables + (lambda (pair) + (format "%s %s" (car pair) (org-babel-clojure-var-to-clojure (cdr pair)))) + vars "\n "))) + (format "(let [%s]\n %s)" vars-forms (org-babel-trim body)))) + +(defun org-babel-prep-session:clojure (session params) + "Prepare SESSION according to the header arguments specified in PARAMS." + + (let* ((session-buf (org-babel-clojure-initiate-session session)) + (vars (org-babel-ref-variables params)) + (var-lines (mapcar ;; define any top level session variables + (lambda (pair) + (format "(defn %s %s)\n" (car pair) (org-babel-clojure-var-to-clojure (cdr pair)))) + vars))) + session-buf)) + +(defun org-babel-clojure-initiate-session (&optional session) + "If there is not a current inferior-process-buffer in SESSION +then create. Return the initialized session." + (unless (string= session "none") + (if (comint-check-proc "*inferior-lisp*") + (get-buffer "*inferior-lisp*") + (let ((session-buffer (save-window-excursion (slime 'clojure) (current-buffer)))) + (sit-for 5) + (if (slime-connected-p) + session-buffer + (error "Couldn't create slime clojure *inferior lisp* process")))))) + +(defun org-babel-clojure-evaluate-external-process (buffer body &optional result-type) + "Evaluate the body in an external process." + (save-window-excursion + (case result-type + (output + (with-temp-buffer + (insert body) + (shell-command-on-region (point-min) (point-max) + (format "%s - " (string-join " " (babel-clojure-cmd))) + 'replace) + (buffer-string))) + (value + (let ((tmp-src-file (make-temp-file "clojure_babel_input_")) + (tmp-results-file (make-temp-file "clojure_babel_results_"))) + (with-temp-file tmp-src-file + (insert (format org-babel-clojure-wrapper-method body tmp-results-file tmp-results-file))) + (shell-command (format "%s %s" (string-join " " (babel-clojure-cmd)) tmp-src-file)) + (org-babel-clojure-table-or-string + (with-temp-buffer (insert-file-contents tmp-results-file) (buffer-string)))))))) + +(defun org-babel-clojure-evaluate-session (buffer body &optional result-type) + "Evaluate the body in the context of a clojure session" + (let ((raw nil) + (results nil)) + (setq raw (org-babel-clojure-slime-eval-sync body)) + (setq results (reverse (mapcar #'org-babel-trim raw))) + (case result-type + (output (mapconcat #'identity (reverse (cdr results)) "\n")) + (value (org-babel-clojure-table-or-string (car results)))))) + +(defun org-babel-clojure-evaluate (buffer body &optional result-type) + "Pass BODY to the Clojure 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 session + (org-babel-clojure-evaluate-session buffer body result-type) + (org-babel-clojure-evaluate-external-process buffer body result-type))) + +(defun org-babel-execute:clojure (body params) + "Execute a block of Clojure code with org-babel. This function +is called by `org-babel-execute-src-block' with the following +variables pre-set using `multiple-value-bind'. + + (session vars result-params result-type)" + + (let* ((body (org-babel-clojure-build-full-form body vars)) + (session (org-babel-clojure-initiate-session session))) + (org-babel-clojure-evaluate session body result-type))) + +(provide 'org-babel-clojure)