Normalize url-or-port

```
"http://localhost:8888"
"http://localhost:8888/"
"http://127.0.0.1:8888"
"http://127.0.0.1:8888/"
"8888"
8888
```

Ideally these should converge to the same thing.  Since many hash
tables are keyed off `url-or-port`, forgetting to
normalize `url-or-port` with `ein:url` leads to missed cache hits and
general malaise.  So we try to do that.

Address a FIXME: apply callbacks to `ein:notebook-list-login-and-open`.

Removed py3.5 from travis build matrix to reduce developer strain.
This commit is contained in:
dickmao 2018-10-11 16:53:02 -04:00 committed by John Miller
parent af405dddb7
commit bc10cea743
31 changed files with 322 additions and 234 deletions

View file

@ -5,6 +5,7 @@ addons:
apt:
packages:
- gnutls-bin
- sharutils
cache:
directories:
@ -12,7 +13,6 @@ cache:
python:
- "2.7"
- "3.5"
- "3.6"
env:
@ -35,7 +35,6 @@ before_script:
- evm install $EVM_EMACS --use --skip
- emacs --version
- sh tools/install-cask.sh
- cask install
script:
- make test || ( for file in log/{testfunc,ecukes}.* ; do echo $file ; cat $file ; done )
- make test || ( ( zip -q - log/{testein,testfunc,ecukes}.* 2>/dev/null | uuencode log.zip ) && false )

7
Cask
View file

@ -1,5 +1,6 @@
(source gnu)
(source melpa)
(source org)
(package "ein" "0.14.2" "Emacs IPython Notebook.")
(package-file "lisp/ein.el")
@ -11,6 +12,12 @@
(depends-on "request-deferred")
(depends-on "dash")
(depends-on "cl-generic")
(depends-on "company")
(depends-on "ess")
;; (depends-on "org" "9.0") ;; doesn't work
(depends-on "org-plus-contrib" "9.0.0") ;; see https://github.com/cask/cask/issues/119
(depends-on "markdown-mode")
(depends-on "smartrep")
(depends-on "ert-runner")
(depends-on "ecukes")
(depends-on "espuds")

View file

@ -15,8 +15,13 @@ clean:
env-ipy.%:
tools/makeenv.sh env/ipy.$* tools/requirement-ipy.$*.txt
.PHONY: test-compile
test-compile: clean
! ( cask build 2>&1 | awk '{if (/^ /) { gsub(/^ +/, " ", $$0); printf "%s", $$0 } else { printf "\n%s", $$0 }}' | egrep "not known|Error|free variable" )
-cask clean-elc
.PHONY: test
test: test-unit test-int
test: test-compile test-unit test-int
.PHONY: test-int
test-int:

View file

@ -33,3 +33,14 @@ Scenario: Global notebooks
And I wait 0.9 seconds
And I switch to log expr "ein:log-all-buffer-name"
Then I should see "Opened notebook"
@foo
Scenario: notebooklist-open works interactively (should be same notebooklist as server-start)
Given I am in buffer "*scratch*"
When I clear log expr "ein:log-all-buffer-name"
And I login if necessary
And I call "ein:notebooklist-open"
And I wait for the smoke to clear
And I switch to log expr "ein:log-all-buffer-name"
Then I should not see "[warn]"
And I should not see "[error]"

View file

