2022-02-08 21:37:57 -04:00
|
|
|
|
2022-12-07 18:02:47 -04:00
|
|
|
KINDS = [
|
|
|
|
'section-major',
|
|
|
|
'section-minor',
|
|
|
|
'section-group',
|
|
|
|
'row',
|
|
|
|
]
|
|
|
|
|
|
|
|
|
2022-02-08 21:37:57 -04:00
|
|
|
def iter_clean_lines(lines):
|
|
|
|
lines = iter(lines)
|
2022-12-07 18:02:47 -04:00
|
|
|
for rawline in lines:
|
|
|
|
line = rawline.strip()
|
|
|
|
if line.startswith('#') and not rawline.startswith('##'):
|
2022-02-08 21:37:57 -04:00
|
|
|
continue
|
2022-12-07 18:02:47 -04:00
|
|
|
yield line, rawline
|
2022-02-08 21:37:57 -04:00
|
|
|
|
|
|
|
|
|
|
|
def parse_table_lines(lines):
|
|
|
|
lines = iter_clean_lines(lines)
|
|
|
|
|
2022-12-07 18:02:47 -04:00
|
|
|
group = None
|
|
|
|
prev = ''
|
|
|
|
for line, rawline in lines:
|
|
|
|
if line.startswith('## '):
|
|
|
|
assert not rawline.startswith(' '), (line, rawline)
|
|
|
|
if group:
|
|
|
|
assert prev, (line, rawline)
|
|
|
|
kind, after, _ = group
|
|
|
|
assert kind and kind != 'section-group', (group, line, rawline)
|
|
|
|
assert after is not None, (group, line, rawline)
|
|
|
|
else:
|
|
|
|
assert not prev, (prev, line, rawline)
|
|
|
|
kind, after = group = ('section-group', None)
|
|
|
|
title = line[3:].lstrip()
|
|
|
|
assert title, (line, rawline)
|
|
|
|
if after is not None:
|
|
|
|
try:
|
|
|
|
line, rawline = next(lines)
|
|
|
|
except StopIteration:
|
|
|
|
line = None
|
|
|
|
if line != after:
|
|
|
|
raise NotImplementedError((group, line, rawline))
|
|
|
|
yield kind, title
|
|
|
|
group = None
|
|
|
|
elif group:
|
|
|
|
raise NotImplementedError((group, line, rawline))
|
|
|
|
elif line.startswith('##---'):
|
|
|
|
assert line.rstrip('-') == '##', (line, rawline)
|
|
|
|
group = ('section-minor', '', line)
|
|
|
|
elif line.startswith('#####'):
|
|
|
|
assert not line.strip('#'), (line, rawline)
|
|
|
|
group = ('section-major', '', line)
|
|
|
|
elif line:
|
|
|
|
yield 'row', line
|
|
|
|
prev = line
|
2022-02-08 21:37:57 -04:00
|
|
|
|
|
|
|
|
|
|
|
def iter_sections(lines):
|
|
|
|
header = None
|
|
|
|
section = []
|
|
|
|
for kind, value in parse_table_lines(lines):
|
|
|
|
if kind == 'row':
|
|
|
|
if not section:
|
|
|
|
if header is None:
|
|
|
|
header = value
|
|
|
|
continue
|
2022-12-07 18:02:47 -04:00
|
|
|
raise NotImplementedError(repr(value))
|
2022-02-08 21:37:57 -04:00
|
|
|
yield tuple(section), value
|
|
|
|
else:
|
|
|
|
if header is None:
|
|
|
|
header = False
|
2022-12-07 18:02:47 -04:00
|
|
|
start = KINDS.index(kind)
|
|
|
|
section[start:] = [value]
|
2022-02-08 21:37:57 -04:00
|
|
|
|
|
|
|
|
|
|
|
def collect_sections(lines):
|
|
|
|
sections = {}
|
|
|
|
for section, row in iter_sections(lines):
|
|
|
|
if section not in sections:
|
|
|
|
sections[section] = [row]
|
|
|
|
else:
|
|
|
|
sections[section].append(row)
|
|
|
|
return sections
|
|
|
|
|
|
|
|
|
|
|
|
def collate_sections(lines):
|
|
|
|
collated = {}
|
|
|
|
for section, rows in collect_sections(lines).items():
|
|
|
|
parent = collated
|
|
|
|
current = ()
|
|
|
|
for name in section:
|
|
|
|
current += (name,)
|
|
|
|
try:
|
|
|
|
child, secrows, totalrows = parent[name]
|
|
|
|
except KeyError:
|
|
|
|
child = {}
|
|
|
|
secrows = []
|
|
|
|
totalrows = []
|
|
|
|
parent[name] = (child, secrows, totalrows)
|
|
|
|
parent = child
|
|
|
|
if current == section:
|
|
|
|
secrows.extend(rows)
|
|
|
|
totalrows.extend(rows)
|
|
|
|
return collated
|
|
|
|
|
|
|
|
|
|
|
|
#############################
|
|
|
|
# the commands
|
|
|
|
|
|
|
|
def cmd_count_by_section(lines):
|
2022-02-09 20:10:53 -04:00
|
|
|
div = ' ' + '-' * 50
|
2022-02-08 21:37:57 -04:00
|
|
|
total = 0
|
|
|
|
def render_tree(root, depth=0):
|
|
|
|
nonlocal total
|
|
|
|
indent = ' ' * depth
|
|
|
|
for name, data in root.items():
|
|
|
|
subroot, rows, totalrows = data
|
|
|
|
sectotal = f'({len(totalrows)})' if totalrows != rows else ''
|
|
|
|
count = len(rows) if rows else ''
|
2022-02-09 20:10:53 -04:00
|
|
|
if depth == 0:
|
|
|
|
yield div
|
2022-02-08 21:37:57 -04:00
|
|
|
yield f'{sectotal:>7} {count:>4} {indent}{name}'
|
|
|
|
yield from render_tree(subroot, depth+1)
|
|
|
|
total += len(rows)
|
|
|
|
sections = collate_sections(lines)
|
|
|
|
yield from render_tree(sections)
|
2022-02-09 20:10:53 -04:00
|
|
|
yield div
|
2022-02-08 21:37:57 -04:00
|
|
|
yield f'(total: {total})'
|
|
|
|
|
|
|
|
|
|
|
|
#############################
|
|
|
|
# the script
|
|
|
|
|
|
|
|
def parse_args(argv=None, prog=None):
|
|
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(prog=prog)
|
|
|
|
parser.add_argument('filename')
|
|
|
|
|
|
|
|
args = parser.parse_args(argv)
|
|
|
|
ns = vars(args)
|
|
|
|
|
|
|
|
return ns
|
|
|
|
|
|
|
|
|
|
|
|
def main(filename):
|
|
|
|
with open(filename) as infile:
|
|
|
|
for line in cmd_count_by_section(infile):
|
|
|
|
print(line)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
kwargs = parse_args()
|
|
|
|
main(**kwargs)
|