Show uploaders in todo list
This commit is contained in:
parent
16abb245f6
commit
874d2b4aeb
1 changed files with 115 additions and 16 deletions
131
todo
131
todo
|
|
@ -10,10 +10,13 @@ from urllib.request import urlopen
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from debian import deb822
|
from debian import deb822
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.table import Table
|
||||||
|
|
||||||
|
|
||||||
TODO_URL = "https://udd.debian.org/dmd/?email1=edward%404angle.com&format=json"
|
TODO_URL = "https://udd.debian.org/dmd/?email1=edward%404angle.com&format=json"
|
||||||
TODO_PATH = Path("todo.json")
|
TODO_PATH = Path("todo.json")
|
||||||
|
NOTES_PATH = Path("notes")
|
||||||
TodoItem = dict[str, Any]
|
TodoItem = dict[str, Any]
|
||||||
TodoList = list[TodoItem]
|
TodoList = list[TodoItem]
|
||||||
|
|
||||||
|
|
@ -22,6 +25,9 @@ CURRENTLY_RE = re.compile(
|
||||||
r"^(?P<new>.+?)\s*\(currently in unstable:\s*(?P<current>.+?)\)\s*$"
|
r"^(?P<new>.+?)\s*\(currently in unstable:\s*(?P<current>.+?)\)\s*$"
|
||||||
)
|
)
|
||||||
CACHE_PATH = Path(".vcs_git_cache.json")
|
CACHE_PATH = Path(".vcs_git_cache.json")
|
||||||
|
CACHE_VERSION = 4
|
||||||
|
SourceInfo = dict[str, str]
|
||||||
|
HIDE_UPLOADER = "Edward Betts <edward@4angle.com>"
|
||||||
|
|
||||||
|
|
||||||
def parse_details(details: str) -> tuple[str, Optional[str]]:
|
def parse_details(details: str) -> tuple[str, Optional[str]]:
|
||||||
|
|
@ -45,7 +51,13 @@ def vcs_git_to_team(vcs_git: Optional[str]) -> Optional[str]:
|
||||||
return match.group(1)
|
return match.group(1)
|
||||||
|
|
||||||
|
|
||||||
def load_cache(source_paths: list[str]) -> Optional[dict[str, str]]:
|
def normalize_uploaders(uploaders: str) -> str:
|
||||||
|
parts = [part.strip().strip(",") for part in uploaders.split(",")]
|
||||||
|
cleaned = [part for part in parts if part and part != HIDE_UPLOADER]
|
||||||
|
return "\n".join(cleaned)
|
||||||
|
|
||||||
|
|
||||||
|
def load_cache(source_paths: list[str]) -> Optional[dict[str, SourceInfo]]:
|
||||||
try:
|
try:
|
||||||
with CACHE_PATH.open("r", encoding="utf-8") as handle:
|
with CACHE_PATH.open("r", encoding="utf-8") as handle:
|
||||||
data = json.load(handle)
|
data = json.load(handle)
|
||||||
|
|
@ -54,6 +66,8 @@ def load_cache(source_paths: list[str]) -> Optional[dict[str, str]]:
|
||||||
if not isinstance(data, dict):
|
if not isinstance(data, dict):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if data.get("cache_version") != CACHE_VERSION:
|
||||||
|
return None
|
||||||
cached_mtimes = data.get("sources_mtimes", {})
|
cached_mtimes = data.get("sources_mtimes", {})
|
||||||
if not isinstance(cached_mtimes, dict):
|
if not isinstance(cached_mtimes, dict):
|
||||||
return None
|
return None
|
||||||
|
|
@ -68,15 +82,24 @@ def load_cache(source_paths: list[str]) -> Optional[dict[str, str]]:
|
||||||
vcs_by_source = data.get("vcs_by_source")
|
vcs_by_source = data.get("vcs_by_source")
|
||||||
if not isinstance(vcs_by_source, dict):
|
if not isinstance(vcs_by_source, dict):
|
||||||
return None
|
return None
|
||||||
if not all(
|
normalized: dict[str, SourceInfo] = {}
|
||||||
isinstance(key, str) and isinstance(value, str)
|
for key, value in vcs_by_source.items():
|
||||||
for key, value in vcs_by_source.items()
|
if not isinstance(key, str):
|
||||||
):
|
return None
|
||||||
return None
|
if isinstance(value, str):
|
||||||
return cast(dict[str, str], vcs_by_source)
|
normalized[key] = {"vcs_git": value, "uploaders": ""}
|
||||||
|
continue
|
||||||
|
if not isinstance(value, dict):
|
||||||
|
return None
|
||||||
|
vcs_git = value.get("vcs_git")
|
||||||
|
uploaders = value.get("uploaders")
|
||||||
|
if not isinstance(vcs_git, str) or not isinstance(uploaders, str):
|
||||||
|
return None
|
||||||
|
normalized[key] = {"vcs_git": vcs_git, "uploaders": uploaders}
|
||||||
|
return normalized
|
||||||
|
|
||||||
|
|
||||||
def save_cache(source_paths: list[str], vcs_by_source: dict[str, str]) -> None:
|
def save_cache(source_paths: list[str], vcs_by_source: dict[str, SourceInfo]) -> None:
|
||||||
sources_mtimes: dict[str, float] = {}
|
sources_mtimes: dict[str, float] = {}
|
||||||
for path in source_paths:
|
for path in source_paths:
|
||||||
try:
|
try:
|
||||||
|
|
@ -84,6 +107,7 @@ def save_cache(source_paths: list[str], vcs_by_source: dict[str, str]) -> None:
|
||||||
except OSError:
|
except OSError:
|
||||||
return
|
return
|
||||||
data = {
|
data = {
|
||||||
|
"cache_version": CACHE_VERSION,
|
||||||
"sources_mtimes": sources_mtimes,
|
"sources_mtimes": sources_mtimes,
|
||||||
"vcs_by_source": vcs_by_source,
|
"vcs_by_source": vcs_by_source,
|
||||||
}
|
}
|
||||||
|
|
@ -94,13 +118,13 @@ def save_cache(source_paths: list[str], vcs_by_source: dict[str, str]) -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def load_vcs_git_map() -> dict[str, str]:
|
def load_source_info_map() -> dict[str, SourceInfo]:
|
||||||
source_paths = sorted(glob.glob("/var/lib/apt/lists/*Sources"))
|
source_paths = sorted(glob.glob("/var/lib/apt/lists/*Sources"))
|
||||||
cached = load_cache(source_paths)
|
cached = load_cache(source_paths)
|
||||||
if cached is not None:
|
if cached is not None:
|
||||||
return cached
|
return cached
|
||||||
|
|
||||||
vcs_by_source: dict[str, str] = {}
|
vcs_by_source: dict[str, SourceInfo] = {}
|
||||||
for path in source_paths:
|
for path in source_paths:
|
||||||
with Path(path).open("r", encoding="utf-8", errors="replace") as handle:
|
with Path(path).open("r", encoding="utf-8", errors="replace") as handle:
|
||||||
for entry in deb822.Deb822.iter_paragraphs(handle):
|
for entry in deb822.Deb822.iter_paragraphs(handle):
|
||||||
|
|
@ -108,10 +132,20 @@ def load_vcs_git_map() -> dict[str, str]:
|
||||||
if not source or source in vcs_by_source:
|
if not source or source in vcs_by_source:
|
||||||
continue
|
continue
|
||||||
vcs_git = entry.get("Vcs-Git")
|
vcs_git = entry.get("Vcs-Git")
|
||||||
|
uploaders = entry.get("Uploaders")
|
||||||
|
team = None
|
||||||
if vcs_git:
|
if vcs_git:
|
||||||
team = vcs_git_to_team(vcs_git.strip())
|
team = vcs_git_to_team(vcs_git.strip())
|
||||||
if team:
|
uploaders_text = ""
|
||||||
vcs_by_source[source] = team
|
if uploaders:
|
||||||
|
uploaders_text = normalize_uploaders(
|
||||||
|
re.sub(r"\s+", " ", uploaders).strip()
|
||||||
|
)
|
||||||
|
if team or uploaders_text:
|
||||||
|
vcs_by_source[source] = {
|
||||||
|
"vcs_git": team or "",
|
||||||
|
"uploaders": uploaders_text,
|
||||||
|
}
|
||||||
save_cache(source_paths, vcs_by_source)
|
save_cache(source_paths, vcs_by_source)
|
||||||
return vcs_by_source
|
return vcs_by_source
|
||||||
|
|
||||||
|
|
@ -137,6 +171,27 @@ def summarize_sources(todo_list: TodoList) -> set[str]:
|
||||||
return sources
|
return sources
|
||||||
|
|
||||||
|
|
||||||
|
def load_notes() -> dict[str, str]:
|
||||||
|
if not NOTES_PATH.exists():
|
||||||
|
return {}
|
||||||
|
notes_by_source: dict[str, list[str]] = {}
|
||||||
|
with NOTES_PATH.open("r", encoding="utf-8") as handle:
|
||||||
|
for line in handle:
|
||||||
|
stripped = line.strip()
|
||||||
|
if not stripped:
|
||||||
|
continue
|
||||||
|
parts = stripped.split(None, 1)
|
||||||
|
if not parts:
|
||||||
|
continue
|
||||||
|
source = parts[0]
|
||||||
|
note = parts[1] if len(parts) > 1 else ""
|
||||||
|
notes_by_source.setdefault(source, []).append(note)
|
||||||
|
return {
|
||||||
|
source: "; ".join(note for note in notes if note)
|
||||||
|
for source, notes in notes_by_source.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def filter_todo_list(todo_list: TodoList, include_prerelease: bool = False) -> TodoList:
|
def filter_todo_list(todo_list: TodoList, include_prerelease: bool = False) -> TodoList:
|
||||||
filtered: TodoList = []
|
filtered: TodoList = []
|
||||||
for item in todo_list:
|
for item in todo_list:
|
||||||
|
|
@ -148,10 +203,24 @@ def filter_todo_list(todo_list: TodoList, include_prerelease: bool = False) -> T
|
||||||
continue
|
continue
|
||||||
if not include_prerelease and is_prerelease_version(details):
|
if not include_prerelease and is_prerelease_version(details):
|
||||||
continue
|
continue
|
||||||
|
new_version, current_version = parse_details(details)
|
||||||
|
if current_version:
|
||||||
|
normalized_new = normalize_upstream_version(new_version)
|
||||||
|
normalized_current = normalize_upstream_version(current_version)
|
||||||
|
if normalized_new == normalized_current:
|
||||||
|
continue
|
||||||
filtered.append(item)
|
filtered.append(item)
|
||||||
return filtered
|
return filtered
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_upstream_version(version: str) -> str:
|
||||||
|
if ":" in version:
|
||||||
|
version = version.split(":", 1)[1]
|
||||||
|
if "-" in version:
|
||||||
|
version = version.rsplit("-", 1)[0]
|
||||||
|
return version.strip()
|
||||||
|
|
||||||
|
|
||||||
def print_changes(old_list: TodoList, new_list: TodoList) -> None:
|
def print_changes(old_list: TodoList, new_list: TodoList) -> None:
|
||||||
old_sources = summarize_sources(old_list)
|
old_sources = summarize_sources(old_list)
|
||||||
new_sources = summarize_sources(new_list)
|
new_sources = summarize_sources(new_list)
|
||||||
|
|
@ -174,12 +243,42 @@ def list_todos(include_prerelease: bool) -> None:
|
||||||
with TODO_PATH.open("r", encoding="utf-8") as handle:
|
with TODO_PATH.open("r", encoding="utf-8") as handle:
|
||||||
todo_list = cast(TodoList, json.load(handle))
|
todo_list = cast(TodoList, json.load(handle))
|
||||||
|
|
||||||
vcs_git_map = load_vcs_git_map()
|
source_info_map = load_source_info_map()
|
||||||
|
notes_by_source = load_notes()
|
||||||
|
console = Console()
|
||||||
|
table = Table(title="Debian New Upstream TODOs")
|
||||||
|
table.add_column("Source", style="bold")
|
||||||
|
table.add_column("New", style="green", justify="right")
|
||||||
|
table.add_column("Current", style="dim", justify="right")
|
||||||
|
table.add_column("Team", justify="right")
|
||||||
|
table.add_column("Note/Uploaders", overflow="fold")
|
||||||
|
|
||||||
for todo in filter_todo_list(todo_list, include_prerelease=include_prerelease):
|
filtered = filter_todo_list(todo_list, include_prerelease=include_prerelease)
|
||||||
|
for todo in filtered:
|
||||||
new_version, current_version = parse_details(todo[":details"])
|
new_version, current_version = parse_details(todo[":details"])
|
||||||
vcs_git = vcs_git_map.get(todo[":source"])
|
source_info = source_info_map.get(todo[":source"], {})
|
||||||
print((todo[":source"], new_version, current_version, vcs_git))
|
vcs_git = source_info.get("vcs_git")
|
||||||
|
uploaders = source_info.get("uploaders", "")
|
||||||
|
source = todo[":source"]
|
||||||
|
note = notes_by_source.get(source, "")
|
||||||
|
display_new = new_version
|
||||||
|
if is_prerelease_version(todo[":details"]):
|
||||||
|
display_new = f"[yellow]{new_version} (pre)[/yellow]"
|
||||||
|
display_team = vcs_git or "-"
|
||||||
|
if display_team == "homeassistant-team":
|
||||||
|
display_team = "HA"
|
||||||
|
elif display_team.endswith("-team"):
|
||||||
|
display_team = display_team[:-5]
|
||||||
|
display_note = note or uploaders or "-"
|
||||||
|
table.add_row(
|
||||||
|
source,
|
||||||
|
display_new,
|
||||||
|
current_version or "-",
|
||||||
|
display_team,
|
||||||
|
display_note,
|
||||||
|
)
|
||||||
|
console.print(table)
|
||||||
|
console.print(f"Packages: {len(filtered)}")
|
||||||
|
|
||||||
def update_todos() -> None:
|
def update_todos() -> None:
|
||||||
old_list: TodoList = []
|
old_list: TodoList = []
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue