#!/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 = "Experimental" DEFERRED_STATUS = "Deferred" REVISION_RE = re.compile(r"\s+") REVISION_TEMPLATE = """ {version} {now:%Y-%m-%d} XEP Editor ({initials}) Defer due to lack of activity. """ @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()