"""Receipts: the record of exactly what an install placed, so updates know what they're replacing and removal can take every piece back out.""" from dataclasses import asdict, dataclass, field from pathlib import Path import tomlkit from Core import ReceiptsDir @dataclass class PlacedFile: Path: str Kind: str # "Template", "Config" or "ConfigNew" (a .new file left beside a config) Root: bool = False # Where a file that already existed at Path (not placed by SaraSuite) was # moved to. It's put back when this program is removed. Displaced: str = "" @dataclass class IniRecord: File: str Section: str Key: str Value: str HadPrevious: bool = False Previous: str = "" @dataclass class Receipt: Name: str Version: str = "" Commit: str = "" ManifestHash: str = "" RequirementsHash: str = "" InstalledAt: str = "" UpdatedAt: str = "" # False while an install/update is still running, or if it failed partway. # The files it lists are still real, so Remove still cleans them up. Complete: bool = False Options: dict[str, bool] = field(default_factory=dict) Answers: dict[str, str] = field(default_factory=dict) Files: list[PlacedFile] = field(default_factory=list) IniEdits: list[IniRecord] = field(default_factory=list) EnabledServices: list[str] = field(default_factory=list) @property def FilePath(self) -> Path: return ReceiptsDir / f"{self.Name}.toml" def FindFile(self, TargetPath: str) -> PlacedFile | None: return next((File for File in self.Files if File.Path == TargetPath), None) def FindIni(self, File: str, Section: str, Key: str) -> IniRecord | None: return next( (Edit for Edit in self.IniEdits if (Edit.File, Edit.Section, Edit.Key) == (File, Section, Key)), None, ) def Save(self) -> None: ReceiptsDir.mkdir(parents=True, exist_ok=True) self.FilePath.write_text(tomlkit.dumps(asdict(self))) def LoadReceipt(Name: str) -> Receipt | None: ReceiptPath = ReceiptsDir / f"{Name}.toml" if not ReceiptPath.exists(): return None Data = tomlkit.parse(ReceiptPath.read_text()).unwrap() Data["Files"] = [PlacedFile(**Item) for Item in Data.get("Files", [])] Data["IniEdits"] = [IniRecord(**Item) for Item in Data.get("IniEdits", [])] Known = set(Receipt.__dataclass_fields__) return Receipt(**{Key: Value for Key, Value in Data.items() if Key in Known})