119 lines
4.3 KiB
Python
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:])
|