From 7d20c0aee2f9c896215f35232905b23532ef04c5 Mon Sep 17 00:00:00 2001 From: Tomasz Mieszkowski Date: Sun, 27 Feb 2022 13:48:11 +0100 Subject: [PATCH] Sort available kernelspecs; guess with ^; use guessing in jupyter-run-repl --- jupyter-kernelspec.el | 28 +++++++++++++++------------- jupyter-repl.el | 6 +----- test/jupyter-test.el | 18 ++++++++++++++++++ test/test-helper.el | 28 ++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/jupyter-kernelspec.el b/jupyter-kernelspec.el index ee07916..e5d9e71 100644 --- a/jupyter-kernelspec.el +++ b/jupyter-kernelspec.el @@ -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) diff --git a/jupyter-repl.el b/jupyter-repl.el index c9ee0e1..cbc6c6a 100644 --- a/jupyter-repl.el +++ b/jupyter-repl.el @@ -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. diff --git a/test/jupyter-test.el b/test/jupyter-test.el index 0ba777c..8720fd5 100644 --- a/test/jupyter-test.el +++ b/test/jupyter-test.el @@ -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) diff --git a/test/test-helper.el b/test/test-helper.el index 2d5699e..58bc153 100644 --- a/test/test-helper.el +++ b/test/test-helper.el @@ -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