From 2d7aac6ac1f021a9ab9004fdb13fafa089620e7c Mon Sep 17 00:00:00 2001 From: Dan Shernicoff Date: Fri, 7 Nov 2025 16:06:35 -0500 Subject: [PATCH 1/3] Add CLI to generate the site map. --- src/render_engine_cli/cli.py | 40 +++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/render_engine_cli/cli.py b/src/render_engine_cli/cli.py index 8519edd..6e8eaaf 100644 --- a/src/render_engine_cli/cli.py +++ b/src/render_engine_cli/cli.py @@ -5,7 +5,8 @@ import click from dateutil import parser as dateparser from dateutil.parser import ParserError -from render_engine import Collection +from render_engine import Collection, Page +from render_engine.site_map import SiteMap from rich.console import Console from render_engine_cli.event import ServerEventHandler @@ -396,5 +397,42 @@ def templates(module_site: str, theme_name: str, filter_value: str): ) +@app.command +@click.option( + "--module-site", + type=click.STRING, + # help="The module (python file) and site (the Site object) for your site in the format module:site", + callback=validate_module_site, +) +@click.option( + "-x", + "--xml", + help="Generate site map as XML", + type=click.BOOL, + is_flag=True, + default=False, +) +@click.option("-o", "--output", help="File to output site map to.", type=click.Path(exists=False)) +def sitemap(xml: bool, output: click.File, module_site: str): + """ + CLI for generating the site map in HTML (default) or XML to either the console (default) or a file. + """ + module, site_name = split_module_site(module_site) + site = get_site(module, site_name) + site_map = SiteMap(site.route_list, site.site_vars.get("SITE_URL", "")) + if xml: + site.theme_manager.engine.globals.update(site.site_vars) + site_map_page = Page() + site_map_page.template = "sitemap.xml" + site_map_page.site_map = site_map + content = site_map_page._render_content(site.theme_manager.engine) + else: + content = site_map.html + if output: + Path(output).write_text(content) + else: + print(content) + + if __name__ == "__main__": app() From 96e8f2bcb09a4b79a9d31c7949607df8cac4b886 Mon Sep 17 00:00:00 2001 From: Dan Shernicoff Date: Fri, 7 Nov 2025 22:55:33 -0500 Subject: [PATCH 2/3] Add tests --- src/render_engine_cli/cli.py | 2 +- tests/test_cli_commands.py | 43 ++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/render_engine_cli/cli.py b/src/render_engine_cli/cli.py index 6e8eaaf..6dfa624 100644 --- a/src/render_engine_cli/cli.py +++ b/src/render_engine_cli/cli.py @@ -431,7 +431,7 @@ def sitemap(xml: bool, output: click.File, module_site: str): if output: Path(output).write_text(content) else: - print(content) + click.echo(content) if __name__ == "__main__": diff --git a/tests/test_cli_commands.py b/tests/test_cli_commands.py index 4ef3877..6742fbf 100644 --- a/tests/test_cli_commands.py +++ b/tests/test_cli_commands.py @@ -387,3 +387,46 @@ def test_serve_command_with_reload(runner, test_site_module, monkeypatch): assert result.exit_code == 0, result.output mock_get_paths.assert_called_once_with(mock_site) + + +@pytest.mark.parametrize( + "params, expected", + [ + ([], ["console", "html"]), + (["-x"], ["console", "xml"]), + (["-x", "-o", "sitemap.xml"], ["file", "xml"]), + (["-o", "sitemap.html"], ["file", "html"]), + ], +) +def test_sitemap(runner, test_site_module, monkeypatch, params, expected): + """Tests sitemap generation command""" + + class MockSiteMap: + def __init__(self, *args, **kwargs): ... + + @property + def html(self): + return "sitemap" + + class MockPage: + def __init__(self, *args, **kwargs): ... + + def _render_content(self, *args, **kwargs): + return "sitemap" + + with ( + patch("render_engine_cli.cli.get_site") as mock_get_site, + ): + monkeypatch.setattr("render_engine_cli.cli.SiteMap", MockSiteMap) + monkeypatch.setattr("render_engine_cli.cli.Page", MockPage) + mock_site = Mock() + mock_get_site.return_value = mock_site + with runner.isolated_filesystem(): + result = runner.invoke(app, ["sitemap", "--module-site", "test:test", *params]) + output_loc, output_type = expected + expected_out = f"sitemap.{output_type}" + match output_loc: + case "console": + assert result.output.strip() == "sitemap" + case "file": + assert Path(expected_out).read_text().strip() == "sitemap" From 75ec824c3421d76c09bf8ca58083e81a9d1b4108 Mon Sep 17 00:00:00 2001 From: Dan Shernicoff Date: Sun, 9 Nov 2025 12:08:24 -0500 Subject: [PATCH 3/3] Uncomment help for `--module-site` throughout. --- src/render_engine_cli/cli.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/render_engine_cli/cli.py b/src/render_engine_cli/cli.py index 6dfa624..363ff4d 100644 --- a/src/render_engine_cli/cli.py +++ b/src/render_engine_cli/cli.py @@ -26,6 +26,8 @@ validate_module_site, ) +MODULE_SITE_HELP = "The module (python file) and site (the Site object) for your site in the format module:site" + try: # Get the RE version for display. If it's not set it means we're working locally. from render_engine.__version__ import version as re_version @@ -123,7 +125,7 @@ def init(template: str, extra_context: str, no_input: bool, output_dir: Path, co @click.option( "--module-site", type=click.STRING, - # help="The module (python file) and site (the Site object) for your site in the format module:site", + help=MODULE_SITE_HELP, callback=validate_module_site, ) @click.option( @@ -146,7 +148,7 @@ def build(module_site: str, clean: bool): @click.option( "--module-site", type=click.STRING, - # help="The module (python file) and site (the Site object) for your site in the format module:site", + help=MODULE_SITE_HELP, callback=validate_module_site, ) @click.option( @@ -207,7 +209,7 @@ def serve(module_site: str, clean: bool, reload: bool, port: int): @click.option( "--module-site", type=click.STRING, - # help="The module (python file) and site (the Site object) for your site in the format module:site", + help=MODULE_SITE_HELP, callback=validate_module_site, ) @click.option( @@ -348,7 +350,7 @@ def new_entry( @click.option( "--module-site", type=click.STRING, - # help="The module (python file) and site (the Site object) for your site in the format module:site", + help=MODULE_SITE_HELP, callback=validate_module_site, ) @click.option( @@ -401,7 +403,7 @@ def templates(module_site: str, theme_name: str, filter_value: str): @click.option( "--module-site", type=click.STRING, - # help="The module (python file) and site (the Site object) for your site in the format module:site", + help=MODULE_SITE_HELP, callback=validate_module_site, ) @click.option(