migrate clan history to clan-vm-manager

this functionality is not really useful or used in clan-vm-manager and
therefore should live in the clan-vm-manager.

Not porting the test for now because we probably get rid of the clan-vm-manager soon in favour of the UI.
This commit is contained in:
Jörg Thalheim
2025-04-16 14:22:45 +02:00
parent f4792109ec
commit 0b4e896af3
13 changed files with 79 additions and 111 deletions

View File

@@ -125,7 +125,6 @@ nav:
- reference/cli/facts.md - reference/cli/facts.md
- reference/cli/flakes.md - reference/cli/flakes.md
- reference/cli/flash.md - reference/cli/flash.md
- reference/cli/history.md
- reference/cli/machines.md - reference/cli/machines.md
- reference/cli/select.md - reference/cli/select.md
- reference/cli/secrets.md - reference/cli/secrets.md

View File

@@ -16,7 +16,6 @@ __all__ = ["admin", "directory", "disk", "iwd", "mdns_discovery", "modules", "up
from . import ( from . import (
backups, backups,
clan, clan,
history,
secrets, secrets,
select, select,
state, state,
@@ -372,12 +371,6 @@ For more detailed information, visit: {help_hyperlink("deploy", "https://docs.cl
) )
vms.register_parser(parser_vms) vms.register_parser(parser_vms)
parser_history = subparsers.add_parser(
"history",
description="manage history",
)
history.register_parser(parser_history)
parser_select = subparsers.add_parser( parser_select = subparsers.add_parser(
"select", "select",
aliases=["se"], aliases=["se"],

View File

@@ -1,19 +0,0 @@
# !/usr/bin/env python3
import argparse
from .add import register_add_parser
from .list import register_list_parser
# takes a (sub)parser and configures it
def register_parser(parser: argparse.ArgumentParser) -> None:
subparser = parser.add_subparsers(
title="command",
description="the command to run",
help="the command to run",
required=True,
)
add_parser = subparser.add_parser("add", help="Add a clan flake")
register_add_parser(add_parser)
list_parser = subparser.add_parser("list", help="List recently used flakes")
register_list_parser(list_parser)

View File

@@ -1,26 +0,0 @@
# !/usr/bin/env python3
import argparse
from datetime import datetime
from .add import HistoryEntry, list_history
def list_history_command(args: argparse.Namespace) -> None:
res: dict[str, list[HistoryEntry]] = {}
for history_entry in list_history():
url = str(history_entry.flake.flake_url)
if res.get(url) is None:
res[url] = []
res[url].append(history_entry)
for flake_url, entries in res.items():
print(flake_url)
for entry in entries:
d = datetime.fromisoformat(entry.last_used)
last_used = d.strftime("%d/%m/%Y %H:%M:%S")
print(f" {entry.flake.flake_attr} ({last_used})")
# takes a (sub)parser and configures it
def register_list_parser(parser: argparse.ArgumentParser) -> None:
parser.set_defaults(func=list_history_command)

View File

@@ -1,47 +0,0 @@
import json
from typing import TYPE_CHECKING
import pytest
from clan_cli.dirs import user_history_file
from clan_cli.history.add import HistoryEntry
from clan_cli.tests.fixtures_flakes import FlakeForTest
from clan_cli.tests.helpers import cli
from clan_cli.tests.stdout import CaptureOutput
if TYPE_CHECKING:
pass
@pytest.mark.impure
def test_history_add(
test_flake_with_core: FlakeForTest,
) -> None:
cmd = [
"history",
"add",
f"clan://{test_flake_with_core.path}#vm1",
]
cli.run(cmd)
history_file = user_history_file()
assert history_file.exists()
history = [
HistoryEntry.from_json(entry) for entry in json.loads(history_file.read_text())
]
assert str(history[0].flake.flake_url) == str(test_flake_with_core.path)
@pytest.mark.impure
def test_history_list(
capture_output: CaptureOutput,
test_flake_with_core: FlakeForTest,
) -> None:
with capture_output as output:
cli.run(["history", "list"])
assert str(test_flake_with_core.path) not in output.out
cli.run(["history", "add", f"clan://{test_flake_with_core.path}#vm1"])
with capture_output as output:
cli.run(["history", "list"])
assert str(test_flake_with_core.path) in output.out

View File

@@ -15,12 +15,12 @@ import gi
from clan_cli import vms from clan_cli import vms
from clan_cli.clan_uri import ClanURI from clan_cli.clan_uri import ClanURI
from clan_cli.dirs import vm_state_dir from clan_cli.dirs import vm_state_dir
from clan_cli.history.add import HistoryEntry
from clan_cli.machines.machines import Machine from clan_cli.machines.machines import Machine
from clan_cli.vms.inspect import inspect_vm from clan_cli.vms.inspect import inspect_vm
from clan_cli.vms.qemu import QMPWrapper from clan_cli.vms.qemu import QMPWrapper
from clan_vm_manager.components.executor import MPProcess, spawn from clan_vm_manager.components.executor import MPProcess, spawn
from clan_vm_manager.history import HistoryEntry
from clan_vm_manager.singletons.toast import ( from clan_vm_manager.singletons.toast import (
InfoToast, InfoToast,
SuccessToast, SuccessToast,

View File

@@ -1,4 +1,3 @@
# !/usr/bin/env python3
import argparse import argparse
import dataclasses import dataclasses
import datetime import datetime
@@ -113,12 +112,46 @@ def add_history_command(args: argparse.Namespace) -> None:
add_history(args.uri) add_history(args.uri)
# takes a (sub)parser and configures it def list_history_command(args: argparse.Namespace) -> None:
def register_add_parser(parser: argparse.ArgumentParser) -> None: res: dict[str, list[HistoryEntry]] = {}
parser.add_argument( for history_entry in list_history():
url = str(history_entry.flake.flake_url)
if res.get(url) is None:
res[url] = []
res[url].append(history_entry)
for flake_url, entries in res.items():
print(flake_url)
for entry in entries:
d = datetime.datetime.fromisoformat(entry.last_used)
last_used = d.strftime("%d/%m/%Y %H:%M:%S")
print(f" {entry.flake.flake_attr} ({last_used})")
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="clan history",
description="Manage clan history",
)
subparser = parser.add_subparsers(
title="command",
description="the command to run",
help="the command to run",
required=True,
)
add_parser = subparser.add_parser("add", help="Add a clan flake")
add_parser.add_argument(
"uri", type=ClanURI.from_str, help="Path to the flake", default="." "uri", type=ClanURI.from_str, help="Path to the flake", default="."
) )
parser.add_argument( add_parser.add_argument(
"--all", help="Add all machines", default=False, action="store_true" "--all", help="Add all machines", default=False, action="store_true"
) )
parser.set_defaults(func=add_history_command) add_parser.set_defaults(func=add_history_command)
list_parser = subparser.add_parser("list", help="List recently used flakes")
list_parser.set_defaults(func=list_history_command)
return parser.parse_args()
def main() -> None:
args = parse_args()
args.func(args)

View File

@@ -5,10 +5,10 @@ from typing import Any, ClassVar, cast
import gi import gi
from clan_cli.clan_uri import ClanURI from clan_cli.clan_uri import ClanURI
from clan_cli.history.add import HistoryEntry, add_history
from clan_cli.machines.machines import Machine from clan_cli.machines.machines import Machine
from clan_vm_manager.components.gkvstore import GKVStore from clan_vm_manager.components.gkvstore import GKVStore
from clan_vm_manager.history import HistoryEntry, add_history
from clan_vm_manager.singletons.use_vms import ClanStore from clan_vm_manager.singletons.use_vms import ClanStore
gi.require_version("Gtk", "4.0") gi.require_version("Gtk", "4.0")

View File

@@ -6,12 +6,12 @@ from typing import Any, ClassVar
import gi import gi
from clan_cli.clan_uri import ClanURI from clan_cli.clan_uri import ClanURI
from clan_cli.flake import Flake from clan_cli.flake import Flake
from clan_cli.history.add import HistoryEntry
from clan_cli.machines.machines import Machine from clan_cli.machines.machines import Machine
from clan_vm_manager import assets from clan_vm_manager import assets
from clan_vm_manager.components.gkvstore import GKVStore from clan_vm_manager.components.gkvstore import GKVStore
from clan_vm_manager.components.vmobj import VMObject from clan_vm_manager.components.vmobj import VMObject
from clan_vm_manager.history import HistoryEntry
from clan_vm_manager.singletons.use_views import ViewStack from clan_vm_manager.singletons.use_views import ViewStack
from clan_vm_manager.views.logs import Logs from clan_vm_manager.views.logs import Logs

View File

@@ -2,9 +2,9 @@ import logging
import threading import threading
import gi import gi
from clan_cli.history.list import list_history
from clan_vm_manager.components.interfaces import ClanConfig from clan_vm_manager.components.interfaces import ClanConfig
from clan_vm_manager.history import list_history
from clan_vm_manager.singletons.toast import ToastOverlay from clan_vm_manager.singletons.toast import ToastOverlay
from clan_vm_manager.singletons.use_views import ViewStack from clan_vm_manager.singletons.use_views import ViewStack
from clan_vm_manager.singletons.use_vms import ClanStore from clan_vm_manager.singletons.use_vms import ClanStore

View File

@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
name = "clan-vm-manager" name = "clan-vm-manager"
description = "clan vm manager" description = "clan vm manager"
dynamic = ["version"] dynamic = ["version"]
scripts = { clan-vm-manager = "clan_vm_manager:main" } scripts = { clan-vm-manager = "clan_vm_manager:main", clan-vm-manager-history = "clan_vm_manager.history:main" }
[project.urls] [project.urls]
Homepage = "https://clan.lol/" Homepage = "https://clan.lol/"

View File

@@ -16,6 +16,7 @@ pytest_plugins = [
"root", "root",
"command", "command",
"wayland", "wayland",
"stdout",
] ]

View File

@@ -0,0 +1,34 @@
import types
import pytest
class CaptureOutput:
def __init__(self, capsys: pytest.CaptureFixture) -> None:
self.capsys = capsys
self.capsys_disabled = capsys.disabled()
self.capsys_disabled.__enter__()
def __enter__(self) -> "CaptureOutput":
self.capsys_disabled.__exit__(None, None, None)
self.capsys.readouterr()
return self
def __exit__(
self,
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
traceback: types.TracebackType | None,
) -> None:
res = self.capsys.readouterr()
self.out = res.out
self.err = res.err
# Disable capsys again
self.capsys_disabled = self.capsys.disabled()
self.capsys_disabled.__enter__()
@pytest.fixture
def capture_output(capsys: pytest.CaptureFixture) -> CaptureOutput:
return CaptureOutput(capsys)