Draft framework for making pytools more language agnostic.

Support will depend on a particular programming language's ability to introspect
and, more importantly, my time & ability as a programmer.
This commit is contained in:
John Miller 2020-02-02 11:29:44 -07:00
parent 42f8efc54b
commit 90bc9b2a4c
3 changed files with 209 additions and 62 deletions

View file

@ -31,7 +31,73 @@
(require 'ein-kernel) (require 'ein-kernel)
(require 'ein-notebook) (require 'ein-notebook)
(require 'ein-shared-output) ;; (require 'ein-shared-output)
;;; Support tooling in languages other than Python
(defvar *ein:langtools-db* (make-hash-table)
"Lookup table tool support functions for a given language. Keys
are symbols representing the language of a running
kernel (i.e. (make-symbol (ein:kernelinfo-language <kinfo>)).
Values are an plist of (TOOL-COMMAND-NAME LANG-CODE). TOOL-COMMAND-NAME is a symbol,
LANG-CODE is a string suitable for passing to format.")
(defun ein:define-langtool-command (language tool-command code)
(ein:aif (gethash language *ein:langtools-db*)
(plist-put it tool-command code)
(setf (gethash language *ein:langtools-db*)
(list tool-command code))))
(defun ein:get-langtool-command (language tool-command)
(plist-get (gethash language *ein:langtools-db*) tool-command))
(eval-when-compile
(cl-defmacro ein:make-langtool (language defs)
(let ((expr (cl-loop for d in defs
collecting `(ein:define-langtool-command ',language ',(car d) ,(cdr d)))))
`(progn
,@expr)))
(cl-defmacro ein:langtool-execute-command (kernel command &key args output)
(let ((lang (cl-gensym))
(cmd (cl-gensym)))
`(let* ((,lang (intern (ein:$kernelspec-language (ein:$kernel-kernelspec ,kernel))))
(,cmd (ein:get-langtool-command ,lang ,command)))
(if ,cmd
(ein:kernel-execute
,kernel
,(if (listp args)
`(format ,cmd ,@args)
`(format ,cmd ,args))
,(if (not (null output))
`(list :output (cons ,@output))))
(error "ein-pytools: Langtool command not defined for %s in language %s" ,command ,lang))))))
(ein:make-langtool python
((get-notebook-dir . "print(__import__('os').getcwd(),end='')")
(add-sys-path . "__import__('sys').path.append('%s')")
(request-tooltip . "__ein_print_object_info_for(%s)")
(request-help . "%s?")
(object-info-request . "__ein_print_object_info_for(%s)")
(find-source . "__ein_find_source('%s')")
(run-doctest . "__ein_run_docstring_examples(%s)")
(set-figure-size . "__ein_set_figure_size('[%s, %s]')")
(set-figure-dpi . "__ein_set_figure_dpi('%s')")
(set-figure-param . "__ein_set_matplotlib_param('%s', '%s', '%s')")
(get-figure-param . "__ein_get_matplotlib_params()")
(export . "__ein_export_nb(r'%s', '%s')")
(tools-file . "ein_remote_safe.py")))
(ein:make-langtool hy
((find-source . "(ein-find-source \"%s\")")
(request-tooltip . "(ein-print-object-info-for \"%s\")")
(object-info-request . "(ein-print-object-info-for \"%s\")")
(tools-file . "ein_hytools.hy")))
(defun ein:goto-file (filename lineno &optional other-window) (defun ein:goto-file (filename lineno &optional other-window)
"Jump to file FILEAME at line LINENO. "Jump to file FILEAME at line LINENO.
@ -60,7 +126,10 @@ If OTHER-WINDOW is non-`nil', open the file in the other window."
(defun ein:pytools-load-safely (kernel) (defun ein:pytools-load-safely (kernel)
(with-temp-buffer (with-temp-buffer
(let ((pytools-file (format "%s/%s" ein:source-dir "ein_remote_safe.py"))) (let* ((fname (ein:get-langtool-command (intern (ein:$kernelspec-language
(ein:$kernel-kernelspec kernel)))
'tools-file))
(pytools-file (format "%s/%s" ein:source-dir fname)))
(insert-file-contents pytools-file) (insert-file-contents pytools-file)
(ein:kernel-execute (ein:kernel-execute
kernel kernel
@ -76,9 +145,7 @@ working."
(ein:pytools-load-safely (ein:get-kernel-or-error))) (ein:pytools-load-safely (ein:get-kernel-or-error)))
(defun ein:pytools-add-sys-path (kernel) (defun ein:pytools-add-sys-path (kernel)
(ein:kernel-execute (ein:langtool-execute-command kernel 'add-sys-path :args ein:source-dir))
kernel
(format "__import__('sys').path.append('%s')" ein:source-dir)))
(defun ein:set-buffer-file-name (nb msg-type content -not-used-) (defun ein:set-buffer-file-name (nb msg-type content -not-used-)
(let ((buf (ein:notebook-buffer nb))) (let ((buf (ein:notebook-buffer nb)))
@ -92,14 +159,15 @@ working."
(defun ein:pytools-get-notebook-dir (packed) (defun ein:pytools-get-notebook-dir (packed)
(cl-multiple-value-bind (kernel notebook) packed (cl-multiple-value-bind (kernel notebook) packed
(ein:kernel-execute (ein:langtool-execute-command kernel 'get-notebook-dir
kernel :output (#'ein:set-buffer-file-name notebook))))
(format "print(__import__('os').getcwd(),end='')") ;; (ein:kernel-execute
(list ;; kernel
:output (cons ;; (format "print(__import__('os').getcwd(),end='')")
#'ein:set-buffer-file-name ;; (list
notebook))))) ;; :output (cons
;; #'ein:set-buffer-file-name
;; notebook)))
;;; Tooltip and help ;;; Tooltip and help
@ -113,16 +181,16 @@ working."
(ein:object-at-point-or-error))) (ein:object-at-point-or-error)))
(unless (ein:pytools-magic-func-p func) (unless (ein:pytools-magic-func-p func)
(if (>= (ein:$kernel-api-version kernel) 3) (if (>= (ein:$kernel-api-version kernel) 3)
(ein:kernel-execute (ein:langtool-execute-command kernel 'object-info-request :args func
kernel :output
(format "__ein_print_object_info_for(%s)" func) ((lambda (name msg-type content -metadata-not-used-)
(list (ein:case-equal msg-type
:output (cons (("stream" "display_data")
(lambda (name msg-type content -metadata-not-used-) (ein:pytools-finish-tooltip name
(ein:case-equal msg-type (ein:json-read-from-string
(("stream" "display_data") (plist-get content :text))
(ein:pytools-finish-tooltip name (ein:json-read-from-string (plist-get content :text)) nil)))) nil))))
func))) func))
(ein:kernel-object-info-request (ein:kernel-object-info-request
kernel func (list :object_info_reply kernel func (list :object_info_reply
(cons #'ein:pytools-finish-tooltip nil)))))) (cons #'ein:pytools-finish-tooltip nil))))))
@ -214,14 +282,18 @@ pager buffer. You can explicitly specify the object by selecting it."
(unless (equal (point) (marker-position last)) (unless (equal (point) (marker-position last))
(push (point-marker) ein:pytools-jump-stack)) (push (point-marker) ein:pytools-jump-stack))
(setq ein:pytools-jump-stack (list (point-marker))))) (setq ein:pytools-jump-stack (list (point-marker)))))
(ein:kernel-execute (ein:langtool-execute-command kernel 'find-source :args object
kernel :output (#'ein:pytools-jump-to-source-1
(format "__ein_find_source('%s')" object) (list kernel object other-window notebook))))
(list
:output ;; (ein:kernel-execute
(cons ;; kernel
#'ein:pytools-jump-to-source-1 ;; (format "__ein_find_source('%s')" object)
(list kernel object other-window notebook))))) ;; (list
;; :output
;; (cons
;; #'ein:pytools-jump-to-source-1
;; (list kernel object other-window notebook))))
(defun ein:pytools-find-source (kernel object &optional callback) (defun ein:pytools-find-source (kernel object &optional callback)
"Find the file and line where object is defined. "Find the file and line where object is defined.
@ -230,14 +302,18 @@ useful for other purposes. If the definition for object can be
found and when callback isort specified, the callback will be found and when callback isort specified, the callback will be
called with a cons of the filename and line number where object called with a cons of the filename and line number where object
is defined." is defined."
(ein:kernel-execute (ein:langtool-execute-command kernel 'find-source :args object
kernel :output (#'ein:pytools-finish-find-source
(format "__ein_find_source('%s')" object) (list kernel object callback))))
(list
:output ;; (ein:kernel-execute
(cons ;; kernel
#'ein:pytools-finish-find-source ;; (format "__ein_find_source('%s')" object)
(list kernel object callback))))) ;; (list
;; :output
;; (cons
;; #'ein:pytools-finish-find-source
;; (list kernel object callback))))
(defun ein:pytools-finish-find-source (packed msg-type content -ignored-) (defun ein:pytools-finish-find-source (packed msg-type content -ignored-)
(cl-destructuring-bind (kernel object callback) packed (cl-destructuring-bind (kernel object callback) packed
@ -297,9 +373,11 @@ given, open the last point in the other window."
(defun ein:pytools-doctest () (defun ein:pytools-doctest ()
"Do the doctest of the object at point." "Do the doctest of the object at point."
(interactive) (interactive)
(let ((object (ein:object-at-point))) (let* ((object (ein:object-at-point))
(kernel (ein:get-kernel))
(cmd (ein:get-langtool-command kernel 'run-docstring)))
(ein:shared-output-eval-string (ein:get-kernel) (ein:shared-output-eval-string (ein:get-kernel)
(format "__ein_run_docstring_examples(%s)" object) (format cmd object)
t))) t)))
(defun ein:pytools-whos () (defun ein:pytools-whos ()
@ -410,40 +488,43 @@ Currently EIN/IPython supports exporting to the following formats:
(defun ein:pytools-set-figure-size (width height) (defun ein:pytools-set-figure-size (width height)
"Set the default figure size for matplotlib figures. Works by setting `rcParams['figure.figsize']`." "Set the default figure size for matplotlib figures. Works by setting `rcParams['figure.figsize']`."
(interactive "nWidth: \nnHeight: ") (interactive "nWidth: \nnHeight: ")
(ein:shared-output-eval-string (ein:get-kernel) (let ((kernel (ein:get-kernel)))
(format "__ein_set_figure_size('[%s, %s]')" width height) (ein:langtool-execute-command kernel 'set-figure-size :args (width height)))
nil)) )
;; (ein:shared-output-eval-string (ein:get-kernel)
;; (format "__ein_set_figure_size('[%s, %s]')" width height)
;; nil)
(defun ein:pytools-set-figure-dpi (dpi) (defun ein:pytools-set-figure-dpi (dpi)
"Set the default figure dpi for matplotlib figures. Works by setting `rcParams['figure.figsize']`." "Set the default figure dpi for matplotlib figures. Works by setting `rcParams['figure.figsize']`."
(interactive "nFigure DPI: ") (interactive "nFigure DPI: ")
(ein:shared-output-eval-string (ein:get-kernel) (let ((kernel (ein:get-kernel)))
(format "__ein_set_figure_dpi('%s')" dpi) (ein:langtool-execute-command kernel 'set-figure-dpi :args dpi)))
nil))
(defun ein:pytools-set-matplotlib-parameter (param value) (defun ein:pytools-set-matplotlib-parameter (param value)
"Generically set any matplotlib parameter exposed in the matplotlib.pyplot.rcParams variable. Value is evaluated as a Python expression, so be careful of side effects." "Generically set any matplotlib parameter exposed in the matplotlib.pyplot.rcParams variable. Value is evaluated as a Python expression, so be careful of side effects."
(interactive (interactive
(list (completing-read "Parameter: " (ein:pytools--get-matplotlib-params) nil t) (list (completing-read "Parameter: " (ein:pytools--get-matplotlib-params) nil t)
(read-string "Value: " nil))) (read-string "Value: " nil)))
(let* ((split (cl-position ?. param)) (let* ((kernel (ein:get-kernel))
(split (cl-position ?. param))
(family (cl-subseq param 0 split)) (family (cl-subseq param 0 split))
(setting (cl-subseq param (1+ split)))) (setting (cl-subseq param (1+ split))))
(ein:shared-output-eval-string (ein:get-kernel) (ein:langtool-execute-command kernel 'set-figure-param :args (family setting value))))
(format "__ein_set_matplotlib_param('%s', '%s', '%s')" family setting value)
nil)))
(defun ein:pytools--get-matplotlib-params () (defun ein:pytools--get-matplotlib-params ()
(ein:shared-output-eval-string (ein:get-kernel) (let* ((kernel (ein:get-kernel))
(format "__ein_get_matplotlib_params()") (cmd (ein:get-langtool-command kernel 'get-figure-param)))
nil) (ein:shared-output-eval-string (ein:get-kernel)
(with-current-buffer (ein:shared-output-create-buffer) (format cmd)
(ein:wait-until #'(lambda () nil)
(slot-value (slot-value *ein:shared-output* :cell) :outputs)) (with-current-buffer (ein:shared-output-create-buffer)
nil (ein:wait-until #'(lambda ()
5.0) (slot-value (slot-value *ein:shared-output* :cell) :outputs))
(let ((outputs (first (slot-value (slot-value *ein:shared-output* :cell) :outputs)))) nil
(ein:json-read-from-string (plist-get outputs :text))))) 5.0)
(let ((outputs (first (slot-value (slot-value *ein:shared-output* :cell) :outputs))))
(ein:json-read-from-string (plist-get outputs :text))))))
(defun ein:pytools--estimate-screen-dpi () (defun ein:pytools--estimate-screen-dpi ()
(let* ((pixel-width (display-pixel-width)) (let* ((pixel-width (display-pixel-width))

67
lisp/ein_hytools.hy Normal file
View file

@ -0,0 +1,67 @@
;; Hy utilities useable from ein.
;; Copyright (C) 2020- John Miller
;; Author: John Miller <millejoh at mac.com>
;; ein_hytools.hy is free software: you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by the Free
;; Software Foundation, either version 3 of the License, or (at your option) any
;; later version.
;; ein_hytools.hy 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
;; ein.py. If not, see <http://www.gnu.org/licenses/>.
(setv --ein-hytools-version-- "1.0.0")
(try
(import [matplotlib [rc :as --ein-rc
rcParams as --ein-rcParams]])
(setv ein-matplotlib-available True)
(except [ImportError]
(setv ein-matplotlib-available False)))
(defn ein-find-edit-target [name]
(import [inspect [getsourcefile getsourcelines]])
(import hy)
(try
(setv obj (eval (hy.read-str name)))
(except [NameError]
(return False))
(else
(setv sfile (getsourcefile obj))
(setv sline (get (getsourcelines obj) -1))
(if (and sfile sline)
(return (, sfile sline False))
(return False)))))
(defn ein-find-source [name]
(setv ret (--ein-find-edit-target name))
(if ret
(do
(setv (, filename lineno use-temp) ret)
(if (not use-temp)
(do
(print filename)
(print lineno)
(return)))))
(raise (RuntimeError (.format "Source code for {0} cannot be found" name))))
(defn ein-print-object-info-for [obj]
(import json)
(import [IPython.core [oinspect]])
(setv inspector (oinspect.Inspector))
(try
(setv oinfo (inspector.info obj))
(except [Exception]
(setv oinfo (inspector.info None))))
(print (json.dumps oinfo)))

View file

@ -144,7 +144,6 @@ def __ein_object_info_for(obj):
except Exception: except Exception:
return inspector.info(None) return inspector.info(None)
def __ein_print_object_info_for(obj): def __ein_print_object_info_for(obj):
import json import json