summaryrefslogtreecommitdiff
path: root/doc/neps/tools/build_index.py
blob: bcf414ddc872cf6d80b45499d342687cd6143b0b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
"""
Scan the directory of nep files and extract their metadata.  The
metadata is passed to Jinja for filling out the toctrees for various NEP
categories.
"""

import os
import jinja2
import glob
import re


def render(tpl_path, context):
    path, filename = os.path.split(tpl_path)
    return jinja2.Environment(
        loader=jinja2.FileSystemLoader(path or './')
    ).get_template(filename).render(context)

def nep_metadata():
    ignore = ('nep-template.rst')
    sources = sorted(glob.glob(r'nep-*.rst'))
    sources = [s for s in sources if not s in ignore]

    meta_re = r':([a-zA-Z\-]*): (.*)'

    has_provisional = False
    neps = {}
    print('Loading metadata for:')
    for source in sources:
        print(f' - {source}')
        nr = int(re.match(r'nep-([0-9]{4}).*\.rst', source).group(1))

        with open(source) as f:
            lines = f.readlines()
            tags = [re.match(meta_re, line) for line in lines]
            tags = [match.groups() for match in tags if match is not None]
            tags = {tag[0]: tag[1] for tag in tags}

            # The title should be the first line after a line containing only
            # * or = signs.
            for i, line in enumerate(lines[:-1]):
                chars = set(line.rstrip())
                if len(chars) == 1 and ("=" in chars or "*" in chars):
                    break
            else:
                raise RuntimeError("Unable to find NEP title.")

            tags['Title'] = lines[i+1].strip()
            tags['Filename'] = source

        if not tags['Title'].startswith(f'NEP {nr} — '):
            raise RuntimeError(
                f'Title for NEP {nr} does not start with "NEP {nr} — " '
                '(note that — here is a special, elongated dash). Got: '
                f'    {tags["Title"]!r}')

        if tags['Status'] in ('Accepted', 'Rejected', 'Withdrawn'):
            if not 'Resolution' in tags:
                raise RuntimeError(
                    f'NEP {nr} is Accepted/Rejected/Withdrawn but '
                    'has no Resolution tag'
                )
        if tags['Status'] == 'Provisional':
            has_provisional = True

        neps[nr] = tags

    # Now that we have all of the NEP metadata, do some global consistency
    # checks

    for nr, tags in neps.items():
        if tags['Status'] == 'Superseded':
            if not 'Replaced-By' in tags:
                raise RuntimeError(
                    f'NEP {nr} has been Superseded, but has no Replaced-By tag'
                )

            replaced_by = int(tags['Replaced-By'])
            replacement_nep = neps[replaced_by]

            if not 'Replaces' in replacement_nep:
                raise RuntimeError(
                    f'NEP {nr} is superseded by {replaced_by}, but that NEP has '
                    f"no Replaces tag."
                )

            if not int(replacement_nep['Replaces']) == nr:
                raise RuntimeError(
                    f'NEP {nr} is superseded by {replaced_by}, but that NEP has a '
                    f"Replaces tag of `{replacement_nep['Replaces']}`."
                )

        if 'Replaces' in tags:
            replaced_nep = int(tags['Replaces'])
            replaced_nep_tags = neps[replaced_nep]
            if not replaced_nep_tags['Status'] == 'Superseded':
                raise RuntimeError(
                    f'NEP {nr} replaces {replaced_nep}, but that NEP has not '
                    f'been set to Superseded'
                )

    return {'neps': neps, 'has_provisional': has_provisional}

meta = nep_metadata()

for nepcat in (
    "provisional", "accepted", "deferred", "finished", "meta",
    "open", "rejected",
):
    infile = f"{nepcat}.rst.tmpl"
    outfile =f"{nepcat}.rst"

    print(f'Compiling {infile} -> {outfile}')
    genf = render(infile, meta)
    with open(outfile, 'w') as f:
        f.write(genf)