1
0
Fork 0
Code Issues Pull requests Projects Releases 2 Packages Wiki Activity Actions Pages
DocForge2/src/docforge/cli.py

135 lines
5.1 KiB
Python
Raw Normal View History

"""Deterministic JSON CLI for inspection and explicit derived-output integration."""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from .context import compile_context
from .errors import DocForgeError
from .index import ProjectIndex
from .project import Project, project_root_fingerprint
from .rendering import RenderService
def _parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="docforge")
parser.add_argument("--project-root", type=Path, required=True)
commands = parser.add_subparsers(dest="command", required=True)
commands.add_parser("info")
commands.add_parser("validate")
commands.add_parser("build")
commands.add_parser("check")
commands.add_parser("validate-index")
show = commands.add_parser("show")
show.add_argument("node_id")
search = commands.add_parser("search")
search.add_argument("query")
search.add_argument("--limit", type=int)
filter_command = commands.add_parser("filter")
filter_command.add_argument("--family")
filter_command.add_argument("--authority")
filter_command.add_argument("--status")
filter_command.add_argument("--tag")
filter_command.add_argument("--limit", type=int)
for name in ("backlinks", "dependencies", "impact"):
command = commands.add_parser(name)
command.add_argument("node_id")
if name == "backlinks":
command.add_argument("--relation")
else:
command.add_argument("--depth", type=int, default=2)
context = commands.add_parser("context")
context.add_argument("profile")
context.add_argument("--budget", type=int)
render = commands.add_parser("render")
render.add_argument("view_id")
render_status = commands.add_parser("render-status")
render_status.add_argument("view_id", nargs="?")
preview = commands.add_parser("preview")
preview.add_argument("changeset_id")
preview.add_argument("view_id")
return parser
def _run(arguments: argparse.Namespace) -> dict[str, object]:
project = Project.open(arguments.project_root)
index = ProjectIndex(project)
if arguments.command == "info":
snapshot = project.load()
return {
"status": "ok",
"project_id": snapshot.descriptor.project_id,
"project_root_fingerprint": project_root_fingerprint(snapshot.descriptor.root),
"title": snapshot.descriptor.title,
"adapter": snapshot.descriptor.adapter,
"revision": snapshot.revision,
"source_hash": snapshot.source_hash,
"node_count": len(snapshot.nodes),
"edge_count": len(snapshot.edges),
"index": str(snapshot.descriptor.index_path),
}
if arguments.command == "validate":
snapshot = project.load()
return {
"status": "ok",
"project_id": snapshot.descriptor.project_id,
"project_root_fingerprint": project_root_fingerprint(snapshot.descriptor.root),
"revision": snapshot.revision,
"source_hash": snapshot.source_hash,
"node_count": len(snapshot.nodes),
"edge_count": len(snapshot.edges),
}
if arguments.command == "build":
return index.build()
if arguments.command == "check":
return index.check()
if arguments.command == "validate-index":
return index.check()
if arguments.command == "show":
return index.get_node(arguments.node_id)
if arguments.command == "search":
return index.search(arguments.query, limit=arguments.limit)
if arguments.command == "filter":
return index.filter_nodes(
family=arguments.family,
authority=arguments.authority,
status=arguments.status,
tag=arguments.tag,
limit=arguments.limit,
)
if arguments.command == "backlinks":
return index.backlinks(arguments.node_id, relation=arguments.relation)
if arguments.command == "dependencies":
return index.dependencies(arguments.node_id, depth=arguments.depth)
if arguments.command == "impact":
return index.impact(arguments.node_id, depth=arguments.depth)
if arguments.command == "context":
return compile_context(index, arguments.profile, arguments.budget)
if arguments.command == "render":
return RenderService(project).render(arguments.view_id)
if arguments.command == "render-status":
return RenderService(project).status(arguments.view_id)
if arguments.command == "preview":
return RenderService(project).preview(arguments.changeset_id, arguments.view_id)
raise DocForgeError("invalid_command", "Unknown command")
def main(argv: list[str] | None = None) -> int:
parser = _parser()
arguments = parser.parse_args(argv)
try:
result = _run(arguments)
code = 0
except DocForgeError as error:
result = {"status": "error", "error": error.as_dict()}
code = 2
print(json.dumps(result, sort_keys=True, indent=2))
return code
if __name__ == "__main__":
sys.exit(main())