Initial upload: ConfDoc v0.1.0 - Config validation and documentation generator
This commit is contained in:
167
src/confdoc/docs/generator.py
Normal file
167
src/confdoc/docs/generator.py
Normal file
@@ -0,0 +1,167 @@
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
class DocGenerator:
|
||||
"""Generates markdown documentation from JSON Schema."""
|
||||
|
||||
def __init__(self, template: Optional[str] = None):
|
||||
"""Initialize doc generator with optional custom template."""
|
||||
self.template = template or self._default_template()
|
||||
|
||||
def generate(self, schema: Dict[str, Any], title: str = "Configuration Documentation") -> str:
|
||||
"""Generate markdown documentation from schema."""
|
||||
sections = []
|
||||
|
||||
sections.append(self._format_title(title))
|
||||
sections.append("")
|
||||
|
||||
if schema.get("$schema"):
|
||||
sections.append(f"*Schema: `{schema['$schema']}`*")
|
||||
sections.append("")
|
||||
|
||||
if schema.get("title"):
|
||||
sections.append(f"## {schema['title']}")
|
||||
sections.append("")
|
||||
|
||||
if schema.get("description"):
|
||||
sections.append(f"{schema['description']}")
|
||||
sections.append("")
|
||||
|
||||
if "properties" in schema:
|
||||
sections.append(self._format_properties(schema.get("properties", {}), schema.get("required", [])))
|
||||
|
||||
if "definitions" in schema or "$defs" in schema:
|
||||
sections.append(self._format_definitions(schema.get("definitions", {}) or schema.get("$defs", {})))
|
||||
|
||||
return "\n".join(sections)
|
||||
|
||||
def _format_title(self, title: str) -> str:
|
||||
"""Format document title."""
|
||||
return f"# {title}\n"
|
||||
|
||||
def _format_properties(self, properties: Dict[str, Any], required: List[str]) -> str:
|
||||
"""Format schema properties as a markdown table."""
|
||||
lines = []
|
||||
lines.append("## Configuration Options")
|
||||
lines.append("")
|
||||
lines.append("| Property | Type | Required | Default | Description |")
|
||||
lines.append("|----------|------|----------|---------|-------------|")
|
||||
|
||||
for prop_name, prop_info in properties.items():
|
||||
prop_type = self._format_type(prop_info.get("type", "any"))
|
||||
is_required = "Yes" if prop_name in required else "No"
|
||||
default = self._format_default(prop_info.get("default"))
|
||||
description = prop_info.get("description", "")
|
||||
|
||||
if prop_info.get("enum"):
|
||||
choices = ", ".join(str(e) for e in prop_info["enum"])
|
||||
description = f"{description} (Allowed values: {choices})"
|
||||
|
||||
lines.append(f"| `{prop_name}` | {prop_type} | {is_required} | {default} | {description} |")
|
||||
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
def _format_definitions(self, definitions: Dict[str, Any]) -> str:
|
||||
"""Format schema definitions/references."""
|
||||
lines = []
|
||||
lines.append("## Definitions")
|
||||
lines.append("")
|
||||
|
||||
for name, definition in definitions.items():
|
||||
lines.append(f"### `{name}`")
|
||||
lines.append("")
|
||||
|
||||
if definition.get("description"):
|
||||
lines.append(definition["description"])
|
||||
lines.append("")
|
||||
|
||||
if "properties" in definition:
|
||||
lines.append("| Property | Type | Description |")
|
||||
lines.append("|----------|------|-------------|")
|
||||
|
||||
for prop_name, prop_info in definition.get("properties", {}).items():
|
||||
prop_type = self._format_type(prop_info.get("type", "any"))
|
||||
description = prop_info.get("description", "")
|
||||
lines.append(f"| `{prop_name}` | {prop_type} | {description} |")
|
||||
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def _format_type(self, type_name: Any) -> str:
|
||||
"""Format type name for display."""
|
||||
if isinstance(type_name, list):
|
||||
return ", ".join(self._format_type(t) for t in type_name)
|
||||
|
||||
type_map = {
|
||||
"string": "string",
|
||||
"integer": "integer",
|
||||
"number": "number",
|
||||
"boolean": "boolean",
|
||||
"object": "object",
|
||||
"array": "array",
|
||||
"null": "null",
|
||||
"any": "any",
|
||||
}
|
||||
|
||||
return type_map.get(str(type_name), str(type_name))
|
||||
|
||||
def _format_default(self, default: Any) -> str:
|
||||
"""Format default value for display."""
|
||||
if default is None:
|
||||
return "-"
|
||||
elif isinstance(default, bool):
|
||||
return str(default).lower()
|
||||
elif isinstance(default, str):
|
||||
return f"`{default}`"
|
||||
else:
|
||||
return str(default)
|
||||
|
||||
def _default_template(self) -> str:
|
||||
"""Return default template string."""
|
||||
return "default"
|
||||
|
||||
def generate_table_section(self, title: str, items: List[Dict[str, Any]], columns: List[str]) -> str:
|
||||
"""Generate a markdown table section."""
|
||||
if not items:
|
||||
return ""
|
||||
|
||||
lines = []
|
||||
lines.append(f"## {title}")
|
||||
lines.append("")
|
||||
|
||||
header = "| " + " | ".join(columns) + " |"
|
||||
separator = "| " + "| ".join("-" * len(col) for col in columns) + " |"
|
||||
|
||||
lines.append(header)
|
||||
lines.append(separator)
|
||||
|
||||
for item in items:
|
||||
row = "| " + " | ".join(str(item.get(col.lower(), "")) for col in columns) + " |"
|
||||
lines.append(row)
|
||||
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
def generate_examples_section(self, examples: List[Dict[str, Any]]) -> str:
|
||||
"""Generate an examples section."""
|
||||
lines = []
|
||||
lines.append("## Examples")
|
||||
lines.append("")
|
||||
|
||||
for i, example in enumerate(examples, 1):
|
||||
lines.append(f"### Example {i}")
|
||||
lines.append("")
|
||||
|
||||
if example.get("description"):
|
||||
lines.append(f"{example['description']}")
|
||||
lines.append("")
|
||||
|
||||
if example.get("config"):
|
||||
lines.append("```json")
|
||||
lines.append(str(example['config']))
|
||||
lines.append("```")
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
Reference in New Issue
Block a user