# !/usr/bin/env python3 import argparse import dataclasses import datetime import json import logging from typing import Any from clan_cli.clan.inspect import FlakeConfig, inspect_flake from clan_cli.machines.list import list_machines from ..clan_uri import ClanURI from ..dirs import user_history_file from ..errors import ClanError from ..locked_open import read_history_file, write_history_file log = logging.getLogger(__name__) @dataclasses.dataclass class HistoryEntry: last_used: str flake: FlakeConfig settings: dict[str, Any] = dataclasses.field(default_factory=dict) def __post_init__(self) -> None: if isinstance(self.flake, dict): self.flake = FlakeConfig(**self.flake) def _merge_dicts(d1: dict, d2: dict) -> dict: # create a new dictionary that copies d1 merged = dict(d1) # iterate over the keys and values of d2 for key, value in d2.items(): # if the key is in d1 and both values are dictionaries, merge them recursively if key in d1 and isinstance(d1[key], dict) and isinstance(value, dict): merged[key] = _merge_dicts(d1[key], value) # otherwise, update the value of the key in the merged dictionary else: merged[key] = value # return the merged dictionary return merged def list_history() -> list[HistoryEntry]: logs: list[HistoryEntry] = [] if not user_history_file().exists(): return [] try: parsed = read_history_file() for i, p in enumerate(parsed.copy()): # Everything from the settings dict is merged into the flake dict, and can override existing values parsed[i] = _merge_dicts(p, p.get("settings", {})) logs = [HistoryEntry(**p) for p in parsed] except (json.JSONDecodeError, TypeError) as ex: raise ClanError(f"History file at {user_history_file()} is corrupted") from ex return logs def new_history_entry(url: str, machine: str) -> HistoryEntry: flake = inspect_flake(url, machine) flake.flake_url = str(flake.flake_url) return HistoryEntry( flake=flake, last_used=datetime.datetime.now().isoformat(), ) def add_all_to_history(uri: ClanURI) -> list[HistoryEntry]: history = list_history() new_entries: list[HistoryEntry] = [] for machine in list_machines(uri.get_url()): new_entry = _add_maschine_to_history_list(uri.get_url(), machine, history) new_entries.append(new_entry) write_history_file(history) return new_entries def add_history(uri: ClanURI) -> HistoryEntry: user_history_file().parent.mkdir(parents=True, exist_ok=True) history = list_history() new_entry = _add_maschine_to_history_list(uri.get_url(), uri.machine.name, history) write_history_file(history) return new_entry def _add_maschine_to_history_list( uri_path: str, uri_machine: str, entries: list[HistoryEntry] ) -> HistoryEntry: for new_entry in entries: if ( new_entry.flake.flake_url == str(uri_path) and new_entry.flake.flake_attr == uri_machine ): new_entry.last_used = datetime.datetime.now().isoformat() return new_entry new_entry = new_history_entry(uri_path, uri_machine) entries.append(new_entry) return new_entry def add_history_command(args: argparse.Namespace) -> None: if args.all: add_all_to_history(args.uri) else: add_history(args.uri) # takes a (sub)parser and configures it def register_add_parser(parser: argparse.ArgumentParser) -> None: parser.add_argument("uri", type=ClanURI, help="Path to the flake", default=".") parser.add_argument( "--all", help="Add all machines", default=False, action="store_true" ) parser.set_defaults(func=add_history_command)