From 581044887a16d37c90116da544f5d9d600faa80c Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Tue, 17 Sep 2024 16:56:36 +0200 Subject: more fixes for reqs2tex --- scripts/reqs2tex.py | 103 ++++++++++++++++++++++++++++++++++------------------ scripts/tex.py | 3 -- 2 files changed, 68 insertions(+), 38 deletions(-) diff --git a/scripts/reqs2tex.py b/scripts/reqs2tex.py index 6b7b77a..db7e174 100755 --- a/scripts/reqs2tex.py +++ b/scripts/reqs2tex.py @@ -1,17 +1,47 @@ #!/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' + +# this doesn't work right def flatten(data): - if 'description' in data: - return [ data ] out = [] + # this key is a requirement + if KEY.DESCRIPTION in data: + out.append(data) + # check for children for key, value in data.items(): + # skip over reserved keys + if key in KEY: continue + items = flatten(value) 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 +50,75 @@ 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], item[KEY.ID], 'ggg', 'hhh', 'iii')), + tex.cmd('newlabel', f"{ref}@cref", tex.group(f"[requirement][aaa][bbb]{item[KEY.ID]}", '[ccc][ddd][eee]fff')), ] return "\n".join(out) def fmt_tex(data): out = [] - for req in data: + for item in data: out.append( - tex.cmd('subsection', req['id']) + "\n\n" +\ + tex.cmd('subsection', item[KEY.ID]) +\ + tex.cmd('label', label2ref(item[KEY.LABEL])) +\ + tex.cmd('par') +\ 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 "") + tex.cmd('item', ['Priority']) + item[KEY.PRIORITY].title() +\ + tex.cmd('item', ['Requirement']) + item[KEY.DESCRIPTION] +\ + (tex.cmd('item', ['Definition of done']) + item[KEY.DONE] if item[KEY.DONE] is not None else "") ) ) - return "\n\n".join(out) + return "".join(out) def main(input_file): data = {} diff --git a/scripts/tex.py b/scripts/tex.py index 2509a87..59c6895 100644 --- a/scripts/tex.py +++ b/scripts/tex.py @@ -47,6 +47,3 @@ def esc(plain): def tabrule(*cells): return "&".join(cells) + "\\\\" -def label2ref(*labels): - return ",".join(["req:" + label.replace('.', ':') for label in labels]) - -- cgit v1.2.3 From ca5fb75953ae2a73d2d41ceff59e2688b11cbf2b Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Tue, 17 Sep 2024 17:06:04 +0200 Subject: fix flatten function --- scripts/reqs2tex.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/reqs2tex.py b/scripts/reqs2tex.py index db7e174..8c2236a 100755 --- a/scripts/reqs2tex.py +++ b/scripts/reqs2tex.py @@ -25,18 +25,19 @@ class REQ_PRIORITY(StrEnum): COULD = 'could' WONT = 'will not' -# this doesn't work right def flatten(data): out = [] - # this key is a requirement - if KEY.DESCRIPTION in data: - out.append(data) - # check for children 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]}" -- cgit v1.2.3 From 1df61d671706436c17e23bc9dcdc3bbd0f14a167 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Tue, 17 Sep 2024 17:35:19 +0200 Subject: labels/refs working inside requirements.tex --- scripts/reqs2tex.py | 28 +++++++++++++++++----------- scripts/tex.py | 17 +++++++++++++++-- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/scripts/reqs2tex.py b/scripts/reqs2tex.py index 8c2236a..700d05f 100755 --- a/scripts/reqs2tex.py +++ b/scripts/reqs2tex.py @@ -107,19 +107,25 @@ def fmt_aux(data): return "\n".join(out) def fmt_tex(data): - out = [] + out = "" for item in data: - out.append( - tex.cmd('subsection', item[KEY.ID]) +\ - tex.cmd('label', label2ref(item[KEY.LABEL])) +\ - tex.cmd('par') +\ - tex.env('description', - tex.cmd('item', ['Priority']) + item[KEY.PRIORITY].title() +\ - tex.cmd('item', ['Requirement']) + item[KEY.DESCRIPTION] +\ - (tex.cmd('item', ['Definition of done']) + item[KEY.DONE] if item[KEY.DONE] is not None else "") - ) + out += tex.join( + tex.cmd('subsection', item[KEY.ID]), + 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('par'), + tex.env('description', tex.join( + tex.cmd('item', ['Priority']) + item[KEY.PRIORITY].title(), + tex.cmd('item', ['Requirement']) + item[KEY.DESCRIPTION], + (tex.cmd('item', ['Definition of done']) + item[KEY.DONE] if item[KEY.DONE] is not None else ""), + )) ) - return "".join(out) + return out def main(input_file): data = {} diff --git a/scripts/tex.py b/scripts/tex.py index 59c6895..eebf8ec 100644 --- a/scripts/tex.py +++ b/scripts/tex.py @@ -9,6 +9,9 @@ def group(*args): out += "{" + arg + "}" return out +def join(*things): + return "".join(things) + def string(content): return r"\string" + content @@ -18,11 +21,14 @@ def cmd(*args): if len(args) == 0: args = [""] return f"\\{name}" + group(*args) +def pedef(*args): + return r"\protected@edef" + cmd(*args) + def csdef(*args): return r"\def" + cmd(*args) -def auxout(content): - return r"\write\@auxout" + group(content) +def auxout(*content): + return r"\write\@auxout" + group(join(*content)) def scmd(*args): return string(cmd(*args)) @@ -47,3 +53,10 @@ def esc(plain): def tabrule(*cells): return "&".join(cells) + "\\\\" +def withatletter(*content): + return join( + cmd('makeatletter'), + *content, + cmd('makeatother'), + ) + -- cgit v1.2.3 From de6821389604dc5254b285a1ca13b19ffb1905b5 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Tue, 17 Sep 2024 19:33:16 +0200 Subject: cleanup --- scripts/tex.py | 9 +++++ scripts/time2tex.py | 111 ++++++++++++++++++++++++++-------------------------- 2 files changed, 65 insertions(+), 55 deletions(-) diff --git a/scripts/tex.py b/scripts/tex.py index eebf8ec..e8fc65b 100644 --- a/scripts/tex.py +++ b/scripts/tex.py @@ -60,3 +60,12 @@ def withatletter(*content): cmd('makeatother'), ) +def explist(*items): + out = [] + for item in items: + if isinstance(item, str) or not hasattr(item, '__iter__'): + out.append(item) + else: + out += explist(*item) + return out + diff --git a/scripts/time2tex.py b/scripts/time2tex.py index 8c3dd9b..a5d6802 100755 --- a/scripts/time2tex.py +++ b/scripts/time2tex.py @@ -35,35 +35,30 @@ def fmt_member_overview(times): tracked[time["name"]] += time["duration"] total_time += time["duration"] - out = "" - - # header - out += tex.cmd('toprule') - out += tex.tabrule(tex.cmd('textbf', 'Member'), tex.cmd('textbf', 'Tracked'), '') - out += tex.cmd('midrule') - - # member overview members = sorted(list(set(time["name"] for time in times))) - for name in members: - out += tex.tabrule(name, fmt_duration(tracked[name]), fmt_percentage(tracked[name] / total_time)) - out += tex.cmd('midrule') - - # sum - out += tex.tabrule('', fmt_duration(total_time), '') - out += tex.cmd('bottomrule') - - out = tex.env('tabular', 'lr@{~}l', out) - out = tex.cmd('centering') +\ - out +\ - tex.cmd('caption', 'Tracked time per group member') +\ - tex.cmd('label', 'tab:time-member') - out = tex.env('table', out) - - return out + return tex.env('table', tex.join( + tex.cmd('centering'), + tex.env('tabular', 'lr@{~}l', tex.join( + tex.cmd('toprule'), + tex.tabrule(tex.cmd('textbf', 'Member'), tex.cmd('textbf', 'Tracked')), + tex.cmd('midrule'), + *[ + tex.tabrule( + name, + fmt_duration(tracked[name]), + fmt_percentage(tracked[name] / total_time)) + for name in members + ], + tex.cmd('midrule'), + tex.tabrule('', fmt_duration(total_time), ''), + tex.cmd('bottomrule'), + )), + tex.cmd('caption', 'Tracked time per group member'), + tex.cmd('label', 'tab:time-member'), + )) def fmt_weekly_overview(times): # calculations - out = "" weeks = [] member_totals = {} total_time = sum(time["duration"] for time in times) @@ -93,34 +88,40 @@ def fmt_weekly_overview(times): for member in members: member_totals[member] = sum(time["duration"] for time in times if time["name"] == member) - # TODO: refactor - # begin table - out += r"\begin{table}\centering" - out += r"\fitimg{" - out += f"\\begin{{tabular}}{{l{'r@{~}l' * len(members)}@{{\\qquad}}r}}\\toprule" - out += r"\textbf{\#}" - for member in members: - out += f"&\\textbf{{{member}}}&" - out += r"&\textbf{Subtotal}\\\midrule{}" - - for entry in weeks: - out += f"{entry['num']}" - for member in members: - out += f"&{fmt_duration(entry['members'][member])}&{fmt_percentage(entry['members'][member] / entry['total'])}" - out += f"&{fmt_duration(entry['total'])}\\\\" - - out += r"\midrule{}" - for member in members: - out += f"&{fmt_duration(member_totals[member])}&{fmt_percentage(member_totals[member] / total_time)}" - out += f"&{fmt_duration(total_time)}\\\\" - - # end table - out += r"\bottomrule\end{tabular}" - out += r"}" # \fitimg - out += r"\caption{Tracked time per week}\label{tab:time-weekly}" - out += r"\end{table}" - - return out + return tex.env('table', tex.join( + tex.cmd('centering'), + tex.cmd('fitimg', + tex.env('tabular', r'l' + r'r@{~}l' * len(members) + r'@{\qquad}r', tex.join( + tex.cmd('toprule'), + tex.tabrule(*[ + tex.cmd('textbf', cell) + for cell in [ + tex.esc("#"), + *tex.explist([ member, "" ] for member in members), + "Subtotal", + ] + ]), + tex.cmd('midrule'), + *[ + tex.tabrule(*[ + str(entry['num']), + *tex.explist( + [ + fmt_duration(entry['members'][member]), + fmt_percentage(entry['members'][member] / entry['total']), + ] + for member in members + ), + fmt_duration(entry['total']), + ]) + for entry in weeks + ], + tex.cmd('bottomrule'), + )), + ), + tex.cmd('caption', 'Tracked time per week'), + tex.cmd('label', 'tab:time-weekly'), + )) def duration2secs(duration): out = 0 # output (seconds) @@ -182,13 +183,13 @@ def parse(content): return out def fmt(times): - return "\n\n".join([ + return tex.join( tex.cmd('section', 'Overviews'), tex.cmd('subsection', 'Members'), fmt_member_overview(times), tex.cmd('subsection', 'Weekly'), fmt_weekly_overview(times), - ]) + ) def main(input_file): content = "" -- cgit v1.2.3 From 6b034454f35819999cc26cfe472d537bf1eb3fbf Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Wed, 18 Sep 2024 13:40:39 +0200 Subject: fix requirement cross-references --- example.tex | 2 -- projdoc.cls | 83 +++++++++++++++++++++++++++++++++++++++++++++++------ readme.md | 6 ---- scripts/reqs2tex.py | 16 +++++++++-- 4 files changed, 88 insertions(+), 19 deletions(-) diff --git a/example.tex b/example.tex index e0d21c0..24c525b 100644 --- a/example.tex +++ b/example.tex @@ -3,8 +3,6 @@ % with the [draft] option. this replaces all images with placeholders. \input{meta.tex} -\externaldocument{reqs}[./requirements.pdf] - \title{Example Document} \begin{document} diff --git a/projdoc.cls b/projdoc.cls index fe8c8bc..23f3ea9 100644 --- a/projdoc.cls +++ b/projdoc.cls @@ -145,13 +145,13 @@ % \cref{} is used \makeatletter \NewDocumentCommand{\customlabel}{omm}{% - \begingroup - \cref@constructprefix{#1}{\cref@result}% - \protected@edef\@currentlabel{#3}% - \protected@edef\@currentlabelname{#3}% - \protected@edef\cref@currentlabel{[#1][][\cref@result]#3} - \label[#1]{#2}% - \endgroup + \begingroup + \cref@constructprefix{#1}{\cref@result}% + \protected@edef\@currentlabel{#3}% + \protected@edef\@currentlabelname{#3}% + \protected@edef\cref@currentlabel{[#1][][\cref@result]#3} + \label[#1]{#2}% + \endgroup } \makeatother @@ -215,6 +215,9 @@ selection={recorded and deps and see}, ] +% requirements +\externaldocument{reqs}[./requirements.pdf] + % default document header/trailer \makeatletter \def\projdoc@header{ @@ -293,8 +296,8 @@ \def\UrlRight{\hbox{\,}}% } \DefineVerbatimEnvironment{blockcode}{Verbatim}{ - tabsize=2, - obeytabs, + tabsize=2, + obeytabs, } % scale down image if it exceeds page margins @@ -321,3 +324,65 @@ \crefname{test}{test}{tests} \Crefname{test}{Test}{Tests} +% fix cleveref showing filename to external cross-reference +% see +% edited from cleveref source +\makeatletter +\def\cref@getref#1#2{% + \expandafter\let\expandafter#2\csname r@#1@cref\endcsname% + \expandafter\expandafter\expandafter\def% + \expandafter\expandafter\expandafter#2% + \expandafter\expandafter\expandafter{% + \expandafter\@firstoffive#2}}% +\def\cpageref@getref#1#2{% + \expandafter\let\expandafter#2\csname r@#1@cref\endcsname% + \expandafter\expandafter\expandafter\def% + \expandafter\expandafter\expandafter#2% + \expandafter\expandafter\expandafter{% + \expandafter\@secondoffive#2}}% +\AtBeginDocument{% + \def\label@noarg#1{% + \cref@old@label{#1}% + \@bsphack% + \edef\@tempa{{page}{\the\c@page}}% + \setcounter{page}{1}% + \edef\@tempb{\thepage}% + \expandafter\setcounter\@tempa% + \cref@constructprefix{page}{\cref@result}% + \protected@write\@auxout{}{% + \string\newlabel{#1@cref}{% + {\cref@currentlabel}% + {[\@tempb][\arabic{page}][\cref@result]\thepage}% + {}% + {}% + {}% + }% + }% + \@esphack% + }% + \def\label@optarg[#1]#2{% + \cref@old@label{#2}% + \@bsphack% + \edef\@tempa{{page}{\the\c@page}}% + \setcounter{page}{1}% + \edef\@tempb{\thepage}% + \expandafter\setcounter\@tempa% + \cref@constructprefix{page}{\cref@result}% + \protected@edef\cref@currentlabel{% + \expandafter\cref@override@label@type% + \cref@currentlabel\@nil{#1}% + }% + \protected@write\@auxout{}{% + \string\newlabel{#2@cref}{% + {\cref@currentlabel}% + {[\@tempb][\arabic{page}][\cref@result]\thepage}% + {}% + {}% + {}% + }% + }% + \@esphack% + }% +} +\makeatother + diff --git a/readme.md b/readme.md index 7b58cfd..818d445 100644 --- a/readme.md +++ b/readme.md @@ -18,12 +18,6 @@ A `latexmkrc` file is provided for copmilation with latexmk. The documents should also compile under [Visual Studio Code][vscode] using the [LaTeX Workshop extension][latexworkshop], as well as [VimTeX][vimtex]. -## TODO - -- Requirement cross-references are broken (they print both the label and the - path to the other document, should be label only). Interesting: - `\creflabelformat` and `\@templabel` (inside #2 of `\creflabelformat`). - [vscode]: https://code.visualstudio.com [latexworkshop]: https://marketplace.visualstudio.com/items?itemName=James-Yu.latex-workshop [vimtex]: https://github.com/lervag/vimtex diff --git a/scripts/reqs2tex.py b/scripts/reqs2tex.py index 700d05f..8a6976a 100755 --- a/scripts/reqs2tex.py +++ b/scripts/reqs2tex.py @@ -101,8 +101,20 @@ def fmt_aux(data): for item in data: ref = label2ref(item[KEY.LABEL]) out += [ - tex.cmd('newlabel', f"{ref}", tex.group(item[KEY.ID], item[KEY.ID], 'ggg', 'hhh', 'iii')), - tex.cmd('newlabel', f"{ref}@cref", tex.group(f"[requirement][aaa][bbb]{item[KEY.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]}", + '[][][]', + '', + '', + './requirements.pdf', + )), ] return "\n".join(out) -- cgit v1.2.3 From f2e1083970107994a031a394198fde039bdf3b77 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Wed, 18 Sep 2024 14:10:59 +0200 Subject: cleanup --- projdoc.cls | 2 +- scripts/reqs2tex.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/projdoc.cls b/projdoc.cls index 23f3ea9..7420d38 100644 --- a/projdoc.cls +++ b/projdoc.cls @@ -216,7 +216,7 @@ ] % requirements -\externaldocument{reqs}[./requirements.pdf] +\externaldocument{reqs}[requirements.pdf] % default document header/trailer \makeatletter diff --git a/scripts/reqs2tex.py b/scripts/reqs2tex.py index 8a6976a..82b0aae 100755 --- a/scripts/reqs2tex.py +++ b/scripts/reqs2tex.py @@ -113,7 +113,7 @@ def fmt_aux(data): '[][][]', '', '', - './requirements.pdf', + '', )), ] return "\n".join(out) -- cgit v1.2.3 From 0027f5df316892f121bb9f4b5b6b641646273ff0 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Wed, 18 Sep 2024 14:54:48 +0200 Subject: add requirements + improve generated reqs.tex --- projdoc.cls | 7 +++++-- reqs.toml | 39 ++++++++++++++++++++++++++++++--------- requirements.tex | 1 + scripts/reqs2tex.py | 15 ++++++++------- scripts/tex.py | 10 ++++++++++ 5 files changed, 54 insertions(+), 18 deletions(-) diff --git a/projdoc.cls b/projdoc.cls index 7420d38..fccf8c1 100644 --- a/projdoc.cls +++ b/projdoc.cls @@ -215,8 +215,11 @@ selection={recorded and deps and see}, ] -% requirements -\externaldocument{reqs}[requirements.pdf] +% allow cross-references to requirements.pdf from all documents except +% requirements.pdf itself +\IfEq*{\jobname}{requirements}{}{ + \externaldocument{reqs}[requirements.pdf] +} % default document header/trailer \makeatletter diff --git a/reqs.toml b/reqs.toml index c05cf71..6645ea4 100644 --- a/reqs.toml +++ b/reqs.toml @@ -10,33 +10,46 @@ # (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] +[audio] # Requirement type ('system' | 'user') -type = 'system' +type = 'user' # 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). +The engine allows the game programmer to easily start, pause and stop +background music, while simultaniously playing sound effects. ''' -# 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. +# Definition of done. If 'done' is a string, it is treated as LaTeX code, if it +# 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', +] #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.async-api] +type = 'system' +priority = 'must' +description = ''' +The public audio \gls{api} supports starting audio samples asynchronously +(i.e.~fire and forget). +''' + [audio.handle] type = 'system' priority = 'must' description = ''' The public audio \gls{api} allows the game programmer to control (i.e.~play, -pause and stop) audio samples after they are created/initialized. +pause, resume and stop) audio samples after they are created/initialized. ''' [audio.stream-mix] @@ -46,6 +59,14 @@ description = ''' The audio system supports playing multiple audio streams simultaniously. ''' +[audio.volume] +type = 'system' +priority = 'must' +description = ''' +The public audio \gls{api} allows the game programmer to control the volume of +audio samples. +''' + [aux.license] type = 'system' priority = 'must' diff --git a/requirements.tex b/requirements.tex index 2936272..cbaba81 100644 --- a/requirements.tex +++ b/requirements.tex @@ -6,6 +6,7 @@ \projdoc@description@labelindent=0pt \projdoc@setdescriptionstyle \makeatother +\setcounter{secnumdepth}{1} \title{Requirements} diff --git a/scripts/reqs2tex.py b/scripts/reqs2tex.py index 82b0aae..ff9f3bb 100755 --- a/scripts/reqs2tex.py +++ b/scripts/reqs2tex.py @@ -122,7 +122,7 @@ def fmt_tex(data): out = "" for item in data: out += tex.join( - tex.cmd('subsection', item[KEY.ID]), + 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]), @@ -130,12 +130,13 @@ def fmt_tex(data): tex.pedef('cref@currentlabel', tex.group(['requirement'], [''], [r'\cref@result']) + item[KEY.ID]), ), tex.cmd('label', ['requirement'], label2ref(item[KEY.LABEL])), - tex.cmd('par'), - tex.env('description', tex.join( - tex.cmd('item', ['Priority']) + item[KEY.PRIORITY].title(), - tex.cmd('item', ['Requirement']) + item[KEY.DESCRIPTION], - (tex.cmd('item', ['Definition of done']) + item[KEY.DONE] if item[KEY.DONE] is not None else ""), - )) + tex.cmd('parbox', tex.cmd('linewidth'), + tex.env('description', tex.join( + tex.cmd('item', ['Priority']) + item[KEY.PRIORITY].title(), + tex.cmd('item', ['Requirement']) + item[KEY.DESCRIPTION], + (tex.cmd('item', ['Definition of done']) + item[KEY.DONE] if item[KEY.DONE] is not None else ""), + )), + ) ) return out diff --git a/scripts/tex.py b/scripts/tex.py index e8fc65b..07d275a 100644 --- a/scripts/tex.py +++ b/scripts/tex.py @@ -69,3 +69,13 @@ def explist(*items): out += explist(*item) return out +def sec(level, heading): + level = max(min(3, level), 0) + section = [ + 'section', + 'subsection', + 'subsubsection', + 'paragraph', + ][level] + return cmd(section, heading) + -- cgit v1.2.3 From abab5051391b7f8a212539400433eb1db0bb4f06 Mon Sep 17 00:00:00 2001 From: Loek Le Blansch Date: Wed, 18 Sep 2024 15:06:45 +0200 Subject: abbreviate requirement field labels after first occurrence on every page --- glossary.bib | 13 +++++++++++++ requirements.tex | 21 +++++++++++++++++++++ scripts/reqs2tex.py | 11 ++++++++--- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/glossary.bib b/glossary.bib index 437db86..8bf48ac 100644 --- a/glossary.bib +++ b/glossary.bib @@ -33,3 +33,16 @@ long = {Application Programming Interface}, } +@acronym{reqlabel-priority, + short = {pri}, + long = {priority}, +} +@acronym{reqlabel-description, + short = {req}, + long = {requirement}, +} +@acronym{reqlabel-done, + short = {DoD}, + long = {definition of done}, +} + diff --git a/requirements.tex b/requirements.tex index cbaba81..78496e8 100644 --- a/requirements.tex +++ b/requirements.tex @@ -8,6 +8,27 @@ \makeatother \setcounter{secnumdepth}{1} +\usepackage{bophook} +\makeatletter +\def\reqlabel#1{\csuse{reqlabel@#1}} +\def\reqlabelreset{% + \global\def\reqlabel@priority{% + \Glsdesc{reqlabel-priority}% + \global\def\reqlabel@priority{\Glstext{reqlabel-priority}}% + }% + \global\def\reqlabel@description{% + \Glsdesc{reqlabel-description}% + \global\def\reqlabel@description{\Glstext{reqlabel-description}}% + }% + \global\def\reqlabel@done{% + \Glsdesc{reqlabel-done}% + \global\def\reqlabel@done{\Glstext{reqlabel-done}}% + }% +} +% Abbreviate requirement field labels after first occurrence on every page +\AtBeginPage{\reqlabelreset} +\makeatother + \title{Requirements} \begin{document} diff --git a/scripts/reqs2tex.py b/scripts/reqs2tex.py index ff9f3bb..e5f063d 100755 --- a/scripts/reqs2tex.py +++ b/scripts/reqs2tex.py @@ -132,9 +132,14 @@ def fmt_tex(data): tex.cmd('label', ['requirement'], label2ref(item[KEY.LABEL])), tex.cmd('parbox', tex.cmd('linewidth'), tex.env('description', tex.join( - tex.cmd('item', ['Priority']) + item[KEY.PRIORITY].title(), - tex.cmd('item', ['Requirement']) + item[KEY.DESCRIPTION], - (tex.cmd('item', ['Definition of done']) + item[KEY.DONE] if item[KEY.DONE] is not None else ""), + 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 []), )), ) ) -- cgit v1.2.3