1
2 def iter_clean_lines(lines):
3 lines = iter(lines)
4 for line in lines:
5 line = line.strip()
6 if line.startswith('# XXX'):
7 continue
8 yield line
9
10
11 def parse_table_lines(lines):
12 lines = iter_clean_lines(lines)
13
14 for line in lines:
15 if line.startswith(('####', '#----')):
16 kind = 0 if line[1] == '#' else 1
17 try:
18 line = next(lines).strip()
19 except StopIteration:
20 line = ''
21 if not line.startswith('# '):
22 raise NotImplementedError(line)
23 yield kind, line[2:].lstrip()
24 continue
25
26 maybe = None
27 while line.startswith('#'):
28 if line != '#' and line[1] == ' ':
29 maybe = line[2:].lstrip()
30 try:
31 line = next(lines).strip()
32 except StopIteration:
33 return
34 if not line:
35 break
36 else:
37 if line:
38 if maybe:
39 yield 2, maybe
40 yield 'row', line
41
42
43 def iter_sections(lines):
44 header = None
45 section = []
46 for kind, value in parse_table_lines(lines):
47 if kind == 'row':
48 if not section:
49 if header is None:
50 header = value
51 continue
52 raise NotImplementedError(value)
53 yield tuple(section), value
54 else:
55 if header is None:
56 header = False
57 section[kind:] = [value]
58
59
60 def collect_sections(lines):
61 sections = {}
62 for section, row in iter_sections(lines):
63 if section not in sections:
64 sections[section] = [row]
65 else:
66 sections[section].append(row)
67 return sections
68
69
70 def collate_sections(lines):
71 collated = {}
72 for section, rows in collect_sections(lines).items():
73 parent = collated
74 current = ()
75 for name in section:
76 current += (name,)
77 try:
78 child, secrows, totalrows = parent[name]
79 except KeyError:
80 child = {}
81 secrows = []
82 totalrows = []
83 parent[name] = (child, secrows, totalrows)
84 parent = child
85 if current == section:
86 secrows.extend(rows)
87 totalrows.extend(rows)
88 return collated
89
90
91 #############################
92 # the commands
93
94 def cmd_count_by_section(lines):
95 div = ' ' + '-' * 50
96 total = 0
97 def render_tree(root, depth=0):
98 nonlocal total
99 indent = ' ' * depth
100 for name, data in root.items():
101 subroot, rows, totalrows = data
102 sectotal = f'({len(totalrows)})' if totalrows != rows else ''
103 count = len(rows) if rows else ''
104 if depth == 0:
105 yield div
106 yield f'{sectotal:>7} {count:>4} {indent}{name}'
107 yield from render_tree(subroot, depth+1)
108 total += len(rows)
109 sections = collate_sections(lines)
110 yield from render_tree(sections)
111 yield div
112 yield f'(total: {total})'
113
114
115 #############################
116 # the script
117
118 def parse_args(argv=None, prog=None):
119 import argparse
120 parser = argparse.ArgumentParser(prog=prog)
121 parser.add_argument('filename')
122
123 args = parser.parse_args(argv)
124 ns = vars(args)
125
126 return ns
127
128
129 def main(filename):
130 with open(filename) as infile:
131 for line in cmd_count_by_section(infile):
132 print(line)
133
134
135 if __name__ == '__main__':
136 kwargs = parse_args()
137 main(**kwargs)