mirror of
https://github.com/moparisthebest/xeps
synced 2024-11-28 12:12:22 -05:00
tooling: refactor commonly used stuff into xeplib.py
This commit is contained in:
parent
8d32a212a8
commit
18fbef73b4
57
tools/deferrals.py
Executable file
57
tools/deferrals.py
Executable file
@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import xml.etree.ElementTree as etree
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from xeplib import load_xepinfos, Status
|
||||||
|
|
||||||
|
|
||||||
|
def get_deferred(accepted):
|
||||||
|
now = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
threshold = now.replace(year=now.year - 1)
|
||||||
|
|
||||||
|
for number, info in sorted(accepted.items()):
|
||||||
|
if info["status"] == Status.EXPERIMENTAL and "last_revision" in info:
|
||||||
|
last_update = info["last_revision"]["date"]
|
||||||
|
if last_update <= threshold:
|
||||||
|
yield info
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Show the XEPs which need to be changed to deferred."
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-l", "--xeplist",
|
||||||
|
type=argparse.FileType("rb"),
|
||||||
|
default=None,
|
||||||
|
help="XEP list to use (defaults to ./build/xeplist.xml)"
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-m", "--modify",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Modify the XEP files in-place."
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.xeplist is None:
|
||||||
|
args.xeplist = open("./build/xeplist.xml", "rb")
|
||||||
|
|
||||||
|
with args.xeplist as f:
|
||||||
|
tree = etree.parse(f)
|
||||||
|
|
||||||
|
accepted, _ = load_xepinfos(tree)
|
||||||
|
deferred = list(get_deferred(accepted))
|
||||||
|
|
||||||
|
for deferred_info in deferred:
|
||||||
|
print(deferred_info["number"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -4,6 +4,13 @@ import xml.dom.minidom
|
|||||||
|
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
|
|
||||||
|
from xeplib import (
|
||||||
|
minidom_find_child,
|
||||||
|
minidom_find_header,
|
||||||
|
minidom_get_text,
|
||||||
|
minidom_children,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTION = """\
|
DESCRIPTION = """\
|
||||||
Extract a list of XEPs with metadata from the xeps repository."""
|
Extract a list of XEPs with metadata from the xeps repository."""
|
||||||
@ -15,53 +22,29 @@ def open_xml(f):
|
|||||||
return xml.dom.minidom.parse(f)
|
return xml.dom.minidom.parse(f)
|
||||||
|
|
||||||
|
|
||||||
def find_child(elem, child_tag):
|
|
||||||
for child in elem.childNodes:
|
|
||||||
if hasattr(child, "tagName") and child.tagName == child_tag:
|
|
||||||
return child
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def find_header(document):
|
|
||||||
header = find_child(document.documentElement, "header")
|
|
||||||
if header is None:
|
|
||||||
raise ValueError("cannot find <header/>")
|
|
||||||
return header
|
|
||||||
|
|
||||||
|
|
||||||
def get_text(elem):
|
|
||||||
return "".join(
|
|
||||||
child.nodeValue
|
|
||||||
for child in elem.childNodes
|
|
||||||
if isinstance(child, (xml.dom.minidom.Text,
|
|
||||||
xml.dom.minidom.CDATASection))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def children(elem):
|
|
||||||
return [
|
|
||||||
child for child in elem.childNodes
|
|
||||||
if isinstance(child, (xml.dom.minidom.Element))
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def extract_xep_metadata(document):
|
def extract_xep_metadata(document):
|
||||||
header = find_header(document)
|
header = minidom_find_header(document)
|
||||||
|
|
||||||
latest_revision = find_child(header, "revision")
|
latest_revision = minidom_find_child(header, "revision")
|
||||||
if latest_revision is not None:
|
if latest_revision is not None:
|
||||||
last_revision_version = get_text(find_child(latest_revision, "version"))
|
last_revision_version = minidom_get_text(
|
||||||
last_revision_date = get_text(find_child(latest_revision, "date"))
|
minidom_find_child(latest_revision, "version")
|
||||||
remark_el = find_child(latest_revision, "remark")
|
)
|
||||||
|
last_revision_date = minidom_get_text(
|
||||||
|
minidom_find_child(latest_revision, "date")
|
||||||
|
)
|
||||||
|
remark_el = minidom_find_child(latest_revision, "remark")
|
||||||
last_revision_remark = None
|
last_revision_remark = None
|
||||||
if remark_el is not None:
|
if remark_el is not None:
|
||||||
remark_children = children(remark_el)
|
remark_children = minidom_children(remark_el)
|
||||||
if len(remark_children) == 1 and remark_children[0].tagName == "p":
|
if len(remark_children) == 1 and remark_children[0].tagName == "p":
|
||||||
last_revision_remark = get_text(remark_children[0])
|
last_revision_remark = minidom_get_text(remark_children[0])
|
||||||
|
|
||||||
if last_revision_remark is not None:
|
if last_revision_remark is not None:
|
||||||
initials_el = find_child(latest_revision, "initials")
|
initials_el = minidom_find_child(latest_revision, "initials")
|
||||||
last_revision_initials = initials_el and get_text(initials_el)
|
last_revision_initials = initials_el and minidom_get_text(
|
||||||
|
initials_el
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
last_revision_initials = None
|
last_revision_initials = None
|
||||||
else:
|
else:
|
||||||
@ -70,24 +53,26 @@ def extract_xep_metadata(document):
|
|||||||
last_revision_remark = None
|
last_revision_remark = None
|
||||||
last_revision_initials = None
|
last_revision_initials = None
|
||||||
|
|
||||||
status = get_text(find_child(header, "status"))
|
status = minidom_get_text(minidom_find_child(header, "status"))
|
||||||
type_ = get_text(find_child(header, "type"))
|
type_ = minidom_get_text(minidom_find_child(header, "type"))
|
||||||
abstract = " ".join(get_text(find_child(header, "abstract")).split())
|
abstract = " ".join(minidom_get_text(
|
||||||
sig_el = find_child(header, "sig")
|
minidom_find_child(header, "abstract")
|
||||||
|
).split())
|
||||||
|
sig_el = minidom_find_child(header, "sig")
|
||||||
if sig_el is None:
|
if sig_el is None:
|
||||||
sig = None
|
sig = None
|
||||||
else:
|
else:
|
||||||
sig = get_text(sig_el)
|
sig = minidom_get_text(sig_el)
|
||||||
shortname = get_text(find_child(header, "shortname"))
|
shortname = minidom_get_text(minidom_find_child(header, "shortname"))
|
||||||
if shortname.replace("-", " ").replace("_", " ").lower() in [
|
if shortname.replace("-", " ").replace("_", " ").lower() in [
|
||||||
"not yet assigned", "n/a", "none", "to be assigned",
|
"not yet assigned", "n/a", "none", "to be assigned",
|
||||||
"to be issued"]:
|
"to be issued"]:
|
||||||
shortname = None
|
shortname = None
|
||||||
title = get_text(find_child(header, "title"))
|
title = minidom_get_text(minidom_find_child(header, "title"))
|
||||||
|
|
||||||
approver_el = find_child(header, "approver")
|
approver_el = minidom_find_child(header, "approver")
|
||||||
if approver_el is not None:
|
if approver_el is not None:
|
||||||
approver = get_text(approver_el)
|
approver = minidom_get_text(approver_el)
|
||||||
else:
|
else:
|
||||||
approver = "Board" if type_ == "Procedural" else "Council"
|
approver = "Board" if type_ == "Procedural" else "Council"
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import configparser
|
|||||||
import getpass
|
import getpass
|
||||||
import itertools
|
import itertools
|
||||||
import email.message
|
import email.message
|
||||||
import enum
|
|
||||||
import os
|
import os
|
||||||
import smtplib
|
import smtplib
|
||||||
import sys
|
import sys
|
||||||
@ -13,6 +12,8 @@ from datetime import datetime
|
|||||||
|
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
|
|
||||||
|
from xeplib import Status, Action, load_xepinfos
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTION = """\
|
DESCRIPTION = """\
|
||||||
Send email updates for XEP changes based on the difference between two \
|
Send email updates for XEP changes based on the difference between two \
|
||||||
@ -35,51 +36,6 @@ standard output are a terminal, the script interactively asks for the option \
|
|||||||
values. If no terminal is connected, the script exits with an error instead."""
|
values. If no terminal is connected, the script exits with an error instead."""
|
||||||
|
|
||||||
|
|
||||||
class Status(enum.Enum):
|
|
||||||
PROTO = 'ProtoXEP'
|
|
||||||
EXPERIMENTAL = 'Experimental'
|
|
||||||
PROPOSED = 'Proposed'
|
|
||||||
DRAFT = 'Draft'
|
|
||||||
ACTIVE = 'Active'
|
|
||||||
FINAL = 'Final'
|
|
||||||
RETRACTED = 'Retracted'
|
|
||||||
OBSOLETE = 'Obsolete'
|
|
||||||
DEFERRED = 'Deferred'
|
|
||||||
REJECTED = 'Rejected'
|
|
||||||
DEPRECATED = 'Deprecated'
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def fromstr(cls, s):
|
|
||||||
if s == "Proto" or s.lower() == "protoxep":
|
|
||||||
s = "ProtoXEP"
|
|
||||||
return cls(s)
|
|
||||||
|
|
||||||
|
|
||||||
class Action(enum.Enum):
|
|
||||||
PROTO = "Proposed XMPP Extension"
|
|
||||||
NEW = "NEW"
|
|
||||||
DRAFT = "DRAFT"
|
|
||||||
ACTIVE = "ACTIVE"
|
|
||||||
FINAL = "FINAL"
|
|
||||||
RETRACT = "RETRACTED"
|
|
||||||
OBSOLETE = "OBSOLETED"
|
|
||||||
DEFER = "DEFERRED"
|
|
||||||
UPDATE = "UPDATED"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def fromstatus(cls, status):
|
|
||||||
return {
|
|
||||||
Status.EXPERIMENTAL: cls.NEW,
|
|
||||||
Status.DRAFT: cls.DRAFT,
|
|
||||||
Status.ACTIVE: cls.ACTIVE,
|
|
||||||
Status.FINAL: cls.FINAL,
|
|
||||||
Status.RETRACTED: cls.RETRACT,
|
|
||||||
Status.OBSOLETED: cls.OBSOLETE,
|
|
||||||
Status.DEPRECATED: cls.DEPRECATE,
|
|
||||||
Status.DEFERRED: cls.DEFERRED,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
XEP_URL_PREFIX = "https://xmpp.org/extensions/"
|
XEP_URL_PREFIX = "https://xmpp.org/extensions/"
|
||||||
|
|
||||||
|
|
||||||
@ -116,61 +72,6 @@ SUBJECT_NONPROTO_TEMPLATE = \
|
|||||||
"{action.value}: XEP-{info[number]:04d} ({info[title]})"
|
"{action.value}: XEP-{info[number]:04d} ({info[title]})"
|
||||||
|
|
||||||
|
|
||||||
def load_xepinfo(el):
|
|
||||||
accepted = el.get("accepted").lower() == "true"
|
|
||||||
|
|
||||||
info = {
|
|
||||||
"title": el.find("title").text,
|
|
||||||
"abstract": el.find("abstract").text,
|
|
||||||
"type": el.find("type").text,
|
|
||||||
"status": Status.fromstr(el.find("status").text),
|
|
||||||
"approver": el.find("approver").text,
|
|
||||||
"accepted": accepted,
|
|
||||||
}
|
|
||||||
|
|
||||||
last_revision_el = el.find("last-revision")
|
|
||||||
if last_revision_el is not None:
|
|
||||||
last_revision = {
|
|
||||||
"version": last_revision_el.find("version").text,
|
|
||||||
"date": last_revision_el.find("date").text,
|
|
||||||
"initials": None,
|
|
||||||
"remark": None,
|
|
||||||
}
|
|
||||||
|
|
||||||
initials_el = last_revision_el.find("initials")
|
|
||||||
if initials_el is not None:
|
|
||||||
last_revision["initials"] = initials_el.text
|
|
||||||
|
|
||||||
remark_el = last_revision_el.find("remark")
|
|
||||||
if remark_el is not None:
|
|
||||||
last_revision["remark"] = remark_el.text
|
|
||||||
|
|
||||||
info["last_revision"] = last_revision
|
|
||||||
|
|
||||||
sig = el.find("sig")
|
|
||||||
if sig is not None:
|
|
||||||
info["sig"] = sig.text
|
|
||||||
|
|
||||||
if accepted:
|
|
||||||
info["number"] = int(el.find("number").text)
|
|
||||||
else:
|
|
||||||
info["protoname"] = el.find("proto-name").text
|
|
||||||
|
|
||||||
return info
|
|
||||||
|
|
||||||
|
|
||||||
def load_xepinfos(tree):
|
|
||||||
accepted, protos = {}, {}
|
|
||||||
for info_el in tree.getroot():
|
|
||||||
info = load_xepinfo(info_el)
|
|
||||||
if info["accepted"]:
|
|
||||||
accepted[info["number"]] = info
|
|
||||||
else:
|
|
||||||
protos[info["protoname"]] = info
|
|
||||||
|
|
||||||
return accepted, protos
|
|
||||||
|
|
||||||
|
|
||||||
def dummy_info(number):
|
def dummy_info(number):
|
||||||
return {
|
return {
|
||||||
"status": None,
|
"status": None,
|
||||||
|
138
tools/xeplib.py
Normal file
138
tools/xeplib.py
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import enum
|
||||||
|
|
||||||
|
import xml.dom.minidom
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class Status(enum.Enum):
|
||||||
|
PROTO = 'ProtoXEP'
|
||||||
|
EXPERIMENTAL = 'Experimental'
|
||||||
|
PROPOSED = 'Proposed'
|
||||||
|
DRAFT = 'Draft'
|
||||||
|
ACTIVE = 'Active'
|
||||||
|
FINAL = 'Final'
|
||||||
|
RETRACTED = 'Retracted'
|
||||||
|
OBSOLETE = 'Obsolete'
|
||||||
|
DEFERRED = 'Deferred'
|
||||||
|
REJECTED = 'Rejected'
|
||||||
|
DEPRECATED = 'Deprecated'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fromstr(cls, s):
|
||||||
|
if s == "Proto" or s.lower() == "protoxep":
|
||||||
|
s = "ProtoXEP"
|
||||||
|
return cls(s)
|
||||||
|
|
||||||
|
|
||||||
|
class Action(enum.Enum):
|
||||||
|
PROTO = "Proposed XMPP Extension"
|
||||||
|
NEW = "NEW"
|
||||||
|
DRAFT = "DRAFT"
|
||||||
|
ACTIVE = "ACTIVE"
|
||||||
|
FINAL = "FINAL"
|
||||||
|
RETRACT = "RETRACTED"
|
||||||
|
OBSOLETE = "OBSOLETED"
|
||||||
|
DEFER = "DEFERRED"
|
||||||
|
UPDATE = "UPDATED"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fromstatus(cls, status):
|
||||||
|
return {
|
||||||
|
Status.EXPERIMENTAL: cls.NEW,
|
||||||
|
Status.DRAFT: cls.DRAFT,
|
||||||
|
Status.ACTIVE: cls.ACTIVE,
|
||||||
|
Status.FINAL: cls.FINAL,
|
||||||
|
Status.RETRACTED: cls.RETRACT,
|
||||||
|
Status.OBSOLETED: cls.OBSOLETE,
|
||||||
|
Status.DEPRECATED: cls.DEPRECATE,
|
||||||
|
Status.DEFERRED: cls.DEFERRED,
|
||||||
|
}[status]
|
||||||
|
|
||||||
|
|
||||||
|
def load_xepinfo(el):
|
||||||
|
accepted = el.get("accepted").lower() == "true"
|
||||||
|
|
||||||
|
info = {
|
||||||
|
"title": el.find("title").text,
|
||||||
|
"abstract": el.find("abstract").text,
|
||||||
|
"type": el.find("type").text,
|
||||||
|
"status": Status.fromstr(el.find("status").text),
|
||||||
|
"approver": el.find("approver").text,
|
||||||
|
"accepted": accepted,
|
||||||
|
}
|
||||||
|
|
||||||
|
last_revision_el = el.find("last-revision")
|
||||||
|
if last_revision_el is not None:
|
||||||
|
last_revision = {
|
||||||
|
"version": last_revision_el.find("version").text,
|
||||||
|
"date": datetime.strptime(
|
||||||
|
last_revision_el.find("date").text,
|
||||||
|
"%Y-%m-%d",
|
||||||
|
),
|
||||||
|
"initials": None,
|
||||||
|
"remark": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
initials_el = last_revision_el.find("initials")
|
||||||
|
if initials_el is not None:
|
||||||
|
last_revision["initials"] = initials_el.text
|
||||||
|
|
||||||
|
remark_el = last_revision_el.find("remark")
|
||||||
|
if remark_el is not None:
|
||||||
|
last_revision["remark"] = remark_el.text
|
||||||
|
|
||||||
|
info["last_revision"] = last_revision
|
||||||
|
|
||||||
|
sig = el.find("sig")
|
||||||
|
if sig is not None:
|
||||||
|
info["sig"] = sig.text
|
||||||
|
|
||||||
|
if accepted:
|
||||||
|
info["number"] = int(el.find("number").text)
|
||||||
|
else:
|
||||||
|
info["protoname"] = el.find("proto-name").text
|
||||||
|
|
||||||
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
def load_xepinfos(tree):
|
||||||
|
accepted, protos = {}, {}
|
||||||
|
for info_el in tree.getroot():
|
||||||
|
info = load_xepinfo(info_el)
|
||||||
|
if info["accepted"]:
|
||||||
|
accepted[info["number"]] = info
|
||||||
|
else:
|
||||||
|
protos[info["protoname"]] = info
|
||||||
|
|
||||||
|
return accepted, protos
|
||||||
|
|
||||||
|
|
||||||
|
def minidom_find_child(elem, child_tag):
|
||||||
|
for child in elem.childNodes:
|
||||||
|
if hasattr(child, "tagName") and child.tagName == child_tag:
|
||||||
|
return child
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def minidom_find_header(document):
|
||||||
|
header = minidom_find_child(document.documentElement, "header")
|
||||||
|
if header is None:
|
||||||
|
raise ValueError("cannot find <header/>")
|
||||||
|
return header
|
||||||
|
|
||||||
|
|
||||||
|
def minidom_get_text(elem):
|
||||||
|
return "".join(
|
||||||
|
child.nodeValue
|
||||||
|
for child in elem.childNodes
|
||||||
|
if isinstance(child, (xml.dom.minidom.Text,
|
||||||
|
xml.dom.minidom.CDATASection))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def minidom_children(elem):
|
||||||
|
return [
|
||||||
|
child for child in elem.childNodes
|
||||||
|
if isinstance(child, (xml.dom.minidom.Element))
|
||||||
|
]
|
Loading…
Reference in New Issue
Block a user