emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
blob aaadd1ecfc54b7a7905e22c515a8f608dd7d376e 7475 bytes (raw)
name: lisp/ob-js.el 	 # note: path name is non-authoritative(*)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
 
;;; ob-js.el --- Babel Functions for Javascript      -*- lexical-binding: t; -*-

;; Copyright (C) 2010-2023 Free Software Foundation, Inc.

;; Author: Eric Schulte
;; Keywords: literate programming, reproducible research, js
;; URL: https://orgmode.org

;; This file is part of GNU Emacs.

;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; Now working with SBCL for both session and external evaluation.
;;
;; This certainly isn't optimally robust, but it seems to be working
;; for the basic use cases.

;;; Requirements:

;; - a non-browser javascript engine such as node.js https://nodejs.org/
;;   or mozrepl https://wiki.github.com/bard/mozrepl/
;;
;; - for session based evaluation mozrepl and moz.el are required see
;;   https://wiki.github.com/bard/mozrepl/emacs-integration for
;;   configuration instructions

;;; Code:

(require 'org-macs)
(unless (version< emacs-version "29")
  (org-assert-version))

(require 'ob)

(declare-function run-mozilla "ext:moz" (arg))
(declare-function httpd-start "ext:simple-httpd" ())
(declare-function run-skewer "ext:skewer-mode" ())
(declare-function skewer-repl "ext:skewer-repl" ())
(declare-function indium-run-node "ext:indium-nodejs" (command))
(declare-function indium-eval "ext:indium-interaction" (string &optional callback))

(defvar org-babel-default-header-args:js '()
  "Default header arguments for js code blocks.")

(defvar org-babel-js-eoe "org-babel-js-eoe"
  "String to indicate that evaluation has completed.")

(defcustom org-babel-js-cmd "node"
  "Name of command used to evaluate js blocks."
  :group 'org-babel
  :version "24.1"
  :type '(choice (const "node")
		 (const "mozrepl")
		 (const "skewer-mode")
		 (const "indium")
		 (const "js-comint"))
  :safe #'stringp)

(defvar org-babel-js-function-wrapper
  "require('process').stdout.write(require('util').inspect(function(){%s}()));"
  "Javascript code to print value of body.")

(defun org-babel-execute:js (body params)
  "Execute a block of Javascript code with org-babel.
This function is called by `org-babel-execute-src-block'."
  (let* ((org-babel-js-cmd (or (cdr (assq :cmd params)) org-babel-js-cmd))
	 (session (cdr (assq :session params)))
         (result-type (cdr (assq :result-type params)))
         (full-body (org-babel-expand-body:generic
		     body params (org-babel-variable-assignments:js params)))
	 (result (cond
		  ;; no session specified, external evaluation
		  ((string= session "none")
		   (let ((script-file (org-babel-temp-file "js-script-")))
		     (with-temp-file script-file
		       (insert
			;; return the value or the output
			(if (string= result-type "value")
			    (format org-babel-js-function-wrapper full-body)
			  full-body)))
		     (org-babel-eval
		      (format "%s %s" org-babel-js-cmd
			      (org-babel-process-file-name script-file)) "")))
		  ;; Indium Node REPL.  Separate case because Indium
		  ;; REPL is not inherited from Comint mode.
		  ((string= session "*JS REPL*")
		   (require 'indium-repl)
		   (unless (get-buffer session)
		     (indium-run-node org-babel-js-cmd))
		   (indium-eval full-body))
		  ;; session evaluation
		  (t
		   (let ((session (org-babel-prep-session:js
				   (cdr (assq :session params)) params)))
		     (nth 1
			  (org-babel-comint-with-output
			      (session (format "%S" org-babel-js-eoe) t body)
			    (dolist (code (list body (format "%S" org-babel-js-eoe)))
			      (insert (org-babel-chomp code))
			      (comint-send-input nil t)))))))))
    (org-babel-result-cond (cdr (assq :result-params params))
      result (org-babel-js-read result))))

(defun org-babel-js-read (results)
  "Convert RESULTS into an appropriate elisp value.
If RESULTS look like a table, then convert them into an
Emacs-lisp table, otherwise return the results as a string."
  (org-babel-read
   (if (and (stringp results)
	    (string-prefix-p "[" results)
	    (string-suffix-p "]" results))
       (org-babel-read
        (concat "'"
                (replace-regexp-in-string
                 "\\[" "(" (replace-regexp-in-string
                            "\\]" ")" (replace-regexp-in-string
                                       ",[[:space:]]" " "
				       (replace-regexp-in-string
					"'" "\"" results))))))
     results)))

(defun org-babel-js-var-to-js (var)
  "Convert VAR into a js variable.
Convert an elisp value into a string of js source code
specifying a variable of the same value."
  (if (listp var)
      (concat "[" (mapconcat #'org-babel-js-var-to-js var ", ") "]")
    (replace-regexp-in-string "\n" "\\\\n" (format "%S" var))))

(defun org-babel-prep-session:js (session params)
  "Prepare SESSION according to the header arguments specified in PARAMS."
  (let* ((session (org-babel-js-initiate-session session))
	 (var-lines (org-babel-variable-assignments:js params)))
    (when session
      (org-babel-comint-in-buffer session
	(goto-char (point-max))
	(dolist (var var-lines)
	  (insert var)
	  (comint-send-input nil t)
	  (org-babel-comint-wait-for-output session)
	  (sit-for .1)
	  (goto-char (point-max)))))
    session))

(defun org-babel-variable-assignments:js (params)
  "Return list of Javascript statements assigning the block's variables."
  (mapcar
   (lambda (pair) (format "var %s=%s;"
			  (car pair) (org-babel-js-var-to-js (cdr pair))))
   (org-babel--get-vars params)))

(defun org-babel-js-initiate-session (&optional session _params)
  "If there is not a current inferior-process-buffer in `SESSION' then create.
Return the initialized session."
  (cond
   ((string= session "none")
    (warn "Session evaluation of ob-js is not supported"))
   ((string= "*skewer-repl*" session)
    (require 'skewer-repl)
    (let ((session-buffer (get-buffer "*skewer-repl*")))
      (if (and session-buffer
	       (org-babel-comint-buffer-livep (get-buffer session-buffer))
	       (comint-check-proc session-buffer))
	  session-buffer
	;; start skewer REPL.
	(httpd-start)
	(run-skewer)
	(skewer-repl)
	session-buffer)))
   ((string= "*Javascript REPL*" session)
    (require 'js-comint)
    (let ((session-buffer "*Javascript REPL*"))
      (if (and (org-babel-comint-buffer-livep (get-buffer session-buffer))
	       (comint-check-proc session-buffer))
	  session-buffer
	(call-interactively 'run-js)
	(sit-for .5)
	session-buffer)))
   ((string= "mozrepl" org-babel-js-cmd)
    (require 'moz)
    (let ((session-buffer (save-window-excursion
			    (run-mozilla nil)
			    (rename-buffer session)
			    (current-buffer))))
      (if (org-babel-comint-buffer-livep session-buffer)
	  (progn (sit-for .25) session-buffer)
	(sit-for .5)
	(org-babel-js-initiate-session session))))
   ((string= "node" org-babel-js-cmd )
    (error "Session evaluation with node.js is not supported"))
   (t
    (error "Sessions are only supported with mozrepl add \":cmd mozrepl\""))))

(provide 'ob-js)

;;; ob-js.el ends here

debug log:

solving aaadd1ecf ...
found aaadd1ecf in https://list.orgmode.org/orgmode/87o7obn0dp.fsf@localhost/
found 910c11686 in https://git.savannah.gnu.org/cgit/emacs/org-mode.git
preparing index
index prepared:
100644 910c116866724712c64942cda2f7a3726dca9a53	lisp/ob-js.el

applying [1/1] https://list.orgmode.org/orgmode/87o7obn0dp.fsf@localhost/
diff --git a/lisp/ob-js.el b/lisp/ob-js.el
index 910c11686..aaadd1ecf 100644

Checking patch lisp/ob-js.el...
Applied patch lisp/ob-js.el cleanly.

index at:
100644 aaadd1ecfc54b7a7905e22c515a8f608dd7d376e	lisp/ob-js.el

(*) Git path names are given by the tree(s) the blob belongs to.
    Blobs themselves have no identifier aside from the hash of its contents.^

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).