nbcat 0.12.0__py3-none-any.whl → 0.13.0__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/main.py +50 -27
- nbcat/pager.py +82 -0
- {nbcat-0.12.0.dist-info → nbcat-0.13.0.dist-info}/METADATA +6 -4
- nbcat-0.13.0.dist-info/RECORD +14 -0
- nbcat-0.12.0.dist-info/RECORD +0 -13
- {nbcat-0.12.0.dist-info → nbcat-0.13.0.dist-info}/WHEEL +0 -0
- {nbcat-0.12.0.dist-info → nbcat-0.13.0.dist-info}/entry_points.txt +0 -0
- {nbcat-0.12.0.dist-info → nbcat-0.13.0.dist-info}/licenses/LICENSE +0 -0
nbcat/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.
|
1
|
+
__version__ = "0.13.0"
|
nbcat/main.py
CHANGED
@@ -6,7 +6,6 @@ import argcomplete
|
|
6
6
|
import requests
|
7
7
|
from argcomplete.completers import FilesCompleter
|
8
8
|
from pydantic import ValidationError
|
9
|
-
from rich import box
|
10
9
|
from rich.console import Console, Group, RenderableType
|
11
10
|
from rich.padding import Padding
|
12
11
|
from rich.panel import Panel
|
@@ -23,6 +22,7 @@ from .exceptions import (
|
|
23
22
|
)
|
24
23
|
from .image import Image
|
25
24
|
from .markdown import Markdown
|
25
|
+
from .pager import Pager
|
26
26
|
from .schemas import Cell, Notebook
|
27
27
|
|
28
28
|
console = Console()
|
@@ -87,10 +87,8 @@ def render_cell(cell: Cell) -> RenderableType:
|
|
87
87
|
def _render_markdown(input: str) -> Markdown:
|
88
88
|
return Markdown(input, code_theme="ansi_dark")
|
89
89
|
|
90
|
-
def _render_code(input: str, language: str = "python") ->
|
91
|
-
return
|
92
|
-
Syntax(input, language, line_numbers=True, theme="ansi_dark", dedent=True), padding=0
|
93
|
-
)
|
90
|
+
def _render_code(input: str, language: str = "python") -> Syntax:
|
91
|
+
return Syntax(input, language, theme="ansi_dark", padding=(1, 2), dedent=True)
|
94
92
|
|
95
93
|
def _render_raw(input: str) -> Text:
|
96
94
|
return Text(input)
|
@@ -116,41 +114,48 @@ def render_cell(cell: Cell) -> RenderableType:
|
|
116
114
|
renderer = RENDERERS.get(cell.cell_type)
|
117
115
|
source = renderer(cell.input) if renderer else None
|
118
116
|
if source:
|
119
|
-
|
117
|
+
s_title = f"[green]In [{cell.execution_count}][/]" if cell.execution_count else None
|
118
|
+
if s_title:
|
119
|
+
rows.append(Panel(source, title=s_title, title_align="left"))
|
120
|
+
else:
|
121
|
+
rows.append(Padding(source, (1, 0)))
|
122
|
+
|
120
123
|
if not cell.outputs:
|
121
|
-
return
|
124
|
+
return rows.pop()
|
122
125
|
|
123
126
|
for o in cell.outputs:
|
127
|
+
o_title = f"[blue]Out [{o.execution_count}][/]" if o.execution_count else None
|
124
128
|
if o.output:
|
125
129
|
renderer = RENDERERS.get(o.output.output_type)
|
126
130
|
output = renderer(o.output.text) if renderer else None
|
127
131
|
if output:
|
128
|
-
|
132
|
+
if o_title:
|
133
|
+
rows.append(Panel(output, style="italic", title=o_title, title_align="left"))
|
134
|
+
else:
|
135
|
+
rows.append(Padding(output, (1, 0), style="italic"))
|
129
136
|
return Group(*rows)
|
130
137
|
|
131
138
|
|
132
|
-
def
|
139
|
+
def render_notebook(nb: Notebook) -> list[RenderableType]:
|
133
140
|
"""
|
134
|
-
|
141
|
+
Convert a Notebook object into a list of rich renderables for terminal display.
|
142
|
+
|
143
|
+
Each cell in the notebook is processed and rendered using `render_cell`,
|
144
|
+
producing a sequence of styled input/output blocks suitable for use in a
|
145
|
+
Textual or Rich-based terminal UI.
|
135
146
|
|
136
147
|
Args:
|
137
|
-
nb (Notebook):
|
138
|
-
"""
|
139
|
-
if not nb.cells:
|
140
|
-
console.print("[bold red]Notebook contains no cells.")
|
141
|
-
return
|
148
|
+
nb (Notebook): The notebook object containing parsed cells (e.g., from a .ipynb file).
|
142
149
|
|
150
|
+
Returns
|
151
|
+
-------
|
152
|
+
list[RenderableType]: A list of rich renderable objects representing the notebook cells.
|
153
|
+
Returns an empty list if the notebook has no cells.
|
154
|
+
"""
|
155
|
+
rendered: list[RenderableType] = []
|
143
156
|
for cell in nb.cells:
|
144
|
-
rendered
|
145
|
-
|
146
|
-
out = Panel(
|
147
|
-
rendered,
|
148
|
-
title=f"[green][{cell.execution_count}][/]" if cell.execution_count else None,
|
149
|
-
title_align="left",
|
150
|
-
)
|
151
|
-
else:
|
152
|
-
out = Padding(rendered, (1, 0))
|
153
|
-
console.print(out)
|
157
|
+
rendered.append(render_cell(cell))
|
158
|
+
return rendered
|
154
159
|
|
155
160
|
|
156
161
|
def main():
|
@@ -162,20 +167,38 @@ def main():
|
|
162
167
|
"file", help="Path or URL to a .ipynb notebook", type=str
|
163
168
|
).completer = FilesCompleter()
|
164
169
|
parser.add_argument(
|
170
|
+
"-v",
|
165
171
|
"--version",
|
166
172
|
help="print version information and quite",
|
167
173
|
action="version",
|
168
174
|
version=__version__,
|
169
175
|
)
|
170
176
|
parser.add_argument(
|
171
|
-
"--debug", help="enable extended error output", action="store_true", default=False
|
177
|
+
"-d", "--debug", help="enable extended error output", action="store_true", default=False
|
178
|
+
)
|
179
|
+
parser.add_argument(
|
180
|
+
"-p",
|
181
|
+
"--page",
|
182
|
+
help="enable paginated view mode (similar to less)",
|
183
|
+
action="store_true",
|
184
|
+
default=False,
|
172
185
|
)
|
173
186
|
|
174
187
|
try:
|
175
188
|
argcomplete.autocomplete(parser)
|
176
189
|
args = parser.parse_args()
|
190
|
+
|
177
191
|
notebook = read_notebook(args.file, debug=args.debug)
|
178
|
-
|
192
|
+
objects = render_notebook(notebook)
|
193
|
+
|
194
|
+
if not objects:
|
195
|
+
console.print("[bold red]Notebook contains no cells.")
|
196
|
+
return
|
197
|
+
|
198
|
+
if args.page:
|
199
|
+
Pager(objects).run()
|
200
|
+
else:
|
201
|
+
console.print(*objects)
|
179
202
|
except Exception as e:
|
180
203
|
sys.exit(f"nbcat: {e}")
|
181
204
|
|
nbcat/pager.py
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
from rich.console import RenderableType
|
2
|
+
from textual.app import App, ComposeResult
|
3
|
+
from textual.binding import Binding
|
4
|
+
from textual.containers import VerticalScroll
|
5
|
+
from textual.widgets import Static
|
6
|
+
|
7
|
+
|
8
|
+
class Pager(App):
|
9
|
+
BINDINGS = [
|
10
|
+
# Exit
|
11
|
+
Binding("q", "quit", "Quit"),
|
12
|
+
Binding(":q", "quit", "Quit"),
|
13
|
+
Binding("Q", "quit", "Quit"),
|
14
|
+
Binding(":Q", "quit", "Quit"),
|
15
|
+
Binding("ZZ", "quit", "Quit"),
|
16
|
+
# One line
|
17
|
+
Binding("j", "scroll_down", "Down"),
|
18
|
+
Binding("e", "scroll_down", "Down"),
|
19
|
+
Binding("^e", "scroll_down", "Down"),
|
20
|
+
Binding("^n", "scroll_down", "Down"),
|
21
|
+
Binding("cr", "scroll_down", "Down"), # carriage return = Enter
|
22
|
+
Binding("k", "scroll_up", "Up"),
|
23
|
+
Binding("y", "scroll_up", "Up"),
|
24
|
+
Binding("ctrl+y", "scroll_up", "Up"),
|
25
|
+
Binding("ctrl+k", "scroll_up", "Up"),
|
26
|
+
Binding("ctrl+p", "scroll_up", "Up"),
|
27
|
+
# One window
|
28
|
+
Binding("f", "page_down", "Page Down"),
|
29
|
+
Binding("ctrl+f", "page_down", "Page Down"),
|
30
|
+
Binding("ctrl+v", "page_down", "Page Down"),
|
31
|
+
Binding("space", "page_down", "Page Down"),
|
32
|
+
Binding("b", "page_up", "Page Up"),
|
33
|
+
Binding("ctrl+b", "page_up", "Page Up"),
|
34
|
+
Binding("escape+v", "page_up", "Page Up"),
|
35
|
+
# Extended window control
|
36
|
+
Binding("z", "page_down", "Window Down (set N)"),
|
37
|
+
Binding("w", "page_up", "Window Up (set N)"),
|
38
|
+
Binding("escape+space", "page_down", "Forward one window, no EOF stop"),
|
39
|
+
Binding("d", "half_page_down", "Half Page Down"),
|
40
|
+
Binding("ctrl+d", "half_page_down", "Half Page Down"),
|
41
|
+
# Jumping
|
42
|
+
Binding("g", "go_to_top", "Top of File"),
|
43
|
+
Binding("<", "go_to_top", "Top of File"),
|
44
|
+
Binding("escape+<", "go_to_top", "Top of File"),
|
45
|
+
Binding("G", "go_to_bottom", "Bottom of File"),
|
46
|
+
Binding(">", "go_to_bottom", "Bottom of File"),
|
47
|
+
Binding("escape+>", "go_to_bottom", "Bottom of File"),
|
48
|
+
]
|
49
|
+
|
50
|
+
def __init__(self, objects: list[RenderableType]):
|
51
|
+
super().__init__()
|
52
|
+
self._objects = objects
|
53
|
+
|
54
|
+
def compose(self) -> ComposeResult:
|
55
|
+
with VerticalScroll():
|
56
|
+
for obj in self._objects:
|
57
|
+
yield Static(obj)
|
58
|
+
|
59
|
+
def on_mount(self) -> None:
|
60
|
+
self.theme = "textual-ansi"
|
61
|
+
self.viewer = self.query_one(VerticalScroll)
|
62
|
+
|
63
|
+
def action_scroll_down(self) -> None:
|
64
|
+
self.viewer.scroll_to(y=self.viewer.scroll_y + 1)
|
65
|
+
|
66
|
+
def action_scroll_up(self) -> None:
|
67
|
+
self.viewer.scroll_to(y=self.viewer.scroll_y - 1)
|
68
|
+
|
69
|
+
def action_page_down(self) -> None:
|
70
|
+
self.viewer.scroll_to(y=self.viewer.scroll_y + self.viewer.virtual_size.height)
|
71
|
+
|
72
|
+
def action_page_up(self) -> None:
|
73
|
+
self.viewer.scroll_to(y=max(self.viewer.scroll_y - self.viewer.virtual_size.height, 0))
|
74
|
+
|
75
|
+
def action_half_page_down(self) -> None:
|
76
|
+
self.viewer.scroll_to(y=self.viewer.scroll_y + (self.viewer.virtual_size.height / 2))
|
77
|
+
|
78
|
+
def action_go_to_top(self) -> None:
|
79
|
+
self.viewer.scroll_to(y=0)
|
80
|
+
|
81
|
+
def action_go_to_bottom(self) -> None:
|
82
|
+
self.viewer.scroll_to(y=self.viewer.virtual_size.height)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: nbcat
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.13.0
|
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
|
@@ -33,6 +33,7 @@ Requires-Dist: argcomplete
|
|
33
33
|
Requires-Dist: pydantic
|
34
34
|
Requires-Dist: requests
|
35
35
|
Requires-Dist: rich
|
36
|
+
Requires-Dist: textual
|
36
37
|
Requires-Dist: timg
|
37
38
|
Provides-Extra: dev
|
38
39
|
Requires-Dist: pytest; extra == 'dev'
|
@@ -52,9 +53,10 @@ Description-Content-Type: text/markdown
|
|
52
53
|
|
53
54
|
## Features
|
54
55
|
|
55
|
-
- Very fast and lightweight with minimal dependencies
|
56
|
-
- Preview remote notebooks without downloading them
|
57
|
-
-
|
56
|
+
- Very fast and lightweight with minimal dependencies.
|
57
|
+
- Preview remote notebooks without downloading them.
|
58
|
+
- Enable paginated view mode with keyboard navigation (similar to `less`).
|
59
|
+
- Supports for all Jupyter notebook versions, including old legacy formats.
|
58
60
|
|
59
61
|
## Motivation
|
60
62
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
nbcat/__init__.py,sha256=DgpLNbv0e1LIEOOe54Db8_390i9pelMEFEnsBsNmyhA,23
|
2
|
+
nbcat/enums.py,sha256=Fn8PIcLl_uY4nQIs1EUvmKTwfhNUIZgmhRFiCSJk9wk,411
|
3
|
+
nbcat/exceptions.py,sha256=Ho7LQz9K70VtIMDNtAwuAtGmb-lFKxGxSj7MN3-EpDA,321
|
4
|
+
nbcat/image.py,sha256=hGZZK5mtij7ckWAvQwzim3thqGC92pLW5ZU5CYbRv_Q,995
|
5
|
+
nbcat/main.py,sha256=FSXD7SobpjhwDvOKAS1C-Lntmv9mPJhDUugjq8dBzAA,6678
|
6
|
+
nbcat/markdown.py,sha256=GkPNfzBhNLMl89pVcj14U6ytYUVk3yNB0G1zGYRRJ04,2962
|
7
|
+
nbcat/pager.py,sha256=Yu0XIh5MvhvT-cmULYVxo6s2Ufjf2CQ-NDS5fcMQ-IM,3184
|
8
|
+
nbcat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
+
nbcat/schemas.py,sha256=a7GoKgPTHgun199-J-sZq-ahkdQwvyRaCdQVg1gC798,3135
|
10
|
+
nbcat-0.13.0.dist-info/METADATA,sha256=rspE5gM8qznr1ZNL9REUdPBJho1QRlDRcXSTl2ngSH8,4265
|
11
|
+
nbcat-0.13.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
12
|
+
nbcat-0.13.0.dist-info/entry_points.txt,sha256=io_GRDsecAkYuCZALsjyea3VBq91VCoSznqlZEAJshY,42
|
13
|
+
nbcat-0.13.0.dist-info/licenses/LICENSE,sha256=7GjUnahXdd5opdvlpJdb1BisLbiXt2iOFhzIUduhdkE,1072
|
14
|
+
nbcat-0.13.0.dist-info/RECORD,,
|
nbcat-0.12.0.dist-info/RECORD
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
nbcat/__init__.py,sha256=eHjt9DPsMbptabS2yGx9Yhbyzq5hFSUHXb7zc8Q_8-o,23
|
2
|
-
nbcat/enums.py,sha256=Fn8PIcLl_uY4nQIs1EUvmKTwfhNUIZgmhRFiCSJk9wk,411
|
3
|
-
nbcat/exceptions.py,sha256=Ho7LQz9K70VtIMDNtAwuAtGmb-lFKxGxSj7MN3-EpDA,321
|
4
|
-
nbcat/image.py,sha256=hGZZK5mtij7ckWAvQwzim3thqGC92pLW5ZU5CYbRv_Q,995
|
5
|
-
nbcat/main.py,sha256=vchFzwcvPGmv4pg5AHRaOOsZh83ynmxkTqIKl0z_Sys,5749
|
6
|
-
nbcat/markdown.py,sha256=GkPNfzBhNLMl89pVcj14U6ytYUVk3yNB0G1zGYRRJ04,2962
|
7
|
-
nbcat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
-
nbcat/schemas.py,sha256=a7GoKgPTHgun199-J-sZq-ahkdQwvyRaCdQVg1gC798,3135
|
9
|
-
nbcat-0.12.0.dist-info/METADATA,sha256=QOcOghWxUMkPA986f-xscLwz-YQrbZQPUJQF8P3r4cQ,4164
|
10
|
-
nbcat-0.12.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
11
|
-
nbcat-0.12.0.dist-info/entry_points.txt,sha256=io_GRDsecAkYuCZALsjyea3VBq91VCoSznqlZEAJshY,42
|
12
|
-
nbcat-0.12.0.dist-info/licenses/LICENSE,sha256=7GjUnahXdd5opdvlpJdb1BisLbiXt2iOFhzIUduhdkE,1072
|
13
|
-
nbcat-0.12.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|