PageRenderTime 60ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/elpa/elnode-20190702.1509/elnode-rle.el

https://github.com/andreaja/dotemacs
Emacs Lisp | 387 lines | 301 code | 41 blank | 45 comment | 9 complexity | 70d857c3be054e771b7edbc42212f668 MD5 | raw file
Possible License(s): GPL-3.0
  1. ;;; elnode-rle.el --- Remote Lisp Executiion with Elnode -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2012 Nic Ferrier
  3. ;; Author: Nic Ferrier
  4. ;; Keywords: lisp, hypermedia, processes
  5. ;; This program is free software; you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation, either version 3 of the License, or
  8. ;; (at your option) any later version.
  9. ;; This program is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. ;;; Commentary:
  16. ;;
  17. ;; This is an elnode handler and tools for doing asynchrous
  18. ;; programming.
  19. ;;
  20. ;; The idea is that you can setup associated child processes and pass
  21. ;; them work to do and receive their output over HTTP.
  22. ;;; Code:
  23. (require 'elnode)
  24. (require 'web)
  25. (require 'loadhist)
  26. (require 'server)
  27. (defun elnode-rle--handler (httpcon)
  28. "Remote Lisp Evaluator handler.
  29. This can be spawned in a client to allow any lisp code to be
  30. passed over the client-server link."
  31. (let* ((lisp-to-run (elnode-http-param httpcon "lisp"))
  32. (lisp
  33. (if lisp-to-run
  34. (car (read-from-string lisp-to-run))))
  35. (bindings-to-use (elnode-http-param httpcon "bindings"))
  36. (bindings
  37. (if bindings-to-use
  38. (car (read-from-string bindings-to-use))))
  39. (to-eval (list 'let bindings lisp)))
  40. (elnode-http-start httpcon 200 '("Content-type" . "text/plain"))
  41. (let ((nomessage t))
  42. (with-stdout-to-elnode httpcon
  43. (eval to-eval)))))
  44. (ert-deftest elnode-rle--handler ()
  45. "Test the Remote Lisp Evaluator handler."
  46. :expected-result :failed
  47. (flet ((lisp-encode (param lisp)
  48. (cons param (format "%S" lisp)))
  49. (do-test (lisp bindings)
  50. (fakir-mock-process
  51. :httpcon
  52. ((:elnode-http-params (list lisp bindings)))
  53. (elnode-rle--handler :httpcon)
  54. (with-current-buffer (process-buffer :httpcon)
  55. (goto-char (point-min))
  56. ;; Find the header end.
  57. (re-search-forward "\r\n\r\n" nil 't)
  58. (buffer-substring (point) (point-max))))))
  59. (should
  60. (equal
  61. ;; Match the content transfer encoded
  62. "c\r\nhello world!\r\n0\r\n\r\n"
  63. (let*
  64. ((lisp (lisp-encode
  65. "lisp" '(let ((a "hello world!")) (princ a))))
  66. (bindings (lisp-encode
  67. "bindings" '((a 10)(b 20)))))
  68. (do-test lisp bindings))))
  69. (should
  70. (equal
  71. "2\r\n30\r\n0\r\n\r\n"
  72. (let*
  73. ((lisp (lisp-encode
  74. "lisp" '(let ((a (+ b 10))) (princ a))))
  75. (bindings (lisp-encode
  76. "bindings" '((a 10)(b 20)))))
  77. (do-test lisp bindings))))))
  78. (defvar elnode-rle--servers (make-hash-table :test 'equal)
  79. "The hash of RLE servers available.")
  80. (defun elnode-rle--load-path-ize (lisp)
  81. "Wrap LISP in the current load-path."
  82. (concat
  83. ;; There is a very strange thing with sending lisp to
  84. ;; (read) over a piped stream... (read) can't cope with
  85. ;; multiple lines; so we encode newline here.
  86. ;;(replace-regexp-in-string
  87. ;; "\n"
  88. ;; "\\\\n"
  89. (format "(progn (setq load-path (quote %S)) %s)"
  90. (append (list default-directory) load-path)
  91. lisp)))
  92. (defun elnode-rle--handler-lisp (to-require)
  93. "Return a file with Lisp to start Elnode with TO-REQUIRE.
  94. Used to construct the lisp to send. You're unlikely to need to
  95. override this at all, the function is just here to make the
  96. implementation easier to debug.
  97. TO-REQUIRE is a list of things to require, currently only 1 is
  98. allowed."
  99. (let ((temp-file
  100. (make-temp-file
  101. (format "elnode-rle-%s" (symbol-name to-require)))))
  102. (with-temp-file temp-file
  103. (insert
  104. (elnode-rle--load-path-ize
  105. (format "(progn
  106. (setq elnode-do-init nil)
  107. (setq elnode--do-error-logging nil)
  108. (require (quote %s))
  109. (require (quote elnode-rle))
  110. (toggle-debug-on-error)
  111. (setq elnode-rle-port (elnode-find-free-service))
  112. (elnode-start 'elnode-rle--handler :port elnode-rle-port)
  113. (print (format \"\\nelnode-port=%%d\\n\" port)))"
  114. to-require))))
  115. temp-file))
  116. (defun elnode-rle--httpcon-mapper (client-header
  117. client-data
  118. elnode-httpcon
  119. &optional end-callback)
  120. "Elnode specific client connection to HTTP connection mapper.
  121. Maps client async data responses to an elnode server response."
  122. (unless (process-get elnode-httpcon :elnode-rle-header-sent)
  123. (elnode-http-start
  124. elnode-httpcon
  125. (gethash 'status-code client-header))
  126. (process-put elnode-httpcon :elnode-rle-header-sent t))
  127. (if (eq client-data :done)
  128. (elnode-http-return elnode-httpcon) ; return if we're done
  129. ;; Else just send the data
  130. (elnode-http-send-string elnode-httpcon client-data)))
  131. (defun elnode-rle--client-data-mapper (con header data stream end-callback)
  132. "Recevies data from the RLE server and sends it to the STREAM.
  133. END-CALLBACK is to be called when the client sees EOF."
  134. (cond
  135. ((processp stream) ; this should really elnode-http-p
  136. (elnode-rle--httpcon-mapper header data stream end-callback))
  137. ((bufferp stream)
  138. (if (not (eq data :done))
  139. (with-current-buffer stream
  140. (save-excursion
  141. (goto-char (point-max))
  142. (insert data)))
  143. ;; Process is done.
  144. (and (functionp end-callback)
  145. (funcall end-callback header))))))
  146. (defun elnode-rle--call-mapper (data-to-send stream port
  147. &optional end-callback)
  148. "Make a client call to PORT mapping response to STREAM.
  149. When it finishes, call END-CALLBACK, if present, with the header."
  150. (web-http-post
  151. (lambda (con header data)
  152. (elnode-rle--client-data-mapper
  153. con
  154. header
  155. data
  156. stream
  157. end-callback))
  158. "/"
  159. :host "localhost"
  160. :port port
  161. :data data-to-send
  162. :mime-type "application/x-elnode"
  163. :mode 'stream))
  164. (defun elnode-rle--make-server (to-require)
  165. "Make an RLE server, a child Emacs running the RLE handler.
  166. Return a proc that represents the child process. The child
  167. process has a property `:exec' which is a function that calls the
  168. RLE handler in the child's Elnode server (waiting for the server
  169. to start first and provide the relevant port) by calling
  170. `elnode-rle-call-mapper' with the stream from the `:exec' call
  171. and the child's remote HTTP port.
  172. The `:exec' proc will signal `elnode-rle-child-port' if the child
  173. server does not start properly." ; yes. I know it's bloody complicated.
  174. (let* ((proc-buffer
  175. (get-buffer-create
  176. (format "* %s *" "thingy")))
  177. (emacsrun
  178. "/usr/bin/emacs -Q --daemon=elnode-debugit")
  179. (proc
  180. (start-process-shell-command
  181. "elnode-rle-server"
  182. proc-buffer
  183. emacsrun))
  184. (file-of-lisp
  185. (elnode-rle--handler-lisp
  186. to-require)))
  187. ;; Start elnode in it
  188. (server-eval-at "elnode-debugit" `(load-file ,file-of-lisp))
  189. (process-put proc :daemonhandle "elnode-debugit")
  190. (process-put
  191. proc
  192. :port
  193. (server-eval-at
  194. (process-get proc :daemonhandle)
  195. 'elnode-rle-port))
  196. ;; Collect the port from the remote Emacs
  197. ;; - FIXME this should also collect the secure token
  198. (set-process-filter
  199. proc
  200. (lambda (proc data)
  201. ;; Optional delay for test reasons
  202. (with-current-buffer (process-buffer proc)
  203. (save-excursion
  204. (goto-char (point-max))
  205. (insert data)))))
  206. ;; Make a handler to call the server
  207. (process-put
  208. proc :exec
  209. (lambda (data stream &optional end-callback)
  210. (let ((ephemeral-port (process-get proc :port)))
  211. (elnode-rle--call-mapper data stream ephemeral-port end-callback))))
  212. proc))
  213. (defun elnode-rle--sender (stream to-require bindings body
  214. &optional end-callback)
  215. "Make a call using a client to the RLE server elsewhere.
  216. The RLE server is reused over TO-REQUIRE, if it's not already
  217. existing, it is created."
  218. (let ((server (gethash to-require elnode-rle--servers)))
  219. ;; Make the server if we don't have it
  220. (unless server
  221. (setq server
  222. (puthash to-require
  223. (elnode-rle--make-server (car to-require))
  224. elnode-rle--servers)))
  225. ;; Now make the call to the server
  226. (let ((data (make-hash-table :test 'equal)))
  227. (puthash "bindings" (format "%S" bindings) data)
  228. (puthash "lisp" (format "%S" body) data)
  229. (let ((client-connection
  230. (funcall
  231. (process-get server :exec)
  232. data
  233. stream
  234. end-callback)))
  235. ;; If we're streaming to elnode then we need to mark the connection
  236. (when (processp stream)
  237. (process-put
  238. stream
  239. :elnode-child-process
  240. client-connection))))))
  241. (defvar elnode-rle--async-do-end-callback nil
  242. "Used by `elnode-async-do' as the source of an end-callback.
  243. This is just used by tests for end signalling.")
  244. (defmacro elnode-async-do (stream
  245. requires requirements
  246. with-environment bindings
  247. do &rest body)
  248. "Execute the BODY in a remote Emacs.
  249. The STREAM is used to handle any output.
  250. The REQUIREMENTS is a list of provide symbol names that will be
  251. used to establish the right environment in the remote.
  252. The BINDINGS are also sent to the remote.
  253. TODO
  254. security for the remote using the stored key."
  255. (assert (eq with-environment 'with-environment))
  256. (assert (eq requires 'requires))
  257. (assert (eq do 'do))
  258. (let ((bodyv (make-symbol "body"))
  259. (bindsv (make-symbol "binds"))
  260. (streamv (make-symbol "streamv"))
  261. (requirev (make-symbol "providing")))
  262. `(let* ((,streamv ,stream)
  263. (,bodyv (quote (progn ,@body)))
  264. (,bindsv (list
  265. ,@(loop for p in bindings
  266. collect
  267. (if (and p (listp p))
  268. (list 'list `(quote ,(car p)) (cadr p))
  269. (list 'cons `,p nil)))))
  270. (,requirev (quote ,requirements)))
  271. (elnode-rle--sender
  272. ,streamv ,requirev ,bindsv ,bodyv
  273. elnode-rle--async-do-end-callback))))
  274. (defmacro with-elnode-rle-wait (&rest body)
  275. "Simplify the wait for RLE; for testers."
  276. `(unwind-protect
  277. (let (ended)
  278. (progn
  279. ,@body)
  280. (while (not ended) (sit-for 1)))
  281. ;; FIXME - can we get to the name of this?
  282. (server-eval-at "elnode-debugit" '(kill-emacs))))
  283. (ert-deftest elnode-rle--make-server ()
  284. "Test making an RLE server.
  285. Do it all 3 ways: directly with the `elnode-rle-make-server',
  286. with the `elnode-rle--sender' function and finally with the user
  287. facing macro `elnode-async-do'.
  288. The output from the RLE call is collected in a buffer
  289. and tested."
  290. :expected-result :failed
  291. (flet ((make-hash (bindings)
  292. (let ((h (make-hash-table :test 'equal)))
  293. (loop for b in bindings
  294. do (puthash (car b) (cadr b) h))
  295. h)))
  296. ;; Do it RAW
  297. (should
  298. (equal
  299. "hello"
  300. (with-temp-buffer
  301. (let* ((child-proc (elnode-rle--make-server 'elnode))
  302. (daemon-handler (process-get child-proc :daemonhandle))
  303. (collect-buf (current-buffer)))
  304. (with-elnode-rle-wait
  305. (funcall
  306. (process-get child-proc :exec)
  307. (make-hash '(("bindings" "((a \"hello\"))")
  308. ("lisp" "(princ \"hello\")")))
  309. (current-buffer)
  310. (lambda (hdr) ; the end proc
  311. (setq ended t))))
  312. (buffer-substring (point-min) (point-max))))))
  313. ;; Do it via the sender func
  314. (should
  315. (equal
  316. "40"
  317. (with-temp-buffer
  318. (with-elnode-rle-wait
  319. (let ((elnode-rle--servers (make-hash-table :test 'equal)))
  320. (elnode-rle--sender
  321. (current-buffer)
  322. '(elnode)
  323. '((a 10) (b 20))
  324. '(let ((c 30))(princ (+ c a)))
  325. (lambda (header)
  326. (message "elnode-rle: all done!")(setq ended t)))))
  327. (buffer-substring (point-min) (point-max)))))
  328. ;; Do it with the macro
  329. (should
  330. (equal
  331. "hello"
  332. (with-temp-buffer
  333. (with-elnode-rle-wait
  334. (let ((elnode-rle--servers (make-hash-table :test 'equal))
  335. (elnode-rle--async-do-end-callback
  336. (lambda (header)
  337. (message "elnode-rle: in the dyn bound callback!")
  338. (setq ended t))))
  339. (elnode-async-do
  340. (current-buffer)
  341. requires (elnode enode-rle)
  342. with-environment ((a 10)(b 20))
  343. do (princ "hello"))))
  344. (buffer-substring (point-min) (point-max)))))))
  345. (provide 'elnode-rle)
  346. ;; elnode-rle ends here