tooling: refactor commonly used stuff into xeplib.py

This commit is contained in:
Jonas Wielicki 2017-08-23 13:47:09 +02:00
parent 8d32a212a8
commit 18fbef73b4
4 changed files with 230 additions and 149 deletions

57
tools/deferrals.py Executable file
View 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()

View File

@ -4,6 +4,13 @@ import xml.dom.minidom
import xml.etree.ElementTree as etree
from xeplib import (
minidom_find_child,
minidom_find_header,
minidom_get_text,
minidom_children,
)
DESCRIPTION = """\
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)
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):
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:
last_revision_version = get_text(find_child(latest_revision, "version"))
last_revision_date = get_text(find_child(latest_revision, "date"))
remark_el = find_child(latest_revision, "remark")
last_revision_version = minidom_get_text(
minidom_find_child(latest_revision, "version")
)
last_revision_date = minidom_get_text(
minidom_find_child(latest_revision, "date")
)
remark_el = minidom_find_child(latest_revision, "remark")
last_revision_remark = 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":
last_revision_remark = get_text(remark_children[0])
last_revision_remark = minidom_get_text(remark_children[0])
if last_revision_remark is not None:
initials_el = find_child(latest_revision, "initials")
last_revision_initials = initials_el and get_text(initials_el)
initials_el = minidom_find_child(latest_revision, "initials")
last_revision_initials = initials_el and minidom_get_text(
initials_el
)
else:
last_revision_initials = None
else:
@ -70,24 +53,26 @@ def extract_xep_metadata(document):
last_revision_remark = None
last_revision_initials = None
status = get_text(find_child(header, "status"))
type_ = get_text(find_child(header, "type"))
abstract = " ".join(get_text(find_child(header, "abstract")).split())
sig_el = find_child(header, "sig")
status = minidom_get_text(minidom_find_child(header, "status"))
type_ = minidom_get_text(minidom_find_child(header, "type"))
abstract = " ".join(minidom_get_text(
minidom_find_child(header, "abstract")
).split())
sig_el = minidom_find_child(header, "sig")
if sig_el is None:
sig = None
else:
sig = get_text(sig_el)
shortname = get_text(find_child(header, "shortname"))
sig = minidom_get_text(sig_el)
shortname = minidom_get_text(minidom_find_child(header, "shortname"))
if shortname.replace("-", " ").replace("_", " ").lower() in [
"not yet assigned", "n/a", "none", "to be assigned",
"to be issued"]:
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:
approver = get_text(approver_el)
approver = minidom_get_text(approver_el)
else:
approver = "Board" if type_ == "Procedural" else "Council"

View File

@ -3,7 +3,6 @@ import configparser
import getpass
import itertools
import email.message
import enum
import os
import smtplib
import sys
@ -13,6 +12,8 @@ from datetime import datetime
import xml.etree.ElementTree as etree
from xeplib import Status, Action, load_xepinfos
DESCRIPTION = """\
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."""
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/"
@ -116,61 +72,6 @@ SUBJECT_NONPROTO_TEMPLATE = \
"{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):
return {
"status": None,

138
tools/xeplib.py Normal file
View 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))
]