Files
gniza4linux/tui/config.py
shuki 587149f062 Add Python Textual TUI replacing gum-based bash TUI
New tui/ package with 14 screens (main menu, backup, restore, targets,
remotes, snapshots, verify, retention, schedule, logs, settings, wizard),
3 custom widgets (folder picker, confirm dialog, operation log), async
backend wrapper, pure-Python config parser, and TCSS theme.

bin/gniza now launches Textual TUI when available, falls back to gum.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 23:39:48 +02:00

93 lines
2.5 KiB
Python

import os
import re
from pathlib import Path
_KV_RE = re.compile(r'^([A-Z_][A-Z_0-9]*)=(.*)')
_QUOTED_RE = re.compile(r'^"(.*)"$|^\'(.*)\'$')
def _get_config_dir() -> Path:
if os.geteuid() == 0:
return Path("/etc/gniza")
xdg = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
return Path(xdg) / "gniza"
def _get_log_dir() -> Path:
if os.geteuid() == 0:
return Path("/var/log/gniza")
xdg = os.environ.get("XDG_STATE_HOME", os.path.expanduser("~/.local/state"))
return Path(xdg) / "gniza" / "log"
CONFIG_DIR = _get_config_dir()
LOG_DIR = _get_log_dir()
def parse_conf(filepath: Path) -> dict[str, str]:
data = {}
if not filepath.is_file():
return data
for line in filepath.read_text().splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
m = _KV_RE.match(line)
if m:
key = m.group(1)
value = m.group(2)
qm = _QUOTED_RE.match(value)
if qm:
value = qm.group(1) if qm.group(1) is not None else qm.group(2)
data[key] = value
return data
def _sanitize_value(value: str) -> str:
return value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "")
def write_conf(filepath: Path, data: dict[str, str]) -> None:
filepath.parent.mkdir(parents=True, exist_ok=True)
lines = []
for key, value in data.items():
lines.append(f'{key}="{_sanitize_value(value)}"')
filepath.write_text("\n".join(lines) + "\n")
filepath.chmod(0o600)
def update_conf_key(filepath: Path, key: str, value: str) -> None:
value = _sanitize_value(value)
filepath.parent.mkdir(parents=True, exist_ok=True)
if not filepath.is_file():
filepath.write_text(f'{key}="{value}"\n')
filepath.chmod(0o600)
return
lines = filepath.read_text().splitlines()
found = False
for i, line in enumerate(lines):
m = _KV_RE.match(line.strip())
if m and m.group(1) == key:
lines[i] = f'{key}="{value}"'
found = True
break
if not found:
lines.append(f'{key}="{value}"')
filepath.write_text("\n".join(lines) + "\n")
filepath.chmod(0o600)
def list_conf_dir(subdir: str) -> list[str]:
d = CONFIG_DIR / subdir
if not d.is_dir():
return []
return sorted(p.stem for p in d.glob("*.conf") if p.is_file())
def has_targets() -> bool:
return len(list_conf_dir("targets.d")) > 0
def has_remotes() -> bool:
return len(list_conf_dir("remotes.d")) > 0