mirror of
https://github.com/pepaskopec-blip/Graduately.git
synced 2026-10-27 15:14:35 +00:00
1294 lines
47 KiB
Python
1294 lines
47 KiB
Python
#!/usr/bin/env python3
|
||
"""Pull educational content out of the GTK C sources into content.json."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import re
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[1]
|
||
SRC = ROOT / "src"
|
||
OUTS = [
|
||
ROOT / "android" / "app" / "src" / "main" / "assets" / "content.json",
|
||
ROOT / "ios" / "Maturita" / "content.json",
|
||
]
|
||
OUT = OUTS[0]
|
||
|
||
IDENT = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
|
||
|
||
|
||
def strip_comments(src: str) -> str:
|
||
out = []
|
||
i, n = 0, len(src)
|
||
while i < n:
|
||
c = src[i]
|
||
if c == '"' or c == "'":
|
||
q = c
|
||
out.append(c)
|
||
i += 1
|
||
while i < n:
|
||
ch = src[i]
|
||
out.append(ch)
|
||
if ch == "\\" and i + 1 < n:
|
||
out.append(src[i + 1])
|
||
i += 2
|
||
continue
|
||
i += 1
|
||
if ch == q:
|
||
break
|
||
continue
|
||
if c == "/" and i + 1 < n and src[i + 1] == "/":
|
||
i += 2
|
||
while i < n and src[i] != "\n":
|
||
i += 1
|
||
continue
|
||
if c == "/" and i + 1 < n and src[i + 1] == "*":
|
||
i += 2
|
||
while i + 1 < n and not (src[i] == "*" and src[i + 1] == "/"):
|
||
i += 1
|
||
i = min(n, i + 2)
|
||
out.append(" ")
|
||
continue
|
||
out.append(c)
|
||
i += 1
|
||
return "".join(out)
|
||
|
||
|
||
def unescape(s: str) -> str:
|
||
out = []
|
||
i = 0
|
||
while i < len(s):
|
||
if s[i] == "\\" and i + 1 < len(s):
|
||
n = s[i + 1]
|
||
mapping = {
|
||
"n": "\n",
|
||
"t": "\t",
|
||
"r": "\r",
|
||
"\\": "\\",
|
||
'"': '"',
|
||
"'": "'",
|
||
}
|
||
if n in mapping:
|
||
out.append(mapping[n])
|
||
i += 2
|
||
continue
|
||
if n == "x":
|
||
j = i + 2
|
||
hexdigits = ""
|
||
while j < len(s) and s[j] in "0123456789abcdefABCDEF" and len(hexdigits) < 2:
|
||
hexdigits += s[j]
|
||
j += 1
|
||
out.append(chr(int(hexdigits, 16)) if hexdigits else "x")
|
||
i = j
|
||
continue
|
||
out.append(s[i])
|
||
i += 1
|
||
return "".join(out)
|
||
|
||
|
||
class Tok:
|
||
def __init__(self, kind: str, value, pos: int):
|
||
self.kind = kind
|
||
self.value = value
|
||
self.pos = pos
|
||
|
||
|
||
def tokenize(src: str) -> list[Tok]:
|
||
toks: list[Tok] = []
|
||
i, n = 0, len(src)
|
||
while i < n:
|
||
c = src[i]
|
||
if c.isspace():
|
||
i += 1
|
||
continue
|
||
if c == '"':
|
||
i += 1
|
||
raw = []
|
||
while i < n:
|
||
ch = src[i]
|
||
if ch == "\\" and i + 1 < n:
|
||
raw.append(ch)
|
||
raw.append(src[i + 1])
|
||
i += 2
|
||
continue
|
||
if ch == '"':
|
||
i += 1
|
||
break
|
||
raw.append(ch)
|
||
i += 1
|
||
while i < n and src[i].isspace():
|
||
i += 1
|
||
while i < n and src[i] == '"':
|
||
i += 1
|
||
while i < n:
|
||
ch = src[i]
|
||
if ch == "\\" and i + 1 < n:
|
||
raw.append(ch)
|
||
raw.append(src[i + 1])
|
||
i += 2
|
||
continue
|
||
if ch == '"':
|
||
i += 1
|
||
break
|
||
raw.append(ch)
|
||
i += 1
|
||
while i < n and src[i].isspace():
|
||
i += 1
|
||
toks.append(Tok("str", unescape("".join(raw)), i))
|
||
continue
|
||
if c in "{}[],;=":
|
||
toks.append(Tok(c, c, i))
|
||
i += 1
|
||
continue
|
||
if c == "-" and i + 1 < n and src[i + 1].isdigit():
|
||
j = i + 1
|
||
while j < n and (src[j].isdigit() or src[j] == "."):
|
||
j += 1
|
||
toks.append(Tok("num", int(float(src[i:j])), i))
|
||
i = j
|
||
continue
|
||
if c.isdigit():
|
||
j = i
|
||
while j < n and (src[j].isdigit() or src[j] == "."):
|
||
j += 1
|
||
text = src[i:j]
|
||
toks.append(Tok("num", int(float(text)) if "." not in text else int(float(text)), i))
|
||
i = j
|
||
continue
|
||
if c == "'" and i + 2 < n:
|
||
j = i + 1
|
||
raw = []
|
||
while j < n and src[j] != "'":
|
||
if src[j] == "\\" and j + 1 < n:
|
||
raw.append(src[j + 1])
|
||
j += 2
|
||
continue
|
||
raw.append(src[j])
|
||
j += 1
|
||
j += 1
|
||
toks.append(Tok("num", ord(raw[0]) if raw else 0, i))
|
||
i = j
|
||
continue
|
||
m = IDENT.match(src, i)
|
||
if m:
|
||
name = m.group(0)
|
||
i = m.end()
|
||
if name == "NULL":
|
||
toks.append(Tok("null", None, i))
|
||
elif name in ("TRUE", "FALSE"):
|
||
toks.append(Tok("num", 1 if name == "TRUE" else 0, i))
|
||
else:
|
||
toks.append(Tok("id", name, i))
|
||
continue
|
||
i += 1
|
||
return toks
|
||
|
||
|
||
def parse_value(toks: list[Tok], i: int):
|
||
if i >= len(toks):
|
||
return None, i
|
||
t = toks[i]
|
||
if t.kind == "str":
|
||
return t.value, i + 1
|
||
if t.kind == "num":
|
||
return t.value, i + 1
|
||
if t.kind == "null":
|
||
return None, i + 1
|
||
if t.kind == "id":
|
||
name = t.value
|
||
i += 1
|
||
if i < len(toks) and toks[i].kind == "(":
|
||
depth = 1
|
||
i += 1
|
||
while i < len(toks) and depth:
|
||
if toks[i].kind == "(":
|
||
depth += 1
|
||
elif toks[i].kind == ")":
|
||
depth -= 1
|
||
i += 1
|
||
return None, i
|
||
return {"$ref": name}, i
|
||
if t.kind == "{":
|
||
return parse_list(toks, i)
|
||
return None, i + 1
|
||
|
||
|
||
def parse_list(toks: list[Tok], i: int):
|
||
assert toks[i].kind == "{"
|
||
i += 1
|
||
items = []
|
||
while i < len(toks) and toks[i].kind != "}":
|
||
if toks[i].kind == ",":
|
||
i += 1
|
||
continue
|
||
val, i = parse_value(toks, i)
|
||
items.append(val)
|
||
if i < len(toks) and toks[i].kind == ",":
|
||
i += 1
|
||
if i < len(toks) and toks[i].kind == "}":
|
||
i += 1
|
||
return items, i
|
||
|
||
|
||
ARRAY_RE = re.compile(
|
||
r"(?:static\s+)?const\s+"
|
||
r"(?:struct\s+)?(?P<type>[A-Za-z_][A-Za-z0-9_]*)"
|
||
r"(?:\s+\*|\s+const\s+\*|\s*)\s*"
|
||
r"(?P<name>[A-Za-z_][A-Za-z0-9_]*)"
|
||
r"(?:\s*\[[^\]]*\])+\s*=\s*\{",
|
||
re.M,
|
||
)
|
||
|
||
CHARPTR_RE = re.compile(
|
||
r"(?:static\s+)?const\s+char\s*\*\s*(?:const\s+)?"
|
||
r"(?P<name>[A-Za-z_][A-Za-z0-9_]*)"
|
||
r"(?:\s*\[[^\]]*\])+\s*=\s*\{",
|
||
re.M,
|
||
)
|
||
|
||
STRING_ASSIGN_RE = re.compile(
|
||
r"(?:static\s+)?const\s+char\s*\*\s*"
|
||
r"(?P<name>[A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?P<body>\"(?:\\.|[^\"\\])*\"(?:\s*\"(?:\\.|[^\"\\])*\")*)\s*;",
|
||
re.S,
|
||
)
|
||
|
||
FUNC_RE = re.compile(
|
||
r"^(?:static\s+)?(?:GtkWidget\s*\*|void|gboolean|int|char\s*\*)\s*"
|
||
r"([A-Za-z_][A-Za-z0-9_]*)\s*\(",
|
||
re.M,
|
||
)
|
||
|
||
|
||
def find_arrays(src: str) -> dict[str, tuple[str, list]]:
|
||
found: dict[str, tuple[str, list]] = {}
|
||
funcs = [(m.start(), m.group(1)) for m in FUNC_RE.finditer(src)]
|
||
|
||
def func_at(pos: int) -> str | None:
|
||
current = None
|
||
for start, name in funcs:
|
||
if start < pos:
|
||
current = name
|
||
else:
|
||
break
|
||
if current is None:
|
||
return None
|
||
# Only treat as local if the array sits after the function's opening brace
|
||
# and before the next top-level function. Good enough for this codebase.
|
||
return current
|
||
|
||
def add(typ: str, name: str, pos_brace: int):
|
||
toks = tokenize(src[pos_brace:])
|
||
if not toks or toks[0].kind != "{":
|
||
return
|
||
val, _ = parse_list(toks, 0)
|
||
fn = func_at(pos_brace)
|
||
key = f"{fn}.{name}" if fn and name in {
|
||
"slides", "qs", "about", "world", "people", "terms", "persons",
|
||
"nouns", "ans",
|
||
} else name
|
||
# Prefer the first global, overwrite locals with prefixed names.
|
||
if key in found and not key.startswith(fn or "\0"):
|
||
return
|
||
found[key] = (typ, val)
|
||
|
||
for m in ARRAY_RE.finditer(src):
|
||
add(m.group("type"), m.group("name"), m.end() - 1)
|
||
for m in CHARPTR_RE.finditer(src):
|
||
add("char*", m.group("name"), m.end() - 1)
|
||
for m in STRING_ASSIGN_RE.finditer(src):
|
||
toks = tokenize(m.group("body"))
|
||
text = "".join(t.value for t in toks if t.kind == "str")
|
||
found[m.group("name")] = ("char", [text])
|
||
return found
|
||
|
||
|
||
def as_list(v):
|
||
return v if isinstance(v, list) else []
|
||
|
||
|
||
def as_str(v) -> str | None:
|
||
if v is None:
|
||
return None
|
||
if isinstance(v, str):
|
||
return v
|
||
return None
|
||
|
||
|
||
def resolve(arrays: dict, val, depth=0):
|
||
if depth > 8:
|
||
return val
|
||
if isinstance(val, dict) and "$ref" in val:
|
||
name = val["$ref"]
|
||
if name in arrays:
|
||
return resolve(arrays, arrays[name][1], depth + 1)
|
||
return None
|
||
if isinstance(val, list):
|
||
return [resolve(arrays, x, depth + 1) for x in val]
|
||
return val
|
||
|
||
|
||
def choice_items(rows) -> list[dict]:
|
||
out = []
|
||
for row in as_list(rows):
|
||
row = as_list(row)
|
||
if len(row) < 4:
|
||
continue
|
||
opts = [o for o in as_list(row[1]) if isinstance(o, str)]
|
||
correct = row[3] if isinstance(row[3], int) else 0
|
||
out.append({
|
||
"prompt": as_str(row[0]) or "",
|
||
"options": opts,
|
||
"correct": correct,
|
||
})
|
||
return out
|
||
|
||
|
||
def typed_items(rows) -> list[dict]:
|
||
out = []
|
||
for row in as_list(rows):
|
||
row = as_list(row)
|
||
if len(row) < 2:
|
||
continue
|
||
out.append({
|
||
"prompt": as_str(row[0]) or "",
|
||
"answers": as_str(row[1]) or "",
|
||
"meaning": as_str(row[2]) if len(row) > 2 else None,
|
||
})
|
||
return out
|
||
|
||
|
||
def assembly_items(rows) -> list[dict]:
|
||
out = []
|
||
for row in as_list(rows):
|
||
row = as_list(row)
|
||
if len(row) < 2:
|
||
continue
|
||
words = [w for w in as_list(row[1]) if isinstance(w, str)]
|
||
n = row[2] if len(row) > 2 and isinstance(row[2], int) else len(words)
|
||
out.append({
|
||
"prompt": as_str(row[0]),
|
||
"words": words[:n],
|
||
})
|
||
return out
|
||
|
||
|
||
def str_list(rows) -> list[str]:
|
||
return [x for x in as_list(rows) if isinstance(x, str)]
|
||
|
||
|
||
def verb_items(rows) -> list[dict]:
|
||
out = []
|
||
for row in as_list(rows):
|
||
row = as_list(row)
|
||
if len(row) < 3:
|
||
continue
|
||
out.append({
|
||
"before": as_str(row[0]) or "",
|
||
"after": as_str(row[1]) or "",
|
||
"answer": as_str(row[2]) or "",
|
||
})
|
||
return out
|
||
|
||
|
||
def verb_clue_items(rows) -> list[dict]:
|
||
out = []
|
||
for row in as_list(rows):
|
||
row = as_list(row)
|
||
if len(row) < 4:
|
||
continue
|
||
out.append({
|
||
"emoji": as_str(row[0]) or "",
|
||
"before": as_str(row[1]) or "",
|
||
"after": as_str(row[2]) or "",
|
||
"answer": as_str(row[3]) or "",
|
||
})
|
||
return out
|
||
|
||
|
||
def assign_items(rows) -> list[dict]:
|
||
out = []
|
||
for row in as_list(rows):
|
||
row = as_list(row)
|
||
if len(row) < 3:
|
||
continue
|
||
out.append({
|
||
"emoji": as_str(row[0]),
|
||
"label": as_str(row[1]) or "",
|
||
"group": row[2] if isinstance(row[2], int) else 0,
|
||
})
|
||
return out
|
||
|
||
|
||
def free_items(rows) -> list[dict]:
|
||
out = []
|
||
for row in as_list(rows):
|
||
row = as_list(row)
|
||
if len(row) < 4:
|
||
continue
|
||
out.append({
|
||
"question": as_str(row[0]) or "",
|
||
"qCs": as_str(row[1]) or "",
|
||
"sample": as_str(row[2]) or "",
|
||
"aCs": as_str(row[3]) or "",
|
||
})
|
||
return out
|
||
|
||
|
||
def profile_items(rows) -> list[dict]:
|
||
out = []
|
||
for row in as_list(rows):
|
||
row = as_list(row)
|
||
if len(row) < 2:
|
||
continue
|
||
out.append({
|
||
"stem": as_str(row[0]) or "",
|
||
"sample": as_str(row[1]) or "",
|
||
})
|
||
return out
|
||
|
||
|
||
def slides_items(rows) -> list[dict]:
|
||
out = []
|
||
for row in as_list(rows):
|
||
row = as_list(row)
|
||
if len(row) < 4:
|
||
continue
|
||
lines = [x for x in as_list(row[3]) if isinstance(x, str)]
|
||
out.append({
|
||
"kicker": as_str(row[0]) or "",
|
||
"title": as_str(row[1]) or "",
|
||
"tip": as_str(row[2]),
|
||
"lines": lines,
|
||
})
|
||
return out
|
||
|
||
|
||
def fill_items(rows) -> list[dict]:
|
||
out = []
|
||
for row in as_list(rows):
|
||
row = as_list(row)
|
||
if len(row) < 9:
|
||
continue
|
||
segs = [as_str(row[1]), as_str(row[3]), as_str(row[5]), as_str(row[7])]
|
||
ans = [as_str(row[2]), as_str(row[4]), as_str(row[6])]
|
||
out.append({
|
||
"num": as_str(row[0]),
|
||
"segs": [s or "" for s in segs],
|
||
"answers": [a for a in ans if a],
|
||
"mean": as_str(row[8]),
|
||
})
|
||
return out
|
||
|
||
|
||
def let_items(rows) -> list[dict]:
|
||
out = []
|
||
for row in as_list(rows):
|
||
row = as_list(row)
|
||
if len(row) < 11:
|
||
continue
|
||
segs = [as_str(row[i]) or "" for i in (1, 3, 5, 7, 9)]
|
||
ans = [as_str(row[i]) for i in (2, 4, 6, 8)]
|
||
out.append({
|
||
"num": as_str(row[0]),
|
||
"segs": segs,
|
||
"answers": [a for a in ans if a],
|
||
"mean": as_str(row[10]),
|
||
})
|
||
return out
|
||
|
||
|
||
def ex4row_items(rows) -> list[dict]:
|
||
out = []
|
||
for row in as_list(rows):
|
||
row = as_list(row)
|
||
if len(row) < 6:
|
||
continue
|
||
segs = [s for s in as_list(row[0]) if s is None or isinstance(s, str)]
|
||
ans = [a for a in as_list(row[1]) if isinstance(a, str)]
|
||
words = [w for w in as_list(row[3]) if isinstance(w, str)] if len(row) > 3 else []
|
||
gmean = [g for g in as_list(row[4]) if isinstance(g, str)] if len(row) > 4 else []
|
||
gaps = row[5] if len(row) > 5 and isinstance(row[5], int) else len(ans)
|
||
out.append({
|
||
"segs": [s or "" for s in segs],
|
||
"answers": ans[:gaps],
|
||
"czech": as_str(row[2]) if len(row) > 2 else None,
|
||
"words": words[:gaps],
|
||
"gmean": gmean[:gaps],
|
||
})
|
||
return out
|
||
|
||
|
||
def kw_items(rows) -> list[dict]:
|
||
out = []
|
||
for row in as_list(rows):
|
||
row = as_list(row)
|
||
if len(row) < 2:
|
||
continue
|
||
out.append({
|
||
"prompt": as_str(row[0]) or "",
|
||
"answers": as_str(row[1]) or "",
|
||
"mean": as_str(row[2]) if len(row) > 2 else None,
|
||
"german": as_str(row[3]) if len(row) > 3 else None,
|
||
})
|
||
return out
|
||
|
||
|
||
def mluv_items(rows) -> list[dict]:
|
||
out = []
|
||
for row in as_list(rows):
|
||
row = as_list(row)
|
||
if len(row) < 5:
|
||
continue
|
||
nblank = row[2] if isinstance(row[2], int) else 1
|
||
out.append({
|
||
"prompt": as_str(row[0]) or "",
|
||
"shown": as_str(row[1]) or "",
|
||
"hints": [h for h in as_list(row[3]) if isinstance(h, str)][:nblank],
|
||
"answers": [a for a in as_list(row[4]) if isinstance(a, str)][:nblank],
|
||
})
|
||
return out
|
||
|
||
|
||
def reveal_items(rows) -> list[dict]:
|
||
out = []
|
||
for row in as_list(rows):
|
||
row = as_list(row)
|
||
if len(row) < 2:
|
||
continue
|
||
out.append({
|
||
"prompt": as_str(row[0]) or "",
|
||
"solution": as_str(row[1]) or "",
|
||
})
|
||
return out
|
||
|
||
|
||
def lit_items(rows) -> list[dict]:
|
||
out = []
|
||
for row in as_list(rows):
|
||
row = as_list(row)
|
||
if len(row) < 4:
|
||
continue
|
||
opts = [o for o in as_list(row[1]) if isinstance(o, str)]
|
||
out.append({
|
||
"prompt": as_str(row[0]) or "",
|
||
"options": opts,
|
||
"correct": row[2] if isinstance(row[2], int) else 0,
|
||
"expl": as_str(row[3]),
|
||
})
|
||
return out
|
||
|
||
|
||
def trans_sections(rows, arrays) -> list[dict]:
|
||
out = []
|
||
for row in as_list(rows):
|
||
row = as_list(row)
|
||
if len(row) < 2:
|
||
continue
|
||
data = resolve(arrays, row[1])
|
||
out.append({
|
||
"header": as_str(row[0]) or "",
|
||
"rows": typed_items(data),
|
||
})
|
||
return out
|
||
|
||
|
||
def dialogs(arrays) -> list[dict]:
|
||
out = []
|
||
for key in ("ex1_d1", "ex1_d2", "ex1_d3"):
|
||
if key not in arrays:
|
||
continue
|
||
rows = []
|
||
for row in as_list(arrays[key][1]):
|
||
row = as_list(row)
|
||
if len(row) < 4:
|
||
continue
|
||
rows.append({
|
||
"speaker": as_str(row[0]) or "",
|
||
"before": as_str(row[1]) or "",
|
||
"answer": as_str(row[2]) or "",
|
||
"after": as_str(row[3]) or "",
|
||
})
|
||
titles = {"ex1_d1": "Sich vorstellen", "ex1_d2": "Begrüßung", "ex1_d3": "Verabschiedung"}
|
||
out.append({"name": titles[key], "rows": rows})
|
||
return out
|
||
|
||
|
||
def ordne_items(rows) -> list[dict]:
|
||
out = []
|
||
for row in as_list(rows):
|
||
row = as_list(row)
|
||
if len(row) < 3:
|
||
continue
|
||
out.append({
|
||
"mid": as_str(row[0]) or "",
|
||
"fw": as_str(row[1]) or "",
|
||
"ans": as_str(row[2]) or "",
|
||
})
|
||
return out
|
||
|
||
|
||
def ex2_items(arrays) -> list[dict]:
|
||
out = []
|
||
if "ex2_u3_items" not in arrays:
|
||
return out
|
||
for row in as_list(arrays["ex2_u3_items"][1]):
|
||
row = as_list(row)
|
||
if len(row) < 2:
|
||
continue
|
||
raw_rows = resolve(arrays, row[1])
|
||
parsed = []
|
||
for r in as_list(raw_rows):
|
||
r = as_list(r)
|
||
if len(r) < 4:
|
||
continue
|
||
segs = [s or "" for s in as_list(r[1]) if s is None or isinstance(s, str)]
|
||
ans = [a for a in as_list(r[2]) if isinstance(a, str)]
|
||
gaps = r[3] if isinstance(r[3], int) else len(ans)
|
||
parsed.append({
|
||
"num": as_str(r[0]),
|
||
"segs": segs,
|
||
"answers": ans[:gaps],
|
||
})
|
||
out.append({"czech": as_str(row[0]) or "", "rows": parsed})
|
||
return out
|
||
|
||
|
||
def net_ans(rows) -> list[list[dict]]:
|
||
out = []
|
||
for block in as_list(rows):
|
||
block = as_list(block)
|
||
inner = []
|
||
for row in block:
|
||
row = as_list(row)
|
||
if len(row) < 5:
|
||
continue
|
||
inner.append({
|
||
"pfx": row[0] if isinstance(row[0], int) else -1,
|
||
"net": as_str(row[1]),
|
||
"bcast": as_str(row[2]),
|
||
"lo": as_str(row[3]),
|
||
"hi": as_str(row[4]),
|
||
})
|
||
out.append(inner)
|
||
return out
|
||
|
||
|
||
def net_tasks(rows) -> list[dict]:
|
||
out = []
|
||
for row in as_list(rows):
|
||
row = as_list(row)
|
||
if len(row) < 2:
|
||
continue
|
||
out.append({"prompt": as_str(row[0]) or "", "solution": as_str(row[1]) or ""})
|
||
return out
|
||
|
||
|
||
def parse_i18n(src: str) -> dict:
|
||
arrays = find_arrays(src)
|
||
out = {}
|
||
for name in ("tr_ui", "tr_content"):
|
||
if name not in arrays:
|
||
continue
|
||
for row in as_list(arrays[name][1]):
|
||
row = as_list(row)
|
||
if not row or not as_str(row[0]):
|
||
continue
|
||
key = as_str(row[0])
|
||
cs = as_str(row[1]) if len(row) > 1 else None
|
||
en = as_str(row[2]) if len(row) > 2 else None
|
||
if key:
|
||
out[key] = {"cs": cs, "en": en}
|
||
return out
|
||
|
||
|
||
def combo(title, sub, pool, rows, meanings=None, kind="combo", **extra):
|
||
spec = {
|
||
"type": kind,
|
||
"title": title,
|
||
"sub": sub,
|
||
"pool": pool or [],
|
||
"rows": rows,
|
||
"meanings": meanings or [],
|
||
}
|
||
spec.update(extra)
|
||
return spec
|
||
|
||
|
||
def main() -> int:
|
||
arrays: dict[str, tuple[str, list]] = {}
|
||
i18n = {}
|
||
for path in sorted(SRC.glob("*.c")):
|
||
text = strip_comments(path.read_text(encoding="utf-8"))
|
||
arrays.update(find_arrays(text))
|
||
if path.name == "i18n.c":
|
||
i18n = parse_i18n(text)
|
||
|
||
def A(name):
|
||
if name not in arrays:
|
||
return []
|
||
return resolve(arrays, arrays[name][1])
|
||
|
||
german_units = [
|
||
{
|
||
"id": 0,
|
||
"title": "Neue Freunde",
|
||
"page": "unit1",
|
||
"tag": "u1",
|
||
"sub": "unit1_sub",
|
||
"unlocked": True,
|
||
"names": [
|
||
"Dialog", "Sätze bilden", "Was ist richtig?", "Freie Antwort",
|
||
"Zahlen", "Wie viel?", "Zahlenreihe", "Verb einsetzen",
|
||
"Wer? Wie? Wo?", "Wörter trennen", "Grußformen",
|
||
"Was macht er/sie?", "Länder",
|
||
],
|
||
"branch": "u1vocab",
|
||
"exercises": {
|
||
"1": combo("Dialog", "sub_dialog", str_list(A("ex1_pool")),
|
||
[], str_list(A("ex1_meaning")), "dialog",
|
||
dialogues=dialogs(arrays)),
|
||
"2": {
|
||
"type": "assembly", "title": "Sätze bilden", "sub": "sub_assembly",
|
||
"items": assembly_items(A("ex2_items")),
|
||
"meanings": str_list(A("ex2_meaning")),
|
||
},
|
||
"3": {
|
||
"type": "choice", "title": "Was ist richtig?", "sub": "sub_choice_num",
|
||
"questions": choice_items(A("ex3_questions")),
|
||
"meanings": str_list(A("ex3_meaning")),
|
||
},
|
||
"4": {
|
||
"type": "free", "title": "Freie Antwort", "sub": "sub_free",
|
||
"tip": "tip_ss", "questions": free_items(A("ex4_questions")),
|
||
},
|
||
"5": combo("Zahlen", "sub_zahlen", str_list(A("ex5_pool")),
|
||
[{"digits": as_list(r)[0], "answer": as_list(r)[1]}
|
||
for r in as_list(A("ex5_data")) if len(as_list(r)) >= 2],
|
||
str_list(A("ex5_meaning")), "number"),
|
||
"6": combo("Wie viel?", "sub_wieviel", str_list(A("ex6_pool")),
|
||
[{"emoji": as_list(r)[0], "count": as_list(r)[1],
|
||
"noun": as_list(r)[2], "answer": as_list(r)[3]}
|
||
for r in as_list(A("ex6_data")) if len(as_list(r)) >= 4],
|
||
str_list(A("ex6_meaning")), "count"),
|
||
"7": combo("Zahlenreihe", "sub_reihe", str_list(A("ex7_pool")),
|
||
[{"before": as_list(r)[0], "after": as_list(r)[1],
|
||
"answer": as_list(r)[2]}
|
||
for r in as_list(A("ex7_data")) if len(as_list(r)) >= 3],
|
||
str_list(A("ex7_meaning")), "seq"),
|
||
"8": combo("Verb einsetzen", "sub_verb", str_list(A("ex8_pool")),
|
||
verb_items(A("ex8_data")), str_list(A("ex8_meaning")),
|
||
"verb"),
|
||
"9": {
|
||
"type": "choice", "title": "Wer? Wie? Wo?", "sub": "sub_wer",
|
||
"questions": choice_items(A("ex9_questions")),
|
||
"meanings": str_list(A("ex9_meaning")),
|
||
},
|
||
"10": {
|
||
"type": "assembly", "title": "Wörter trennen", "sub": "sub_assembly",
|
||
"items": assembly_items(A("ex10_items")),
|
||
"meanings": str_list(A("ex10_meaning")),
|
||
},
|
||
"11": {
|
||
"type": "assign", "title": "Grußformen", "sub": "sub_gruss",
|
||
"items": assign_items(A("ex11_items")),
|
||
"groups": str_list(A("ex11_groups")),
|
||
"meanings": str_list(A("ex11_meaning")),
|
||
},
|
||
"12": combo("Was macht er/sie?", "sub_bild", str_list(A("ex12_pool")),
|
||
verb_clue_items(A("ex12_data")),
|
||
str_list(A("ex12_meaning")), "verbclue"),
|
||
"13": {
|
||
"type": "assign", "title": "Länder", "sub": "sub_land",
|
||
"items": assign_items(A("ex13_items")),
|
||
"groups": str_list(A("ex13_groups")),
|
||
"meanings": str_list(A("ex13_meaning")),
|
||
},
|
||
},
|
||
"vocab": {
|
||
"type": "vocab", "title": "Vokabeltraining", "sub": "sub_translate",
|
||
"sections": trans_sections(A("u1_trans_sections"), arrays),
|
||
},
|
||
},
|
||
{
|
||
"id": 1,
|
||
"title": "Aus aller Welt",
|
||
"page": "unit2",
|
||
"tag": "u2",
|
||
"sub": "unit2_sub",
|
||
"unlocked": True,
|
||
"names": [
|
||
"Verben konjugieren", "aus oder in", "Fragewörter", "sprechen",
|
||
"Nationalitäten", "Woher?", "Länder schreiben", "Verben einsetzen",
|
||
"Freie Antwort", "Euro", "Wörter suchen", "Was ist richtig?",
|
||
"Ordne zu", "Lückentext", "Verbinde", "Zahlen",
|
||
"Steckbrief", "Berufe", "Nationalität",
|
||
],
|
||
"branch": "u2vocab",
|
||
"exercises": {
|
||
"1": combo("Verben konjugieren", "sub_verben", str_list(A("g01_pool")),
|
||
[], [], "verb_sections",
|
||
sections=[
|
||
{"title": "Peter Fritsch:",
|
||
"rows": verb_items(A("g01_peter")),
|
||
"meanings": str_list(A("g01_peter_mean"))},
|
||
{"title": "Jana Nová und Pavol Korčák:",
|
||
"rows": verb_items(A("g01_jana")),
|
||
"meanings": str_list(A("g01_jana_mean"))},
|
||
],
|
||
transUpfront=True, revealGerman=True),
|
||
"2": {
|
||
"type": "choice", "title": "aus oder in", "sub": "sub_ausin",
|
||
"questions": choice_items(A("g02_rows")),
|
||
"meanings": str_list(A("g02_mean")),
|
||
},
|
||
"3": {
|
||
"type": "choice", "title": "Fragewörter", "sub": "sub_wer",
|
||
"questions": choice_items(A("g03_rows")),
|
||
"meanings": str_list(A("g03_mean")),
|
||
},
|
||
"4": combo("sprechen", "sub_sprich", str_list(A("g04_pool")),
|
||
verb_items(A("g04_rows")), str_list(A("g04_mean")), "verb"),
|
||
"5": {
|
||
"type": "typed", "title": "Nationalitäten", "sub": "sub_nation",
|
||
"rows": typed_items(A("g05_rows")),
|
||
"bank": as_str(A("g05_bank")[0]) if A("g05_bank") else
|
||
(as_str(arrays["g05_bank"][1][0]) if "g05_bank" in arrays
|
||
and arrays["g05_bank"][1] and isinstance(arrays["g05_bank"][1][0], str)
|
||
else None),
|
||
"answersNote": False,
|
||
},
|
||
"6": {
|
||
"type": "typed", "title": "Woher?", "sub": "sub_woher",
|
||
"rows": typed_items(A("g06_rows")),
|
||
},
|
||
"7": {
|
||
"type": "typed", "title": "Länder schreiben", "sub": "sub_laender",
|
||
"rows": typed_items(A("g07_rows")),
|
||
},
|
||
"8": combo("Verben einsetzen", "sub_verb2", str_list(A("g08_pool")),
|
||
verb_items(A("g08_rows")), str_list(A("g08_mean")), "verb"),
|
||
"9": {
|
||
"type": "free", "title": "Freie Antwort", "sub": "sub_free",
|
||
"tip": "tip_ss", "questions": free_items(A("ex9_free_qs")),
|
||
},
|
||
"10": combo("Euro", "sub_euro", str_list(A("g10_pool")),
|
||
verb_items(A("g10_rows")), str_list(A("g10_mean")),
|
||
"verb", preMeaning=True),
|
||
"11": {
|
||
"type": "typed", "title": "Wörter suchen", "sub": "sub_wortsuchen",
|
||
"rows": typed_items(A("g11_rows")),
|
||
},
|
||
"12": {
|
||
"type": "choice", "title": "Was ist richtig?", "sub": "sub_verb2",
|
||
"questions": choice_items(A("g12_rows")),
|
||
"meanings": str_list(A("g12_mean")),
|
||
},
|
||
"13": combo("Ordne zu", "sub_ordne", [],
|
||
ordne_items(A("g13_rows")), str_list(A("g13_mean")),
|
||
"ordne",
|
||
fwPool=str_list(A("g13_fw_pool")),
|
||
ansPool=str_list(A("g13_ans_pool"))),
|
||
"14": combo("Lückentext", "sub_luecke", str_list(A("g14_pool")),
|
||
verb_items(A("g14_rows")), str_list(A("g14_mean")),
|
||
"verb", preMeaning=True, gloss=str_list(A("g14_gloss"))),
|
||
"15": combo("Verbinde", "sub_verbinde", str_list(A("g15_pool")),
|
||
verb_items(A("g15_rows")), str_list(A("g15_mean")),
|
||
"verb", preMeaning=True, gloss=str_list(A("g15_gloss"))),
|
||
"16": combo("Zahlen", "sub_zahlpaar", str_list(A("g16_pool")),
|
||
verb_items(A("g16_rows")), str_list(A("g16_mean")),
|
||
"verb"),
|
||
"17": {
|
||
"type": "profile", "title": "Steckbrief", "sub": "sub_steckbrief",
|
||
"questions": profile_items(A("s01_rows")),
|
||
},
|
||
"18": {
|
||
"type": "hangman", "title": "Berufe", "sub": "sub_berufe",
|
||
"words": str_list(A("hm_words")),
|
||
"tips": str_list(A("hm_tips")),
|
||
"letters": str_list(A("hm_letters")),
|
||
},
|
||
"19": {
|
||
"type": "typed", "title": "Nationalität", "sub": "sub_bistdu",
|
||
"rows": typed_items(A("s03_rows")),
|
||
"answersNote": True,
|
||
},
|
||
},
|
||
"vocab": {
|
||
"type": "vocab", "title": "Vokabeltraining", "sub": "sub_translate",
|
||
"sections": trans_sections(A("u2_trans_sections"), arrays),
|
||
},
|
||
},
|
||
{
|
||
"id": 2,
|
||
"title": "Bei uns zu Hause",
|
||
"page": "unit3",
|
||
"tag": "u3",
|
||
"sub": "unit3_sub",
|
||
"unlocked": True,
|
||
"names": [
|
||
"Familienpaare", "mein oder dein", "ein / kein", "Lückentext",
|
||
"Marcos Familie", "Sortieren", "Possessivtabelle", "Akkusativ",
|
||
"kein / nicht", "Sätze bauen", "Was siehst du?", "Wem gehört das?",
|
||
"Es gibt …", "Beschreiben", "Wochenende",
|
||
],
|
||
"branch": "u3vocab",
|
||
"exercises": {
|
||
"1": combo("Familienpaare", "sub3_pair", str_list(A("ex1_u3_pool")),
|
||
fill_items(A("ex1_u3_rows")), [], "fill",
|
||
sample="Beispiel: der Vater ↔ die Mutter",
|
||
showGerman=False),
|
||
"2": {
|
||
"type": "ex2", "title": "mein oder dein", "sub": "sub3_poss",
|
||
"pool": str_list(A("ex2_u3_pool")),
|
||
"items": ex2_items(arrays),
|
||
},
|
||
"3": combo("ein / kein", "sub3_haustier", str_list(A("ex3_u3_pool")),
|
||
fill_items(A("ex3_u3_rows")), [], "fill",
|
||
sample="Beispiel: Hast du eine Katze? – Nein, ich habe keine Katze. Ich habe einen Hund.",
|
||
showGerman=True),
|
||
"4": {
|
||
"type": "letters_gap", "title": "Lückentext", "sub": "sub3_buchst",
|
||
"rows": ex4row_items(A("ex4_u3_rows")),
|
||
},
|
||
"5": {
|
||
"type": "typed", "title": "Marcos Familie", "sub": "sub3_marco",
|
||
"rows": typed_items(A("ex5_u3_rows")),
|
||
"bank": None,
|
||
},
|
||
"6": {
|
||
"type": "assign", "title": "Sortieren", "sub": "sub3_sort",
|
||
"items": assign_items(A("ex6_u3_items")),
|
||
"groups": str_list(A("ex6_u3_groups")),
|
||
"meanings": str_list(A("ex6_u3_meaning")),
|
||
},
|
||
"7": {
|
||
"type": "table", "title": "Possessivtabelle", "sub": "sub3_tabelle",
|
||
"persons": [
|
||
"ich", "du", "er", "sie", "es", "wir", "ihr", "sie / Sie",
|
||
],
|
||
"nouns": ["der Garten", "die Idee", "das Fest", "die Geschwister"],
|
||
"answers": [
|
||
["mein", "dein", "sein", "ihr", "sein", "unser", "euer", "ihr"],
|
||
["meine", "deine", "seine", "ihre", "seine", "unsere", "eure", "ihre"],
|
||
["mein", "dein", "sein", "ihr", "sein", "unser", "euer", "ihr"],
|
||
["meine", "deine", "seine", "ihre", "seine", "unsere", "eure", "ihre"],
|
||
],
|
||
},
|
||
"8": combo("Akkusativ", "sub3_akk", str_list(A("ex8_u3_pool")),
|
||
fill_items(A("ex8_u3_rows")), str_list(A("ex8_u3_gloss")),
|
||
"fill", showGerman=True),
|
||
"9": combo("kein / nicht", "sub3_nicht", str_list(A("ex9_u3_pool")),
|
||
fill_items(A("ex9_u3_rows")), [], "fill", showGerman=True),
|
||
"10": {
|
||
"type": "assembly", "title": "Sätze bauen", "sub": "sub3_satz",
|
||
"items": assembly_items(A("ex10_u3_items")),
|
||
"meanings": str_list(A("ex10_u3_meaning")),
|
||
},
|
||
"11": {
|
||
"type": "kw", "title": "Was siehst du?", "sub": "sub3_sehen",
|
||
"sample": "Beispiel: Ich sehe eine Burg. / Ich sehe ein Schloss.",
|
||
"rows": kw_items(A("ex11_u3_qs")),
|
||
},
|
||
"12": combo("Wem gehört das?", "sub3_wem", str_list(A("ex12_u3_pool")),
|
||
fill_items(A("ex12_u3_rows")), [], "fill", showGerman=True),
|
||
"13": combo("Es gibt …", "sub3_gibt", str_list(A("ex13_u3_pool")),
|
||
fill_items(A("ex13_u3_rows")), [], "fill", showGerman=True),
|
||
"14": {
|
||
"type": "kw", "title": "Beschreiben", "sub": "sub3_saetze",
|
||
"sample": "Beispiel: Ich sehe einen Jungen. Er liest ein Buch.",
|
||
"rows": kw_items(A("ex14_u3_qs")),
|
||
},
|
||
"15": {
|
||
"type": "letters", "title": "Wochenende", "sub": "sub3_wochen",
|
||
"rows": let_items(A("ex15_u3_rows")),
|
||
},
|
||
},
|
||
"vocab": {
|
||
"type": "vocab", "title": "Vokabeltraining", "sub": "sub_translate",
|
||
"sections": trans_sections(A("u3_trans_sections"), arrays),
|
||
},
|
||
},
|
||
]
|
||
|
||
locked_german = [
|
||
"Schule und Freizeit", "Guten Appetit!", "Mein Tagesablauf",
|
||
"Meine Freunde", "Wir treffen uns in Salzburg",
|
||
"Mein Haus ist meine Burg", "Urlaub in Österreich",
|
||
]
|
||
for i, title in enumerate(locked_german, start=3):
|
||
german_units.append({
|
||
"id": i, "title": title, "page": None, "tag": None,
|
||
"sub": None, "unlocked": False, "names": [],
|
||
"exercises": {},
|
||
})
|
||
|
||
# g05_bank is a single string, not an array of strings
|
||
if "g05_bank" in arrays:
|
||
raw = arrays["g05_bank"][1]
|
||
bank = None
|
||
if isinstance(raw, list) and raw and isinstance(raw[0], str) and len(raw) == 1:
|
||
bank = raw[0]
|
||
elif isinstance(raw, str):
|
||
bank = raw
|
||
if bank:
|
||
german_units[1]["exercises"]["5"]["bank"] = bank
|
||
|
||
if "ex5_u3_bank" in arrays:
|
||
raw = arrays["ex5_u3_bank"][1]
|
||
if isinstance(raw, list) and raw and isinstance(raw[0], str):
|
||
german_units[2]["exercises"]["5"]["bank"] = raw[0] if len(raw) == 1 else " ".join(
|
||
x for x in raw if isinstance(x, str)
|
||
)
|
||
|
||
net_lessons = []
|
||
for i in range(1, 28):
|
||
slides = slides_items(A(f"build_net_unit{i}_page.slides"))
|
||
qs = choice_items(A(f"net{i}_qs")) if i > 1 else []
|
||
net_lessons.append({
|
||
"id": i,
|
||
"titleKey": f"net_unit{i}",
|
||
"subKey": f"net_unit{i}_sub",
|
||
"exTitleKey": f"net_ex{i}_title" if i > 1 else "net_ex_title",
|
||
"quizHeadKey": f"net_quiz{i}_head" if i > 1 else None,
|
||
"slides": slides,
|
||
"quiz": qs,
|
||
})
|
||
|
||
hw_lessons = []
|
||
for i in range(1, 4):
|
||
hw_lessons.append({
|
||
"id": i,
|
||
"titleKey": f"hw_unit{i}",
|
||
"subKey": f"hw_unit{i}_sub",
|
||
"exTitleKey": f"hw_ex{i}_title",
|
||
"quizHeadKey": f"hw_quiz{i}_head",
|
||
"slides": slides_items(A(f"build_hw_unit{i}_page.slides")),
|
||
"quiz": choice_items(A(f"build_hw_unit{i}_exercise_page.qs")),
|
||
})
|
||
|
||
mluv_meta = [
|
||
(1, "typed", "Pravopis – i/y po obojetných souhláskách",
|
||
"Doplňte i/í nebo y/ý.", None, "e1_items"),
|
||
(2, "choice", "Pravopis – velká/malá písmena",
|
||
"Vyberte správnou variantu.", None, "e2_qs"),
|
||
(3, "typed", "Slovní druhy",
|
||
"Určete slovní druh podtržených slov.",
|
||
"Do pole napište název slovního druhu.", "e3_items"),
|
||
(4, "typed", "Mluvnické kategorie – podstatná jména",
|
||
"Určete rod, číslo a pád podtržených slov.",
|
||
"Vyplňte tři pole: rod, číslo a pád.", "e4_items"),
|
||
(5, "typed", "Mluvnické kategorie – slovesa",
|
||
"Určete osobu, číslo, čas a způsob u těchto sloves.",
|
||
"U rozkazovacího a podmiňovacího způsobu napište do pole čas „neurčitý“.",
|
||
"e5_items"),
|
||
(6, "reveal", "Slovotvorba", "Rozeberte slova na morfémy.",
|
||
"Určete předponu, kořen, příponu a koncovku.", "e6_items"),
|
||
(7, "typed", "Větné členy",
|
||
"Určete větný člen podtržených výrazů.", None, "e7_items"),
|
||
(8, "typed", "Druhy vedlejších vět",
|
||
"Určete druh vedlejší věty.", None, "e8_items"),
|
||
(9, "typed", "Synonyma", "Ke každému slovu napište synonymum.", None, "e9_items"),
|
||
(10, "typed", "Antonyma", "Napište opak.", None, "e10_items"),
|
||
(11, "typed", "Pravopis – s/z na začátku slova",
|
||
"Vyberte správnou předponu s-/z-.", None, "e11_items"),
|
||
(12, "reveal", "Skladba – najděte chybu",
|
||
"Každá věta obsahuje jednu chybu. Najděte ji a opravte.", None, "e12_items"),
|
||
(13, "typed", "Tvarosloví – správný tvar",
|
||
"Dejte slovo do správného tvaru.", None, "e13_items"),
|
||
(14, "choice", "Výběr ze čtyř možností",
|
||
"Vyberte správnou odpověď.", None, "e14_qs"),
|
||
(15, "reveal", "Přímá a nepřímá řeč",
|
||
"Přepište přímou řeč na nepřímou.", None, "e15_items"),
|
||
(16, "reveal", "Slovní zásoba – rozdíly",
|
||
"Vysvětlete rozdíl mezi těmito dvojicemi slov.", None, "e16_items"),
|
||
(17, "reveal", "Interpunkce",
|
||
"Doplňte čárky tam, kde patří.", None, "e17_items"),
|
||
(18, "typed", "Obrazná pojmenování",
|
||
"Určete, o jaký druh obrazného pojmenování jde.", None, "e18_items"),
|
||
(19, "typed", "Stylistika – slohové útvary",
|
||
"Přiřaďte ukázku ke správnému slohovému útvaru.", None, "e19_items"),
|
||
(20, "choice", "Souhrnné opakování",
|
||
"Vyberte správnou odpověď.", None, "e20_qs"),
|
||
]
|
||
mluv_names = [
|
||
"i/y", "Velká písmena", "Slovní druhy", "Podst. jména", "Slovesa",
|
||
"Slovotvorba", "Větné členy", "Vedlejší věty", "Synonyma", "Antonyma",
|
||
"s/z", "Najdi chybu", "Tvary", "Test A–D", "Přímá řeč",
|
||
"Rozdíly", "Interpunkce", "Obrazná pojmen.", "Slohové útvary",
|
||
"Opakování",
|
||
]
|
||
mluvnice = []
|
||
for n, kind, title, sub, note, key in mluv_meta:
|
||
item = {"id": n, "name": mluv_names[n - 1], "type": kind,
|
||
"title": title, "sub": sub, "note": note}
|
||
if kind == "typed":
|
||
item["items"] = mluv_items(A(key))
|
||
elif kind == "reveal":
|
||
item["items"] = reveal_items(A(key))
|
||
else:
|
||
item["questions"] = choice_items(A(key))
|
||
expl_key = key.replace("_qs", "_expl")
|
||
if expl_key in arrays:
|
||
item["expls"] = str_list(A(expl_key))
|
||
mluvnice.append(item)
|
||
|
||
def note_lines(fn, name):
|
||
return str_list(A(f"{fn}.{name}"))
|
||
|
||
books = [
|
||
{
|
||
"id": "1984",
|
||
"title": "George Orwell – 1984",
|
||
"subKey": "cetba1984_sub",
|
||
"page": "cetba1984",
|
||
"quizTitle": "lit_quiz_title",
|
||
"quizSub": "lit_quiz_sub",
|
||
"plotTitle": "lit_plot_title",
|
||
"plotSub": "lit_plot_sub",
|
||
"notes": [
|
||
{"title": "O knize", "icon": "book",
|
||
"lines": note_lines("build_cetba1984_page", "about")},
|
||
{"title": "Svět a strana", "icon": "globe",
|
||
"lines": note_lines("build_cetba1984_page", "world")},
|
||
{"title": "Postavy", "icon": "people",
|
||
"lines": note_lines("build_cetba1984_page", "people")},
|
||
{"title": "Klíčové pojmy", "icon": "bulb",
|
||
"lines": note_lines("build_cetba1984_page", "terms")},
|
||
],
|
||
"quiz": lit_items(A("lit_qs")),
|
||
"plot": assembly_items(A("lit_plot_items")),
|
||
"plotMeaning": str_list(A("lit_plot_meaning")),
|
||
},
|
||
{
|
||
"id": "fuks",
|
||
"title": "Ladislav Fuks – Spalovač mrtvol",
|
||
"subKey": "cetbaFuks_sub",
|
||
"page": "cetbaFuks",
|
||
"quizTitle": "lit_fuks_quiz_title",
|
||
"quizSub": "lit_fuks_quiz_sub",
|
||
"plotTitle": "lit_fuks_plot_title",
|
||
"plotSub": "lit_fuks_plot_sub",
|
||
"notes": [
|
||
{"title": "O knize", "icon": "book",
|
||
"lines": note_lines("build_cetba_fuks_page", "about")},
|
||
{"title": "Doba a svět", "icon": "globe",
|
||
"lines": note_lines("build_cetba_fuks_page", "world")},
|
||
{"title": "Postavy", "icon": "people",
|
||
"lines": note_lines("build_cetba_fuks_page", "people")},
|
||
{"title": "Klíčové motivy", "icon": "bulb",
|
||
"lines": note_lines("build_cetba_fuks_page", "terms")},
|
||
],
|
||
"quiz": lit_items(A("fuks_qs")),
|
||
"plot": assembly_items(A("fuks_plot_items")),
|
||
"plotMeaning": str_list(A("fuks_plot_meaning")),
|
||
},
|
||
]
|
||
|
||
subjects = [
|
||
{"key": "Deutsch", "open": True, "target": "roadmap", "icon": "de"},
|
||
{"key": "Správa počítačových sítí", "open": True, "target": "netyears", "icon": "wifi"},
|
||
{"key": "Technické vybavení", "open": True, "target": "hwmap", "icon": "chip"},
|
||
{"key": "Český jazyk a literatura", "open": True, "target": "czechmap", "icon": "cz"},
|
||
{"key": "Občanská nauka", "open": False, "target": None, "icon": "lock"},
|
||
{"key": "English", "open": False, "target": None, "icon": "lock"},
|
||
{"key": "Matematika", "open": False, "target": None, "icon": "lock"},
|
||
{"key": "Fyzika", "open": False, "target": None, "icon": "lock"},
|
||
{"key": "Základy Přírodopisných věd", "open": False, "target": None, "icon": "lock"},
|
||
{"key": "Technická grafika", "open": False, "target": None, "icon": "lock"},
|
||
{"key": "Prezentační grafika", "open": False, "target": None, "icon": "lock"},
|
||
{"key": "Programování", "open": False, "target": None, "icon": "lock"},
|
||
{"key": "Programové vybavení", "open": False, "target": None, "icon": "lock"},
|
||
]
|
||
|
||
payload = {
|
||
"i18n": i18n,
|
||
"subjects": subjects,
|
||
"german": german_units,
|
||
"net": {
|
||
"lessons": net_lessons,
|
||
"tasks": net_tasks(A("net_tasks")),
|
||
"answers": net_ans(A("net_ans")),
|
||
},
|
||
"hw": hw_lessons,
|
||
"mluvnice": mluvnice,
|
||
"books": books,
|
||
}
|
||
|
||
# Fix single-string banks stored as char* "arrays"
|
||
for key in ("g05_bank", "ex5_u3_bank"):
|
||
if key not in arrays:
|
||
continue
|
||
raw = arrays[key][1]
|
||
if isinstance(raw, list) and len(raw) == 1 and isinstance(raw[0], str) and " " in raw[0]:
|
||
if key == "g05_bank":
|
||
german_units[1]["exercises"]["5"]["bank"] = raw[0]
|
||
else:
|
||
german_units[2]["exercises"]["5"]["bank"] = raw[0]
|
||
|
||
text = json.dumps(payload, ensure_ascii=False, indent=2)
|
||
for dest in OUTS:
|
||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||
dest.write_text(text, encoding="utf-8")
|
||
|
||
def count(path, pred):
|
||
node = payload
|
||
for p in path:
|
||
node = node[p]
|
||
return sum(1 for x in node if pred(x))
|
||
|
||
print("wrote " + ", ".join(str(dest) for dest in OUTS))
|
||
print(f" i18n keys: {len(i18n)}")
|
||
print(f" german unlocked exercises: "
|
||
f"{sum(len(u.get('exercises', {})) for u in german_units if u['unlocked'])}")
|
||
print(f" net lessons with slides: {sum(1 for L in net_lessons if L['slides'])}")
|
||
print(f" net quizzes: {sum(1 for L in net_lessons if L['quiz'])}")
|
||
print(f" hw lessons: {sum(1 for L in hw_lessons if L['slides'])}")
|
||
print(f" mluvnice: {len(mluvnice)}")
|
||
print(f" books quiz items: {[len(b['quiz']) for b in books]}")
|
||
missing = []
|
||
for u in german_units:
|
||
if not u["unlocked"]:
|
||
continue
|
||
for k, ex in u["exercises"].items():
|
||
t = ex["type"]
|
||
if t == "choice" and not ex.get("questions"):
|
||
missing.append(f"{u['tag']}e{k} choice")
|
||
if t == "typed" and not ex.get("rows"):
|
||
missing.append(f"{u['tag']}e{k} typed")
|
||
if t == "assembly" and not ex.get("items"):
|
||
missing.append(f"{u['tag']}e{k} assembly")
|
||
if t == "vocab" and not ex.get("sections"):
|
||
missing.append(f"{u['tag']} vocab")
|
||
if t == "fill" and not ex.get("rows"):
|
||
missing.append(f"{u['tag']}e{k} fill")
|
||
if t == "hangman" and not ex.get("words"):
|
||
missing.append(f"{u['tag']}e{k} hangman")
|
||
if u.get("vocab") and not u["vocab"].get("sections"):
|
||
missing.append(f"{u['tag']} vocab sections")
|
||
for L in net_lessons:
|
||
if not L["slides"]:
|
||
missing.append(f"net{L['id']} slides")
|
||
if L["id"] > 1 and not L["quiz"]:
|
||
missing.append(f"net{L['id']} quiz")
|
||
for L in hw_lessons:
|
||
if not L["slides"]:
|
||
missing.append(f"hw{L['id']} slides")
|
||
if not L["quiz"]:
|
||
missing.append(f"hw{L['id']} quiz")
|
||
for m in mluvnice:
|
||
if m["type"] in ("typed", "reveal") and not m.get("items"):
|
||
missing.append(f"mluv{m['id']}")
|
||
if m["type"] == "choice" and not m.get("questions"):
|
||
missing.append(f"mluv{m['id']}")
|
||
if missing:
|
||
print("MISSING:")
|
||
for m in missing:
|
||
print(" ", m)
|
||
return 1
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|