import sys
assert sys.version_info[0] == 3, "This program requires Python 3"
import re
from doctest import testfile
from io import StringIO
import bitarray.util
BASE_URL = "https://github.com/ilanschnell/bitarray"
NEW_IN = {
'bitarray': '2.3: optional `buffer` argument',
'bitarray.bytereverse': '2.2.5: optional start and stop arguments',
'bitarray.clear': '1.4',
'bitarray.count': ['1.1.0: optional start and stop arguments',
'2.3.7: optional step argument'],
'bitarray.find': '2.1',
'bitarray.frombytes': '2.5.0: allow bytes-like argument',
'bitarray.invert': '1.5.3: optional index argument',
'bitarray.pack': '2.5.0: allow bytes-like argument',
'decodetree': '1.6',
'frozenbitarray': '1.1',
'get_default_endian': '1.3',
'util.any_and': '2.7',
'util.ba2base': '1.9',
'util.base2ba': '1.9',
'util.count_n': '2.3.6: optional value argument',
'util.deserialize': ['1.8',
'2.5.0: allow bytes-like argument'],
'util.intervals': '2.7',
'util.make_endian': '1.3',
'util.parity': '1.9',
'util.pprint': '1.8',
'util.rindex': '2.3.0: optional start and stop arguments',
'util.serialize': '1.8',
'util.urandom': '1.7',
'util.sc_encode': '2.7',
'util.sc_decode': '2.7',
'util.vl_decode': '2.2',
'util.vl_encode': '2.2',
'util.canonical_huffman': '2.5',
'util.canonical_decode': '2.5',
}
DOCS = {
'chc': ('Canonical Huffman Coding', 'canonical.rst'),
'rep': ('Bitarray representations', 'represent.rst'),
'sc': ('Compression of sparse bitarrays', 'sparse_compression.rst'),
'vlf': ('Variable length bitarray format', 'variable_length.rst'),
}
DOC_LINKS = {
'util.canonical_huffman': 'chc',
'util.canonical_decode': 'chc',
'util.ba2base': 'rep',
'util.base2ba': 'rep',
'util.deserialize': 'rep',
'util.serialize': 'rep',
'util.sc_decode': 'sc',
'util.sc_encode': 'sc',
'util.vl_decode': 'vlf',
'util.vl_encode': 'vlf',
}
NOTES = {
'bitarray.pack': """\
This method, as well as the ``.unpack()`` method, are meant for efficient
transfer of data between bitarray objects to other Python objects (for
example NumPy's ndarray object) which have a different memory view.""",
'bitarray.tolist': """\
Note that the list object being created will require 32 or 64 times more
memory (depending on the machine architecture) than the bitarray object,
which may cause a memory error if the bitarray is very large.""",
'util.count_xor': " This is also known as the Hamming distance."
}
GETSET = {
'bitarray.nbytes': 'int',
'bitarray.padbits': 'int',
'bitarray.readonly': 'bool',
}
_NAMES = set()
sig_pat = re.compile(r"""
( # group 1
(\w+) # function name, group 2
\([^()]*\) # (...)
)
( # optional group 3
\s->\s(.+) # return type, group 4
)?
""", re.VERBOSE)
def get_doc(name):
parts = name.split('.')
obj = bitarray
while parts:
obj = getattr(obj, parts.pop(0))
lines = obj.__doc__.splitlines()
if len(lines) == 1:
sig = '``%s`` -> %s' % (obj.__name__, GETSET[name])
return sig, lines
m = sig_pat.match(lines[0])
if m is None:
raise Exception("signature invalid: %r" % lines[0])
sig = '``%s``' % m.group(1)
assert m.group(2) == obj.__name__
if m.group(4):
sig += ' -> %s' % m.group(4)
assert lines[1] == ''
return sig, lines[2:]
def write_doc(fo, name):
_NAMES.add(name)
sig, lines = get_doc(name)
fo.write(sig + '\n')
for line in lines:
out = line.rstrip()
fo.write(" %s\n" % out.replace('`', '``') if out else "\n")
note = NOTES.get(name)
if note:
fo.write("\n%s\n" % note)
link = DOC_LINKS.get(name)
if link:
title, filename = DOCS[link]
url = BASE_URL + '/blob/master/doc/' + filename
fo.write("\n See also: `%s <%s>`__\n" % (title, url))
new_in = NEW_IN.get(name)
if new_in:
for line in new_in if isinstance(new_in, list) else [new_in]:
fo.write("\n New in version %s.\n" % line.replace('`', '``'))
fo.write('\n\n')
def write_reference(fo):
fo.write("""\
Reference
=========
bitarray version: %s -- `change log <%s>`__
In the following, ``item`` and ``value`` are usually a single bit -
an integer 0 or 1.
The bitarray object:
--------------------
""" % (bitarray.__version__, BASE_URL + "/blob/master/doc/changelog.rst"))
write_doc(fo, 'bitarray')
fo.write("bitarray methods:\n"
"-----------------\n\n")
for method in sorted(dir(bitarray.bitarray)):
if method.startswith('_'):
continue
name = 'bitarray.%s' % method
if name not in GETSET:
write_doc(fo, name)
fo.write("bitarray data descriptors:\n"
"--------------------------\n\n"
"Data descriptors were added in version 2.6.\n\n")
for getset in sorted(dir(bitarray.bitarray)):
name = 'bitarray.%s' % getset
if name in GETSET:
write_doc(fo, name)
fo.write("Other objects:\n"
"--------------\n\n")
write_doc(fo, 'frozenbitarray')
write_doc(fo, 'decodetree')
fo.write("Functions defined in the `bitarray` module:\n"
"-------------------------------------------\n\n")
for func in sorted(['test', 'bits2bytes', 'get_default_endian']):
write_doc(fo, func)
fo.write("Functions defined in `bitarray.util` module:\n"
"--------------------------------------------\n\n"
"This sub-module was added in version 1.2.\n\n")
for func in bitarray.util.__all__:
write_doc(fo, 'util.%s' % func)
for name in list(NEW_IN) + list(DOC_LINKS):
assert name in _NAMES, name
def update_readme(path):
ver_pat = re.compile(r'(bitarray.+?)\s(\d+\.\d+\.\d+)')
with open(path, 'r') as fi:
data = fi.read()
with StringIO() as fo:
for line in data.splitlines():
if line == 'Reference':
break
line = ver_pat.sub(r'\1 ' + bitarray.__version__, line)
fo.write("%s\n" % line.rstrip())
write_reference(fo)
new_data = fo.getvalue()
if new_data == data:
print("already up-to-date")
else:
with open(path, 'w') as f:
f.write(new_data)
def write_changelog(fo):
ver_pat = re.compile(r'(\d{4}-\d{2}-\d{2})\s+(\d+\.\d+\.\d+)')
hash_pat = re.compile(r'#([0-9a-f]+)')
link_pat = re.compile(r'\[(.+)\]\((.+)\)')
def hash_replace(match):
group1 = match.group(1)
if len(group1) >= 7:
if len(group1) != 8:
print("Warning: commit hash length != 8, got", len(group1))
url = "%s/commit/%s" % (BASE_URL, group1)
else:
url = "%s/issues/%d" % (BASE_URL, int(group1))
return "`%s <%s>`__" % (match.group(0), url)
fo.write("Change log\n"
"==========\n\n")
for line in open('./CHANGE_LOG'):
line = line.rstrip()
match = ver_pat.match(line)
if match:
line = match.expand(r'**\2** (\1):')
elif line.startswith('-----'):
line = ''
elif line.startswith(' '):
line = line[2:]
line = line.replace('`', '``')
line = hash_pat.sub(hash_replace, line)
line = link_pat.sub(r"`\1 <\2>`__", line)
fo.write(line + '\n')
def main():
if len(sys.argv) > 1:
sys.exit("no arguments expected")
update_readme('./README.rst')
with open('./doc/reference.rst', 'w') as fo:
write_reference(fo)
with open('./doc/changelog.rst', 'w') as fo:
write_changelog(fo)
testfile('./README.rst')
testfile('./doc/buffer.rst')
testfile('./doc/canonical.rst')
testfile('./doc/represent.rst')
testfile('./doc/sparse_compression.rst')
testfile('./doc/variable_length.rst')
if __name__ == '__main__':
main()