feat: prompt installing optional components

This commit is contained in:
2 * r + 2 * t
2026-06-14 23:06:47 +10:00
parent efd59b79d9
commit 994f2d86f5
3 changed files with 64 additions and 18 deletions
+48 -5
View File
@@ -10,7 +10,7 @@ from caelestia.utils.dots.manifest import ComponentError, Manifest, ManifestErro
from caelestia.utils.dots.packages import DEFAULT_AUR_HELPER, PackageInstaller from caelestia.utils.dots.packages import DEFAULT_AUR_HELPER, PackageInstaller
from caelestia.utils.dots.source import DotsSource, SourceError from caelestia.utils.dots.source import DotsSource, SourceError
from caelestia.utils.dots.state import DotsState from caelestia.utils.dots.state import DotsState
from caelestia.utils.io import confirm, disable_input, fatal, info, log, pause, warn from caelestia.utils.io import PROMPT_COLOUR, confirm, disable_input, fatal, format_msg, info, log, pause, prompt, warn
from caelestia.utils.paths import ( from caelestia.utils.paths import (
config_backup_dir, config_backup_dir,
config_dir, config_dir,
@@ -102,12 +102,14 @@ class Command:
except SourceError as e: except SourceError as e:
fatal(e) fatal(e)
enable = _parse_list_arg(self.args.enable_components)
disable = _parse_list_arg(self.args.disable_components)
try: try:
manifest = source.manifest_at(tip) manifest = source.manifest_at(tip)
manifest.resolve_components( manifest.resolve_components(enable=enable, disable=disable)
enable=_parse_list_arg(self.args.enable_components),
disable=_parse_list_arg(self.args.disable_components), if enable is None and disable is None:
) self.prompt_optional_components(manifest)
except (SourceError, ManifestError, ComponentError) as e: except (SourceError, ManifestError, ComponentError) as e:
fatal(e) fatal(e)
@@ -116,6 +118,47 @@ class Command:
return source, tip, manifest return source, tip, manifest
def prompt_optional_components(self, manifest: Manifest) -> None:
comp_arr = manifest.disabled_components
if not comp_arr:
return
print(format_msg(PROMPT_COLOUR, "Components to enable?"))
for i, comp in enumerate(comp_arr):
print(format_msg(PROMPT_COLOUR, f" [{i + 1}] {comp}"))
print(format_msg(PROMPT_COLOUR, "[A]ll or (1 2 3, 1-3, ^4)"))
ans = prompt("", end="").lower().strip()
def _valid_v(v: str) -> int:
try:
i_v = int(v, base=10) - 1 # -1 to translate to 0 index
except ValueError:
fatal(f'Invalid input. Given value "{v}" must be an integer.')
if i_v < 0 or i_v >= len(comp_arr):
fatal(f'Invalid input. Given value "{v}" must be between 1 and {len(comp_arr)} inclusive.')
return i_v
if ans in ("a", "all"):
manifest.resolve_components(enable=list(manifest.components))
elif ans:
enabled: list[str] = []
toks = ans.split()
for tok in toks:
fr, sep, to = tok.partition("-")
if sep:
fr = _valid_v(fr)
to = _valid_v(to)
if fr > to:
fatal(f'Invalid input. Given range "{tok}" must be lo-hi.')
enabled += comp_arr[fr : to + 1]
elif tok.startswith("^"):
t = _valid_v(tok[1:])
enabled += comp_arr[:t] + comp_arr[t + 1 :]
else:
t = _valid_v(tok)
enabled.append(comp_arr[t])
manifest.resolve_components(enable=list(set(enabled)))
def deploy_configs(self, source: DotsSource, manifest: Manifest) -> None: def deploy_configs(self, source: DotsSource, manifest: Manifest) -> None:
log("Installing configs...") log("Installing configs...")
deployer = Deployer() deployer = Deployer()
+2 -5
View File
@@ -68,7 +68,6 @@ class ManifestComponent:
@dataclass @dataclass
class _ManifestData: class _ManifestData:
resolved_comps: bool = False
enabled_comps: list[str] = field(default_factory=list) enabled_comps: list[str] = field(default_factory=list)
disabled_comps: list[str] = field(default_factory=list) disabled_comps: list[str] = field(default_factory=list)
@@ -121,9 +120,6 @@ class Manifest:
) -> None: ) -> None:
"""Resolves enabled/disabled components. This MUST be called before calling any other method.""" """Resolves enabled/disabled components. This MUST be called before calling any other method."""
if self._data.resolved_comps:
return
enable_set = set(enable or []) enable_set = set(enable or [])
disable_set = set(disable or []) disable_set = set(disable or [])
known = set(self.components) known = set(self.components)
@@ -140,12 +136,13 @@ class Manifest:
enabled |= enable_set enabled |= enable_set
enabled -= disable_set enabled -= disable_set
self._data.enabled_comps.clear()
self._data.disabled_comps.clear()
for name in self.components: for name in self.components:
if name in enabled: if name in enabled:
self._data.enabled_comps.append(name) self._data.enabled_comps.append(name)
else: else:
self._data.disabled_comps.append(name) self._data.disabled_comps.append(name)
self._data.resolved_comps = True
def enabled_entries(self) -> list[ManifestEntry]: def enabled_entries(self) -> list[ManifestEntry]:
"""The entries of every enabled component.""" """The entries of every enabled component."""
+14 -8
View File
@@ -1,6 +1,12 @@
import sys import sys
from typing import Never from typing import Never
LOG_COLOUR: int = 2
INFO_COLOUR: int = 0
PROMPT_COLOUR: int = 36
WARNING_COLOUR: int = 33
ERROR_COLOUR: int = 31
_disable_input: bool = False _disable_input: bool = False
@@ -25,28 +31,28 @@ def log_exception(func):
return wrapper return wrapper
def _format_msg(colour: int, msg: str) -> str: def format_msg(colour: int, msg: str) -> str:
return f"\033[{colour}m:: {msg}\033[0m" return f"\033[{colour}m:: {msg}\033[0m"
def log(msg: str) -> None: def log(msg: str) -> None:
print(_format_msg(2, msg)) print(format_msg(LOG_COLOUR, msg))
def info(msg: str) -> None: def info(msg: str) -> None:
print(_format_msg(0, msg)) print(format_msg(INFO_COLOUR, msg))
def warn(msg: str) -> None: def warn(msg: str) -> None:
print(_format_msg(33, f"Warning: {msg}")) print(format_msg(WARNING_COLOUR, f"Warning: {msg}"))
def error(err: str | Exception) -> None: def error(err: str | Exception) -> None:
print(_format_msg(31, f"Error: {err}"), file=sys.stderr) print(format_msg(ERROR_COLOUR, f"Error: {err}"), file=sys.stderr)
def fatal(err: str | Exception) -> Never: def fatal(err: str | Exception) -> Never:
print(_format_msg(31, f"Fatal: {err}"), file=sys.stderr) print(format_msg(ERROR_COLOUR, f"Fatal: {err}"), file=sys.stderr)
sys.exit(1) sys.exit(1)
@@ -62,8 +68,8 @@ def _input(prompt: str) -> str:
raise KeyboardInterrupt() raise KeyboardInterrupt()
def prompt(msg: str) -> str: def prompt(msg: str, end: str = " ") -> str:
return _input(_format_msg(36, msg) + " ") return _input(format_msg(PROMPT_COLOUR, msg) + end)
def confirm(msg: str, default: bool = True) -> bool: def confirm(msg: str, default: bool = True) -> bool: