trainlog/tools/generate_exercise_name_catalog.py

63 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""Validate exercise-names-v1 and generate its bounded C lookup table."""
import json
import re
import sys
source, output = sys.argv[1:]
with open(source, encoding="utf-8") as handle:
root = json.load(handle)
if set(root) != {"format", "version", "names"} or \
root.get("format") != "trainlog-exercise-names-v1" or \
root.get("version") != 1 or isinstance(root.get("version"), bool):
raise SystemExit("unsupported exercise-name catalogue")
names = root.get("names")
if not isinstance(names, list) or not names:
raise SystemExit("names must be a non-empty list")
seen_ids = set()
for item in names:
if set(item) != {"exercise_id", "display_name", "normalized_name",
"previous_display_names"}:
raise SystemExit("invalid exercise-name entry shape")
exercise_id = item["exercise_id"]
display_name = item["display_name"]
if not isinstance(exercise_id, str) or re.fullmatch(
r"ex_[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}",
exercise_id,
) is None or exercise_id in seen_ids:
raise SystemExit("duplicate or invalid exercise_id")
if not isinstance(display_name, str) or not display_name.strip() or \
display_name != display_name.strip():
raise SystemExit("invalid exercise display_name")
seen_ids.add(exercise_id)
def c(value):
return json.dumps(value, ensure_ascii=False)
with open(output, "w", encoding="utf-8") as generated:
generated.write('#include "trainlog/exercise_name_catalog.h"\n')
generated.write('#include <stddef.h>\n#include <string.h>\n\n')
generated.write("typedef struct TrainlogExerciseNameEntry {\n"
" const char *exercise_id;\n"
" const char *display_name;\n"
"} TrainlogExerciseNameEntry;\n\n")
generated.write("static const TrainlogExerciseNameEntry entries[] = {\n")
for item in names:
generated.write(" {%s, %s},\n" % (
c(item["exercise_id"]), c(item["display_name"])))
generated.write("};\n\n")
generated.write("const char *trainlog_exercise_name_catalog_lookup(const char *exercise_id) {\n"
" size_t index;\n"
" if (exercise_id == NULL) return NULL;\n"
" for (index = 0U; index < sizeof(entries) / sizeof(entries[0]); ++index)\n"
" if (strcmp(entries[index].exercise_id, exercise_id) == 0)\n"
" return entries[index].display_name;\n"
" return NULL;\n"
"}\n")