mirror of
https://github.com/moparisthebest/xeps
synced 2024-11-21 16:55:07 -05:00
Implement tool to accept ProtoXEPs
This commit is contained in:
parent
6f4924685e
commit
a512dadefa
203
tools/accept.py
Normal file
203
tools/accept.py
Normal 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()
|
@ -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")
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user