From 83d5a08c7ad4597b01edc44706610d970b387d9e Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Sat, 31 Jan 2026 07:10:14 +0000 Subject: [PATCH] Initial upload: ConfDoc v0.1.0 - Config validation and documentation generator --- src/confdoc/docs/generator.py | 167 ++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 src/confdoc/docs/generator.py diff --git a/src/confdoc/docs/generator.py b/src/confdoc/docs/generator.py new file mode 100644 index 0000000..d263930 --- /dev/null +++ b/src/confdoc/docs/generator.py @@ -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)