aboutsummaryrefslogtreecommitdiff
path: root/scripts/reqs2tex.py
blob: 6b7b77a850983e02fc3932f5b7efaf2f8c90f35c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#!/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 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'])}")

  # 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)
  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
    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]

  return reqs

def fmt_aux(data):
  out = []
  for req in data:
    ref = tex.label2ref(req['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')),
    ]
  return "\n".join(out)

def fmt_tex(data):
  out = []
  for req in data:
    out.append(
      tex.cmd('subsection', req['id']) + "\n\n" +\
      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 "")
      )
    )
  return "\n\n".join(out)

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])