dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

ob-js.el (7434B)


      1 ;;; ob-js.el --- Babel Functions for Javascript      -*- lexical-binding: t; -*-
      2 
      3 ;; Copyright (C) 2010-2023 Free Software Foundation, Inc.
      4 
      5 ;; Author: Eric Schulte
      6 ;; Keywords: literate programming, reproducible research, js
      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 ;; Now working with SBCL for both session and external evaluation.
     27 ;;
     28 ;; This certainly isn't optimally robust, but it seems to be working
     29 ;; for the basic use cases.
     30 
     31 ;;; Requirements:
     32 
     33 ;; - a non-browser javascript engine such as node.js https://nodejs.org/
     34 ;;   or mozrepl https://wiki.github.com/bard/mozrepl/
     35 ;;
     36 ;; - for session based evaluation mozrepl and moz.el are required see
     37 ;;   https://wiki.github.com/bard/mozrepl/emacs-integration for
     38 ;;   configuration instructions
     39 
     40 ;;; Code:
     41 
     42 (require 'org-macs)
     43 (org-assert-version)
     44 
     45 (require 'ob)
     46 
     47 (declare-function run-mozilla "ext:moz" (arg))
     48 (declare-function httpd-start "ext:simple-httpd" ())
     49 (declare-function run-skewer "ext:skewer-mode" ())
     50 (declare-function skewer-repl "ext:skewer-repl" ())
     51 (declare-function indium-run-node "ext:indium-nodejs" (command))
     52 (declare-function indium-eval "ext:indium-interaction" (string &optional callback))
     53 
     54 (defvar org-babel-default-header-args:js '()
     55   "Default header arguments for js code blocks.")
     56 
     57 (defvar org-babel-js-eoe "org-babel-js-eoe"
     58   "String to indicate that evaluation has completed.")
     59 
     60 (defcustom org-babel-js-cmd "node"
     61   "Name of command used to evaluate js blocks."
     62   :group 'org-babel
     63   :version "24.1"
     64   :type '(choice (const "node")
     65 		 (const "mozrepl")
     66 		 (const "skewer-mode")
     67 		 (const "indium")
     68 		 (const "js-comint"))
     69   :safe #'stringp)
     70 
     71 (defvar org-babel-js-function-wrapper
     72   "require('process').stdout.write(require('util').inspect(function(){%s}()));"
     73   "Javascript code to print value of body.")
     74 
     75 (defun org-babel-execute:js (body params)
     76   "Execute a block of Javascript code with org-babel.
     77 This function is called by `org-babel-execute-src-block'."
     78   (let* ((org-babel-js-cmd (or (cdr (assq :cmd params)) org-babel-js-cmd))
     79 	 (session (cdr (assq :session params)))
     80          (result-type (cdr (assq :result-type params)))
     81          (full-body (org-babel-expand-body:generic
     82 		     body params (org-babel-variable-assignments:js params)))
     83 	 (result (cond
     84 		  ;; no session specified, external evaluation
     85 		  ((string= session "none")
     86 		   (let ((script-file (org-babel-temp-file "js-script-")))
     87 		     (with-temp-file script-file
     88 		       (insert
     89 			;; return the value or the output
     90 			(if (string= result-type "value")
     91 			    (format org-babel-js-function-wrapper full-body)
     92 			  full-body)))
     93 		     (org-babel-eval
     94 		      (format "%s %s" org-babel-js-cmd
     95 			      (org-babel-process-file-name script-file)) "")))
     96 		  ;; Indium Node REPL.  Separate case because Indium
     97 		  ;; REPL is not inherited from Comint mode.
     98 		  ((string= session "*JS REPL*")
     99 		   (require 'indium-repl)
    100 		   (unless (get-buffer session)
    101 		     (indium-run-node org-babel-js-cmd))
    102 		   (indium-eval full-body))
    103 		  ;; session evaluation
    104 		  (t
    105 		   (let ((session (org-babel-prep-session:js
    106 				   (cdr (assq :session params)) params)))
    107 		     (nth 1
    108 			  (org-babel-comint-with-output
    109 			      (session (format "%S" org-babel-js-eoe) t body)
    110 			    (dolist (code (list body (format "%S" org-babel-js-eoe)))
    111 			      (insert (org-babel-chomp code))
    112 			      (comint-send-input nil t)))))))))
    113     (org-babel-result-cond (cdr (assq :result-params params))
    114       result (org-babel-js-read result))))
    115 
    116 (defun org-babel-js-read (results)
    117   "Convert RESULTS into an appropriate elisp value.
    118 If RESULTS look like a table, then convert them into an
    119 Emacs-lisp table, otherwise return the results as a string."
    120   (org-babel-read
    121    (if (and (stringp results)
    122 	    (string-prefix-p "[" results)
    123 	    (string-suffix-p "]" results))
    124        (org-babel-read
    125         (concat "'"
    126                 (replace-regexp-in-string
    127                  "\\[" "(" (replace-regexp-in-string
    128                             "\\]" ")" (replace-regexp-in-string
    129                                        ",[[:space:]]" " "
    130 				       (replace-regexp-in-string
    131 					"'" "\"" results))))))
    132      results)))
    133 
    134 (defun org-babel-js-var-to-js (var)
    135   "Convert VAR into a js variable.
    136 Convert an elisp value into a string of js source code
    137 specifying a variable of the same value."
    138   (if (listp var)
    139       (concat "[" (mapconcat #'org-babel-js-var-to-js var ", ") "]")
    140     (replace-regexp-in-string "\n" "\\\\n" (format "%S" var))))
    141 
    142 (defun org-babel-prep-session:js (session params)
    143   "Prepare SESSION according to the header arguments specified in PARAMS."
    144   (let* ((session (org-babel-js-initiate-session session))
    145 	 (var-lines (org-babel-variable-assignments:js params)))
    146     (when session
    147       (org-babel-comint-in-buffer session
    148 	(goto-char (point-max))
    149 	(dolist (var var-lines)
    150 	  (insert var)
    151 	  (comint-send-input nil t)
    152 	  (org-babel-comint-wait-for-output session)
    153 	  (sit-for .1)
    154 	  (goto-char (point-max)))))
    155     session))
    156 
    157 (defun org-babel-variable-assignments:js (params)
    158   "Return list of Javascript statements assigning the block's variables."
    159   (mapcar
    160    (lambda (pair) (format "var %s=%s;"
    161 			  (car pair) (org-babel-js-var-to-js (cdr pair))))
    162    (org-babel--get-vars params)))
    163 
    164 (defun org-babel-js-initiate-session (&optional session _params)
    165   "If there is not a current inferior-process-buffer in `SESSION' then create.
    166 Return the initialized session."
    167   (cond
    168    ((string= session "none")
    169     (warn "Session evaluation of ob-js is not supported"))
    170    ((string= "*skewer-repl*" session)
    171     (require 'skewer-repl)
    172     (let ((session-buffer (get-buffer "*skewer-repl*")))
    173       (if (and session-buffer
    174 	       (org-babel-comint-buffer-livep (get-buffer session-buffer))
    175 	       (comint-check-proc session-buffer))
    176 	  session-buffer
    177 	;; start skewer REPL.
    178 	(httpd-start)
    179 	(run-skewer)
    180 	(skewer-repl)
    181 	session-buffer)))
    182    ((string= "*Javascript REPL*" session)
    183     (require 'js-comint)
    184     (let ((session-buffer "*Javascript REPL*"))
    185       (if (and (org-babel-comint-buffer-livep (get-buffer session-buffer))
    186 	       (comint-check-proc session-buffer))
    187 	  session-buffer
    188 	(call-interactively 'run-js)
    189 	(sit-for .5)
    190 	session-buffer)))
    191    ((string= "mozrepl" org-babel-js-cmd)
    192     (require 'moz)
    193     (let ((session-buffer (save-window-excursion
    194 			    (run-mozilla nil)
    195 			    (rename-buffer session)
    196 			    (current-buffer))))
    197       (if (org-babel-comint-buffer-livep session-buffer)
    198 	  (progn (sit-for .25) session-buffer)
    199 	(sit-for .5)
    200 	(org-babel-js-initiate-session session))))
    201    ((string= "node" org-babel-js-cmd )
    202     (error "Session evaluation with node.js is not supported"))
    203    (t
    204     (error "Sessions are only supported with mozrepl add \":cmd mozrepl\""))))
    205 
    206 (provide 'ob-js)
    207 
    208 ;;; ob-js.el ends here