xeps/tools/issue-cfe.py

250 lines
6.7 KiB
Python

#!/usr/bin/env python3
import configparser
import email.message
import os
import sys
from datetime import datetime, timedelta
import xml.etree.ElementTree as etree
from xeplib import (
Status, load_xepinfos, choose,
make_fake_smtpconn,
interactively_extend_smtp_config,
make_smtpconn,
wraptext,
)
XEP_URL_PREFIX = "https://xmpp.org/extensions/"
SUBJECT_TEMPLATE = "Call for Experience: XEP-{info[number]:04d}: {info[title]}"
MAIL_TEMPLATE = """\
The XEP Editor would like to Call for Experience with XEP-{info[number]:04d} \
before presenting it to the {info[approver]} for advancing it to Final status.
During the Call for Experience, please answer the following questions:
1. What software has XEP-{info[number]:04d} implemented? Please note that the \
protocol must be implemented in at least two separate codebases (at least one \
of which must be free or open-source software) in order to advance from Draft \
to Final.
2. Have developers experienced any problems with the protocol as defined in \
XEP-{info[number]:04d}? If so, please describe the problems and, if possible, \
suggested solutions.
3. Is the text of XEP-{info[number]:04d} clear and unambiguous? Are more \
examples needed? Is the conformance language (MAY/SHOULD/MUST) appropriate? \
Have developers found the text confusing at all? Please describe any \
suggestions you have for improving the text.
If you have any comments about advancing XEP-{info[number]:04d} from Draft \
to Final, please provide them by the close of business on {enddate}. After \
the Call for Experience, this XEP might undergo revisions to address feedback \
received, after which it will be presented to the XMPP Council for voting to \
a status of Final.
You can review the specification here:
{url}
Please send all feedback to the standards@xmpp.org discussion list.
"""
def make_mail(info, enddate):
kwargs = {
"info": info,
"url": "{}xep-{:04d}.html".format(
XEP_URL_PREFIX,
info["number"],
),
"enddate": enddate
}
mail = email.message.EmailMessage()
mail["Subject"] = SUBJECT_TEMPLATE.format(**kwargs)
mail["XSF-XEP-Action"] = "CFE"
mail["XSF-XEP-Title"] = info["title"]
mail["XSF-XEP-Type"] = info["type"]
mail["XSF-XEP-Status"] = info["status"].value
mail["XSF-XEP-Url"] = kwargs["url"]
mail["XSF-XEP-Approver"] = info["approver"]
mail.set_content(
wraptext(MAIL_TEMPLATE.format(**kwargs)),
"plain",
"utf-8",
)
return mail
def main():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
"-c", "--config",
metavar="FILE",
type=argparse.FileType("r"),
help="Configuration file",
)
parser.add_argument(
"-y",
dest="ask_confirmation",
default=True,
action="store_false",
help="'I trust this script to do the right thing and send emails"
"without asking for confirmation.'"
)
parser.add_argument(
"-n", "--dry-run",
dest="dry_run",
action="store_true",
default=False,
help="Instead of sending emails, print them to stdout (implies -y)",
)
parser.add_argument(
"--duration", "-d",
metavar="DAYS",
default=14,
help="Duration of the CFE in days (default and at least: 14)",
type=int,
)
parser.add_argument(
"--xeplist",
default=None,
type=argparse.FileType("r")
)
parser.add_argument(
"-x", "--xep",
type=int,
dest="xeps",
action="append",
default=[],
help="XEP(s) to issue a CFE for"
)
parser.add_argument(
"to",
nargs="+",
help="The mail addresses to send the update mails to."
)
args = parser.parse_args()
can_be_interactive = (
os.isatty(sys.stdin.fileno()) and
os.isatty(sys.stdout.fileno())
)
if not args.xeps:
print("nothing to do (use -x/--xep)", file=sys.stderr)
sys.exit(1)
if args.duration < 14:
print("duration must be at least 14", file=sys.stderr)
sys.exit(1)
enddate = (datetime.utcnow() + timedelta(days=args.duration)).date()
if args.dry_run:
args.ask_confirmation = False
if args.ask_confirmation and not can_be_interactive:
print("Cannot ask for confirmation (stdio is not a TTY), but -y is",
"not given either. Aborting.", sep="\n", file=sys.stderr)
sys.exit(2)
config = configparser.ConfigParser()
if args.config is not None:
config.read_file(args.config)
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)
matched_xeps = []
has_error = False
for num in args.xeps:
try:
info = accepted[num]
except KeyError:
print("no such xep: {}".format(num), file=sys.stderr)
has_error = True
continue
if info["status"] != Status.DRAFT:
print("XEP-{:04d} is in {}, but must be Draft".format(
num,
info["status"].value,
))
has_error = True
continue
matched_xeps.append(info)
if has_error:
sys.exit(1)
if args.dry_run:
smtpconn = make_fake_smtpconn()
else:
if can_be_interactive:
interactively_extend_smtp_config(config)
try:
smtpconn = make_smtpconn(config)
except (configparser.NoSectionError,
configparser.NoOptionError) as exc:
print("Missing configuration: {}".format(exc),
file=sys.stderr)
print("(cannot ask for configuration on stdio because it is "
"not a TTY)", file=sys.stderr)
sys.exit(3)
try:
for info in matched_xeps:
mail = make_mail(info, enddate)
mail["Date"] = datetime.utcnow()
mail["From"] = config.get("smtp", "from")
mail["To"] = args.to
if args.ask_confirmation:
print()
print("---8<---")
print(mail.as_string())
print("--->8---")
print()
choice = choose(
"Send this email? [y]es, [n]o, [a]bort: ",
"yna",
eof="a",
)
if choice == "n":
continue
elif choice == "a":
print("Exiting on user request.", file=sys.stderr)
sys.exit(4)
smtpconn.send_message(mail)
finally:
smtpconn.close()
if __name__ == "__main__":
main()