emacs-jupyter/jupyter-kernelspec.el

144 lines
5.5 KiB
EmacsLisp
Raw Normal View History

2018-01-17 20:00:44 -06:00
;;; jupyter-kernelspec.el --- Jupyter kernelspecs -*- lexical-binding: t -*-
2020-04-07 15:13:51 -05:00
;; Copyright (C) 2018-2020 Nathaniel Nicandro
2018-01-17 20:00:44 -06:00
;; Author: Nathaniel Nicandro <nathanielnicandro@gmail.com>
;; Created: 17 Jan 2018
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
2019-05-31 09:44:39 -05:00
;; published by the Free Software Foundation; either version 3, or (at
2018-01-17 20:00:44 -06:00
;; your option) any later version.
;; This program is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;; Commentary:
2019-01-21 23:22:44 -06:00
;; Functions to work with kernelspecs found by the shell command
2018-01-17 20:00:44 -06:00
;;
2019-01-21 23:22:44 -06:00
;; jupyter kernelspec list
2018-01-17 20:00:44 -06:00
;;; Code:
(require 'json)
(require 'jupyter-env)
2018-01-17 20:00:44 -06:00
(defgroup jupyter-kernelspec nil
"Jupyter kernelspecs"
:group 'jupyter)
(declare-function jupyter-read-plist "jupyter-base" (file))
(declare-function jupyter-read-plist-from-string "jupyter-base" (file))
(defvar jupyter--kernelspecs (make-hash-table :test #'equal :size 5)
"An alist matching kernel names to their kernelspec directories.")
(defun jupyter-available-kernelspecs (&optional refresh)
"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
2020-03-11 20:50:52 +09:00
is its kernelspec plist. The alist is formed by parsing the
output of the shell command
jupyter kernelspec list
2020-03-11 20:50:52 +09:00
By default the available kernelspecs are cached. To force an
update of the cached kernelspecs, give a non-nil value to
REFRESH.
If the `default-directory' is a remote directory, return the
2020-03-11 20:50:52 +09:00
mapping for the kernelspecs on the remote host. In this case,
each DIRECTORY will be a remote file name."
(let ((host (or (file-remote-p default-directory) "local")))
(or (and (not refresh) (gethash host jupyter--kernelspecs))
(let ((specs (plist-get
(jupyter-read-plist-from-string
(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)))))
(defun jupyter-get-kernelspec (name &optional refresh)
"Get the kernelspec for a kernel named NAME.
2020-03-11 20:50:52 +09:00
If no kernelspec is found, return nil. Otherwise return the
kernelspec plist for the kernel names NAME. Optional argument
REFRESH has the same meaning as in
`jupyter-available-kernelspecs'."
(cdr (assoc name (jupyter-available-kernelspecs refresh))))
(defun jupyter-find-kernelspecs (re &optional specs refresh)
"Find all specs of kernels that have names matching matching RE.
RE is a regular expression use to match the name of a kernel.
2018-01-23 13:44:12 -06:00
Return an alist with elements of the form:
(KERNEL-NAME . (DIRECTORY . PLIST))
where KERNEL-NAME is the name of a kernel that matches RE,
2018-01-23 13:44:12 -06:00
DIRECTORY is the kernel's resource directory, and PLIST is the
kernelspec propery list read from the \"kernel.json\" file in the
resource directory.
If SPECS is non-nil search SPECS, otherwise search the specs
returned by `jupyter-available-kernelspecs'.
Optional argument REFRESH has the same meaning as in
`jupyter-available-kernelspecs'."
(cl-remove-if-not
(lambda (s) (string-match-p re (car s)))
(or specs (jupyter-available-kernelspecs refresh))))
(defun jupyter-guess-kernelspec (name &optional specs refresh)
Refactor of `jupyter-kernel-manager.el` This refactor implements a new class hierarchy to manage the lifetime of a Jupyter kernel. The first node in this hierarchy is the `jupyter-kernel-lifetime` class which defines a set of methods to manage the lifetime of a kernel. An object that inherits from `jupyter-kernel-lifetime` is stating that it has an association with a kernel and can be used to manage the lifetime of the associated kernel. The `jupyter-meta-kernel` class inherits from `jupyter-kernel-lifetime` and mainly defines a `spec` slot used to hold the `kernelspec` from which a command can be constructed to start a kernel and a `session` slot used to hold the `jupyter-session` object that clients can use to establish communication with a kernel once its live. Concrete classes that actually launch kernels are intended to inherit from this class and use its slots. `jupyter-kernel-process` manages the lifetime of a kernel started as a process using the function `start-file-process`, `jupyter-command-kernel` calls the `jupyter kernel` shell command to start a kernel, finally `jupyter-spec-kernel` uses the `spec` slot to construct a shell command to start a kernel. A `jupyter-kernel-manager` now consists of a `kernel` slot that holds a `jupyter-meta-kernel` and a `control-channel` slot and inherits from `jupyter-kernel-lifetime`. The `jupyter-kernel-lifetime` methods of the manager just defer to those of `kernel` while also taking into account the `control-channel`. * jupyter-base.el (jupyter-write-connection-file): New function. * jupyter-channel-ioloop.el (jupyter-channel-ioloop-add-start-channel-event): Remove `sleep-for` call. The startup message is not so important anymore. * jupyter-client.el (jupyter-wait-until-startup: New function. * jupyter-kernel-manager.el (jupyter-kernel-lifetime) (jupyter-kernel, jupyter-kernel-process, jupyter-command-kernel) (jupyter-spec-kernel): New classes. (jupyter-kernel-manager): Inherit from jupyter-kernel-lifetime only and implement its methods. (jupyter-kernel-manager--cleanup, jupyter-kernel-managers) (jupyter-delete-all-kernels, jupyter--kernel-sentinel) (jupyter--start-kernel): Remove and remove related, their functionality has been generalized in the new classes. (jupyter-interrupt-kernel, jupyter-shutdown-kernel) (jupyter-start-channels, jupyter-start-kernel, jupyter-kernel-alive-p) (jupyter-kill-kernel): Refactor and implement to use the new class hierarchy. * test/jupyter-test.el: Refactor tests to account for changes. (jupyter-write-connect-file, jupyter-command-kernel): New tests. * jupyter-kernelspec.el (jupyter-guess-kernelspec): New function.
2019-05-09 08:31:00 -05:00
"Return the first kernelspec matching 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))
Refactor of `jupyter-kernel-manager.el` This refactor implements a new class hierarchy to manage the lifetime of a Jupyter kernel. The first node in this hierarchy is the `jupyter-kernel-lifetime` class which defines a set of methods to manage the lifetime of a kernel. An object that inherits from `jupyter-kernel-lifetime` is stating that it has an association with a kernel and can be used to manage the lifetime of the associated kernel. The `jupyter-meta-kernel` class inherits from `jupyter-kernel-lifetime` and mainly defines a `spec` slot used to hold the `kernelspec` from which a command can be constructed to start a kernel and a `session` slot used to hold the `jupyter-session` object that clients can use to establish communication with a kernel once its live. Concrete classes that actually launch kernels are intended to inherit from this class and use its slots. `jupyter-kernel-process` manages the lifetime of a kernel started as a process using the function `start-file-process`, `jupyter-command-kernel` calls the `jupyter kernel` shell command to start a kernel, finally `jupyter-spec-kernel` uses the `spec` slot to construct a shell command to start a kernel. A `jupyter-kernel-manager` now consists of a `kernel` slot that holds a `jupyter-meta-kernel` and a `control-channel` slot and inherits from `jupyter-kernel-lifetime`. The `jupyter-kernel-lifetime` methods of the manager just defer to those of `kernel` while also taking into account the `control-channel`. * jupyter-base.el (jupyter-write-connection-file): New function. * jupyter-channel-ioloop.el (jupyter-channel-ioloop-add-start-channel-event): Remove `sleep-for` call. The startup message is not so important anymore. * jupyter-client.el (jupyter-wait-until-startup: New function. * jupyter-kernel-manager.el (jupyter-kernel-lifetime) (jupyter-kernel, jupyter-kernel-process, jupyter-command-kernel) (jupyter-spec-kernel): New classes. (jupyter-kernel-manager): Inherit from jupyter-kernel-lifetime only and implement its methods. (jupyter-kernel-manager--cleanup, jupyter-kernel-managers) (jupyter-delete-all-kernels, jupyter--kernel-sentinel) (jupyter--start-kernel): Remove and remove related, their functionality has been generalized in the new classes. (jupyter-interrupt-kernel, jupyter-shutdown-kernel) (jupyter-start-channels, jupyter-start-kernel, jupyter-kernel-alive-p) (jupyter-kill-kernel): Refactor and implement to use the new class hierarchy. * test/jupyter-test.el: Refactor tests to account for changes. (jupyter-write-connect-file, jupyter-command-kernel): New tests. * jupyter-kernelspec.el (jupyter-guess-kernelspec): New function.
2019-05-09 08:31:00 -05:00
(error "No valid kernelspec for kernel name (%s)" name)))
(defun jupyter-completing-read-kernelspec (&optional specs refresh)
"Use `completing-read' to select a kernel and return its kernelspec.
The returned kernelspec has the form
(KERNEL-NAME . (DIRECTORY . PLIST))
where KERNEL-NAME is the name of the kernel, DIRECTORY is the
resource directory of the kernel, and PLIST is the kernelspec
plist.
If SPECS is non-nil then it should be a list of kernelspecs that
will be used to select from otherwise the list of kernelspecs
will be taken from `jupyter-available-kernelspecs'.
Optional argument REFRESH has the same meaning as in
`jupyter-available-kernelspecs'."
(let* ((specs (or specs (jupyter-available-kernelspecs refresh)))
(display-names (if (null specs) (error "No kernelspecs available")
(mapcar (lambda (k) (plist-get (cddr k) :display_name))
specs)))
(name (completing-read "kernel: " display-names nil t)))
(when (equal name "")
(error "No kernelspec selected"))
(nth (- (length display-names)
(length (member name display-names)))
specs)))
(provide 'jupyter-kernelspec)
2018-01-17 20:00:44 -06:00
;;; jupyter-kernelspec.el ends here