From 5b5f1975c5a9927809016f04e0aef20dee5329ff Mon Sep 17 00:00:00 2001 From: a-kenji Date: Thu, 24 Jul 2025 23:27:46 +0200 Subject: [PATCH] pkgs/cli/lib: Allow clan templates list to function outside a clan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow `clan templates list` to function outside a clan. Currently when bootstrapping a clan and trying to list the templates it fails as follows: ``` Traceback (most recent call last): File "/nix/store/pkrsr8zr90bps1fwrl8n74zbb9g038b8-clan-cli/bin/.clan-wrapped", line 9, in sys.exit(main()) ~~~~^^ File "/nix/store/pkrsr8zr90bps1fwrl8n74zbb9g038b8-clan-cli/lib/python3.13/site-packages/clan_cli/cli.py", line 516, in main args.func(args) ~~~~~~~~~^^^^^^ File "/nix/store/pkrsr8zr90bps1fwrl8n74zbb9g038b8-clan-cli/lib/python3.13/site-packages/clan_cli/templates/list.py", line 11, in list_command templates = list_templates(args.flake) File "/nix/store/pkrsr8zr90bps1fwrl8n74zbb9g038b8-clan-cli/lib/python3.13/site-packages/clan_lib/templates/__init__.py", line 20, in list_templates custom_templates = flake.select("clanInternals.inventoryClass.templatesPerSource") ^^^^^^^^^^^^ AttributeError: 'NoneType' object has no attribute 'select' ``` With the change we get the following output: ``` Available 'clan' templates ├── │ ├── default: Initialize a new clan flake │ ├── flake-parts: Flake-parts │ └── minimal: for clans managed via (G)UI Available 'disko' templates ├── │ └── single-disk: A simple ext4 disk with a single partition Available 'machine' templates ├── │ ├── flash-installer: Initialize a new flash-installer machine │ └── new-machine: Initialize a new machine ``` Allowing to check for available templates without needing to have a clan, which improves the bootstrapping experience. --- pkgs/clan-cli/clan_cli/templates/list_test.py | 19 ++++++++++- pkgs/clan-cli/clan_lib/templates/__init__.py | 33 ++++++++++++++++--- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/templates/list_test.py b/pkgs/clan-cli/clan_cli/templates/list_test.py index a1c4dbd32..7d9ff8b98 100644 --- a/pkgs/clan-cli/clan_cli/templates/list_test.py +++ b/pkgs/clan-cli/clan_cli/templates/list_test.py @@ -1,3 +1,5 @@ +from pathlib import Path + import pytest from clan_cli.tests.fixtures_flakes import FlakeForTest @@ -11,7 +13,6 @@ def test_templates_list( ) -> None: with capture_output as output: cli.run(["templates", "list", "--flake", str(test_flake_with_core.path)]) - print(output.out) assert "Available 'clan' templates" in output.out assert "Available 'disko' templates" in output.out assert "Available 'machine' templates" in output.out @@ -21,3 +22,19 @@ def test_templates_list( assert "minimal:" in output.out assert "new-machine" in output.out assert "flash-installer" in output.out + + +@pytest.mark.with_core +def test_templates_list_outside_clan( + capture_output: CaptureOutput, temp_dir: Path +) -> None: + """Test templates list command when run outside a clan directory.""" + with capture_output as output: + # Use --flake pointing to a non-clan directory to trigger fallback + cli.run(["templates", "list", "--flake", str(temp_dir)]) + assert "Available 'clan' templates" in output.out + assert "Available 'disko' templates" in output.out + assert "Available 'machine' templates" in output.out + assert "" in output.out + # Should NOT show any custom templates + assert "inputs." not in output.out diff --git a/pkgs/clan-cli/clan_lib/templates/__init__.py b/pkgs/clan-cli/clan_lib/templates/__init__.py index 2e61406da..b75530a1f 100644 --- a/pkgs/clan-cli/clan_lib/templates/__init__.py +++ b/pkgs/clan-cli/clan_lib/templates/__init__.py @@ -1,6 +1,7 @@ import logging from dataclasses import dataclass +from clan_lib.dirs import clan_templates from clan_lib.flake import Flake from clan_lib.nix_models.clan import ClanTemplatesType @@ -13,11 +14,35 @@ class TemplateList: custom: dict[str, ClanTemplatesType] -def list_templates(flake: Flake) -> TemplateList: +def get_builtin_template_list() -> TemplateList: + """ + Fallback to get only builtin clan templates with no custom templates. + """ + builtin_flake = Flake(str(clan_templates())) + builtin_templates = builtin_flake.select("clanInternals.templates") + custom_templates: dict[str, ClanTemplatesType] = {} + return TemplateList(builtin_templates, custom_templates) + + +def list_templates(flake: Flake | None) -> TemplateList: """ Show information about a module """ - custom_templates = flake.select("clanInternals.inventoryClass.templatesPerSource") - builtin_templates = flake.select("clanInternals.templates") + if flake is None: + log.debug("No flake provided, falling back to clan-core builtin templates") + return get_builtin_template_list() - return TemplateList(builtin_templates, custom_templates) + try: + custom_templates = flake.select( + "clanInternals.inventoryClass.templatesPerSource" + ) + builtin_templates = flake.select("clanInternals.templates") + + return TemplateList(builtin_templates, custom_templates) + + except (AttributeError, KeyError, Exception): + log.debug( + "Failed to get templates from clan inputs, " + "falling back to clan-core builtin templates" + ) + return get_builtin_template_list()