Implement tool to accept ProtoXEPs

This commit is contained in:
Jonas Wielicki 2018-01-25 08:46:43 +01:00
parent 6f4924685e
commit a512dadefa
3 changed files with 230 additions and 25 deletions

203
tools/accept.py Normal file
View File

@ -0,0 +1,203 @@
#!/usr/bin/env python3
import pathlib
import re
import shutil
import sys
import xml.etree.ElementTree as etree
from datetime import datetime, timedelta
from xeplib import load_xepinfos, Status, choose
DEFAULT_XEPLIST_PATH = "./build/xeplist.xml"
XEP_FILENAME_RE = re.compile(r"xep-(\d+)\.xml")
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
BLANK_NUMBER = re.compile("<number>[xX]{4}</number>")
PROTOXEP_STATUS = "<status>ProtoXEP</status>"
EXPERIMENTAL_STATUS = "<status>Experimental</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>Accepted by vote of {approving_body} on {date}.</remark>
</revision>"""
def accept_xep(number, last_version,
initials,
approving_body,
votedate):
filename = "xep-{:04d}.xml".format(number)
with open(filename, "r") as f:
xep_text = f.read()
if PROTOXEP_STATUS not in xep_text:
raise ValueError("cannot find experimental status in XEP text")
# this is so incredibly evil ...
xep_text = xep_text.replace(PROTOXEP_STATUS, EXPERIMENTAL_STATUS, 1)
xep_text, n = BLANK_NUMBER.subn("<number>{:04d}</number>".format(number),
xep_text,
1)
if n == 0:
raise ValueError("cannot find number placeholder in XEP text")
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:]
xep_text = (
xep_text[:revision_match.start()] +
REVISION_TEMPLATE.format(
now=datetime.utcnow(),
version=".".join(version),
initials=initials,
date=votedate,
approving_body=approving_body,
) + xep_text[revision_match.start():]
)
with open(filename, "w") as f:
f.write(xep_text)
f.flush()
def isodate(s):
return datetime.strptime(s, "%Y-%m-%d")
def get_next_xep_number(accepted):
return max(accepted.keys()) + 1
def main():
import argparse
parser = argparse.ArgumentParser(
description="Accept an inbox XEP."
)
parser.add_argument(
"-l", "--xeplist",
type=argparse.FileType("rb"),
default=None,
help="XEP list to use (defaults to {})".format(DEFAULT_XEPLIST_PATH)
)
parser.add_argument(
"-y", "--yes",
dest="ask",
action="store_false",
help="Assume default answer to all questions.",
default=True,
)
parser.add_argument(
"-f", "--force",
dest="force",
action="store_true",
default=False,
help="Force acceptance even if suspicious.",
)
parser.add_argument(
"item",
help="Inbox name"
)
parser.add_argument(
"votedate",
type=isodate,
help="The date of the vote, in ISO format (%Y-%m-%d)."
)
parser.add_argument(
"initials",
help="Your editor initials"
)
args = parser.parse_args()
if args.item.endswith(".xml"):
# strip the path
p = pathlib.Path(args.item)
args.item = p.parts[-1].rsplit(".")[0]
if args.xeplist is None:
args.xeplist = open(DEFAULT_XEPLIST_PATH, "rb")
if args.xeplist is not None:
with args.xeplist as f:
tree = etree.parse(f)
accepted, inbox = load_xepinfos(tree)
try:
xepinfo = inbox[args.item]
except KeyError:
print("no such inbox xep: {!r}".format(args.item), file=sys.stderr)
print("maybe run make build/xeplist.xml first?", file=sys.stderr)
sys.exit(1)
new_number = get_next_xep_number(accepted)
new_filename = pathlib.Path(".") / "xep-{:04d}.xml".format(new_number)
inbox_path = pathlib.Path("inbox") / "{}.xml".format(args.item)
if new_filename.exists():
raise FileExistsError(
"Internal error: XEP file does already exist! ({})".format(
new_filename
)
)
if not inbox_path.exists():
print("inbox file does not exist or is not readable: {}".format(
inbox_path
))
if args.ask:
print("I am going to accept:")
print()
print(" Title: {!r}".format(xepinfo["title"]))
print(" Abstract: {!r}".format(xepinfo["abstract"]))
print(" Last Revision: {} ({})".format(
xepinfo["last_revision"]["date"].date(),
xepinfo["last_revision"]["version"],
))
print()
print("as new XEP-{:04d}.".format(new_number))
print()
choice = choose("Is this correct? [y]es, [n]o: ", "yn", eof="n")
if choice != "y":
print("aborted at user request")
sys.exit(2)
shutil.copy(str(inbox_path), str(new_filename))
accept_xep(new_number,
xepinfo["last_revision"]["version"],
args.initials,
xepinfo["approver"],
args.votedate.date())
if __name__ == "__main__":
main()

View File

@ -12,7 +12,7 @@ from datetime import datetime
import xml.etree.ElementTree as etree
from xeplib import Status, Action, load_xepinfos
from xeplib import Status, Action, load_xepinfos, choose
DESCRIPTION = """\
@ -279,30 +279,6 @@ def interactively_extend_smtp_config(config):
config.set("smtp", "from", from_)
def choose(prompt, options, *,
eof=EOFError,
keyboard_interrupt=KeyboardInterrupt):
while True:
try:
choice = input(prompt).strip()
except EOFError:
if eof is EOFError:
raise
return eof
except KeyboardInterrupt:
if keyboard_interrupt is KeyboardInterrupt:
raise
return keyboard_interrupt
if choice not in options:
print("invalid choice. please enter one of: {}".format(
", ".join(map(str, options))
))
continue
return choice
def make_smtpconn(config):
host = config.get("smtp", "host")
port = config.getint("smtp", "port")

View File

@ -37,6 +37,7 @@ class Action(enum.Enum):
UPDATE = "UPDATED"
DEPRECATE = "DEPRECATED"
LAST_CALL = "LAST CALL"
REJECT = "REJECT"
@classmethod
def fromstatus(cls, status):
@ -50,6 +51,7 @@ class Action(enum.Enum):
Status.DEPRECATED: cls.DEPRECATE,
Status.DEFERRED: cls.DEFER,
Status.PROPOSED: cls.LAST_CALL,
Status.REJECTED: cls.REJECT,
}[status]
@ -143,3 +145,27 @@ def minidom_children(elem):
child for child in elem.childNodes
if isinstance(child, (xml.dom.minidom.Element))
]
def choose(prompt, options, *,
eof=EOFError,
keyboard_interrupt=KeyboardInterrupt):
while True:
try:
choice = input(prompt).strip()
except EOFError:
if eof is EOFError:
raise
return eof
except KeyboardInterrupt:
if keyboard_interrupt is KeyboardInterrupt:
raise
return keyboard_interrupt
if choice not in options:
print("invalid choice. please enter one of: {}".format(
", ".join(map(str, options))
))
continue
return choice