Sort available kernelspecs; guess with ^; use guessing in jupyter-run-repl

This commit is contained in:
Tomasz Mieszkowski 2022-02-27 13:48:11 +01:00 committed by Nathaniel Nicandro
parent 0a7055d7b1
commit 7d20c0aee2
4 changed files with 62 additions and 18 deletions

View file

@ -45,10 +45,10 @@
"Get the available kernelspecs.
Return an alist mapping kernel names to (DIRECTORY . PLIST) pairs
where DIRECTORY is the resource directory of the kernel and PLIST
is its kernelspec plist. The alist is formed by parsing the
output of the shell command
is its kernelspec plist. The alist is formed by parsing and
sorting the output of the shell command
jupyter kernelspec list
jupyter kernelspec list --json
By default the available kernelspecs are cached. To force an
update of the cached kernelspecs, give a non-nil value to
@ -64,14 +64,16 @@ each DIRECTORY will be a remote file name."
(or (jupyter-command "kernelspec" "list" "--json")
(error "Can't obtain kernelspecs from jupyter shell command")))
:kernelspecs)))
(puthash
host (cl-loop
for (name spec) on specs by #'cddr
for dir = (concat (unless (equal host "local") host)
(plist-get spec :resource_dir))
collect (cons (substring (symbol-name name) 1)
(cons dir (plist-get spec :spec))))
jupyter--kernelspecs)))))
(puthash host
(sort (cl-loop
for (name spec) on specs by #'cddr
for dir = (concat (unless (equal host "local") host)
(plist-get spec :resource_dir))
collect (cons (substring (symbol-name name) 1)
(cons dir (plist-get spec :spec))))
(lambda (x y)
(string< (car x) (car y))))
jupyter--kernelspecs)))))
(defun jupyter-get-kernelspec (name &optional refresh)
"Get the kernelspec for a kernel named NAME.
@ -103,12 +105,12 @@ Optional argument REFRESH has the same meaning as in
(or specs (jupyter-available-kernelspecs refresh))))
(defun jupyter-guess-kernelspec (name &optional specs refresh)
"Return the first kernelspec matching NAME.
"Return the first kernelspec starting with NAME.
Raise an error if no kernelspec could be found.
SPECS and REFRESH have the same meaning as in
`jupyter-find-kernelspecs'."
(or (car (jupyter-find-kernelspecs name specs refresh))
(or (car (jupyter-find-kernelspecs (format "^%s" name) specs refresh))
(error "No valid kernelspec for kernel name (%s)" name)))
(defun jupyter-completing-read-kernelspec (&optional specs refresh)

View file

@ -2097,7 +2097,7 @@ completing all of the above.")
;;;###autoload
(defun jupyter-run-repl (kernel-name &optional repl-name associate-buffer client-class display)
"Run a Jupyter REPL connected to a kernel with name, KERNEL-NAME.
KERNEL-NAME will be passed to `jupyter-find-kernelspecs' and the
KERNEL-NAME will be passed to `jupyter-guess-kernelspec' and the
first kernel found will be used to start the new kernel.
With a prefix argument give a new REPL-NAME for the REPL.
@ -2128,10 +2128,6 @@ command on the host."
t nil t))
(or client-class (setq client-class 'jupyter-repl-client))
(jupyter-error-if-not-client-class-p client-class 'jupyter-repl-client)
(unless (called-interactively-p 'interactive)
(or (when-let* ((name (caar (jupyter-find-kernelspecs kernel-name))))
(setq kernel-name name))
(error "No kernel found for prefix (%s)" kernel-name)))
;; For `jupyter-start-new-kernel', we don't require this at top-level since
;; there are many ways to interact with a kernel, e.g. through a notebook
;; server, and we don't want to load any unnecessary files.

View file

@ -1912,6 +1912,24 @@ next(x"))))))
font-lock-extend-region-functions)))
(font-lock-ensure)))))
(ert-deftest jupyter-available-kernelspecs-sorting ()
:tags '(repl)
(jupyter-test-with-some-kernelspecs '("foo_qux" "qux" "bar_qux")
(let ((result (mapcar #'car (jupyter-available-kernelspecs t))))
(should (equal result (sort (copy-sequence result) #'string<))))))
(ert-deftest jupyter-run-repl-issue-371 ()
:tags '(repl)
(jupyter-test-with-some-kernelspecs '("foo_qux" "qux" "bar_qux")
(let ((client))
(unwind-protect
(progn
(setq client (jupyter-run-repl "qux"))
(should (equal (plist-get (jupyter-session-conn-info (oref client session))
:kernel_name)
"qux")))
(jupyter-test-kill-buffer (oref client buffer))))))
;;; `org-mode'
(defvar org-babel-jupyter-resource-directory nil)

View file

@ -365,8 +365,36 @@ For `url-retrieve', the callback will be called with a nil status."
(progn ,@body)
(jupyter-api-shutdown-kernel ,server ,id))))))
(defmacro jupyter-test-with-some-kernelspecs (names &rest body)
"Execute BODY in the context where extra kernelspecs with NAMES are available.
Those kernelspecs will be created in a temporary dir, which will
be presented to Jupyter process via JUPYTER_PATH environemnt
variable."
(declare (indent 1) (debug (listp body)))
`(unwind-protect
(let ((jupyter-extra-dir (make-temp-file "jupyter-extra-dir" 'directory)))
(jupyter-test-create-some-kernelspecs ,names jupyter-extra-dir)
(setenv "JUPYTER_PATH" jupyter-extra-dir)
,@body)
(setenv "JUPYTER_PATH")))
;;; Functions
(defun jupyter-test-create-some-kernelspecs (kernel-names data-dir)
"In DATA-DIR, create kernelspecs according to KERNEL-NAMES list.
The only difference between them will be their names."
(let ((argv (vector "python" "-m" "ipykernel_launcher" "-f" "{connection_file}"))
(save-silently t))
(dolist (name kernel-names)
(let ((kernel-dir (format "%s/kernels/%s" data-dir name)))
(make-directory kernel-dir t)
(append-to-file (json-serialize
`(:argv ,argv :display_name ,name :language "python"))
nil
(format "%s/kernel.json" kernel-dir))))))
(defun jupyter-test-ipython-kernel-version (spec)
"Return the IPython kernel version string corresponding to SPEC.
Assumes that SPEC is a kernelspec for a Python kernel and