diff --git a/doc2man/generators/man.py b/doc2man/generators/man.py new file mode 100644 index 0000000..28fb674 --- /dev/null +++ b/doc2man/generators/man.py @@ -0,0 +1,112 @@ +"""Man page generator for Doc2Man.""" + +from pathlib import Path +from typing import Any, Dict, List, Optional + +from jinja2 import Environment, FileSystemLoader, TemplateSyntaxError + + +def generate_man_page( + parsed_data: List[Dict[str, Any]], + output_path: Path, + template_path: Optional[Path] = None, +) -> str: + """Generate a man page from parsed documentation data. + + Args: + parsed_data: List of parsed documentation dictionaries. + output_path: Path to write the man page. + template_path: Optional custom template file. + + Returns: + The generated man page content. + """ + env = Environment( + loader=FileSystemLoader(str(template_path.parent) if template_path else get_template_dir()), + autoescape=True, + ) + env.filters['first_line'] = first_line_filter + + try: + if template_path: + template = env.from_string(template_path.read_text()) + else: + template = env.get_template("man_page.j2") + except TemplateSyntaxError as e: + raise ValueError(f"Template syntax error: {e}") + + man_content = template.render( + data=parsed_data, + title=get_man_title(parsed_data), + section=get_man_section(), + ) + + if output_path: + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_text(man_content, encoding="utf-8") + + return man_content + + +def first_line_filter(text: str) -> str: + """Get the first line of text.""" + if not text: + return "" + lines = text.split('\n') + return lines[0] if lines else "" + + +def get_template_dir() -> Path: + """Get the directory containing templates.""" + return Path(__file__).parent.parent / "templates" + + +def get_man_title(parsed_data: List[Dict[str, Any]]) -> str: + """Extract a title for the man page.""" + for item in parsed_data: + data = item.get("data", {}) + if data.get("title"): + return data["title"] + if data.get("functions"): + for func in data["functions"]: + if func.get("name"): + return func["name"] + return "Command Reference" + + +def get_man_section() -> str: + """Get the man page section number.""" + return "1" + + +class ManPageValidator: + """Validator for man page format.""" + + VALID_MACROS = { + ".TH", ".SH", ".B", ".I", ".BR", ".RI", ".PP", ".P", ".IP", + ".TP", ".HP", ".LP", ".PD", ".nf", ".fi", ".RS", ".RE", + ".EX", ".EE", ".EQ", ".EN", ".IB", ".BI", ".RB", ".BR", + } + + @staticmethod + def validate(content: str) -> List[str]: + """Validate man page content. + + Args: + content: The man page content to validate. + + Returns: + List of validation warnings. + """ + warnings = [] + + if ".TH" not in content: + warnings.append("Missing .TH macro (required for man page title)") + + if ".SH NAME" not in content: + warnings.append("Missing NAME section") + + if ".SH DESCRIPTION" not in content: + warnings.append("Missing DESCRIPTION section") + + return warnings