fastcore 2.1.2__tar.gz → 2.1.4__tar.gz
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.
- {fastcore-2.1.2/fastcore.egg-info → fastcore-2.1.4}/PKG-INFO +1 -1
- fastcore-2.1.4/fastcore/__init__.py +1 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/_modidx.py +25 -7
- fastcore-2.1.4/fastcore/editskill.py +63 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/nbio.py +159 -23
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/tools.py +39 -12
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/xml.py +8 -20
- {fastcore-2.1.2 → fastcore-2.1.4/fastcore.egg-info}/PKG-INFO +1 -1
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore.egg-info/SOURCES.txt +1 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore.egg-info/entry_points.txt +1 -1
- {fastcore-2.1.2 → fastcore-2.1.4}/pyproject.toml +1 -1
- fastcore-2.1.2/fastcore/__init__.py +0 -1
- {fastcore-2.1.2 → fastcore-2.1.4}/CONTRIBUTING.md +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/LICENSE +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/MANIFEST.in +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/README.md +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/aio.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/all.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/ansi.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/basics.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/dispatch.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/docments.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/docscrape.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/foundation.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/imghdr.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/imports.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/meta.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/nb_imports.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/net.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/parallel.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/py2pyi.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/script.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/shutil.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/style.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/test.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/transform.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/utils.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/xdg.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/xtras.py +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore.egg-info/dependency_links.txt +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore.egg-info/requires.txt +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/fastcore.egg-info/top_level.txt +0 -0
- {fastcore-2.1.2 → fastcore-2.1.4}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.1.4"
|
|
@@ -354,6 +354,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
354
354
|
'fastcore.docments.sig2str': ('docments.html#sig2str', 'fastcore/docments.py'),
|
|
355
355
|
'fastcore.docments.sig_source': ('docments.html#sig_source', 'fastcore/docments.py')},
|
|
356
356
|
'fastcore.docscrape': {},
|
|
357
|
+
'fastcore.editskill': {},
|
|
357
358
|
'fastcore.foundation': { 'fastcore.foundation.CollBase': ('foundation.html#collbase', 'fastcore/foundation.py'),
|
|
358
359
|
'fastcore.foundation.CollBase.__delitem__': ( 'foundation.html#collbase.__delitem__',
|
|
359
360
|
'fastcore/foundation.py'),
|
|
@@ -479,7 +480,12 @@ d = { 'settings': { 'branch': 'main',
|
|
|
479
480
|
'fastcore.meta.use_kwargs': ('meta.html#use_kwargs', 'fastcore/meta.py'),
|
|
480
481
|
'fastcore.meta.use_kwargs_dict': ('meta.html#use_kwargs_dict', 'fastcore/meta.py')},
|
|
481
482
|
'fastcore.nb_imports': {},
|
|
482
|
-
'fastcore.nbio': { 'fastcore.nbio.
|
|
483
|
+
'fastcore.nbio': { 'fastcore.nbio.CellRow': ('nbio.html#cellrow', 'fastcore/nbio.py'),
|
|
484
|
+
'fastcore.nbio.CellRow.__init__': ('nbio.html#cellrow.__init__', 'fastcore/nbio.py'),
|
|
485
|
+
'fastcore.nbio.CellRow.__repr__': ('nbio.html#cellrow.__repr__', 'fastcore/nbio.py'),
|
|
486
|
+
'fastcore.nbio.CellRows': ('nbio.html#cellrows', 'fastcore/nbio.py'),
|
|
487
|
+
'fastcore.nbio.CellRows.__repr__': ('nbio.html#cellrows.__repr__', 'fastcore/nbio.py'),
|
|
488
|
+
'fastcore.nbio.NbCell': ('nbio.html#nbcell', 'fastcore/nbio.py'),
|
|
483
489
|
'fastcore.nbio.NbCell.__eq__': ('nbio.html#nbcell.__eq__', 'fastcore/nbio.py'),
|
|
484
490
|
'fastcore.nbio.NbCell.__hash__': ('nbio.html#nbcell.__hash__', 'fastcore/nbio.py'),
|
|
485
491
|
'fastcore.nbio.NbCell.__init__': ('nbio.html#nbcell.__init__', 'fastcore/nbio.py'),
|
|
@@ -504,14 +510,18 @@ d = { 'settings': { 'branch': 'main',
|
|
|
504
510
|
'fastcore.nbio.Notebook.add': ('nbio.html#notebook.add', 'fastcore/nbio.py'),
|
|
505
511
|
'fastcore.nbio.Notebook.cells': ('nbio.html#notebook.cells', 'fastcore/nbio.py'),
|
|
506
512
|
'fastcore.nbio.Notebook.concise': ('nbio.html#notebook.concise', 'fastcore/nbio.py'),
|
|
507
|
-
'fastcore.nbio.Notebook.
|
|
513
|
+
'fastcore.nbio.Notebook.find_cells': ('nbio.html#notebook.find_cells', 'fastcore/nbio.py'),
|
|
508
514
|
'fastcore.nbio.Notebook.md': ('nbio.html#notebook.md', 'fastcore/nbio.py'),
|
|
509
515
|
'fastcore.nbio.Notebook.meta': ('nbio.html#notebook.meta', 'fastcore/nbio.py'),
|
|
510
516
|
'fastcore.nbio.Notebook.move': ('nbio.html#notebook.move', 'fastcore/nbio.py'),
|
|
511
517
|
'fastcore.nbio.Notebook.open': ('nbio.html#notebook.open', 'fastcore/nbio.py'),
|
|
512
518
|
'fastcore.nbio.Notebook.save': ('nbio.html#notebook.save', 'fastcore/nbio.py'),
|
|
513
|
-
'fastcore.nbio.Notebook.
|
|
519
|
+
'fastcore.nbio.Notebook.summary': ('nbio.html#notebook.summary', 'fastcore/nbio.py'),
|
|
520
|
+
'fastcore.nbio.Notebook.to_dict': ('nbio.html#notebook.to_dict', 'fastcore/nbio.py'),
|
|
521
|
+
'fastcore.nbio.Notebook.view_cell': ('nbio.html#notebook.view_cell', 'fastcore/nbio.py'),
|
|
522
|
+
'fastcore.nbio._cell_method': ('nbio.html#_cell_method', 'fastcore/nbio.py'),
|
|
514
523
|
'fastcore.nbio._dict2obj': ('nbio.html#_dict2obj', 'fastcore/nbio.py'),
|
|
524
|
+
'fastcore.nbio._dir_attrs': ('nbio.html#_dir_attrs', 'fastcore/nbio.py'),
|
|
515
525
|
'fastcore.nbio._dir_line': ('nbio.html#_dir_line', 'fastcore/nbio.py'),
|
|
516
526
|
'fastcore.nbio._dir_pre': ('nbio.html#_dir_pre', 'fastcore/nbio.py'),
|
|
517
527
|
'fastcore.nbio._directive': ('nbio.html#_directive', 'fastcore/nbio.py'),
|
|
@@ -521,6 +531,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
521
531
|
'fastcore.nbio._join': ('nbio.html#_join', 'fastcore/nbio.py'),
|
|
522
532
|
'fastcore.nbio._meta_directives': ('nbio.html#_meta_directives', 'fastcore/nbio.py'),
|
|
523
533
|
'fastcore.nbio._mkout': ('nbio.html#_mkout', 'fastcore/nbio.py'),
|
|
534
|
+
'fastcore.nbio._nb_cell': ('nbio.html#_nb_cell', 'fastcore/nbio.py'),
|
|
524
535
|
'fastcore.nbio._read_json': ('nbio.html#_read_json', 'fastcore/nbio.py'),
|
|
525
536
|
'fastcore.nbio._rejoin_cell': ('nbio.html#_rejoin_cell', 'fastcore/nbio.py'),
|
|
526
537
|
'fastcore.nbio._rejoin_mime': ('nbio.html#_rejoin_mime', 'fastcore/nbio.py'),
|
|
@@ -529,10 +540,13 @@ d = { 'settings': { 'branch': 'main',
|
|
|
529
540
|
'fastcore.nbio._split_mime': ('nbio.html#_split_mime', 'fastcore/nbio.py'),
|
|
530
541
|
'fastcore.nbio._unparse_dir': ('nbio.html#_unparse_dir', 'fastcore/nbio.py'),
|
|
531
542
|
'fastcore.nbio.cell2xml': ('nbio.html#cell2xml', 'fastcore/nbio.py'),
|
|
543
|
+
'fastcore.nbio.cell_edit': ('nbio.html#cell_edit', 'fastcore/nbio.py'),
|
|
532
544
|
'fastcore.nbio.cells2xml': ('nbio.html#cells2xml', 'fastcore/nbio.py'),
|
|
533
545
|
'fastcore.nbio.concat_streams': ('nbio.html#concat_streams', 'fastcore/nbio.py'),
|
|
534
546
|
'fastcore.nbio.dict2nb': ('nbio.html#dict2nb', 'fastcore/nbio.py'),
|
|
547
|
+
'fastcore.nbio.find_cells': ('nbio.html#find_cells', 'fastcore/nbio.py'),
|
|
535
548
|
'fastcore.nbio.first_code_ln': ('nbio.html#first_code_ln', 'fastcore/nbio.py'),
|
|
549
|
+
'fastcore.nbio.item2xml': ('nbio.html#item2xml', 'fastcore/nbio.py'),
|
|
536
550
|
'fastcore.nbio.mk_cell': ('nbio.html#mk_cell', 'fastcore/nbio.py'),
|
|
537
551
|
'fastcore.nbio.mk_display': ('nbio.html#mk_display', 'fastcore/nbio.py'),
|
|
538
552
|
'fastcore.nbio.mk_error': ('nbio.html#mk_error', 'fastcore/nbio.py'),
|
|
@@ -550,8 +564,10 @@ d = { 'settings': { 'branch': 'main',
|
|
|
550
564
|
'fastcore.nbio.render_text': ('nbio.html#render_text', 'fastcore/nbio.py'),
|
|
551
565
|
'fastcore.nbio.repair_cell': ('nbio.html#repair_cell', 'fastcore/nbio.py'),
|
|
552
566
|
'fastcore.nbio.repair_nb': ('nbio.html#repair_nb', 'fastcore/nbio.py'),
|
|
567
|
+
'fastcore.nbio.summary_nb': ('nbio.html#summary_nb', 'fastcore/nbio.py'),
|
|
553
568
|
'fastcore.nbio.validate_cell': ('nbio.html#validate_cell', 'fastcore/nbio.py'),
|
|
554
569
|
'fastcore.nbio.validate_nb': ('nbio.html#validate_nb', 'fastcore/nbio.py'),
|
|
570
|
+
'fastcore.nbio.view_cell': ('nbio.html#view_cell', 'fastcore/nbio.py'),
|
|
555
571
|
'fastcore.nbio.write_nb': ('nbio.html#write_nb', 'fastcore/nbio.py')},
|
|
556
572
|
'fastcore.net': { 'fastcore.net.HTTP4xxClientError': ('net.html#http4xxclienterror', 'fastcore/net.py'),
|
|
557
573
|
'fastcore.net.HTTP5xxServerError': ('net.html#http5xxservererror', 'fastcore/net.py'),
|
|
@@ -677,14 +693,17 @@ d = { 'settings': { 'branch': 'main',
|
|
|
677
693
|
'fastcore.test.test_warns': ('test.html#test_warns', 'fastcore/test.py')},
|
|
678
694
|
'fastcore.tools': { 'fastcore.tools._norm_lines': ('tools.html#_norm_lines', 'fastcore/tools.py'),
|
|
679
695
|
'fastcore.tools.ast_replace': ('tools.html#ast_replace', 'fastcore/tools.py'),
|
|
696
|
+
'fastcore.tools.create_file': ('tools.html#create_file', 'fastcore/tools.py'),
|
|
680
697
|
'fastcore.tools.del_lines': ('tools.html#del_lines', 'fastcore/tools.py'),
|
|
681
|
-
'fastcore.tools.file_create': ('tools.html#file_create', 'fastcore/tools.py'),
|
|
682
698
|
'fastcore.tools.file_edit': ('tools.html#file_edit', 'fastcore/tools.py'),
|
|
683
|
-
'fastcore.tools.file_view': ('tools.html#file_view', 'fastcore/tools.py'),
|
|
684
699
|
'fastcore.tools.insert_line': ('tools.html#insert_line', 'fastcore/tools.py'),
|
|
700
|
+
'fastcore.tools.line_hash': ('tools.html#line_hash', 'fastcore/tools.py'),
|
|
701
|
+
'fastcore.tools.lnhash': ('tools.html#lnhash', 'fastcore/tools.py'),
|
|
702
|
+
'fastcore.tools.lnhash_at': ('tools.html#lnhash_at', 'fastcore/tools.py'),
|
|
685
703
|
'fastcore.tools.replace_lines': ('tools.html#replace_lines', 'fastcore/tools.py'),
|
|
686
704
|
'fastcore.tools.str_replace': ('tools.html#str_replace', 'fastcore/tools.py'),
|
|
687
|
-
'fastcore.tools.strs_replace': ('tools.html#strs_replace', 'fastcore/tools.py')
|
|
705
|
+
'fastcore.tools.strs_replace': ('tools.html#strs_replace', 'fastcore/tools.py'),
|
|
706
|
+
'fastcore.tools.view_file': ('tools.html#view_file', 'fastcore/tools.py')},
|
|
688
707
|
'fastcore.transform': {},
|
|
689
708
|
'fastcore.utils': {},
|
|
690
709
|
'fastcore.xdg': { 'fastcore.xdg._path_from_env': ('xdg.html#_path_from_env', 'fastcore/xdg.py'),
|
|
@@ -729,7 +748,6 @@ d = { 'settings': { 'branch': 'main',
|
|
|
729
748
|
'fastcore.xml.dict2xml': ('xml.html#dict2xml', 'fastcore/xml.py'),
|
|
730
749
|
'fastcore.xml.ft': ('xml.html#ft', 'fastcore/xml.py'),
|
|
731
750
|
'fastcore.xml.highlight': ('xml.html#highlight', 'fastcore/xml.py'),
|
|
732
|
-
'fastcore.xml.item2xml': ('xml.html#item2xml', 'fastcore/xml.py'),
|
|
733
751
|
'fastcore.xml.mk_getattr': ('xml.html#mk_getattr', 'fastcore/xml.py'),
|
|
734
752
|
'fastcore.xml.showtags': ('xml.html#showtags', 'fastcore/xml.py'),
|
|
735
753
|
'fastcore.xml.to_xml': ('xml.html#to_xml', 'fastcore/xml.py'),
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Text, file, cell, and notebook editing from `fastcore.tools` and `fastcore.nbio`, plus the conventions the whole fastai editing toolkit follows. Read this before working with the editing tools in any package that shares them.
|
|
2
|
+
|
|
3
|
+
`from fastcore.editskill import *` loads the fastcore editing layer: the text primitives and file tools of `fastcore.tools`, and the notebook I/O and cell editors of `fastcore.nbio`. Sibling packages extend the same toolkit, and where they are installed prefer them as noted: `exhash` for hash-verified editing, `rgapi` for search, `llmsurgery`/`dialoghelper` for dialogs.
|
|
4
|
+
|
|
5
|
+
## Carriers
|
|
6
|
+
|
|
7
|
+
Each editing function works on one carrier: text (a str in memory), a file (a path on disk), a cell (one cell's source in an .ipynb file, addressed by `path, cell_id`), or a notebook (the parsed .ipynb; `Notebook` and `NbCell` are its held-object forms). These are the representation layer: an .ipynb is a file of cells, whatever produced it. The dialog layer above adds the msg and dlg carriers: a Solveit dialog is an .ipynb whose cells are messages (notes, runnable code, prompt/reply pairs), and `llmsurgery.dlgskill` and `dialoghelper` provide the message tools, following the conventions here with their own nouns. The word picks the layer: cell tools answer representation questions ("why does Jupyter reject this file?"), msg tools answer content questions ("what does this message say?").
|
|
8
|
+
|
|
9
|
+
## Naming
|
|
10
|
+
|
|
11
|
+
Two name shapes cover the toolkit, and the pivot is the verb's direct object:
|
|
12
|
+
|
|
13
|
+
- 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).
|
|
14
|
+
- 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.
|
|
15
|
+
|
|
16
|
+
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 llmsurgery, `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.
|
|
17
|
+
|
|
18
|
+
## Parameters
|
|
19
|
+
|
|
20
|
+
One vocabulary, identical wherever it appears:
|
|
21
|
+
|
|
22
|
+
- The carrier's address comes first (`text`; `path`; `path, cell_id`; a message `id`), the payload next, and ambient context last as keyword-only (message tools name their dialog that way).
|
|
23
|
+
- `start_line`/`end_line`: 1-based, inclusive, `None` for first/last, negative counting from the end. Destructive ops (`del_lines`) accept no defaults: state the range.
|
|
24
|
+
- `re_filter`/`invert_filter`: restrict an edit to lines matching (or not matching) a regex, like ex's `g//` and `g!//`; combines with the range.
|
|
25
|
+
- Searches read patterns as regex by default; editors read them as literal text until `use_regex=True`. Searching is read-only, so its default favors power; editing favors safety.
|
|
26
|
+
- `nums` and `lnhashs` on any view: line numbers, or `lineno|hash|` addresses. `maxlen` caps characters per summary line; `trunc_out`/`trunc_in` truncate outputs and sources in dialog views.
|
|
27
|
+
- Search tools share one filter vocabulary: `pattern` first, `root='.'`, and the same include/exclude/ext/hidden/ignore block across `fd`, `ls`, `rg`, and `nbrg`. Variants differ by defaults, not API: `ls` is `fd` with listing defaults. Boolean filters narrow as `only_*` and widen as `include_*`.
|
|
28
|
+
- `context=` counts the medium's own units: lines (or blocks in summary mode) for files, cells for notebooks, messages for dialogs. Dialog search defaults to context 1 because the neighbouring note usually explains the match: the why lives next to the what.
|
|
29
|
+
- Every editor returns a diff ("none: No changes." when nothing changed). The diff is the verification: read it instead of re-viewing the target.
|
|
30
|
+
|
|
31
|
+
## Functions and methods
|
|
32
|
+
|
|
33
|
+
Every operation has two shapes with one contract each. The function is a transaction: it addresses a file by path, applies the edit, writes, and returns a diff. The method is a session: it mutates the object in hand, and nothing reaches disk until an explicit save. The correspondence is mechanical - the method is the function minus its address arguments, keeping its name except that a carrier token which became `self` drops: `cell_str_replace(path, cell_id, ...)` is `c.str_replace(...)` on a held `NbCell`, `find_cells(path, pat)` is `nb.find_cells(pat)` on a held `Notebook`. Function-side reads return dead snapshot rows carrying addresses, source, and meta; session-side reads return the live objects. Use one shape at a time per file: save before switching to functions, reopen after.
|
|
34
|
+
|
|
35
|
+
## Addresses
|
|
36
|
+
|
|
37
|
+
Edits say where with line numbers or lnhash addresses. Take addresses from the read you were already doing instead of with a second look: views accept `nums=True` or `lnhashs=True`, and searches return addresses directly (`rg(lnhashs=True)`). Prefer lnhash addresses whenever `exhash` is installed: they are verified against current content at edit time, so a stale address fails loudly instead of editing nearby text, which is exactly what makes taking addresses early safe. Plain line numbers are unverified and shift as edits apply, so re-view after each edit and apply multi-edits bottom-to-top. `exhash.skill` owns the address format and the verified editor.
|
|
38
|
+
|
|
39
|
+
When you don't yet know where to edit, locate with a summary first: `rgapi`'s `rg(summary=True)` and `nbrg`, and llmsurgery's `summary_dlg`, each show one line per natural unit of their medium (block, cell, message), carrying the unit's address. Summaries locate, views read, addresses edit, diffs confirm.
|
|
40
|
+
|
|
41
|
+
## What's where
|
|
42
|
+
|
|
43
|
+
- `fastcore.tools`: text primitives, file tools, and `line_hash`/`lnhash`/`lnhash_at` for creating addresses without exhash installed.
|
|
44
|
+
- `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`).
|
|
45
|
+
- `exhash.skill`: hash-verified editing for files and cells; prefer it for edits where installed.
|
|
46
|
+
- `rgapi.skill`: `rg`/`fd`/`ls`/`nbrg` search with lnhash output.
|
|
47
|
+
- `remold`: structural search and rewrite for Python source (declarative ast-grep rules, LibCST matcher transforms, symbol queries); the engine behind `ast_replace`.
|
|
48
|
+
- `llmsurgery.dlgskill`, `dialoghelper`: the dialog layer, including its own theory of dialogs and projections.
|
|
49
|
+
|
|
50
|
+
Docs: https://fastcore.fast.ai/tools.html.md and https://fastcore.fast.ai/nbio.html.md
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
from fastcore.tools import (insert_line, str_replace, strs_replace, replace_lines, del_lines, ast_replace,
|
|
54
|
+
file_insert_line, file_str_replace, file_strs_replace, file_replace_lines, file_del_lines, file_ast_replace,
|
|
55
|
+
view_file, create_file, line_hash, lnhash, lnhash_at)
|
|
56
|
+
from fastcore.nbio import (read_nb, write_nb, new_nb, mk_cell, validate_nb, validate_cell, repair_nb, repair_cell,
|
|
57
|
+
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)
|
|
58
|
+
|
|
59
|
+
__all__ = ['insert_line', 'str_replace', 'strs_replace', 'replace_lines', 'del_lines', 'ast_replace',
|
|
60
|
+
'file_insert_line', 'file_str_replace', 'file_strs_replace', 'file_replace_lines', 'file_del_lines', 'file_ast_replace',
|
|
61
|
+
'view_file', 'create_file', 'line_hash', 'lnhash', 'lnhash_at',
|
|
62
|
+
'read_nb', 'write_nb', 'new_nb', 'mk_cell', 'validate_nb', 'validate_cell', 'repair_nb', 'repair_cell',
|
|
63
|
+
'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']
|
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
"""Reading and writing Jupyter notebooks
|
|
2
2
|
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
Naming and parameter conventions shared across the editing toolkit are documented in `fastcore.editskill`, which also re-exports this module's editing tools.
|
|
6
|
+
|
|
3
7
|
Docs: https://fastcore.fast.ai/nbio.html.md"""
|
|
4
8
|
|
|
5
9
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/13_nbio.ipynb.
|
|
6
10
|
|
|
7
11
|
# %% auto #0
|
|
8
|
-
__all__ = ['langs', '
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
+
__all__ = ['langs', 'cell_insert_line', 'cell_str_replace', 'cell_strs_replace', 'cell_replace_lines', 'cell_del_lines',
|
|
13
|
+
'cell_ast_replace', 'nb_lang', 'NbCell', 'dict2nb', 'read_nb', 'mk_cell', 'new_nb', 'first_code_ln',
|
|
14
|
+
'nb2dict', 'nb2str', 'write_nb', 'cell_edit', 'view_cell', 'validate_cell', 'validate_nb', 'repair_cell',
|
|
15
|
+
'repair_nb', 'preferred_out', 'mk_stream', 'mk_result', 'mk_display', 'mk_error', 'concat_streams',
|
|
16
|
+
'preferred_msg_out', 'render_output', 'render_outputs', 'render_text', 'item2xml', 'cell2xml', 'cells2xml',
|
|
17
|
+
'Notebook', 'CellRow', 'CellRows', 'summary_nb', 'find_cells']
|
|
12
18
|
|
|
13
19
|
# %% ../nbs/13_nbio.ipynb #954ca1aa
|
|
14
20
|
from .basics import *
|
|
15
|
-
from .xtras import rtoken_hex,clean_cli_output,take_lines
|
|
21
|
+
from .xtras import rtoken_hex,clean_cli_output,take_lines,str_diff
|
|
16
22
|
from .imports import *
|
|
17
23
|
from .ansi import ansi2html
|
|
18
|
-
from .meta import delegates
|
|
24
|
+
from .meta import delegates,splice_sig
|
|
25
|
+
from .tools import insert_line,str_replace,strs_replace,replace_lines,del_lines,ast_replace,lnhash
|
|
19
26
|
|
|
20
|
-
import ast,functools
|
|
27
|
+
import ast,copy,functools
|
|
21
28
|
from collections import defaultdict
|
|
22
29
|
from pprint import pformat,pprint
|
|
23
30
|
from json import loads,dumps
|
|
@@ -150,19 +157,20 @@ def _directive(s, lang='python'):
|
|
|
150
157
|
return name, ('' if v=='true' else v)
|
|
151
158
|
|
|
152
159
|
# %% ../nbs/13_nbio.ipynb #16c08410
|
|
153
|
-
def _meta_directives(
|
|
154
|
-
"Directives from the `nbdev` dict in
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
+
def _meta_directives(meta):
|
|
161
|
+
"Directives from the `nbdev` dict in metadata dict `meta`, as `{name: value}`; values must be str (`'true'` for a bare directive)"
|
|
162
|
+
d = (meta or {}).get('nbdev',{})
|
|
163
|
+
if bad := [k for k,v in d.items() if not isinstance(v,str)]:
|
|
164
|
+
raise TypeError(f"`nbdev` metadata directive values must be str (e.g 'true'/'false'), got non-str for: {bad}")
|
|
165
|
+
return {k:'' if v=='true' else v for k,v in d.items()}
|
|
166
|
+
|
|
167
|
+
def _dir_attrs(meta):
|
|
168
|
+
"Metadata directives as XML attrs: bare directives become True, so they render as bare attributes"
|
|
169
|
+
return {k:v or True for k,v in _meta_directives(meta).items()}
|
|
160
170
|
|
|
161
171
|
def _unparse_dir(v):
|
|
162
172
|
"Inverse of `_meta_directives` value parsing: value string back to a metadata value"
|
|
163
|
-
if v==''
|
|
164
|
-
if v=='false': return False
|
|
165
|
-
return v
|
|
173
|
+
return 'true' if v=='' else v
|
|
166
174
|
|
|
167
175
|
def _dir_line(k, v, lang='python', quarto=False):
|
|
168
176
|
"A canonical directive comment line; with `quarto`, bare directives render as `: true`"
|
|
@@ -181,7 +189,7 @@ def _directives_get(self):
|
|
|
181
189
|
if '_directives_' not in self:
|
|
182
190
|
dirs,_ = self._partition()
|
|
183
191
|
cmts = dict(t for t in (_directive(s, self.lang_) for s in dirs) if t)
|
|
184
|
-
meta = _meta_directives(self)
|
|
192
|
+
meta = _meta_directives(self.get('metadata'))
|
|
185
193
|
self['_meta_names_'] = set(meta) - set(cmts)
|
|
186
194
|
self['_directives_'] = cmts | {k:v for k,v in meta.items() if k not in cmts}
|
|
187
195
|
return dict(self['_directives_'])
|
|
@@ -243,7 +251,9 @@ def nb2dict(d, k=None):
|
|
|
243
251
|
# %% ../nbs/13_nbio.ipynb #21ffe533
|
|
244
252
|
def nb2str(nb):
|
|
245
253
|
"Convert `nb` to a `str`"
|
|
246
|
-
|
|
254
|
+
# Unconditional: a plain dict can hold live NbCells (e.g. spliced in by a processor), whose working
|
|
255
|
+
# attrs (`idx_` etc) and str `source` must not serialize. Idempotent on already-canonical dicts.
|
|
256
|
+
nb = nb2dict(nb)
|
|
247
257
|
return dumps(nb, sort_keys=True, indent=1, ensure_ascii=False) + "\n"
|
|
248
258
|
|
|
249
259
|
# %% ../nbs/13_nbio.ipynb #d979e25a
|
|
@@ -255,6 +265,63 @@ def write_nb(nb, path):
|
|
|
255
265
|
if new!=old:
|
|
256
266
|
with open(path, 'w', encoding='utf-8') as f: f.write(new)
|
|
257
267
|
|
|
268
|
+
# %% ../nbs/13_nbio.ipynb #3453e541
|
|
269
|
+
_cell_edit_doc = """
|
|
270
|
+
This is a *cell* editing function.
|
|
271
|
+
|
|
272
|
+
Cell editing standard parameters are `path`: notebook file to modify, and `cell_id`: id of the cell to edit (exact, or unique prefix)
|
|
273
|
+
|
|
274
|
+
returns: diff of changes, or "none: No changes.", or "error: ..."
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
def _nb_cell(nb, cell_id):
|
|
278
|
+
"Cell in `nb` with id `cell_id` (exact match, or unique prefix)"
|
|
279
|
+
res = [c for c in nb.cells if c.id==cell_id] or [c for c in nb.cells if c.id.startswith(cell_id)]
|
|
280
|
+
if len(res)!=1: raise KeyError(f"{'ambiguous' if res else 'no'} cell id: {cell_id!r}")
|
|
281
|
+
return res[0]
|
|
282
|
+
|
|
283
|
+
def cell_edit(f, name=None):
|
|
284
|
+
"Wrap text editor `f` as a cell editing function: `path, cell_id` addressing, diff-or-error return"
|
|
285
|
+
def wrapper(path:str, cell_id:str, *args, **kw):
|
|
286
|
+
nb = read_nb(path)
|
|
287
|
+
cell = _nb_cell(nb, cell_id)
|
|
288
|
+
text = cell.source
|
|
289
|
+
try: new_text = f(text, *args, **kw)
|
|
290
|
+
except ValueError as e: return PrettyString(f'error: {e}')
|
|
291
|
+
cell.source = new_text
|
|
292
|
+
write_nb(nb, path)
|
|
293
|
+
return PrettyString(str_diff(text, new_text) or 'none: No changes.')
|
|
294
|
+
res = splice_sig(wrapper, f, 'text')
|
|
295
|
+
if name: res.__name__ = res.__qualname__ = name
|
|
296
|
+
res.__doc__ = (f.__doc__ or '') + _cell_edit_doc
|
|
297
|
+
return res
|
|
298
|
+
|
|
299
|
+
# %% ../nbs/13_nbio.ipynb #bde31026
|
|
300
|
+
cell_insert_line = cell_edit(insert_line, 'cell_insert_line')
|
|
301
|
+
cell_str_replace = cell_edit(str_replace, 'cell_str_replace')
|
|
302
|
+
cell_strs_replace = cell_edit(strs_replace, 'cell_strs_replace')
|
|
303
|
+
cell_replace_lines = cell_edit(replace_lines, 'cell_replace_lines')
|
|
304
|
+
cell_del_lines = cell_edit(del_lines, 'cell_del_lines')
|
|
305
|
+
cell_ast_replace = cell_edit(ast_replace, 'cell_ast_replace')
|
|
306
|
+
|
|
307
|
+
# %% ../nbs/13_nbio.ipynb #421b2b9c
|
|
308
|
+
def view_cell(
|
|
309
|
+
path:str, # Notebook file to read
|
|
310
|
+
cell_id:str, # Id of the cell to view (exact, or unique prefix)
|
|
311
|
+
start_line:int=1, # Starting line to view
|
|
312
|
+
end_line:int=None, # End line (defaults to last line if None; may be past EOF, which clamps to the last line)
|
|
313
|
+
nums:bool=True, # Show line numbers?
|
|
314
|
+
lnhashs:bool=False # Show exhash `lineno|hash|` addresses instead of line numbers?
|
|
315
|
+
):
|
|
316
|
+
"View a cell's source, optionally limited to 1-based line range"
|
|
317
|
+
lines = _nb_cell(read_nb(path), cell_id).source.splitlines()
|
|
318
|
+
if not lines: return ''
|
|
319
|
+
if end_line is None or end_line > len(lines): end_line = len(lines)
|
|
320
|
+
if end_line < 0: end_line = len(lines)+end_line+1
|
|
321
|
+
if not (1 <= start_line <= len(lines)): return f'error: Invalid start_line {start_line}. Valid range: 1-{len(lines)}'
|
|
322
|
+
fmt = (lambda i,l: lnhash(i,l)+l) if lnhashs else (lambda i,l: f'{i}: {l}') if nums else (lambda i,l: l)
|
|
323
|
+
return PrettyString('\n'.join(fmt(i,l) for i,l in enumerate(lines[start_line-1:end_line], start_line)))
|
|
324
|
+
|
|
258
325
|
# %% ../nbs/13_nbio.ipynb #86453c0f
|
|
259
326
|
def validate_cell(cell, idx=None):
|
|
260
327
|
"Raise `ValueError` for structural problems in notebook cell dict `cell`; returns it unchanged if fine"
|
|
@@ -321,7 +388,7 @@ def repair_nb(nb):
|
|
|
321
388
|
return res
|
|
322
389
|
|
|
323
390
|
# %% ../nbs/13_nbio.ipynb #530b9cd1
|
|
324
|
-
from .xml import NB,to_xml,ft
|
|
391
|
+
from .xml import NB,to_xml,ft
|
|
325
392
|
|
|
326
393
|
# %% ../nbs/13_nbio.ipynb #9f22b923
|
|
327
394
|
def preferred_out(data, html1st=True, include_imgs=False):
|
|
@@ -424,11 +491,25 @@ def render_text(outputs, html1st=False):
|
|
|
424
491
|
if not items: return ''
|
|
425
492
|
return items[0][0] if len(items)==1 else '\n'.join(o[1] for o in items)
|
|
426
493
|
|
|
494
|
+
# %% ../nbs/13_nbio.ipynb #d32fc4bc
|
|
495
|
+
def item2xml(
|
|
496
|
+
typ, # Tag name: the cell or message type, e.g. 'code', 'markdown', 'raw', 'prompt'
|
|
497
|
+
content='', # The item's source text
|
|
498
|
+
out='', # Rendered output text
|
|
499
|
+
id=None, # Optional id attribute
|
|
500
|
+
meta=None, # Cell/message metadata: directives in its `nbdev` dict render as attrs, bare ones as bare attrs
|
|
501
|
+
**attrs, # Extra attributes; falsy values are dropped, names stay verbatim (no hyphenation)
|
|
502
|
+
):
|
|
503
|
+
"A notebook cell or dialog message as concise XML: content, then an `<out>` section when `out` is non-empty"
|
|
504
|
+
kw = {k:v for k,v in {'id':id, **_dir_attrs(meta), **attrs}.items() if v}
|
|
505
|
+
return ft(typ, content, ft('out', out), attrmap=str, **kw) if out else ft(typ, content, attrmap=str, **kw)
|
|
506
|
+
|
|
427
507
|
# %% ../nbs/13_nbio.ipynb #ca73be1c
|
|
428
508
|
def cell2xml(cell, ids=True, incl_out=True):
|
|
429
509
|
"Convert NbCell to concise XML format"
|
|
430
510
|
outputs = getattr(cell, 'outputs', None) if incl_out else None
|
|
431
|
-
return item2xml(cell.cell_type, cell.source, render_text(outputs) if outputs else '', id=cell.id if ids else None
|
|
511
|
+
return item2xml(cell.cell_type, cell.source, render_text(outputs) if outputs else '', id=cell.id if ids else None,
|
|
512
|
+
meta=cell.get('metadata'))
|
|
432
513
|
|
|
433
514
|
def cells2xml(cells, wrap=NB, ids=True, incl_out=True, **kw):
|
|
434
515
|
"Convert notebook cells to XML format"
|
|
@@ -467,6 +548,19 @@ class Notebook:
|
|
|
467
548
|
@property
|
|
468
549
|
def concise (self): return cells2xml(self.cells, path=self.path.name, incl_out=False)
|
|
469
550
|
|
|
551
|
+
# %% ../nbs/13_nbio.ipynb #af50ea0b
|
|
552
|
+
def _cell_method(f):
|
|
553
|
+
def method(self, *args, **kw):
|
|
554
|
+
old = self.source
|
|
555
|
+
self.set_source(f(old, *args, **kw))
|
|
556
|
+
return PrettyString(str_diff(old, self.source) or 'none: No changes.')
|
|
557
|
+
res = splice_sig(method, f, 'text')
|
|
558
|
+
res.__name__ = res.__qualname__ = f.__name__
|
|
559
|
+
res.__doc__ = (f.__doc__ or '') + "\nIn-memory edit of this cell's `source`, returning a diff; nothing is written to disk."
|
|
560
|
+
return res
|
|
561
|
+
|
|
562
|
+
for _f in (insert_line, str_replace, strs_replace, replace_lines, del_lines, ast_replace): setattr(NbCell, _f.__name__, _cell_method(_f))
|
|
563
|
+
|
|
470
564
|
# %% ../nbs/13_nbio.ipynb #161b6662
|
|
471
565
|
@patch
|
|
472
566
|
def add(self:Notebook, source, cell_type='code', idx=None, after=None, before=None, **kwargs):
|
|
@@ -489,7 +583,7 @@ def md(self:Notebook, source, idx=None, after=None, before=None, **kwargs):
|
|
|
489
583
|
|
|
490
584
|
# %% ../nbs/13_nbio.ipynb #5a09a2fa
|
|
491
585
|
@patch
|
|
492
|
-
def
|
|
586
|
+
def find_cells(self:Notebook, pat, cell_type=None):
|
|
493
587
|
"Find cells with source matching regex `pat`"
|
|
494
588
|
return [c for c in self.cells if re.search(pat, c.source) and (not cell_type or c.cell_type==cell_type)]
|
|
495
589
|
|
|
@@ -507,8 +601,50 @@ def move(self:Notebook, src_ids, after=None, before=None):
|
|
|
507
601
|
|
|
508
602
|
# %% ../nbs/13_nbio.ipynb #e064c39e
|
|
509
603
|
@patch
|
|
510
|
-
def
|
|
604
|
+
def view_cell(self:Notebook, id, nums=True):
|
|
511
605
|
"Show cell source with optional line numbers"
|
|
512
606
|
lines = self[id].source.splitlines()
|
|
513
607
|
if nums: lines = [f'{i+1:6d} │ {l}' for i,l in enumerate(lines)]
|
|
514
608
|
return '\n'.join(lines)
|
|
609
|
+
|
|
610
|
+
# %% ../nbs/13_nbio.ipynb #804670bc
|
|
611
|
+
class CellRow:
|
|
612
|
+
"Snapshot of one cell, shown as `id:t[directives]:source` (t: c=code m=markdown r=raw)"
|
|
613
|
+
def __init__(self, c, maxlen=120):
|
|
614
|
+
self.id,self.cell_type,self.source,self.maxlen = c.id,c.cell_type,c.source,maxlen
|
|
615
|
+
self.meta = copy.deepcopy(dict(c.get('metadata',{})))
|
|
616
|
+
def __repr__(self):
|
|
617
|
+
d = self.meta.get('nbdev',{})
|
|
618
|
+
tag = ' '.join(k if v in ('','true') else f'{k}={v}' for k,v in d.items())
|
|
619
|
+
src = self.source.replace('\n', '\\n')
|
|
620
|
+
if len(src)>self.maxlen: src = src[:self.maxlen]+'…'
|
|
621
|
+
return f"{self.id}:{self.cell_type[0]}{'['+tag+']' if tag else ''}:{src}"
|
|
622
|
+
|
|
623
|
+
class CellRows(list):
|
|
624
|
+
def __repr__(self): return '\n'.join(repr(o) for o in self)
|
|
625
|
+
|
|
626
|
+
# %% ../nbs/13_nbio.ipynb #62d7f8d1
|
|
627
|
+
@patch
|
|
628
|
+
def summary(self:Notebook, maxlen=120):
|
|
629
|
+
"One `CellRow` line per cell"
|
|
630
|
+
return CellRows(CellRow(c, maxlen) for c in self.cells)
|
|
631
|
+
|
|
632
|
+
def summary_nb(
|
|
633
|
+
path, # Notebook file to read
|
|
634
|
+
maxlen:int=120, # Maximum source characters per line
|
|
635
|
+
):
|
|
636
|
+
"One snapshot line per cell of the notebook at `path`"
|
|
637
|
+
return Notebook.open(path).summary(maxlen)
|
|
638
|
+
|
|
639
|
+
def find_cells(
|
|
640
|
+
path, # Notebook file to search
|
|
641
|
+
pat:str='', # Regex over cell source
|
|
642
|
+
cell_type:str=None, # Optional limit by type ('code', 'markdown', or 'raw')
|
|
643
|
+
):
|
|
644
|
+
"Snapshot `CellRows` for matching cells in the notebook at `path`"
|
|
645
|
+
return CellRows(CellRow(c) for c in Notebook.open(path).find_cells(pat, cell_type))
|
|
646
|
+
|
|
647
|
+
@patch
|
|
648
|
+
def to_dict(self:Notebook):
|
|
649
|
+
"The plain dict form of the held notebook (`nb2dict`): the representation layer"
|
|
650
|
+
return nb2dict(self.nb)
|
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
"""Text and file editing primitives shared by the fastai editing tools
|
|
2
2
|
|
|
3
|
-
The editors here are string-level: each takes `text` plus edit parameters and returns the new text, raising `ValueError` when an edit can't apply. The file tools below wrap them with path I/O and diff reporting; message-level wrappers live in llmsurgery. (This module previously held experimental LLM path-editing and command tools, superseded by safecmd, rgapi, and the tools here.)
|
|
3
|
+
The editors here are string-level: each takes `text` plus edit parameters and returns the new text, raising `ValueError` when an edit can't apply. The file tools below wrap them with path I/O and diff reporting; message-level wrappers live in llmsurgery. (This module previously held experimental LLM path-editing and command tools, superseded by safecmd, rgapi, and the tools here.) Naming, parameter, and workflow conventions for the whole editing toolkit, this module included, are documented in `fastcore.editskill`, which also re-exports these tools alongside `fastcore.nbio`'s.
|
|
4
|
+
|
|
5
|
+
`line_hash`, `lnhash`, and `lnhash_at` implement the [exhash](https://answerdotai.github.io/exhash) line-address format in pure Python: `lineno|hash|`, where the hash is 4 hex chars of crc32. They let any tool create lnhash-addressed views of text it holds, without depending on the exhash package.
|
|
4
6
|
|
|
5
7
|
File tools wrap the primitives with path I/O, returning unified diffs of what changed ("none: No changes." / "error: ..." otherwise). The path is the first argument, e.g:
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
view_file('~/a/b.py', 3)
|
|
10
|
+
create_file('~/a/b/c.py', 'content here')
|
|
9
11
|
file_str_replace('myfile.py', 'old_name', 'new_name')
|
|
10
12
|
file_del_lines('myfile.py', 2, 4)
|
|
11
13
|
file_replace_lines('myfile.py', new_content=src) # no line numbers: replace the entire contents
|
|
12
14
|
|
|
13
|
-
`file_str_replace`, `file_strs_replace`, and `file_del_lines` support `re_filter` and `invert_filter` for targeting only lines matching (or not matching) a regex, like ex's `g//` and `g!//`, combined with `start_line`/`end_line` to restrict to a region. `ast_replace(text, repls)` and `
|
|
15
|
+
`file_str_replace`, `file_strs_replace`, and `file_del_lines` support `re_filter` and `invert_filter` for targeting only lines matching (or not matching) a regex, like ex's `g//` and `g!//`, combined with `start_line`/`end_line` to restrict to a region. `ast_replace(text, repls)` and `file_ast_replace(path, repls)` apply ast-grep `(pattern, replacement)` rules with `$VAR` metavariables (requires the optional `remold` package).
|
|
14
16
|
|
|
15
17
|
Docs: https://fastcore.fast.ai/tools.html.md"""
|
|
16
18
|
|
|
17
19
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/12_tools.ipynb.
|
|
18
20
|
|
|
19
21
|
# %% auto #0
|
|
20
|
-
__all__ = ['file_insert_line', 'file_str_replace', 'file_strs_replace', 'file_replace_lines', 'file_del_lines',
|
|
21
|
-
'insert_line', 'str_replace', 'strs_replace', 'replace_lines', 'del_lines', '
|
|
22
|
-
'file_edit', 'ast_replace']
|
|
22
|
+
__all__ = ['file_insert_line', 'file_str_replace', 'file_strs_replace', 'file_replace_lines', 'file_del_lines',
|
|
23
|
+
'file_ast_replace', 'insert_line', 'str_replace', 'strs_replace', 'replace_lines', 'del_lines', 'line_hash',
|
|
24
|
+
'lnhash', 'lnhash_at', 'view_file', 'create_file', 'file_edit', 'ast_replace']
|
|
23
25
|
|
|
24
26
|
# %% ../nbs/12_tools.ipynb #578246d2
|
|
27
|
+
import zlib
|
|
25
28
|
from .imports import *
|
|
26
29
|
from .meta import splice_sig
|
|
27
30
|
from .basics import PrettyString
|
|
@@ -138,11 +141,34 @@ def del_lines(
|
|
|
138
141
|
else: del lines[s-1:e]
|
|
139
142
|
return ''.join(lines)
|
|
140
143
|
|
|
144
|
+
# %% ../nbs/12_tools.ipynb #6274581f
|
|
145
|
+
def line_hash(
|
|
146
|
+
line:str # A single line of text, without trailing newline
|
|
147
|
+
)->str:
|
|
148
|
+
"4-char hex exhash hash of `line`"
|
|
149
|
+
return f'{zlib.crc32(line.encode()) & 0xffff:04x}'
|
|
150
|
+
|
|
151
|
+
def lnhash(
|
|
152
|
+
lineno:int, # 1-based line number
|
|
153
|
+
line:str # The line's current text
|
|
154
|
+
)->str:
|
|
155
|
+
"`lineno|hash|` exhash address for `line` at `lineno`"
|
|
156
|
+
return f'{lineno}|{line_hash(line)}|'
|
|
157
|
+
|
|
158
|
+
def lnhash_at(
|
|
159
|
+
s:str|list|tuple, # A document as a str, or its lines
|
|
160
|
+
line:int # 1-based line number within `s`
|
|
161
|
+
)->str:
|
|
162
|
+
"`lineno|hash|` exhash address of line `line` of `s`"
|
|
163
|
+
if isinstance(s,str): s = s.splitlines()
|
|
164
|
+
return lnhash(line, s[line-1])
|
|
165
|
+
|
|
141
166
|
# %% ../nbs/12_tools.ipynb #806c957b
|
|
142
|
-
def
|
|
167
|
+
def view_file(
|
|
143
168
|
path:str, # Path to view (expands `~` if needed)
|
|
144
169
|
start_line:int=1, # Starting line to view
|
|
145
|
-
end_line:int=None # End line (defaults to last line if None; may be past EOF, which clamps to the last line - handy when the file size is unknown)
|
|
170
|
+
end_line:int=None, # End line (defaults to last line if None; may be past EOF, which clamps to the last line - handy when the file size is unknown)
|
|
171
|
+
lnhashs:bool=False # Prefix `lineno|hash|` exhash addresses instead of `lineno: `
|
|
146
172
|
):
|
|
147
173
|
"Read file contents, optionally limited to 1-based line range"
|
|
148
174
|
path = Path(path).expanduser()
|
|
@@ -152,10 +178,10 @@ def file_view(
|
|
|
152
178
|
if end_line < 0: end_line = len(lines)+end_line+1
|
|
153
179
|
if not (1 <= start_line <= len(lines)): return f'error: Invalid start_line {start_line}. Valid range: 1-{len(lines)}'
|
|
154
180
|
if end_line > len(lines): end_line = len(lines)
|
|
155
|
-
return PrettyString('\n'.join(f'{i}:
|
|
181
|
+
return PrettyString('\n'.join((lnhash(i,l) if lnhashs else f'{i}: ')+l for i,l in enumerate(lines[start_line-1:end_line], start_line)))
|
|
156
182
|
|
|
157
183
|
# %% ../nbs/12_tools.ipynb #424d09e1
|
|
158
|
-
def
|
|
184
|
+
def create_file(
|
|
159
185
|
path:str, # Path to create (expands `~` if needed)
|
|
160
186
|
contents:str, # Contents of file to create
|
|
161
187
|
overwrite:bool=False, # Replace the file if it already exists?
|
|
@@ -178,6 +204,7 @@ returns: diff of changes, or "none: No changes.", or "error: ..."
|
|
|
178
204
|
|
|
179
205
|
# %% ../nbs/12_tools.ipynb #a6f7361d
|
|
180
206
|
def file_edit(f, name=None):
|
|
207
|
+
"Wrap text editor `f` as a file editing function: `path` addressing, diff-or-error return"
|
|
181
208
|
def wrapper(path:str, *args, **kw):
|
|
182
209
|
path = Path(path).expanduser()
|
|
183
210
|
text = path.read_text()
|
|
@@ -218,4 +245,4 @@ def ast_replace(
|
|
|
218
245
|
except ImportError as e: raise ImportError('ast_replace requires the optional `remold` package: `pip install remold`') from e
|
|
219
246
|
return astmap(*repls)(text)
|
|
220
247
|
|
|
221
|
-
|
|
248
|
+
file_ast_replace = file_edit(ast_replace, 'file_ast_replace')
|
|
@@ -5,14 +5,14 @@ Docs: https://fastcore.fast.ai/xml.html.md"""
|
|
|
5
5
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/09_xml.ipynb.
|
|
6
6
|
|
|
7
7
|
# %% auto #0
|
|
8
|
-
__all__ = ['voids', 'attrmap', 'valmap', 'FT', 'ft', 'Html', 'Safe', 'to_xml', 'dict2xml', '
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
8
|
+
__all__ = ['voids', 'attrmap', 'valmap', 'FT', 'ft', 'Html', 'Safe', 'to_xml', 'dict2xml', 'highlight', 'showtags', 'mk_getattr',
|
|
9
|
+
'Head', 'Title', 'Meta', 'Link', 'Style', 'Body', 'Pre', 'Code', 'Div', 'Span', 'P', 'H1', 'H2', 'H3', 'H4',
|
|
10
|
+
'H5', 'H6', 'Strong', 'Em', 'B', 'I', 'U', 'S', 'Strike', 'Sub', 'Sup', 'Hr', 'Br', 'Img', 'A', 'Nav', 'Ul',
|
|
11
|
+
'Ol', 'Li', 'Dl', 'Dt', 'Dd', 'Table', 'Thead', 'Tbody', 'Tfoot', 'Tr', 'Th', 'Td', 'Caption', 'Col',
|
|
12
|
+
'Colgroup', 'Form', 'Input', 'Textarea', 'Button', 'Select', 'Option', 'Label', 'Fieldset', 'Legend',
|
|
13
|
+
'Details', 'Summary', 'Main', 'Header', 'Footer', 'Section', 'Article', 'Aside', 'Figure', 'Figcaption',
|
|
14
|
+
'Mark', 'Small', 'Iframe', 'Object', 'Embed', 'Param', 'Video', 'Audio', 'Source', 'Canvas', 'Svg', 'Math',
|
|
15
|
+
'Script', 'Noscript', 'Template', 'Slot']
|
|
16
16
|
|
|
17
17
|
# %% ../nbs/09_xml.ipynb #f6c9a7f5
|
|
18
18
|
from .utils import *
|
|
@@ -241,18 +241,6 @@ def dict2xml(d, do_escape=False, unwrap=None):
|
|
|
241
241
|
if first(d) in listify(unwrap): return first(d.values())
|
|
242
242
|
return to_xml(*[ft(k, str(v)) for k,v in d.items()], do_escape=do_escape)
|
|
243
243
|
|
|
244
|
-
# %% ../nbs/09_xml.ipynb #7b26e0f9
|
|
245
|
-
def item2xml(
|
|
246
|
-
typ, # Tag name: the cell or message type, e.g. 'code', 'markdown', 'raw', 'prompt'
|
|
247
|
-
content='', # The item's source text
|
|
248
|
-
out='', # Rendered output text
|
|
249
|
-
id=None, # Optional id attribute
|
|
250
|
-
**attrs, # Extra attributes; falsy values are dropped
|
|
251
|
-
):
|
|
252
|
-
"A notebook cell or dialog message as concise XML: content, then an `<out>` section when `out` is non-empty"
|
|
253
|
-
kw = {k:v for k,v in dict(id=id, **attrs).items() if v}
|
|
254
|
-
return ft(typ, content, ft('out', out), **kw) if out else ft(typ, content, **kw)
|
|
255
|
-
|
|
256
244
|
# %% ../nbs/09_xml.ipynb #5f0e91e0
|
|
257
245
|
def highlight(s, lang='html'):
|
|
258
246
|
"Markdown to syntax-highlight `s` in language `lang`"
|
|
@@ -22,7 +22,7 @@ Documentation = "https://fastcore.fast.ai/"
|
|
|
22
22
|
fastcore = "fastcore._modidx:d"
|
|
23
23
|
|
|
24
24
|
[project.entry-points.pyskills]
|
|
25
|
-
"fastcore.
|
|
25
|
+
"fastcore.editskill" = "fastcore.editskill"
|
|
26
26
|
|
|
27
27
|
[project.optional-dependencies]
|
|
28
28
|
dev = ['numpy', 'nbdev>=0.2.39', 'matplotlib', 'pillow', 'torch', 'pandas', 'nbclassic', 'pysym2md>=0.0.6', 'llms-txt', 'plum-dispatch', 'toolslm']
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.1.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|