commit | author | age
|
76bbd0
|
1 |
;;; ob-gnuplot.el --- Babel Functions for Gnuplot -*- lexical-binding: t; -*- |
C |
2 |
|
|
3 |
;; Copyright (C) 2009-2018 Free Software Foundation, Inc. |
|
4 |
|
|
5 |
;; Author: Eric Schulte |
|
6 |
;; Keywords: literate programming, reproducible research |
|
7 |
;; Homepage: https://orgmode.org |
|
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 <https://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/ |
|
37 |
;; |
|
38 |
;; - gnuplot-mode :: http://cars9.uchicago.edu/~ravel/software/gnuplot-mode.html |
|
39 |
|
|
40 |
;;; Code: |
|
41 |
(require 'ob) |
|
42 |
|
|
43 |
(declare-function org-time-string-to-time "org" (s)) |
|
44 |
(declare-function org-combine-plists "org" (&rest plists)) |
|
45 |
(declare-function orgtbl-to-generic "org-table" (table params)) |
|
46 |
(declare-function gnuplot-mode "ext:gnuplot-mode" ()) |
|
47 |
(declare-function gnuplot-send-string-to-gnuplot "ext:gnuplot-mode" (str txt)) |
|
48 |
(declare-function gnuplot-send-buffer-to-gnuplot "ext:gnuplot-mode" ()) |
|
49 |
|
|
50 |
(defvar org-babel-default-header-args:gnuplot |
|
51 |
'((:results . "file") (:exports . "results") (:session . nil)) |
|
52 |
"Default arguments to use when evaluating a gnuplot source block.") |
|
53 |
|
|
54 |
(defvar org-babel-header-args:gnuplot |
|
55 |
'((title . :any) |
|
56 |
(lines . :any) |
|
57 |
(sets . :any) |
|
58 |
(x-labels . :any) |
|
59 |
(y-labels . :any) |
|
60 |
(timefmt . :any) |
|
61 |
(time-ind . :any) |
|
62 |
(missing . :any) |
|
63 |
(term . :any)) |
|
64 |
"Gnuplot specific header args.") |
|
65 |
|
|
66 |
(defvar org-babel-gnuplot-timestamp-fmt nil) ; Dynamically scoped. |
|
67 |
|
|
68 |
(defvar *org-babel-gnuplot-missing* nil) |
|
69 |
|
|
70 |
(defcustom *org-babel-gnuplot-terms* |
|
71 |
'((eps . "postscript eps")) |
|
72 |
"List of file extensions and the associated gnuplot terminal." |
|
73 |
:group 'org-babel |
|
74 |
:type '(repeat (cons (symbol :tag "File extension") |
|
75 |
(string :tag "Gnuplot terminal")))) |
|
76 |
|
|
77 |
(defun org-babel-gnuplot-process-vars (params) |
|
78 |
"Extract variables from PARAMS and process the variables. |
|
79 |
Dumps all vectors into files and returns an association list |
|
80 |
of variable names and the related value to be used in the gnuplot |
|
81 |
code." |
|
82 |
(let ((*org-babel-gnuplot-missing* (cdr (assq :missing params)))) |
|
83 |
(mapcar |
|
84 |
(lambda (pair) |
|
85 |
(cons |
|
86 |
(car pair) ;; variable name |
|
87 |
(let* ((val (cdr pair)) ;; variable value |
|
88 |
(lp (listp val))) |
|
89 |
(if lp |
|
90 |
(org-babel-gnuplot-table-to-data |
|
91 |
(let* ((first (car val)) |
|
92 |
(tablep (or (listp first) (symbolp first)))) |
|
93 |
(if tablep val (mapcar 'list val))) |
|
94 |
(org-babel-temp-file "gnuplot-") params) |
|
95 |
val)))) |
|
96 |
(org-babel--get-vars params)))) |
|
97 |
|
|
98 |
(defun org-babel-expand-body:gnuplot (body params) |
|
99 |
"Expand BODY according to PARAMS, return the expanded body." |
|
100 |
(save-window-excursion |
|
101 |
(let* ((vars (org-babel-gnuplot-process-vars params)) |
|
102 |
(out-file (cdr (assq :file params))) |
|
103 |
(prologue (cdr (assq :prologue params))) |
|
104 |
(epilogue (cdr (assq :epilogue params))) |
|
105 |
(term (or (cdr (assq :term params)) |
|
106 |
(when out-file |
|
107 |
(let ((ext (file-name-extension out-file))) |
|
108 |
(or (cdr (assoc (intern (downcase ext)) |
|
109 |
*org-babel-gnuplot-terms*)) |
|
110 |
ext))))) |
|
111 |
(title (cdr (assq :title params))) |
|
112 |
(lines (cdr (assq :line params))) |
|
113 |
(sets (cdr (assq :set params))) |
|
114 |
(x-labels (cdr (assq :xlabels params))) |
|
115 |
(y-labels (cdr (assq :ylabels params))) |
|
116 |
(timefmt (cdr (assq :timefmt params))) |
|
117 |
(time-ind (or (cdr (assq :timeind params)) |
|
118 |
(when timefmt 1))) |
|
119 |
(directory (and (buffer-file-name) |
|
120 |
(file-name-directory (buffer-file-name)))) |
|
121 |
(add-to-body (lambda (text) (setq body (concat text "\n" body))))) |
|
122 |
;; append header argument settings to body |
|
123 |
(when title (funcall add-to-body (format "set title '%s'" title))) |
|
124 |
(when lines (mapc (lambda (el) (funcall add-to-body el)) lines)) |
|
125 |
(when sets |
|
126 |
(mapc (lambda (el) (funcall add-to-body (format "set %s" el))) sets)) |
|
127 |
(when x-labels |
|
128 |
(funcall add-to-body |
|
129 |
(format "set xtics (%s)" |
|
130 |
(mapconcat (lambda (pair) |
|
131 |
(format "\"%s\" %d" |
|
132 |
(cdr pair) (car pair))) |
|
133 |
x-labels ", ")))) |
|
134 |
(when y-labels |
|
135 |
(funcall add-to-body |
|
136 |
(format "set ytics (%s)" |
|
137 |
(mapconcat (lambda (pair) |
|
138 |
(format "\"%s\" %d" |
|
139 |
(cdr pair) (car pair))) |
|
140 |
y-labels ", ")))) |
|
141 |
(when time-ind |
|
142 |
(funcall add-to-body "set xdata time") |
|
143 |
(funcall add-to-body (concat "set timefmt \"" |
|
144 |
(or timefmt |
|
145 |
"%Y-%m-%d-%H:%M:%S") "\""))) |
|
146 |
(when out-file |
|
147 |
;; set the terminal at the top of the block |
|
148 |
(funcall add-to-body (format "set output \"%s\"" out-file)) |
|
149 |
;; and close the terminal at the bottom of the block |
|
150 |
(setq body (concat body "\nset output\n"))) |
|
151 |
(when term (funcall add-to-body (format "set term %s" term))) |
|
152 |
;; insert variables into code body: this should happen last |
|
153 |
;; placing the variables at the *top* of the code in case their |
|
154 |
;; values are used later |
|
155 |
(funcall add-to-body |
|
156 |
(mapconcat #'identity |
|
157 |
(org-babel-variable-assignments:gnuplot params) |
|
158 |
"\n")) |
|
159 |
;; replace any variable names preceded by '$' with the actual |
|
160 |
;; value of the variable |
|
161 |
(mapc (lambda (pair) |
|
162 |
(setq body (replace-regexp-in-string |
|
163 |
(format "\\$%s" (car pair)) (cdr pair) body))) |
|
164 |
vars) |
|
165 |
(when prologue (funcall add-to-body prologue)) |
|
166 |
(when epilogue (setq body (concat body "\n" epilogue))) |
|
167 |
;; Setting the directory needs to be done first so that |
|
168 |
;; subsequent 'output' directive goes to the right place. |
|
169 |
(when directory (funcall add-to-body (format "cd '%s'" directory)))) |
|
170 |
body)) |
|
171 |
|
|
172 |
(defun org-babel-execute:gnuplot (body params) |
|
173 |
"Execute a block of Gnuplot code. |
|
174 |
This function is called by `org-babel-execute-src-block'." |
|
175 |
(require 'gnuplot) |
|
176 |
(let ((session (cdr (assq :session params))) |
|
177 |
(result-type (cdr (assq :results params))) |
|
178 |
(body (org-babel-expand-body:gnuplot body params)) |
|
179 |
output) |
|
180 |
(save-window-excursion |
|
181 |
;; evaluate the code body with gnuplot |
|
182 |
(if (string= session "none") |
|
183 |
(let ((script-file (org-babel-temp-file "gnuplot-script-"))) |
|
184 |
(with-temp-file script-file |
|
185 |
(insert (concat body "\n"))) |
|
186 |
(message "gnuplot \"%s\"" script-file) |
|
187 |
(setq output |
|
188 |
(shell-command-to-string |
|
189 |
(format |
|
190 |
"gnuplot \"%s\"" |
|
191 |
(org-babel-process-file-name |
|
192 |
script-file |
|
193 |
(if (member system-type '(cygwin windows-nt ms-dos)) |
|
194 |
t nil))))) |
|
195 |
(message "%s" output)) |
|
196 |
(with-temp-buffer |
|
197 |
(insert (concat body "\n")) |
|
198 |
(gnuplot-mode) |
|
199 |
(gnuplot-send-buffer-to-gnuplot))) |
|
200 |
(if (member "output" (split-string result-type)) |
|
201 |
output |
|
202 |
nil)))) ;; signal that output has already been written to file |
|
203 |
|
|
204 |
(defun org-babel-prep-session:gnuplot (session params) |
|
205 |
"Prepare SESSION according to the header arguments in PARAMS." |
|
206 |
(let* ((session (org-babel-gnuplot-initiate-session session)) |
|
207 |
(var-lines (org-babel-variable-assignments:gnuplot params))) |
|
208 |
(message "%S" session) |
|
209 |
(org-babel-comint-in-buffer session |
|
210 |
(dolist (var-line var-lines) |
|
211 |
(insert var-line) |
|
212 |
(comint-send-input nil t) |
|
213 |
(org-babel-comint-wait-for-output session) |
|
214 |
(sit-for .1) |
|
215 |
(goto-char (point-max)))) |
|
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 |
|
|
227 |
(defun org-babel-variable-assignments:gnuplot (params) |
|
228 |
"Return list of gnuplot statements assigning the block's variables." |
|
229 |
(mapcar |
|
230 |
(lambda (pair) (format "%s = \"%s\"" (car pair) (cdr pair))) |
|
231 |
(org-babel-gnuplot-process-vars params))) |
|
232 |
|
|
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." |
|
247 |
(format-time-string org-babel-gnuplot-timestamp-fmt |
|
248 |
(org-time-string-to-time s))) |
|
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) |
|
259 |
(if (zerop (length s)) |
|
260 |
(or *org-babel-gnuplot-missing* s) |
|
261 |
(if (string-match "[ \"]" s) |
|
262 |
(concat "\"" (mapconcat 'identity (split-string s "\"") "\"\"") |
|
263 |
"\"") |
|
264 |
s))))) |
|
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 |
(insert (let ((org-babel-gnuplot-timestamp-fmt |
|
271 |
(or (plist-get params :timefmt) "%Y-%m-%d-%H:%M:%S"))) |
|
272 |
(orgtbl-to-generic |
|
273 |
table |
|
274 |
(org-combine-plists |
|
275 |
'(:sep "\t" :fmt org-babel-gnuplot-quote-tsv-field) |
|
276 |
params))))) |
|
277 |
data-file) |
|
278 |
|
|
279 |
(provide 'ob-gnuplot) |
|
280 |
|
|
281 |
|
|
282 |
|
|
283 |
;;; ob-gnuplot.el ends here |