From 72771bcbe820a996aa2884b484f6a9149ca6d2e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Mekina?= Date: Tue, 11 Nov 2025 21:39:10 +0100 Subject: [PATCH] Re-apply improved argument checking This reverts commit b846fb2b7d67b6937b203b40c92d26af3fa4dfdb. --- documentation.typ | 12 ++ template/arguments.typ | 143 ++++++++++++++++ template/type_signature.typ | 324 ++++++++++++++++++++++++++++++++++++ 3 files changed, 479 insertions(+) create mode 100644 template/type_signature.typ diff --git a/documentation.typ b/documentation.typ index 539d625..62c3752 100644 --- a/documentation.typ +++ b/documentation.typ @@ -426,6 +426,18 @@ do požadavků. Pak můžete postupně přepisovat/vyplňovat. Funkce `todo` vám zároveň zabrání v tom, aby se text Lorem Ipsum vyskytl ve výsledném dokumentu. +#pagebreak(weak: true) += Argumenty šablony + +Následujících pár stránek je kompletní soupis argumentů, které tato šablona přijímá. +Pokud s šablonou nemáte rozsáhlé zkušenosti, doporučujeme raději využít generátoru +(https://tulsablona.zumepro.cz/generate/). + +// Automaticky generovaný seznam argumentů +#import "template/arguments.typ": print_argument_docs +#print_argument_docs() + + #attachments( attach_link("Zdrojový kód této šablony", "https://git.zumepro.cz/tul/tultemplate2"), attach_content("Testovací obsah vygenerovaný Typstem", [Sem lze psát _stylovaný_ obsah.]), diff --git a/template/arguments.typ b/template/arguments.typ index 8657a78..b8434df 100644 --- a/template/arguments.typ +++ b/template/arguments.typ @@ -1,4 +1,147 @@ #import "utils.typ": assert_type_signature, is_none, map_none, deref, assert_dict_has +#import "type_signature.typ": * + +#let lang_keys = variants(literal("cs"), literal("en")); +#let nonrec_str = doc(string, none, "Je doporučeno použít 'content', pokud je to možné"); +#let cont_or_str = variants(cont, nonrec_str); +#let opt_cont_or_str = variants(cont, nonrec_str); + +// "fs", "ft", "fp", "ef", "fua", "fm", "fzs", "cxi" +#let arguments_structure = struct( + keyval(literal("document"), struct( // document + doc( + keyval(literal("visual_style"), variants(literal("classic"))), // document.visual_style + "style", "Vizuální styl šablony" + ), + + doc(keyval(literal("faculty"), variants( // document.faculty + doc(literal("fs"), none, "Fakulta strojní"), + doc(literal("ft"), none, "Fakulta textilní"), + doc(literal("fp"), none, "Fakulta přírodovědně-humanitní a pedagogická"), + doc(literal("ef"), none, "Ekonomická fakulta"), + doc(literal("fua"), none, "Fakulta umění a architektury"), + doc(literal("fm"), none, "Fakulta mechatroniky, informatiky a mezioborových studií"), + doc(literal("fzs"), none, "Fakulta zdravotnických studií"), + doc(literal("cxi"), none, "Ústav pro nanomateriály, pokročilé technologie a inovace"), + )), "faculty", "Fakulta (na základě toho budou vybrány barvy, logotypy, ...)"), + + doc(keyval(literal("language"), variants( // document.language + doc(literal("cs"), none, "Čeština"), + doc(literal("en"), none, "Angličtina"), + )), "lang", "Primární jazyk dokumentu"), + + doc(keyval(literal("type"), variants( // document.type + doc(literal("bp"), none, "Bakalářská práce"), + doc(literal("dp"), none, "Diplomová práce"), + doc(literal("prj"), none, "Projekt (ročník před odevzdáním bakalářské práce)"), + )), "document", "Typ dokumentu"), + )), + + doc(keyval(literal("title_pages"), variants( // title_pages + doc(string, none, "Cesta k souboru PDF, který má být vložen"), + doc(null, none, ( + "Stránky jsou generovány pomocí šablony. Tohle u některých prací může vyžadovat doplnění" + + " informací (argumentů)." + )), + )), "title_pages", "Způsob generování stránek na začátku dokumentu"), + + doc(keyval(literal("title"), dict(keyval(variants( // title + doc(literal("cs"), none, "Název práce v češtině"), + doc(literal("en"), none, "Název práce v angličtině"), + ), cont_or_str))), "title", "Název práce"), + + keyval(literal("author"), struct( // author + doc(keyval( // author.name + literal("name"), opt_cont_or_str + ), "author", "Jméno autora včetně titulů"), + + doc(keyval(literal("pronouns"), variants( // author.pronouns + doc(literal("masculine"), none, "Pro češtinu, oslovení v mužském rodě"), + doc(literal("feminine"), none, "Pro češtinu, oslovení v ženském rodě"), + doc(literal("we"), none, "Pro češtinu a angličtinu, oslovení v množném čísle"), + doc(literal("me"), none, "Pro angličtinu, oslovení v jednotném čísle"), + doc(null, none, "Pro angličtinu, oslovení v jednotném čísle"), + )), "author", "Jméno autora včetně titulů"), + + doc(keyval( // author.programme + literal("programme"), dict(keyval(lang_keys, opt_cont_or_str)) + ), "programme", "Studijní program, pod kterým byl tento dokument vytvořen"), + + doc(keyval( // author.specialization + literal("specialization"), dict(keyval(lang_keys, opt_cont_or_str)) + ), "specialization", "Specializace, pod kterou byl tento dokument vytvořen"), + + doc(keyval( // author.year_of_study + literal("year_of_study"), dict(keyval(lang_keys, opt_cont_or_str)) + ), "year_of_study", "Specializace, pod kterou byl tento dokument vytvořen"), + )), + + keyval(literal("project"), struct( // project + doc(keyval(literal("supervisor"), variants(cont, nonrec_str, struct( // project.supervisor + doc(keyval(literal("name"), cont_or_str), none, "Jméno vedoucího projektu"), + doc(keyval(literal("institute"), cont_or_str), none, "Ústav vedoucího projektu"), + ), null)), "supervisor", "Vedoucí projektu"), + + doc(keyval(literal("consultant"), variants(cont, nonrec_str, struct( // project.consultant + doc(keyval(literal("name"), cont_or_str), none, "Jméno konzultanta projektu"), + doc(keyval(literal("institute"), cont_or_str), none, "Ústav konzultanta projektu"), + ), null)), "consultant", "Konzultant projektu"), + )), + + keyval(literal("abstract"), struct( // abstract + doc( + keyval(literal("content"), struct(..lang_keys.variants.map((k) => { // abstract.content + keyval(k, variants(cont, nonrec_str, slice(string))) + }))), + "abstract", "Abstrakt projektu" + ), + + doc( + keyval( + literal("keywords"), // abstract.keywords + struct(..lang_keys.variants.map((k) => { + keyval(k, variants(cont, nonrec_str, slice(string))) + })), + ), + "keywords", "Klíčová slova projektu" + ), + )), + + doc( // acknowledgement + keyval(literal("acknowledgement"), dict(keyval(lang_keys, opt_cont_or_str))), + "acknowledgement", "Poděkování", + ), + + doc( // assignment + keyval(literal("assignment"), variants( + doc(string, none, "Zadání bude vloženo z PDF souboru s touto cestou"), + doc( + struct( + keyval(literal("personal_number"), cont_or_str), + keyval(literal("department"), cont_or_str), + keyval(literal("academical_year"), cont_or_str), + doc(keyval(literal("content"), cont), none, "Obsah zadání (zobrazen pod informacemi)"), + ), + none, "Zadání bude vygenerováno šablonou" + ), + doc( + null, none, + "Pokud je zadání vyžadováno typem dokumentu, bude vložena strana s upozorněním na vložení", + ), + doc(cont, none, "Zdrojový kód se zadáním (tato možnost se nedoporučuje)"), + )), + "assignment", "Stránka / stránky se zadáním", + ), + + doc( // citations + keyval(literal("citations"), string), + "citations", "Cesta k souboru s citacemi", + ), +); + +#let print_argument_docs() = { + signature_docs(arguments_structure); +} #let arguments_structure = ( document: ( diff --git a/template/type_signature.typ b/template/type_signature.typ new file mode 100644 index 0000000..5ca1ab2 --- /dev/null +++ b/template/type_signature.typ @@ -0,0 +1,324 @@ +#let doc(inner, argument_name, explanation) = { + inner.doc = (argname: argument_name, expl: explanation); + inner +} + +#let type_primitive(id, name, cs_name) = { + (type: "primitive", type_id: id, type_name: name, type_name_cs: cs_name) +} + +#let int = type_primitive("integer", "an 'integer'", "celé číslo"); +#let float = type_primitive("float", "a 'float'", "desetinné číslo"); +#let bool = type_primitive("boolean", "a 'boolean'", "pravdivostní hodnota"); +#let string = type_primitive("string", "a 'string'", "textový řetězec"); +#let cont = type_primitive("content", "a 'content'", "obsah generovaný Typst syntaxí"); +#let null = type_primitive("none", "a 'none'", "prázdná hodnota"); + +#let literal(value) = { + (type: "literal", value: value) +} + +#let variants(..variants) = { + (type: "variants", variants: variants.pos()) +} + +#let slice(items) = { + (type: "slice", items: items) +} + +#let tuple(..items) = { + (type: "tuple", items: items.pos()) +} + +#let keyval(key, val) = { + (type: "keyval", key: key, val: val) +} + +#let dict(keyval) = { + (type: "dictionary", key: keyval.key, val: keyval.val) +} + +#let struct(..keyvals) = { + let copy_doc(from, to) = { + if "doc" in from { + let to = to; + to.doc = from.doc; + to + } else { + to + } + } + + let keyvals = keyvals.pos(); + let res = ().to-dict(); + for keyval in keyvals { + if keyval.key.type != "literal" { + panic("invalid type signature, struct keys must be literals"); + } + res.insert(keyval.key.value, copy_doc(keyval, keyval.val)); + } + (type: "struct", pairs: res) +} + +#let dbg_literal(literal, is_content: false) = { + let res = if type(literal) == str { + "\"" + literal + "\"" + } else { + "'" + str(literal) + "'" + }; + if is_content { + raw(res) + } else { + res + } +} + + +#let signature_check(value, signature, name_prefix) = { + let error_target_name(target, name_prefix) = { + name_prefix + if "doc" in target { " ('" + target.doc.argname + "')" } else { "" } + } + + let error_value_name(value) = { + str(type(value)) + } + + let error_expected_type(target) = { + if target.type == "variants" { + let variants = target.variants; + let last = variants.pop(); + let res = (variants.map((v) => { error_expected_type(v) })).join(", "); + if type(last) != type(none) { + if type(res) != type(none) { + res + " or " + error_expected_type(last) + } else { + error_expected_type(last) + } + } else { + res + } + } else if target.type == "slice" or target.type == "tuple" { + "an array" + } else if target.type == "dictionary" or target.type == "struct" { + "a dictionary/hashmap" + } else if target.type == "primitive" { + target.type_name + } else if target.type == "literal" { + dbg_literal(target.value) + } else { + panic(); + } + } + + let error(target, name_prefix, value, is_value: false, target_doc: none) = { + if type(target_doc) != type(none) { + error_target_name(target_doc) + } else { + error_target_name(target, name_prefix) + } + " " + if is_value { + "is unexpected" + } else { + "has an unexpected type '" + error_value_name(value) + "'" + } + ", expected " + error_expected_type(target) + } + + let in_variants(value, variants, matcher) = { + for variant in variants { + if matcher(value, variant).at(0) == true { + return true; + } + } + false + } + + let matches_type(value, target, name_prefix: "") = { + if target.type == "variants" { + return if in_variants(value, target.variants, matches_type.with(name_prefix: name_prefix)) { + (true,) + } else { + (false, error(target, name_prefix, value, is_value: true)) + } + } else if target.type == "literal" { + return if value != target.value { + (false, error(target, name_prefix, value, is_value: true)) + } else { + (true,) + } + } + if type(value) == dictionary { + if target.type == "struct" { + for key in target.pairs.keys() { + if key not in value { + return (false, ( + error_target_name(target, name_prefix) + + " is missing an entry for key " + + dbg_literal(key) + )); + } + } + for (key, val) in value.pairs() { + if key not in target.pairs { + return ( + false, name_prefix + " contains an unexpected key " + dbg_literal(key) + ); + } + matches_type( + val, target.pairs.at(key), name_prefix: name_prefix + " " + str(key) + ) + } + } else if target.type == "dictionary" { + for (key, val) in value.pairs() { + let cur = matches_type(key, target.key, name_prefix: name_prefix + " key"); + if not cur.at(0) { + return cur; + } + let cur = matches_type( + val, target.val, name_prefix: name_prefix + " value" + ); + if not cur.at(0) { + return cur; + } + } + (true,) + } else { + (false, error(target, name_prefix, value)) + } + } else if type(value) == array { + if target.type == "slice" { + for (idx, val) in value.enumerate() { + let cur = matches_type( + val, target.items, name_prefix: name_prefix + " item at index " + str(idx) + ); + if not cur.at(0) { + return cur; + } + } + (true,) + } else if target.type == "tuple" { + for (idx, target) in target.items.enumerate() { + if idx >= value.len() { + return (false, name_prefix + " is missing an item: " + error_expected_type(target)) + } + let cur = matches_type( + value.at(idx), target, name_prefix: name_prefix + " item at index " + str(idx) + ); + if not cur.at(0) { + return cur; + } + } + (true,) + } else { + (false, error(target, name_prefix, value)) + } + } else { + if target.type != "primitive" or str(type(value)) != target.type_id { + (false, error(target, name_prefix, value)) + } else { + (true,) + } + } + } + + matches_type(value, signature, name_prefix: name_prefix) +} + +#let signature_docs(target, is_nested: false) = { + let typeinfo(target, flatten: false, disable_doc: false) = { + let try_doc(target, mapper: (v) => { v }) = { + if "doc" in target and not disable_doc { + mapper(target.doc.expl); + } else { + "" + } + } + + if target.type == "struct" { + if not flatten { + [slovník s přesnými atributy (_dictionary_)]; + try_doc(target, mapper: (v) => { [ -- ]; v; }); + ":"; + } + list( + ..target.pairs.pairs().map(((key, val)) => { + raw(key); + try_doc(val, mapper: (v) => { [ -- ]; text(v); }); + ": " + typeinfo(val, disable_doc: true); + }) + ); + } else if target.type == "dictionary" { + [slovník (_dictionary_)]; + try_doc(target, mapper: (v) => { [ -- ]; v; }); + ":"; + list( + { + "S klíči: "; + typeinfo(target.key); + }, + { + "S hodnotami: "; + typeinfo(target.val); + }, + ); + } else if target.type == "slice" { + try_doc(target, mapper: (v) => { v; ": "; }); + [pole různé délky (_array_) s prvky: ]; + typeinfo(target.items, disable_doc: true); + } else if target.type == "tuple" { + try_doc(target, mapper: (v) => { v; ": "; }); + [pole (_array_) s upřesněnými prvky: ]; + list( + ..target.items.map((v) => { + list.item({ + typeinfo(v); + }); + }) + ); + } else if target.type == "primitive" { + if target.type_id == "none" { + text("prázdné ('none')"); + try_doc(target, mapper: (v) => { [ -- ]; text(v); }); + } else { + text(target.type_name_cs + " ("); + text(target.type_id, style: "italic"); + text(")"); + try_doc(target, mapper: (v) => { [ -- ]; text(v); }); + } + } else if target.type == "variants" { + try_doc(target, mapper: (v) => { text(v); ":"; }); + list( + ..target.variants.map((v) => { + list.item({ + typeinfo(v); + }); + }) + ); + } else if target.type == "literal" { + dbg_literal(target.value, is_content: true); + try_doc(target, mapper: (v) => { [ -- ]; text(v); }); + } else { + panic(); + } + } + + let args = (); + if target.type == "struct" { + for val in target.pairs.values() { + args.push(signature_docs(val, is_nested: true)); + } + } else { + if "doc" in target and type(target.doc.argname) != type(none) { + args.push({ + raw(target.doc.argname); + [ -- ] + typeinfo(target); + }); + } + } + + if not is_nested { + list(spacing: 2em, ..args.flatten()); + } else { + args.flatten() + } +}