Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Notebook Output Rendering #243

Merged
merged 11 commits into from
Aug 24, 2020
Merged

Conversation

chrisjsewell
Copy link
Member

No description provided.

- Ensure source (path, lineno) are correctly propagated to `CellOutputBundleNode`
- Capture cell level metadata in `CellOutputBundleNode`
- New `CellOutputRenderer` class to contain render methods
- Simplify test code, using sphinx `get_doctree` and `get_and_resolve_doctree` methods
This allows ofr other post-transforms, like `ReferencesResolver` to act on the output nodes.
@codecov
Copy link

codecov bot commented Aug 23, 2020

Codecov Report

Merging #243 into master will increase coverage by 0.64%.
The diff coverage is 88.01%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #243      +/-   ##
==========================================
+ Coverage   86.52%   87.16%   +0.64%     
==========================================
  Files          10       12       +2     
  Lines         987     1239     +252     
==========================================
+ Hits          854     1080     +226     
- Misses        133      159      +26     
Flag Coverage Δ
#pytests 87.16% <88.01%> (+0.64%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
myst_nb/__init__.py 88.27% <82.60%> (-1.33%) ⬇️
myst_nb/ansi_lexer.py 84.70% <84.70%> (ø)
myst_nb/render_outputs.py 86.89% <86.89%> (ø)
myst_nb/nb_glue/domain.py 90.45% <100.00%> (-0.19%) ⬇️
myst_nb/nb_glue/transform.py 82.35% <100.00%> (ø)
myst_nb/nodes.py 100.00% <100.00%> (ø)
myst_nb/parser.py 96.40% <100.00%> (-0.31%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0b09901...3368a64. Read the comment docs.

The notebook renderer class is now loaded from an entrypoint, with a configurable name.
Also moved node classes to a separate module.
ANSI lexer is applied to stdout/stderr and text/plain by default
@mgeier
Copy link

mgeier commented Aug 23, 2020

If you need a few test cases for ANSI coloring, feel free to have a look at https://nbsphinx.readthedocs.io/code-cells.html#ANSI-Colors

I've implemented the ANSI support in nbconvert, notebook and jupyterlab, so I know a few of the pitfalls.
If you need pointers, please let me know.

@chrisjsewell
Copy link
Member Author

Thanks @mgeier!
I added your 8-bit ANSI into the docs, and it looks to be working well 😄 https://myst-nb--243.org.readthedocs.build/en/243/use/formatting_outputs.html#ansi-outputs
I'll leave 256-but ANSI for another time/PR though (or feel free to give it a go 😬)

@chrisjsewell chrisjsewell force-pushed the improve/cell-metadata branch from 73098ec to c4f3b40 Compare August 23, 2020 21:44
@chrisjsewell
Copy link
Member Author

chrisjsewell commented Aug 23, 2020

Hey @mmcky and @AakashGfude (and I see you lurking @choldgraf 👀 😆) I have maybe a few more bits to add on this, but the main restructuring and features are there if you want to have a look and give any thoughts.
The commit messages and this page should explain the main changes/additions: https://myst-nb--243.org.readthedocs.build/en/243/use/formatting_outputs.html

@chrisjsewell
Copy link
Member Author

chrisjsewell commented Aug 23, 2020

Oh and also @akhmerov; a) because I'm sure it'll be of interest, but also b) because you can now close jupyter/jupyter-sphinx#130 however you see fit, since I don't even use cell_output_to_nodes any more lol

@akhmerov
Copy link
Contributor

@chrisjsewell thanks for the heads up, this is indeed very informative. At a glance render_outputs.py seems like it could as well be in jupyter_sphinx. Did you consider that option?

@chrisjsewell
Copy link
Member Author

chrisjsewell commented Aug 23, 2020

Well we can have a look and think about that at a later date, but for now (a) its too much complication, in terms of cross-repo testing, documentation, reviewing, etc, but also (b) it has dependencies on myst-parser, myst-nb configuration values, and relies on the use of cell metadata, which jupyter-sphinx's directives might have difficulty handling.

So I'm open to the possibility, but I'm afraid I haven't really got time to figure out how 😬

@akhmerov
Copy link
Contributor

akhmerov commented Aug 23, 2020

Sounds reasonable. As a quick note: factoring out myst-specifc parts would amount to making an entry point for the myst-markdown renderer.

Passing render state via cell tags seems alright.

@chrisjsewell
Copy link
Member Author

Passing render state via cell tags seems alright.

Well, its going to be more than just tags...

@akhmerov
Copy link
Contributor

akhmerov commented Aug 23, 2020

Really? That seemed to be the only context where metadata occurs in render_outputs.py.

