aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--reqs.toml25
-rwxr-xr-xscripts/reqs2tex.py42
-rw-r--r--scripts/tex.py3
3 files changed, 63 insertions, 7 deletions
diff --git a/reqs.toml b/reqs.toml
index 5e00dd9..c05cf71 100644
--- a/reqs.toml
+++ b/reqs.toml
@@ -1,10 +1,35 @@
+# 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}).
[audio.async-api]
+# Requirement type ('system' | 'user')
type = 'system'
+# MoSCoW priority ('must' | 'should' | 'could' | 'will not')
priority = 'must'
+# Requirement body. Supports LaTeX formatting. (tip: use single quotes so
+# backslash doesn't act as an escape character)
description = '''
The public audio \gls{api} supports starting audio samples asynchronously
(i.e.~fire and forget).
'''
+# Definition of done (user requirements only). If 'done' is a string, it is
+# treated as LaTeX code (like description), if it is a list of strings, each
+# item is treated as the ID of another requirement.
+#done = 'When I feel like it'
+#done = [ 'audio.handle', 'audio.stream-mix' ]
+# Requirements that are no longer applicable should set `deleted` to `true`.
+# This will make sure the requirements are numbered consistently across
+# different document revisions.
+#deleted = true
[audio.handle]
type = 'system'
diff --git a/scripts/reqs2tex.py b/scripts/reqs2tex.py
index 9e71a48..c5ab3dd 100755
--- a/scripts/reqs2tex.py
+++ b/scripts/reqs2tex.py
@@ -9,7 +9,7 @@ def flatten(data):
items = flatten(value)
for item in items:
if 'label' in item:
- item['label'] = f"{key}:{item['label']}"
+ item['label'] = f"{key}.{item['label']}"
else:
item['label'] = f"{key}"
out += items
@@ -24,16 +24,44 @@ def make_id(item):
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'])}")
+
+ # logic checks
+ if item['type'] != 'user' and item['done'] is not None:
+ die("has definition of done but is not a user requirement")
+
+ # 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)
- for index, item in enumerate(reqs):
+ index = 0
+ for item in reqs:
item['id'] = tex.esc(make_id(item))
- item['index'] = index
- item['description'] = item.get('description', '???')
- item['done'] = item.get('done', None)
- item['priority'] = item.get('priority', 'must')
- item['type'] = item.get('type', 'system')
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]
diff --git a/scripts/tex.py b/scripts/tex.py
index b044857..2fd51d8 100644
--- a/scripts/tex.py
+++ b/scripts/tex.py
@@ -41,3 +41,6 @@ def esc(plain):
def tabrule(*cells):
return "&".join(cells) + "\\\\"
+def label2ref(*labels):
+ return ",".join(["req:" + label.replace('.', ':') for label in labels])
+