xeps/tools/deferrals.py

188 lines
5.1 KiB
Python
Executable File

#!/usr/bin/env python3
import contextlib
import re
import subprocess
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
EXPERIMENTAL_STATUS = "<status>Experimental</status>"
DEFERRED_STATUS = "<status>Deferred</status>"
REVISION_RE = re.compile(r"\s+<revision>")
REVISION_TEMPLATE = """
<revision>
<version>{version}</version>
<date>{now:%Y-%m-%d}</date>
<initials>XEP Editor ({initials})</initials>
<remark>Defer due to lack of activity.</remark>
</revision>"""
@contextlib.contextmanager
def stash_guard():
try:
subprocess.check_call([
"git", "diff-index", "--quiet", "HEAD", "--"
])
except subprocess.CalledProcessError:
# there are changes
pass
else:
yield
return
subprocess.check_call([
"git", "stash",
])
try:
yield
finally:
subprocess.check_call(["git", "stash", "pop"])
def defer_xep(number, last_version, initials, insert_revision):
filename = "xep-{:04d}.xml".format(number)
with open(filename, "r") as f:
xep_text = f.read()
if EXPERIMENTAL_STATUS not in xep_text:
raise ValueError("cannot find experimental status in XEP text")
# this is so incredibly evil ...
xep_text = xep_text.replace(EXPERIMENTAL_STATUS, DEFERRED_STATUS, 1)
revision_match = REVISION_RE.search(xep_text)
version = last_version.split(".")
if len(version) == 1:
version.append("1")
else:
version[1] = str(int(version[1]) + 1)
del version[2:]
version.append("0")
if insert_revision:
xep_text = (
xep_text[:revision_match.start()] +
REVISION_TEMPLATE.format(
now=datetime.utcnow(),
version=".".join(version),
initials=initials,
) + xep_text[revision_match.start():]
)
with open(filename, "w") as f:
f.write(xep_text)
f.flush()
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(
"-v", "--verbose",
help="Print additional metadata for deferred XEPs",
action="store_true",
default=False,
)
parser.add_argument(
"-m", "--modify",
default=False,
metavar="INITIALS",
help="Modify the to-be-deferred XEPs in-place and use the given "
"INITIALS in the remarks."
)
parser.add_argument(
"--no-revision",
dest="insert_revision",
action="store_false",
default=True,
help="Do not create a revision block",
)
parser.add_argument(
"-c", "--commit",
default=False,
action="store_true",
help="Create a git commit for each deferral (only reasonable with -m)"
)
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))
with contextlib.ExitStack() as stack:
if args.commit:
stack.enter_context(stash_guard())
for deferred_info in deferred:
if args.modify:
defer_xep(deferred_info["number"],
deferred_info["last_revision"]["version"],
args.modify,
args.insert_revision)
if args.commit:
subprocess.check_call([
"git", "add", "xep-{:04d}.xml".format(
deferred_info["number"],
),
])
commit_argv = [
"git", "commit", "-vm",
"XEP-{:04d}: deferred due to lack of activity".format(
deferred_info["number"],
)
]
if args.insert_revision:
commit_argv.append("-e")
subprocess.check_call(commit_argv)
if args.verbose:
print(
"XEP-{info[number]:04d}: {info[title]} "
"(last update {info[last_revision][date]:%Y-%m-%d})".format(
info=deferred_info
)
)
else:
print(deferred_info["number"])
if __name__ == "__main__":
main()