config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

ob-ruby.el (10599B)


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