generate_wiki_docs.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. #!/usr/bin/env python3
  2. """Generate GitHub Wiki documentation for module variables.
  3. This script auto-generates variable documentation in GitHub Wiki markdown format
  4. for all registered modules, using the latest schema version for each.
  5. """
  6. import sys
  7. from pathlib import Path
  8. # Add project root to path (script is in .github/scripts, so go up twice)
  9. project_root = Path(__file__).parent.parent.parent
  10. sys.path.insert(0, str(project_root))
  11. # ruff: noqa: E402
  12. # Import all modules to register them
  13. import cli.modules.ansible
  14. import cli.modules.compose
  15. import cli.modules.helm
  16. import cli.modules.kubernetes
  17. import cli.modules.packer
  18. import cli.modules.terraform # noqa: F401
  19. from cli.core.registry import registry # Module import after path manipulation
  20. def format_value(value):
  21. """Format value for markdown display."""
  22. if value is None or value == "":
  23. return "_none_"
  24. if isinstance(value, bool):
  25. return "✓" if value else "✗"
  26. if isinstance(value, list):
  27. return ", ".join(f"`{v}`" for v in value)
  28. return f"`{value}`"
  29. def generate_module_docs(module_name: str, output_dir: Path): # noqa: PLR0912, PLR0915
  30. """Generate wiki documentation for a single module."""
  31. # Get module class from registry
  32. module_classes = dict(registry.iter_module_classes())
  33. if module_name not in module_classes:
  34. sys.stderr.write(f"Warning: Module '{module_name}' not found, skipping\n")
  35. return False
  36. module_cls = module_classes[module_name]
  37. schema_version = module_cls.schema_version
  38. # Get the spec for the latest schema version
  39. if hasattr(module_cls, "schemas") and schema_version in module_cls.schemas:
  40. spec = module_cls.schemas[schema_version]
  41. elif hasattr(module_cls, "spec"):
  42. spec = module_cls.spec
  43. else:
  44. sys.stderr.write(f"Warning: No spec found for module '{module_name}', skipping\n")
  45. return False
  46. # Generate markdown content
  47. lines = []
  48. # Header
  49. lines.append(f"# {module_name.title()} Variables")
  50. lines.append("")
  51. lines.append(f"**Module:** `{module_name}` ")
  52. lines.append(f"**Schema Version:** `{schema_version}` ")
  53. lines.append(f"**Description:** {module_cls.description}")
  54. lines.append("")
  55. lines.append("---")
  56. lines.append("")
  57. lines.append(
  58. "This page documents all available variables for the "
  59. + f"{module_name} module. Variables are organized into sections "
  60. + "that can be enabled/disabled based on your configuration needs."
  61. )
  62. lines.append("")
  63. # Table of contents
  64. lines.append("## Table of Contents")
  65. lines.append("")
  66. for section_key, section_data in spec.items():
  67. section_title = section_data.get("title", section_key)
  68. anchor = section_title.lower().replace(" ", "-").replace("/", "")
  69. lines.append(f"- [{section_title}](#{anchor})")
  70. lines.append("")
  71. lines.append("---")
  72. lines.append("")
  73. # Process each section
  74. for section_key, section_data in spec.items():
  75. section_title = section_data.get("title", section_key)
  76. section_desc = section_data.get("description", "")
  77. section_toggle = section_data.get("toggle", "")
  78. section_needs = section_data.get("needs", "")
  79. section_required = section_data.get("required", False)
  80. section_vars = section_data.get("vars", {})
  81. # Section header
  82. lines.append(f"## {section_title}")
  83. lines.append("")
  84. # Section metadata
  85. metadata = []
  86. if section_required:
  87. metadata.append("**Required:** Yes")
  88. if section_toggle:
  89. metadata.append(f"**Toggle Variable:** `{section_toggle}`")
  90. if section_needs:
  91. if isinstance(section_needs, list):
  92. needs_str = ", ".join(f"`{n}`" for n in section_needs)
  93. else:
  94. needs_str = f"`{section_needs}`"
  95. metadata.append(f"**Depends On:** {needs_str}")
  96. if metadata:
  97. lines.append(" \n".join(metadata))
  98. lines.append("")
  99. if section_desc:
  100. lines.append(section_desc)
  101. lines.append("")
  102. # Skip sections with no variables
  103. if not section_vars:
  104. lines.append("_No variables defined in this section._")
  105. lines.append("")
  106. continue
  107. # Variables table
  108. lines.append("| Variable | Type | Default | Description |")
  109. lines.append("|----------|------|---------|-------------|")
  110. for var_name, var_data in section_vars.items():
  111. var_type = var_data.get("type", "str")
  112. var_default = format_value(var_data.get("default"))
  113. var_description = var_data.get("description", "").replace("\n", " ")
  114. # Add extra metadata to description
  115. extra_parts = []
  116. if var_data.get("sensitive"):
  117. extra_parts.append("**Sensitive**")
  118. if var_data.get("autogenerated"):
  119. extra_parts.append("**Auto-generated**")
  120. if "options" in var_data:
  121. opts = ", ".join(f"`{o}`" for o in var_data["options"])
  122. extra_parts.append(f"**Options:** {opts}")
  123. if "needs" in var_data:
  124. extra_parts.append(f"**Needs:** `{var_data['needs']}`")
  125. if "extra" in var_data:
  126. extra_parts.append(var_data["extra"])
  127. if extra_parts:
  128. var_description += "<br>" + " • ".join(extra_parts)
  129. lines.append(f"| `{var_name}` | `{var_type}` | {var_default} | {var_description} |")
  130. lines.append("")
  131. lines.append("---")
  132. lines.append("")
  133. # Footer
  134. lines.append("## Notes")
  135. lines.append("")
  136. lines.append("- **Required sections** must be configured")
  137. lines.append("- **Toggle variables** enable/disable entire sections")
  138. lines.append("- **Dependencies** (`needs`) control when sections/variables are available")
  139. lines.append("- **Sensitive variables** are masked during prompts")
  140. lines.append("- **Auto-generated variables** are populated automatically if not provided")
  141. lines.append("")
  142. lines.append("---")
  143. lines.append("")
  144. lines.append(f"_Last updated: Schema version {schema_version}_")
  145. # Write to file
  146. output_file = output_dir / f"Variables-{module_name.title()}.md"
  147. output_file.write_text("\n".join(lines))
  148. sys.stdout.write(f"Generated: {output_file.name}\n")
  149. return True
  150. def generate_variables_index(modules: list[str], output_dir: Path):
  151. """Generate index page for all variable documentation."""
  152. lines = []
  153. lines.append("# Variables Documentation")
  154. lines.append("")
  155. lines.append("This section contains auto-generated documentation for all " + "available variables in each module.")
  156. lines.append("")
  157. lines.append("## Available Modules")
  158. lines.append("")
  159. for module_name in sorted(modules):
  160. lines.append(f"- [{module_name.title()}](Variables-{module_name.title()})")
  161. lines.append("")
  162. lines.append("---")
  163. lines.append("")
  164. lines.append("Each module page includes:")
  165. lines.append("")
  166. lines.append("- Schema version information")
  167. lines.append("- Complete list of sections and variables")
  168. lines.append("- Variable types, defaults, and descriptions")
  169. lines.append("- Section dependencies and toggle configurations")
  170. lines.append("")
  171. lines.append("---")
  172. lines.append("")
  173. lines.append("_This documentation is auto-generated from module schemas._")
  174. output_file = output_dir / "Variables.md"
  175. output_file.write_text("\n".join(lines))
  176. sys.stdout.write(f"Generated: {output_file.name}\n")
  177. # Minimum required arguments
  178. MIN_ARGS = 2
  179. def main():
  180. """Main entry point."""
  181. if len(sys.argv) < MIN_ARGS:
  182. sys.stderr.write("Usage: python3 scripts/generate_wiki_docs.py <output_directory>\n")
  183. sys.exit(1)
  184. output_dir = Path(sys.argv[1])
  185. output_dir.mkdir(parents=True, exist_ok=True)
  186. sys.stdout.write(f"Generating wiki documentation in: {output_dir}\n")
  187. sys.stdout.write("\n")
  188. # Get all registered modules
  189. module_classes = dict(registry.iter_module_classes())
  190. successful_modules = []
  191. for module_name in sorted(module_classes.keys()):
  192. if generate_module_docs(module_name, output_dir):
  193. successful_modules.append(module_name)
  194. sys.stdout.write("\n")
  195. # Generate index page
  196. if successful_modules:
  197. generate_variables_index(successful_modules, output_dir)
  198. sys.stdout.write("\n")
  199. sys.stdout.write(f"✓ Successfully generated documentation for {len(successful_modules)} module(s)\n")
  200. else:
  201. sys.stderr.write("Error: No documentation generated\n")
  202. sys.exit(1)
  203. if __name__ == "__main__":
  204. main()