aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--example.tex13
-rw-r--r--latexmkrc5
-rw-r--r--projdoc.cls8
-rwxr-xr-xreqs2tex.py27
-rw-r--r--scripts/.gitignore1
-rwxr-xr-xscripts/reqs2tex.py83
-rw-r--r--scripts/tex.py43
-rwxr-xr-xscripts/time2tex.py (renamed from time2tex.py)51
8 files changed, 179 insertions, 52 deletions
diff --git a/example.tex b/example.tex
index 4ae4d95..ee1377a 100644
--- a/example.tex
+++ b/example.tex
@@ -3,6 +3,8 @@
% with the [draft] option. this replaces all images with placeholders.
\input{meta.tex}
+\input{reqs.aux}
+
\title{Example Document}
\begin{document}
@@ -155,17 +157,24 @@ Description:
\con{Bad thing 2}
\end{comparison}
-\subsection{Citations}
+\subsection{References}
+
+\subsubsection{Citations}
Citations are inserted using the \codeinline{\autocite} command \autocite{rfc:3339}.
The bibliography is automatically printed after \codeinline{\end{document}}.
-\subsection{Glossary}
+\subsubsection{Glossary}
Glossary entries can be inserted using the \codeinline{\gls} commands. Example:
``\Gls{sdl2} handles \glspl{hid} as well!''. In following occurrences of acronyms,
only their short form is printed: `\gls{sdl2}' and `\gls{hid}'. All of these link to
the glossary that is automatically printed after \codeinline{\end{document}}.
+\subsubsection{Requirements}
+
+Requirements are referenced like \codeinline{\label}s:
+e.g.~\cref{req:audio:handle:id,req:audio:async-api:id}.
+
\end{document}
diff --git a/latexmkrc b/latexmkrc
index 2ce20fa..5a49064 100644
--- a/latexmkrc
+++ b/latexmkrc
@@ -26,11 +26,12 @@ sub plantuml {
add_cus_dep('txt', 'tex', 0, 'time2tex');
sub time2tex {
- return system "python3 time2tex.py '$_[0].txt'";
+ return system "python3 scripts/time2tex.py '$_[0].txt'";
}
add_cus_dep('toml', 'tex', 0, 'reqs2tex');
+add_cus_dep('toml', 'aux', 0, 'reqs2tex');
sub reqs2tex {
- return system "python3 reqs2tex.py '$_[0].toml'";
+ return system "python3 scripts/reqs2tex.py '$_[0].toml'";
}
diff --git a/projdoc.cls b/projdoc.cls
index 0d27a1f..c11fe61 100644
--- a/projdoc.cls
+++ b/projdoc.cls
@@ -307,3 +307,11 @@
\newcommand\noparbreak{\par\nobreak\@afterheading}
\makeatother
+% cleveref extra types
+\crefname{paragraph}{paragraph}{paragraphs}
+\Crefname{paragraph}{Paragraph}{Paragraphs}
+\crefname{requirement}{requirement}{requirements}
+\Crefname{requirement}{Requirement}{Requirements}
+\crefname{test}{test}{tests}
+\Crefname{test}{Test}{Tests}
+
diff --git a/reqs2tex.py b/reqs2tex.py
deleted file mode 100755
index 68c8f40..0000000
--- a/reqs2tex.py
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/python3
-import sys, tomllib
-
-def fmt(data):
- print(data)
- return f"""
-\\makeatletter
-\\makeatother
-"""
-
-def main(input_file):
- data = {}
- with open(input_file, "rb") as file:
- data = tomllib.load(file)
-
- output = fmt(data)
-
- output_file = input_file.removesuffix(".toml") + ".tex"
- with open(output_file, "w+") as file:
- file.write(output)
-
-if __name__ == "__main__":
- if len(sys.argv) != 2:
- print("usage: reqs2tex.py reqs.toml")
- exit(1)
- main(sys.argv[1])
-
diff --git a/scripts/.gitignore b/scripts/.gitignore
new file mode 100644
index 0000000..bee8a64
--- /dev/null
+++ b/scripts/.gitignore
@@ -0,0 +1 @@
+__pycache__
diff --git a/scripts/reqs2tex.py b/scripts/reqs2tex.py
new file mode 100755
index 0000000..9e71a48
--- /dev/null
+++ b/scripts/reqs2tex.py
@@ -0,0 +1,83 @@
+#!/bin/python3
+import sys, tomllib, tex
+
+def flatten(data):
+ if 'description' in data:
+ return [ data ]
+ out = []
+ for key, value in data.items():
+ items = flatten(value)
+ for item in items:
+ if 'label' in item:
+ item['label'] = f"{key}:{item['label']}"
+ else:
+ item['label'] = f"{key}"
+ out += items
+ return out
+
+id_counter = 0
+def make_id(item):
+ global id_counter
+ id_counter += 1
+ return "{type_short}#{counter:03d}".format(
+ type_short = item['type'][0].upper(),
+ counter = id_counter,
+ )
+
+def convert(data):
+ reqs = flatten(data)
+ for index, item in enumerate(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)
+
+ # skip deleted requirements (but process for make_id)
+ reqs = [item for item in reqs if item['deleted'] == False]
+
+ return reqs
+
+def req2aux(req):
+ # TODO: this is a dead-end solution, newlabel only works for in-document anchors, not external links
+ out = [
+ tex.scmd('newlabel', f"req:{req['label']}:id", tex.group(req['id'], req['id'], '', './requirements.pdf', '')),
+ tex.scmd('newlabel', f"req:{req['label']}:id@cref", tex.group(f"[requirement][][]{req['id']}", '')),
+ ]
+ return "\n".join([tex.auxout(line) for line in out])
+
+def fmt_aux(data):
+ out = ""
+ out += tex.cmd('makeatletter')
+ out += "\n".join([req2aux(req) for req in data])
+ out += tex.cmd('makeatother')
+ return out
+
+def fmt_tex(data):
+ return "\n".join([
+ tex.cmd('relax')
+ ])
+
+def main(input_file):
+ data = {}
+ with open(input_file, "rb") as file:
+ data = tomllib.load(file)
+
+ requirements = convert(data)
+
+ output_aux = input_file.removesuffix(".toml") + ".aux"
+ with open(output_aux, "w+") as file:
+ file.write(fmt_aux(requirements))
+
+ output_tex = input_file.removesuffix(".toml") + ".tex"
+ with open(output_tex, "w+") as file:
+ file.write(fmt_tex(requirements))
+
+if __name__ == "__main__":
+ if len(sys.argv) != 2:
+ print("usage: reqs2tex.py reqs.toml")
+ exit(1)
+ main(sys.argv[1])
+
diff --git a/scripts/tex.py b/scripts/tex.py
new file mode 100644
index 0000000..b044857
--- /dev/null
+++ b/scripts/tex.py
@@ -0,0 +1,43 @@
+# utility function for converting latex code
+
+def group(*args):
+ return "".join("{" + arg + "}" for arg in args)
+
+def string(content):
+ return r"\string" + content
+
+def cmd(*args):
+ name = args[0]
+ args = args[1:]
+ if len(args) == 0: args = [""]
+ return f"\\{name}" + group(*args)
+
+def csdef(*args):
+ return r"\def" + cmd(*args)
+
+def auxout(content):
+ return r"\write\@auxout" + group(content)
+
+def scmd(*args):
+ return string(cmd(*args))
+
+def env(name, *args):
+ content = args[-1]
+ args = args[0:-1]
+ out = f"\\begin{{{name}}}"
+ if len(args) > 0:
+ out += group(*args)
+ out += content
+ out += f"\\end{{{name}}}"
+ return out
+
+def esc(plain):
+ plain = plain.replace("\\", "\\string\\")
+ plain = plain.replace("#", "\\#")
+ plain = plain.replace("$", "\\$")
+ plain = plain.replace("%", "\\%")
+ return plain
+
+def tabrule(*cells):
+ return "&".join(cells) + "\\\\"
+
diff --git a/time2tex.py b/scripts/time2tex.py
index 6e3de9c..17661d5 100755
--- a/time2tex.py
+++ b/scripts/time2tex.py
@@ -1,5 +1,5 @@
#!/bin/python3
-import sys
+import sys, tex
from datetime import datetime, timedelta
def fmt_duration(sec):
@@ -19,11 +19,14 @@ def fmt_duration(sec):
return "\\,".join(out)
def fmt_percentage(fac):
- return f"{{\\footnotesize\\itshape({round(fac * 100)}\\%)}}"
+ return tex.sgroup(
+ tex.cmd('footnotesize') +\
+ tex.cmd('itshape') +\
+ tex.esc(f"({round(fac * 100)}%)")
+ )
def fmt_member_overview(times):
# calculations
- out = ""
tracked = {}
total_time = 0
for time in times:
@@ -32,24 +35,29 @@ def fmt_member_overview(times):
tracked[time["name"]] += time["duration"]
total_time += time["duration"]
- # begin table
- out += r"\begin{table}\centering"
- out += r"\begin{tabular}{lr@{~}l}\toprule"
- out += r"\textbf{Member} & \textbf{Tracked} &\\\midrule{}"
+ 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 += f"{name} & {fmt_duration(tracked[name])} & {fmt_percentage(tracked[name] / total_time)}\\\\"
- out += r"\midrule{}"
+ out += tex.tabrule(name, fmt_duration(tracked[name]), fmt_percentage(tracked[name] / total_time))
+ out += tex.cmd('midrule')
# sum
- out += f"&{fmt_duration(total_time)}&\\\\"
+ out += tex.tabrule('', fmt_duration(total_time), '')
+ out += tex.cmd('bottomrule')
- # end table
- out += r"\bottomrule\end{tabular}"
- out += r"\caption{Tracked time per group member}\label{tab:time-member}"
- out += r"\end{table}"
+ 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
@@ -85,6 +93,7 @@ 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{"
@@ -173,13 +182,13 @@ def parse(content):
return out
def fmt(times):
- return f"""
-\\section{{Overviews}}\n
-\\subsection{{Members}}\n
-{fmt_member_overview(times)}
-\\subsection{{Weekly}}\n
-{fmt_weekly_overview(times)}
-"""
+ return "\n\n".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 = ""