1"""
2Invoke development tasks.
3"""
4from pathlib import Path
5from subprocess import check_output, check_call
6
7import invoke
8
9
10@invoke.task(help={"version": "version being released"})
11def announce(ctx, version):
12    """Generates a new release announcement entry in the docs."""
13    # Get our list of authors
14    stdout = check_output(["git", "describe", "--abbrev=0", "--tags"])
15    stdout = stdout.decode("utf-8")
16    last_version = stdout.strip()
17
18    stdout = check_output(
19        ["git", "log", "{}..HEAD".format(last_version), "--format=%aN"]
20    )
21    stdout = stdout.decode("utf-8")
22
23    contributors = set(stdout.splitlines())
24
25    template_name = "release.minor.rst" if version.endswith(
26        ".0"
27    ) else "release.patch.rst"
28    template_text = Path(__file__).parent.joinpath(template_name).read_text(
29        encoding="UTF-8"
30    )
31
32    contributors_text = "\n".join(
33        "* {}".format(name) for name in sorted(contributors)
34    ) + "\n"
35    text = template_text.format(version=version, contributors=contributors_text)
36
37    target = Path(__file__).parent.joinpath(
38        "../doc/en/announce/release-{}.rst".format(version)
39    )
40    target.write_text(text, encoding="UTF-8")
41    print("[generate.announce] Generated {}".format(target.name))
42
43    # Update index with the new release entry
44    index_path = Path(__file__).parent.joinpath("../doc/en/announce/index.rst")
45    lines = index_path.read_text(encoding="UTF-8").splitlines()
46    indent = "   "
47    for index, line in enumerate(lines):
48        if line.startswith("{}release-".format(indent)):
49            new_line = indent + target.stem
50            if line != new_line:
51                lines.insert(index, new_line)
52                index_path.write_text("\n".join(lines) + "\n", encoding="UTF-8")
53                print("[generate.announce] Updated {}".format(index_path.name))
54            else:
55                print(
56                    "[generate.announce] Skip {} (already contains release)".format(
57                        index_path.name
58                    )
59                )
60            break
61
62    check_call(["git", "add", str(target)])
63
64
65@invoke.task()
66def regen(ctx):
67    """Call regendoc tool to update examples and pytest output in the docs."""
68    print("[generate.regen] Updating docs")
69    check_call(["tox", "-e", "regen"])
70
71
72@invoke.task()
73def make_tag(ctx, version):
74    """Create a new, local tag for the release, only if the repository is clean."""
75    from git import Repo
76
77    repo = Repo(".")
78    if repo.is_dirty():
79        print("Current repository is dirty. Please commit any changes and try again.")
80        raise invoke.Exit(code=2)
81
82    tag_names = [x.name for x in repo.tags]
83    if version in tag_names:
84        print("[generate.make_tag] Delete existing tag {}".format(version))
85        repo.delete_tag(version)
86
87    print("[generate.make_tag] Create tag {}".format(version))
88    repo.create_tag(version)
89
90
91@invoke.task(help={"version": "version being released"})
92def pre_release(ctx, version):
93    """Generates new docs, release announcements and creates a local tag."""
94    announce(ctx, version)
95    regen(ctx)
96    changelog(ctx, version, write_out=True)
97
98    msg = "Preparing release version {}".format(version)
99    check_call(["git", "commit", "-a", "-m", msg])
100
101    make_tag(ctx, version)
102
103    print()
104    print("[generate.pre_release] Please push your branch and open a PR.")
105
106
107@invoke.task(
108    help={
109        "version": "version being released",
110        "write_out": "write changes to the actual changelog",
111    }
112)
113def changelog(ctx, version, write_out=False):
114    if write_out:
115        addopts = []
116    else:
117        addopts = ["--draft"]
118    check_call(["towncrier", "--yes", "--version", version] + addopts)
119