1import sys
2assert sys.version_info[0] == 3, "This program requires Python 3"
3
4import re
5import doctest
6from io import StringIO
7
8import bitarray
9import bitarray.util
10
11
12BASE_URL = "https://github.com/ilanschnell/bitarray"
13
14NEW_IN = {
15    'frozenbitarray':       '1.1',
16    'get_default_endian':   '1.3',
17    'util.make_endian':     '1.3',
18    'bitarray':             '2.3: optional `buffer` argument',
19    'bitarray.bytereverse': '2.2.5: optional `start` and `stop` arguments',
20    'bitarray.count':       '1.1.0: optional `start` and `stop` arguments',
21    'bitarray.clear':       '1.4',
22    'bitarray.find':        '2.1',
23    'bitarray.invert':      '1.5.3: optional `index` argument',
24    'decodetree':           '1.6',
25    'util.urandom':         '1.7',
26    'util.pprint':          '1.8',
27    'util.serialize':       '1.8',
28    'util.deserialize':     '1.8',
29    'util.ba2base':         '1.9',
30    'util.base2ba':         '1.9',
31    'util.parity':          '1.9',
32    'util.rindex':          '2.3.0: optional `start` and `stop` arguments',
33    'util.vl_encode':       '2.2',
34    'util.vl_decode':       '2.2',
35}
36
37DOCS = {
38    'rep': ('Bitarray representations', 'represent.rst'),
39    'vlf': ('Variable length bitarray format', 'variable_length.rst'),
40}
41
42DOC_LINKS = {
43    'util.ba2base':     'rep',
44    'util.base2ba':     'rep',
45    'util.serialize':   'rep',
46    'util.deserialize': 'rep',
47    'util.vl_encode':   'vlf',
48    'util.vl_decode':   'vlf',
49}
50
51_NAMES = set()
52
53sig_pat = re.compile(r'(\w+\([^()]*\))( -> (.+))?')
54def write_doc(fo, name):
55    _NAMES.add(name)
56    doc = eval('bitarray.%s.__doc__' % name)
57    assert doc, name
58    lines = doc.splitlines()
59    m = sig_pat.match(lines[0])
60    if m is None:
61        raise Exception("signature line invalid: %r" % lines[0])
62    s = '``%s``' %  m.group(1)
63    if m.group(3):
64        s += ' -> %s' % m.group(3)
65    fo.write('%s\n' % s)
66    assert lines[1] == ''
67    for line in lines[2:]:
68        out = line.rstrip()
69        fo.write("   %s\n" % out.replace('`', '``') if out else "\n")
70
71    link = DOC_LINKS.get(name)
72    if link:
73        title, filename = DOCS[link]
74        url = BASE_URL + '/blob/master/doc/' + filename
75        fo.write("\n   See also: `%s <%s>`__\n" % (title, url))
76
77    new_in = NEW_IN.get(name)
78    if new_in:
79        fo.write("\n   New in version %s.\n" % new_in.replace('`', '``'))
80
81    fo.write('\n\n')
82
83
84def write_reference(fo):
85    fo.write("""\
86Reference
87=========
88
89bitarray version: %s -- `change log <%s>`__
90
91In the following, ``item`` and ``value`` are usually a single bit -
92an integer 0 or 1.
93
94
95The bitarray object:
96--------------------
97
98""" % (bitarray.__version__, BASE_URL + "/blob/master/doc/changelog.rst"))
99    write_doc(fo, 'bitarray')
100
101    fo.write("**A bitarray object supports the following methods:**\n\n")
102    for method in sorted(dir(bitarray.bitarray)):
103        if method.startswith('_'):
104            continue
105        write_doc(fo, 'bitarray.%s' % method)
106
107    fo.write("Other objects:\n"
108             "--------------\n\n")
109    write_doc(fo, 'frozenbitarray')
110    write_doc(fo, 'decodetree')
111
112    fo.write("Functions defined in the `bitarray` module:\n"
113             "-------------------------------------------\n\n")
114    for func in sorted(['test', 'bits2bytes', 'get_default_endian']):
115        write_doc(fo, func)
116
117    fo.write("Functions defined in `bitarray.util` module:\n"
118             "--------------------------------------------\n\n"
119             "This sub-module was add in version 1.2.\n\n")
120    for func in bitarray.util.__all__:
121        write_doc(fo, 'util.%s' % func)
122
123    for name in list(NEW_IN) + list(DOC_LINKS):
124        assert name in _NAMES, name
125
126def update_readme(path):
127    ver_pat = re.compile(r'(bitarray.+?)(\d+\.\d+\.\d+)')
128
129    with open(path, 'r') as fi:
130        data = fi.read()
131
132    with StringIO() as fo:
133        for line in data.splitlines():
134            if line == 'Reference':
135                break
136            line = ver_pat.sub(lambda m: m.group(1) + bitarray.__version__,
137                               line)
138            fo.write("%s\n" % line.rstrip())
139
140        write_reference(fo)
141        new_data = fo.getvalue()
142
143    if new_data == data:
144        print("already up-to-date")
145    else:
146        with open(path, 'w') as f:
147            f.write(new_data)
148
149
150def write_changelog(fo):
151    ver_pat = re.compile(r'(\d{4}-\d{2}-\d{2})\s+(\d+\.\d+\.\d+)')
152    hash_pat = re.compile(r'#([0-9a-f]+)')
153    link_pat = re.compile(r'\[(.+)\]\((.+)\)')
154
155    def hash_replace(match):
156        group1 = match.group(1)
157        if len(group1) >= 7:
158            if len(group1) != 8:
159                print("Warning: commit hash length != 8, got", len(group1))
160            url = "%s/commit/%s" % (BASE_URL, group1)
161        else:
162            url = "%s/issues/%d" % (BASE_URL, int(group1))
163        return "`%s <%s>`__" % (match.group(0), url)
164
165    fo.write("Change log\n"
166             "==========\n\n")
167
168    for line in open('./CHANGE_LOG'):
169        line = line.rstrip()
170        match = ver_pat.match(line)
171        if match:
172            line = match.expand(r'**\2** (\1):')
173        elif line.startswith('-----'):
174            line = ''
175        elif line.startswith('  '):
176            line = line[2:]
177        line = line.replace('`', '``')
178        line = hash_pat.sub(hash_replace, line)
179        line = link_pat.sub(
180                    lambda m: "`%s <%s>`__" % (m.group(1), m.group(2)), line)
181        fo.write(line + '\n')
182
183
184def main():
185    if len(sys.argv) > 1:
186        sys.exit("no arguments expected")
187
188    update_readme('./README.rst')
189    with open('./doc/reference.rst', 'w') as fo:
190        write_reference(fo)
191    with open('./doc/changelog.rst', 'w') as fo:
192        write_changelog(fo)
193
194    doctest.testfile('./README.rst')
195    doctest.testfile('./doc/buffer.rst')
196    doctest.testfile('./doc/represent.rst')
197    doctest.testfile('./doc/variable_length.rst')
198
199
200if __name__ == '__main__':
201    main()
202