Files
soleprint/soleprint/atlas2/docgen/cli/view.py

90 lines
3.6 KiB
Python

"""
python3 -m docgen view <ir.json> [views...] [-o out.json]
IR -> a smaller IR. Views compose, applied in a fixed order: overview, the drops,
only, subtree, around, depth. Each produces a document that still validates.
"""
import argparse
import json
from pathlib import Path
from .common import PROG, Abort, emit, read_json
def main(argv) -> int:
p = argparse.ArgumentParser(prog=f"{PROG} view")
p.add_argument("ir", type=Path)
p.add_argument("--output", "-o", type=Path)
p.add_argument("--overview", action="store_true",
help="The default view for this source type. Usually what you want.")
p.add_argument("--drop-stdlib", action="store_true", help="Remove stdlib externals.")
p.add_argument("--drop-builtins", action="store_true", help="Remove builtin externals.")
p.add_argument("--drop-external", action="store_true", help="Remove every unresolved name.")
p.add_argument("--only", action="append", default=[], help="Keep only this kind. Repeatable.")
p.add_argument("--drop", action="append", default=[], help="Remove this kind. Repeatable.")
p.add_argument("--subtree", help="Just this node id and its contents.")
p.add_argument("--around", help="This node id and its neighbours.")
p.add_argument("--hops", type=int, default=1)
p.add_argument("--depth", type=int, help="Collapse to this containment depth.")
p.add_argument("--split", action="store_true",
help="Write one document per subsystem into OUT/ (a directory).")
p.add_argument("--shape", action="store_true",
help="Report what this will look like, and write nothing.")
args = p.parse_args(argv)
from ..ir import check
from ..ops import filter as F
# Read without validating first: a view is often how an oversized document
# is made readable, and the result is validated before it is written.
ir = read_json(args.ir)
before = (len(ir["nodes"]), len(ir["edges"]))
if args.overview:
ir = F.overview(ir)
if args.drop_stdlib:
ir = F.drop_stdlib(ir)
if args.drop_builtins:
ir = F.drop_builtins(ir)
if args.drop_external:
ir = F.drop_external(ir)
if args.drop:
ir = F.drop_kinds(ir, args.drop)
if args.only:
ir = F.only_kinds(ir, args.only)
if args.subtree:
ir = F.subtree(ir, args.subtree)
if args.around:
ir = F.neighbourhood(ir, args.around, hops=args.hops)
if args.depth is not None:
ir = F.collapse_to_depth(ir, args.depth)
if args.shape:
for k, v in F.shape(ir).items():
print(f" {k:<14} {v}")
return 0
if args.split:
if not args.output:
raise Abort("--split needs -o DIRECTORY")
args.output.mkdir(parents=True, exist_ok=True)
for name, part in F.split(ir).items():
target = args.output / f"{name}.json"
target.write_text(json.dumps(part, indent=2) + "\n")
sh = F.shape(part)
print(f" {name:<16} {sh['nodes']:>4} nodes {sh['edges']:>4} edges -> {target}")
return 0
problems = check(ir)
if problems:
# A view that produces an invalid document is a bug in the view, and it
# must not be written out for an emitter to trip over later.
raise Abort(f"the view produced an invalid IR ({len(problems)}):\n "
+ "\n ".join(problems[:5]))
emit(json.dumps(ir, indent=2) + "\n", args.output,
f" {before[0]} nodes, {before[1]} edges -> "
f"{len(ir['nodes'])} nodes, {len(ir['edges'])} edges -> {args.output}")
return 0