@chrisjsewell
Copy link
Member Author

Really? That seemed to be the only context where metadata occurs in render_outputs.py.

Thats now, in a few hours, maybe not lol

@chrisjsewell
Copy link
Member Author

We can also set a caption (which is rendered as [CommonMark](https://commonmark.org/)) and name, by which to reference the figure:

````md
```{code-cell} ipython3
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chrisjsewell what happens if the code in this code-block doesn't produce an image? Do we throw an error if the returned mime type of the output block isn't compatible with the image directive?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then the render_image method is never called, and the metadata just isn't used,
i.e. the metadata is accessed (and validated) "lazily" when it is required

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is invalid, for example I give a bad width here, then it produces a warning, and since this a text-based notebook, that warning will point to the correct line of the document (the first one of the code cell)

```{code-cell} ipython3
---
myst:
  image:
    width: abc
---
from IPython.display import Image
Image("images/fun-fish.png")
```
/Users/chrisjsewell/Documents/GitHub/MyST-NB-actual/docs/use/formatting_outputs.md:110: WARNING: output render: Invalid image attribute: (key: 'width'; value: abc)
not a positive measure of one of the following units:
"em" "ex" "px" "in" "cm" "mm" "pt" "pc" "%"

and also, because the image is no longer created:

/Users/chrisjsewell/Documents/GitHub/MyST-NB-actual/docs/use/formatting_outputs.md:124: WARNING: 'myst' reference target not found: fun-fish

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see -- thanks @chrisjsewell

Copy link
Member

@mmcky mmcky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @chrisjsewell. It is neat to be able to apply styles and formatting on images that are generated by code and the ansi support is timely on the LaTeX side.

For ansi support -- I came across the ansifilter project this morning that may be useful in converting to various formats. However I don't think it is directly importable to python that I can tell.

For LaTeX output -- from what I can tell reading the code you parse ansi codes and return a new token object for inclusion into the AST so we just need to support those new node types when writing the tex (or skip those elements for stripping output of ansi codes)?

"KL\x1b[49mMN\x1b[39mOP\x1b[22mQR\x1b[24mST\x1b[27mUV")
```

This uses the built-in {py:class}`~myst_nb.ansi_lexer.AnsiColorLexer` [pygments lexer](https://pygments.org/).
Copy link
Member

@mmcky mmcky Aug 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chrisjsewell is there a reason we don't just import this from pygments.

Copy link
Member Author

@chrisjsewell chrisjsewell Aug 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because its not part of pygments, there is no built-in ANSI lexer unfortunately (from what I could see), this is a bespoke one

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears to work well for the 8-bit ansi though, perhaps some finessing of the CSS.
Then, as it says in the docs, we can get round to the adding 256-bit support as and when necessary

Copy link
Member

@mmcky mmcky Aug 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh OK -- sorry had misread header comment on the file. Makes sense.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this something we should contribute upstream to pygments at some point?

Copy link
Member Author

@chrisjsewell chrisjsewell Aug 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeh quite possibly; just wanted to get everything working first, and then we can look to tidy up

@chrisjsewell
Copy link
Member Author

that may be useful in converting to various formats

Well, fingers crossed, that should already happen, because we are using pygments to lex it to tokens, then sphinx chooses the appropriate formatter to convert it: https://pygments.org/docs/formatters/

@chrisjsewell
Copy link
Member Author

@mmcky has given i the thumbs up for "working" (or at least not breaking) LaTeX, so I'm going to merge this monstrosity!

You can change the lexer used in the `conf.py`, for example to turn off lexing:

```python
nb_render_text_lexer = "none"
Copy link
Member

@mmcky mmcky Aug 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chrisjsewell one last question. Does this option in conf.py just control ansi parsing of text?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, it controls what pygments lexer is applied to plain text outputs (including stdout/stderr)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok - so it does more than just ansi. Was just thinking it might be better named nb_render_text_ansi but it is more generally a control over the lexer used which contains more than just ansi parsing.

@mgeier
Copy link

mgeier commented Aug 24, 2020

I added your 8-bit ANSI into the docs, and it looks to be working well

image

It doesn't seem to support "underline" and "invert", and there don't seem to be any "intense" colors when switching to "bold".

image

I'll leave 256-but ANSI for another time/PR though (or feel free to give it a go)

I've already implemented this in three different places (in two languages), and I don't currently have the energy to do it yet again.

Do you even have to use Pygments? Why not just use nbconvert's implementation?

For reference, the nbconvert implementation is there: https://github.com/jupyter/nbconvert/blob/master/nbconvert/filters/ansi.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants