trainlog/tools/generate_equipment_catalog.py

72 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""Generate the desktop C catalogue from the one versioned JSON source."""
import json
import re
import sys
src, out = sys.argv[1:]
root = json.load(open(src, encoding="utf-8"))
if root.get("format") != "trainlog-equipment-catalog" or root.get("version") != 1:
raise SystemExit("unsupported equipment catalogue")
items = root.get("equipment")
if not isinstance(items, list):
raise SystemExit("equipment must be a list")
ids = set()
for item in items:
required = ("id", "label_name", "display_name", "aliases", "type", "load_semantics")
if not all(key in item for key in required) or not isinstance(item["aliases"], list):
raise SystemExit("invalid equipment entry")
if not item["id"] or item["id"] in ids:
raise SystemExit("empty or duplicate equipment id")
ids.add(item["id"])
if item["load_semantics"] not in {"external", "assistance", "bodyweight", "cardio"}:
raise SystemExit("invalid load semantics")
for relation in root.get("exercise_equipment", []):
if relation.get("equipment_id") not in ids:
raise SystemExit("relation references unknown equipment")
def c(value):
return json.dumps(value, ensure_ascii=False)
with open(out, "w", encoding="utf-8") as f:
f.write('#include "trainlog/equipment_catalog.h"\n#include <string.h>\n\n')
f.write('static const TrainlogEquipment entries[] = {\n')
for item in items:
f.write(' {%s, %s, %s, %s, %s, %s},\n' % (
c(item['id']), c(item['label_name']), c(item['display_name']),
c(item['type']), c(item['load_semantics']), c('\n'.join(item['aliases']))))
f.write('};\nstatic const TrainlogExerciseEquipmentRelation relations[] = {\n')
for rel in root.get('exercise_equipment', []):
f.write(' {%s, %s, %s},\n' % (c(rel['exercise_id']), c(rel['equipment_id']), c(rel['load_semantics'])))
f.write('};\n')
f.write(r'''
static int folded_contains(const char *haystack, const char *needle) {
size_t i, j;
if (needle[0] == '\0') return 1;
for (i = 0; haystack[i] != '\0'; ++i) {
for (j = 0; needle[j] != '\0'; ++j) {
unsigned char a = (unsigned char)haystack[i + j];
unsigned char b = (unsigned char)needle[j];
if (a >= 'A' && a <= 'Z') a = (unsigned char)(a + ('a' - 'A'));
if (b >= 'A' && b <= 'Z') b = (unsigned char)(b + ('a' - 'A'));
if (a == '\0' || a != b) break;
}
if (needle[j] == '\0') return 1;
}
return 0;
}
size_t trainlog_equipment_catalog_count(void) { return sizeof(entries) / sizeof(entries[0]); }
const TrainlogEquipment *trainlog_equipment_catalog_at(size_t i) { return i < trainlog_equipment_catalog_count() ? &entries[i] : NULL; }
const TrainlogEquipment *trainlog_equipment_catalog_lookup(const char *id) {
size_t i; if (id == NULL) return NULL;
for (i = 0; i < trainlog_equipment_catalog_count(); ++i) if (strcmp(entries[i].equipment_id, id) == 0) return &entries[i];
return NULL;
}
size_t trainlog_equipment_catalog_search(const char *q, const TrainlogEquipment **out, size_t cap) {
size_t i, found = 0; if (q == NULL || out == NULL) return 0;
for (i = 0; i < trainlog_equipment_catalog_count(); ++i) if (folded_contains(entries[i].label_name, q) || folded_contains(entries[i].display_name, q) || folded_contains(entries[i].aliases, q)) { if (found < cap) out[found] = &entries[i]; ++found; }
return found;
}
size_t trainlog_equipment_catalog_relation_count(void) { return sizeof(relations) / sizeof(relations[0]); }
const TrainlogExerciseEquipmentRelation *trainlog_equipment_catalog_relation_at(size_t i) { return i < trainlog_equipment_catalog_relation_count() ? &relations[i] : NULL; }
''')