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

119 lines
4.3 KiB
Python

"""
python3 -m docgen extract python --root SRC [-o ir.json] [--exclude NAME ...]
python3 -m docgen extract code --root SRC [-o ir.json] [--ext .cs ...]
python3 -m docgen extract db --schema schema.json [-o ir.json]
python3 -m docgen extract openapi --spec spec.yaml [-o ir.json]
python3 -m docgen extract usage --har session.har [-o ir.json]
Source -> IR, one reader per source type. Writes to stdout without `-o`, so it
composes with `view` and `emit`.
"""
import argparse
import json
from collections import Counter
from pathlib import Path
from .common import PROG, Abort, dump_ir
def _parser(reader: str, source_flag: str, help_: str) -> argparse.ArgumentParser:
p = argparse.ArgumentParser(prog=f"{PROG} extract {reader}")
p.add_argument(source_flag, "-s", required=True, type=Path, help=help_)
p.add_argument("--output", "-o", type=Path, help="Where to write. Default stdout.")
return p
def _counts(g) -> str:
c = Counter(n.kind for n in g.nodes)
return " · ".join(f"{v} {k}" for k, v in sorted(c.items(), key=lambda kv: -kv[1]))
def python(argv) -> int:
p = _parser("python", "--root", "Tree to read.")
p.add_argument("--exclude", action="append", default=[], help="Directory name to skip.")
args = p.parse_args(argv)
from ..extractors.python import extract
try:
g = extract(args.root, exclude=tuple(args.exclude))
except (NotADirectoryError, OSError) as e:
raise Abort(str(e)) from None
unparsed = sum(1 for n in g.nodes if n.attrs.get("error"))
dump_ir(g, args.output,
f"{len(g.nodes)} nodes, {len(g.edges)} edges -> {args.output}"
+ (f" ({unparsed} file(s) unparsed)" if unparsed else ""))
return 0
def code(argv) -> int:
p = _parser("code", "--root", "Tree to read.")
p.add_argument("--ext", action="append", default=[],
help="Limit to these extensions. Default: every registered one.")
p.add_argument("--exclude", action="append", default=[], help="Directory name to skip.")
args = p.parse_args(argv)
from ..extractors.code import MissingParser, extract
try:
g = extract(args.root, suffixes=args.ext or None, exclude=tuple(args.exclude))
except (MissingParser, NotADirectoryError, OSError) as e:
raise Abort(str(e)) from None
dump_ir(g, args.output, f"{len(g.nodes)} nodes -> {args.output} {_counts(g)}")
return 0
def db(argv) -> int:
args = _parser("db", "--schema",
"A graphgen-compatible schema.json, as modelgen emits.").parse_args(argv)
from ..extractors.db import extract
try:
g = extract(args.schema)
except (OSError, json.JSONDecodeError, KeyError) as e:
raise Abort(f"could not read {args.schema}: {e}") from None
dump_ir(g, args.output, f"{len(g.nodes)} nodes, {len(g.edges)} edges -> {args.output}")
return 0
def openapi(argv) -> int:
args = _parser("openapi", "--spec", "An OpenAPI document.").parse_args(argv)
from ..extractors.openapi import extract
try:
g = extract(args.spec)
except (OSError, ImportError, ValueError) as e:
raise Abort(str(e)) from None
eps = sum(1 for n in g.nodes if n.kind == "endpoint")
dump_ir(g, args.output,
f"{len(g.nodes)} nodes ({eps} endpoints), {len(g.edges)} edges -> {args.output}")
return 0
def usage(argv) -> int:
args = _parser("usage", "--har",
"A HAR recording, as devtools/mitmproxy/Charles export.").parse_args(argv)
from ..extractors.usage import extract
try:
g = extract(args.har)
except (OSError, json.JSONDecodeError, KeyError) as e:
raise Abort(f"could not read {args.har}: {e}") from None
eps = sum(1 for n in g.nodes if n.kind == "endpoint")
ops = sum(1 for n in g.nodes if n.kind == "operation")
dump_ir(g, args.output,
f"{len(g.nodes)} nodes ({eps} endpoints, {ops} graphql), "
f"{len(g.edges)} sequence edges -> {args.output}")
return 0
READERS = {"python": python, "code": code, "db": db, "openapi": openapi, "usage": usage}
def main(argv) -> int:
if not argv or argv[0] in ("-h", "--help"):
print(__doc__.strip("\n"))
return 0 if argv else 2
if argv[0] not in READERS:
raise Abort(f"no reader {argv[0]!r} — have: {', '.join(READERS)}")
return READERS[argv[0]](argv[1:])