From f63057474a461fe814458c66515e281700f296df Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Thu, 19 Sep 2024 09:52:35 +0200 Subject: fix requirement ordering in reqs2tex --- reqs.toml | 27 +++++++++------------- scripts/reqs2tex.py | 65 ++++++++++++++++++++++++++++------------------------- 2 files changed, 45 insertions(+), 47 deletions(-) diff --git a/reqs.toml b/reqs.toml index 6645ea4..35c7a96 100644 --- a/reqs.toml +++ b/reqs.toml @@ -1,15 +1,10 @@ # This is a TOML file containing all project requirements. The reqs2tex script # can be used to generate the files necessary to compile requirements.tex and # cross-reference the requirements from other documents. -# -# Note that TOML has a hash table structure, so keys used for defining -# requirement properties cannot be used for requirement IDs. The properties are: -# label, type, id, index, deleted, done, description, priority # This is the requirement cross-reference ID. Requirements can be -# (cross-)referenced from LaTeX by prefixing this ID with `req:` and -# substituting dots for colons (i.e. this requirement is referenced as -# \cref{req:audio:async-api}). +# (cross-)referenced from LaTeX by prefixing this ID with `req:` (i.e. this +# requirement is referenced as \cref{req:audio}). [audio] # Requirement type ('system' | 'user') type = 'user' @@ -25,10 +20,10 @@ background music, while simultaniously playing sound effects. # is a list of strings, each item is treated as the ID of another requirement, # and the references are checked before LaTeX runs. done = [ - 'audio.async-api', - 'audio.handle', - 'audio.stream-mix', - 'audio.volume', + 'audio:async-api', + 'audio:handle', + 'audio:stream-mix', + 'audio:volume', ] #done = 'When I feel like it' # Requirements that are no longer applicable should set `deleted` to `true`. @@ -36,7 +31,7 @@ done = [ # different document revisions. #deleted = true -[audio.async-api] +[audio:async-api] type = 'system' priority = 'must' description = ''' @@ -44,7 +39,7 @@ The public audio \gls{api} supports starting audio samples asynchronously (i.e.~fire and forget). ''' -[audio.handle] +[audio:handle] type = 'system' priority = 'must' description = ''' @@ -52,14 +47,14 @@ The public audio \gls{api} allows the game programmer to control (i.e.~play, pause, resume and stop) audio samples after they are created/initialized. ''' -[audio.stream-mix] +[audio:stream-mix] type = 'system' priority = 'must' description = ''' The audio system supports playing multiple audio streams simultaniously. ''' -[audio.volume] +[audio:volume] type = 'system' priority = 'must' description = ''' @@ -67,7 +62,7 @@ The public audio \gls{api} allows the game programmer to control the volume of audio samples. ''' -[aux.license] +[aux:license] type = 'system' priority = 'must' description = ''' diff --git a/scripts/reqs2tex.py b/scripts/reqs2tex.py index e5f063d..1863b0d 100755 --- a/scripts/reqs2tex.py +++ b/scripts/reqs2tex.py @@ -1,9 +1,9 @@ #!/bin/python3 -import sys, tomllib, tex +import sys, tomllib, tex, re from enum import StrEnum def label2ref(*labels): - return ",".join(["req:" + label.replace('.', ':') for label in labels]) + return ",".join(["req:" + label for label in labels]) class KEY(StrEnum): LABEL = 'label' @@ -25,27 +25,6 @@ class REQ_PRIORITY(StrEnum): COULD = 'could' WONT = 'will not' -def flatten(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 KEY.LABEL in item: - item[KEY.LABEL] = f"{key}.{item[KEY.LABEL]}" - else: - item[KEY.LABEL] = f"{key}" - out += items - return out - id_counter = 0 def make_id(item): global id_counter @@ -79,8 +58,7 @@ def sanitize(item, ids): die("definition of done includes unknown requirement(s)") item[KEY.DONE] = tex.cmd('Cref', label2ref(*item[KEY.DONE])) -def convert(data): - reqs = flatten(data) +def convert(reqs): all_ids = [item[KEY.LABEL] for item in reqs] index = 0 for item in reqs: @@ -94,6 +72,9 @@ def convert(data): # skip deleted requirements (but process for make_id) reqs = [item for item in reqs if item[KEY.DELETED] == False] + # sort by label + reqs = sorted(reqs, key=lambda req: req[KEY.LABEL]) + return reqs def fmt_aux(data): @@ -145,20 +126,42 @@ def fmt_tex(data): ) return out +def tomlload(content): + # replace requirement labels with temp value + label_map = dict() + label_idx = 0 + lines = content.split("\n") + for index, line in enumerate(lines): + match = re.search(r"^\s*\[(.+)\]", line) + if match is None: continue + lines[index] = f"[{label_idx}]" + label_map[str(label_idx)] = match.group(1) + label_idx += 1 + content = "\n".join(lines) + + # load TOML and replace temporary labels with real labels + data_dict = tomllib.loads(content) + data_list = [] + for key, value in data_dict.items(): + value[KEY.LABEL] = label_map[key] + data_list.append(value) + + return data_list + def main(input_file): - data = {} - with open(input_file, "rb") as file: - data = tomllib.load(file) + data = [] + with open(input_file, "r") as file: + data = tomlload(file.read()) - requirements = convert(data) + items = convert(data) output_aux = input_file.removesuffix(".toml") + ".aux" with open(output_aux, "w+") as file: - file.write(fmt_aux(requirements)) + file.write(fmt_aux(items)) output_tex = input_file.removesuffix(".toml") + ".tex" with open(output_tex, "w+") as file: - file.write(fmt_tex(requirements)) + file.write(fmt_tex(items)) if __name__ == "__main__": if len(sys.argv) != 2: -- cgit v1.2.3