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.
Files changed (43) hide show
  1. {fastcore-2.1.2/fastcore.egg-info → fastcore-2.1.4}/PKG-INFO +1 -1
  2. fastcore-2.1.4/fastcore/__init__.py +1 -0
  3. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/_modidx.py +25 -7
  4. fastcore-2.1.4/fastcore/editskill.py +63 -0
  5. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/nbio.py +159 -23
  6. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/tools.py +39 -12
  7. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/xml.py +8 -20
  8. {fastcore-2.1.2 → fastcore-2.1.4/fastcore.egg-info}/PKG-INFO +1 -1
  9. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore.egg-info/SOURCES.txt +1 -0
  10. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore.egg-info/entry_points.txt +1 -1
  11. {fastcore-2.1.2 → fastcore-2.1.4}/pyproject.toml +1 -1
  12. fastcore-2.1.2/fastcore/__init__.py +0 -1
  13. {fastcore-2.1.2 → fastcore-2.1.4}/CONTRIBUTING.md +0 -0
  14. {fastcore-2.1.2 → fastcore-2.1.4}/LICENSE +0 -0
  15. {fastcore-2.1.2 → fastcore-2.1.4}/MANIFEST.in +0 -0
  16. {fastcore-2.1.2 → fastcore-2.1.4}/README.md +0 -0
  17. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/aio.py +0 -0
  18. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/all.py +0 -0
  19. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/ansi.py +0 -0
  20. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/basics.py +0 -0
  21. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/dispatch.py +0 -0
  22. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/docments.py +0 -0
  23. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/docscrape.py +0 -0
  24. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/foundation.py +0 -0
  25. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/imghdr.py +0 -0
  26. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/imports.py +0 -0
  27. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/meta.py +0 -0
  28. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/nb_imports.py +0 -0
  29. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/net.py +0 -0
  30. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/parallel.py +0 -0
  31. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/py2pyi.py +0 -0
  32. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/script.py +0 -0
  33. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/shutil.py +0 -0
  34. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/style.py +0 -0
  35. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/test.py +0 -0
  36. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/transform.py +0 -0
  37. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/utils.py +0 -0
  38. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/xdg.py +0 -0
  39. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore/xtras.py +0 -0
  40. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore.egg-info/dependency_links.txt +0 -0
  41. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore.egg-info/requires.txt +0 -0
  42. {fastcore-2.1.2 → fastcore-2.1.4}/fastcore.egg-info/top_level.txt +0 -0
  43. {fastcore-2.1.2 → fastcore-2.1.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastcore
3
- Version: 2.1.2
3
+ Version: 2.1.4
4
4
  Summary: Python supercharged for fastai development
5
5
  Author-email: Jeremy Howard and Sylvain Gugger <infos@fast.ai>
6
6
  License: Apache-2.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.NbCell': ('nbio.html#nbcell', 'fastcore/nbio.py'),
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.find': ('nbio.html#notebook.find', 'fastcore/nbio.py'),
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.view': ('nbio.html#notebook.view', 'fastcore/nbio.py'),
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', 'nb_lang', 'NbCell', 'dict2nb', 'read_nb', 'mk_cell', 'new_nb', 'first_code_ln', 'nb2dict', 'nb2str',
9
- 'write_nb', 'validate_cell', 'validate_nb', 'repair_cell', 'repair_nb', 'preferred_out', 'mk_stream',
10
- 'mk_result', 'mk_display', 'mk_error', 'concat_streams', 'preferred_msg_out', 'render_output',
11
- 'render_outputs', 'render_text', 'cell2xml', 'cells2xml', 'Notebook']
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(cell):
154
- "Directives from the `nbdev` dict in cell metadata, as `{name: value}`"
155
- def _v(v):
156
- if isinstance(v,bool): return '' if v else 'false'
157
- if isinstance(v,(list,tuple)): return ' '.join(map(str,v))
158
- return '' if v=='true' else str(v)
159
- return {k:_v(v) for k,v in cell.get('metadata',{}).get('nbdev',{}).items()}
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=='': return True
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
- if isinstance(nb, (AttrDict,list)): nb = nb2dict(nb)
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,item2xml
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 find(self:Notebook, pat, cell_type=None):
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 view(self:Notebook, id, nums=True):
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
- file_view('~/a/b.py', 3)
8
- file_create('~/a/b/c.py', 'content here')
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 `ast_file(path, repls)` apply ast-grep `(pattern, replacement)` rules with `$VAR` metavariables (requires the optional `remold` package). Where the `exhash` package is available, prefer it for editing: its hash-verified addressing fails loudly on stale context instead of editing nearby text.
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', 'ast_file',
21
- 'insert_line', 'str_replace', 'strs_replace', 'replace_lines', 'del_lines', 'file_view', 'file_create',
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 file_view(
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}: {l}' for i,l in enumerate(lines[start_line-1:end_line], start_line)))
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 file_create(
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
- ast_file = file_edit(ast_replace, 'ast_file')
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', 'item2xml', 'highlight', 'showtags',
9
- 'mk_getattr', 'Head', 'Title', 'Meta', 'Link', 'Style', 'Body', 'Pre', 'Code', 'Div', 'Span', 'P', 'H1',
10
- 'H2', 'H3', 'H4', 'H5', 'H6', 'Strong', 'Em', 'B', 'I', 'U', 'S', 'Strike', 'Sub', 'Sup', 'Hr', 'Br', 'Img',
11
- 'A', 'Nav', 'Ul', 'Ol', 'Li', 'Dl', 'Dt', 'Dd', 'Table', 'Thead', 'Tbody', 'Tfoot', 'Tr', 'Th', 'Td',
12
- 'Caption', 'Col', 'Colgroup', 'Form', 'Input', 'Textarea', 'Button', 'Select', 'Option', 'Label', 'Fieldset',
13
- 'Legend', 'Details', 'Summary', 'Main', 'Header', 'Footer', 'Section', 'Article', 'Aside', 'Figure',
14
- 'Figcaption', 'Mark', 'Small', 'Iframe', 'Object', 'Embed', 'Param', 'Video', 'Audio', 'Source', 'Canvas',
15
- 'Svg', 'Math', 'Script', 'Noscript', 'Template', 'Slot']
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`"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastcore
3
- Version: 2.1.2
3
+ Version: 2.1.4
4
4
  Summary: Python supercharged for fastai development
5
5
  Author-email: Jeremy Howard and Sylvain Gugger <infos@fast.ai>
6
6
  License: Apache-2.0
@@ -12,6 +12,7 @@ fastcore/basics.py
12
12
  fastcore/dispatch.py
13
13
  fastcore/docments.py
14
14
  fastcore/docscrape.py
15
+ fastcore/editskill.py
15
16
  fastcore/foundation.py
16
17
  fastcore/imghdr.py
17
18
  fastcore/imports.py
@@ -6,4 +6,4 @@ replace_wildcards = fastcore.py2pyi:replace_wildcards
6
6
  fastcore = fastcore._modidx:d
7
7
 
8
8
  [pyskills]
9
- fastcore.tools = fastcore.tools
9
+ fastcore.editskill = fastcore.editskill
@@ -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.tools" = "fastcore.tools"
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