From bc10cea743ffe6f5249048a39dce9eacd24542bc Mon Sep 17 00:00:00 2001 From: dickmao Date: Thu, 11 Oct 2018 16:53:02 -0400 Subject: [PATCH] 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. --- .travis.yml | 5 +- Cask | 7 + Makefile | 7 +- features/notebooklist.feature | 11 ++ features/step-definitions/ein-steps.el | 35 ++++- features/support/env.el | 31 ++--- features/undo.feature | 22 +-- lisp/ein-cell-edit.el | 3 + lisp/ein-company.el | 4 +- lisp/ein-connect.el | 3 +- lisp/ein-contents-api.el | 8 +- lisp/ein-file.el | 1 + lisp/ein-inspector.el | 2 + lisp/ein-jedi.el | 3 +- lisp/ein-jupyter.el | 49 +++---- lisp/ein-loaddefs.el | 62 ++++----- lisp/ein-log.el | 5 +- lisp/ein-multilang.el | 1 + lisp/ein-notebook.el | 38 +++--- lisp/ein-notebooklist.el | 180 ++++++++++++++----------- lisp/ein-notification.el | 1 + lisp/ein-pager.el | 1 + lisp/ein-pytools.el | 1 + lisp/ein-subpackages.el | 2 +- lisp/ein-traceback.el | 1 + lisp/ein-utils.el | 25 ++-- lisp/ob-ein.el | 4 +- test/ein-testing.el | 23 +++- test/test-ein-utils.el | 8 +- test/test-func.el | 8 +- test/testfunc.el | 5 +- 31 files changed, 322 insertions(+), 234 deletions(-) diff --git a/.travis.yml b/.travis.yml index f2a7e09..e874cec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 ) diff --git a/Cask b/Cask index b207851..d155781 100644 --- a/Cask +++ b/Cask @@ -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") diff --git a/Makefile b/Makefile index 3baf3e5..33279fa 100644 --- a/Makefile +++ b/Makefile @@ -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: diff --git a/features/notebooklist.feature b/features/notebooklist.feature index b0ae0a6..fb69d4c 100644 --- a/features/notebooklist.feature +++ b/features/notebooklist.feature @@ -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]" diff --git a/features/step-definitions/ein-steps.el b/features/step-definitions/ein-steps.el index 13bbbc8..5f10608 100644 --- a/features/step-definitions/ein-steps.el +++ b/features/step-definitions/ein-steps.el @@ -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))) diff --git a/features/support/env.el b/features/support/env.el index 8cb64de..68de14f 100644 --- a/features/support/env.el +++ b/features/support/env.el @@ -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")) diff --git a/features/undo.feature b/features/undo.feature index d0bd3d8..57399b9 100644 --- a/features/undo.feature +++ b/features/undo.feature @@ -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-" And I press "C-c " And I press "C-/" - Then the cursor should be at point "55" + Then the cursor should be at point "54" And I press "C-" And I press "C-" And I wait for cell to execute And I press "C-c " 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-" 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-" - 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 diff --git a/lisp/ein-cell-edit.el b/lisp/ein-cell-edit.el index 44069d0..119eae4 100644 --- a/lisp/ein-cell-edit.el +++ b/lisp/ein-cell-edit.el @@ -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) diff --git a/lisp/ein-company.el b/lisp/ein-company.el index c62bb96..46f78e5 100644 --- a/lisp/ein-company.el +++ b/lisp/ein-company.el @@ -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) diff --git a/lisp/ein-connect.el b/lisp/ein-connect.el index b7d5225..3d4ee8d 100644 --- a/lisp/ein-connect.el +++ b/lisp/ein-connect.el @@ -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") diff --git a/lisp/ein-contents-api.el b/lisp/ein-contents-api.el index 65d1af8..c657cc9 100644 --- a/lisp/ein-contents-api.el +++ b/lisp/ein-contents-api.el @@ -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) diff --git a/lisp/ein-file.el b/lisp/ein-file.el index d57de26..240240f 100644 --- a/lisp/ein-file.el +++ b/lisp/ein-file.el @@ -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) diff --git a/lisp/ein-inspector.el b/lisp/ein-inspector.el index 6ee0fa2..bff1946 100644 --- a/lisp/ein-inspector.el +++ b/lisp/ein-inspector.el @@ -25,6 +25,8 @@ ;;; Code: +(require 'ein-pytools) + ;;;###autoload (defun ein:inspect-object (kernel object) (interactive (list (ein:get-kernel-or-error) diff --git a/lisp/ein-jedi.el b/lisp/ein-jedi.el index bb4b945..259302a 100644 --- a/lisp/ein-jedi.el +++ b/lisp/ein-jedi.el @@ -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) diff --git a/lisp/ein-jupyter.el b/lisp/ein-jupyter.el index 8fa1be3..6396288 100644 --- a/lisp/ein-jupyter.el +++ b/lisp/ein-jupyter.el @@ -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)))))))) diff --git a/lisp/ein-loaddefs.el b/lisp/ein-loaddefs.el index 0e0fd0d..c2e5028 100644 --- a/lisp/ein-loaddefs.el +++ b/lisp/ein-loaddefs.el @@ -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)) ;;;*** diff --git a/lisp/ein-log.el b/lisp/ein-log.el index 752c38c..b220206 100644 --- a/lisp/ein-log.el +++ b/lisp/ein-log.el @@ -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)) diff --git a/lisp/ein-multilang.el b/lisp/ein-multilang.el index 8b75da8..e94a409 100644 --- a/lisp/ein-multilang.el +++ b/lisp/ein-multilang.el @@ -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 diff --git a/lisp/ein-notebook.el b/lisp/ein-notebook.el index 5bf1412..e00b727 100644 --- a/lisp/ein-notebook.el +++ b/lisp/ein-notebook.el @@ -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) diff --git a/lisp/ein-notebooklist.el b/lisp/ein-notebooklist.el index 0d350ab..93f87b1 100644 --- a/lisp/ein-notebooklist.el +++ b/lisp/ein-notebooklist.el @@ -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 "