7

I would like to be able to read a jupyter notebook without actually having a jupyter kernel, in Emacs. For instance, having a file example.ipynb, I would like to be able to open that file with Emacs, and make it render it with basic formatting, as would an org file. I don't want it to connect to a jupyter kernel, or to be able to write to it. Basically, I want to be able to read instructions given in the format of a jupyter notebook offline and without installing jupyter.

All the solutions I have seen so far try to make Emacs a jupyter client, ie. something that interacts with a jupyter kernel, which is not what I try to achieve. I have also found tools to convert from org-mode to jupyter notebooks, but not the other way around (which could be a solution).

Any idea on how to solve this issue?

2 Answers 2

4

SECOND EDIT

Easiest answer is in the comments, but I think the code example in the FIRST EDIT is very much worth it to check out also.

FIRST EDIT

To 'demonstrate' that you could convert the file with some basic elisp (and because it is just too much fun to write elisp) I have created a simple ipynb-to-markdown convert function (the code only exports .png images, but you can easily add support for other formats by slightly adapting the code. Also there is a small hindrance when using markdown-mode, because it does not straightforwardly allow for embedded images. Therefore I have outcommented the activation of markdown mode, allowing you to use the code without installing markdown-mode)

(require 'markdown-mode nil t)

(defun ipynb-to-markdown (file)
  (interactive "f")
  (let* ((data (with-temp-buffer
                 (insert-file-literally file)
                 (json-parse-string (buffer-string)
                                    :object-type 'alist
                                    :array-type 'list)))
         (metadata (alist-get 'metadata data))
         (kernelspec (alist-get 'kernelspec metadata))
         (language (alist-get 'language kernelspec)))
    (pop-to-buffer "ipynb-as-markdown")
    ;; (when (featurep 'markdown-mode)
    ;;   (markdown-mode))
    (dolist (c (alist-get 'cells data))
      (let* ((contents (alist-get 'source c))
             (outputs (alist-get 'outputs c)))
        (pcase (alist-get 'cell_type c)
          ("markdown"
           (when contents
             (mapcar #'insert contents)
             (insert "\n\n")))
          ("code"
           (when contents
             (insert "```")
             (insert language)
             (insert "\n")
             (mapcar #'insert contents)
             (insert "\n```\n\n")
             (dolist (x outputs)
               (when-let (text (alist-get 'text x))
                 (insert "```stdout\n")
                 (insert (mapconcat #'identity text ""))
                 (insert "\n```\n\n"))
               (when-let (data (alist-get 'data x))
                 (when-let (im64 (alist-get 'image/png data))
                   (let ((imdata (base64-decode-string im64)))
                     (insert-image (create-image imdata 'png t)))))
               (insert "\n\n")))))))))

You can eval the code and then call the command using M-x ipynb-to-markdown, then, after you select a file, it will convert its contents and open it in a markdown buffer (this requires markdown-mode to be installed).

If you are moderately ambitious, then you could (continue to) convert it to org-mode.

END EDIT

As you mention that converting the file would be an acceptable solution, I would suggest that you use an online converter tool like e.g. vertopal to convert the '.ipynb' file to 'html' or 'pdf'. Subsequently, for html you can use eww-open-file to render the file nicely. For PDF of course there is docview or even better pdf-tools.

Alternatively, if you know only some basic elisp, then you could quite easily convert the .ipynb to markdown, e.g. by opening the file literally and then use something like (json-parse-buffer :object-type 'alist :array-type 'list) (see also here) and subsequently print the cells to some markdown buffer/file requiring you to add only a tiny bit of syntax.

Finally, if you decide that installing jupyter is fine, then you could use the nbconvert with the nbcorg package to convert the notebook to org-mode directly.

4
  • 2
    What about using Pandoc? predictablynoisy.com/posts/2019/2019-11-11-ipynb_pandoc
    – mankoff
    Commented Nov 29, 2021 at 22:50
  • 1
    My god! That for sure is the easiest/best solution. When I googled for it, I did not find this option. Anyway, when converting to org-mode with pandoc, then don't forget to add the --extract-media=[some folder] option. I will leave the original answer here for the comments (and for the still interesting code example). Commented Nov 30, 2021 at 8:38
  • And because the code example 1. does not even require pandoc, 2. embeds the images, 3. is much faster than pandoc (more or less instant). Commented Nov 30, 2021 at 8:47
  • 4. does not force you to create/save any additional files (beside the .ipynb itself) Commented Nov 30, 2021 at 9:04
3

Besides pandoc, which can now even round-trip convert ipynb to org, there is also code-cells that will automatically open ipynb as a py text file via jupytext. The second solution is useful for quickly inspecting the input code and also saving easily code changes, but note: output cells are not shown and saving the py file will save to the ipynb file with all the output cells lost. There might be a way around this by plugging pandoc into code-cells, see tweaking the conversion.

A third way is converting the ipynb to html via nbconvert and render the html with shr inside emacs. The notebook's css is not shown but everything else is there. I use a modification of the following function

(defun gm/shr-open-ipynb (filename)
  "Open ipynb file as html."
  (interactive)
  (let* ((shortname (file-name-nondirectory filename))
         (command "jupyter nbconvert --to html --log-level WARN --stdout --stdin"))
    (with-temp-buffer
      (insert-file-contents filename)
      (shell-command-on-region (point-min) (point-max) command nil 'no-mark)
      (shr-render-buffer (current-buffer))
      )
    (with-current-buffer "*html*"
      (rename-buffer shortname 'unique)
      (read-only-mode t))
    ))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.