Beautify restic forget table in backup history
This commit is contained in:
@@ -74,6 +74,98 @@ def _tail(path: str, lines: int = 120) -> str:
|
||||
return "".join(data).strip() or "(empty)"
|
||||
|
||||
|
||||
def _beautify_restic_forget(raw: str) -> str | None:
|
||||
"""
|
||||
Parse restic forget output tables into a compact bullet list.
|
||||
"""
|
||||
if "Reasons" not in raw or "Paths" not in raw:
|
||||
return None
|
||||
|
||||
lines = raw.splitlines()
|
||||
headers = []
|
||||
for idx, line in enumerate(lines):
|
||||
if line.startswith("ID") and "Reasons" in line and "Paths" in line:
|
||||
headers.append(idx)
|
||||
if not headers:
|
||||
return None
|
||||
|
||||
def parse_block(start_idx: int, end_idx: int) -> list[dict]:
|
||||
header = lines[start_idx]
|
||||
cols = ["ID", "Time", "Host", "Tags", "Reasons", "Paths", "Size"]
|
||||
positions = []
|
||||
for name in cols:
|
||||
pos = header.find(name)
|
||||
if pos == -1:
|
||||
return []
|
||||
positions.append(pos)
|
||||
positions.append(len(header))
|
||||
|
||||
entries: list[dict] = []
|
||||
current: dict | None = None
|
||||
for line in lines[start_idx + 2 : end_idx]:
|
||||
if not line.strip():
|
||||
continue
|
||||
segments = []
|
||||
for i in range(len(cols)):
|
||||
segments.append(line[positions[i] : positions[i + 1]].strip())
|
||||
row = dict(zip(cols, segments))
|
||||
if row["ID"]:
|
||||
current = {
|
||||
"id": row["ID"],
|
||||
"time": row["Time"],
|
||||
"host": row["Host"],
|
||||
"size": row["Size"],
|
||||
"tags": row["Tags"],
|
||||
"reasons": [],
|
||||
"paths": [],
|
||||
}
|
||||
if row["Reasons"]:
|
||||
current["reasons"].append(row["Reasons"])
|
||||
if row["Paths"]:
|
||||
current["paths"].append(row["Paths"])
|
||||
entries.append(current)
|
||||
elif current:
|
||||
if row["Reasons"]:
|
||||
current["reasons"].append(row["Reasons"])
|
||||
if row["Paths"]:
|
||||
current["paths"].append(row["Paths"])
|
||||
return entries
|
||||
|
||||
blocks = []
|
||||
for i, start in enumerate(headers):
|
||||
end = headers[i + 1] if i + 1 < len(headers) else len(lines)
|
||||
entries = parse_block(start, end)
|
||||
if not entries:
|
||||
continue
|
||||
label = "Plan"
|
||||
prev_line = lines[start - 1].lower() if start - 1 >= 0 else ""
|
||||
prev2 = lines[start - 2].lower() if start - 2 >= 0 else ""
|
||||
if "keep" in prev_line:
|
||||
label = prev_line.strip()
|
||||
elif "keep" in prev2:
|
||||
label = prev2.strip()
|
||||
elif "snapshots" in prev_line:
|
||||
label = prev_line.strip()
|
||||
blocks.append((label, entries))
|
||||
|
||||
if not blocks:
|
||||
return None
|
||||
|
||||
out_lines = []
|
||||
for label, entries in blocks:
|
||||
out_lines.append(f"📦 {label}")
|
||||
for e in entries:
|
||||
head = f"🧉 {e['id']} | {e['time']} | {e['host']} | {e['size'] or 'n/a'}"
|
||||
out_lines.append(head)
|
||||
if e["reasons"]:
|
||||
out_lines.append(" 📌 " + "; ".join(e["reasons"]))
|
||||
if e["paths"]:
|
||||
for p in e["paths"]:
|
||||
out_lines.append(f" • {p}")
|
||||
out_lines.append("")
|
||||
return "\n".join(out_lines).rstrip()
|
||||
|
||||
|
||||
def _load_json(raw: str, label: str) -> tuple[bool, object | None, str]:
|
||||
if not raw or not raw.strip():
|
||||
return False, None, f"? {label} returned empty output"
|
||||
@@ -436,6 +528,7 @@ async def backup_history(msg: Message):
|
||||
if content.startswith("⚠️"):
|
||||
await msg.answer(content, reply_markup=backup_kb)
|
||||
return
|
||||
pretty = _beautify_restic_forget(content)
|
||||
trimmed = False
|
||||
max_len = 3500
|
||||
if len(content) > max_len:
|
||||
@@ -444,11 +537,14 @@ async def backup_history(msg: Message):
|
||||
header = "📜 Backup history (tail)"
|
||||
if trimmed:
|
||||
header += " (trimmed)"
|
||||
await msg.answer(
|
||||
f"{header}\n`{log_path}`\n```\n{content}\n```",
|
||||
reply_markup=backup_kb,
|
||||
parse_mode="Markdown",
|
||||
)
|
||||
if pretty:
|
||||
await msg.answer(f"{header}\n`{log_path}`\n\n{pretty}", reply_markup=backup_kb)
|
||||
else:
|
||||
await msg.answer(
|
||||
f"{header}\n`{log_path}`\n```\n{content}\n```",
|
||||
reply_markup=backup_kb,
|
||||
parse_mode="Markdown",
|
||||
)
|
||||
|
||||
|
||||
@dp.callback_query(F.data == "backup:retry")
|
||||
|
||||
Reference in New Issue
Block a user