(require 'widget)
(require 'cus-edit)
(require 'ein-core)
(require 'ein-contents-api)
(require 'deferred)
(require 'dash)
(require 'ido)
(declare-function ein:jupyter-crib-token "ein-jupyter")
(declare-function ein:jupyter-get-default-kernel "ein-jupyter")
(declare-function ein:jupyter-crib-running-servers "ein-jupyter")
(declare-function ein:file-open "ein-file")
(autoload 'ein:get-notebook "ein-notebook")
(defcustom ein:notebooklist-login-timeout (truncate (* 6.3 1000))
"Timeout in milliseconds for logging into server"
:group 'ein
:type 'integer)
(make-obsolete-variable 'ein:notebooklist-first-open-hook nil "0.17.0")
(cl-defstruct ein:$notebooklist
"Hold notebooklist variables.
`ein:$notebooklist-url-or-port'
URL or port of IPython server.
`ein:$notebooklist-path'
The path for the notebooklist.
`ein:$notebooklist-data'
JSON data sent from the server.
`ein:$notebooklist-api-version'
Major version of the IPython notebook server we are talking to."
url-or-port
path
data
api-version)
(define-obsolete-variable-alias 'ein:notebooklist 'ein:%notebooklist% "0.1.2")
(ein:deflocal ein:%notebooklist% nil
"Buffer local variable to store an instance of `ein:$notebooklist'.")
(ein:deflocal ein:%notebooklist-new-kernel% nil
"Buffer local variable to store kernel type for newly created notebooks.")
(defcustom ein:notebooklist-sort-field :name
"The notebook list sort field."
:type '(choice (const :tag "Name" :name)
(const :tag "Last modified" :last_modified))
:group 'ein)
(defcustom ein:notebooklist-sort-order :ascending
"The notebook list sort order."
:type '(choice (const :tag "Ascending" :ascending)
(const :tag "Descending" :descending))
:group 'ein)
(defvar ein:notebooklist-buffer-name-template "*ein:notebooklist %s*")
(defvar ein:notebooklist-map (make-hash-table :test 'equal)
"Data store for `ein:notebooklist-list'.
Mapping from URL-OR-PORT to an instance of `ein:$notebooklist'.")
(defun ein:notebooklist-keys ()
"Get a list of registered server urls."
(hash-table-keys ein:notebooklist-map))
(defun ein:notebooklist-list ()
"Get a list of opened `ein:$notebooklist'."
(hash-table-values ein:notebooklist-map))
(defun ein:notebooklist-list-remove (url-or-port)
(remhash url-or-port ein:notebooklist-map))
(defun ein:notebooklist-list-add (nblist)
"Register notebook list instance NBLIST for global lookup.
This function adds NBLIST to `ein:notebooklist-map'."
(puthash (ein:$notebooklist-url-or-port nblist)
nblist
ein:notebooklist-map))
(defun ein:notebooklist-list-get (url-or-port)
"Get an instance of `ein:$notebooklist' by URL-OR-PORT as a key."
(gethash url-or-port ein:notebooklist-map))
(defsubst ein:notebooklist-url (url-or-port &rest paths)
(apply #'ein:url url-or-port "api/contents" paths))
(defun ein:notebooklist-sentinel (url-or-port process event)
"Remove URL-OR-PORT from ein:notebooklist-map when PROCESS dies"
(when (not (string= "open" (substring event 0 4)))
(ein:log 'info "Process %s %s %s"
(car (process-command process))
(replace-regexp-in-string "\n$" "" event)
url-or-port)
(ein:notebooklist-list-remove url-or-port)))
(defun ein:notebooklist-get-buffer (url-or-port)
(get-buffer-create
(format ein:notebooklist-buffer-name-template url-or-port)))
(defun ein:notebooklist-token-or-password (url-or-port)
"Return token or password for URL-OR-PORT.
Jupyter requires one or the other but not both.
Return empty string token if all authentication disabled.
Return nil if unclear what, if any, authentication applies."
(cl-multiple-value-bind (password-p token) (ein:jupyter-crib-token url-or-port)
(cond ((eq password-p t) (read-passwd (format "Password for %s: " url-or-port)))
((and (stringp token) (eq password-p :json-false)) token)
(t nil))))
(defun ein:notebooklist-ask-url-or-port ()
(let* ((default (ein:url (aif (ein:get-notebook)
(ein:$notebook-url-or-port it)
(aif ein:%notebooklist%
(ein:$notebooklist-url-or-port it)))))
(url-or-port-list
(-distinct (mapcar #'ein:url
(append (when default (list default))
(if (stringp ein:urls)
(list ein:urls)
ein:urls)
(mapcar
(lambda (json)
(cl-destructuring-bind (&key url &allow-other-keys)
json
(ein:url url)))
(ein:jupyter-crib-running-servers))))))
(url-or-port (let (ido-report-no-match ido-use-faces)
(ein:completing-read "URL or port: "
url-or-port-list
nil nil nil nil
(car-safe url-or-port-list)))))
(ein:url url-or-port)))
(defsubst ein:notebooklist-canonical-url-or-port (url-host username)
"Canonicalize.
For the record,
https://hub.data8x.berkeley.edu
needs to look like
https://hub.data8x.berkeley.edu/user/1dcdab3c2f59736806b85af865a1a28d"
(ein:url url-host "user" username))
(cl-defun ein:notebooklist-open* (url-or-port &optional path resync callback errback hub-p
&aux (canonical-p (not hub-p)) tokens-key)
"Workhorse of `ein:login'.
A notebooklist can be opened from any PATH within the server root hierarchy.
PATH is empty at the root. RESYNC, when non-nil, requeries the contents-api
version and kernelspecs.
Full jupyterhub url is https://hub.data8x.berkeley.edu/user/1dcdab3c2f59736806b85af865a1a28d/?token=c421c6863ddb4e7ea5a311c31c948cd0
URL-HOST is hub.data8x.berkeley.edu
USERNAME is 1dcdab3c2f59736806b85af865a1a28d
TOKEN is c421c6863ddb4e7ea5a311c31c948cd0
CALLBACK takes two arguments, the resulting buffer and URL-OR-PORT.
ERRBACK takes one argument, the resulting buffer."
(setq path (or path ""))
(if (and (not resync) (ein:notebooklist-list-get url-or-port))
(ein:content-query-contents
url-or-port path
(apply-partially #'ein:notebooklist-open--finish url-or-port callback)
errback)
(when hub-p
(let* ((parsed-url (url-generic-parse-url url-or-port))
(url-host (url-host parsed-url))
(cookies (ein:query-get-cookies url-host "/user/"))
(previous-users
(mapcar
(lambda (entry)
(file-name-nondirectory (directory-file-name (plist-get entry :path))))
cookies))
(pq (url-path-and-query parsed-url))
(path0 (car pq))
(query (cdr pq))
(_ (setf canonical-p
(and (stringp path0)
(string-match "user/\\([a-z0-9]+\\)" path0))))
(username (if canonical-p
(match-string-no-properties 1 path0)
(read-no-blanks-input "User: " (car previous-users))))
(_ (setf url-or-port
(ein:notebooklist-canonical-url-or-port url-host username)))
(_ (setf tokens-key
(ein:query-divine-authorization-tokens-key url-or-port)))
(token
(if (and (stringp query)
(string-match "token=\\([a-z0-9]+\\)" query))
(prog1
(match-string-no-properties 1 query)
(cl-assert canonical-p))
(when canonical-p
(read-no-blanks-input "Token: ")))))
(when token
(setf (gethash tokens-key ein:query-authorization-tokens) token))))
(if (not canonical-p)
(progn
(ein:notebooklist-list-remove url-or-port)
(ein:notebooklist-login--iteration url-or-port callback errback nil -1 nil))
(when tokens-key
(let ((belay-tokens
(lambda (&rest _args)
(remhash tokens-key ein:query-authorization-tokens))))
(add-function :before (var errback) belay-tokens)
(add-function :before (var callback) belay-tokens)))
(ein:query-notebook-api-version
url-or-port
(lambda ()
(ein:query-kernelspecs
url-or-port
(lambda ()
(deferred:$
(deferred:next
(lambda ()
(ein:content-query-hierarchy url-or-port))))
(ein:content-query-contents
url-or-port path
(apply-partially #'ein:notebooklist-open--finish url-or-port callback)
errback))))))))
(make-obsolete-variable 'ein:notebooklist-keepalive-refresh-time nil "0.17.0")
(make-obsolete-variable 'ein:enable-keepalive nil "0.17.0")
(defcustom ein:notebooklist-date-format "%F"
"The format spec for date in notebooklist mode.
See `ein:format-time-string'."
:type '(or string function)
:group 'ein)
(defun ein:notebooklist-open--finish (url-or-port callback content)
"Called via `ein:notebooklist-open*'."
(ein:log 'verbose "Opening notebooklist at %s"
(ein:url url-or-port (ein:$content-path content)))
(with-current-buffer (ein:notebooklist-get-buffer url-or-port)
(ein:notebooklist-mode)
(let ((restore-point (aand (widget-at)
(awhen (widget-value it)
(and (stringp it) it))
(string-match-p "Open\\|Stop\\|Delete" it)
(point))))
(awhen ein:%notebooklist%
(ein:notebooklist-list-remove (ein:$notebooklist-url-or-port it)))
(setq ein:%notebooklist%
(make-ein:$notebooklist :url-or-port url-or-port
:path (ein:$content-path content)
:data (ein:$content-raw-content content)
:api-version (ein:$content-notebook-api-version content)))
(ein:notebooklist-list-add ein:%notebooklist%)
(let ((inhibit-read-only t))
(erase-buffer))
(when callback
(funcall callback (current-buffer) url-or-port))
(ein:content-query-sessions url-or-port (apply-partially #'ein:notebooklist-render
url-or-port
restore-point))
(current-buffer))))
(cl-defun ein:notebooklist-open-error (url-or-port path
&key error-thrown &allow-other-keys)
(ein:log 'error
"ein:notebooklist-open-error %s: ERROR %s DATA %s" (concat (file-name-as-directory url-or-port) path) (car error-thrown) (cdr error-thrown)))
(defun ein:notebooklist-reload (&optional nblist resync callback)
"Reload current Notebook list."
(interactive)
(setq nblist (or nblist ein:%notebooklist%))
(ein:notebooklist-open* (ein:$notebooklist-url-or-port nblist)
(ein:$notebooklist-path nblist) resync callback))
(defun ein:notebooklist-new-notebook (url-or-port kernelspec &optional callback no-pop retry explicit-path)
(interactive (list (ein:notebooklist-ask-url-or-port)
(ein:completing-read
"Select kernel: "
(ein:list-available-kernels
(ein:$notebooklist-url-or-port ein:%notebooklist%))
nil t nil nil "default" nil)))
(let* ((notebooklist (ein:notebooklist-list-get url-or-port))
(path (or explicit-path (ein:$notebooklist-path notebooklist)))
(url (ein:notebooklist-url url-or-port path)))
(ein:query-singleton-ajax
url
:type "POST"
:data (ein:json-encode '((type . "notebook")))
:headers (list (cons "Content-Type" "application/json"))
:parser #'ein:json-read
:error (apply-partially #'ein:notebooklist-new-notebook-error
url-or-port kernelspec callback no-pop retry explicit-path)
:success (apply-partially #'ein:notebooklist-new-notebook-success
url-or-port kernelspec
path
callback no-pop))))
(cl-defun ein:notebooklist-new-notebook-success (url-or-port
kernelspec
path
callback
no-pop
&key data
&allow-other-keys)
(let ((nbpath (plist-get data :path)))
(ein:notebook-open url-or-port nbpath kernelspec callback nil no-pop)
(ein:notebooklist-open* url-or-port path)))
(cl-defun ein:notebooklist-new-notebook-error
(url-or-port kernelspec callback no-pop retry explicit-path
&key symbol-status error-thrown &allow-other-keys)
(let ((notice (format "ein:notebooklist-new-notebook-error: %s %s"
symbol-status error-thrown)))
(if retry
(ein:log 'error notice)
(ein:log 'info notice)
(sleep-for 0 1500)
(ein:notebooklist-new-notebook url-or-port kernelspec callback no-pop t explicit-path))))
(defun ein:notebooklist-new-notebook-with-name
(url-or-port kernelspec name &optional callback no-pop)
"Upon notebook-open, rename the notebook, then funcall CALLBACK."
(interactive
(let ((url-or-port (ein:get-url-or-port)))
(unless url-or-port
(error "ein:notebooklist-new-notebook-with-name: no server context"))
(let ((kernelspec (ein:completing-read
"Select kernel: "
(ein:list-available-kernels url-or-port)
nil t nil nil "default" nil))
(name (read-from-minibuffer
(format "Notebook name (at %s): " url-or-port))))
(list url-or-port kernelspec name))))
(unless callback
(setq callback #'ignore))
(add-function :before (var callback)
(apply-partially
(lambda (name* notebook _created)
(with-current-buffer (ein:notebook-buffer notebook)
(ein:notebook-rename-command name*)))
name))
(ein:notebooklist-new-notebook url-or-port kernelspec callback no-pop))
(defun ein:notebooklist-delete-notebook (_notebooklist url-or-port path &optional callback)
"CALLBACK with no arguments, e.g., semaphore"
(setq callback (or callback #'ignore))
(dolist (buf (seq-filter (lambda (b)
(with-current-buffer b
(aif (ein:get-notebook)
(string= path (ein:$notebook-notebook-path it)))))
(buffer-list)))
(cl-letf (((symbol-function 'y-or-n-p) (lambda (&rest _args) nil)))
(kill-buffer buf)))
(if (ein:notebook-opened-notebooks (lambda (nb)
(string= path
(ein:$notebook-notebook-path nb))))
(ein:log 'error "ein:notebooklist-delete-notebook: cannot close %s" path)
(let ((delete-nb
(apply-partially
(lambda (url* settings* _kernel)
(apply #'ein:query-singleton-ajax url* settings*))
(ein:notebooklist-url url-or-port path)
(list :type "DELETE"
:complete (apply-partially
#'ein:notebooklist-delete-notebook--complete
(ein:url url-or-port path) callback)))))
(ein:message-whir
"Ending session" (var delete-nb)
(ein:kernel-delete-session delete-nb
:url-or-port url-or-port
:path path)))))
(cl-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 callback (funcall callback)))
(defun generate-breadcrumbs (path)
"Given notebooklist path, generate alist of breadcrumps of form (name . path)."
(let* ((paths (split-string path "/" t))
(current-path "/")
(pairs (list (cons "Home" ""))))
(dolist (p paths pairs)
(setf current-path (concat current-path "/" p)
pairs (append pairs (list (cons p current-path)))))))
(cl-defun ein:nblist--sort-group (group by-param order)
(sort group #'(lambda (x y)
(cond ((eq order :ascending)
(string-lessp (plist-get x by-param)
(plist-get y by-param)))
((eq order :descending)
(string-greaterp (plist-get x by-param)
(plist-get y by-param)))))))
(defun ein:notebooklist--order-data (nblist-data sort-param sort-order)
"Try to sanely sort the notebooklist data for the current path."
(let* ((groups (-group-by (lambda (x) (plist-get x :type)) nblist-data))
(dirs (ein:nblist--sort-group (cdr (assoc "directory" groups))
sort-param
sort-order))
(nbs (ein:nblist--sort-group (cdr (assoc "notebook" groups))
sort-param
sort-order))
(files (ein:nblist--sort-group
(-flatten-n 1 (-map #'cdr (-group-by
#'(lambda (x) (car (last (split-string (plist-get x :name) "\\."))))
(cdr (assoc "file" groups)))))
sort-param
sort-order)))
(-concat dirs nbs files)))
(defun render-header (url-or-port &rest _args)
(with-current-buffer (ein:notebooklist-get-buffer url-or-port)
(widget-insert
(format "Contents API %s (%s)\n\n"
(ein:need-notebook-api-version url-or-port)
url-or-port))
(let ((breadcrumbs (generate-breadcrumbs
(ein:$notebooklist-path ein:%notebooklist%))))
(dolist (p breadcrumbs)
(let ((url-or-port url-or-port)
(name (car p))
(path (cdr p)))
(widget-insert " | ")
(widget-create
'link
:notify (lambda (&rest _ignore)
(ein:notebooklist-open* url-or-port path nil
(lambda (buffer _url-or-port)
(pop-to-buffer buffer))))
name)))
(widget-insert " |\n\n"))
(let* ((url-or-port url-or-port)
(kernels (ein:list-available-kernels url-or-port)))
(widget-create
'link
:notify (lambda (&rest _ignore) (ein:notebooklist-new-notebook
url-or-port
ein:%notebooklist-new-kernel%))
"New Notebook")
(widget-insert " ")
(widget-create
'link
:notify (lambda (&rest _ignore) (ein:notebooklist-reload nil t))
"Resync")
(widget-insert " ")
(widget-create
'link
:notify (lambda (&rest _ignore)
(browse-url (ein:url url-or-port)))
"Open In Browser")
(widget-insert "\n\nCreate New Notebooks Using Kernel:\n")
(let ((radio-widget
(widget-create
'radio-button-choice
:notify (lambda (widget &rest _args)
(let ((update (ein:get-kernelspec url-or-port
(widget-value widget))))
(unless (equal ein:%notebooklist-new-kernel% update)
(when ein:%notebooklist-new-kernel%
(message "New notebooks started with %s kernel"
(ein:$kernelspec-display-name update)))
(setq ein:%notebooklist-new-kernel% update)))))))
(if kernels
(let ((initial (ein:jupyter-get-default-kernel kernels)))
(dolist (k kernels)
(let ((child (widget-radio-add-item
radio-widget
(list 'item
:value (car k)
:format (format "%s\n" (cdr k))))))
(when (string= initial (car k))
(widget-apply-action (widget-get child :button)))))
(widget-insert "\n"))
(widget-insert "\n No kernels found\n"))))))
(defun ein:format-nbitem-data (name last-modified)
(let ((dt (date-to-time last-modified)))
(format "%-40s%+20s" name
(ein:format-time-string ein:notebooklist-date-format dt))))
(defun render-directory (url-or-port sessions)
(with-current-buffer (ein:notebooklist-get-buffer url-or-port)
(cl-loop with reloader = (apply-partially (lambda (nblist _kernel)
(ein:notebooklist-reload nblist))
ein:%notebooklist%)
for note in (ein:notebooklist--order-data
(ein:$notebooklist-data ein:%notebooklist%)
ein:notebooklist-sort-field
ein:notebooklist-sort-order)
for name = (plist-get note :name)
for path = (plist-get note :path)
for last-modified = (plist-get note :last_modified)
for type = (plist-get note :type)
do (ein:notebook-get-opened-notebook url-or-port path)
if (string= type "directory")
do (progn (widget-create
'link
:notify (let ((url-or-port url-or-port)
(name name))
(lambda (&rest _ignore)
(ein:notebooklist-open* url-or-port
(concat (file-name-as-directory
(ein:$notebooklist-path ein:%notebooklist%))
name)
nil
(lambda (buffer _url-or-port) (pop-to-buffer buffer)))))
"Dir")
(widget-insert " : " name)
(widget-insert "\n"))
end
if (string= type "file")
do (progn (widget-create
'link
:notify (apply-partially
(lambda (url-or-port* path* &rest _args)
(ein:file-open url-or-port* path*))
url-or-port path)
"Open")
(widget-insert " ")
(widget-insert " : " (ein:format-nbitem-data name last-modified))
(widget-insert "\n"))
end
if (string= type "notebook")
do (progn (widget-create
'link
:notify (apply-partially
(lambda (url-or-port* path* &rest _args)
(ein:notebook-open url-or-port* path*))
url-or-port path)
"Open")
(widget-insert " ")
(if (gethash path sessions)
(widget-create
'link
:notify
(apply-partially
(cl-function
(lambda (url-or-port*
path*
&rest _ignore
&aux (callback (lambda (_kernel) t)))
(ein:message-whir
"Ending session" (var callback)
(ein:kernel-delete-session callback
:url-or-port url-or-port*
:path path*))))
url-or-port path)
"Stop")
(widget-insert "[----]"))
(widget-insert " ")
(widget-create
'link
:notify (apply-partially
(lambda (notebooklist* url-or-port* path* callback*
&rest _args)
(when (or noninteractive
(y-or-n-p (format "Delete notebook %s?" path*)))
(ein:notebooklist-delete-notebook
notebooklist* url-or-port* path*
(apply-partially callback* nil))))
ein:%notebooklist% url-or-port path reloader)
"Delete")
(widget-insert " : " (ein:format-nbitem-data name last-modified))
(widget-insert "\n"))
end)))
(defun ein:notebooklist-render (url-or-port restore-point sessions)
(with-current-buffer (ein:notebooklist-get-buffer url-or-port)
(if (not (ein:$notebooklist-path ein:%notebooklist%))
(ein:log 'error "ein:notebooklist-render: cannot render null")
(render-header url-or-port sessions)
(render-directory url-or-port sessions)
(widget-setup)
(awhen (get-buffer-window (current-buffer))
(set-window-point it (or restore-point (point-min)))))))
(defun ein:notebooklist-list-paths (&optional content-type)
"Return all files of CONTENT-TYPE for all sessions"
(apply #'append
(cl-loop for nblist in (ein:notebooklist-list)
for url-or-port = (ein:$notebooklist-url-or-port nblist)
collect
(cl-loop for content in (ein:content-need-hierarchy url-or-port)
when (or (null content-type)
(string= (ein:$content-type content) content-type))
collect (ein:url url-or-port (ein:$content-path content))))))
(defun ein:notebooklist-parse-nbpath (nbpath)
"Return `(,url-or-port ,path) from URL-OR-PORT/PATH"
(cl-loop for url-or-port in (ein:notebooklist-keys)
if (cl-search url-or-port nbpath :end2 (length url-or-port))
return (list (substring nbpath 0 (length url-or-port))
(substring nbpath (1+ (length url-or-port))))
end
finally (ein:display-warning
(format "%s not among: %s" nbpath (ein:notebooklist-keys))
:error)))
(defsubst ein:notebooklist-ask-path (&optional content-type)
(ein:completing-read (format "Open %s: " content-type)
(ein:notebooklist-list-paths content-type)
nil t))
(defun ein:notebooklist-load (&optional url-or-port)
"Load notebook list but do not pop-up the notebook list buffer.
For example, if you want to load notebook list when Emacs starts,
add this in the Emacs initialization file::
(add-to-hook 'after-init-hook 'ein:notebooklist-load)
or even this (if you want fast Emacs start-up)::
;; load notebook list if Emacs is idle for 3 sec after start-up
(run-with-idle-timer 3 nil #'ein:notebooklist-load)"
(ein:notebooklist-open* url-or-port))
(defun ein:notebooklist-login--iteration (url-or-port callback errback token iteration response-status)
(ein:log 'debug "Login attempt #%d in response to %s from %s."
iteration response-status url-or-port)
(setq callback (or callback #'ignore))
(setq errback (or errback #'ignore))
(let* ((reset-p (not response-status))
(request-curl-options (if reset-p
(cons "--junk-session-cookies" request-curl-options)
request-curl-options))
(parsed-url (url-generic-parse-url (file-name-as-directory url-or-port)))
(host (url-host parsed-url))
(query (cdr (url-path-and-query parsed-url))))
(when reset-p
(remhash host ein:query-xsrf-cache))
(ein:query-singleton-ajax
(ein:url url-or-port (if query "" "login"))
:timeout ein:notebooklist-login-timeout
:data (when (and token (not query)) (concat "password=" (url-hexify-string token)))
: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 token
callback errback iteration)
:success (apply-partially #'ein:notebooklist-login--success url-or-port callback
errback token iteration))))
(defun ein:notebooklist-open (url-or-port callback)
"This is now an alias for `ein:notebooklist-login'."
(interactive `(,(ein:notebooklist-ask-url-or-port)
,(lambda (buffer _url-or-port) (pop-to-buffer buffer))))
(ein:notebooklist-login url-or-port callback))
(make-obsolete 'ein:notebooklist-open 'ein:notebooklist-login "0.14.2")
(defalias 'ein:login 'ein:notebooklist-login)
(defun ein:notebooklist-login (url-or-port callback &optional cookie-name cookie-content token)
"Deal with security before main entry of ein:notebooklist-open*.
CALLBACK takes two arguments, the buffer created by
ein:notebooklist-open--success and the url-or-port argument of
ein:notebooklist-open*."
(interactive `(,(ein:notebooklist-ask-url-or-port)
,(lambda (buffer _url-or-port) (pop-to-buffer buffer))
,(when current-prefix-arg
(read-no-blanks-input "Cookie name: "))
,(when current-prefix-arg
(read-no-blanks-input "Cookie content: "))
nil))
(when cookie-name
(let* ((parsed-url (url-generic-parse-url (file-name-as-directory url-or-port)))
(domain (url-host parsed-url))
(securep (string-match "^wss://" url-or-port))
(line (mapconcat #'identity (list domain "FALSE" (car (url-path-and-query parsed-url)) (if securep "TRUE" "FALSE") "0" cookie-name (concat cookie-content "\n")) "\t")))
(write-region line nil (request--curl-cookie-jar) 'append)))
(let ((token (or token (ein:notebooklist-token-or-password url-or-port))))
(cond ((null token) (ein:notebooklist-login--iteration url-or-port callback nil nil -1 nil))
((string= token "") (ein:log 'verbose "Skipping login %s" url-or-port)
(ein:notebooklist-open* url-or-port nil nil callback nil))
(t
(ein:notebooklist-login--iteration url-or-port callback nil token 0 nil)))))
(defun ein:notebooklist-login--parser ()
(save-excursion
(goto-char (point-min))
(when (re-search-forward "<input type=.?password" nil t)
(list :reprompt t))))
(defun ein:notebooklist-login--success-1 (url-or-port callback errback &optional hub-p)
(ein:log 'info "Login to %s complete." url-or-port)
(ein:notebooklist-open* url-or-port nil nil callback errback hub-p))
(defun ein:notebooklist-login--error-1 (url-or-port error-thrown response errback)
(ein:log 'error "Login to %s failed, error-thrown %s, raw-header %s"
url-or-port
(subst-char-in-string ?\n ?\ (format "%s" error-thrown))
(request-response--raw-header response))
(funcall errback))
(cl-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))
(cl-defun ein:notebooklist-login--success
(url-or-port callback errback token iteration
&key data response error-thrown
&allow-other-keys &aux
(response-status (request-response-status-code response))
(hub-p (request-response-header response "x-jupyterhub-version")))
(if (plist-get data :reprompt)
(cond ((>= iteration 0)
(ein:notebooklist-login--error-1 url-or-port error-thrown response errback))
(hub-p (ein:notebooklist-open* url-or-port nil nil callback errback t))
(t (setq token (read-passwd (format "Password for %s: " url-or-port)))
(ein:notebooklist-login--iteration url-or-port callback errback token
(1+ iteration) response-status)))
(ein:notebooklist-login--success-1 url-or-port callback errback hub-p)))
(cl-defun ein:notebooklist-login--error
(url-or-port token callback errback iteration
&key _data response error-thrown
&allow-other-keys &aux
(response-status (request-response-status-code response))
(hub-p (request-response-header response "x-jupyterhub-version")))
(cond (hub-p
(if (< iteration 0)
(ein:notebooklist-login--iteration url-or-port callback errback
token (1+ iteration) response-status)
(if (and (eq response-status 405)) (ein:notebooklist-login--success-1 url-or-port callback errback hub-p)
(ein:notebooklist-login--error-1 url-or-port error-thrown response errback))))
((and response-status (< iteration 0))
(setq token (read-passwd (format "Password for %s: " url-or-port)))
(ein:notebooklist-login--iteration url-or-port callback errback token (1+ iteration) response-status))
((and (eq response-status 403) (< iteration 1))
(ein:notebooklist-login--iteration url-or-port callback errback token (1+ iteration) response-status))
(t (ein:notebooklist-login--error-1 url-or-port error-thrown response errback))))
(defun ein:get-url-or-port--notebooklist ()
(when (ein:$notebooklist-p ein:%notebooklist%)
(ein:$notebooklist-url-or-port ein:%notebooklist%)))
(defun ein:notebooklist-prev-item () (interactive) (move-beginning-of-line 0))
(defun ein:notebooklist-next-item () (interactive) (move-beginning-of-line 2))
(defvar ein:notebooklist-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map (make-composed-keymap widget-keymap
special-mode-map))
(define-key map "\C-c\C-r" 'ein:notebooklist-reload)
(define-key map "\C-c\C-f" 'ein:file-open)
(define-key map "\C-c\C-o" 'ein:notebook-open)
(define-key map "p" 'ein:notebooklist-prev-item)
(define-key map "n" 'ein:notebooklist-next-item)
map)
"Keymap for ein:notebooklist-mode.")
(easy-menu-define ein:notebooklist-menu ein:notebooklist-mode-map
"EIN Notebook List Mode Menu"
`("EIN Notebook List"
,@(ein:generate-menu
'(("Reload" ein:notebooklist-reload)
("New Notebook" ein:notebooklist-new-notebook)
("New Notebook (with name)"
ein:notebooklist-new-notebook-with-name)))))
(define-derived-mode ein:notebooklist-mode special-mode "ein:notebooklist"
"IPython notebook list mode.
Commands:
\\{ein:notebooklist-mode-map}"
(set (make-local-variable 'revert-buffer-function)
(lambda (&rest _args) (ein:notebooklist-reload))))
(provide 'ein-notebooklist)