diff --git a/fastcore/_modidx.py b/fastcore/_modidx.py index 6208571c..7ab0ccb2 100644 --- a/fastcore/_modidx.py +++ b/fastcore/_modidx.py @@ -587,6 +587,7 @@ 'fastcore.nbio.Notebook.meta': ('nbio.html#notebook.meta', 'fastcore/nbio.py'), 'fastcore.nbio.Notebook.move': ('nbio.html#notebook.move', 'fastcore/nbio.py'), 'fastcore.nbio.Notebook.open': ('nbio.html#notebook.open', 'fastcore/nbio.py'), + 'fastcore.nbio.Notebook.remove': ('nbio.html#notebook.remove', 'fastcore/nbio.py'), 'fastcore.nbio.Notebook.save': ('nbio.html#notebook.save', 'fastcore/nbio.py'), 'fastcore.nbio.Notebook.summary': ('nbio.html#notebook.summary', 'fastcore/nbio.py'), 'fastcore.nbio.Notebook.to_dict': ('nbio.html#notebook.to_dict', 'fastcore/nbio.py'), @@ -622,6 +623,7 @@ 'fastcore.nbio.cells2xml': ('nbio.html#cells2xml', 'fastcore/nbio.py'), 'fastcore.nbio.concat_streams': ('nbio.html#concat_streams', 'fastcore/nbio.py'), 'fastcore.nbio.deep_merge': ('nbio.html#deep_merge', 'fastcore/nbio.py'), + 'fastcore.nbio.del_cells': ('nbio.html#del_cells', 'fastcore/nbio.py'), 'fastcore.nbio.dict2nb': ('nbio.html#dict2nb', 'fastcore/nbio.py'), 'fastcore.nbio.diff_cells': ('nbio.html#diff_cells', 'fastcore/nbio.py'), 'fastcore.nbio.dir_tag': ('nbio.html#dir_tag', 'fastcore/nbio.py'), diff --git a/fastcore/editskill.py b/fastcore/editskill.py index 7023c016..89984234 100644 --- a/fastcore/editskill.py +++ b/fastcore/editskill.py @@ -43,7 +43,7 @@ ## What's where - `fastcore.tools`: text primitives, file tools, and `line_hash`/`lnhash`/`lnhash_at` for creating addresses without exhash installed. -- `fastcore.nbio`: notebook read/write/validate/repair, cell construction, cell editors, and the `Notebook`/`NbCell` session objects with their snapshot queries (`find_cells`, `summary_nb`). +- `fastcore.nbio`: notebook read/write/validate/repair, cell construction, cell editors, and the `Notebook`/`NbCell` session objects with their snapshot queries (`find_cells`, `summary_nb`), plus `nb.remove`/`del_cells` for taking cells out. - `exhash.skill`: hash-verified editing for files and cells, plus `open_doc` section outlines for Markdown, code, and notebooks; prefer it for edits where installed. - `rgapi.skill`: `rg`/`fd`/`ls`/`nbrg` search with lnhash output, and `rgstr` to search text already in hand. - `remold`: structural search and rewrite for Python source (declarative ast-grep rules, LibCST matcher transforms, symbol queries); the engine behind `ast_replace`. @@ -56,12 +56,12 @@ 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) 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_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, del_cells) __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', '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_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', 'del_cells'] __pyskill_params__ = {'replace_params': ('start_line', 'end_line', 'n_matches', 're_filter', 'invert_filter', 'use_regex')} diff --git a/fastcore/nbio.py b/fastcore/nbio.py index 00510a54..e3d71e19 100644 --- a/fastcore/nbio.py +++ b/fastcore/nbio.py @@ -18,8 +18,8 @@ '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', 'Found', 'FoundCells', - 'find_cells', 'deep_merge', 'update_cell', 'fm_default_eval', 'does_cell_eval', 'select_cells', 'run_cell', - 'msg2out', 'msgs2outs'] + 'find_cells', 'del_cells', 'deep_merge', 'update_cell', 'fm_default_eval', 'does_cell_eval', 'select_cells', + 'run_cell', 'msg2out', 'msgs2outs'] # %% ../nbs/13_nbio.ipynb #954ca1aa from .basics import * @@ -715,12 +715,19 @@ def md(self:Notebook, source, idx=None, after=None, before=None, **kwargs): "Add a new cell with `source` at `idx` (default: end), or `after`/`before` a cell id" return self.add(source, cell_type='markdown', idx=idx, after=after, before=before, **kwargs) +# %% ../nbs/13_nbio.ipynb #dac137e1 +@patch +def remove(self:Notebook, *ids): + "Remove cells by id (exact or unique prefix), index, or held cell, returning them" + cells = [k if isinstance(k, dict) else self[k] for k in ids] + for c in cells: self.cells.remove(c) + return cells + # %% ../nbs/13_nbio.ipynb #63ba4a93 @patch def move(self:Notebook, src_ids, after=None, before=None): "Move cells with `src_ids` after/before a cell id, or to end" - cells = [self[k] for k in listify(src_ids)] - for c in cells: self.cells.remove(c) + cells = self.remove(*listify(src_ids)) if after: idx = next((i+1 for i,c in enumerate(self.cells) if c.id==after), None) elif before: idx = next((i for i,c in enumerate(self.cells) if c.id==before), None) else: idx = len(self.cells) @@ -825,6 +832,17 @@ def find_cells( fc = Notebook.open(path).find_cells(pat, cell_type, ids=ids, context=context) return FoundCells([CellRow(c) for c in fc], matched=fc.matched) +# %% ../nbs/13_nbio.ipynb #2a645b36 +def del_cells( + path, # Notebook file to modify + *ids, # Cell ids (exact or unique prefix) or indexes +): + "Delete cells from the notebook at `path`, returning `CellRow` snapshots of the removed cells" + nb = Notebook.open(path) + res = nb.remove(*ids) + nb.save() + return CellRows(CellRow(c) for c in res) + # %% ../nbs/13_nbio.ipynb #202a29f1 def deep_merge( d:dict, # Base dict diff --git a/nbs/13_nbio.ipynb b/nbs/13_nbio.ipynb index 602bf8c9..216c2290 100644 --- a/nbs/13_nbio.ipynb +++ b/nbs/13_nbio.ipynb @@ -3093,6 +3093,54 @@ "len(nbo) == prev_len - 1\n" ] }, + { + "cell_type": "markdown", + "id": "bcc758dd", + "metadata": {}, + "source": [ + "`remove` takes any number of ids, or held cells, and returns what it removed. Passing a cell rather than an id is how to pick one of two cells that share an id, as nbdev's merge driver leaves them after a conflict:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dac137e1", + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "@patch\n", + "def remove(self:Notebook, *ids):\n", + " \"Remove cells by id (exact or unique prefix), index, or held cell, returning them\"\n", + " cells = [k if isinstance(k, dict) else self[k] for k in ids]\n", + " for c in cells: self.cells.remove(c)\n", + " return cells" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9eee34a1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['e2147a69', '801558df']" + ] + }, + "execution_count": 138, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "nbo = Notebook.open(minimal_fn)\n", + "removed = nbo.remove('e214', nbo[0])\n", + "test_eq(len(nbo), 0)\n", + "[c.id for c in removed]" + ] + }, { "cell_type": "code", "execution_count": null, @@ -3104,8 +3152,7 @@ "@patch\n", "def move(self:Notebook, src_ids, after=None, before=None):\n", " \"Move cells with `src_ids` after/before a cell id, or to end\"\n", - " cells = [self[k] for k in listify(src_ids)]\n", - " for c in cells: self.cells.remove(c)\n", + " cells = self.remove(*listify(src_ids))\n", " if after: idx = next((i+1 for i,c in enumerate(self.cells) if c.id==after), None)\n", " elif before: idx = next((i for i,c in enumerate(self.cells) if c.id==before), None)\n", " else: idx = len(self.cells)\n", @@ -3444,6 +3491,60 @@ "rows" ] }, + { + "cell_type": "markdown", + "id": "9ddf85ef", + "metadata": {}, + "source": [ + "`del_cells` is the transaction form of `remove`: it deletes by id in the notebook at `path`, saves, and returns `CellRow` snapshots of the removed cells:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2a645b36", + "metadata": {}, + "outputs": [], + "source": [ + "#| export\n", + "def del_cells(\n", + " path, # Notebook file to modify\n", + " *ids, # Cell ids (exact or unique prefix) or indexes\n", + "):\n", + " \"Delete cells from the notebook at `path`, returning `CellRow` snapshots of the removed cells\"\n", + " nb = Notebook.open(path)\n", + " res = nb.remove(*ids)\n", + " nb.save()\n", + " return CellRows(CellRow(c) for c in res)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b3995434", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "aa11:c:a=1\n", + "cc33:c:c=3" + ] + }, + "execution_count": 139, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tp = Path('tmp_del.ipynb')\n", + "write_nb(new_nb([mk_cell('a=1', id='aa11'), mk_cell('b=2', id='bb22'), mk_cell('c=3', id='cc33')]), tp)\n", + "rows = del_cells(tp, 'aa11', 'cc')\n", + "test_eq([c.id for c in read_nb(tp).cells], ['bb22'])\n", + "tp.unlink()\n", + "rows" + ] + }, { "cell_type": "markdown", "id": "f215aa99",