Commit | Line | Data |
---|---|---|
86fbb8ca CD |
1 | ;;; ob-gnuplot.el --- org-babel functions for gnuplot evaluation |
2 | ||
ba318903 | 3 | ;; Copyright (C) 2009-2014 Free Software Foundation, Inc. |
86fbb8ca CD |
4 | |
5 | ;; Author: Eric Schulte | |
6 | ;; Keywords: literate programming, reproducible research | |
7 | ;; Homepage: http://orgmode.org | |
86fbb8ca CD |
8 | |
9 | ;; This file is part of GNU Emacs. | |
10 | ||
11 | ;; GNU Emacs is free software: you can redistribute it and/or modify | |
12 | ;; it under the terms of the GNU General Public License as published by | |
13 | ;; the Free Software Foundation, either version 3 of the License, or | |
14 | ;; (at your option) any later version. | |
15 | ||
16 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 | ;; GNU General Public License for more details. | |
20 | ||
21 | ;; You should have received a copy of the GNU General Public License | |
22 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | |
23 | ||
24 | ;;; Commentary: | |
25 | ||
26 | ;; Org-Babel support for evaluating gnuplot source code. | |
27 | ;; | |
28 | ;; This differs from most standard languages in that | |
29 | ;; | |
30 | ;; 1) we are generally only going to return results of type "file" | |
31 | ;; | |
32 | ;; 2) we are adding the "file" and "cmdline" header arguments | |
33 | ||
34 | ;;; Requirements: | |
35 | ||
36 | ;; - gnuplot :: http://www.gnuplot.info/ | |
14e1337f | 37 | ;; |
86fbb8ca CD |
38 | ;; - gnuplot-mode :: http://cars9.uchicago.edu/~ravel/software/gnuplot-mode.html |
39 | ||
40 | ;;; Code: | |
41 | (require 'ob) | |
86fbb8ca CD |
42 | (eval-when-compile (require 'cl)) |
43 | ||
44 | (declare-function org-time-string-to-time "org" (s)) | |
45 | (declare-function org-combine-plists "org" (&rest plists)) | |
46 | (declare-function orgtbl-to-generic "org-table" (table params)) | |
47 | (declare-function gnuplot-mode "ext:gnuplot-mode" ()) | |
48 | (declare-function gnuplot-send-string-to-gnuplot "ext:gnuplot-mode" (str txt)) | |
49 | (declare-function gnuplot-send-buffer-to-gnuplot "ext:gnuplot-mode" ()) | |
50 | ||
51 | (defvar org-babel-default-header-args:gnuplot | |
52 | '((:results . "file") (:exports . "results") (:session . nil)) | |
53 | "Default arguments to use when evaluating a gnuplot source block.") | |
54 | ||
271672fa BG |
55 | (defvar org-babel-header-args:gnuplot |
56 | '((title . :any) | |
57 | (lines . :any) | |
58 | (sets . :any) | |
59 | (x-labels . :any) | |
60 | (y-labels . :any) | |
61 | (timefmt . :any) | |
62 | (time-ind . :any) | |
63 | (missing . :any) | |
64 | (term . :any)) | |
65 | "Gnuplot specific header args.") | |
66 | ||
86fbb8ca CD |
67 | (defvar org-babel-gnuplot-timestamp-fmt nil) |
68 | ||
271672fa BG |
69 | (defvar *org-babel-gnuplot-missing* nil) |
70 | ||
71 | (defcustom *org-babel-gnuplot-terms* | |
72 | '((eps . "postscript eps")) | |
73 | "List of file extensions and the associated gnuplot terminal." | |
74 | :group 'org-babel | |
75 | :type '(repeat (cons (symbol :tag "File extension") | |
76 | (string :tag "Gnuplot terminal")))) | |
77 | ||
86fbb8ca CD |
78 | (defun org-babel-gnuplot-process-vars (params) |
79 | "Extract variables from PARAMS and process the variables. | |
80 | Dumps all vectors into files and returns an association list | |
81 | of variable names and the related value to be used in the gnuplot | |
82 | code." | |
271672fa BG |
83 | (let ((*org-babel-gnuplot-missing* (cdr (assoc :missing params)))) |
84 | (mapcar | |
85 | (lambda (pair) | |
86 | (cons | |
87 | (car pair) ;; variable name | |
30cb51f1 BG |
88 | (let* ((val (cdr pair)) ;; variable value |
89 | (lp (listp val))) | |
90 | (if lp | |
91 | (org-babel-gnuplot-table-to-data | |
92 | (let* ((first (car val)) | |
93 | (tablep (or (listp first) (symbolp first)))) | |
94 | (if tablep val (mapcar 'list val))) | |
95 | (org-babel-temp-file "gnuplot-") params) | |
96 | val)))) | |
271672fa | 97 | (mapcar #'cdr (org-babel-get-header params :var))))) |
86fbb8ca | 98 | |
afe98dfa | 99 | (defun org-babel-expand-body:gnuplot (body params) |
86fbb8ca CD |
100 | "Expand BODY according to PARAMS, return the expanded body." |
101 | (save-window-excursion | |
102 | (let* ((vars (org-babel-gnuplot-process-vars params)) | |
103 | (out-file (cdr (assoc :file params))) | |
271672fa BG |
104 | (prologue (cdr (assoc :prologue params))) |
105 | (epilogue (cdr (assoc :epilogue params))) | |
106 | (term (or (cdr (assoc :term params)) | |
107 | (when out-file | |
108 | (let ((ext (file-name-extension out-file))) | |
109 | (or (cdr (assoc (intern (downcase ext)) | |
110 | *org-babel-gnuplot-terms*)) | |
111 | ext))))) | |
86fbb8ca | 112 | (cmdline (cdr (assoc :cmdline params))) |
271672fa BG |
113 | (title (cdr (assoc :title params))) |
114 | (lines (cdr (assoc :line params))) | |
115 | (sets (cdr (assoc :set params))) | |
116 | (x-labels (cdr (assoc :xlabels params))) | |
117 | (y-labels (cdr (assoc :ylabels params))) | |
118 | (timefmt (cdr (assoc :timefmt params))) | |
119 | (time-ind (or (cdr (assoc :timeind params)) | |
86fbb8ca | 120 | (when timefmt 1))) |
271672fa | 121 | (missing (cdr (assoc :missing params))) |
8223b1d2 | 122 | (add-to-body (lambda (text) (setq body (concat text "\n" body)))) |
86fbb8ca | 123 | output) |
8223b1d2 | 124 | ;; append header argument settings to body |
271672fa BG |
125 | (when title (funcall add-to-body (format "set title '%s'" title))) |
126 | (when lines (mapc (lambda (el) (funcall add-to-body el)) lines)) | |
127 | (when missing | |
128 | (funcall add-to-body (format "set datafile missing '%s'" missing))) | |
8223b1d2 BG |
129 | (when sets |
130 | (mapc (lambda (el) (funcall add-to-body (format "set %s" el))) sets)) | |
131 | (when x-labels | |
132 | (funcall add-to-body | |
133 | (format "set xtics (%s)" | |
134 | (mapconcat (lambda (pair) | |
271672fa BG |
135 | (format "\"%s\" %d" |
136 | (cdr pair) (car pair))) | |
8223b1d2 BG |
137 | x-labels ", ")))) |
138 | (when y-labels | |
139 | (funcall add-to-body | |
140 | (format "set ytics (%s)" | |
141 | (mapconcat (lambda (pair) | |
271672fa BG |
142 | (format "\"%s\" %d" |
143 | (cdr pair) (car pair))) | |
8223b1d2 BG |
144 | y-labels ", ")))) |
145 | (when time-ind | |
146 | (funcall add-to-body "set xdata time") | |
147 | (funcall add-to-body (concat "set timefmt \"" | |
148 | (or timefmt | |
149 | "%Y-%m-%d-%H:%M:%S") "\""))) | |
271672fa BG |
150 | (when out-file |
151 | ;; set the terminal at the top of the block | |
152 | (funcall add-to-body (format "set output \"%s\"" out-file)) | |
153 | ;; and close the terminal at the bottom of the block | |
154 | (setq body (concat body "\nset output\n"))) | |
8223b1d2 BG |
155 | (when term (funcall add-to-body (format "set term %s" term))) |
156 | ;; insert variables into code body: this should happen last | |
157 | ;; placing the variables at the *top* of the code in case their | |
158 | ;; values are used later | |
271672fa BG |
159 | (funcall add-to-body |
160 | (mapconcat #'identity | |
161 | (org-babel-variable-assignments:gnuplot params) | |
162 | "\n")) | |
8223b1d2 BG |
163 | ;; replace any variable names preceded by '$' with the actual |
164 | ;; value of the variable | |
165 | (mapc (lambda (pair) | |
166 | (setq body (replace-regexp-in-string | |
167 | (format "\\$%s" (car pair)) (cdr pair) body))) | |
271672fa BG |
168 | vars) |
169 | (when prologue (funcall add-to-body prologue)) | |
170 | (when epilogue (setq body (concat body "\n" epilogue)))) | |
8223b1d2 | 171 | body)) |
86fbb8ca CD |
172 | |
173 | (defun org-babel-execute:gnuplot (body params) | |
174 | "Execute a block of Gnuplot code. | |
175 | This function is called by `org-babel-execute-src-block'." | |
176 | (require 'gnuplot) | |
177 | (let ((session (cdr (assoc :session params))) | |
178 | (result-type (cdr (assoc :results params))) | |
179 | (out-file (cdr (assoc :file params))) | |
180 | (body (org-babel-expand-body:gnuplot body params)) | |
181 | output) | |
182 | (save-window-excursion | |
183 | ;; evaluate the code body with gnuplot | |
184 | (if (string= session "none") | |
afe98dfa | 185 | (let ((script-file (org-babel-temp-file "gnuplot-script-"))) |
86fbb8ca CD |
186 | (with-temp-file script-file |
187 | (insert (concat body "\n"))) | |
188 | (message "gnuplot \"%s\"" script-file) | |
189 | (setq output | |
afe98dfa CD |
190 | (shell-command-to-string |
191 | (format | |
192 | "gnuplot \"%s\"" | |
e66ba1df BG |
193 | (org-babel-process-file-name |
194 | script-file | |
195 | (if (member system-type '(cygwin windows-nt ms-dos)) | |
196 | t nil))))) | |
86fbb8ca CD |
197 | (message output)) |
198 | (with-temp-buffer | |
199 | (insert (concat body "\n")) | |
200 | (gnuplot-mode) | |
201 | (gnuplot-send-buffer-to-gnuplot))) | |
202 | (if (member "output" (split-string result-type)) | |
203 | output | |
3ab2c837 | 204 | nil)))) ;; signal that output has already been written to file |
86fbb8ca CD |
205 | |
206 | (defun org-babel-prep-session:gnuplot (session params) | |
207 | "Prepare SESSION according to the header arguments in PARAMS." | |
208 | (let* ((session (org-babel-gnuplot-initiate-session session)) | |
afe98dfa | 209 | (var-lines (org-babel-variable-assignments:gnuplot params))) |
86fbb8ca CD |
210 | (message "%S" session) |
211 | (org-babel-comint-in-buffer session | |
212 | (mapc (lambda (var-line) | |
213 | (insert var-line) (comint-send-input nil t) | |
214 | (org-babel-comint-wait-for-output session) | |
215 | (sit-for .1) (goto-char (point-max))) var-lines)) | |
216 | session)) | |
217 | ||
218 | (defun org-babel-load-session:gnuplot (session body params) | |
219 | "Load BODY into SESSION." | |
220 | (save-window-excursion | |
221 | (let ((buffer (org-babel-prep-session:gnuplot session params))) | |
222 | (with-current-buffer buffer | |
223 | (goto-char (process-mark (get-buffer-process (current-buffer)))) | |
224 | (insert (org-babel-chomp body))) | |
225 | buffer))) | |
226 | ||
afe98dfa | 227 | (defun org-babel-variable-assignments:gnuplot (params) |
8223b1d2 | 228 | "Return list of gnuplot statements assigning the block's variables." |
afe98dfa CD |
229 | (mapcar |
230 | (lambda (pair) (format "%s = \"%s\"" (car pair) (cdr pair))) | |
231 | (org-babel-gnuplot-process-vars params))) | |
232 | ||
86fbb8ca CD |
233 | (defvar gnuplot-buffer) |
234 | (defun org-babel-gnuplot-initiate-session (&optional session params) | |
235 | "Initiate a gnuplot session. | |
236 | If there is not a current inferior-process-buffer in SESSION | |
237 | then create one. Return the initialized session. The current | |
238 | `gnuplot-mode' doesn't provide support for multiple sessions." | |
239 | (require 'gnuplot) | |
240 | (unless (string= session "none") | |
241 | (save-window-excursion | |
242 | (gnuplot-send-string-to-gnuplot "" "line") | |
243 | gnuplot-buffer))) | |
244 | ||
245 | (defun org-babel-gnuplot-quote-timestamp-field (s) | |
246 | "Convert S from timestamp to Unix time and export to gnuplot." | |
271672fa BG |
247 | (format-time-string org-babel-gnuplot-timestamp-fmt |
248 | (org-time-string-to-time s))) | |
86fbb8ca CD |
249 | |
250 | (defvar org-table-number-regexp) | |
251 | (defvar org-ts-regexp3) | |
252 | (defun org-babel-gnuplot-quote-tsv-field (s) | |
253 | "Quote S for export to gnuplot." | |
254 | (unless (stringp s) | |
255 | (setq s (format "%s" s))) | |
256 | (if (string-match org-table-number-regexp s) s | |
257 | (if (string-match org-ts-regexp3 s) | |
258 | (org-babel-gnuplot-quote-timestamp-field s) | |
271672fa BG |
259 | (if (zerop (length s)) |
260 | (or *org-babel-gnuplot-missing* s) | |
3c8b09ca | 261 | (if (string-match "[ \"]" s) |
271672fa BG |
262 | (concat "\"" (mapconcat 'identity (split-string s "\"") "\"\"") |
263 | "\"") | |
264 | s))))) | |
86fbb8ca CD |
265 | |
266 | (defun org-babel-gnuplot-table-to-data (table data-file params) | |
267 | "Export TABLE to DATA-FILE in a format readable by gnuplot. | |
268 | Pass PARAMS through to `orgtbl-to-generic' when exporting TABLE." | |
269 | (with-temp-file data-file | |
270 | (make-local-variable 'org-babel-gnuplot-timestamp-fmt) | |
271 | (setq org-babel-gnuplot-timestamp-fmt (or | |
272 | (plist-get params :timefmt) | |
273 | "%Y-%m-%d-%H:%M:%S")) | |
274 | (insert (orgtbl-to-generic | |
275 | table | |
276 | (org-combine-plists | |
277 | '(:sep "\t" :fmt org-babel-gnuplot-quote-tsv-field) | |
278 | params)))) | |
279 | data-file) | |
280 | ||
281 | (provide 'ob-gnuplot) | |
282 | ||
5b409b39 | 283 | |
86fbb8ca CD |
284 | |
285 | ;;; ob-gnuplot.el ends here |