diff --git a/pkgs/clan-cli/clan_cli/clan_uri.py b/pkgs/clan-cli/clan_cli/clan_uri.py index 7da3c6365..3102b5ffe 100644 --- a/pkgs/clan-cli/clan_cli/clan_uri.py +++ b/pkgs/clan-cli/clan_cli/clan_uri.py @@ -88,7 +88,13 @@ class ClanURI: raise ClanError(f"Unsupported uri components: {comb}") def get_internal(self) -> str: - return self._nested_uri + match self.scheme: + case ClanScheme.FILE.value(path): + return str(path) # type: ignore + case ClanScheme.HTTP.value(url): + return url # type: ignore + case _: + raise ClanError(f"Unsupported uri components: {self.scheme}") @classmethod def from_path(cls, path: Path, params: ClanParameters) -> Self: # noqa diff --git a/pkgs/clan-cli/clan_cli/flakes/__init__.py b/pkgs/clan-cli/clan_cli/flakes/__init__.py index 556233537..d1a5893a1 100644 --- a/pkgs/clan-cli/clan_cli/flakes/__init__.py +++ b/pkgs/clan-cli/clan_cli/flakes/__init__.py @@ -3,6 +3,7 @@ import argparse from clan_cli.flakes.add import register_add_parser from clan_cli.flakes.history import register_list_parser +from clan_cli.flakes.inspect import register_inspect_parser from .create import register_create_parser @@ -21,3 +22,5 @@ def register_parser(parser: argparse.ArgumentParser) -> None: register_add_parser(add_parser) list_parser = subparser.add_parser("list", help="List recently used flakes") register_list_parser(list_parser) + inspect_parser = subparser.add_parser("inspect", help="Inspect a clan flake") + register_inspect_parser(inspect_parser) diff --git a/pkgs/clan-cli/clan_cli/flakes/inspect.py b/pkgs/clan-cli/clan_cli/flakes/inspect.py new file mode 100644 index 000000000..89afe7307 --- /dev/null +++ b/pkgs/clan-cli/clan_cli/flakes/inspect.py @@ -0,0 +1,83 @@ +import argparse +import shlex +import subprocess +from dataclasses import dataclass +from pathlib import Path + +from ..errors import ClanError +from ..nix import nix_config, nix_eval, nix_metadata + + +@dataclass +class FlakeConfig: + flake_url: str | Path + flake_attr: str + + icon: str | None + description: str | None + last_updated: str + revision: str | None + + +def inspect_flake(flake_url: str | Path, flake_attr: str) -> FlakeConfig: + config = nix_config() + system = config["system"] + + cmd = nix_eval( + [ + f'{flake_url}#clanInternals.machines."{system}"."{flake_attr}".config.clanCore.clanIcon' + ] + ) + + proc = subprocess.run(cmd, check=True, text=True, stdout=subprocess.PIPE) + assert proc.stdout is not None + if proc.returncode != 0: + raise ClanError( + f""" +command: {shlex.join(cmd)} +exit code: {proc.returncode} +stdout: +{proc.stdout} +""" + ) + res = proc.stdout.strip() + if res == "null": + icon_path = None + else: + icon_path = res + + meta = nix_metadata(flake_url) + + return FlakeConfig( + flake_url=flake_url, + flake_attr=flake_attr, + icon=icon_path, + description=meta.get("description"), + last_updated=meta["lastModified"], + revision=meta.get("revision"), + ) + + +@dataclass +class InspectOptions: + machine: str + flake: Path + + +def inspect_command(args: argparse.Namespace) -> None: + inspect_options = InspectOptions( + machine=args.machine, + flake=args.flake or Path.cwd(), + ) + res = inspect_flake( + flake_url=inspect_options.flake, flake_attr=inspect_options.machine + ) + print("Icon:", res.icon) + print("Description:", res.description) + print("Last updated:", res.last_updated) + print("Revision:", res.revision) + + +def register_inspect_parser(parser: argparse.ArgumentParser) -> None: + parser.add_argument("--machine", type=str, default="defaultVM") + parser.set_defaults(func=inspect_command) diff --git a/pkgs/clan-cli/clan_cli/nix.py b/pkgs/clan-cli/clan_cli/nix.py index ae47a4f48..fb9837727 100644 --- a/pkgs/clan-cli/clan_cli/nix.py +++ b/pkgs/clan-cli/clan_cli/nix.py @@ -10,7 +10,6 @@ from .dirs import nixpkgs_flake, nixpkgs_source from .errors import ClanError -@deal.raises(ClanError) def nix_command(flags: list[str]) -> list[str]: return ["nix", "--extra-experimental-features", "nix-command flakes", *flags] @@ -28,7 +27,6 @@ def nix_flake_show(flake_url: str | Path) -> list[str]: ) -@deal.raises(ClanError) def nix_build( flags: list[str], ) -> list[str]: @@ -45,7 +43,6 @@ def nix_build( ) -@deal.raises(ClanError) def nix_config() -> dict[str, Any]: cmd = nix_command(["show-config", "--json"]) proc = subprocess.run(cmd, check=True, text=True, stdout=subprocess.PIPE) @@ -56,7 +53,6 @@ def nix_config() -> dict[str, Any]: return config -@deal.raises(ClanError) def nix_eval(flags: list[str]) -> list[str]: default_flags = nix_command( [ @@ -82,9 +78,8 @@ def nix_eval(flags: list[str]) -> list[str]: return default_flags + flags -@deal.raises(ClanError) -def nix_metadata(flake: str) -> dict[str, Any]: - cmd = nix_command(["flake", "metadata", "--json", flake]) +def nix_metadata(flake_url: str | Path) -> dict[str, Any]: + cmd = nix_command(["flake", "metadata", "--json", f"{flake_url}"]) proc = subprocess.run(cmd, check=True, text=True, stdout=subprocess.PIPE) data = json.loads(proc.stdout) return data diff --git a/pkgs/clan-cli/clan_cli/vms/inspect.py b/pkgs/clan-cli/clan_cli/vms/inspect.py index ae0efb377..b89285865 100644 --- a/pkgs/clan-cli/clan_cli/vms/inspect.py +++ b/pkgs/clan-cli/clan_cli/vms/inspect.py @@ -30,6 +30,7 @@ def inspect_vm(flake_url: str | Path, flake_attr: str) -> VmConfig: f'{flake_url}#clanInternals.machines."{system}"."{flake_attr}".config.system.clan.vm.config' ] ) + proc = subprocess.run(cmd, check=True, text=True, stdout=subprocess.PIPE) assert proc.stdout is not None if proc.returncode != 0: @@ -65,5 +66,5 @@ def inspect_command(args: argparse.Namespace) -> None: def register_inspect_parser(parser: argparse.ArgumentParser) -> None: - parser.add_argument("machine", type=str) + parser.add_argument("machine", type=str, default="defaultVM") parser.set_defaults(func=inspect_command) diff --git a/pkgs/clan-cli/default.nix b/pkgs/clan-cli/default.nix index 2e914609b..daa999419 100644 --- a/pkgs/clan-cli/default.nix +++ b/pkgs/clan-cli/default.nix @@ -37,7 +37,6 @@ , clan-core-path , writeShellScriptBin , nodePackages -, schemathesis ? null }: let @@ -155,6 +154,7 @@ python3.pkgs.buildPythonApplication { ${checkPython}/bin/python -m pytest -m "not impure and with_core" -s ./tests touch $out ''; + clan-pytest = runCommand "clan-pytest" { } '' echo ${clan-pytest-without-core} echo ${clan-pytest-with-core} diff --git a/pkgs/clan-cli/flake-module.nix b/pkgs/clan-cli/flake-module.nix index 27ace29dc..d0161cd66 100644 --- a/pkgs/clan-cli/flake-module.nix +++ b/pkgs/clan-cli/flake-module.nix @@ -31,14 +31,15 @@ { devShells.clan-cli = pkgs.callPackage ./shell.nix { inherit (self'.packages) clan-cli ui-assets nix-unit; - + # inherit (inputs) democlan; }; packages = { clan-cli = pkgs.python3.pkgs.callPackage ./default.nix { inherit (self'.packages) ui-assets; inherit (inputs) nixpkgs; + # inherit (inputs) democlan; inherit (inputs.nixpkgs-for-deal.legacyPackages.${system}.python3Packages) deal; - inherit (inputs.nixpkgs-for-deal.legacyPackages.${system}.python3Packages) schemathesis; + #inherit (inputs.nixpkgs-for-deal.legacyPackages.${system}.python3Packages) schemathesis; clan-core-path = clanCoreWithVendoredDeps; }; default = self'.packages.clan-cli; diff --git a/pkgs/clan-cli/tests/fixtures_flakes.py b/pkgs/clan-cli/tests/fixtures_flakes.py index 120298c87..948d953bf 100644 --- a/pkgs/clan-cli/tests/fixtures_flakes.py +++ b/pkgs/clan-cli/tests/fixtures_flakes.py @@ -118,6 +118,24 @@ def test_flake_with_core( ) +@pytest.fixture +def test_local_democlan( + monkeypatch: pytest.MonkeyPatch, temporary_home: Path +) -> Iterator[FlakeForTest]: + democlan = os.getenv(key="DEMOCLAN_ROOT") + if democlan is None: + raise Exception( + "DEMOCLAN_ROOT not set. This test requires the democlan flake to be present" + ) + democlan_p = Path(democlan).resolve() + if not democlan_p.is_dir(): + raise Exception( + f"DEMOCLAN_ROOT ({democlan_p}) is not a directory. This test requires the democlan directory to be present" + ) + + yield FlakeForTest(democlan_p) + + @pytest.fixture def test_democlan_url( monkeypatch: pytest.MonkeyPatch, temporary_home: Path diff --git a/pkgs/clan-cli/tests/test_clan_uri.py b/pkgs/clan-cli/tests/test_clan_uri.py index 9170c8279..9780e43f2 100644 --- a/pkgs/clan-cli/tests/test_clan_uri.py +++ b/pkgs/clan-cli/tests/test_clan_uri.py @@ -6,6 +6,21 @@ from clan_cli.clan_uri import ClanParameters, ClanScheme, ClanURI from clan_cli.errors import ClanError +def test_get_internal() -> None: + # Create a ClanURI object from a remote URI with parameters + uri = ClanURI("clan://https://example.com?flake_attr=myVM&password=1234") + assert uri.get_internal() == "https://example.com?password=1234" + + uri = ClanURI("clan://~/Downloads") + assert uri.get_internal() == "~/Downloads" + + uri = ClanURI("clan:///home/user/Downloads") + assert uri.get_internal() == "/home/user/Downloads" + + uri = ClanURI("clan://file:///home/user/Downloads") + assert uri.get_internal() == "/home/user/Downloads" + + def test_local_uri() -> None: # Create a ClanURI object from a local URI uri = ClanURI("clan://file:///home/user/Downloads") diff --git a/pkgs/clan-cli/tests/test_flakes_cli.py b/pkgs/clan-cli/tests/test_flakes_cli.py index 4b4984551..da9d0abee 100644 --- a/pkgs/clan-cli/tests/test_flakes_cli.py +++ b/pkgs/clan-cli/tests/test_flakes_cli.py @@ -1,6 +1,7 @@ import json from typing import TYPE_CHECKING +import pytest from cli import Cli from fixtures_flakes import FlakeForTest from pytest import CaptureFixture @@ -46,3 +47,23 @@ def test_flakes_list( cli.run(["flakes", "add", str(test_flake.path)]) cli.run(cmd) assert str(test_flake.path) in capsys.readouterr().out + + +@pytest.mark.impure +def test_flakes_inspect( + test_flake_with_core: FlakeForTest, capsys: pytest.CaptureFixture +) -> None: + cli = Cli() + cli.run( + [ + "--flake", + str(test_flake_with_core.path), + "flakes", + "inspect", + "--machine", + "vm1", + ] + ) + out = capsys.readouterr() # empty the buffer + + assert "Icon" in out.out diff --git a/pkgs/clan-cli/tests/test_with_deal.py b/pkgs/clan-cli/tests/test_with_deal.py index e28c34ae9..6d83e8518 100644 --- a/pkgs/clan-cli/tests/test_with_deal.py +++ b/pkgs/clan-cli/tests/test_with_deal.py @@ -3,26 +3,6 @@ import deal from clan_cli import nix -@deal.cases(nix.nix_command) -def test_nix_command(case: deal.TestCase) -> None: - case() - - -@deal.cases(nix.nix_build) -def test_nix_build(case: deal.TestCase) -> None: - case() - - -@deal.cases(nix.nix_config) -def test_nix_config(case: deal.TestCase) -> None: - case() - - -@deal.cases(nix.nix_eval) -def test_nix_eval(case: deal.TestCase) -> None: - case() - - @deal.cases(nix.nix_shell) def test_nix_shell(case: deal.TestCase) -> None: case()