chore: Add XEP comparison script

The idea is that we keep our DOAP file updated.
Then we always know which version of a XEP we implemented.
By running this script we can then find out if a new version of a XEP
came out.
And we can check whether we need to update our code to adhere to the
latest version or whether we can just version bump.
This commit is contained in:
Michael Vetter
2026-03-09 11:51:49 +01:00
parent 205634534e
commit aaf146bd4f

136
scripts/check-new-xeps.py Executable file
View File

@@ -0,0 +1,136 @@
#!/usr/bin/env python3
import os
import re
import sys
import urllib.request
import xml.etree.ElementTree as ET
from itertools import zip_longest
from typing import Dict, List, Optional
# Namespaces in DOAP
NS = {
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
"foaf": "http://xmlns.com/foaf/0.1/",
"doap": "http://usefulinc.com/ns/doap#",
"xmpp": "https://linkmauve.fr/ns/xmpp-doap#",
"schema": "https://schema.org/",
}
def compare_versions(v1: str, v2: str) -> int:
"""
Compare two version strings.
Returns 1 if v2 > v1, -1 if v1 > v2, 0 if equal.
"""
def parse_v(v: str) -> List[int]:
return [int(x) for x in re.split(r"[^0-9]+", v) if x]
parts1 = parse_v(v1)
parts2 = parse_v(v2)
for p1, p2 in zip_longest(parts1, parts2, fillvalue=0):
if p2 > p1:
return 1
if p1 > p2:
return -1
return 0
def find_doap() -> Optional[str]:
"""Try to find profanity.doap in current or parent directory."""
candidates = ["profanity.doap", "../profanity.doap"]
for c in candidates:
if os.path.exists(c):
return c
return None
def get_implemented_xeps(doap_path: str) -> Dict[str, str]:
"""Parse DOAP file and return a map of XEP number to version."""
implemented_xeps: Dict[str, str] = {}
try:
tree = ET.parse(doap_path)
root = tree.getroot()
for implements in root.findall(".//doap:implements", NS):
supported_xep = implements.find(".//xmpp:SupportedXep", NS)
if supported_xep is not None:
xep_res = supported_xep.find(".//xmpp:xep", NS)
version_elem = supported_xep.find(".//xmpp:version", NS)
if xep_res is not None and version_elem is not None:
resource = xep_res.attrib.get(f"{{{NS['rdf']}}}resource", "")
match = re.search(r"xep-(\d+)\.html", resource)
if match:
xep_num = match.group(1)
current_version = version_elem.text.strip() if version_elem.text else ""
implemented_xeps[xep_num] = current_version
except (ET.ParseError, PermissionError) as e:
print(f"Error reading {doap_path}: {e}")
sys.exit(1)
return implemented_xeps
def check_xeps() -> None:
"""Main logic for checking XEP updates."""
doap_path = find_doap()
if not doap_path:
print("Error: Could not find DOAP file.")
sys.exit(1)
implemented_xeps = get_implemented_xeps(doap_path)
if not implemented_xeps:
print("No XEPs found in DOAP file.")
return
print(f"XEPs tracked: {len(implemented_xeps)}")
try:
url = "https://xmpp.org/extensions/xeplist.xml"
with urllib.request.urlopen(url, timeout=15) as response:
xeplist_xml = response.read()
except Exception as e:
print(f"Error fetching xeplist.xml: {e}")
return
try:
xeplist_tree = ET.fromstring(xeplist_xml)
except ET.ParseError as e:
print(f"Error parsing xeplist.xml: {e}")
return
print(f"\n{'XEP':<10} | {'Name':<35} | {'Ours':<8} | {'Latest':<8}")
print("-" * 80)
updates_found = 0
for xep_node in xeplist_tree.findall("xep"):
number_node = xep_node.find("number")
title_node = xep_node.find("title")
last_revision_node = xep_node.find("last-revision")
if number_node is None or title_node is None or last_revision_node is None:
continue
number = (number_node.text or "0").zfill(4)
name = title_node.text or "Unknown"
version_node = last_revision_node.find("version")
if version_node is None or version_node.text is None:
continue
latest_version = version_node.text
if number in implemented_xeps:
old_version = implemented_xeps[number]
if compare_versions(old_version, latest_version) > 0:
print(f"XEP-{number:<4} | {name[:35]:<35} | {old_version:<8} | {latest_version:<8}")
updates_found += 1
if updates_found == 0:
print("All tracked XEPs are up to date.")
if __name__ == "__main__":
check_xeps()