Skip to content
6 changes: 4 additions & 2 deletions fastcore/_modidx.py
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@
'fastcore.nbio._split_cell': ('nbio.html#_split_cell', 'fastcore/nbio.py'),
'fastcore.nbio._split_mime': ('nbio.html#_split_mime', 'fastcore/nbio.py'),
'fastcore.nbio._unparse_dir': ('nbio.html#_unparse_dir', 'fastcore/nbio.py'),
'fastcore.nbio._view_cell': ('nbio.html#_view_cell', 'fastcore/nbio.py'),
'fastcore.nbio.cell2xml': ('nbio.html#cell2xml', 'fastcore/nbio.py'),
'fastcore.nbio.cell_edit': ('nbio.html#cell_edit', 'fastcore/nbio.py'),
'fastcore.nbio.cells2xml': ('nbio.html#cells2xml', 'fastcore/nbio.py'),
Expand Down Expand Up @@ -609,7 +610,7 @@
'fastcore.nbio.unpack_frames': ('nbio.html#unpack_frames', 'fastcore/nbio.py'),
'fastcore.nbio.validate_cell': ('nbio.html#validate_cell', 'fastcore/nbio.py'),
'fastcore.nbio.validate_nb': ('nbio.html#validate_nb', 'fastcore/nbio.py'),
'fastcore.nbio.view_cell': ('nbio.html#view_cell', 'fastcore/nbio.py'),
'fastcore.nbio.view_cells': ('nbio.html#view_cells', 'fastcore/nbio.py'),
'fastcore.nbio.write_nb': ('nbio.html#write_nb', 'fastcore/nbio.py')},
'fastcore.net': { 'fastcore.net.HTTP4xxClientError': ('net.html#http4xxclienterror', 'fastcore/net.py'),
'fastcore.net.HTTP5xxServerError': ('net.html#http5xxservererror', 'fastcore/net.py'),
Expand Down Expand Up @@ -745,7 +746,8 @@
'fastcore.tools.replace_lines': ('tools.html#replace_lines', 'fastcore/tools.py'),
'fastcore.tools.str_replace': ('tools.html#str_replace', 'fastcore/tools.py'),
'fastcore.tools.strs_replace': ('tools.html#strs_replace', 'fastcore/tools.py'),
'fastcore.tools.view_file': ('tools.html#view_file', 'fastcore/tools.py')},
'fastcore.tools.view_file': ('tools.html#view_file', 'fastcore/tools.py'),
'fastcore.tools.view_files': ('tools.html#view_files', 'fastcore/tools.py')},
'fastcore.transform': {},
'fastcore.utils': {},
'fastcore.xdg': { 'fastcore.xdg._path_from_env': ('xdg.html#_path_from_env', 'fastcore/xdg.py'),
Expand Down
12 changes: 6 additions & 6 deletions fastcore/editskill.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@

Two name shapes cover the toolkit, and the pivot is the verb's direct object:

- An operation on a whole carrier takes the carrier as its noun: verb_carrier. `view_file`, `create_file`, `read_nb`, `write_nb`, `view_cell`, `validate_nb`; in the dialog layer `view_msg` and `view_dlg`. Coined verbs follow the same shape: `lnhashview_cell` is "lnhashview this cell". When the verb's object is instead the medium's unit, and that unit names its carrier uniquely, no prefix is needed - the unit noun is the carrier signal: `find_msgs`, `add_msg`, `del_msgs` (msgs live only in dialogs), `find_cells`, `summary_nb`'s rows (cells live only in notebooks).
- An operation on a whole carrier takes the carrier as its noun: verb_carrier. `view_files`, `create_file`, `read_nb`, `write_nb`, `view_cells`, `validate_nb`; in the dialog layer `view_msgs` and `view_dlg`. Coined verbs follow the same shape: `lnhashview_cells` is "lnhashview these cells". When the verb's object is instead the medium's unit, and that unit names its carrier uniquely, no prefix is needed - the unit noun is the carrier signal: `find_msgs`, `add_msg`, `del_msgs` (msgs live only in dialogs), `find_cells`, `summary_nb`'s rows (cells live only in notebooks).
- An operation within a carrier already owns its noun (`insert_line`, `del_lines`, `replace_lines`, `str_replace`), so the carrier prefixes as a namespace and the op name survives intact: carrier_op, as in `file_del_lines`, `cell_del_lines`, `msg_del_lines`. The bare op names are the text-level primitives, and every carrier version keeps the identical signature after its address arguments, so each family is learned once and recognized everywhere.

The exceptions are deliberate and closed. `str_replace` keeps the name and argument order established by Anthropic's text editor tool. Instrument-named ops put the instrument first and elide their unit: `ast_replace` (the AST pattern is how the edit finds its target) and `exhash` (hash-verified line addresses travel inside its commands), carrier-prefixed like any other line-level op: `file_ast_replace`, `msg_ast_replace`, `file_exhash`, `cell_exhash`. Converters are named x2y (`nb2dict`, `cell2xml`; in aidialog, `dlg` on exactly one side of every converter), and on a held object the converter is a `to_y` method (`nb.to_dict()`). Plural marks arity: `view_cell` takes one cell, `lnhashview_cells` several, `del_msgs` many.
The exceptions are deliberate and closed. `str_replace` keeps the name and argument order established by Anthropic's text editor tool. Instrument-named ops put the instrument first and elide their unit: `ast_replace` (the AST pattern is how the edit finds its target) and `exhash` (hash-verified line addresses travel inside its commands), carrier-prefixed like any other line-level op: `file_ast_replace`, `msg_ast_replace`, `file_exhash`, `cell_exhash`. Converters are named x2y (`nb2dict`, `cell2xml`; in aidialog, `dlg` on exactly one side of every converter), and on a held object the converter is a `to_y` method (`nb.to_dict()`). Views take one or more targets, so they use the plural name whatever the count: `view_files`, `view_cells`, and the dialog layer's `view_msgs`; `view_file` stays alongside `view_files` as the single-file form, keeping the name and positional line range established by Anthropic's text editor tool.

## Parameters

Expand Down Expand Up @@ -52,14 +52,14 @@

from fastcore.tools import (insert_line, str_replace, strs_replace, replace_lines, del_lines, ast_replace,
file_insert_line, file_str_replace, file_strs_replace, file_replace_lines, file_del_lines, file_ast_replace,
view_file, create_file, line_hash, lnhash, lnhash_at)
view_file, view_files, create_file, line_hash, lnhash, lnhash_at)
from fastcore.nbio import (read_nb, write_nb, new_nb, mk_cell, validate_nb, validate_cell, repair_nb, repair_cell,
view_cell, cell_insert_line, cell_str_replace, cell_strs_replace, cell_replace_lines, cell_del_lines, cell_ast_replace, Notebook, NbCell, find_cells, summary_nb)
view_cells, cell_insert_line, cell_str_replace, cell_strs_replace, cell_replace_lines, cell_del_lines, cell_ast_replace, Notebook, NbCell, find_cells, summary_nb)

__all__ = ['insert_line', 'str_replace', 'strs_replace', 'replace_lines', 'del_lines', 'ast_replace',
'file_insert_line', 'file_str_replace', 'file_strs_replace', 'file_replace_lines', 'file_del_lines', 'file_ast_replace',
'view_file', 'create_file', 'line_hash', 'lnhash', 'lnhash_at',
'view_file', 'view_files', 'create_file', 'line_hash', 'lnhash', 'lnhash_at',
'read_nb', 'write_nb', 'new_nb', 'mk_cell', 'validate_nb', 'validate_cell', 'repair_nb', 'repair_cell',
'view_cell', 'cell_insert_line', 'cell_str_replace', 'cell_strs_replace', 'cell_replace_lines', 'cell_del_lines', 'cell_ast_replace', 'Notebook', 'NbCell', 'find_cells', 'summary_nb']
'view_cells', 'cell_insert_line', 'cell_str_replace', 'cell_strs_replace', 'cell_replace_lines', 'cell_del_lines', 'cell_ast_replace', 'Notebook', 'NbCell', 'find_cells', 'summary_nb']

__pyskill_params__ = {'replace_params': ('start_line', 'end_line', 'n_matches', 're_filter', 'invert_filter', 'use_regex')}
37 changes: 22 additions & 15 deletions fastcore/nbio.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Reading, writing, and running Jupyter notebooks

Cell tools apply `fastcore.tools`' string editing primitives to one notebook cell's source, addressed by path and cell id, mirroring that module's file tools: the same operations and parameters, with `path, cell_id` in place of `path`. Each editor (including the structural `cell_ast_replace`) returns a diff of the change, and `view_cell` shows a cell's source with optional line numbers or exhash addresses.
Cell tools apply `fastcore.tools`' string editing primitives to one notebook cell's source, addressed by path and cell id, mirroring that module's file tools: the same operations and parameters, with `path, cell_id` in place of `path`. Each editor (including the structural `cell_ast_replace`) returns a diff of the change, and `view_cells` shows one or more cells' sources with optional line numbers or exhash addresses.