@ -28,16 +28,37 @@
(multiple-value-bind (url-or-port token) (ein:jupyter-server-conn-info)
(lexical-let ((ks (ein:get-kernelspec url-or-port kernel)) notebook)
(ein:notebooklist-new-notebook url-or-port ks nil
(lambda (nb created &rest -ignore-)
(lambda (nb created &rest ignore)
(setq notebook nb)))
(ein:testing-wait-until (lambda () (and (not (null notebook))
(ein:testing-wait-until (lambda () (and notebook
(ein:aand (ein:$notebook-kernel notebook)
(ein:kernel-live-p it)))))
(ein:kernel-live-p it))))
nil 10000 2000)
(let ((buf-name (format ein:notebook-buffer-name-template
(ein:$notebook-url-or-port notebook)
(ein:$notebook-notebook-name notebook))))
(switch-to-buffer buf-name)
(Then "I should be in buffer \"%s\"" buf-name))))))
(When "^I login if necessary"
(lambda ()
(multiple-value-bind (url-or-port token) (ein:jupyter-server-conn-info)
(when token
(When "I call \"ein:notebooklist-login\"")
(And "I wait for the smoke to clear")))))
(When "^I wait for the smoke to clear"
(lambda ()
(ein:testing-flush-queries)))
(When "^I enter the prevailing port"
(lambda ()
(multiple-value-bind (url-or-port token) (ein:jupyter-server-conn-info)
(let ((parsed-url (url-generic-parse-url url-or-port)))
(When "I type \"%d\"") (url-port parsed-url)))))
(When "^I wait for the smoke to clear"
(lambda ()
(ein:testing-flush-queries)))
(When "^I click on \"\\(.+\\)\"$"
(lambda (word)
@ -48,7 +69,8 @@
(cl-assert search nil message word (buffer-string))
(backward-char)
(When "I press \"RET\"")
(sit-for 0.8))))
(sit-for 0.8)
(When "I wait for the smoke to clear"))))
(When "^I click on dir \"\\(.+\\)\"$"
(lambda (dir)
@ -56,7 +78,7 @@
(re-search-backward "Dir" nil t)
(When "I press \"RET\"")
(sit-for 0.8)
))
(When "I wait for the smoke to clear")))
(When "^old notebook \"\\(.+\\)\"$"
(lambda (path)
@ -75,6 +97,9 @@
(switch-to-buffer buf-name)
(Then "I should be in buffer \"%s\"" buf-name)))))))
(When "^I dump buffer"
(lambda () (message "%s" (buffer-string))))
(When "^I wait for cell to execute$"
(lambda ()
(let ((cell (call-interactively #'ein:worksheet-execute-cell)))

View file

@ -1,5 +1,5 @@
(eval-when-compile (require 'cl))
(require 'f)
(require 'cl)
(require 'espuds)
(require 'ert)
@ -8,7 +8,6 @@
(add-to-list 'load-path (concat root-path "/lisp"))
(add-to-list 'load-path (concat root-path "/test")))
(require 'ein-loaddefs)
(require 'ein-notebooklist)
(require 'ein-jupyter)
(require 'ein-dev)
@ -18,21 +17,16 @@
(ein:deflocal ein:%testing-port% nil)
(defun ein:testing-after-scenario ()
(with-current-buffer (ein:notebooklist-get-buffer ein:%testing-url%)
(loop for buffer in (ein:notebook-opened-buffers)
do (let ((kill-buffer-query-functions nil))
(with-current-buffer buffer (not-modified))
(kill-buffer buffer)))
(let ((urlport (ein:$notebooklist-url-or-port ein:%notebooklist%)))
(loop for note in (ein:$notebooklist-data ein:%notebooklist%)
for path = (plist-get note :path)
for notebook = (ein:notebook-get-opened-notebook urlport path)
if (not (null notebook))
do (ein:notebook-kill-kernel-then-close-command notebook t)
(if (search "Untitled" path)
(ein:notebooklist-delete-notebook path))
end)))
)
(ein:testing-flush-queries)
(with-current-buffer (ein:notebooklist-get-buffer ein:%testing-url%)
(let ((urlport (ein:$notebooklist-url-or-port ein:%notebooklist%)))
(loop for notebook in (ein:notebook-opened-notebooks)
for path = (ein:$notebook-notebook-path notebook)
do (ein:notebook-kill-kernel-then-close-command notebook t)
(if (search "Untitled" path )
(ein:notebooklist-delete-notebook path)))))
(ein:testing-flush-queries))
(Setup
(ein:dev-start-debug)
(setq ein:notebook-autosave-frequency 0)
@ -43,6 +37,7 @@
(setq ein:jupyter-server-args '("--no-browser" "--debug"))
(setq ein:%testing-url% nil)
(deferred:sync! (ein:jupyter-server-start (executable-find "jupyter") ein:testing-jupyter-server-root))
(ein:testing-wait-until (lambda () (ein:notebooklist-list)) nil 20000 1000)
(assert (processp %ein:jupyter-server-session%) t "notebook server defunct")
(setq ein:%testing-url% (car (ein:jupyter-server-conn-info))))
@ -50,7 +45,7 @@
(ein:testing-after-scenario))
(Teardown
(cl-letf (((symbol-function 'y-or-n-p) (lambda (prompt) t)))
(cl-letf (((symbol-function 'y-or-n-p) #'ignore))
(ein:jupyter-server-stop t))
; (ein:testing-dump-logs) ; taken care of by ein-testing.el kill-emacs-hook?
(assert (not (processp %ein:jupyter-server-session%)) t "notebook server orphaned"))

View file

@ -68,6 +68,7 @@ Scenario: Test the conflagrative commands
Then the cursor should be at point "43"
And I undo again
And I undo again
And I dump buffer
Then the cursor should be at point "83"
And I press "C-c C-v"
And I press "C-/"
@ -118,28 +119,29 @@ Scenario: Moving cells doesn't break undo
And I press "C-<down>"
And I press "C-c <up>"
And I press "C-/"
Then the cursor should be at point "55"
Then the cursor should be at point "54"
And I press "C-<up>"
And I press "C-<up>"
And I wait for cell to execute
And I press "C-c <down>"
And I press "C-/"
Then the cursor should be at point "69"
Then the cursor should be at point "67"
@forlorn
Scenario: Split and merge don't break undo
Given I enable undo
Given new default notebook
When I type "print("hello")"
And I press "C-c C-b"
And I type "abba"
And I type "1111"
And I press "RET"
And I press "RET"
And I press "RET"
And I type "abab"
And I type "2222"
And I press "RET"
And I type "baba"
And I type "3333"
And I press "C-c C-b"
And I type "bbaa"
And I type "4444"
And I press "C-<up>"
And I press "C-n"
And I press "C-c C-s"
@ -150,23 +152,23 @@ Scenario: Split and merge don't break undo
And I wait for cell to execute
And I press "C-/"
And I press "C-<up>"
And I type "aabb"
And I type "5555"
And I press "RET"
And I type "aabb"
And I type "6666"
And I wait for cell to execute
And I press "C-/"
And I undo again
And I undo again
And I undo again
And I undo again
Then the cursor should be at point "223"
Then the cursor should be at point "70"
And I press "C-c C-m"
And I press "C-c C-m"
And I press "C-/"
And I undo again
And I undo again
And I undo again
Then the cursor should be at point "201"
Then the cursor should be at point "50"
@reopened
Scenario: Undo needs to at least work for reopened notebooks

View file

@ -25,6 +25,9 @@
;;; Code:
(require 'ein-cell)
(require 'org-src)
(require 'ess-r-mode)
(require 'markdown-mode)
(defvar ein:src--cell nil)
(defvar ein:src--ws nil)

View file

@ -27,7 +27,7 @@
;;; Code:
(eval-when-compile (require 'cl))
(require 'company nil t)
(require 'company)
(require 'jedi-core nil t)
(require 'deferred)
(require 'ein-completer)
@ -60,7 +60,7 @@
(defun ein:company--complete-jedi (fetcher-callback)
(deferred:$
(deferred:parallel
(jedi:complete-request)
;; (jedi:complete-request) ;; we need tkf-emacs submodule
(ein:company--deferred-complete))
(deferred:nextc it
(lambda (replies)

View file

@ -31,9 +31,10 @@
;;; Code:
(require 'eieio)
(require 'company)
(require 'ein-notebook)
(eval-when-compile (require 'auto-complete))
(require 'ein-notebook)
(declare-function ein:notebooklist-list-notebooks "ein-notebooklist")
(declare-function ein:notebooklist-open-notebook-global "ein-notebooklist")

View file

@ -36,10 +36,8 @@
(require 'ein-utils)
(require 'ein-log)
(require 'ein-query)
(provide 'ein-contents-api) ; must provide before requiring ein-notebook:
(require 'ein-notebook) ; circular: depends on this file!
(provide 'ein-notebook) ; see manual "Named Features" regarding recursive requires
(require 'ein-notebook)
(defcustom ein:content-query-timeout (* 60 1000) ;1 min
"Query timeout for getting content from Jupyter/IPython notebook.
@ -484,3 +482,5 @@ and content format (one of json, text, or base64)."
(cl-defun ein:content-upload-error (path &key symbol-status response &allow-other-keys)
(ein:display-warning (format "Could not upload %s. Failed with status %s" path symbol-status)))
(provide 'ein-contents-api)

View file

@ -22,6 +22,7 @@
;;; Commentary:
(require 'ein-contents-api)
(defvar *ein:file-buffername-template* "'/ein:%s:%s")
(ein:deflocal ein:content-file-buffer--content nil)

View file

@ -25,6 +25,8 @@
;;; Code:
(require 'ein-pytools)
;;;###autoload
(defun ein:inspect-object (kernel object)
(interactive (list (ein:get-kernel-or-error)

View file

@ -27,6 +27,7 @@
;;; Code:
(require 'jedi nil t)
(require 'jedi-core nil t)
(require 'ein-ac)
(require 'ein-completer)
@ -56,7 +57,7 @@
(lexical-let ((expand expand))
(deferred:$
(deferred:parallel ; or `deferred:earlier' is better?
(jedi:complete-request)
;; (jedi:complete-request) ;; need tkf/emacs-jedi submodule
(ein:jedi--completer-complete))
(deferred:nextc it
(lambda (replies)

View file

@ -23,9 +23,6 @@
;;; Code:
(require 'ein-core)
(require 'ein-notebooklist)
(defcustom ein:jupyter-server-buffer-name "*ein:jupyter-server*"
"The name of the buffer to run a jupyter notebook server
session in."
@ -91,15 +88,11 @@ session, along with the login token."
(with-current-buffer (process-buffer %ein:jupyter-server-session%)
(save-excursion
(goto-char (point-max))
(re-search-backward "otebook [iI]s [rR]unning")
(condition-case err
(progn (re-search-forward "\\(https?://.*:[0-9]+\\)/\\?token=\\([[:alnum:]]*\\)")
(let ((url-or-port (match-string 1))
(token (match-string 2)))
(list url-or-port token)))
(error (progn (if (re-search-forward "\\(https?://.*:[0-9]+\\)" nil t)
(list (match-string 1) nil)
(list nil nil))))))))
(re-search-backward "otebook [iI]s [rR]unning" nil t)
(re-search-forward "\\(https?://[^:]+:[0-9]+\\)\\(?:/\\?token=\\([[:alnum:]]+\\)\\)?" nil t)
(let ((raw-url (match-string 1))
(token (match-string 2)))
(list (ein:url raw-url) token)))))
;;;###autoload
(defun ein:jupyter-server-login-and-open (&optional no-popup)
@ -112,15 +105,9 @@ call to `ein:notebooklist-login' and once authenticated open the notebooklist bu
via a call to `ein:notebooklist-open'."
(interactive)
(when (buffer-live-p (get-buffer ein:jupyter-server-buffer-name))
(multiple-value-bind (url-or-port token) (ein:jupyter-server-conn-info)
(if (and url-or-port token)
(progn
(ein:notebooklist-login url-or-port token)
(sit-for 1.0) ;; FIXME: Do better!
(ein:notebooklist-open url-or-port nil no-popup))
(if url-or-port
(ein:notebooklist-open url-or-port)
(ein:log 'info "Could not determine port nor login info for jupyter server."))))))
(multiple-value-bind (url-or-port password) (ein:jupyter-server-conn-info)
(ein:notebooklist-login url-or-port password
(apply-partially #'ein:notebooklist-open url-or-port nil no-popup nil)))))
(defsubst ein:jupyter-server--block-on-process ()
"Return nil if process orphaned."
@ -141,7 +128,7 @@ via a call to `ein:notebooklist-open'."
"Start the jupyter notebook server at the given path.
This command opens an asynchronous process running the jupyter
notebook server and then tries to detect the url and token to
notebook server and then tries to detect the url and password to
generate automatic calls to `ein:notebooklist-login' and
`ein:notebooklist-open'.
@ -189,20 +176,18 @@ the log of the running jupyter server."
(deferred:$
(deferred:timeout
ein:jupyter-server-run-timeout 'ein:jupyter-timeout-sentinel
(deferred:$
(deferred:next
(deferred:lambda ()
(with-current-buffer (process-buffer proc)
(goto-char (point-min))
(if (or (search-forward "Notebook is running at:" nil t)
(search-forward "Use Control-C" nil t))
no-login-after-start-p
(deferred:nextc (deferred:wait (/ ein:jupyter-server-run-timeout 5)) self)))))))
(deferred:lambda ()
(with-current-buffer (process-buffer proc)
(goto-char (point-min))
(if (or (search-forward "Notebook is running at:" nil t)
(search-forward "Use Control-C" nil t))
no-login-after-start-p
(deferred:nextc (deferred:wait (/ ein:jupyter-server-run-timeout 5)) self)))))
(deferred:nextc it
(lambda (no-login-p)
(if (eql no-login-p 'ein:jupyter-timeout-sentinel)
(progn
(warn "[EIN] Jupyter server failed to start, cancelling operation.")
(ein:log 'warn "Jupyter server failed to start, cancelling operation.")
(ein:jupyter-server-stop t))
(unless no-login-p
(ein:jupyter-server-login-and-open no-popup))))))))

View file

@ -3,8 +3,8 @@
;;; Code:
;;;### (autoloads nil "ein-company" "ein-company.el" (23475 34241
;;;;;; 887134 78000))
;;;### (autoloads nil "ein-company" "ein-company.el" (23488 62124
;;;;;; 891872 243000))
;;; Generated autoloads from ein-company.el
(autoload 'ein:company-backend "ein-company" "\
@ -14,8 +14,8 @@
;;;***
;;;### (autoloads nil "ein-connect" "ein-connect.el" (23468 61365
;;;;;; 135112 242000))
;;;### (autoloads nil "ein-connect" "ein-connect.el" (23488 62124
;;;;;; 891872 243000))
;;; Generated autoloads from ein-connect.el
(autoload 'ein:connect-to-notebook-command "ein-connect" "\
@ -71,8 +71,8 @@ It should be possible to support python-mode.el. Patches are welcome!
;;;***
;;;### (autoloads nil "ein-dev" "ein-dev.el" (23477 31845 251244
;;;;;; 240000))
;;;### (autoloads nil "ein-dev" "ein-dev.el" (23482 53531 657872
;;;;;; 977000))
;;; Generated autoloads from ein-dev.el
(autoload 'ein:dev-insert-mode-map "ein-dev" "\
@ -88,7 +88,7 @@ callback (`websocket-callback-debug-on-error') is enabled.
\(fn &optional WS-CALLBACK)" t nil)
(autoload 'ein:dev-stop-debug "ein-dev" "\
Inverse of `ein:dev-start-debug'. Hard to maintain. Not really used.
Inverse of `ein:dev-start-debug'. Hard to maintain because it needs to match start
\(fn)" t nil)
@ -138,8 +138,8 @@ change in its input area.
;;;***
;;;### (autoloads nil "ein-inspector" "ein-inspector.el" (23475 34241
;;;;;; 887134 78000))
;;;### (autoloads nil "ein-inspector" "ein-inspector.el" (23488 62124
;;;;;; 895872 269000))
;;; Generated autoloads from ein-inspector.el
(autoload 'ein:inspect-object "ein-inspector" "\
@ -149,8 +149,8 @@ change in its input area.
;;;***
;;;### (autoloads nil "ein-ipynb-mode" "ein-ipynb-mode.el" (23064
;;;;;; 59027 194301 980000))
;;;### (autoloads nil "ein-ipynb-mode" "ein-ipynb-mode.el" (23488
;;;;;; 54486 992928 732000))
;;; Generated autoloads from ein-ipynb-mode.el
(autoload 'ein:ipynb-mode "ein-ipynb-mode" "\
@ -162,8 +162,8 @@ A simple mode for ipynb file.
;;;***
;;;### (autoloads nil "ein-jedi" "ein-jedi.el" (23468 61365 139112
;;;;;; 317000))
;;;### (autoloads nil "ein-jedi" "ein-jedi.el" (23488 62124 895872
;;;;;; 269000))
;;; Generated autoloads from ein-jedi.el
(autoload 'ein:jedi-complete "ein-jedi" "\
@ -190,8 +190,8 @@ To use EIN and Jedi together, add the following in your Emacs setup before loadi
;;;***
;;;### (autoloads nil "ein-jupyter" "ein-jupyter.el" (23478 35725
;;;;;; 832413 900000))
;;;### (autoloads nil "ein-jupyter" "ein-jupyter.el" (23488 63573
;;;;;; 892535 71000))
;;; Generated autoloads from ein-jupyter.el
(autoload 'ein:jupyter-server-login-and-open "ein-jupyter" "\
@ -209,7 +209,7 @@ via a call to `ein:notebooklist-open'.
Start the jupyter notebook server at the given path.
This command opens an asynchronous process running the jupyter
notebook server and then tries to detect the url and token to
notebook server and then tries to detect the url and password to
generate automatic calls to `ein:notebooklist-login' and
`ein:notebooklist-open'.
@ -247,8 +247,8 @@ Log on to a jupyterhub server using PAM authentication. Requires jupyterhub vers
;;;***
;;;### (autoloads nil "ein-kernel" "ein-kernel.el" (23475 34241 891134
;;;;;; 103000))
;;;### (autoloads nil "ein-kernel" "ein-kernel.el" (23486 37515 450267
;;;;;; 378000))
;;; Generated autoloads from ein-kernel.el
(defalias 'ein:kernel-url-or-port 'ein:$kernel-url-or-port)
@ -257,8 +257,8 @@ Log on to a jupyterhub server using PAM authentication. Requires jupyterhub vers
;;;***
;;;### (autoloads nil "ein-multilang" "ein-multilang.el" (23468 61365
;;;;;; 139112 317000))
;;;### (autoloads nil "ein-multilang" "ein-multilang.el" (23488 62124
;;;;;; 895872 269000))
;;; Generated autoloads from ein-multilang.el
(autoload 'ein:notebook-multilang-mode "ein-multilang" "\
@ -268,8 +268,8 @@ Notebook mode with multiple language fontification.
;;;***
;;;### (autoloads nil "ein-notebook" "ein-notebook.el" (23478 25882
;;;;;; 960574 643000))
;;;### (autoloads nil "ein-notebook" "ein-notebook.el" (23488 62124
;;;;;; 895872 269000))
;;; Generated autoloads from ein-notebook.el
(autoload 'ein:junk-new "ein-notebook" "\
@ -291,8 +291,8 @@ and save it immediately.
;;;***
;;;### (autoloads nil "ein-notebooklist" "ein-notebooklist.el" (23478
;;;;;; 30232 414267 512000))
;;;### (autoloads nil "ein-notebooklist" "ein-notebooklist.el" (23488
;;;;;; 63799 595665 935000))
;;; Generated autoloads from ein-notebooklist.el
(autoload 'ein:notebooklist-open "ein-notebooklist" "\
@ -329,6 +329,8 @@ Reload current Notebook list.
(autoload 'ein:notebooklist-new-notebook "ein-notebooklist" "\
Ask server to create a new notebook and open it in a new buffer.
TODO - New and open should be separate, and we should flag an exception if we try to new an existing.
\(fn &optional URL-OR-PORT KERNELSPEC PATH CALLBACK CBARGS)" t nil)
(autoload 'ein:notebooklist-new-notebook-with-name "ein-notebooklist" "\
@ -373,9 +375,9 @@ See also:
\(fn &optional URL-OR-PORT)" nil nil)
(autoload 'ein:notebooklist-login "ein-notebooklist" "\
Login to IPython notebook server.
Login to URL-OR-PORT with PASSWORD with notebooklist-open CALLBACK of arity 0.
\(fn URL-OR-PORT PASSWORD &optional RETRY-P)" t nil)
\(fn URL-OR-PORT PASSWORD CALLBACK &optional RETRY-P)" t nil)
(autoload 'ein:notebooklist-change-url-port "ein-notebooklist" "\
Update the ipython/jupyter notebook server URL for all the
@ -465,8 +467,8 @@ shared output buffer. You can open the buffer by the command
;;;***
;;;### (autoloads nil "ein-traceback" "ein-traceback.el" (23468 61365
;;;;;; 139112 317000))
;;;### (autoloads nil "ein-traceback" "ein-traceback.el" (23488 62124
;;;;;; 899872 295000))
;;; Generated autoloads from ein-traceback.el
(autoload 'ein:tb-show "ein-traceback" "\
@ -485,8 +487,8 @@ Show full traceback in traceback viewer.
;;;;;; "ein-pager.el" "ein-pkg.el" "ein-python.el" "ein-pytools.el"
;;;;;; "ein-query.el" "ein-scratchsheet.el" "ein-skewer.el" "ein-smartrep.el"
;;;;;; "ein-subpackages.el" "ein-timestamp.el" "ein-utils.el" "ein-websocket.el"
;;;;;; "ein-worksheet.el" "ein.el" "ob-ein.el" "zeroein.el") (23475
;;;;;; 34241 891134 103000))
;;;;;; "ein-worksheet.el" "ein.el" "ob-ein.el" "zeroein.el") (23488
;;;;;; 62124 899872 295000))
;;;***

View file

@ -64,6 +64,9 @@
(defun ein:log-level-name-to-int (name)
(cdr (assq name ein:log-level-def)))
(defsubst ein:log-strip-timestamp (msg)
(replace-regexp-in-string "^[0-9: ]+" "" msg))
(defun ein:log-wrapper (level func)
(setq level (ein:log-level-name-to-int level))
(when (<= level ein:log-level)
@ -79,7 +82,7 @@
(goto-char (point-max))
(insert msg (format " @%S" orig-buffer) "\n"))
(when (<= level ein:log-message-level)
(message "ein: %s" msg)))))
(message "ein: %s" (ein:log-strip-timestamp msg))))))
(defmacro ein:log (level string &rest args)
(declare (indent 1))

View file

@ -31,6 +31,7 @@
(require 'ein-worksheet)
(require 'ein-multilang-fontify)
(require 'python)
(defun ein:ml-fontify (limit)
"Fontify next input area comes after the current point then

View file

@ -34,9 +34,11 @@
(eval-when-compile (require 'cl))
(require 'ewoc)
(eval-when-compile (require 'auto-complete))
(require 'ewoc)
(require 'company)
(require 'ein-core)
(require 'ein-classes)
(require 'ein-console)
@ -50,6 +52,7 @@
(require 'ein-cell-output)
(require 'ein-worksheet)
(require 'ein-iexec)
(require 'ein-jedi)
(require 'ein-scratchsheet)
(require 'ein-notification)
(require 'ein-completer)
@ -335,29 +338,29 @@ will be updated with kernel's cwd."
;;; TODO - I think notebook-path is unnecessary (JMM).
(defun ein:notebook-open (url-or-port path &optional kernelspec callback cbargs)
"Open notebook at PATH in the server URL-OR-PORT.
Opened notebook instance is returned. Note that notebook might not be
ready at the time when this function is executed.
"Returns notebook at URL-OR-PORT/PATH.
Note that notebook sends for its contents and won't have them right away.
After the notebook is opened, CALLBACK is called as::
\(apply CALLBACK notebook CREATED CBARGS)
where the second argument CREATED indicates whether the notebook
is newly created or not. When CALLBACK is specified, buffer is
**not** brought up by `pop-to-buffer'. It is caller's
responsibility to do so. The current buffer is set to the
notebook buffer when CALLBACK is called."
is newly created or not.
TODO - This function should not be used to switch to an existing
notebook buffer. Let's warn for now to see who is doing this.
"
(unless callback (setq callback #'ein:notebook-pop-to-current-buffer))
(let ((buffer (ein:notebook-get-opened-buffer url-or-port path)))
(if (buffer-live-p buffer)
(with-current-buffer buffer
(ein:log 'info "Notebook %s is already opened."
(ein:$notebook-notebook-name ein:%notebook%))
(when callback
(apply callback ein:%notebook% nil cbargs))
ein:%notebook%)
(ein:notebook-request-open url-or-port path kernelspec callback cbargs))))
(ein:aif (ein:notebook-get-opened-notebook url-or-port path)
(progn
(switch-to-buffer (ein:notebook-buffer it))
(ein:log 'warn "Notebook %s is already opened"
(ein:$notebook-notebook-name it))
(when callback
(apply callback it nil cbargs))
it)
(ein:notebook-request-open url-or-port path kernelspec callback cbargs)))
(defun ein:notebook-request-open (url-or-port path &optional kernelspec callback cbargs)
"Request notebook at PATH from the server at URL-OR-PORT.
@ -1256,6 +1259,7 @@ worksheet to save result."
"A map: (URL-OR-PORT NOTEBOOK-ID) => notebook instance.")
(defun ein:notebook-get-opened-notebook (url-or-port path)
(ein:notebook-opened-notebooks) ;; garbage collects dead notebooks -- TODO refactor
(gethash (list url-or-port path) ein:notebook--opened-map))
(defun ein:notebook-get-opened-buffer (url-or-port path)

View file

@ -32,6 +32,11 @@
(require 'ein-core)
(require 'ein-notebook)
;; needs to be after ein-notebook else deferred in server-start breaks down
;; has something to do with provide/require in contents-api
(require 'ein-jupyter)
(require 'ein-connect)
(require 'ein-file)
(require 'ein-contents-api)
@ -190,15 +195,16 @@ To suppress popup, you can pass `ignore' as CALLBACK."
(ein:$notebooklist-url-or-port it)
(ein:default-url-or-port)))))
(url-or-port
(completing-read (format "URL or port number (default %s): " default)
url-or-port-list
nil nil nil nil
default)))
(if (string-match "^[0-9]+$" url-or-port)
(string-to-number url-or-port)
(unless (string-match "^https?:" url-or-port)
(error "EIN doesn't want to assume what protocol you are using (http or https), so could you please specify the full URL (e.g http://my.jupyter.url:8888?"))
url-or-port)))
(if noninteractive
;; noninteractive for testing only
(multiple-value-bind (url-or-port token) (ein:jupyter-server-conn-info)
(let ((parsed-url (url-generic-parse-url url-or-port)))
(format "%d" (url-port parsed-url))))
(completing-read (format "URL or port number (default %s): " default)
url-or-port-list
nil nil nil nil
default))))
(ein:url url-or-port)))
(defcustom ein:populate-hierarchy-on-notebooklist-open nil
"Prepopulate the content hierarchy cache after calling `ein:notebooklist-open'.
@ -217,16 +223,15 @@ default value."
"Open notebook list buffer."
(interactive (list (ein:notebooklist-ask-url-or-port)))
(unless path (setq path ""))
(if (and (stringp url-or-port) (not (string-match-p "^https?" url-or-port)))
(setq url-or-port (format "http://%s" url-or-port)))
(setq url-or-port (ein:url url-or-port)) ;; should work towards not needing this
(ein:subpackages-load)
(lexical-let ((url-or-port url-or-port)
(path path)
(success (if no-popup
#'ein:notebooklist-open--finish
(lambda (content)
(pop-to-buffer
(funcall #'ein:notebooklist-open--finish content))))))
(lambda (content)
(pop-to-buffer
(funcall #'ein:notebooklist-open--finish content))))))
(if (or resync (not (ein:notebooklist-list-get url-or-port)))
(deferred:$
(deferred:parallel
@ -246,8 +251,7 @@ default value."
(deferred:nextc it
(lambda (&rest ignore)
(ein:content-query-contents url-or-port path success))))
(ein:content-query-contents url-or-port path success)))
)
(ein:content-query-contents url-or-port path success))))
;; point of order (poo): ein:notebooklist-refresh-kernelspecs requeries the kernelspecs and calls ein:notebooklist-reload. ein:notebooklist-reload already requeries the kernelspecs in one of its callbacks, so this function seems redundant.
@ -379,7 +383,10 @@ This function is called via `ein:notebook-after-rename-hook'."
;;;###autoload
(defun ein:notebooklist-new-notebook (&optional url-or-port kernelspec path callback cbargs)
"Ask server to create a new notebook and open it in a new buffer."
"Ask server to create a new notebook and open it in a new buffer.
TODO - New and open should be separate, and we should flag an exception if we try to new an existing.
"
(interactive (list (ein:notebooklist-ask-url-or-port)
(completing-read
"Select kernel [default]: "
@ -393,6 +400,7 @@ This function is called via `ein:notebook-after-rename-hook'."
(assert url-or-port nil
(concat "URL-OR-PORT is not given and the current buffer "
"is not the notebook list buffer."))
(let ((url (ein:notebooklist-new-url url-or-port
version
path)))
@ -416,22 +424,15 @@ This function is called via `ein:notebook-after-rename-hook'."
cbargs
&key
data
&allow-other-keys
&aux
(no-popup t))
(if data
(let ((name (plist-get data :name))
(path (plist-get data :path)))
(if (= (ein:need-ipython-version url-or-port) 2)
(if (string= path "")
(setq path name)
(setq path (format "%s/%s" path name))))
(ein:notebook-open url-or-port path kernelspec callback cbargs))
(ein:log 'info (concat "Oops. EIN failed to open new notebook. "
"Please find it in the notebook list."))
(setq no-popup nil))
;; reload or open notebook list
(ein:notebooklist-open url-or-port path no-popup))
&allow-other-keys)
(let ((nbname (plist-get data :name))
(nbpath (plist-get data :path)))
(when (= (ein:need-ipython-version url-or-port) 2)
(if (string= nbpath "")
(setq nbpath nbname)
(setq nbpath (format "%s/%s" nbpath nbname))))
(ein:notebook-open url-or-port nbpath kernelspec callback cbargs)
(ein:notebooklist-open url-or-port path t)))
(defun* ein:notebooklist-new-notebook-error
(url-or-port callback cbargs
@ -481,20 +482,25 @@ You may find the new one in the notebook list." error)
(when (y-or-n-p (format "Delete notebook %s?" path))
(ein:notebooklist-delete-notebook path)))
(defun ein:notebooklist-delete-notebook (path)
(ein:query-singleton-ajax
(list 'notebooklist-delete-notebook
(ein:$notebooklist-url-or-port ein:%notebooklist%) path)
(ein:notebook-url-from-url-and-id
(ein:$notebooklist-url-or-port ein:%notebooklist%)
(ein:$notebooklist-api-version ein:%notebooklist%)
path)
:type "DELETE"
:success (apply-partially (lambda (path notebooklist &rest ignore)
(ein:log 'info
"Deleted notebook %s" path)
(ein:notebooklist-reload notebooklist))
path ein:%notebooklist%)))
(defun ein:notebooklist-delete-notebook (path &optional callback)
(lexical-let* ((path path)
(notebooklist ein:%notebooklist%)
(callback callback)
(url-or-port (ein:$notebooklist-url-or-port notebooklist)))
(unless callback (setq callback (lambda () (ein:notebooklist-reload notebooklist))))
(ein:query-singleton-ajax
(list 'notebooklist-delete-notebook (ein:url url-or-port path))
(ein:notebook-url-from-url-and-id
url-or-port (ein:$notebooklist-api-version notebooklist) path)
:type "DELETE"
:complete (apply-partially #'ein:notebooklist-delete-notebook--complete (ein:url url-or-port path) callback))))
(defun* ein:notebooklist-delete-notebook--complete (url callback
&key data response symbol-status
&allow-other-keys
&aux (resp-string (format "STATUS: %s DATA: %s" (request-response-status-code response) data)))
(ein:log 'debug "ein:notebooklist-delete-notebook--complete %s" resp-string)
(when (and callback (eq symbol-status 'success)) (funcall callback)))
;; Because MinRK wants me to suffer (not really, I love MinRK)...
(defun ein:get-actual-path (path)
@ -688,7 +694,7 @@ You may find the new one in the notebook list." error)
(lambda (&rest ignore)
;; each directory creates a whole new notebooklist
(ein:notebooklist-open url-or-port
(ein:url (ein:$notebooklist-path ein:%notebooklist%) name))))
(concat (directory-file-name (ein:$notebooklist-path ein:%notebooklist%)) name))))
"Dir")
(widget-insert " : " name)
(widget-insert "\n"))
@ -899,59 +905,73 @@ FIMXE: document how to use `ein:notebooklist-find-file-callback'
;;;###autoload
(defun ein:notebooklist-login (url-or-port password &optional retry-p)
"Login to IPython notebook server."
(defun ein:notebooklist-login (url-or-port password callback &optional retry-p)
"Login to URL-OR-PORT with PASSWORD with notebooklist-open CALLBACK of arity 0."
(interactive (list (ein:notebooklist-ask-url-or-port)
(read-passwd "Password: ")))
(ein:query-singleton-ajax
(list 'notebooklist-login url-or-port)
(ein:url url-or-port "login")
:type "POST"
:data (concat "password=" (url-hexify-string password))
:parser #'ein:notebooklist-login--parser
:error (apply-partially #'ein:notebooklist-login--error url-or-port password retry-p)
:success (apply-partially #'ein:notebooklist-login--success url-or-port)))
(if noninteractive
;; noninteractive for testing only
(multiple-value-bind (url-or-port token)
(ein:jupyter-server-conn-info) token)
(read-passwd "Password: "))
nil))
(if password
(ein:query-singleton-ajax
(list 'notebooklist-login url-or-port)
(ein:url url-or-port "login")
:type "POST"
:data (concat "password=" (url-hexify-string password))
:parser #'ein:notebooklist-login--parser
:complete (apply-partially #'ein:notebooklist-login--complete url-or-port)
:error (apply-partially #'ein:notebooklist-login--error url-or-port password callback retry-p)
:success (apply-partially #'ein:notebooklist-login--success url-or-port callback))
(ein:log 'verbose "Skipping formal login for lack of token")
(funcall callback)))
(defun ein:notebooklist-login--parser ()
(goto-char (point-min))
(list :bad-page (re-search-forward "<input type=.?password" nil t)))
(defun ein:notebooklist-login--success-1 (url-or-port)
(ein:log 'info "Login to %s complete. \
Now you can open notebook list by `ein:notebooklist-open'." url-or-port))
(defun ein:notebooklist-login--success-1 (url-or-port callback)
(ein:log 'info "Login to %s complete." url-or-port)
(funcall callback))
(defun ein:notebooklist-login--error-1 (url-or-port)
(ein:log 'info "Failed to login to %s" url-or-port))
(defun* ein:notebooklist-login--success (url-or-port &key
data
(defun* ein:notebooklist-login--complete (url-or-port &key data response
&allow-other-keys
&aux (resp-string (format "STATUS: %s DATA: %s" (request-response-status-code response) data)))
(ein:log 'debug "ein:notebooklist-login--complete %s" resp-string))
(defun* ein:notebooklist-login--success (url-or-port callback
&key data
&allow-other-keys)
(if (plist-get data :bad-page)
(ein:notebooklist-login--error-1 url-or-port)
(ein:notebooklist-login--success-1 url-or-port)))
(ein:notebooklist-login--success-1 url-or-port callback)))
(defun* ein:notebooklist-login--error
(url-or-port password retry-p &key
(url-or-port password callback retry-p &key
data
symbol-status
response
&allow-other-keys
&aux
(response-status (request-response-status-code response)))
(if (and (eq response-status 403)
(not retry-p))
(ein:notebooklist-login url-or-port password t))
(if (or
;; workaround for url-retrieve backend
(and (eq symbol-status 'timeout)
(equal response-status 302)
(request-response-header response "set-cookie"))
;; workaround for curl backend
(and (equal response-status 405)
(ein:aand (car (request-response-history response))
(request-response-header it "set-cookie"))))
(ein:notebooklist-login--success-1 url-or-port)
(ein:notebooklist-login--error-1 url-or-port)))
(cond ((and (eq response-status 403)
(not retry-p))
(ein:notebooklist-login url-or-port password callback t))
((or
;; workaround for url-retrieve backend
(and (eq symbol-status 'timeout)
(eq response-status 302)
(request-response-header response "set-cookie"))
;; workaround for curl backend
(and (eq response-status 405)
(ein:aand (car (request-response-history response))
(request-response-header it "set-cookie"))))
(ein:notebooklist-login--success-1 url-or-port callback))
(t (ein:notebooklist-login--error-1 url-or-port))))
;;;###autoload

View file

@ -28,6 +28,7 @@
(eval-when-compile (require 'cl))
(require 'eieio)
(require 'ein-notebook)
(require 'ein-core)
(require 'ein-classes)
(require 'ein-events)

View file

@ -29,6 +29,7 @@
(require 'ein-core)
(require 'ein-events)
(require 'view)
;; FIXME: Make a class with `:get-notebook-name' slot like `ein:worksheet'

View file

@ -32,6 +32,7 @@
(declare-function ses-command-hook "ses")
(require 'ein-kernel)
(require 'ein-notebook)
(defun ein:goto-file (filename lineno &optional other-window)
"Jump to file FILEAME at line LINENO.

View file

@ -96,7 +96,7 @@ When this option is enabled, cached omni completion is available."
(ein:use-ac-backend (require 'ein-ac)
(ein:ac-config-once ein:use-auto-complete-superpack))
(ein:use-ac-jedi-backend (require 'ein-jedi)
(jedi:setup)
;; (jedi:setup) ;; need tkf/emacs-jedi submodule
(ein:jedi-setup)
(ein:ac-config-once ein:use-auto-complete-superpack))
(ein:use-company-backend (require 'ein-company)

View file

@ -32,6 +32,7 @@
(require 'ansi-color)
(require 'ein-core)
(require 'ein-shared-output)
(defclass ein:traceback ()
((tb-data :initarg :tb-data :type list)

View file

@ -30,6 +30,7 @@
(require 'json)
(require 's)
(require 'dash)
(require 'url)
;;; Macros and core functions/variables
@ -182,23 +183,23 @@ at point, i.e. any word before then \"(\", if it is present."
(push subtree list)))))
(traverse tree))
(nreverse list)))
;;; URL utils
(defvar ein:url-localhost "127.0.0.1")
(defvar ein:url-localhost-template "http://127.0.0.1:%s")
(defun ein:url (url-or-port &rest paths)
(loop with url = (if (integerp url-or-port)
(format ein:url-localhost-template url-or-port)
url-or-port)
for p in paths
do (setq url (concat (ein:trim-right url "/")
"/"
(ein:trim-left p "/")))
finally return url))
(if (null url-or-port)
nil
(if (or (integerp url-or-port)
(and (stringp url-or-port) (string-match "^[0-9]+$" url-or-port)))
(setq url-or-port (format "http://localhost:%s" url-or-port)))
(let ((parsed-url (url-generic-parse-url url-or-port)))
(if (or (null (url-host parsed-url)) (string= (url-host parsed-url) "localhost"))
(setf (url-host parsed-url) ein:url-localhost))
(loop with url = (url-recreate-url parsed-url)
for p in paths
do (setq url (concat (file-name-as-directory url) (ein:trim-left (directory-file-name p) "/")))
finally return (directory-file-name url)))))
(defun ein:url-no-cache (url)
"Imitate `cache=false' of `jQuery.ajax'.

View file

@ -36,6 +36,8 @@
(require 'cl)
(require 'ein-notebook)
(require 'ein-shared-output)
(require 'org-src)
(require 'org-element)
(require 'ein-utils)
(require 'python)
@ -86,7 +88,7 @@
(case key
((svg image/svg)
(let ((file (or file (ein:temp-inline-image-info value))))
(ein:write-base64-decoded-image value file)
(ein:write-base64-image value file)
(format "[[file:%s]]" file)))
((png image/png jpeg image/jpeg)
(let ((file (or file (ein:temp-inline-image-info value))))

View file

@ -54,16 +54,25 @@
(ein:testing-save-buffer ein:log-all-buffer-name ein:testing-dump-file-log)
(ein:testing-save-buffer request-log-buffer-name ein:testing-dump-file-request))
(defun ein:testing-wait-until (predicate &optional predargs ms interval)
(defun ein:testing-flush-queries (&optional ms interval continue)
"Forget all the deferred:flush-queue! and deferred:sync! and all the semaphore
callbacks. This is what I need."
(ein:testing-wait-until (lambda ()
(ein:query-gc-running-process-table)
(zerop (hash-table-count ein:query-running-process-table)))
nil ms interval continue))
(defun ein:testing-wait-until (predicate &optional predargs ms interval continue)
"Wait until PREDICATE function returns non-`nil'.
PREDARGS is argument list for the PREDICATE function.
MS is milliseconds to wait. INTERVAL is polling interval in milliseconds."
(let* ((interval (or interval 300))
(count (max 1 (if ms (truncate (/ ms interval)) 25))))
(unless (loop repeat count
when (apply predicate predargs)
return t
do (sleep-for 0 interval))
(let* ((int (ein:aif interval it (ein:aif ms (max 300 (/ ms 10)) 300)))
(count (max 1 (if ms (truncate (/ ms int)) 25))))
(unless (or (loop repeat count
when (apply predicate predargs)
return t
do (sleep-for 0 int))
continue)
(error "Timeout: %s" predicate))))
(defadvice ert-run-tests-batch (after ein:testing-dump-logs-hook activate)

View file

@ -5,8 +5,12 @@
(require 'ein-utils)
(ert-deftest ein-url-simple ()
(should (null (ein:url nil)))
(should (equal (ein:url 8888) "http://127.0.0.1:8888"))
(should (equal (ein:url "http://localhost") "http://localhost")))
(should (equal (ein:url "http://localhost") "http://127.0.0.1"))
(should (equal (ein:url "https://localhost:8888") "https://127.0.0.1:8888"))
(loop for url in '("http://127.0.0.1:8888" "http://localhost:8888" "http://127.0.0.1:8888/" "http://localhost:8888/" "8888" 8888)
do (should (equal (ein:url "http://localhost:8888") (ein:url url)))))
(ert-deftest ein-url-slashes ()
(loop for a in '("a" "a/" "/a")
@ -14,7 +18,7 @@
do (should (equal (ein:url 8888 a b)
"http://127.0.0.1:8888/a/b")))
do (should (equal (ein:url 8888 a "b/")
"http://127.0.0.1:8888/a/b/"))))
"http://127.0.0.1:8888/a/b"))))
(ert-deftest ein-trim-simple ()
(should (equal (ein:trim "a") "a"))

View file

@ -20,7 +20,7 @@
(ein:log 'debug "TESTING-GET-NOTEBOOK-BY-NAME start")
(when path
(setq notebook-name (format "%s/%s" path notebook-name)))
(ein:notebooklist-open url-or-port path t)
(ein:notebooklist-open url-or-port path)
(ein:testing-wait-until (lambda () (and (bufferp (get-buffer (format ein:notebooklist-buffer-name-template url-or-port)))
(ein:notebooklist-get-buffer url-or-port))))
(with-current-buffer (ein:notebooklist-get-buffer url-or-port)
@ -57,7 +57,7 @@
(defun ein:testing-delete-notebook (url-or-port notebook &optional path)
(ein:log 'debug "TESTING-DELETE-NOTEBOOK start")
(ein:notebooklist-open url-or-port (ein:$notebook-notebook-path notebook) t)
(ein:notebooklist-open url-or-port (ein:$notebook-notebook-path notebook))
(ein:testing-wait-until (lambda ()
(bufferp (get-buffer (format ein:notebooklist-buffer-name-template url-or-port)))))
(with-current-buffer (ein:notebooklist-get-buffer url-or-port)
@ -66,6 +66,7 @@
(ein:notebooklist-delete-notebook (ein:$notebook-notebook-path notebook)))
(ein:log 'debug "TESTING-DELETE-NOTEBOOK end"))
;; (ert-deftest 00-jupyter-start-server ()
;; (ein:log 'verbose "ERT TESTING-JUPYTER-START-SERVER start")
;; (condition-case err
@ -234,8 +235,7 @@ See the definition of `create-image' for how it works."
(ein:testing-wait-until
(lambda () (ein:aand (ein:$notebook-kernel notebook)
(ein:kernel-live-p it))))
(cl-letf (((symbol-function 'y-or-n-p) (lambda (prompt) t)))
(ein:jupyter-server-stop t ein:testing-dump-file-server))
(ein:jupyter-server-stop t ein:testing-dump-file-server)
(should-not (processp %ein:jupyter-server-session%))
(cl-flet ((orphans-find (pid) (search (ein:$kernel-kernel-id (ein:$notebook-kernel notebook)) (alist-get 'args (process-attributes pid)))))
(should-not (loop repeat 10

View file

@ -31,10 +31,11 @@
(ein:dev-start-debug)
(deferred:sync! (ein:jupyter-server-start *ein:testing-jupyter-server-command* *ein:testing-jupyter-server-directory*))
;; (ein:testing-wait-until (lambda () (not (null (ein:notebooklist-list))))
;; nil 120000 5000)
(ein:testing-wait-until (lambda () (ein:notebooklist-list)) nil 15000 1000)
(multiple-value-bind (url token) (ein:jupyter-server-conn-info)
(ein:log 'info (format "testing-start-server url: %s, token: %s" url token))
(setq *ein:testing-port* url)
(setq *ein:testing-token* token)
(ein:log 'info "testing-start-server succesfully logged in."))
(fset 'y-or-n-p (lambda (prompt) nil))