aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLoek Le Blansch <loek@pipeframe.xyz>2024-09-19 09:52:35 +0200
committerLoek Le Blansch <loek@pipeframe.xyz>2024-09-19 09:52:35 +0200
commitf63057474a461fe814458c66515e281700f296df (patch)
treea38b4510343dacf3091b3390dcd4f972c720754c
parent5d13bc5999c4abc51b9bcccf4b8b2cbd44a0193c (diff)
fix requirement ordering in reqs2texloek/requirements
-rw-r--r--reqs.toml27
-rwxr-xr-xscripts/reqs2tex.py65
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: