|
3 | 3 | # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/13_nbio.ipynb. |
4 | 4 |
|
5 | 5 | # %% auto #0 |
6 | | -__all__ = ['NbCell', 'dict2nb', 'read_nb', 'mk_cell', 'new_nb', 'nb2dict', 'nb2str', 'write_nb'] |
| 6 | +__all__ = ['NbCell', 'dict2nb', 'read_nb', 'mk_cell', 'new_nb', 'nb2dict', 'nb2str', 'write_nb', 'cell2xml', 'cells2xml', |
| 7 | + 'Notebook'] |
7 | 8 |
|
8 | 9 | # %% ../nbs/13_nbio.ipynb #5faca748 |
9 | 10 | from .basics import * |
@@ -102,3 +103,100 @@ def write_nb(nb, path): |
102 | 103 | old = Path(path).read_text(encoding='utf-8') if path.exists() else None |
103 | 104 | if new!=old: |
104 | 105 | with open(path, 'w', encoding='utf-8') as f: f.write(new) |
| 106 | + |
| 107 | +# %% ../nbs/13_nbio.ipynb #102e32c6 |
| 108 | +from .xml import Code,Markdown,Raw,NB,Source,Out,to_xml |
| 109 | + |
| 110 | +# %% ../nbs/13_nbio.ipynb #ca73be1c |
| 111 | +_ctfuns_nb = dict(code=Code, markdown=Markdown, raw=Raw) |
| 112 | + |
| 113 | +def cell2xml(cell, ids=True, incl_out=True): |
| 114 | + "Convert NbCell to concise XML format" |
| 115 | + f = _ctfuns_nb[cell.cell_type] |
| 116 | + kw = dict(id=cell.id) if ids else {} |
| 117 | + output = getattr(cell, 'outputs', None) if incl_out else None |
| 118 | + if not output: return f(cell.source, **kw) |
| 119 | + return f(Source(cell.source), Out(str(output)), **kw) |
| 120 | + |
| 121 | +def cells2xml(cells, wrap=NB, ids=True, incl_out=True, **kw): |
| 122 | + "Convert notebook cells to XML format" |
| 123 | + return to_xml(wrap(*[cell2xml(c, ids=ids, incl_out=incl_out) for c in cells], **kw), do_escape=False) |
| 124 | + |
| 125 | + |
| 126 | +# %% ../nbs/13_nbio.ipynb #f9eda9e3 |
| 127 | +class Notebook: |
| 128 | + "Read, query, and edit Jupyter notebooks" |
| 129 | + def __init__(self, nb, path=None): |
| 130 | + if not path: path = Path("unnamed.ipynb") |
| 131 | + store_attr() |
| 132 | + |
| 133 | + @property |
| 134 | + def _id2cell(self): return {c.id: c for c in self.nb.cells} |
| 135 | + |
| 136 | + @classmethod |
| 137 | + def open(cls, path): |
| 138 | + path = Path(path).resolve() |
| 139 | + return cls(read_nb(path), path) |
| 140 | + |
| 141 | + def save(self, path=None): write_nb(self.nb, path or self.path) |
| 142 | + |
| 143 | + @property |
| 144 | + def cells(self): return self.nb.cells |
| 145 | + @property |
| 146 | + def meta(self): return self.nb.metadata |
| 147 | + |
| 148 | + def __getitem__(self, k): return self.cells[k] if isinstance(k, (int, slice)) else self._id2cell[k] |
| 149 | + def __setitem__(self, k, source): self[k].set_source(source) |
| 150 | + def __len__(self): return len(self.cells) |
| 151 | + def __iter__(self): return iter(self.cells) |
| 152 | + def __contains__(self, k): return k in self._id2cell |
| 153 | + def __delitem__(self, k): self.cells.remove(self[k]) |
| 154 | + def __repr__(self): return cells2xml(self.cells, path=self.path) |
| 155 | + @property |
| 156 | + def concise (self): return cells2xml(self.cells, path=self.path.name, incl_out=False) |
| 157 | + |
| 158 | +# %% ../nbs/13_nbio.ipynb #161b6662 |
| 159 | +@patch |
| 160 | +def add(self:Notebook, source, cell_type='code', idx=None, after=None, before=None, **kwargs): |
| 161 | + "Add a new cell with `source` at `idx` (default: end), or `after`/`before` a cell id" |
| 162 | + if after: idx = next((i for i,c in enumerate(self.cells) if c.id==after), None) |
| 163 | + if idx is None and after: raise KeyError(after) |
| 164 | + if idx is not None: idx += 1 |
| 165 | + elif before: idx = next((i for i,c in enumerate(self.cells) if c.id==before), None) |
| 166 | + if idx is None and before: raise KeyError(before) |
| 167 | + c = mk_cell(source, cell_type=cell_type, **kwargs) |
| 168 | + if idx is None: self.cells.append(c) |
| 169 | + else: self.cells.insert(idx, c) |
| 170 | + return c |
| 171 | + |
| 172 | +# %% ../nbs/13_nbio.ipynb #8ad4e43b |
| 173 | +@patch |
| 174 | +def md(self:Notebook, source, idx=None, after=None, before=None, **kwargs): |
| 175 | + "Add a new cell with `source` at `idx` (default: end), or `after`/`before` a cell id" |
| 176 | + return self.add(source, cell_type='markdown', idx=idx, after=after, before=before, **kwargs) |
| 177 | + |
| 178 | +# %% ../nbs/13_nbio.ipynb #5a09a2fa |
| 179 | +@patch |
| 180 | +def find(self:Notebook, pat, cell_type=None): |
| 181 | + "Find cells with source matching regex `pat`" |
| 182 | + return [c for c in self.cells if re.search(pat, c.source) and (not cell_type or c.cell_type==cell_type)] |
| 183 | + |
| 184 | +# %% ../nbs/13_nbio.ipynb #63ba4a93 |
| 185 | +@patch |
| 186 | +def move(self:Notebook, src_ids, after=None, before=None): |
| 187 | + "Move cells with `src_ids` after/before a cell id, or to end" |
| 188 | + cells = [self[k] for k in listify(src_ids)] |
| 189 | + for c in cells: self.cells.remove(c) |
| 190 | + if after: idx = next((i+1 for i,c in enumerate(self.cells) if c.id==after), None) |
| 191 | + elif before: idx = next((i for i,c in enumerate(self.cells) if c.id==before), None) |
| 192 | + else: idx = len(self.cells) |
| 193 | + if idx is None: raise KeyError(after or before) |
| 194 | + for i,c in enumerate(cells): self.cells.insert(idx+i, c) |
| 195 | + |
| 196 | +# %% ../nbs/13_nbio.ipynb #e064c39e |
| 197 | +@patch |
| 198 | +def view(self:Notebook, id, nums=True): |
| 199 | + "Show cell source with optional line numbers" |
| 200 | + lines = self[id].source.splitlines() |
| 201 | + if nums: lines = [f'{i+1:6d} │ {l}' for i,l in enumerate(lines)] |
| 202 | + return '\n'.join(lines) |
0 commit comments