nbdevAuto 0.3.75__tar.gz → 0.3.77__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.
- {nbdevauto-0.3.75/nbdevAuto.egg-info → nbdevauto-0.3.77}/PKG-INFO +1 -1
- nbdevauto-0.3.77/nbdevAuto/__init__.py +1 -0
- {nbdevauto-0.3.75 → nbdevauto-0.3.77}/nbdevAuto/_modidx.py +7 -0
- {nbdevauto-0.3.75 → nbdevauto-0.3.77}/nbdevAuto/fleet.py +204 -12
- {nbdevauto-0.3.75 → nbdevauto-0.3.77/nbdevAuto.egg-info}/PKG-INFO +1 -1
- nbdevauto-0.3.75/nbdevAuto/__init__.py +0 -1
- {nbdevauto-0.3.75 → nbdevauto-0.3.77}/LICENSE +0 -0
- {nbdevauto-0.3.75 → nbdevauto-0.3.77}/MANIFEST.in +0 -0
- {nbdevauto-0.3.75 → nbdevauto-0.3.77}/README.md +0 -0
- {nbdevauto-0.3.75 → nbdevauto-0.3.77}/nbdevAuto/automate.py +0 -0
- {nbdevauto-0.3.75 → nbdevauto-0.3.77}/nbdevAuto/core.py +0 -0
- {nbdevauto-0.3.75 → nbdevauto-0.3.77}/nbdevAuto/functions.py +0 -0
- {nbdevauto-0.3.75 → nbdevauto-0.3.77}/nbdevAuto/github.py +0 -0
- {nbdevauto-0.3.75 → nbdevauto-0.3.77}/nbdevAuto/pdf.py +0 -0
- {nbdevauto-0.3.75 → nbdevauto-0.3.77}/nbdevAuto.egg-info/SOURCES.txt +0 -0
- {nbdevauto-0.3.75 → nbdevauto-0.3.77}/nbdevAuto.egg-info/dependency_links.txt +0 -0
- {nbdevauto-0.3.75 → nbdevauto-0.3.77}/nbdevAuto.egg-info/entry_points.txt +0 -0
- {nbdevauto-0.3.75 → nbdevauto-0.3.77}/nbdevAuto.egg-info/requires.txt +0 -0
- {nbdevauto-0.3.75 → nbdevauto-0.3.77}/nbdevAuto.egg-info/top_level.txt +0 -0
- {nbdevauto-0.3.75 → nbdevauto-0.3.77}/pyproject.toml +0 -0
- {nbdevauto-0.3.75 → nbdevauto-0.3.77}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.77"
|
|
@@ -47,7 +47,9 @@ d = { 'settings': { 'branch': 'main',
|
|
|
47
47
|
'nbdevAuto.fleet._hits': ('fleet.html#_hits', 'nbdevAuto/fleet.py'),
|
|
48
48
|
'nbdevAuto.fleet._line_of': ('fleet.html#_line_of', 'nbdevAuto/fleet.py'),
|
|
49
49
|
'nbdevAuto.fleet._porcelain': ('fleet.html#_porcelain', 'nbdevAuto/fleet.py'),
|
|
50
|
+
'nbdevAuto.fleet._public_notebooks': ('fleet.html#_public_notebooks', 'nbdevAuto/fleet.py'),
|
|
50
51
|
'nbdevAuto.fleet._render': ('fleet.html#_render', 'nbdevAuto/fleet.py'),
|
|
52
|
+
'nbdevAuto.fleet._src': ('fleet.html#_src', 'nbdevAuto/fleet.py'),
|
|
51
53
|
'nbdevAuto.fleet.ahead_of_remote': ('fleet.html#ahead_of_remote', 'nbdevAuto/fleet.py'),
|
|
52
54
|
'nbdevAuto.fleet.copy_one': ('fleet.html#copy_one', 'nbdevAuto/fleet.py'),
|
|
53
55
|
'nbdevAuto.fleet.dirty_submodules': ('fleet.html#dirty_submodules', 'nbdevAuto/fleet.py'),
|
|
@@ -55,6 +57,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
55
57
|
'nbdevAuto.fleet.finish': ('fleet.html#finish', 'nbdevAuto/fleet.py'),
|
|
56
58
|
'nbdevAuto.fleet.fleet': ('fleet.html#fleet', 'nbdevAuto/fleet.py'),
|
|
57
59
|
'nbdevAuto.fleet.fleet_copy': ('fleet.html#fleet_copy', 'nbdevAuto/fleet.py'),
|
|
60
|
+
'nbdevAuto.fleet.fleet_nblint': ('fleet.html#fleet_nblint', 'nbdevAuto/fleet.py'),
|
|
58
61
|
'nbdevAuto.fleet.fleet_push': ('fleet.html#fleet_push', 'nbdevAuto/fleet.py'),
|
|
59
62
|
'nbdevAuto.fleet.fleet_scan': ('fleet.html#fleet_scan', 'nbdevAuto/fleet.py'),
|
|
60
63
|
'nbdevAuto.fleet.fleet_status': ('fleet.html#fleet_status', 'nbdevAuto/fleet.py'),
|
|
@@ -64,6 +67,9 @@ d = { 'settings': { 'branch': 'main',
|
|
|
64
67
|
'nbdevAuto.fleet.format_summary': ('fleet.html#format_summary', 'nbdevAuto/fleet.py'),
|
|
65
68
|
'nbdevAuto.fleet.init_new': ('fleet.html#init_new', 'nbdevAuto/fleet.py'),
|
|
66
69
|
'nbdevAuto.fleet.is_published': ('fleet.html#is_published', 'nbdevAuto/fleet.py'),
|
|
70
|
+
'nbdevAuto.fleet.lint_notebook': ('fleet.html#lint_notebook', 'nbdevAuto/fleet.py'),
|
|
71
|
+
'nbdevAuto.fleet.lint_one': ('fleet.html#lint_one', 'nbdevAuto/fleet.py'),
|
|
72
|
+
'nbdevAuto.fleet.lint_totals': ('fleet.html#lint_totals', 'nbdevAuto/fleet.py'),
|
|
67
73
|
'nbdevAuto.fleet.looks_secret': ('fleet.html#looks_secret', 'nbdevAuto/fleet.py'),
|
|
68
74
|
'nbdevAuto.fleet.notebook_text': ('fleet.html#notebook_text', 'nbdevAuto/fleet.py'),
|
|
69
75
|
'nbdevAuto.fleet.parse_verb': ('fleet.html#parse_verb', 'nbdevAuto/fleet.py'),
|
|
@@ -74,6 +80,7 @@ d = { 'settings': { 'branch': 'main',
|
|
|
74
80
|
'nbdevAuto.fleet.scan_one': ('fleet.html#scan_one', 'nbdevAuto/fleet.py'),
|
|
75
81
|
'nbdevAuto.fleet.script_path': ('fleet.html#script_path', 'nbdevAuto/fleet.py'),
|
|
76
82
|
'nbdevAuto.fleet.status_one': ('fleet.html#status_one', 'nbdevAuto/fleet.py'),
|
|
83
|
+
'nbdevAuto.fleet.unlinked_from_index': ('fleet.html#unlinked_from_index', 'nbdevAuto/fleet.py'),
|
|
77
84
|
'nbdevAuto.fleet.update_one': ('fleet.html#update_one', 'nbdevAuto/fleet.py'),
|
|
78
85
|
'nbdevAuto.fleet.upload_one': ('fleet.html#upload_one', 'nbdevAuto/fleet.py'),
|
|
79
86
|
'nbdevAuto.fleet.usage': ('fleet.html#usage', 'nbdevAuto/fleet.py'),
|
|
@@ -6,11 +6,13 @@ Docs: https://bthek1.github.io/nbdevAuto/fleet.html.md"""
|
|
|
6
6
|
|
|
7
7
|
# %% auto #0
|
|
8
8
|
__all__ = ['RAW_DOC_SUFFIXES', 'KNOWN_TOKEN', 'SECRET_PATTERNS', 'ALLOW', 'RENDERED_SUFFIXES', 'TEXT_SUFFIXES', 'EMAIL',
|
|
9
|
-
'RESERVED_DOMAINS', 'BULK_EMAIL_MIN', '
|
|
10
|
-
'
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
'
|
|
9
|
+
'RESERVED_DOMAINS', 'BULK_EMAIL_MIN', 'NB_RULES', 'DLTASK_SECTIONS', 'DLTASK_NOT_TASKS', 'LARGE_NB_BYTES',
|
|
10
|
+
'NOT_PUBLISHED', 'VERBS', 'Skip', 'Outcome', 'Report', 'format_summary', 'walk', 'finish', 'script_path',
|
|
11
|
+
'upload_one', 'fleet_upload', 'ahead_of_remote', 'push_one', 'copy_one', 'fleet_push', 'fleet_copy',
|
|
12
|
+
'status_one', 'fleet_status', 'fetch_all', 'update_one', 'dirty_submodules', 'init_new', 'pull_root',
|
|
13
|
+
'fleet_update', 'fleet_sync', 'looks_secret', 'notebook_text', 'is_published', 'scan_file', 'scan_one',
|
|
14
|
+
'fleet_scan', 'lint_notebook', 'unlinked_from_index', 'lint_one', 'lint_totals', 'fleet_nblint',
|
|
15
|
+
'parse_verb', 'usage', 'run_verb', 'fleet']
|
|
14
16
|
|
|
15
17
|
# %% ../nbs/04_Fleet.ipynb #d361ee5f-9f09-464f-aec6-0e27cec90fe9
|
|
16
18
|
import json
|
|
@@ -21,6 +23,7 @@ import sys
|
|
|
21
23
|
from concurrent.futures import ThreadPoolExecutor
|
|
22
24
|
from dataclasses import dataclass, field
|
|
23
25
|
from pathlib import Path
|
|
26
|
+
from urllib.parse import unquote
|
|
24
27
|
|
|
25
28
|
from fastcore.script import *
|
|
26
29
|
|
|
@@ -354,13 +357,20 @@ def init_new(root=None, dry_run=False):
|
|
|
354
357
|
return new
|
|
355
358
|
|
|
356
359
|
def pull_root(base):
|
|
357
|
-
"Fast-forward the superproject itself, with the advice its
|
|
360
|
+
"Fast-forward the superproject itself, with the advice its failure actually needs"
|
|
358
361
|
try: return _run("git", "-C", str(base), "pull", "--ff-only").strip().splitlines()[-1:]
|
|
359
362
|
except StepFailed as e:
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
"
|
|
363
|
+
# Two failures, two fixes. Discarding gitlinks does nothing for a diverged branch,
|
|
364
|
+
# and a rebase does nothing for drifted gitlinks.
|
|
365
|
+
if "Not possible to fast-forward" in e.output:
|
|
366
|
+
hint = ("Local main and origin/main both have new commits.\n"
|
|
367
|
+
"Replay yours on top of the remote, then re-run:\n"
|
|
368
|
+
" git pull --rebase && just sync")
|
|
369
|
+
else:
|
|
370
|
+
hint = ("If it complains about submodule paths, the recorded gitlinks drifted.\n"
|
|
371
|
+
"Discard them (the update step re-advances them anyway):\n"
|
|
372
|
+
" git checkout -- $(git diff --name-only)")
|
|
373
|
+
raise StepFailed(e.cmd, e.code, f"{e.output}\n{hint}") from None
|
|
364
374
|
|
|
365
375
|
# %% ../nbs/04_Fleet.ipynb #8fb7bc91-d79a-4c1a-bb81-e28b3a77b118
|
|
366
376
|
def fleet_update(
|
|
@@ -568,10 +578,187 @@ def fleet_scan(
|
|
|
568
578
|
sys.exit(1 if published or (strict and rep.ran) else 0)
|
|
569
579
|
|
|
570
580
|
|
|
581
|
+
# %% ../nbs/04_Fleet.ipynb #7a605ea9
|
|
582
|
+
# (key, label, issue #24 phase). The phase is what a count is FOR: each phase of the
|
|
583
|
+
# cleanup drives its own rows to 0, so the table doubles as the plan's progress bar.
|
|
584
|
+
NB_RULES = [
|
|
585
|
+
("mermaid_plain", "Plain ```mermaid fence (raw source on the site)", 1),
|
|
586
|
+
("title_leading_rule","Title cell starts with --- (breaks nbdev-test)", 1),
|
|
587
|
+
("title_no_h1", "Title cell has no # H1", 1),
|
|
588
|
+
("index_unlinked", "Public notebook missing from index.ipynb", 1),
|
|
589
|
+
("mermaid_braced", "Braced {mermaid} not yet an .svg file", 2),
|
|
590
|
+
("svg_in_index", "Inline <svg> in index.ipynb (README strips it)", 2),
|
|
591
|
+
("title_code", "Title cell is a code cell", 3),
|
|
592
|
+
("title_no_desc", "Title cell has no > description", 3),
|
|
593
|
+
("no_skip_showdoc", "Concept notebook without skip_showdoc", 3),
|
|
594
|
+
("non_ascii", "Non-ASCII characters in code cells", 4),
|
|
595
|
+
("naive_datetime", "Naive now() or utcnow(), no timezone", 4),
|
|
596
|
+
("bad_filename", "Filename with a space or &", 4),
|
|
597
|
+
("dl_matplotlib", "DL_methods chart in matplotlib", 5),
|
|
598
|
+
("dltask_sections", "DL_tasks notebook missing a required section", 5),
|
|
599
|
+
("dltask_no_require", "DL_tasks live-capture demo without require(...)", 5),
|
|
600
|
+
("stale_outputs", "skip_exec notebook carrying outputs", 6),
|
|
601
|
+
("large_file", "Notebook over 3 MB", 6),
|
|
602
|
+
("multi_h2", "Markdown cell with several ## sections", 7),
|
|
603
|
+
("no_trailing_rule", "## cell without a trailing ---", 7),
|
|
604
|
+
]
|
|
605
|
+
# Matched case-insensitively against the `##` headings, as substrings, so "Datasets"
|
|
606
|
+
# also accepts "Datasets and splits". From dl-task-notebook-format.instructions.md.
|
|
607
|
+
DLTASK_SECTIONS = ["What is", "Real-world use cases", "Evaluation", "Datasets",
|
|
608
|
+
"Setup", "benchmark", "Common frameworks", "Going further"]
|
|
609
|
+
DLTASK_NOT_TASKS = ("Theory", "Frameworks")
|
|
610
|
+
LARGE_NB_BYTES = 3_000_000
|
|
611
|
+
# Never a site, so never held to the publishing standard (see special-submodules).
|
|
612
|
+
NOT_PUBLISHED = ("Personal/bthek1_career",)
|
|
613
|
+
|
|
614
|
+
def _src(c):
|
|
615
|
+
s = c.get("source", "")
|
|
616
|
+
return "".join(s) if isinstance(s, list) else s
|
|
617
|
+
|
|
618
|
+
def lint_notebook(path, sub=""):
|
|
619
|
+
"""{rule key: count} for one notebook. A count is the number of offending items
|
|
620
|
+
(diagrams, characters, cells); 1 for a yes/no rule. Empty means it meets the standard."""
|
|
621
|
+
out = {}
|
|
622
|
+
name = path.name
|
|
623
|
+
if " " in name or "&" in name: out["bad_filename"] = 1
|
|
624
|
+
try: nb = json.loads(path.read_text(encoding="utf-8"))
|
|
625
|
+
except Exception: return out
|
|
626
|
+
cells = nb.get("cells", [])
|
|
627
|
+
if not cells: return out
|
|
628
|
+
code = [_src(c) for c in cells if c.get("cell_type") == "code"]
|
|
629
|
+
md = [_src(c) for c in cells if c.get("cell_type") == "markdown"]
|
|
630
|
+
allcode, allmd = "\n".join(code), "\n".join(md)
|
|
631
|
+
exports = re.search(r"^#\|\s*(export|exporti|exports|default_exp)\b", allcode, re.M)
|
|
632
|
+
first = _src(cells[0]).strip()
|
|
633
|
+
|
|
634
|
+
if cells[0].get("cell_type") != "markdown": out["title_code"] = 1
|
|
635
|
+
else:
|
|
636
|
+
if first.startswith("---"): out["title_leading_rule"] = 1
|
|
637
|
+
if not re.match(r"^(---[\s\S]*?---\s*)?#\s+\S", first): out["title_no_h1"] = 1
|
|
638
|
+
if not re.search(r"^>\s*\S", first, re.M): out["title_no_desc"] = 1
|
|
639
|
+
if not exports and "skip_showdoc: true" not in first: out["no_skip_showdoc"] = 1
|
|
640
|
+
|
|
641
|
+
# The title cell is checked above; a `##` packed into it is a title problem first.
|
|
642
|
+
multi = sum(len(re.findall(r"^##\s", m, re.M)) > 1 for m in md[1:])
|
|
643
|
+
trail = sum(bool(re.search(r"^##\s", m, re.M)) and not m.rstrip().endswith("---")
|
|
644
|
+
for m in md[1:])
|
|
645
|
+
if multi: out["multi_h2"] = multi
|
|
646
|
+
if trail: out["no_trailing_rule"] = trail
|
|
647
|
+
|
|
648
|
+
plain = len(re.findall(r"^```mermaid\s*$", allmd, re.M))
|
|
649
|
+
brace = len(re.findall(r"^```\{mermaid\}", allmd, re.M))
|
|
650
|
+
if plain: out["mermaid_plain"] = plain
|
|
651
|
+
if brace: out["mermaid_braced"] = brace
|
|
652
|
+
if name == "index.ipynb" and re.search(r"<svg[\s>]", allmd): out["svg_in_index"] = 1
|
|
653
|
+
|
|
654
|
+
nonascii = sum(ord(ch) > 127 for ch in allcode)
|
|
655
|
+
if nonascii: out["non_ascii"] = nonascii
|
|
656
|
+
if re.search(r"datetime\.utcnow\(|\b(?:dt|datetime)\.now\(\s*\)", allcode):
|
|
657
|
+
out["naive_datetime"] = 1
|
|
658
|
+
|
|
659
|
+
if sub == "DL/DL_methods" and re.search(
|
|
660
|
+
r"^\s*(import|from)\s+(matplotlib|seaborn|plotly)", allcode, re.M):
|
|
661
|
+
out["dl_matplotlib"] = 1
|
|
662
|
+
# Only a real task notebook: not the index, a library notebook, a stub, or the
|
|
663
|
+
# Theory/ and Frameworks/ reference pages, which have no task to benchmark.
|
|
664
|
+
if (sub == "DL/DL_tasks" and name != "index.ipynb" and not exports and len(cells) > 3
|
|
665
|
+
and path.parent.name not in DLTASK_NOT_TASKS):
|
|
666
|
+
heads = "\n".join(re.findall(r"^##\s+.*$", allmd, re.M)).lower()
|
|
667
|
+
missing = [s for s in DLTASK_SECTIONS if s.lower() not in heads]
|
|
668
|
+
if missing: out["dltask_sections"] = len(missing)
|
|
669
|
+
if re.search(r"cv2\.VideoCapture|sounddevice|pyaudio", allcode) and "require(" not in allcode:
|
|
670
|
+
out["dltask_no_require"] = 1
|
|
671
|
+
|
|
672
|
+
# DL notebooks keep outputs on purpose (benchmarks, demos), so only elsewhere is a
|
|
673
|
+
# skip_exec notebook with outputs a sign of stale results.
|
|
674
|
+
outs = sum(len(c.get("outputs", [])) for c in cells if c.get("cell_type") == "code")
|
|
675
|
+
if outs and "skip_exec: true" in first and not sub.startswith("DL/"):
|
|
676
|
+
out["stale_outputs"] = outs
|
|
677
|
+
if path.stat().st_size > LARGE_NB_BYTES: out["large_file"] = 1
|
|
678
|
+
return out
|
|
679
|
+
|
|
680
|
+
def _public_notebooks(nbs):
|
|
681
|
+
"Every published notebook under `nbs`, relative paths. `_`/`.` dirs are build output"
|
|
682
|
+
found = []
|
|
683
|
+
for p in sorted(nbs.rglob("*.ipynb")):
|
|
684
|
+
rel = p.relative_to(nbs)
|
|
685
|
+
if any(part.startswith((".", "_")) for part in rel.parts[:-1]): continue
|
|
686
|
+
if p.name.lower().startswith("p_"): continue
|
|
687
|
+
found.append(rel)
|
|
688
|
+
return found
|
|
689
|
+
|
|
690
|
+
def unlinked_from_index(nbs, public=None):
|
|
691
|
+
"Public notebooks (as posix paths) that `nbs/index.ipynb` does not link to"
|
|
692
|
+
idx = nbs/"index.ipynb"
|
|
693
|
+
if not idx.exists(): return []
|
|
694
|
+
public = public if public is not None else _public_notebooks(nbs)
|
|
695
|
+
body = "".join(_src(c) for c in json.loads(idx.read_text(encoding="utf-8")).get("cells", []))
|
|
696
|
+
links = {unquote(l).split("#")[0] for l in re.findall(r"\]\(([^)\s]+)\)", body)
|
|
697
|
+
if not l.startswith(("http", "#"))}
|
|
698
|
+
return [r.as_posix() for r in public
|
|
699
|
+
if r.name != "index.ipynb" and r.as_posix() not in links]
|
|
700
|
+
|
|
701
|
+
def lint_one(path, sub=None, nbs="nbs"):
|
|
702
|
+
"{notebook path relative to the superproject: {rule key: count}} for one submodule"
|
|
703
|
+
name = sub.path if sub else path.name
|
|
704
|
+
root = path/nbs
|
|
705
|
+
if name in NOT_PUBLISHED: raise Skip("not published")
|
|
706
|
+
if not root.is_dir(): raise Skip("without nbs")
|
|
707
|
+
public = _public_notebooks(root)
|
|
708
|
+
found = {}
|
|
709
|
+
for rel in public:
|
|
710
|
+
hits = lint_notebook(root/rel, name)
|
|
711
|
+
if hits: found[f"{name}/{nbs}/{rel.as_posix()}"] = hits
|
|
712
|
+
for rel in unlinked_from_index(root, public):
|
|
713
|
+
found.setdefault(f"{name}/{nbs}/{rel}", {})["index_unlinked"] = 1
|
|
714
|
+
return found
|
|
715
|
+
|
|
716
|
+
def lint_totals(found):
|
|
717
|
+
"{rule key: (notebooks, items)} over the output of `lint_one`, every rule present"
|
|
718
|
+
tot = {k: [0, 0] for k, _, _ in NB_RULES}
|
|
719
|
+
for hits in found.values():
|
|
720
|
+
for k, n in hits.items(): tot[k][0] += 1; tot[k][1] += n
|
|
721
|
+
return {k: tuple(v) for k, v in tot.items()}
|
|
722
|
+
|
|
723
|
+
def fleet_nblint(
|
|
724
|
+
detail:bool = False, # List the offending notebooks under each rule
|
|
725
|
+
phase:int = None, # Only the rules for this phase of issue #24
|
|
726
|
+
strict:bool = False, # Exit 1 when any rule is above 0
|
|
727
|
+
root:str = None, # Superproject root (default: nearest ancestor with .gitmodules)
|
|
728
|
+
):
|
|
729
|
+
"Count public notebooks that break the notebook standard, one row per rule"
|
|
730
|
+
from rich.table import Table
|
|
731
|
+
found = {}
|
|
732
|
+
def _collect(p, s):
|
|
733
|
+
found.update(lint_one(p, s))
|
|
734
|
+
raise Skip("linted") # counted below in one table, not narrated per submodule
|
|
735
|
+
rep = walk(_collect, root=root, workers=8, style="line")
|
|
736
|
+
if rep.failed: sys.exit(finish(rep, "linted"))
|
|
737
|
+
rules = [r for r in NB_RULES if phase is None or r[2] == phase]
|
|
738
|
+
tot = lint_totals(found)
|
|
739
|
+
t = Table(title="Notebook standard (issue #24)", title_justify="left")
|
|
740
|
+
for col, kw in (("Phase", {"justify": "right"}), ("Rule", {}),
|
|
741
|
+
("Notebooks", {"justify": "right"}), ("Items", {"justify": "right"})):
|
|
742
|
+
t.add_column(col, **kw)
|
|
743
|
+
for key, label, ph in rules:
|
|
744
|
+
nbks, items = tot[key]
|
|
745
|
+
style = "green" if nbks == 0 else None
|
|
746
|
+
t.add_row(str(ph), escape(label), str(nbks), str(items), style=style)
|
|
747
|
+
console.print(t)
|
|
748
|
+
left = sum(tot[k][0] for k, _, _ in rules)
|
|
749
|
+
console.print(f"[bold]{len(found)} notebooks flagged, {left} rule hits in view[/bold]")
|
|
750
|
+
if detail:
|
|
751
|
+
for key, label, _ in rules:
|
|
752
|
+
hits = sorted((p, h[key]) for p, h in found.items() if key in h)
|
|
753
|
+
if not hits: continue
|
|
754
|
+
console.print(f"\n[bold cyan]{escape(label)}[/bold cyan]")
|
|
755
|
+
for p, n in hits: console.print(f" {escape(p)} [dim]({n})[/dim]")
|
|
756
|
+
sys.exit(1 if strict and left else 0)
|
|
757
|
+
|
|
571
758
|
# %% ../nbs/04_Fleet.ipynb #0281bd2b-91ba-4e49-90dc-d1a95d54d5a6
|
|
572
759
|
VERBS = {"upload": fleet_upload, "push": fleet_push, "copy": fleet_copy,
|
|
573
760
|
"status": fleet_status, "update": fleet_update, "sync": fleet_sync,
|
|
574
|
-
"scan": fleet_scan}
|
|
761
|
+
"scan": fleet_scan, "nblint": fleet_nblint}
|
|
575
762
|
|
|
576
763
|
def parse_verb(argv):
|
|
577
764
|
"(verb, remaining args) for `fleet ...`, or None when `argv` does not name a verb"
|
|
@@ -608,7 +795,12 @@ def fleet():
|
|
|
608
795
|
if not picked:
|
|
609
796
|
print(usage())
|
|
610
797
|
sys.exit(0 if sys.argv[1:2] in ([], ["-h"], ["--help"]) else 2)
|
|
611
|
-
|
|
798
|
+
# `_step` has already printed the failing command's output, so a traceback on top of
|
|
799
|
+
# it only buries the hint. One line and the exit code are all that is left to say.
|
|
800
|
+
try: return run_verb(*picked)
|
|
801
|
+
except StepFailed as e:
|
|
802
|
+
console.print(f"[bold red]FAILED[/bold red] [dim]{escape(str(e))}[/dim]")
|
|
803
|
+
sys.exit(1)
|
|
612
804
|
|
|
613
805
|
# %% ../nbs/04_Fleet.ipynb #7adb89c6-6788-4241-acff-53cb4566b5ce
|
|
614
806
|
if __name__ == "__main__": fleet()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.75"
|
|
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
|