config

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

ob-js.el (7834B)


      1 ;;; ob-js.el --- Babel Functions for Javascript      -*- lexical-binding: t; -*-
      2 
      3 ;; Copyright (C) 2010-2024 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   ;; Note that newline after %s - it makes sure that closing
     73   ;; parenthesis are not shadowed if the last line of the body is a
     74   ;; line comment.
     75   "require('process').stdout.write(require('util').inspect(function(){%s\n}()));"
     76   "Javascript code to print value of body.")
     77 
     78 (defun org-babel-execute:js (body params)
     79   "Execute Javascript BODY according to PARAMS.
     80 This function is called by `org-babel-execute-src-block'."
     81   (let* ((org-babel-js-cmd (or (cdr (assq :cmd params)) org-babel-js-cmd))
     82 	 (session (cdr (assq :session params)))
     83          (result-type (cdr (assq :result-type params)))
     84          (full-body (org-babel-expand-body:generic
     85 		     body params (org-babel-variable-assignments:js params)))
     86 	 (result (cond
     87 		  ;; no session specified, external evaluation
     88 		  ((string= session "none")
     89 		   (let ((script-file (org-babel-temp-file "js-script-")))
     90 		     (with-temp-file script-file
     91 		       (insert
     92 			;; return the value or the output
     93 			(if (string= result-type "value")
     94 			    (format org-babel-js-function-wrapper full-body)
     95 			  full-body)))
     96 		     (org-babel-eval
     97 		      (format "%s %s" org-babel-js-cmd
     98 			      (org-babel-process-file-name script-file)) "")))
     99 		  ;; Indium Node REPL.  Separate case because Indium
    100 		  ;; REPL is not inherited from Comint mode.
    101 		  ((string= session "*JS REPL*")
    102                    (org-require-package 'indium-repl "indium")
    103 		   (unless (get-buffer session)
    104 		     (indium-run-node org-babel-js-cmd))
    105 		   (indium-eval full-body))
    106 		  ;; session evaluation
    107 		  (t
    108 		   (let ((session (org-babel-prep-session:js
    109 				   (cdr (assq :session params)) params)))
    110 		     (nth 1
    111 			  (org-babel-comint-with-output
    112 			      (session (format "%S" org-babel-js-eoe) t body)
    113 			    (dolist (code (list body (format "%S" org-babel-js-eoe)))
    114 			      (insert (org-babel-chomp code))
    115 			      (comint-send-input nil t)))))))))
    116     (org-babel-result-cond (cdr (assq :result-params params))
    117       result (org-babel-js-read result))))
    118 
    119 (defun org-babel-js-read (results)
    120   "Convert RESULTS into an appropriate elisp value.
    121 If RESULTS look like a table, then convert them into an
    122 Emacs-lisp table, otherwise return the results as a string."
    123   (org-babel-read
    124    (if (and (stringp results)
    125 	    (string-prefix-p "[" results)
    126 	    (string-suffix-p "]" results))
    127        (org-babel-read
    128         (concat "'"
    129                 (replace-regexp-in-string
    130                  "\\[" "(" (replace-regexp-in-string
    131                             "\\]" ")" (replace-regexp-in-string
    132                                        ",[[:space:]]" " "
    133 				       (replace-regexp-in-string
    134 					"'" "\"" results))))))
    135      results)))
    136 
    137 (defun org-babel-js-var-to-js (var)
    138   "Convert VAR into a js variable.
    139 Convert an elisp value into a string of js source code
    140 specifying a variable of the same value."
    141   (if (listp var)
    142       (concat "[" (mapconcat #'org-babel-js-var-to-js var ", ") "]")
    143     (replace-regexp-in-string "\n" "\\\\n" (format "%S" var))))
    144 
    145 (defun org-babel-prep-session:js (session params)
    146   "Prepare SESSION according to the header arguments specified in PARAMS."
    147   (let* ((session (org-babel-js-initiate-session session))
    148 	 (var-lines (org-babel-variable-assignments:js params)))
    149     (when session
    150       (org-babel-comint-in-buffer session
    151 	(goto-char (point-max))
    152 	(dolist (var var-lines)
    153 	  (insert var)
    154 	  (comint-send-input nil t)
    155 	  (org-babel-comint-wait-for-output session)
    156 	  (sit-for .1)
    157 	  (goto-char (point-max)))))
    158     session))
    159 
    160 (defun org-babel-variable-assignments:js (params)
    161   "Return list of Javascript statements assigning the block's variables.
    162 The variables are defined in PARAMS."
    163   (mapcar
    164    (lambda (pair) (format "var %s=%s;"
    165 			  (car pair) (org-babel-js-var-to-js (cdr pair))))
    166    (org-babel--get-vars params)))
    167 
    168 (defun org-babel-js-initiate-session (&optional session _params)
    169   "If there is not a current inferior-process-buffer in `SESSION' then create.
    170 Return the initialized session."
    171   (cond
    172    ((string= session "none")
    173     (warn "Session evaluation of ob-js is not supported"))
    174    ((string= "*skewer-repl*" session)
    175     (org-require-package 'skewer-repl "skewer-mode")
    176     (let ((session-buffer (get-buffer "*skewer-repl*")))
    177       (if (and session-buffer
    178 	       (org-babel-comint-buffer-livep (get-buffer session-buffer))
    179 	       (comint-check-proc session-buffer))
    180 	  session-buffer
    181 	;; start skewer REPL.
    182 	(httpd-start)
    183 	(run-skewer)
    184 	(skewer-repl)
    185 	session-buffer)))
    186    ((string= "*Javascript REPL*" session)
    187     (org-require-package 'js-comint)
    188     (let ((session-buffer "*Javascript REPL*"))
    189       (if (and (org-babel-comint-buffer-livep (get-buffer session-buffer))
    190 	       (comint-check-proc session-buffer))
    191 	  session-buffer
    192 	(call-interactively 'run-js)
    193 	(sit-for .5)
    194 	session-buffer)))
    195    ((string= "mozrepl" org-babel-js-cmd)
    196     ;; FIXME: According to https://github.com/bard/mozrepl, this REPL
    197     ;; is outdated and does not work for Firefox >54.
    198     (org-require-package 'moz "mozrepl")
    199     (let ((session-buffer (save-window-excursion
    200 			    (run-mozilla nil)
    201 			    (rename-buffer session)
    202 			    (current-buffer))))
    203       (if (org-babel-comint-buffer-livep session-buffer)
    204 	  (progn (sit-for .25) session-buffer)
    205 	(sit-for .5)
    206 	(org-babel-js-initiate-session session))))
    207    ((string= "node" org-babel-js-cmd )
    208     (error "Session evaluation with node.js is not supported"))
    209    (t
    210     (error "Sessions are only supported with mozrepl add \":cmd mozrepl\""))))
    211 
    212 (provide 'ob-js)
    213 
    214 ;;; ob-js.el ends here