commit | author | age
|
76bbd0
|
1 |
;;; ob-ruby.el --- Babel Functions for Ruby -*- 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 ruby source code. |
|
27 |
|
|
28 |
;;; Requirements: |
|
29 |
|
|
30 |
;; - ruby and irb executables :: http://www.ruby-lang.org/ |
|
31 |
;; |
|
32 |
;; - ruby-mode :: Can be installed through ELPA, or from |
|
33 |
;; http://github.com/eschulte/rinari/raw/master/util/ruby-mode.el |
|
34 |
;; |
|
35 |
;; - inf-ruby mode :: Can be installed through ELPA, or from |
|
36 |
;; http://github.com/eschulte/rinari/raw/master/util/inf-ruby.el |
|
37 |
|
|
38 |
;;; Code: |
|
39 |
(require 'ob) |
|
40 |
|
|
41 |
(declare-function org-trim "org" (s &optional keep-lead)) |
|
42 |
(declare-function run-ruby "ext:inf-ruby" (&optional command name)) |
|
43 |
(declare-function xmp "ext:rcodetools" (&optional option)) |
|
44 |
|
|
45 |
(defvar inf-ruby-default-implementation) |
|
46 |
(defvar inf-ruby-implementations) |
|
47 |
|
|
48 |
(defvar org-babel-tangle-lang-exts) |
|
49 |
(add-to-list 'org-babel-tangle-lang-exts '("ruby" . "rb")) |
|
50 |
|
|
51 |
(defvar org-babel-default-header-args:ruby '()) |
|
52 |
|
|
53 |
(defvar org-babel-ruby-command "ruby" |
|
54 |
"Name of command to use for executing ruby code.") |
|
55 |
|
|
56 |
(defcustom org-babel-ruby-hline-to "nil" |
|
57 |
"Replace hlines in incoming tables with this when translating to ruby." |
|
58 |
:group 'org-babel |
|
59 |
:version "24.4" |
|
60 |
:package-version '(Org . "8.0") |
|
61 |
:type 'string) |
|
62 |
|
|
63 |
(defcustom org-babel-ruby-nil-to 'hline |
|
64 |
"Replace nil in ruby tables with this before returning." |
|
65 |
:group 'org-babel |
|
66 |
:version "24.4" |
|
67 |
:package-version '(Org . "8.0") |
|
68 |
:type 'symbol) |
|
69 |
|
|
70 |
(defun org-babel-execute:ruby (body params) |
|
71 |
"Execute a block of Ruby code with Babel. |
|
72 |
This function is called by `org-babel-execute-src-block'." |
|
73 |
(let* ((session (org-babel-ruby-initiate-session |
|
74 |
(cdr (assq :session params)))) |
|
75 |
(result-params (cdr (assq :result-params params))) |
|
76 |
(result-type (cdr (assq :result-type params))) |
|
77 |
(full-body (org-babel-expand-body:generic |
|
78 |
body params (org-babel-variable-assignments:ruby params))) |
|
79 |
(result (if (member "xmp" result-params) |
|
80 |
(with-temp-buffer |
|
81 |
(require 'rcodetools) |
|
82 |
(insert full-body) |
|
83 |
(xmp (cdr (assq :xmp-option params))) |
|
84 |
(buffer-string)) |
|
85 |
(org-babel-ruby-evaluate |
|
86 |
session full-body result-type result-params)))) |
|
87 |
(org-babel-reassemble-table |
|
88 |
(org-babel-result-cond result-params |
|
89 |
result |
|
90 |
(org-babel-ruby-table-or-string result)) |
|
91 |
(org-babel-pick-name (cdr (assq :colname-names params)) |
|
92 |
(cdr (assq :colnames params))) |
|
93 |
(org-babel-pick-name (cdr (assq :rowname-names params)) |
|
94 |
(cdr (assq :rownames params)))))) |
|
95 |
|
|
96 |
(defun org-babel-prep-session:ruby (session params) |
|
97 |
"Prepare SESSION according to the header arguments specified in PARAMS." |
|
98 |
;; (message "params=%S" params) ;; debugging |
|
99 |
(let* ((session (org-babel-ruby-initiate-session session)) |
|
100 |
(var-lines (org-babel-variable-assignments:ruby params))) |
|
101 |
(org-babel-comint-in-buffer session |
|
102 |
(sit-for .5) (goto-char (point-max)) |
|
103 |
(mapc (lambda (var) |
|
104 |
(insert var) (comint-send-input nil t) |
|
105 |
(org-babel-comint-wait-for-output session) |
|
106 |
(sit-for .1) (goto-char (point-max))) var-lines)) |
|
107 |
session)) |
|
108 |
|
|
109 |
(defun org-babel-load-session:ruby (session body params) |
|
110 |
"Load BODY into SESSION." |
|
111 |
(save-window-excursion |
|
112 |
(let ((buffer (org-babel-prep-session:ruby session params))) |
|
113 |
(with-current-buffer buffer |
|
114 |
(goto-char (process-mark (get-buffer-process (current-buffer)))) |
|
115 |
(insert (org-babel-chomp body))) |
|
116 |
buffer))) |
|
117 |
|
|
118 |
;; helper functions |
|
119 |
|
|
120 |
(defun org-babel-variable-assignments:ruby (params) |
|
121 |
"Return list of ruby statements assigning the block's variables." |
|
122 |
(mapcar |
|
123 |
(lambda (pair) |
|
124 |
(format "%s=%s" |
|
125 |
(car pair) |
|
126 |
(org-babel-ruby-var-to-ruby (cdr pair)))) |
|
127 |
(org-babel--get-vars params))) |
|
128 |
|
|
129 |
(defun org-babel-ruby-var-to-ruby (var) |
|
130 |
"Convert VAR into a ruby variable. |
|
131 |
Convert an elisp value into a string of ruby source code |
|
132 |
specifying a variable of the same value." |
|
133 |
(if (listp var) |
|
134 |
(concat "[" (mapconcat #'org-babel-ruby-var-to-ruby var ", ") "]") |
|
135 |
(if (eq var 'hline) |
|
136 |
org-babel-ruby-hline-to |
|
137 |
(format "%S" var)))) |
|
138 |
|
|
139 |
(defun org-babel-ruby-table-or-string (results) |
|
140 |
"Convert RESULTS into an appropriate elisp value. |
|
141 |
If RESULTS look like a table, then convert them into an |
|
142 |
Emacs-lisp table, otherwise return the results as a string." |
|
143 |
(let ((res (org-babel-script-escape results))) |
|
144 |
(if (listp res) |
|
145 |
(mapcar (lambda (el) (if (not el) |
|
146 |
org-babel-ruby-nil-to el)) |
|
147 |
res) |
|
148 |
res))) |
|
149 |
|
|
150 |
(defun org-babel-ruby-initiate-session (&optional session _params) |
|
151 |
"Initiate a ruby session. |
|
152 |
If there is not a current inferior-process-buffer in SESSION |
|
153 |
then create one. Return the initialized session." |
|
154 |
(unless (string= session "none") |
|
155 |
(require 'inf-ruby) |
|
156 |
(let* ((cmd (cdr (assoc inf-ruby-default-implementation |
|
157 |
inf-ruby-implementations))) |
|
158 |
(buffer (get-buffer (format "*%s*" session))) |
|
159 |
(session-buffer (or buffer (save-window-excursion |
|
160 |
(run-ruby cmd session) |
|
161 |
(current-buffer))))) |
|
162 |
(if (org-babel-comint-buffer-livep session-buffer) |
|
163 |
(progn (sit-for .25) session-buffer) |
|
164 |
(sit-for .5) |
|
165 |
(org-babel-ruby-initiate-session session))))) |
|
166 |
|
|
167 |
(defvar org-babel-ruby-eoe-indicator ":org_babel_ruby_eoe" |
|
168 |
"String to indicate that evaluation has completed.") |
|
169 |
(defvar org-babel-ruby-f-write |
|
170 |
"File.open('%s','w'){|f| f.write((_.class == String) ? _ : _.inspect)}") |
|
171 |
(defvar org-babel-ruby-pp-f-write |
|
172 |
"File.open('%s','w'){|f| $stdout = f; pp(results); $stdout = orig_out}") |
|
173 |
(defvar org-babel-ruby-wrapper-method |
|
174 |
" |
|
175 |
def main() |
|
176 |
%s |
|
177 |
end |
|
178 |
results = main() |
|
179 |
File.open('%s', 'w'){ |f| f.write((results.class == String) ? results : results.inspect) } |
|
180 |
") |
|
181 |
(defvar org-babel-ruby-pp-wrapper-method |
|
182 |
" |
|
183 |
require 'pp' |
|
184 |
def main() |
|
185 |
%s |
|
186 |
end |
|
187 |
results = main() |
|
188 |
File.open('%s', 'w') do |f| |
|
189 |
$stdout = f |
|
190 |
pp results |
|
191 |
end |
|
192 |
") |
|
193 |
|
|
194 |
(defun org-babel-ruby-evaluate |
|
195 |
(buffer body &optional result-type result-params) |
|
196 |
"Pass BODY to the Ruby process in BUFFER. |
|
197 |
If RESULT-TYPE equals `output' then return a list of the outputs |
|
198 |
of the statements in BODY, if RESULT-TYPE equals `value' then |
|
199 |
return the value of the last statement in BODY, as elisp." |
|
200 |
(if (not buffer) |
|
201 |
;; external process evaluation |
|
202 |
(pcase result-type |
|
203 |
(`output (org-babel-eval org-babel-ruby-command body)) |
|
204 |
(`value (let ((tmp-file (org-babel-temp-file "ruby-"))) |
|
205 |
(org-babel-eval |
|
206 |
org-babel-ruby-command |
|
207 |
(format (if (member "pp" result-params) |
|
208 |
org-babel-ruby-pp-wrapper-method |
|
209 |
org-babel-ruby-wrapper-method) |
|
210 |
body (org-babel-process-file-name tmp-file 'noquote))) |
|
211 |
(org-babel-eval-read-file tmp-file)))) |
|
212 |
;; comint session evaluation |
|
213 |
(pcase result-type |
|
214 |
(`output |
|
215 |
(let ((eoe-string (format "puts \"%s\"" org-babel-ruby-eoe-indicator))) |
|
216 |
;; Force the session to be ready before the actual session |
|
217 |
;; code is run. There is some problem in comint that will |
|
218 |
;; sometimes show the prompt after the the input has already |
|
219 |
;; been inserted and that throws off the extraction of the |
|
220 |
;; result for Babel. |
|
221 |
(org-babel-comint-with-output |
|
222 |
(buffer org-babel-ruby-eoe-indicator t eoe-string) |
|
223 |
(insert eoe-string) (comint-send-input nil t)) |
|
224 |
;; Now we can start the evaluation. |
|
225 |
(mapconcat |
|
226 |
#'identity |
|
227 |
(butlast |
|
228 |
(split-string |
|
229 |
(mapconcat |
|
230 |
#'org-trim |
|
231 |
(org-babel-comint-with-output |
|
232 |
(buffer org-babel-ruby-eoe-indicator t body) |
|
233 |
(mapc |
|
234 |
(lambda (line) |
|
235 |
(insert (org-babel-chomp line)) (comint-send-input nil t)) |
|
236 |
(list "conf.echo=false;_org_prompt_mode=conf.prompt_mode;conf.prompt_mode=:NULL" |
|
237 |
body |
|
238 |
"conf.prompt_mode=_org_prompt_mode;conf.echo=true" |
|
239 |
eoe-string))) |
|
240 |
"\n") "[\r\n]") 4) "\n"))) |
|
241 |
(`value |
|
242 |
(let* ((tmp-file (org-babel-temp-file "ruby-")) |
|
243 |
(ppp (or (member "code" result-params) |
|
244 |
(member "pp" result-params)))) |
|
245 |
(org-babel-comint-with-output |
|
246 |
(buffer org-babel-ruby-eoe-indicator t body) |
|
247 |
(when ppp (insert "require 'pp';") (comint-send-input nil t)) |
|
248 |
(mapc |
|
249 |
(lambda (line) |
|
250 |
(insert (org-babel-chomp line)) (comint-send-input nil t)) |
|
251 |
(append |
|
252 |
(list body) |
|
253 |
(if (not ppp) |
|
254 |
(list (format org-babel-ruby-f-write |
|
255 |
(org-babel-process-file-name tmp-file 'noquote))) |
|
256 |
(list |
|
257 |
"results=_" "require 'pp'" "orig_out = $stdout" |
|
258 |
(format org-babel-ruby-pp-f-write |
|
259 |
(org-babel-process-file-name tmp-file 'noquote)))) |
|
260 |
(list org-babel-ruby-eoe-indicator))) |
|
261 |
(comint-send-input nil t)) |
|
262 |
(org-babel-eval-read-file tmp-file)))))) |
|
263 |
|
|
264 |
(provide 'ob-ruby) |
|
265 |
|
|
266 |
|
|
267 |
|
|
268 |
;;; ob-ruby.el ends here |