;;; ob-processing.el --- org-babel functions for evaluation of processing
;; Copyright (C) 2009-2015 Free Software Foundation, Inc.
;; Author: Jarmo Hurri (adapted from ob-asymptote.el written by Eric Schulte)
;; Keywords: literate programming, reproducible research
;; Homepage: http://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:
;; Org-Babel support for evaluating processing source code.
;;
;; This differs from most standard languages in that
;;
;; 1) there is no such thing as a "session" in processing
;;
;; 2) results can only be exported as html; in this case, the
;; processing code is embedded via a file into a javascript block
;; using the processing.js module; the script then draws the
;; resulting output when the web page is viewed in a browser
;;
;; 3) when not exporting html, evaluation of processing code results
;; in interactive viewing of the results via Processing 2.0 Emacs
;; mode; note that the user is responsible for making sure that
;; processing.js is available on the website
;;; Requirements:
;; - processing2-emacs mode :: https://github.com/ptrv/processing2-emacs
;; - Processing.js module :: http://processingjs.org/
;;; Code:
(require 'ob)
(require 'ox)
(eval-when-compile (require 'cl))
;; declaration needed because requiring ob does not define the variable
(eval-when-compile (defvar org-babel-temporary-directory))
(defvar org-babel-tangle-lang-exts)
(add-to-list 'org-babel-tangle-lang-exts '("processing" . "pde"))
;; default header tags depend on whether exporting html or not; if not
;; exporting html, then no results are produced; otherwise results are
;; html
(defvar org-babel-default-header-args:processing
'((:results . (or (and org-export-current-backend "html") "none"))
(:exports . "results"))
"Default arguments when evaluating a Processing source block.")
(defvar org-babel-processing-processing-js-filename
"processing.js"
"Filename of the processing.js file.")
;; a running index for producing unique ids for processing sketches
(defvar org-babel-processing-sketch-number 0)
(add-hook 'org-export-before-processing-hook
(lambda (backend) (setq org-babel-processing-sketch-number 0)))
;; declaration of needed function from processing mode in case
;; processing-mode is not available
(if (null (require 'processing-mode nil :noerror))
(declare-function processing-sketch-run "processing-mode.el" nil))
(defun org-babel-execute:processing (body params)
"Execute a block of Processing code.
This function is called by `org-babel-execute-src-block'."
(let ((sketch-code
(org-babel-expand-body:generic
body
params
(org-babel-variable-assignments:processing params))))
(if (and (not (null org-babel-exp-reference-buffer))
(string= org-export-current-backend "html"))
;; results are html if exporting html
(let ((sketch-canvas-id
(concat "org-processing-canvas-"
(number-to-string org-babel-processing-sketch-number))))
(setq org-babel-processing-sketch-number
(1+ org-babel-processing-sketch-number))
(concat "\n "))
;; if not exporting html, show sketch in external viewer
;; note: sketch filename can not contain a hyphen, since it has to be
;; a valid java class name - for this reason make-temp-file is repeated
;; until no hyphen is in the name; also sketch dir name must be the same
;; as the basename of the sketch file
(let* ((temporary-file-directory org-babel-temporary-directory)
(sketch-dir
(let (sketch-dir-candidate)
(while
(progn
(setq sketch-dir-candidate
(make-temp-file "processing" t))
(if (string-match
"-"
(file-name-nondirectory sketch-dir-candidate))
(progn
(delete-directory sketch-dir-candidate)
t)
nil)))
sketch-dir-candidate))
(sketch-filename
(concat sketch-dir
"/"
(file-name-nondirectory sketch-dir)
".pde")))
(with-temp-file sketch-filename (insert sketch-code))
(find-file sketch-filename)
(processing-sketch-run)
(kill-buffer)
nil)))) ;; signal no output in this case
(defun org-babel-prep-session:processing (session params)
"Return an error if the :session header argument is set.
Processing does not support sessions"
(error "Processing does not support sessions"))
(defun org-babel-variable-assignments:processing (params)
"Return list of processing statements assigning the block's variables."
(mapcar #'org-babel-processing-var-to-processing
(mapcar #'cdr (org-babel-get-header params :var))))
(defun org-babel-processing-var-to-processing (pair)
"Convert an elisp value into a Processing variable.
The elisp value PAIR is converted into Processing code specifying
a variable of the same value."
(let ((var (car pair))
(val (let ((v (cdr pair)))
(if (symbolp v) (symbol-name v) v))))
(cond
((integerp val)
(format "int %S=%S;" var val))
((floatp val)
(format "float %S=%S;" var val))
((stringp val)
(format "String %S=\"%s\";" var val))
((and (listp val) (not (listp (car val))))
(let* ((type (org-babel-processing-define-type val))
(fmt (if (eq 'String type) "\"%s\"" "%s"))
(vect (mapconcat (lambda (e) (format fmt e)) val ", ")))
(format "%s[] %S={%s};" type var vect)))
((listp val)
(let* ((type (org-babel-processing-define-type val))
(fmt (if (eq 'String type) "\"%s\"" "%s"))
(array (mapconcat (lambda (row)
(concat "{"
(mapconcat (lambda (e) (format fmt e))
row ", ")
"}"))
val ",")))
(format "%S[][] %S={%s};" type var array))))))
(defun org-babel-processing-define-type (data)
"Determine type of DATA.
DATA is a list. Return type as a symbol.
The type is `String' if any element in DATA is
a string. Otherwise, it is either `float', if some elements are
floats, or `int'."
(let* ((type 'int)
find-type ; for byte-compiler
(find-type
(function
(lambda (row)
(catch 'exit
(mapc (lambda (el)
(cond ((listp el) (funcall find-type el))
((stringp el) (throw 'exit (setq type 'String)))
((floatp el) (setq type 'float))))
row))))))
(funcall find-type data) type))
(provide 'ob-processing)
;;; ob-processing.el ends here