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:
|
|
|
|
|
2018-10-07 13:36:18 -05:00
|
|
|
(require 'json)
|
2019-06-28 20:02:00 -05:00
|
|
|
(require 'jupyter-env)
|
2018-10-07 13:36:18 -05:00
|
|
|
|
2018-01-17 20:00:44 -06:00
|
|
|
(defgroup jupyter-kernelspec nil
|
|
|
|
"Jupyter kernelspecs"
|
|
|
|
:group 'jupyter)
|
|
|
|
|
2018-05-16 15:10:10 -05:00
|
|
|
(declare-function jupyter-read-plist "jupyter-base" (file))
|
2019-06-28 20:02:00 -05:00
|
|
|
(declare-function jupyter-read-plist-from-string "jupyter-base" (file))
|
2018-05-16 15:10:10 -05:00
|
|
|
|
2020-04-17 15:05:09 -05:00
|
|
|
(cl-defstruct jupyter-kernelspec
|
|
|
|
(name "python"
|
|
|
|
:type string
|
|
|
|
:documentation "The name of the kernelspec."
|
|
|
|
:read-only t)
|
|
|
|
(plist nil
|
|
|
|
:type list
|
|
|
|
:documentation "The kernelspec as a property list."
|
|
|
|
:read-only t)
|
|
|
|
(resource-directory nil
|
|
|
|
:type (or null string)
|
|
|
|
:documentation "The resource directory."
|
|
|
|
:read-only t))
|
|
|
|
|
2018-08-27 20:38:24 -05:00
|
|
|
(defvar jupyter--kernelspecs (make-hash-table :test #'equal :size 5)
|
2020-04-17 15:05:09 -05:00
|
|
|
"A hash table mapping hosts to the kernelspecs available on them.")
|
2018-01-04 23:03:18 -06:00
|
|
|
|
2018-01-17 20:02:29 -06:00
|
|
|
(defun jupyter-available-kernelspecs (&optional refresh)
|
2020-04-17 15:05:09 -05:00
|
|
|
"Return the available kernelspecs.
|
|
|
|
Return a list of `jupyter-kernelspec's available on the host
|
|
|
|
associated with the `default-directory'. If `default-directory'
|
|
|
|
is a remote file name, return the list of available kernelspecs
|
|
|
|
on the remote system. The kernelspecs on the local system are
|
|
|
|
returned otherwise.
|
|
|
|
|
|
|
|
On any system, the list is formed by parsing the output of the
|
|
|
|
shell command
|
2018-01-04 23:03:18 -06:00
|
|
|
|
2022-02-27 13:48:11 +01:00
|
|
|
jupyter kernelspec list --json
|
2018-01-04 23:03:18 -06:00
|
|
|
|
2020-03-11 20:50:52 +09:00
|
|
|
By default the available kernelspecs are cached. To force an
|
2018-01-17 20:02:29 -06:00
|
|
|
update of the cached kernelspecs, give a non-nil value to
|
2020-04-17 15:05:09 -05:00
|
|
|
REFRESH."
|
|
|
|
(let* ((host (or (file-remote-p default-directory) "local"))
|
|
|
|
(kernelspecs
|
|
|
|
(or (and (not refresh) (gethash host jupyter--kernelspecs))
|
|
|
|
(let ((specs
|
|
|
|
(plist-get
|
2019-05-09 15:16:45 -05:00
|
|
|
(jupyter-read-plist-from-string
|
2022-11-14 12:59:18 -08:00
|
|
|
(or (jupyter-command "kernelspec" "list" "--json" "--log-level" "ERROR")
|
2019-07-08 20:55:20 -05:00
|
|
|
(error "Can't obtain kernelspecs from jupyter shell command")))
|
2019-05-09 15:16:45 -05:00
|
|
|
:kernelspecs)))
|
2020-04-17 15:05:09 -05:00
|
|
|
(puthash
|
|
|
|
host (cl-loop
|
|
|
|
for (kname spec) on specs by #'cddr
|
|
|
|
for name = (substring (symbol-name kname) 1)
|
|
|
|
for dir = (plist-get spec :resource_dir)
|
|
|
|
collect (make-jupyter-kernelspec
|
|
|
|
:name name
|
|
|
|
:resource-directory (concat
|
|
|
|
(unless (string= host "local") host)
|
|
|
|
dir)
|
|
|
|
:plist (plist-get spec :spec)))
|
|
|
|
jupyter--kernelspecs)))))
|
|
|
|
kernelspecs))
|
|
|
|
|
|
|
|
(defun jupyter-get-kernelspec (name &optional specs refresh)
|
2018-01-16 11:10:17 -06:00
|
|
|
"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
|
2019-04-22 11:41:28 -05:00
|
|
|
REFRESH has the same meaning as in
|
2020-04-17 15:05:09 -05:00
|
|
|
`jupyter-available-kernelspecs'.
|
|
|
|
|
|
|
|
If SPECS is provided, it is a list of kernelspecs that will be
|
|
|
|
searched, otherwise the kernelspecs returned by
|
|
|
|
`jupyter-available-kernelspecs' are used."
|
|
|
|
(cl-loop
|
|
|
|
for kernelspec in (or specs (jupyter-available-kernelspecs refresh))
|
|
|
|
thereis (when (string= (jupyter-kernelspec-name kernelspec) name)
|
|
|
|
kernelspec)))
|
2018-01-04 23:03:18 -06:00
|
|
|
|
2019-05-31 14:55:00 -05:00
|
|
|
(defun jupyter-find-kernelspecs (re &optional specs refresh)
|
2020-04-17 15:05:09 -05:00
|
|
|
"Find all specs of kernels that have names matching RE.
|
2018-10-01 23:05:35 -05:00
|
|
|
RE is a regular expression use to match the name of a kernel.
|
2020-04-17 15:05:09 -05:00
|
|
|
Return a list of `jupyter-kernelspec' objects.
|
2018-01-16 11:10:17 -06:00
|
|
|
|
2019-05-31 14:55:00 -05:00
|
|
|
If SPECS is non-nil search SPECS, otherwise search the specs
|
|
|
|
returned by `jupyter-available-kernelspecs'.
|
|
|
|
|
2018-01-17 20:02:29 -06:00
|
|
|
Optional argument REFRESH has the same meaning as in
|
2018-01-16 11:10:17 -06:00
|
|
|
`jupyter-available-kernelspecs'."
|
2019-05-31 14:55:00 -05:00
|
|
|
(cl-remove-if-not
|
2020-04-17 15:05:09 -05:00
|
|
|
(lambda (kernelspec)
|
|
|
|
(string-match-p re (jupyter-kernelspec-name kernelspec)))
|
2019-05-31 14:55:00 -05:00
|
|
|
(or specs (jupyter-available-kernelspecs refresh))))
|
2018-01-04 23:03:18 -06:00
|
|
|
|
2019-05-31 14:55:00 -05:00
|
|
|
(defun jupyter-guess-kernelspec (name &optional specs refresh)
|
2022-02-27 13:48:11 +01:00
|
|
|
"Return the first kernelspec starting with NAME.
|
2019-05-31 14:55:00 -05:00
|
|
|
Raise an error if no kernelspec could be found.
|
|
|
|
|
|
|
|
SPECS and REFRESH have the same meaning as in
|
|
|
|
`jupyter-find-kernelspecs'."
|
2022-02-27 13:48:11 +01:00
|
|
|
(or (car (jupyter-find-kernelspecs (format "^%s" 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)))
|
|
|
|
|
2018-01-22 18:30:38 -06:00
|
|
|
(defun jupyter-completing-read-kernelspec (&optional specs refresh)
|
2018-01-17 20:04:40 -06:00
|
|
|
"Use `completing-read' to select a kernel and return its kernelspec.
|
|
|
|
|
2020-04-17 15:05:09 -05:00
|
|
|
SPECS is a list of kernelspecs that will be used for completion,
|
|
|
|
if it is nil the `jupyter-available-kernelspecs' will be used.
|
2018-01-22 18:30:38 -06:00
|
|
|
|
2018-01-17 20:04:40 -06:00
|
|
|
Optional argument REFRESH has the same meaning as in
|
|
|
|
`jupyter-available-kernelspecs'."
|
2018-01-22 18:30:38 -06:00
|
|
|
(let* ((specs (or specs (jupyter-available-kernelspecs refresh)))
|
2019-02-09 14:36:00 -06:00
|
|
|
(display-names (if (null specs) (error "No kernelspecs available")
|
2020-04-17 15:05:09 -05:00
|
|
|
(mapcar (lambda (k)
|
|
|
|
(plist-get
|
|
|
|
(jupyter-kernelspec-plist k)
|
|
|
|
:display_name))
|
|
|
|
specs)))
|
2019-02-09 14:36:00 -06:00
|
|
|
(name (completing-read "kernel: " display-names nil t)))
|
|
|
|
(when (equal name "")
|
|
|
|
(error "No kernelspec selected"))
|
2018-01-17 20:04:40 -06:00
|
|
|
(nth (- (length display-names)
|
|
|
|
(length (member name display-names)))
|
|
|
|
specs)))
|
|
|
|
|
2020-04-18 21:28:00 -05:00
|
|
|
(defun jupyter-expand-environment-variables (var)
|
|
|
|
"Return VAR with all environment variables expanded.
|
|
|
|
VAR is a string, if VAR contains a sequence of characters like
|
|
|
|
${ENV_VAR}, substitute it with the value of ENV_VAR in
|
|
|
|
`process-environment'."
|
|
|
|
(let ((expanded "")
|
|
|
|
(start 0))
|
|
|
|
(while (string-match "\\${\\([^}]+\\)}" var start)
|
|
|
|
(cl-callf concat expanded
|
|
|
|
(substring var start (match-beginning 0))
|
|
|
|
(getenv (match-string 1 var)))
|
|
|
|
(setq start (match-end 0)))
|
|
|
|
(cl-callf concat expanded (substring var start))))
|
|
|
|
|
|
|
|
(defun jupyter-process-environment (kernelspec)
|
|
|
|
"Return a list of environment variables contained in KERNELSPEC.
|
|
|
|
The list of environment variables have the same form as the
|
|
|
|
entries in `process-environment'.
|
|
|
|
|
|
|
|
The environment variables returned are constructed from those in
|
|
|
|
the :env key of KERNELSPEC's property list."
|
|
|
|
(cl-loop
|
|
|
|
with env = (plist-get (jupyter-kernelspec-plist kernelspec) :env)
|
|
|
|
for (k v) on env by #'cddr
|
|
|
|
collect (format "%s=%s" (cl-subseq (symbol-name k) 1)
|
|
|
|
(jupyter-expand-environment-variables v))))
|
|
|
|
|
|
|
|
(defun jupyter-kernel-argv (kernelspec conn-file)
|
|
|
|
"Return a list of process arguments contained in KERNELSPEC.
|
|
|
|
The process arguments are the ones that should be passed to
|
|
|
|
kernel processes launched using KERNELSPEC.
|
|
|
|
|
|
|
|
CONN-FILE is the file name of a connection file, containing the
|
|
|
|
IP address and ports (among other things), a
|
|
|
|
launched kernel should connect to."
|
|
|
|
(cl-loop
|
|
|
|
with argv = (plist-get (jupyter-kernelspec-plist kernelspec) :argv)
|
|
|
|
for arg in (append argv nil)
|
|
|
|
if (equal arg "{connection_file}")
|
|
|
|
collect (file-local-name conn-file)
|
|
|
|
else if (equal arg "{resource_dir}")
|
|
|
|
collect (file-local-name
|
|
|
|
(jupyter-kernelspec-resource-directory
|
|
|
|
kernelspec))
|
|
|
|
else collect arg))
|
|
|
|
|
2018-01-04 23:03:18 -06:00
|
|
|
(provide 'jupyter-kernelspec)
|
2018-01-17 20:00:44 -06:00
|
|
|
|
|
|
|
;;; jupyter-kernelspec.el ends here
|