generated from coulomb/repo-seed
Contract framework with markdown-native contracts utilizing fenced YAML blocks
This commit is contained in:
@@ -9,6 +9,13 @@ import click
|
||||
import yaml
|
||||
|
||||
from markitect_tool.core import parse_markdown_file
|
||||
from markitect_tool.contract import (
|
||||
ContractLoaderError,
|
||||
check_markdown_file,
|
||||
collect_metrics,
|
||||
load_contract_file,
|
||||
validate_contract,
|
||||
)
|
||||
from markitect_tool.schema import load_schema_file, validate_markdown_file, validate_schema
|
||||
|
||||
|
||||
@@ -41,6 +48,23 @@ def parse(file: Path, output_format: str) -> None:
|
||||
click.echo(json.dumps(data, indent=2, ensure_ascii=False))
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument("file", type=click.Path(exists=True, dir_okay=False, path_type=Path))
|
||||
@click.option(
|
||||
"--format",
|
||||
"output_format",
|
||||
type=click.Choice(["json", "yaml", "text"], case_sensitive=False),
|
||||
default="text",
|
||||
show_default=True,
|
||||
)
|
||||
def metrics(file: Path, output_format: str) -> None:
|
||||
"""Report practical size and complexity metrics for a Markdown file."""
|
||||
|
||||
document = parse_markdown_file(file)
|
||||
data = collect_metrics(document).to_dict() | {"document_path": str(file)}
|
||||
_emit_metrics(data, output_format)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument("file", type=click.Path(exists=True, dir_okay=False, path_type=Path))
|
||||
@click.option(
|
||||
@@ -88,6 +112,54 @@ def schema_validate(schema_file: Path, output_format: str) -> None:
|
||||
raise click.exceptions.Exit(0 if result.valid else 1)
|
||||
|
||||
|
||||
@main.group()
|
||||
def contract() -> None:
|
||||
"""Work with Markdown document contracts."""
|
||||
|
||||
|
||||
@contract.command("validate")
|
||||
@click.argument("contract_file", type=click.Path(exists=True, dir_okay=False, path_type=Path))
|
||||
@click.option(
|
||||
"--format",
|
||||
"output_format",
|
||||
type=click.Choice(["json", "yaml", "text"], case_sensitive=False),
|
||||
default="text",
|
||||
show_default=True,
|
||||
)
|
||||
def contract_validate(contract_file: Path, output_format: str) -> None:
|
||||
"""Validate that a Markdown contract file is well formed."""
|
||||
|
||||
result = validate_contract(load_contract_file(contract_file))
|
||||
_emit_diagnostic_result(result.to_dict(), output_format)
|
||||
raise click.exceptions.Exit(0 if result.valid else 1)
|
||||
|
||||
|
||||
@contract.command("check")
|
||||
@click.argument("file", type=click.Path(exists=True, dir_okay=False, path_type=Path))
|
||||
@click.option(
|
||||
"--contract",
|
||||
"contract_file",
|
||||
required=True,
|
||||
type=click.Path(exists=True, dir_okay=False, path_type=Path),
|
||||
)
|
||||
@click.option(
|
||||
"--format",
|
||||
"output_format",
|
||||
type=click.Choice(["json", "yaml", "text"], case_sensitive=False),
|
||||
default="text",
|
||||
show_default=True,
|
||||
)
|
||||
def contract_check(file: Path, contract_file: Path, output_format: str) -> None:
|
||||
"""Check a Markdown file against a Markdown document contract."""
|
||||
|
||||
try:
|
||||
result = check_markdown_file(file, contract_file)
|
||||
except ContractLoaderError as exc:
|
||||
raise click.ClickException(str(exc)) from exc
|
||||
_emit_diagnostic_result(result.to_dict(), output_format)
|
||||
raise click.exceptions.Exit(0 if result.valid else 1)
|
||||
|
||||
|
||||
def _emit_result(data: dict, output_format: str) -> None:
|
||||
if output_format == "json":
|
||||
click.echo(json.dumps(data, indent=2, ensure_ascii=False))
|
||||
@@ -102,5 +174,45 @@ def _emit_result(data: dict, output_format: str) -> None:
|
||||
click.echo(f"- {violation['path']}: {violation['message']}")
|
||||
|
||||
|
||||
def _emit_diagnostic_result(data: dict, output_format: str) -> None:
|
||||
if output_format == "json":
|
||||
click.echo(json.dumps(data, indent=2, ensure_ascii=False))
|
||||
elif output_format == "yaml":
|
||||
click.echo(yaml.safe_dump(data, sort_keys=False))
|
||||
else:
|
||||
click.echo("valid" if data.get("valid") else "invalid")
|
||||
for diagnostic in data.get("diagnostics", []):
|
||||
click.echo(
|
||||
f"- [{diagnostic['severity']}] {diagnostic['code']}: "
|
||||
f"{diagnostic['message']}"
|
||||
)
|
||||
if diagnostic.get("source"):
|
||||
source = diagnostic["source"]
|
||||
suffix = f":{source['line']}" if source.get("line") else ""
|
||||
click.echo(f" source: {source.get('path', '<document>')}{suffix}")
|
||||
if diagnostic.get("guidance"):
|
||||
click.echo(f" guidance: {diagnostic['guidance']}")
|
||||
|
||||
|
||||
def _emit_metrics(data: dict, output_format: str) -> None:
|
||||
if output_format == "json":
|
||||
click.echo(json.dumps(data, indent=2, ensure_ascii=False))
|
||||
elif output_format == "yaml":
|
||||
click.echo(yaml.safe_dump(data, sort_keys=False))
|
||||
else:
|
||||
doc = data["document"]
|
||||
click.echo("document")
|
||||
for metric, value in doc.items():
|
||||
click.echo(f"- {metric}: {value}")
|
||||
sections = data.get("sections", [])
|
||||
if sections:
|
||||
click.echo("sections")
|
||||
for section in sections:
|
||||
click.echo(
|
||||
f"- {section['heading']}: words={section['words']}, "
|
||||
f"paragraphs={section['paragraphs']}, line={section['line']}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user