2026-05-03 21:37:00 +02:00
|
|
|
"""`mkt` command entry point."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
import click
|
|
|
|
|
import yaml
|
|
|
|
|
|
|
|
|
|
from markitect_tool.core import parse_markdown_file
|
2026-05-03 22:12:46 +02:00
|
|
|
from markitect_tool.schema import load_schema_file, validate_markdown_file, validate_schema
|
2026-05-03 21:37:00 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@click.group()
|
|
|
|
|
@click.version_option()
|
|
|
|
|
def main() -> None:
|
|
|
|
|
"""Markdown-native toolkit for structured knowledge artifacts."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@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", "tree"], case_sensitive=False),
|
|
|
|
|
default="json",
|
|
|
|
|
show_default=True,
|
|
|
|
|
)
|
|
|
|
|
def parse(file: Path, output_format: str) -> None:
|
|
|
|
|
"""Parse a Markdown file into a structured representation."""
|
|
|
|
|
|
|
|
|
|
document = parse_markdown_file(file)
|
|
|
|
|
data = document.to_dict()
|
|
|
|
|
if output_format == "yaml":
|
|
|
|
|
click.echo(yaml.safe_dump(data, sort_keys=False))
|
|
|
|
|
elif output_format == "tree":
|
|
|
|
|
for heading in document.headings:
|
|
|
|
|
click.echo(f"{'#' * heading.level} {heading.text}")
|
|
|
|
|
else:
|
|
|
|
|
click.echo(json.dumps(data, indent=2, ensure_ascii=False))
|
|
|
|
|
|
|
|
|
|
|
2026-05-03 22:12:46 +02:00
|
|
|
@main.command()
|
|
|
|
|
@click.argument("file", type=click.Path(exists=True, dir_okay=False, path_type=Path))
|
|
|
|
|
@click.option(
|
|
|
|
|
"--schema",
|
|
|
|
|
"schema_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 validate(file: Path, schema_file: Path, output_format: str) -> None:
|
|
|
|
|
"""Validate a Markdown file against a Markdown schema file."""
|
|
|
|
|
|
|
|
|
|
result = validate_markdown_file(file, schema_file)
|
|
|
|
|
_emit_result(result.to_dict(), output_format)
|
|
|
|
|
raise click.exceptions.Exit(0 if result.valid else 1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@main.group()
|
|
|
|
|
def schema() -> None:
|
|
|
|
|
"""Work with Markdown schema files."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@schema.command("validate")
|
|
|
|
|
@click.argument("schema_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 schema_validate(schema_file: Path, output_format: str) -> None:
|
|
|
|
|
"""Validate that a Markdown schema contains a well-formed JSON Schema."""
|
|
|
|
|
|
|
|
|
|
loaded = load_schema_file(schema_file)
|
|
|
|
|
result = validate_schema(loaded.schema)
|
|
|
|
|
data = result.to_dict() | {"schema_path": str(schema_file)}
|
|
|
|
|
_emit_result(data, 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))
|
|
|
|
|
elif output_format == "yaml":
|
|
|
|
|
click.echo(yaml.safe_dump(data, sort_keys=False))
|
|
|
|
|
else:
|
|
|
|
|
if data.get("valid"):
|
|
|
|
|
click.echo("valid")
|
|
|
|
|
else:
|
|
|
|
|
click.echo("invalid")
|
|
|
|
|
for violation in data.get("violations", []):
|
|
|
|
|
click.echo(f"- {violation['path']}: {violation['message']}")
|
|
|
|
|
|
|
|
|
|
|
2026-05-03 21:37:00 +02:00
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|