Add Pacman module
This commit is contained in:
parent
a6998456df
commit
b7ebc8543c
6 changed files with 119 additions and 13 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
from . import modules
|
from . import modules
|
||||||
from .cmd import run_capture, run_check
|
from .cmd import run_capture, run_execute
|
||||||
from .orchestrator import Module, Orchestrator
|
from .orchestrator import Module, Orchestrator
|
||||||
|
|
||||||
__all__: list[str] = [
|
__all__: list[str] = [
|
||||||
|
|
@ -7,5 +7,5 @@ __all__: list[str] = [
|
||||||
"Orchestrator",
|
"Orchestrator",
|
||||||
"modules",
|
"modules",
|
||||||
"run_capture",
|
"run_capture",
|
||||||
"run_check",
|
"run_execute",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,12 @@ from rich import print
|
||||||
from rich.markup import escape
|
from rich.markup import escape
|
||||||
|
|
||||||
|
|
||||||
def run_check(*cmd: str) -> None:
|
def run_execute(*cmd: str) -> None:
|
||||||
print(f"[bright_black]$ {escape(shlex.join(cmd))}")
|
print(f"[bright_black]$ {escape(shlex.join(cmd))}")
|
||||||
subprocess.run(cmd, check=True)
|
subprocess.run(cmd, check=True)
|
||||||
|
|
||||||
|
|
||||||
def run_capture(*cmd: str) -> str:
|
def run_capture(*cmd: str) -> str:
|
||||||
print(f"[bright_black]$ {escape(shlex.join(cmd))}")
|
print(f"[bright_black italic]$ {escape(shlex.join(cmd))}")
|
||||||
result = subprocess.run(cmd, check=True, capture_output=True, encoding="utf-8")
|
result = subprocess.run(cmd, check=True, capture_output=True, encoding="utf-8")
|
||||||
return result.stdout
|
return result.stdout
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
from .echo import Echo
|
from .echo import Echo
|
||||||
|
from .pacman import Pacman
|
||||||
|
|
||||||
__all__: list[str] = [
|
__all__: list[str] = [
|
||||||
"Echo",
|
"Echo",
|
||||||
|
"Pacman",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from pasch.cmd import run_check
|
from pasch.cmd import run_execute
|
||||||
from pasch.orchestrator import Module, Orchestrator
|
from pasch.orchestrator import Module, Orchestrator
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -11,4 +11,4 @@ class Echo(Module):
|
||||||
self.args.append(arg)
|
self.args.append(arg)
|
||||||
|
|
||||||
def realize(self) -> None:
|
def realize(self) -> None:
|
||||||
run_check("echo", *self.args)
|
run_execute("echo", *self.args)
|
||||||
|
|
|
||||||
98
pasch/modules/pacman.py
Normal file
98
pasch/modules/pacman.py
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
from rich import print
|
||||||
|
from rich.markup import escape
|
||||||
|
|
||||||
|
from pasch.cmd import run_capture, run_execute
|
||||||
|
from pasch.orchestrator import Module, Orchestrator
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PacmanPackage:
|
||||||
|
name: str
|
||||||
|
exclude: set[str] = field(default_factory=set)
|
||||||
|
|
||||||
|
|
||||||
|
class Pacman(Module):
|
||||||
|
def __init__(self, orchestrator: Orchestrator) -> None:
|
||||||
|
super().__init__(orchestrator)
|
||||||
|
self.binary: str = "pacman"
|
||||||
|
self.packages: set[str] = set()
|
||||||
|
self.excluded: dict[str, set[str]] = {}
|
||||||
|
|
||||||
|
def install(self, *packages: str) -> None:
|
||||||
|
self.packages.update(packages)
|
||||||
|
|
||||||
|
def exclude(self, group: str, *packages: str) -> None:
|
||||||
|
self.excluded.setdefault(group, set()).update(packages)
|
||||||
|
|
||||||
|
def realize(self) -> None:
|
||||||
|
groups = self._get_groups()
|
||||||
|
|
||||||
|
installed = self._get_explicitly_installed_packages()
|
||||||
|
target = self._resolve_packages(groups, self.packages)
|
||||||
|
|
||||||
|
to_install = target - installed
|
||||||
|
to_uninstall = installed - target
|
||||||
|
|
||||||
|
for package in sorted(to_install):
|
||||||
|
print(f"[bold green]+[/] {escape(package)}")
|
||||||
|
for package in sorted(to_uninstall):
|
||||||
|
print(f"[bold red]-[/] {escape(package)}")
|
||||||
|
|
||||||
|
self._install_packages(to_install)
|
||||||
|
self._uninstall_packages(to_uninstall)
|
||||||
|
|
||||||
|
def _pacman_capture(self, *args: str) -> str:
|
||||||
|
return run_capture(self.binary, *args)
|
||||||
|
|
||||||
|
def _pacman_execute(self, *args: str) -> None:
|
||||||
|
if self.binary == "paru":
|
||||||
|
run_execute(self.binary, *args) # Calls sudo itself
|
||||||
|
else:
|
||||||
|
run_execute("sudo", self.binary, *args)
|
||||||
|
|
||||||
|
def _get_explicitly_installed_packages(self) -> set[str]:
|
||||||
|
return set(self._pacman_capture("-Qqe").splitlines())
|
||||||
|
|
||||||
|
def _get_groups(self) -> dict[str, set[str]]:
|
||||||
|
groups = {}
|
||||||
|
for line in self._pacman_capture("-Sgg").splitlines():
|
||||||
|
group, package = line.split(" ", maxsplit=1)
|
||||||
|
groups.setdefault(group, set()).add(package)
|
||||||
|
return groups
|
||||||
|
|
||||||
|
def _resolve_packages(
|
||||||
|
self,
|
||||||
|
groups: dict[str, set[str]],
|
||||||
|
packages: set[str],
|
||||||
|
) -> set[str]:
|
||||||
|
result = set()
|
||||||
|
for package in packages:
|
||||||
|
result.update(self._resolve_package(groups, package))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _resolve_package(self, groups: dict[str, set[str]], package: str) -> set[str]:
|
||||||
|
packages = groups.get(package)
|
||||||
|
if packages is None:
|
||||||
|
return {package}
|
||||||
|
packages = packages - self.excluded.get(package, set())
|
||||||
|
return self._resolve_packages(groups, packages)
|
||||||
|
|
||||||
|
def _install_packages(self, packages: set[str]) -> None:
|
||||||
|
if self.orchestrator.dry_run:
|
||||||
|
return
|
||||||
|
if not packages:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._pacman_execute("-S", "--needed", *sorted(packages))
|
||||||
|
self._pacman_execute("-D", "--asexplicit", *sorted(packages))
|
||||||
|
|
||||||
|
def _uninstall_packages(self, packages: set[str]) -> None:
|
||||||
|
if self.orchestrator.dry_run:
|
||||||
|
return
|
||||||
|
if not packages:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._pacman_execute("-D", "--asdeps", *sorted(packages))
|
||||||
|
self._pacman_execute("-Rsn", *self._pacman_capture("-Qqdt").splitlines())
|
||||||
|
|
@ -2,6 +2,9 @@ from __future__ import annotations
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
from rich import print
|
||||||
|
from rich.markup import escape
|
||||||
|
|
||||||
|
|
||||||
class Module(ABC):
|
class Module(ABC):
|
||||||
def __init__(self, orchestrator: Orchestrator) -> None:
|
def __init__(self, orchestrator: Orchestrator) -> None:
|
||||||
|
|
@ -13,16 +16,19 @@ class Module(ABC):
|
||||||
|
|
||||||
|
|
||||||
class Orchestrator:
|
class Orchestrator:
|
||||||
def __init__(self) -> None:
|
def __init__(self, dry_run: bool = False) -> None:
|
||||||
self.frozen: bool = False
|
self.dry_run = dry_run
|
||||||
self.modules: list[Module] = []
|
|
||||||
|
self._frozen: bool = False
|
||||||
|
self._modules: list[Module] = []
|
||||||
|
|
||||||
def register(self, module: Module) -> None:
|
def register(self, module: Module) -> None:
|
||||||
if self.frozen:
|
if self._frozen:
|
||||||
raise Exception("registering module wile orchestrator is frozen")
|
raise Exception("registering module wile orchestrator is frozen")
|
||||||
self.modules.append(module)
|
self._modules.append(module)
|
||||||
|
|
||||||
def realize(self) -> None:
|
def realize(self) -> None:
|
||||||
self.frozen = True
|
self._frozen = True
|
||||||
for module in reversed(self.modules):
|
for module in reversed(self._modules):
|
||||||
|
print(f"[bold bright_magenta]\\[{escape(type(module).__name__)}]")
|
||||||
module.realize()
|
module.realize()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue