diff options
Diffstat (limited to 'scripts/reqs2tex.py')
-rwxr-xr-x | scripts/reqs2tex.py | 132 |
1 files changed, 95 insertions, 37 deletions
diff --git a/scripts/reqs2tex.py b/scripts/reqs2tex.py index 6b7b77a..e5f063d 100755 --- a/scripts/reqs2tex.py +++ b/scripts/reqs2tex.py @@ -1,17 +1,48 @@ #!/bin/python3 import sys, tomllib, tex +from enum import StrEnum + +def label2ref(*labels): + return ",".join(["req:" + label.replace('.', ':') for label in labels]) + +class KEY(StrEnum): + LABEL = 'label' + TYPE = 'type' + ID = 'id' + INDEX = 'index' + DELETED = 'deleted' + DONE = 'done' + DESCRIPTION = 'description' + PRIORITY = 'priority' + +class REQ_TYPE(StrEnum): + SYSTEM = 'system' + USER = 'user' + +class REQ_PRIORITY(StrEnum): + MUST = 'must' + SHOULD = 'should' + COULD = 'could' + WONT = 'will not' def flatten(data): - if 'description' in data: - return [ data ] out = [] for key, value in data.items(): + # this item is a requirement + if key == KEY.DESCRIPTION: + out.append(data) + + # skip over reserved keys + if key in KEY: continue + + # recursively flatten other requirements items = flatten(value) + # and prefix them with the current key for item in items: - if 'label' in item: - item['label'] = f"{key}.{item['label']}" + if KEY.LABEL in item: + item[KEY.LABEL] = f"{key}.{item[KEY.LABEL]}" else: - item['label'] = f"{key}" + item[KEY.LABEL] = f"{key}" out += items return out @@ -20,72 +51,99 @@ def make_id(item): global id_counter id_counter += 1 return "{type_short}#{counter:03d}".format( - type_short = item['type'][0].upper(), + type_short = item[KEY.TYPE][0].upper(), counter = id_counter, ) def sanitize(item, ids): def die(msg): - print(f"[{item['label']}]: {msg}") + print(f"[{item[KEY.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') + item[KEY.DESCRIPTION] = item.get(KEY.DESCRIPTION) + item[KEY.DONE] = item.get(KEY.DONE) + item[KEY.PRIORITY] = item.get(KEY.PRIORITY) + item[KEY.TYPE] = item.get(KEY.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'])}") + if item[KEY.TYPE] not in REQ_TYPE: + die(f"unknown or missing requirement type: {repr(item[KEY.TYPE])}") + if item[KEY.PRIORITY] not in REQ_PRIORITY: + die(f"unknown or missing requirement priority: {repr(item[KEY.PRIORITY])}") # conversions - if isinstance(item['done'], list): + if isinstance(item[KEY.DONE], list): # safety check - if not set(item['done']).issubset(ids): + if not set(item[KEY.DONE]).issubset(ids): die("definition of done includes unknown requirement(s)") - item['done'] = tex.cmd('Cref', tex.label2ref(*item['done'])) + item[KEY.DONE] = tex.cmd('Cref', label2ref(*item[KEY.DONE])) def convert(data): reqs = flatten(data) + all_ids = [item[KEY.LABEL] for item in reqs] 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 + item[KEY.ID] = tex.esc(make_id(item)) + item[KEY.DELETED] = item.get(KEY.DELETED, False) + if item[KEY.DELETED]: continue + item[KEY.INDEX] = index index += 1 - sanitize(item, [req['label'] for req in reqs]) + sanitize(item, all_ids) # skip deleted requirements (but process for make_id) - reqs = [item for item in reqs if item['deleted'] == False] + reqs = [item for item in reqs if item[KEY.DELETED] == False] return reqs def fmt_aux(data): out = [] - for req in data: - ref = tex.label2ref(req['label']) + for item in data: + ref = label2ref(item[KEY.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')), + tex.cmd('newlabel', f"{ref}", tex.group( + item[KEY.ID], + '', + '', + ref, + '', + )), + tex.cmd('newlabel', f"{ref}@cref", tex.group( + f"[requirement][][]{item[KEY.ID]}", + '[][][]', + '', + '', + '', + )), ] 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 "") + out = "" + for item in data: + out += tex.join( + tex.cmd('subsection', f"{item[KEY.ID]}: {item[KEY.LABEL]}".upper()), + tex.withatletter( + tex.cmd('cref@constructprefix', 'requirement', r'\cref@result'), + tex.pedef('@currentlabel', item[KEY.ID]), + tex.pedef('@currentlabelname', item[KEY.ID]), + tex.pedef('cref@currentlabel', tex.group(['requirement'], [''], [r'\cref@result']) + item[KEY.ID]), + ), + tex.cmd('label', ['requirement'], label2ref(item[KEY.LABEL])), + tex.cmd('parbox', tex.cmd('linewidth'), + tex.env('description', tex.join( + tex.cmd('item', [tex.cmd('reqlabel', 'priority')]), + item[KEY.PRIORITY].title(), + tex.cmd('item', [tex.cmd('reqlabel', 'description')]), + item[KEY.DESCRIPTION], + *([ + tex.cmd('item', [tex.cmd('reqlabel', 'done')]), + item[KEY.DONE] + ] if item[KEY.DONE] is not None else []), + )), ) ) - return "\n\n".join(out) + return out def main(input_file): data = {} |