Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions docs/usage/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,38 @@ If you pass `--json` then the content will not be represented as a rich output b
```bash
jpterm show path/to/notebook --json | jq -r '.cells[0].source'
```

Use `--cell CELL_ID` to show a single notebook cell, including its saved outputs:

```bash
jpterm show notebook.ipynb --cell CELL_ID
jpterm show notebook.ipynb --cell CELL_ID --json
```

To display IDs use `--show-cell-ids`:

```bash
jpterm show notebook.ipynb --show-cell-ids
```

This also works with `--cell CELL_ID`. JSON output already includes cell IDs.

Use `write` to create or replace a text file:

```bash
jpterm write script.py --source 'print("hello")'
cat script.py | jpterm write another.py --stdin
```

For notebooks, specify `--cell CELL_ID` to replace a cell's source:

```bash
jpterm write notebook.ipynb --cell CELL_ID --source 'print("hello")'
cat script.py | jpterm write notebook.ipynb --cell CELL_ID --stdin
jpterm --server http://127.0.0.1:8000 write notebook.ipynb --cell CELL_ID --source 'print(42)'
```

Provide exactly one of `--source` or `--stdin`; `--source ''` clears the content.
When a code cell's source changes, its saved outputs and execution count are cleared.
Other cells are preserved, and no cells are executed. Writing a whole notebook without
`--cell` is not supported.
149 changes: 70 additions & 79 deletions examples/plotext.ipynb
Original file line number Diff line number Diff line change
@@ -1,82 +1,73 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from ypywidgets_textual.plotext import Plotext\n",
"plt = Plotext()"
]
"cells": [
{
"source": "from ypywidgets_textual.plotext import Plotext\nplt = Plotext()",
"cell_type": "code",
"metadata": {},
"execution_count": null,
"id": "eff14ab7-9e42-40a3-847f-8fab1c8320da",
"outputs": []
},
{
"cell_type": "code",
"source": "plt",
"metadata": {},
"outputs": [],
"id": "fff14ab7-9e42-40a3-847f-8fab1c8320db",
"execution_count": null
},
{
"source": "plt.title('Scatter Plot')",
"metadata": {},
"cell_type": "code",
"execution_count": null,
"id": "fff14ab7-9e42-40a3-847f-8fab1c8321da",
"outputs": []
},
{
"id": "fff14ab7-9e42-40a3-847f-8fab1c8420da",
"source": "x = [0, 1, 2, 3, 4, 5]\ny = [5, 6, 7, 1, 2, 3]\nplt.scatter(x, y)",
"execution_count": null,
"metadata": {},
"outputs": [],
"cell_type": "code"
},
{
"execution_count": null,
"id": "fff14ab7-9e42-40a3-847f-8fab1c9320da",
"metadata": {},
"outputs": [],
"source": "plt.title('Hello')",
"cell_type": "code"
},
{
"id": "fff14ab7-9e42-40a3-847f-8fab1d8320da",
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"source": "plt.clear_data()"
}
],
"metadata": {
"language_info": {
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"file_extension": ".py",
"name": "python",
"version": "3.11.5",
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"mimetype": "text/x-python"
},
"kernelspec": {
"language": "python",
"display_name": "Python 3 (ipykernel)",
"name": "python3"
}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plt"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plt.title('Scatter Plot')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"x = [0, 1, 2, 3, 4, 5]\n",
"y = [5, 6, 7, 1, 2, 3]\n",
"plt.scatter(x, y)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plt.title('Hello')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plt.clear_data()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.5"
}
},
"nbformat": 4,
"nbformat_minor": 4
"nbformat": 4,
"nbformat_minor": 5
}
8 changes: 7 additions & 1 deletion plugins/cell/txl_cell/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from anyio import create_memory_object_stream, create_task_group, sleep
from fps import Module
from pycrdt import Doc, Map, MapEvent, Text
from rich.markup import escape
from rich.text import Text as RichText
from textual.app import ComposeResult
from textual.containers import Container
Expand Down Expand Up @@ -63,6 +64,7 @@ def __init__(
task_group,
show_execution_count: bool = True,
show_border: bool = True,
show_cell_id: bool = False,
) -> None:
super().__init__()
self.ycell = ycell
Expand All @@ -73,6 +75,7 @@ def __init__(
self.task_group = task_group
self.show_execution_count = show_execution_count
self.show_border = show_border
self.show_cell_id = show_cell_id
self.outputs = []
self.update(mount=False)
self.ycell.observe_deep(self.on_change)
Expand Down Expand Up @@ -180,7 +183,10 @@ async def observe_cell_changes(self):

def get_execution_count(self, value):
execution_count = " " if value is None else str(value).removesuffix(".0")
return r"[green]In \[[#66ff00]" + execution_count + r"[/#66ff00]]:[/green]"
prompt = r"[green]In \[[#66ff00]" + execution_count + r"[/#66ff00]]:[/green]"
if self.show_cell_id:
prompt += " " + escape(self.ycell.get("id") or "(missing)")
return prompt

def compose(self) -> ComposeResult:
if self.show_execution_count:
Expand Down
8 changes: 4 additions & 4 deletions plugins/local_contents/txl_local_contents/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ async def get(
if await p.is_file():
jupyter_ydoc = ydocs[type]()
if type == "file":
jupyter_ydoc.source = await p.read_text()
jupyter_ydoc.source = await p.read_text(encoding="utf-8")
elif type == "blob":
jupyter_ydoc.source = await p.read_bytes()
elif type == "notebook":
jupyter_ydoc.source = json.loads(await p.read_text())
jupyter_ydoc.source = json.loads(await p.read_text(encoding="utf-8"))
return jupyter_ydoc

async def save(
Expand All @@ -42,11 +42,11 @@ async def save(
p = Path(path)
source = jupyter_ydoc.source
if isinstance(source, dict):
await p.write_text(json.dumps(source, indent=2))
await p.write_text(json.dumps(source, indent=2), encoding="utf-8")
elif isinstance(source, bytes):
await p.write_bytes(source)
else:
await p.write_text(source)
await p.write_text(source, encoding="utf-8")


class LocalContentsModule(Module):
Expand Down
19 changes: 16 additions & 3 deletions plugins/notebook_editor/txl_notebook_editor/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,24 @@ async def create_empty_notebook(self, kernel_name: str) -> str:
await self.contents.save(path, ynb)
return path

async def open(self, path: str | None = None) -> None:
async def open(
self, path: str | None = None, *, cell_id: str | None = None, show_cell_ids: bool = False
) -> None:
if path is None:
kernel_name = await self.select_kernel()
path = await self.create_empty_notebook(kernel_name)
self.main_area.set_label(path)
self.path = path
self.ynb = await self.contents.get(self.path, type="notebook", format="json")
self.cell_index = None
self.show_cell_ids = show_cell_ids
if cell_id is not None:
self.cell_index = next(
(i for i, cell in enumerate(self.ynb.source["cells"])
if cell.get("id") == cell_id), None
)
if self.cell_index is None:
raise ValueError(f"Cell ID not found: {cell_id}")
self.update()
if not self.app.cli:
self.nb_change_target = StapledObjectStream(
Expand All @@ -166,9 +177,11 @@ def update(self):
if kernel_name:
self.kernel = self.kernels(kernel_name)
self.kernel.kernel.busy.connect(self.watch_busy)
for i_cell in range(self.ynb.cell_number):
indices = range(self.ynb.cell_number) if self.cell_index is None else [self.cell_index]
for i_cell in indices:
cell = self.cell_factory(
self.ynb.ycells[i_cell], self.language, self.kernel
self.ynb.ycells[i_cell], self.language, self.kernel,
**({"show_cell_id": True} if self.show_cell_ids else {}),
)
if self.app.cli:
cell.source.read_only = True
Expand Down
1 change: 1 addition & 0 deletions plugins/remote_contents/txl_remote_contents/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ async def save(
cookies=self.cookies,
)
self.cookies.update(r.cookies)
r.raise_for_status()

async def websocket_provider(self, room_id, ydoc, session_id=None, synced=None):
ws_url = f"{self.ws_url}/api/collaboration/room/{room_id}"
Expand Down
21 changes: 21 additions & 0 deletions src/jpterm/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""Shared application setup for CLI commands."""

from fps import get_root_module

from txl.app import AppModule


def create_app(settings, timeout, inline=False):
config = {"type": AppModule, "config": {"cli": True, "inline": inline}}
for setting in settings:
if "=" not in setting:
raise ValueError(f"No '=' while setting a module parameter: {setting}")
key, value = setting.split("=", 1)
node = config
parts = key.split(".")
for name in parts[:-1]:
node = node.setdefault("modules", {}).setdefault(name, {})
node.setdefault("config", {})[parts[-1]] = value
module = get_root_module({"app": config})
module._global_start_timeout = timeout
return module
4 changes: 4 additions & 0 deletions src/jpterm/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from txl.cli import set_main, txl_main

from .show import show
from .write import write


def jpterm_main(kwargs):
Expand Down Expand Up @@ -78,11 +79,14 @@ def command(ctx, **kwargs):
else:
if kwargs["collaborative"] and not kwargs["server"]:
raise click.UsageError("--collaborative requires --server for CLI commands.")
if kwargs["collaborative"] and ctx.invoked_subcommand == "write":
raise click.UsageError("write does not support --collaborative yet.")
ctx.obj = jpterm_main(kwargs)

# Keep the existing TUI options on the root command.
command.params.extend(click.command()(_main).params)
command.add_command(show)
command.add_command(write)
command.add_command(logo)
command()

Expand Down
Loading
Loading