Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,7 @@ generate-cli-commands: ## Run generation of the CLI commands

.PHONY: generate-cli-reference-docs
generate-cli-reference-docs: ## Generate the CLI reference documentation
$(UV) run --frozen packages/nemo_platform_ext/scripts/docs_generator.py reference > docs/cli/reference.mdx
$(UV) run --frozen packages/nemo_platform_ext/scripts/docs_generator.py summary > docs/fern/snippets/_snippets/cli-summary.mdx
NMP_CONFIG_FILE_PATH="$(NMP_CONFIG_FILE_PATH)" $(UV) run --frozen packages/nemo_platform_ext/scripts/docs_generator.py all

.PHONY: generate-config-reference-docs
generate-config-reference-docs: ## Generate the platform config reference documentation
Expand Down
35 changes: 28 additions & 7 deletions packages/nemo_platform_ext/scripts/docs_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from datetime import datetime
from functools import cache
from importlib import import_module
from pathlib import Path
from types import ModuleType
from typing import Any

Expand Down Expand Up @@ -724,38 +725,58 @@ def _escape_mdx_line(line: str) -> str:
}


_REPO_ROOT = Path(__file__).resolve().parents[3]
_REFERENCE_DOCS_PATH = _REPO_ROOT / "docs/cli/reference.mdx"
_SUMMARY_DOCS_PATH = _REPO_ROOT / "docs/fern/snippets/_snippets/cli-summary.mdx"


def _enable_plugin_cli_docs() -> None:
"""Include supported plugin commands in generated CLI documentation."""
import os

os.environ.update(_PLUGIN_DOCS_DISCOVERY_ENV)


def _with_trailing_newline(output: str) -> str:
"""Return output with exactly the trailing newline expected in generated files."""
return output if output.endswith("\n") else output + "\n"


def write_docs_files(app: typer.Typer, reference_path: Path, summary_path: Path, name: str = "nemo") -> None:
"""Write generated CLI reference and summary docs from one imported CLI app."""
reference_path.parent.mkdir(parents=True, exist_ok=True)
summary_path.parent.mkdir(parents=True, exist_ok=True)
reference_path.write_text(_with_trailing_newline(generate_docs(app, name=name)), encoding="utf-8")
summary_path.write_text(_with_trailing_newline(generate_index_snippet(app, name=name)), encoding="utf-8")


def main() -> None:
"""Generate CLI documentation and print to stdout.

Usage:
docs_generator.py reference # Full CLI reference
docs_generator.py summary # Index page summary snippet
docs_generator.py all # Write both generated docs files
"""
import sys

if len(sys.argv) != 2 or sys.argv[1] not in ("reference", "summary", "all"):
print("Usage: docs_generator.py {reference|summary|all}", file=sys.stderr)
sys.exit(1)

_enable_plugin_cli_docs()

from nemo_platform_ext.cli.app import app

if len(sys.argv) != 2 or sys.argv[1] not in ("reference", "summary"):
print("Usage: docs_generator.py {reference|summary}", file=sys.stderr)
sys.exit(1)

mode = sys.argv[1]
if mode == "all":
write_docs_files(app, _REFERENCE_DOCS_PATH, _SUMMARY_DOCS_PATH, name="nemo")
return
if mode == "summary":
output = generate_index_snippet(app, name="nemo")
else:
output = generate_docs(app, name="nemo")
sys.stdout.write(output)
if not output.endswith("\n"):
sys.stdout.write("\n")
sys.stdout.write(_with_trailing_newline(output))


if __name__ == "__main__":
Expand Down
24 changes: 24 additions & 0 deletions packages/nemo_platform_ext/tests/cli/test_docs_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ def _load_docs_generator():
_docs_generator = _load_docs_generator()
generate_docs = _docs_generator.generate_docs
generate_index_snippet = _docs_generator.generate_index_snippet
write_docs_files = _docs_generator.write_docs_files
with_trailing_newline = _docs_generator._with_trailing_newline
enable_plugin_cli_docs = _docs_generator._enable_plugin_cli_docs
documented_plugin_clis = _docs_generator._DOCUMENTED_PLUGIN_CLIS
plugin_docs_discovery_env = _docs_generator._PLUGIN_DOCS_DISCOVERY_ENV
Expand Down Expand Up @@ -143,3 +145,25 @@ def visible() -> None:
assert "hidden-command" not in snippet
assert "Hidden command." not in snippet
assert "* `--help, -h`: Show this message and exit." in reference


def test_write_docs_files_matches_individual_generators(tmp_path):
docs_app = typer.Typer()

@docs_app.callback()
def main() -> None:
"""Test CLI."""

@docs_app.command(rich_help_panel="Setup")
def visible() -> None:
"""Visible command."""

reference_path = tmp_path / "docs/cli/reference.mdx"
summary_path = tmp_path / "docs/fern/snippets/_snippets/cli-summary.mdx"

write_docs_files(docs_app, reference_path, summary_path, name="nemo")

assert reference_path.read_text(encoding="utf-8") == generate_docs(docs_app, name="nemo")
assert summary_path.read_text(encoding="utf-8") == with_trailing_newline(
generate_index_snippet(docs_app, name="nemo")
)
2 changes: 1 addition & 1 deletion script/generate-openapi-spec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

uv run --frozen python -m script.generate_openapi_spec -v "$@"
uv run --frozen python -m script.generate_openapi_spec "$@"
Loading
Loading