Naming and parameter conventions shared across the editing toolkit are documented in `fastcore.editskill`, which also re-exports this module's editing tools.

Expand All @@ -11,7 +11,7 @@
# %% auto #0
__all__ = ['langs', 'cell_insert_line', 'cell_str_replace', 'cell_strs_replace', 'cell_replace_lines', 'cell_del_lines',
'cell_ast_replace', 'IMG_MIMES', 'nb_lang', 'NbCell', 'dict2nb', 'read_nb', 'mk_cell', 'new_nb',
'first_code_ln', 'dir_tag', 'nb2dict', 'nb2str', 'write_nb', 'cell_edit', 'view_cell', 'validate_cell',
'first_code_ln', 'dir_tag', 'nb2dict', 'nb2str', 'write_nb', 'cell_edit', 'view_cells', 'validate_cell',
'validate_nb', 'repair_cell', 'repair_nb', 'preferred_out', 'join_out', 'mk_stream', 'mk_result',
'mk_display', 'mk_error', 'concat_streams', 'preferred_msg_out', 'render_output', 'render_outputs',
'render_text', 'item2xml', 'cell2xml', 'cells2xml', 'Notebook', 'CellRow', 'CellRows', 'summary_nb',
Expand Down Expand Up @@ -316,18 +316,7 @@ def wrapper(path:str, cell_id:str, *args, **kw):
__pyskill_params__ = {'replace_params': ('start_line', 'end_line', 'n_matches', 're_filter', 'invert_filter', 'use_regex')}

# %% ../nbs/13_nbio.ipynb #421b2b9c
def view_cell(
path:str, # Notebook file to read
cell_id:str, # Id of the cell to view (exact, or unique prefix)
start_line:int=1, # Starting line to view
end_line:int=None, # End line (defaults to last line if None; may be past EOF, which clamps to the last line)
nums:bool=True, # Show line numbers?
lnhashs:bool=False, # Show exhash `lineno|hash|` addresses instead of line numbers?
incl_out:bool=False, # Append the cell's outputs in an `<out>` block?
trunc_out:bool=True # Truncate included outputs to ~512 chars?
):
"View a cell's source, optionally limited to 1-based line range"
cell = _nb_cell(read_nb(path), cell_id)
def _view_cell(cell, start_line, end_line, nums, lnhashs, incl_out, trunc_out):
lines = cell.source.splitlines()
if not lines: return ''
if end_line is None or end_line > len(lines): end_line = len(lines)
Expand All @@ -337,7 +326,25 @@ def view_cell(
res = '\n'.join(fmt(i,l) for i,l in enumerate(lines[start_line-1:end_line], start_line))
if incl_out and (o := render_text(cell.get('outputs') or [])):
res += f"\n<out>\n{truncstr(o, 512) if trunc_out else o}\n</out>"
return PrettyString(res)
return res

def view_cells(
path:str, # Notebook file to read
*cell_ids:str, # Ids of the cells to view (each exact, or a unique prefix)
start_line:int=1, # Starting line to view
end_line:int=None, # End line (defaults to last line if None; may be past EOF, which clamps to the last line)
nums:bool=True, # Show line numbers?
lnhashs:bool=False, # Show exhash `lineno|hash|` addresses instead of line numbers?
incl_out:bool=False, # Append each cell's outputs in an `<out>` block?
trunc_out:bool=True # Truncate included outputs to ~512 chars?
):
"View one or more cells' sources, optionally limited to 1-based line range; each cell follows a `# cell <id>` header when several"
if not cell_ids: raise TypeError("view_cells() requires at least one cell id")
nb = read_nb(path)
cells = [_nb_cell(nb, c) for c in cell_ids]
res = [_view_cell(c, start_line, end_line, nums, lnhashs, incl_out, trunc_out) for c in cells]
if len(res)==1: return PrettyString(res[0])
return PrettyString('\n'.join(f'# cell {c.id}\n{r}' for c,r in zip(cells,res)))

# %% ../nbs/13_nbio.ipynb #86453c0f
def _is_text(x):
Expand Down
16 changes: 15 additions & 1 deletion fastcore/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# %% auto #0
__all__ = ['file_insert_line', 'file_str_replace', 'file_strs_replace', 'file_replace_lines', 'file_del_lines',
'file_ast_replace', 'insert_line', 'str_replace', 'strs_replace', 'replace_lines', 'del_lines', 'line_hash',
'lnhash', 'lnhash_at', 'view_file', 'create_file', 'file_edit', 'ast_replace']
'lnhash', 'lnhash_at', 'view_file', 'view_files', 'create_file', 'file_edit', 'ast_replace']

# %% ../nbs/12_tools.ipynb #578246d2
import zlib
Expand Down Expand Up @@ -185,6 +185,20 @@ def view_file(
fmt = (lambda i,l: lnhash(i,l)+l) if lnhashs else (lambda i,l: f'{i}: {l}') if nums else (lambda i,l: l)
return PrettyString('\n'.join(fmt(i,l) for i,l in enumerate(lines[start_line-1:end_line], start_line)))

# %% ../nbs/12_tools.ipynb #3ffec660
def view_files(
*paths:str, # Paths to view (each expands `~` if needed)
start_line:int=1, # Starting line to view (applied to each file)
end_line:int=None, # End line (defaults to last line if None; may be past EOF, which clamps to the last line)
nums:bool=True, # Show line numbers?
lnhashs:bool=False # Show exhash `lineno|hash|` addresses instead of line numbers?
):
"View one or more files, each after a `# file <path>` header when several; any line range applies to each file separately"
if not paths: raise TypeError("view_files() requires at least one path")
res = [view_file(p, start_line, end_line, nums=nums, lnhashs=lnhashs) for p in paths]
if len(res)==1: return res[0]
return PrettyString('\n'.join(f'# file {p}\n{r}' for p,r in zip(paths,res)))

# %% ../nbs/12_tools.ipynb #424d09e1
def create_file(
path:str, # Path to create (expands `~` if needed)
Expand Down
83 changes: 80 additions & 3 deletions nbs/12_tools.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,18 @@
"execution_count": null,
"id": "8079ef9d",
"metadata": {},
"outputs": [],
"outputs": [
{
"data": {
"text/plain": [
"'a-b\\nb a'"
]
},
"execution_count": null,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"res = str_replace('keep q\\nfix q\\nkeep q', 'q', 'y', re_filter='fix')\n",
"test_eq(res, 'keep q\\nfix y\\nkeep q')\n",
Expand Down Expand Up @@ -487,7 +498,7 @@
{
"data": {
"text/plain": [
"'/var/folders/51/b2_szf2945n072c0vj2cyty40000gn/T/tmpaax7zfjh/test.txt'"
"'/tmp/tmpsjvautdg/test.txt'"
]
},
"execution_count": null,
Expand Down Expand Up @@ -552,6 +563,64 @@
"res"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3ffec660",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def view_files(\n",
" *paths:str, # Paths to view (each expands `~` if needed)\n",
" start_line:int=1, # Starting line to view (applied to each file)\n",
" end_line:int=None, # End line (defaults to last line if None; may be past EOF, which clamps to the last line)\n",
" nums:bool=True, # Show line numbers?\n",
" lnhashs:bool=False # Show exhash `lineno|hash|` addresses instead of line numbers?\n",
"):\n",
" \"View one or more files, each after a `# file <path>` header when several; any line range applies to each file separately\"\n",
" if not paths: raise TypeError(\"view_files() requires at least one path\")\n",
" res = [view_file(p, start_line, end_line, nums=nums, lnhashs=lnhashs) for p in paths]\n",
" if len(res)==1: return res[0]\n",
" return PrettyString('\\n'.join(f'# file {p}\\n{r}' for p,r in zip(paths,res)))"
]
},
{
"cell_type": "markdown",
"id": "a6223841",
"metadata": {},
"source": [
"`view_files` is the plural companion (`view_file` keeps the single-file name and positional line range models know from Anthropic's text editor tool): one or more paths, several files shown each under a `# file <path>` header, with any line range applied to each file separately, and a single path rendered bare.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "325d8a6a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"# file /tmp/tmpsjvautdg/test.txt\n",
"2: beta\n",
"# file /tmp/tmpsjvautdg/other.txt\n",
"2: two"
]
},
"execution_count": null,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"p2 = Path(tmp.name)/'other.txt'\n",
"p2.write_text('one\\ntwo\\n')\n",
"res = view_files(test_path, p2, start_line=2, end_line=2)\n",
"test_eq(res, f'# file {test_path}\\n2: beta\\n# file {p2}\\n2: two')\n",
"res"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand Down Expand Up @@ -902,7 +971,15 @@
]
}
],
"metadata": {},
"metadata": {
"solveit": {
"default_code": true,
"mode": "learning",
"use_thinking": true,
"use_tools": false,
"ver": 2
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading
Loading