diff options
author | Max-001 <80035972+Max-001@users.noreply.github.com> | 2024-09-17 17:11:50 +0200 |
---|---|---|
committer | Max-001 <80035972+Max-001@users.noreply.github.com> | 2024-09-17 17:11:50 +0200 |
commit | a76cbcc5def5a6a09fb72b8aad5724c1de80b2a7 (patch) | |
tree | 5788b2ba519f74060db15f1683db963f75532bcb /scripts/reqs2tex.py | |
parent | e007d0bcd7484a364d8f1ca425ef58b13d0bef93 (diff) | |
parent | 5c0649ac31b030cbb3c8c5e0684ee9419fe50054 (diff) |
Merge remote-tracking branch 'origin/master' into max/time
Diffstat (limited to 'scripts/reqs2tex.py')
-rwxr-xr-x | scripts/reqs2tex.py | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/scripts/reqs2tex.py b/scripts/reqs2tex.py new file mode 100755 index 0000000..6b7b77a --- /dev/null +++ b/scripts/reqs2tex.py @@ -0,0 +1,110 @@ +#!/bin/python3 +import sys, tomllib, tex + +def flatten(data): + if 'description' in data: + return [ data ] + out = [] + for key, value in data.items(): + items = flatten(value) + for item in items: + if 'label' in item: + item['label'] = f"{key}.{item['label']}" + else: + item['label'] = f"{key}" + out += items + return out + +id_counter = 0 +def make_id(item): + global id_counter + id_counter += 1 + return "{type_short}#{counter:03d}".format( + type_short = item['type'][0].upper(), + counter = id_counter, + ) + +def sanitize(item, ids): + def die(msg): + print(f"[{item['label']}]: {msg}") + exit(1) + + # ensure properties + item['description'] = item.get('description') + item['done'] = item.get('done') + item['priority'] = item.get('priority') + item['type'] = item.get('type') + + # type checks + if item['type'] not in ['system', 'user']: + die(f"unknown or missing requirement type {repr(item['type'])}") + if item['priority'] not in ['must', 'should', 'could', 'will not']: + die(f"unknown or missing requirement priority {repr(item['type'])}") + + # conversions + if isinstance(item['done'], list): + # safety check + if not set(item['done']).issubset(ids): + die("definition of done includes unknown requirement(s)") + item['done'] = tex.cmd('Cref', tex.label2ref(*item['done'])) + +def convert(data): + reqs = flatten(data) + index = 0 + for item in reqs: + item['id'] = tex.esc(make_id(item)) + item['deleted'] = item.get('deleted', False) + if item['deleted']: continue + item['index'] = index + index += 1 + sanitize(item, [req['label'] for req in reqs]) + + # skip deleted requirements (but process for make_id) + reqs = [item for item in reqs if item['deleted'] == False] + + return reqs + +def fmt_aux(data): + out = [] + for req in data: + ref = tex.label2ref(req['label']) + out += [ + tex.cmd('newlabel', f"{ref}", tex.group(req['id'], req['id'], 'ggg', 'hhh', 'iii')), + tex.cmd('newlabel', f"{ref}@cref", tex.group(f"[requirement][aaa][bbb]{req['id']}", '[ccc][ddd][eee]fff')), + ] + return "\n".join(out) + +def fmt_tex(data): + out = [] + for req in data: + out.append( + tex.cmd('subsection', req['id']) + "\n\n" +\ + tex.env('description', + tex.cmd('item', ['Priority']) + req['priority'].title() +\ + tex.cmd('item', ['Requirement']) + req['description'] +\ + (tex.cmd('item', ['Definition of done']) + req['done'] if req['done'] is not None else "") + ) + ) + return "\n\n".join(out) + +def main(input_file): + data = {} + with open(input_file, "rb") as file: + data = tomllib.load(file) + + requirements = convert(data) + + output_aux = input_file.removesuffix(".toml") + ".aux" + with open(output_aux, "w+") as file: + file.write(fmt_aux(requirements)) + + output_tex = input_file.removesuffix(".toml") + ".tex" + with open(output_tex, "w+") as file: + file.write(fmt_tex(requirements)) + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("usage: reqs2tex.py reqs.toml") + exit(1) + main(sys.argv[1]) + |