confused about pyouts

This commit is contained in:
dickmao 2020-01-09 13:12:41 -05:00
parent 0ee5f01fe4
commit 9ac1bf62e5
9 changed files with 152 additions and 210 deletions

View file

@ -112,7 +112,7 @@ Scenario: Specific port, portless localhost refers to same, concurrent execution
And I wait for buffer to say "3.1415"
And I should not see "[....]"
@org
@imogene
Scenario: portless url with path, image
When I open temp file "path.org"
And I call "org-mode"

View file

@ -1,3 +1,4 @@
;;; -*- lexical-binding: t; -*-
(require 'f)
(require 'espuds)
(require 'ert)

View file

@ -42,6 +42,10 @@
(require 'ein-kernel)
(require 'ein-output-area)
(declare-function mm-encode-buffer "mm-encode")
(declare-function mm-possibly-verify-or-decrypt "mm-decode")
(declare-function mm-dissect-singlepart "mm-decode")
(defun ein:cell--ewoc-delete (ewoc &rest nodes)
"Delete NODES from EWOC."
(ewoc--set-buffer-bind-dll-let* ewoc
@ -156,7 +160,6 @@ Delete current text first, thus effecting a \"refresh\"."
"Face for tooltip when using pos-tip backend."
:group 'ein)
;;; Customization
(make-obsolete-variable 'ein:enable-dynamic-javascript nil "0.17.0")
@ -182,18 +185,6 @@ is on. See also `ein:connect-aotoexec-lighter'."
:type 'string
:group 'ein)
(defcustom ein:slice-image nil
"[EXPERIMENTAL] When non-`nil', use `insert-sliced-image' when
drawing images. If it is of the form of ``(ROWS COLS)``, it is
passed to the corresponding arguments of `insert-sliced-image'.
.. FIXME: ROWS and COLS must be determined dynamically by measuring
the size of iamge and Emacs window.
See also: https://github.com/tkf/emacs-ipython-notebook/issues/94"
:type 'boolean
:group 'ein)
(defcustom ein:truncate-long-cell-output nil
"When nil do not truncate cells with long outputs. When set to
a number will limit the number of lines in a cell output."
@ -209,7 +200,6 @@ a number will limit the number of lines in a cell output."
:type 'list
:group 'ein)
;;; EIEIO related utils
(defmacro ein:oset-if-empty (obj slot value)
@ -220,26 +210,64 @@ a number will limit the number of lines in a cell output."
`(when (slot-boundp ,obj ,slot)
(slot-value ,obj ,slot)))
;;; Utils
(defvar ein:mime-type-map
'((image/svg+xml . svg) (image/png . png) (image/jpeg . jpeg)))
(defun ein:make-mm-handle (image)
(let ((mime-type (mailcap-extension-to-mime
(symbol-name (plist-get (cdr image) :type)))))
(with-temp-buffer
(save-excursion (insert (plist-get (cdr image) :data)))
(let ((encoding (mm-encode-buffer (list mime-type))))
(mm-possibly-verify-or-decrypt
(mm-dissect-singlepart (list mime-type) encoding)
(list mime-type))))))
(defun ein:external-image-viewer (image-type)
(let (major ; Major encoding (text, etc)
minor ; Minor encoding (html, etc)
info ; Other info
major-info ; (assoc major mailcap-mime-data)
viewers ; Possible viewers
passed ; Viewers that passed the test
viewer ; The one and only viewer
(ctl (mail-header-parse-content-type (concat "image/" image-type))))
(mailcap-parse-mailcaps nil t)
(setq major (split-string (car ctl) "/"))
(setq minor (cadr major)
major (car major))
(when (setq major-info (cdr (assoc major mailcap-mime-data)))
(when (setq viewers (mailcap-possible-viewers major-info minor))
(setq info (mapcar (lambda (a)
(cons (symbol-name (car a)) (cdr a)))
(cdr ctl)))
(dolist (entry viewers)
(when (mailcap-viewer-passes-test entry info)
(push entry passed)))
(setq passed (sort (nreverse passed) 'mailcap-viewer-lessp))
;; When we want to prefer entries from the user's
;; ~/.mailcap file, then we filter out the system entries
;; and see whether we have anything left.
(when (if (boundp 'mailcap-prefer-mailcap-viewers)
mailcap-prefer-mailcap-viewers
t)
(when-let ((user-entry
(seq-find (lambda (elem)
(eq (cdr (assq 'source elem)) 'user))
passed)))
(setq passed (list user-entry))))
(setq viewer (car passed))))
(when (and (stringp (cdr (assq 'viewer viewer)))
passed)
(setq viewer (car passed)))
(mailcap-unescape-mime-test (cdr (assq 'viewer viewer)) info)))
(defun ein:insert-image (&rest args)
;; Try to insert the image, otherwise emit a warning message and proceed.
(condition-case-unless-debug err
(let* ((img (apply #'create-image args))
(buffer-undo-list t))
(if ein:slice-image
(destructuring-bind (&optional rows cols)
(when (listp ein:slice-image) ein:slice-image)
(insert-sliced-image img "." nil (or rows 20) cols))
(insert-image img ".")))
(error (ein:log 'warn "Could not insert image: %s"
(error-message-string err))
nil)))
(let ((img (apply #'create-image args))
(buffer-undo-list t))
(insert-image img "."))
(error (ein:log 'warn "Could not insert image: %s" (error-message-string err)))))
;;; Cell factory
(defun ein:cell-class-from-type (type)
@ -364,7 +392,6 @@ a number will limit the number of lines in a cell output."
for en in (ein:cell-all-element cell)
do (ein:cell--ewoc-invalidate ewoc en))))
;;; Getter/setter
(cl-defmethod ein:cell-num-outputs ((cell ein:codecell))
@ -430,7 +457,6 @@ Return language name as a string or `nil' when not defined.
(cl-defmethod ein:cell-language ((cell ein:htmlcell)) nil "html")
(cl-defmethod ein:cell-language ((cell ein:rawcell)) nil "rst")
;; EWOC
(defun ein:cell-make-element (make-node num-outputs)
@ -776,7 +802,6 @@ If END is non-`nil', return the location of next element."
"Return a buffer associated by CELL (if any)."
(ein:aand (ein:oref-safe cell 'ewoc) (ewoc-buffer it)))
;; Data manipulation
(cl-defmethod ein:cell-clear-output ((cell ein:codecell) stdout stderr other)
@ -836,7 +861,7 @@ If END is non-`nil', return the location of next element."
(list 'output-stream 'output-subarea
(intern (format "output-%s" (plist-get json :stream)))))))
(cl-defmethod ein:cell-append-output ((cell ein:codecell) json dynamic)
(cl-defmethod ein:cell-append-output ((cell ein:codecell) json)
;; When there is a python error, we actually get two identical tracebacks back
;; from the kernel, one from the "shell" channel, and one from the "iopub"
;; channel. As a workaround, we remember the cell's traceback and ignore
@ -847,10 +872,10 @@ If END is non-`nil', return the location of next element."
(null old-tb)
(null new-tb)
(not (cl-equalp new-tb old-tb)))
(ein:cell-actually-append-output cell json dynamic))
(ein:cell-actually-append-output cell json))
(setf (slot-value cell 'traceback) new-tb)))
(cl-defmethod ein:cell-actually-append-output ((cell ein:codecell) json dynamic)
(cl-defmethod ein:cell-actually-append-output ((cell ein:codecell) json)
(ein:cell-expand cell)
;; (ein:flush-clear-timeout)
(setf (slot-value cell 'outputs)
@ -876,7 +901,7 @@ Called from ewoc pretty printer via `ein:cell-insert-output'."
(or (plist-get json :prompt_number) " "))
'font-lock-face 'ein:cell-output-prompt)
(ein:insert-read-only "\n")
(ein:cell-append-mime-type json (slot-value cell 'dynamic))
(ein:cell-append-mime-type json)
(ein:insert-read-only "\n"))
(cl-defmethod ein:cell-append-pyerr ((cell ein:codecell) json)
@ -923,86 +948,33 @@ Called from ewoc pretty printer via `ein:cell-insert-output'."
(cl-defmethod ein:cell-append-display-data ((cell ein:codecell) json)
"Insert display-data type output in the buffer.
Called from ewoc pretty printer via `ein:cell-insert-output'."
(ein:cell-append-mime-type json (slot-value cell 'dynamic))
(ein:cell-append-mime-type json)
(ein:insert-read-only "\n"))
(defcustom ein:output-type-preference
(if (and (fboundp 'shr-insert-document)
(fboundp 'libxml-parse-xml-region))
#'ein:output-type-prefer-pretty-text-over-html
'(emacs-lisp svg image/svg+xml png image/png jpeg image/jpeg html text/html latex text/latex javascript text/javascript text text/plain))
"Output types to be used in notebook.
First output-type found in this list will be used.
This variable can be a list or a function returning a list given
DATA plist.
See also `ein:output-type-prefer-pretty-text-over-html'.
(make-obsolete-variable 'ein:output-type-preference nil "0.17.0")
**Example**:
If you prefer HTML type over text type, you can set it as::
(defsubst ein:cell-output-type (mime-type)
"Investigate why :image/svg+xml to :svg and :text/plain to :text"
(let* ((mime-str (if (symbolp mime-type) (symbol-name mime-type) mime-type))
(minor (car (nreverse (split-string mime-str "/")))))
(intern (concat ":"
(cond ((string= minor "plain") "text")
(t (intern (cl-subseq minor 0 (cl-search "+" minor)))))))))
(setq ein:output-type-preference
'(emacs-lisp svg png jpeg html text latex javascript))
Note that ``html`` comes before ``text``."
:type '(choice function (repeat symbol))
:group 'ein)
(defvar ein:output-types-text-preferred
'(emacs-lisp svg image/svg+xml png image/png jpeg image/jpeg text text/plain html text/html latex text/latex javascript text/javascript))
(defvar ein:output-types-html-preferred
'(emacs-lisp svg image/svg+xml png image/png jpeg image/jpeg html text/html latex text/latex javascript text/javascript text text/plain))
(defun ein:output-type-prefer-pretty-text-over-html (data)
"Use text type if it is a \"prettified\" text instead of HTML.
This is mostly for *not* using HTML table for pandas but using
HTML for other object.
If the text type output contains a newline, it is assumed be a
prettified text thus be used instead of HTML type."
(if (ein:aand (or (plist-get data :text)
(plist-get data :text/plain))
(string-match-p "\n" it))
ein:output-types-text-preferred
ein:output-types-html-preferred))
(defun ein:fix-mime-type (type)
(aif (assoc type ein:mime-type-map)
(cdr it)
type))
(defun ein:cell-append-mime-type (json dynamic)
(when (plist-get json :data)
(setq json (plist-get json :data))) ;; For nbformat v4 support.
(cl-loop
for key in (cond
((functionp ein:output-type-preference)
(funcall ein:output-type-preference json))
(t ein:output-type-preference))
for type = (intern (format ":%s" key)) ; something like `:text'
for value = (plist-get json type) ; FIXME: optimize
when (plist-member json type)
return
(case key
;; NOTE: Normally `javascript' and `html' will not be inserted as
;; they come out after `text'. Maybe it is better to inform user
;; when one of them is inserted.
((javascript text/javascript)
(when dynamic
(ein:log 'info (concat "ein:cell-append-mime-type does not support "
"dynamic javascript. got: %s") value))
(ein:insert-read-only (plist-get json type)))
(emacs-lisp
(when dynamic
(ein:cell-safe-read-eval-insert (plist-get json type))))
((html text/html)
(funcall (ein:output-area-get-html-renderer) (plist-get json type)))
((latex text/latex text text/plain)
(ein:insert-read-only (plist-get json type)))
((svg image/svg+xml)
(ein:insert-image value (ein:fix-mime-type key) t))
((png image/png jpeg image/jpeg)
(ein:insert-image (base64-decode-string value) (ein:fix-mime-type key) t)))))
(defun ein:cell-append-mime-type (json)
(ein:output-area-case-type
json
(cl-case type
((:html)
(funcall (ein:output-area-get-html-renderer) value))
((:svg :png :jpeg)
(ein:insert-image (condition-case nil
(base64-decode-string value)
(error value))
type
t))
(otherwise
(ein:insert-read-only value)))))
(defun ein:cell-append-text (data &rest properties)
;; escape ANSI in plaintext:
@ -1154,7 +1126,6 @@ prettified text thus be used instead of HTML type."
(when (cl-typep cell 'ein:basecell)
cell))))
;;; Kernel related calls.
(cl-defmethod ein:cell-set-kernel ((cell ein:codecell) kernel)
@ -1172,7 +1143,6 @@ prettified text thus be used instead of HTML type."
(ein:cell-running-set cell t)
(ein:cell-clear-output cell t t t)
(ein:cell-set-input-prompt cell "*")
(setf (slot-value cell 'dynamic) t)
(apply #'ein:kernel-execute kernel code (ein:cell-make-callbacks cell) args))
(cl-defmethod ein:cell-make-callbacks ((cell ein:codecell))
@ -1201,7 +1171,6 @@ prettified text thus be used instead of HTML type."
))
;;; Output area
;; These function should go to ein-output-area.el. But as cell and
@ -1220,31 +1189,28 @@ prettified text thus be used instead of HTML type."
(when (or (equal msg-type "pyout")
(equal msg-type "execute_result"))
(plist-put json :prompt_number (plist-get content :execution_count)))
(setq json
(ein:output-area-convert-mime-types json (plist-get content :data))))
(setq json (ein:output-area-convert-mime-types json (plist-get content :data))))
(("pyerr" "error")
(plist-put json :ename (plist-get content :ename))
(plist-put json :evalue (plist-get content :evalue))
(plist-put json :traceback (plist-get content :traceback))))
(ein:cell-append-output cell json t)
(ein:cell-append-output cell json)
;; (setf (slot-value cell 'dirty) t)
(ein:events-trigger (slot-value cell 'events) 'maybe_reset_undo.Worksheet cell)))
(defun ein:output-area-convert-mime-types (json data)
(cl-loop for (prop . mime) in '((:text . :text/plain)
(:html . :text/html)
(:svg . :image/svg+xml)
(:png . :image/png)
(:jpeg . :image/jpeg)
(:latex . :text/latex)
(:json . :application/json)
(:javascript . :application/javascript)
(:emacs-lisp . :application/emacs-lisp))
when (plist-member data mime)
do (plist-put json prop (plist-get data mime)))
json)
(let ((known-mimes (cl-remove-if-not
#'identity
(mapcar (lambda (x) (intern-soft (concat ":" x)))
(mailcap-mime-types)))))
(or (seq-some (lambda (x)
(-when-let* ((mime-val
(plist-get data x))
(minor-kw
(ein:cell-output-type x)))
(plist-put json minor-kw mime-val)))
known-mimes)
json)))
(cl-defmethod ein:cell--handle-clear-output ((cell ein:codecell) content
_metadata)
@ -1256,7 +1222,6 @@ prettified text thus be used instead of HTML type."
)
(ein:events-trigger (slot-value cell 'events) 'maybe_reset_undo.Worksheet cell))
;;; Misc.
(cl-defmethod ein:cell-has-image-ouput-p ((cell ein:codecell))

View file

@ -270,17 +270,6 @@ Typed `:input-prompt-number' becomes a problem when reading a
notebook that saved "*". So don't add `:type'!")
(collapsed :initarg :collapsed :initform nil :type boolean)
(running :initarg :running :initform nil :type boolean)
(dynamic :initarg :dynamic :initform nil :type boolean
:documentation "\
Whether cell output is evaluated dynamically or not.
Only Emacs lisp type output data will be affected by this
slot (Javascript will not be evaluated). This value must be set
to `t' when executing cell. See `ein:notebook-execute-cell'.
In the implantation of IPython web client it is passed around via
argument, but since it is difficult to pass argument to EWOC
pretty printer, `ein:codecell' instance holds this setting in a
slot.")
(autoexec :initarg :autoexec :initform nil :type boolean
:documentation "Auto-execution flag.

View file

@ -100,13 +100,14 @@ Same as `ein:connect-run-command'."
(const :tag "Ask" ask))
:group 'ein)
(defcustom ein:connect-aotoexec-lighter nil
(defcustom ein:connect-autoexec-lighter nil
"String appended to the lighter of `ein:connect-mode' (`ein:c')
when auto-execution mode is on. When `nil', use the same string
as `ein:cell-autoexec-prompt'."
:type '(choice (string :tag "String appended to ein:c" "@")
(const :tag "Use `ein:cell-autoexec-prompt'." nil))
:group 'ein)
(define-obsolete-variable-alias 'ein:connect-aotoexec-lighter 'ein:connect-autoexec-lighter "0.17.0")
(defcustom ein:connect-default-notebook nil
"Notebook to be connect when `ein:connect-to-default-notebook' is called.
@ -383,7 +384,7 @@ notebook."
(defun ein:connect-mode-get-lighter ()
(if (slot-value ein:%connect% 'autoexec)
(format " ein:c%s" (or ein:connect-aotoexec-lighter
(format " ein:c%s" (or ein:connect-autoexec-lighter
ein:cell-autoexec-prompt))
" ein:c"))

View file

@ -64,16 +64,11 @@ when REPLACE-P returns non-`nil'."
(setcdr attr-cell (funcall replacer val))
t))))
;;; HTML renderer
(defun ein:output-area-get-html-renderer ()
;; FIXME: make this configurable
(cond
((and (fboundp 'shr-insert-document)
(fboundp 'libxml-parse-xml-region))
#'ein:insert-html-shr)
(t #'ein:insert-read-only)))
(if (and (fboundp 'shr-insert-document) (fboundp 'libxml-parse-xml-region))
#'ein:insert-html-shr
#'ein:insert-read-only))
(defcustom ein:shr-env
'((shr-table-horizontal-line ?-)
@ -125,6 +120,12 @@ Usage::
(ein:xml-replace-attributes dom 'a 'href replace-p replacer)
(ein:xml-replace-attributes dom 'img 'src replace-p replacer)))
(defmacro ein:output-area-case-type (json &rest case-body)
`(progn (aif (plist-get ,json :data) (setq ,json it)) ;; nbformat v4 ???
(seq-some (lambda (type)
(when-let ((value (plist-get ,json type)))
,@case-body))
(list :svg :png :jpeg :text :html :latex :javascript))))
(provide 'ein-output-area)

View file

@ -58,7 +58,8 @@
(autoload 'ein:shared-output-get-cell "ein-shared-output")
(autoload 'ein:shared-output-eval-string "ein-shared-output")
(autoload 'ein:kernel-live-p "ein-kernel")
(autoload 'ein:query-singleton-ajax "ein:query")
(autoload 'ein:query-singleton-ajax "ein-query")
(autoload 'ein:output-area-case-type "ein-output-area")
(defvar *ob-ein-sentinel* "[....]"
"Placeholder string replaced after async cell execution")
@ -117,22 +118,14 @@
(base64-decode-region (point-min) (point-max)))))
(defun ob-ein--return-mime-type (json file)
(cl-loop
for key in ein:output-types-text-preferred
for type = (intern (format ":%s" key)) ; something like `:text'
for value = (plist-get json type) ; FIXME: optimize
when (plist-member json type)
return
(case key
((svg image/svg)
(let ((file (or file (ob-ein--inline-image-info value))))
(ob-ein--write-base64-image value file)
(format "[[file:%s]]" file)))
((png image/png jpeg image/jpeg)
(let ((file (or file (ob-ein--inline-image-info value))))
(ob-ein--write-base64-image value file)
(format "[[file:%s]]" file)))
(t (plist-get json type)))))
(ein:output-area-case-type
json
(cl-case type
((:svg :png :jpeg)
(ob-ein--write-base64-image value
(or file (ob-ein--inline-image-info value)))
(format "[[file:%s]]" file))
(otherwise value))))
(defun ob-ein--process-outputs (outputs params)
(let ((file (cdr (assoc :image params))))

View file

@ -6,7 +6,6 @@
(require 'ein-notebook)
(require 'ein-testing-notebook)
;; ein:cell-location
(ert-deftest ein:cell-location-codecell-prompt-beg ()
@ -34,7 +33,6 @@
(should (equal (marker-position (ein:cell-location cell :input t))
(1+ (point))))))
;; from-json
(ert-deftest eintest:cell-input-prompt-number ()
@ -78,27 +76,23 @@ In \\[ \\]:
some input
"))))
;; Insert pyout/display_data
(defun eintest:cell-insert-output (outputs regexp)
(let ((ein:output-type-preference (reverse (if (functionp ein:output-type-preference)
(funcall ein:output-type-preference nil)
ein:output-type-preference))))
(ein:testing-with-one-cell
(ein:cell-from-json
(list :cell_type "code"
:outputs outputs
:source "some input"
:metadata (list :collapsed json-false :autoscroll json-false)
:execution_count 111)
:ewoc (oref ein:%worksheet% :ewoc))
(goto-char (ein:cell-location cell))
;; (message "%s" (buffer-string))
(should (looking-at (format "\
(ein:testing-with-one-cell
(ein:cell-from-json
(list :cell_type "code"
:outputs outputs
:source "some input"
:metadata (list :collapsed json-false :autoscroll json-false)
:execution_count 111)
:ewoc (oref ein:%worksheet% :ewoc))
(goto-char (ein:cell-location cell))
(message "%s" (buffer-string))
(should (looking-at (format "\
In \\[111\\]:
some input
%s" regexp))))))
%s" regexp)))))
(defmacro eintest:gene-test-cell-insert-output-pyout-and-display-data
(name regexps outputs)
@ -109,11 +103,11 @@ some input
(intern (format "ein:cell-insert-output-display-data-%s" name)))
(outputs-pyout
(cl-loop for i from 1
for x in outputs
collect
;; ein:cell--handle-output doesn't get called
;; so can't use :execution_count here although that is preferable
(append x (list :output_type "execute_result" :prompt_number i :metadata nil))))
for x in outputs
collect
;; ein:cell--handle-output doesn't get called
;; so can't use :execution_count here although that is preferable
(append x (list :output_type "execute_result" :prompt_number i :metadata nil))))
(outputs-display-data
(mapcar (lambda (x) (append '(:output_type "display_data" :metadata nil) x))
outputs))
@ -134,47 +128,47 @@ some input
,regexp-display-data)))))
(eintest:gene-test-cell-insert-output-pyout-and-display-data
text ("some output") ((:data (:text/plain "some output"))))
text ("some output") ((:data (:text "some output"))))
(eintest:gene-test-cell-insert-output-pyout-and-display-data
latex
("some output \\\\LaTeX")
((:data (:text/latex "some output \\LaTeX"))))
((:data (:latex "some output \\LaTeX"))))
(when (image-type-available-p 'svg)
(eintest:gene-test-cell-insert-output-pyout-and-display-data
svg
("some output text")
((:data (:text/plain "some output text" :image/svg ein:testing-example-svg)))))
((:data (:text "some output text" :svg ein:testing-example-svg)))))
(eintest:gene-test-cell-insert-output-pyout-and-display-data
html
("some output text")
((:data (:text/plain "some output text" :text/html "<b>not shown</b>"))))
((:data (:text "some output text" :html "<b>not shown</b>"))))
(eintest:gene-test-cell-insert-output-pyout-and-display-data
javascript
("some output text")
((:data (:text/plain "some output text" :text/javascript "$.do.something()"))))
((:data (:text "some output text" :javascript "$.do.something()"))))
(eintest:gene-test-cell-insert-output-pyout-and-display-data
text-two
("first output text" "second output text")
((:data (:text/plain "first output text")) (:data (:text/plain "second output text"))))
((:data (:text "first output text")) (:data (:text "second output text"))))
(eintest:gene-test-cell-insert-output-pyout-and-display-data
text-javascript
("first output text" "second output text")
((:data (:text/plain "first output text"))
(:data (:text/plain "second output text" :text/javascript "$.do.something()"))))
((:data (:text "first output text"))
(:data (:text "second output text" :javascript "$.do.something()"))))
(when (image-type-available-p 'svg)
(eintest:gene-test-cell-insert-output-pyout-and-display-data
text-latex-svg
("first output text" "second output \\\\LaTeX" "some output text")
((:data (:text/plain "first output text"))
(:data (:text/latex "second output \\LaTeX"))
(:data (:text/plain "some output text" :image/svg ein:testing-example-svg)))))
((:data (:text "first output text"))
(:data (:latex "second output \\LaTeX"))
(:data (:text "some output text" :svg ein:testing-example-svg)))))
;; Insert pyerr

View file

@ -16,11 +16,9 @@ if [ "x$UNAME" = "xLinux" ] ; then
make && make install
find ${WORKDIR}/R -name R -print
fi
R -e "install.packages('IRkernel', repos='http://cran.mirrors.hoobly.com')"
R -e "IRkernel::installspec()"
elif [ "x$UNAME" = "xDarwin" ]; then
brew list r &>/dev/null || HOMEBREW_NO_AUTO_UPDATE=1 brew install r
R -e "install.packages('IRkernel', repos='http://cran.mirrors.hoobly.com')"
R -e "IRkernel::installspec()"
fi
R -e "install.packages('IRkernel', repos='http://cran.mirrors.hoobly.com')"
R -e "IRkernel::installspec()"
R --version