nbcat 0.9.4__py3-none-any.whl → 0.9.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nbcat/__init__.py +1 -1
- nbcat/enums.py +7 -0
- nbcat/main.py +23 -9
- nbcat/markdown.py +51 -0
- nbcat/schemas.py +25 -12
- {nbcat-0.9.4.dist-info → nbcat-0.9.6.dist-info}/METADATA +26 -12
- nbcat-0.9.6.dist-info/RECORD +12 -0
- nbcat-0.9.4.dist-info/RECORD +0 -11
- {nbcat-0.9.4.dist-info → nbcat-0.9.6.dist-info}/WHEEL +0 -0
- {nbcat-0.9.4.dist-info → nbcat-0.9.6.dist-info}/entry_points.txt +0 -0
- {nbcat-0.9.4.dist-info → nbcat-0.9.6.dist-info}/licenses/LICENSE +0 -0
nbcat/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.9.
|
1
|
+
__version__ = "0.9.6"
|
nbcat/enums.py
CHANGED
nbcat/main.py
CHANGED
@@ -9,19 +9,20 @@ from argcomplete.completers import FilesCompleter
|
|
9
9
|
from pydantic import ValidationError
|
10
10
|
from rich import box
|
11
11
|
from rich.console import Console, RenderableType
|
12
|
-
from rich.markdown import Markdown
|
13
12
|
from rich.panel import Panel
|
13
|
+
from rich.pretty import Pretty
|
14
14
|
from rich.syntax import Syntax
|
15
15
|
from rich.table import Table
|
16
16
|
from rich.text import Text
|
17
17
|
|
18
18
|
from . import __version__
|
19
|
-
from .enums import CellType
|
19
|
+
from .enums import CellType, OutputCellType
|
20
20
|
from .exceptions import (
|
21
21
|
InvalidNotebookFormatError,
|
22
22
|
NotebookNotFoundError,
|
23
23
|
UnsupportedNotebookTypeError,
|
24
24
|
)
|
25
|
+
from .markdown import Markdown
|
25
26
|
from .schemas import Cell, Notebook
|
26
27
|
|
27
28
|
console = Console()
|
@@ -92,18 +93,28 @@ def render_cell(cell: Cell) -> list[tuple[Union[str, None], RenderableType]]:
|
|
92
93
|
def _render_raw(input: str) -> Text:
|
93
94
|
return Text(input)
|
94
95
|
|
96
|
+
def _render_image(input: str) -> None:
|
97
|
+
return None
|
98
|
+
|
99
|
+
def _render_json(input: str) -> Pretty:
|
100
|
+
return Pretty(input)
|
101
|
+
|
95
102
|
RENDERERS = {
|
96
103
|
CellType.MARKDOWN: _render_markdown,
|
97
104
|
CellType.CODE: _render_code,
|
98
105
|
CellType.RAW: _render_raw,
|
99
106
|
CellType.HEADING: _render_markdown,
|
107
|
+
OutputCellType.PLAIN: _render_raw,
|
108
|
+
OutputCellType.HTML: _render_markdown,
|
109
|
+
OutputCellType.IMAGE: _render_image,
|
110
|
+
OutputCellType.JSON: _render_json,
|
100
111
|
}
|
101
112
|
|
102
113
|
rows: list[tuple[Union[str, None], RenderableType]] = []
|
103
114
|
renderer = RENDERERS.get(cell.cell_type)
|
104
115
|
source = renderer(cell.input) if renderer else None
|
105
116
|
if source:
|
106
|
-
label = f"[green][{cell.execution_count}][/]
|
117
|
+
label = f"[green][{cell.execution_count}][/]" if cell.execution_count else None
|
107
118
|
rows.append(
|
108
119
|
(
|
109
120
|
label,
|
@@ -113,13 +124,16 @@ def render_cell(cell: Cell) -> list[tuple[Union[str, None], RenderableType]]:
|
|
113
124
|
|
114
125
|
for o in cell.outputs:
|
115
126
|
if o.output:
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
127
|
+
renderer = RENDERERS.get(o.output.output_type)
|
128
|
+
output = renderer(o.output.text) if renderer else None
|
129
|
+
if output:
|
130
|
+
label = f"[blue][{o.execution_count}][/]" if o.execution_count else None
|
131
|
+
rows.append(
|
132
|
+
(
|
133
|
+
label,
|
134
|
+
output,
|
135
|
+
)
|
121
136
|
)
|
122
|
-
)
|
123
137
|
return rows
|
124
138
|
|
125
139
|
|
nbcat/markdown.py
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# In an ideal world, this code shouldn't exist. However, for reasons unknown to me,
|
2
|
+
# `rich` decided to format markdown headers in a way that makes them unusable
|
3
|
+
# in the terminal: they are centered and wrapped in a Panel, which causes them to
|
4
|
+
# blend in with the rest of the content. It takes me time to understand what I'm
|
5
|
+
# looking at.
|
6
|
+
#
|
7
|
+
# Instead, I override the default implementation by adding colors and preserving
|
8
|
+
# the original formatting, so that the headers remain recognizable even on
|
9
|
+
# black-and-white screens.
|
10
|
+
|
11
|
+
from __future__ import annotations
|
12
|
+
|
13
|
+
from typing import ClassVar
|
14
|
+
|
15
|
+
from rich import markdown as md
|
16
|
+
from rich.console import Console, ConsoleOptions, RenderResult
|
17
|
+
from rich.text import Text
|
18
|
+
|
19
|
+
|
20
|
+
class Heading(md.Heading):
|
21
|
+
"""A heading."""
|
22
|
+
|
23
|
+
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
24
|
+
styles = {
|
25
|
+
"h1": "bold cyan",
|
26
|
+
"h2": "bold magenta",
|
27
|
+
"h3": "bold yellow",
|
28
|
+
}
|
29
|
+
indent = int(self.tag.strip("h"))
|
30
|
+
yield Text(f"{'#' * indent} {self.text}", style=styles.get(self.tag, "dim white"))
|
31
|
+
|
32
|
+
|
33
|
+
class Markdown(md.Markdown):
|
34
|
+
elements: ClassVar[dict[str, type[md.MarkdownElement]]] = {
|
35
|
+
"paragraph_open": md.Paragraph,
|
36
|
+
"heading_open": Heading,
|
37
|
+
"fence": md.CodeBlock,
|
38
|
+
"code_block": md.CodeBlock,
|
39
|
+
"blockquote_open": md.BlockQuote,
|
40
|
+
"hr": md.HorizontalRule,
|
41
|
+
"bullet_list_open": md.ListElement,
|
42
|
+
"ordered_list_open": md.ListElement,
|
43
|
+
"list_item_open": md.ListItem,
|
44
|
+
"image": md.ImageItem,
|
45
|
+
"table_open": md.TableElement,
|
46
|
+
"tbody_open": md.TableBodyElement,
|
47
|
+
"thead_open": md.TableHeaderElement,
|
48
|
+
"tr_open": md.TableRowElement,
|
49
|
+
"td_open": md.TableDataElement,
|
50
|
+
"th_open": md.TableDataElement,
|
51
|
+
}
|
nbcat/schemas.py
CHANGED
@@ -2,7 +2,7 @@ from typing import Any, Union
|
|
2
2
|
|
3
3
|
from pydantic import BaseModel, computed_field, model_validator
|
4
4
|
|
5
|
-
from .enums import CellType, OutputType
|
5
|
+
from .enums import CellType, OutputCellType, OutputType
|
6
6
|
from .exceptions import InvalidNotebookFormatError
|
7
7
|
|
8
8
|
|
@@ -11,15 +11,19 @@ class BaseOutput(BaseModel):
|
|
11
11
|
execution_count: Union[int, None] = None
|
12
12
|
|
13
13
|
|
14
|
+
class CellOutput(BaseModel):
|
15
|
+
output_type: OutputCellType
|
16
|
+
text: str
|
17
|
+
|
18
|
+
|
14
19
|
class StreamOutput(BaseOutput):
|
15
20
|
text: Union[list[str], str]
|
16
21
|
|
17
22
|
@computed_field
|
18
23
|
@property
|
19
|
-
def output(self) ->
|
20
|
-
if isinstance(self.text, list)
|
21
|
-
|
22
|
-
return self.text
|
24
|
+
def output(self) -> CellOutput:
|
25
|
+
text = "".join(self.text) if isinstance(self.text, list) else self.text
|
26
|
+
return CellOutput(output_type=OutputCellType.PLAIN, text=text)
|
23
27
|
|
24
28
|
|
25
29
|
class DisplayDataOutput(BaseOutput):
|
@@ -27,9 +31,18 @@ class DisplayDataOutput(BaseOutput):
|
|
27
31
|
|
28
32
|
@computed_field
|
29
33
|
@property
|
30
|
-
def output(self) ->
|
31
|
-
|
32
|
-
|
34
|
+
def output(self) -> Union[CellOutput, None]:
|
35
|
+
data_type_map = {
|
36
|
+
"text/html": OutputCellType.HTML,
|
37
|
+
"text/png": OutputCellType.IMAGE,
|
38
|
+
"text/plain": OutputCellType.PLAIN,
|
39
|
+
"application/vnd.raw.v1+json": OutputCellType.JSON,
|
40
|
+
}
|
41
|
+
for data_type, output_type in data_type_map.items():
|
42
|
+
data = self.data.get(data_type)
|
43
|
+
if data:
|
44
|
+
text = "".join(data) if isinstance(data, list) else str(data)
|
45
|
+
return CellOutput(output_type=output_type, text=text)
|
33
46
|
|
34
47
|
|
35
48
|
class ErrorOutput(BaseOutput):
|
@@ -39,8 +52,8 @@ class ErrorOutput(BaseOutput):
|
|
39
52
|
|
40
53
|
@computed_field
|
41
54
|
@property
|
42
|
-
def output(self) ->
|
43
|
-
return "\n".join(self.traceback)
|
55
|
+
def output(self) -> CellOutput:
|
56
|
+
return CellOutput(output_type=OutputCellType.PLAIN, text="\n".join(self.traceback))
|
44
57
|
|
45
58
|
|
46
59
|
class PyoutDataOutput(BaseOutput):
|
@@ -48,8 +61,8 @@ class PyoutDataOutput(BaseOutput):
|
|
48
61
|
|
49
62
|
@computed_field
|
50
63
|
@property
|
51
|
-
def output(self) ->
|
52
|
-
return "\n".join(self.text)
|
64
|
+
def output(self) -> CellOutput:
|
65
|
+
return CellOutput(output_type=OutputCellType.PLAIN, text="\n".join(self.text))
|
53
66
|
|
54
67
|
|
55
68
|
class Cell(BaseModel):
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: nbcat
|
3
|
-
Version: 0.9.
|
3
|
+
Version: 0.9.6
|
4
4
|
Summary: cat for jupyter notebooks
|
5
5
|
Project-URL: Homepage, https://github.com/akopdev/nbcat
|
6
6
|
Project-URL: Repository, https://github.com/akopdev/nbcat
|
@@ -41,17 +41,31 @@ Requires-Dist: pytest-responses; extra == 'dev'
|
|
41
41
|
Requires-Dist: ruff; extra == 'dev'
|
42
42
|
Description-Content-Type: text/markdown
|
43
43
|
|
44
|
-
#
|
44
|
+
# nbcat
|
45
45
|
|
46
|
-
`nbcat`
|
46
|
+
`nbcat` let you preview Jupyter notebooks directly in your terminal. Think of it as `cat`, but for `.ipynb` files.
|
47
47
|
|
48
|
-
|
48
|
+
<p align="center">
|
49
|
+
<a href="docs/screenshot.png" target="blank"><img src="docs/screenshot.png" width="400" /></a>
|
50
|
+
</p>
|
49
51
|
|
50
|
-
|
52
|
+
## Features
|
53
|
+
|
54
|
+
- Very fast and lightweight with minimal dependencies
|
51
55
|
- Preview remote notebooks without downloading them
|
52
|
-
- Supports for all Jupyter notebook versions
|
56
|
+
- Supports for all Jupyter notebook versions, including old legacy formats
|
57
|
+
|
58
|
+
## Motivation
|
59
|
+
|
60
|
+
The idea of previewing notebooks in a terminal is not new - there have been many previous attempts to achieve it.
|
61
|
+
However, most are either slow and overengineered with a ton of half-working features, or they're outdated and incompatible with modern Python.
|
62
|
+
|
63
|
+
I was looking for a simple tool that let me quickly render Jupyter notebooks without switching context from my terminal window or installing a ton of dependencies.
|
64
|
+
|
65
|
+
Please note, that `nbcat` doesn't aim to replace JupyterLab. If you need a full-featured terminal experience, I recommend checking out [euporie](https://euporie.readthedocs.io/) instead.
|
66
|
+
|
53
67
|
|
54
|
-
##
|
68
|
+
## Installation
|
55
69
|
|
56
70
|
From the command line using pip:
|
57
71
|
|
@@ -59,7 +73,7 @@ From the command line using pip:
|
|
59
73
|
pip install nbcat
|
60
74
|
```
|
61
75
|
|
62
|
-
##
|
76
|
+
## Quickstart
|
63
77
|
|
64
78
|
```bash
|
65
79
|
$ nbcat notebook.ipynb
|
@@ -77,7 +91,7 @@ Example use case with `fzf` command that lists all `.ipynb` files and uses `nbca
|
|
77
91
|
find . -type f -name "*.ipynb" | fzf --preview 'nbcat {}'
|
78
92
|
```
|
79
93
|
|
80
|
-
##
|
94
|
+
## Testing & Development
|
81
95
|
|
82
96
|
Run the tests:
|
83
97
|
|
@@ -91,15 +105,15 @@ Check code quality:
|
|
91
105
|
make format lint
|
92
106
|
```
|
93
107
|
|
94
|
-
##
|
108
|
+
## Contributing
|
95
109
|
|
96
110
|
Contributions are welcome! Please open an issue or [pull request](https://github.com/akopdev/nbcat/pulls).
|
97
111
|
|
98
|
-
##
|
112
|
+
## License
|
99
113
|
|
100
114
|
Distributed under the MIT License. See [`LICENSE`](./LICENSE) for more information.
|
101
115
|
|
102
|
-
##
|
116
|
+
## Useful Links
|
103
117
|
|
104
118
|
- 📘 Documentation: _coming soon_
|
105
119
|
- 🐛 Issues: [GitHub Issues](https://github.com/akopdev/nbcat/issues)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
nbcat/__init__.py,sha256=IgVHjr-TeioZYLJSkvpT80LLGi6U3ONzR1cfYfd5XNQ,22
|
2
|
+
nbcat/enums.py,sha256=Fn8PIcLl_uY4nQIs1EUvmKTwfhNUIZgmhRFiCSJk9wk,411
|
3
|
+
nbcat/exceptions.py,sha256=Ho7LQz9K70VtIMDNtAwuAtGmb-lFKxGxSj7MN3-EpDA,321
|
4
|
+
nbcat/main.py,sha256=7RYjHvWmKWoD9LFI1dopKvp_wvqWa2Rk9AYZ_RsrIaw,5805
|
5
|
+
nbcat/markdown.py,sha256=K6yL9rY3uTxMPsRIJnxqNHmCX2Qc-f3my8eiHxTLfic,1835
|
6
|
+
nbcat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
|
+
nbcat/schemas.py,sha256=fjSbdXa4pHKRV1Z-vUESkYZywkojanCvSu8s7rc9Xkw,3134
|
8
|
+
nbcat-0.9.6.dist-info/METADATA,sha256=v-8f9HAlHG2t-Mw36CZAIGcMRTeCWiyt0BzZMlS164w,4082
|
9
|
+
nbcat-0.9.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
10
|
+
nbcat-0.9.6.dist-info/entry_points.txt,sha256=io_GRDsecAkYuCZALsjyea3VBq91VCoSznqlZEAJshY,42
|
11
|
+
nbcat-0.9.6.dist-info/licenses/LICENSE,sha256=7GjUnahXdd5opdvlpJdb1BisLbiXt2iOFhzIUduhdkE,1072
|
12
|
+
nbcat-0.9.6.dist-info/RECORD,,
|
nbcat-0.9.4.dist-info/RECORD
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
nbcat/__init__.py,sha256=e56AvHfJCtG2ZwwINqsxINVbehWdKxMYgIDbjd7P-II,22
|
2
|
-
nbcat/enums.py,sha256=ZsuOwYLF0D4PVwSkS74LwoXY0y0DkeBToLBWnmiS97Y,300
|
3
|
-
nbcat/exceptions.py,sha256=Ho7LQz9K70VtIMDNtAwuAtGmb-lFKxGxSj7MN3-EpDA,321
|
4
|
-
nbcat/main.py,sha256=02qOCh4I4kXpoIgZCRYztiY5QPDw7FkAbC4smjl1Sxc,5273
|
5
|
-
nbcat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
nbcat/schemas.py,sha256=miVL0GDHIqXaFxz_cd9qn7cSZvIjZYK8TTY9YsfkuXs,2407
|
7
|
-
nbcat-0.9.4.dist-info/METADATA,sha256=VkAsyObzS8n5gT7qzA7Gxertoz863ZWENfqgaz3EGu0,3375
|
8
|
-
nbcat-0.9.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
9
|
-
nbcat-0.9.4.dist-info/entry_points.txt,sha256=io_GRDsecAkYuCZALsjyea3VBq91VCoSznqlZEAJshY,42
|
10
|
-
nbcat-0.9.4.dist-info/licenses/LICENSE,sha256=7GjUnahXdd5opdvlpJdb1BisLbiXt2iOFhzIUduhdkE,1072
|
11
|
-
nbcat-0.9.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|