prepare for more thesis types

This commit is contained in:
2025-10-01 19:19:17 +02:00
parent 1f75a74f61
commit c41ac4bc77
9 changed files with 589 additions and 161 deletions

34
template/classic/bp.typ Normal file
View File

@@ -0,0 +1,34 @@
#import "../lang.typ": get_lang_item
#import "common.typ": mainpage, default_styling, assignment, disclaimer, abstract, toc, abbrlist
#import "../utils.typ": is_none, assert_dict_has, assert_not_none
#let bp(
// general settings
faculty_id, faculty_color, language, assignment_document, citation_file,
// document info
title, author, author_gender, supervisor, study_programme, abstract_content, keywords,
content
) = {
let force_langs = ("cs", "en");
assert_dict_has(force_langs, title, "title");
assert_not_none(abstract_content, "abstract");
assert_dict_has(force_langs, abstract_content, "abstract");
if not is_none(keywords) {
assert_dict_has(force_langs, keywords, "keywords");
}
assert_not_none(author_gender, "author gender");
mainpage(faculty_id, language, none, title, author, supervisor, study_programme);
assignment(language, assignment_document);
default_styling(false, faculty_color, {
disclaimer(language, faculty_id, "bp", author, author_gender);
abstract("cs", title, abstract_content, keywords);
abstract("en", title, abstract_content, keywords);
toc(language);
abbrlist(language);
content
bibliography(citation_file, style: "../tul_citace.csl");
});
}

View File

@@ -0,0 +1,41 @@
// tools & utils
#import "../theme.typ": faculty_logotype, tul_logomark, faculty_color
#import "../lang.typ": lang_id, get_lang_item
#import "../utils.typ": assert_in_dict, assert_in_arr, map_none
// thesis types
#import "bp.typ": bp
#import "other.typ": other
#let template_classic(
// general settings
language, faculty_id, document_type, citation_file, assignment_document,
// document info
title, author, author_gender, supervisor, study_programme, abstract, keywords,
// content
content,
) = {
let document_types = (
"bp": bp,
"other": other,
)
assert_in_dict(document_type, document_types, "document type");
document_types.at(document_type)(
faculty_id,
faculty_color(faculty_id),
language,
assignment_document,
map_none(citation_file, (v) => "../../" + v),
title,
author,
author_gender,
supervisor,
study_programme,
abstract,
keywords,
content,
);
}

View File

@@ -1,13 +1,66 @@
#import "theme.typ": faculty_logotype, tul_logomark, faculty_color
#import "lang.typ": lang_id, get_lang_item
#import "utils.typ": assert_in_dict, assert_in_arr
#import "../theme.typ": faculty_logotype, tul_logomark, faculty_color
#import "../lang.typ": get_lang_item
#import "../utils.typ": is_none
#let base_font = "Inter";
#let mono_font = "Noto Sans Mono";
#let serif_font = "Merriweather";
#let tul_logomark_size = 6.5em;
#let classic_header(faculty_id, language) = {
// TYPST ELEMENT STYLING
#let default_styling(flip_bonding, faculty_color, content) = {
// page
set page(
margin: if flip_bonding {
(inside: 4cm, top: 3cm, bottom: 3cm)
} else {
(left: 4cm, top: 3cm, bottom: 3cm)
},
numbering: "1", footer: {
context {
let page = counter(page).get().at(0);
if flip_bonding {
align(str(page), if calc.rem(page, 2) == 1 { right } else { left });
} else {
align(str(page), right);
}
}
});
// text
set text(font: serif_font);
set par(justify: true);
// heading
set heading(numbering: "1.1.1 ");
show heading: it => {
set par(justify: false);
block(
above: 2em,
below: 2em,
text(it, faculty_color, font: "TUL Mono", size: 1.2em)
);
};
show heading.where(level: 1): it => {
pagebreak(weak: true);
v(2cm);
it
};
// other
show raw: set text(font: mono_font);
show raw.where(block: true): it => {
block(it, fill: rgb("#eee"), inset: 1em)
};
set highlight(fill: faculty_color.lighten(90%));
set image(width: 80%);
content
}
#let header(faculty_id, language) = {
let logotype = faculty_logotype(faculty_id, language);
grid(
block(logotype, width: 100%),
@@ -16,7 +69,9 @@
);
}
#let classic_info(
// DOCUMENT INFO
#let info(
faculty_id,
language,
document_type,
@@ -27,9 +82,6 @@
// document type
if type(document_type) != type(none) {
// TODO: hab, teze, autoref, proj, sp
let document_types = ("bp", "dp", "dis");
assert_in_arr(document_type, document_types, "document type abbreviation");
text(get_lang_item(language, document_type), weight: "bold", font: base_font);
v(0em);
}
@@ -45,7 +97,7 @@
// [field_name, field_value, bold]
let info_fields = (
("study_programme", study_programme, false),
("author", author, true),
(if author.contains(", ") { "authors" } else { "author" }, author, true),
("supervisor", supervisor, false),
)
context {
@@ -75,13 +127,15 @@
}
}
#let classic_mainpage(
// MAINPAGE
#let mainpage(
faculty_id,
language,
document_type,
title, author, supervisor, study_programme,
) = {
import "utils.typ": has_all_none
import "../utils.typ": has_all_none
let nonetype = type(none);
page({
if has_all_none((
@@ -89,9 +143,9 @@
)) {
place(center + horizon, align(left, faculty_logotype(faculty_id, language)));
} else {
classic_header(faculty_id, language);
header(faculty_id, language);
align({
classic_info(
info(
faculty_id, language, document_type, title.at(language),
author, supervisor, study_programme,
);
@@ -101,6 +155,8 @@
}, margin: 2cm);
}
// ASSIGNMENT PAGE
#let assignment(language, document) = {
if type(document) == type(none) {
page(
@@ -111,6 +167,7 @@
font: base_font,
weight: "bold",
)),
margin: 0em,
footer: none,
);
return;
@@ -120,31 +177,28 @@
muchpdf(read(document, encoding: none));
}
#let disclaimer(language, faculty_id, disclaimer_type, author) = {
let disclaimers_for = ("bp");
if type(disclaimer_type) == type(none) or disclaimer_type not in disclaimers_for {
return;
}
// DISCLAIMER PAGE
#let disclaimer(language, faculty_id, disclaimer_type, author, author_gender) = {
import "../lang.typ": disclaimer
heading(get_lang_item(language, "disclaimer"), numbering: none, outlined: false);
par(
text(get_lang_item(language, "disclaimer_" + disclaimer_type))
text(disclaimer(language, disclaimer_type, author_gender))
);
v(5em);
grid(
columns: 2,
gutter: 1em,
block(text(datetime.today().display(get_lang_item(language, "date")), lang: "cs"), width: 100%),
block(
text(datetime.today().display(get_lang_item(language, "date")), lang: "cs"), width: 100%
),
text(author),
);
}
// ABSTRACT
#let abstract(language, title, content, keywords) = {
if type(content.at(language)) == type(none) {
return;
}
if type(title.at(language)) == type(none) {
panic("no title found for language `" + language + "` (required for abstract)");
}
heading(text(title.at(language), font: base_font), numbering: none, outlined: false);
v(2em);
heading(
@@ -154,7 +208,7 @@
outlined: false,
);
text(content.at(language));
if type(keywords.at(language)) != type(none) {
if not is_none(keywords) and type(keywords.at(language)) != type(none) {
linebreak();
linebreak();
text(get_lang_item(language, "keywords") + ": ", weight: "bold", font: base_font);
@@ -162,15 +216,17 @@
}
}
// ABBREVIATION LIST
#let abbrlist(language) = {
import "abbreviations.typ": abbrlist
import "../abbreviations.typ": abbrlist
context {
let abbrs = abbrlist();
let max_abbr_width = if abbrs.len() > 0 {
calc.max(abbrs.keys().map((v) => measure(v).width)).at(0)
} else { return };
pagebreak(weak: true);
heading(("Seznam zkratek", "List of abbreviations").at(language), numbering: none);
heading(get_lang_item(language, "abbrs"), numbering: none);
align(center, grid(
columns: 2,
gutter: 1em,
@@ -184,113 +240,12 @@
}
}
#let template_classic(
faculty_id,
language,
document_type,
title_cs, author, supervisor, study_programme, abstract_cs, keywords_cs,
title_en, abstract_en, keywords_en,
assignment_document,
citation_file,
content,
) = {
let flip_bonding = if document_type == "bp" {
false
} else {
true
};
// TABLE OF CONTENTS
let title = (
"cs": title_cs,
"en": title_en,
);
// main page
classic_mainpage(faculty_id, language, document_type, title, author, supervisor, study_programme);
// styling
let faculty_color = faculty_color(faculty_id);
set par(justify: true);
set heading(numbering: "1.1.1 ");
set page(
margin: if flip_bonding {
(inside: 4cm, top: 3cm, bottom: 3cm)
} else {
(left: 4cm, top: 3cm, bottom: 3cm)
},
numbering: "1", footer: {
context {
let page = counter(page).get().at(0);
if flip_bonding {
align(str(page), if calc.rem(page, 2) == 1 { right } else { left });
} else {
align(str(page), right);
}
}
});
set text(font: serif_font);
show heading: it => {
set par(justify: false);
block(
above: 2em,
below: 2em,
text(it, faculty_color, font: "TUL Mono", size: 1.2em)
);
};
show heading.where(level: 1): it => {
pagebreak();
v(2cm);
it
};
show raw: set text(font: mono_font);
show raw.where(block: true): it => {
block(it, fill: rgb("#eee"), inset: 1em)
};
set highlight(fill: faculty_color.lighten(90%));
set image(width: 80%);
// assignment
if document_type in ("bp", "dp", "dis") or type(assignment_document) != type(none) {
assignment(language, assignment_document);
}
// disclaimer
disclaimer(language, faculty_id, document_type, author);
// abstract
let abstract_content = (
"cs": abstract_cs,
"en": abstract_en,
);
let keywords = (
"cs": keywords_cs,
"en": keywords_en,
);
if language == "cs" {
abstract("cs", title, abstract_content, keywords);
abstract("en", title, abstract_content, keywords);
} else {
abstract(language, title, abstract_content, keywords);
}
let language = lang_id(language);
// toc
#let toc(language) = {
show outline.entry.where(level: 1): it => {
show repeat: none;
block(text(it, weight: "bold", size: 1.2em), above: 1.5em);
};
outline(title: ("Obsah", "Contents").at(language));
// abbreviation list
abbrlist(language);
// content
if flip_bonding {
pagebreak(to: "even", weak: true);
}
content
// bibliography
bibliography(citation_file, style: "./tul_citace.csl")
outline(title: get_lang_item(language, "toc"));
}

View File

@@ -0,0 +1,25 @@
#import "../lang.typ": get_lang_item
#import "common.typ": mainpage, default_styling, assignment, disclaimer, abstract, toc, abbrlist
#import "../utils.typ": is_none
#let other(
// general settings
faculty_id, faculty_color, language, assignment_document, citation_file,
// document info
title, author, _, supervisor, study_programme, abstract_content, keywords,
content
) = {
mainpage(faculty_id, language, none, title, author, supervisor, study_programme);
default_styling(true, faculty_color, {
toc(language);
abbrlist(language);
pagebreak(to: "even", weak: true);
content
// bibliography
bibliography(citation_file, style: "../tul_citace.csl");
});
}

View File

@@ -1,6 +1,7 @@
{
"cs": {
"author": "Autor",
"authors": "Autoři",
"supervisor": "Vedoucí práce",
"study_programme": "Studijní program",
@@ -10,19 +11,37 @@
"city": "Liberec",
"toc": "Obsah",
"disclaimer": "Prohlášení",
"disclaimer_bp": "Prohlašuji, že svou bakalářskou práci jsem vypracoval samostatně jako původní dílo s použitím uvedené literatury a na základě konzultací s vedoucím mé bakalářské práce a konzultantem.\n\nJsem si vědom toho, že na mou bakalářskou práci se plně vztahuje zákon č. 121/2000 Sb., o právu autorském, zejména § 60 školní dílo.\n\nBeru na vědomí, že Technická univerzita v Liberci nezasahuje do mých autorských práv užitím mé bakalářské práce pro vnitřní potřebu Technické univerzity v Liberci.\n\nUžiji-li bakalářskou práci nebo poskytnu-li licenci k jejímu využití, jsem si vědom povinnosti informovat o této skutečnosti Technic-kou univerzitu v Liberci; v tomto případě má Technická univerzita v Liberci právo ode mne požadovat úhradu nákladů, které vynaložila na vytvoření díla, až do jejich skutečné výše.\n\nSoučasně čestně prohlašuji, že text elektronické podoby práce vložený do IS/STAG se shoduje s textem tištěné podoby práce.\n\nBeru na vědomí, že má bakalářská práce bude zveřejněna Tech- nickou univerzitou v Liberci v souladu s § 47b zákona č. 111/1998 Sb., o vysokých školách a o změně a doplnění dalších zákonů (zá- kon o vysokých školách), ve znění pozdějších předpisů.\n\nJsem si vědom následků, které podle zákona o vysokých školách mohou vyplývat z porušení tohoto prohlášení.",
"disclaimer_content": "Prohlašuji, že {svůj} {práce:tu} jsem vypracoval{a} samostatně jako původní dílo s použitím uvedené literatury a na základě konzultací s vedoucím mé bakalářské práce a konzultantem.\n\nJsem si vědom{a} toho, že na {moji} {práce:tu} se plně vztahuje zákon č. 121/2000 Sb., o právu autorském, zejména § 60 školní dílo.\n\nBeru na vědomí, že Technická univerzita v Liberci nezasahuje do mých autorských práv užitím {} {práce:té} pro vnitřní potřebu Technické univerzity v Liberci.\n\nUžiji-li {práce:tu} nebo poskytnu-li licenci k {jejímu} využití, jsem si vědom{a} povinnosti informovat o této skutečnosti Technickou univerzitu v Liberci; v tomto případě má Technická univerzita v Liberci právo ode mne požadovat úhradu nákladů, které vynaložila na vytvoření díla, až do jejich skutečné výše.\n\nSoučasně čestně prohlašuji, že text elektronické podoby práce vložený do IS/STAG se shoduje s textem tištěné podoby práce.\n\nBeru na vědomí, že {můj} {práce:ta} bude {zveřejněn} Technickou univerzitou v Liberci v souladu s § 47b zákona č. 111/1998 Sb., o vysokých školách a o změně a doplnění dalších zákonů (zákon o vysokých školách), ve znění pozdějších předpisů.\n\nJsem si vědom{a} následků, které podle zákona o vysokých školách mohou vyplývat z porušení tohoto prohlášení.",
"disclaimer_replace": {
"bp": {
"práce:ta": "bakalářská práce",
"práce:tu": "bakalářskou práci",
"práce:té": "bakalářské práce",
"moji": "moji",
"mé": "mé",
"můj": "moje",
"svůj": "svoji",
"jejímu": "jejímu",
"zveřejněn": "zveřejněna"
}
},
"date": "[day]. [month]. [year]",
"abstract": "Abstrakt",
"keywords": "Klíčová slova",
"abbrs": "Seznam zkratek",
"place_assignment": "Sem vložte zadání"
},
"en": {
"author": "Author",
"authors": "Authors",
"supervisor": "Supervisor",
"study_programme": "Study programme",
@@ -32,6 +51,8 @@
"city": "Liberec",
"toc": "Contents",
"disclaimer": "Declaration",
"disclaimer_bp": "I hereby certify, I, myself, have written my bachelor thesis as an original and primary work using the literature listed below and consulting it with my thesis supervisor and my thesis counsellor.\n\nI acknowledge that my bachelor thesis is fully governed by Act No. 121/2000 Coll., the Copyright Act, in particular Article 60 School Work.\n\nI acknowledge that the Technical University of Liberec does not infringe my copyrights by using my bachelor thesis for internal purposes of the Technical University of Liberec.\n\nI am aware of my obligation to inform the Technical University of Liberec on having used or granted license to use the results of my bachelor thesis; in such a case the Technical University of Liberec may require reimbursement of the costs incurred for creating the result up to their actual amount.\n\nAt the same time, I honestly declare that the text of the printed version of my bachelor thesis is identical with the text of the electronic version uploaded into the IS STAG.\n\nI acknowledge that the Technical University of Liberec will make my bachelor thesis public in accordance with paragraph 47b of Act No. 111/1998 Coll., on Higher Education Institutions and on Amendment to Other Acts (the Higher Education Act), as amended.\n\nI am aware of the consequences which may under the Higher Education Act result from a breach of this declaration.",
@@ -39,6 +60,7 @@
"abstract": "Abstract",
"keywords": "Keywords",
"abbrs": "List of abbreviations",
"place_assignment": "Insert your assignment here"
}

View File

@@ -20,3 +20,23 @@
let lang_items = fetch_lang_items();
return lang_items.at(lang_abbr).at(item_name);
}
#let disclaimer(language, document_type, author_gender) = {
let disclaimer = get_lang_item(language, "disclaimer_content");
let replacements = get_lang_item(language, "disclaimer_replace").at(document_type);
if language == "cs" {
let gender_transforms = (
male: "",
female: "a",
);
assert_in_dict(author_gender, gender_transforms, "author gender");
disclaimer = disclaimer.replace("{a}", gender_transforms.at(author_gender));
}
for (key, value) in replacements.pairs() {
disclaimer = disclaimer.replace("{" + key + "}", value);
}
if disclaimer.contains("{") or disclaimer.contains("}") {
panic("invalid language file");
}
disclaimer
}

View File

@@ -38,41 +38,54 @@
//
//-> none
#let tultemplate2(
style: "classic",
faculty: "tul",
lang: "cs",
document: none,
title_cs: none, author: none, supervisor: none, programme: none, abstract_cs: none,
keywords_cs: none,
title_en: none, abstract_en: none, keywords_en: none,
assignment: none,
citations: "citations.bib",
// general settings
style: "classic", faculty: "tul", lang: "cs", document: "other",
// document info
title: none, keywords: none, abstract: none, author: none, author_gender: none,
supervisor: none, programme: none,
// links
assignment: none, citations: "citations.bib",
// content
content,
) = {
import "template_classic.typ": template_classic
import "utils.typ": assert_in_dict
import "utils.typ": assert_in_dict, assert_type_signature
// argument checking
assert_type_signature(style, "string", "visual style argument");
assert_type_signature(faculty, "string", "faculty id argument");
assert_type_signature(lang, "string", "language abbreviation argument");
assert_type_signature(document, "string | none", "document kind argument");
assert_type_signature(title, "dictionary[string : string] | none", "title argument");
assert_type_signature(keywords, "dictionary[string : array[string]] | none", "keywords argument");
assert_type_signature(abstract, "dictionary[string : string] | none", "abstract argument");
assert_type_signature(author, "string | none", "author argument");
assert_type_signature(author_gender, "string | none", "author gender argument");
assert_type_signature(supervisor, "string | none", "supervisor argument");
assert_type_signature(
programme, "dictionary[string : string] | none", "study programme argument"
);
assert_type_signature(assignment, "string | none", "assignment document argument");
assert_type_signature(citations, "string", "citations file argument");
// templates
import "classic/classic.typ": template_classic
let templates = (
classic: template_classic,
);
assert_in_dict(style, templates, "template name");
// global set-up
// language set-up
import "lang.typ": lang_ids
assert_in_dict(lang, lang_ids, "language abbreviation");
set text(lang: lang);
// verify
if document == "bp" and (type(abstract_cs) == type(none) or type(abstract_en) == type(none)) {
panic("need both czech and english abstract for document of type 'bp'");
}
// template call
templates.at(style)(
faculty, lang, document,
title_cs, author, supervisor, programme, abstract_cs, keywords_cs,
title_en, abstract_en, keywords_en,
if type(assignment) == type(none) { none } else { "../" + assignment },
"../" + citations,
lang, faculty, document, citations, assignment,
title, author, author_gender, supervisor, programme, abstract, keywords,
content
);

View File

@@ -1,9 +1,198 @@
#let join(a, b) = {
let res = ();
if type(a) == array {
for a in a {
res.push(a);
}
} else {
res.push(a);
}
if type(b) == array {
for b in b {
res.push(b);
}
} else {
res.push(b);
}
res
}
#let serialize_array(arr) = {
arr.map((v) => { "'" + str(v) + "'" }).join(", ")
}
// Assumes a valid type signature
#let decompose_type_signature(signature) = {
let parse_variants(raw) = {
let tmp = "";
let res = ();
for char in raw {
if char == ":" {
let trimmed = tmp.trim();
tmp = "";
if trimmed.len() != 0 {
res.push(trimmed);
}
res.push(":");
} else if char == "|" {
let trimmed = tmp.trim();
tmp = "";
if trimmed.len() != 0 {
res.push(trimmed);
}
} else {
tmp += char;
}
}
if tmp.len() != 0 {
res.push(tmp.trim());
}
res
};
let parse_groups(raw) = {
let tmp = "";
let groups = ();
let found_nested = false;
let nested = 0;
for char in raw {
if nested == 2 {
found_nested = true;
}
if char == "[" {
if nested > 0 {
tmp += char;
} else {
groups = join(groups, parse_variants(tmp));
tmp = "";
found_nested = false;
}
nested += 1;
} else if char == "]" {
if nested > 1 {
tmp += char;
} else {
groups.push(if found_nested { parse_groups(tmp) } else { parse_variants(tmp) });
tmp = "";
found_nested = false;
}
nested -= 1;
} else {
tmp += char;
}
}
if tmp.len() != 0 {
groups = join(groups, parse_variants(tmp));
}
groups
};
let parse_nested(grouped) = {
if type(grouped) != array or grouped.len() == 0 {
return grouped;
}
let first = grouped.at(0);
if type(first) == str and first == "dictionary" {
let body = grouped.at(1);
let key = ();
for group in body {
if group == ":" {
break;
}
key.push(group);
}
let val = body.slice(key.len() + 1);
join((("dictionary", parse_nested(key), parse_nested(val)),), parse_nested(grouped.slice(2)))
} else if type(first) == str and first == "array" {
join((("array", parse_nested(grouped.at(1))),), parse_nested(grouped.slice(2)))
} else {
join(parse_nested(first), parse_nested(grouped.slice(1)))
}
};
let grouped = parse_groups(signature);
parse_nested(grouped)
}
#let serialize_type_signature(value) = {
let serialize_type(value, array_serializer, dict_serializer) = {
if type(value) == dictionary {
dict_serializer(value, array_serializer)
} else if type(value) == array {
array_serializer(value)
} else {
str(type(value))
}
}
let serialize_multi_type(values, array_serializer, dict_serializer) = {
let signatures = ().to-dict();
for value in values {
signatures.insert(serialize_type(value, array_serializer, dict_serializer), none);
}
signatures.keys().join(" | ")
}
let serialize_dict_type(dict, array_serializer) = {
(
"dictionary[" +
serialize_multi_type(dict.keys(), array_serializer, serialize_dict_type) +
" : " +
serialize_multi_type(dict.values(), array_serializer, serialize_dict_type) +
"]"
)
}
let serialize_array_type(arr) = {
"array[" + serialize_multi_type(arr, serialize_array_type, serialize_dict_type) + "]"
}
serialize_type(value, serialize_array_type, serialize_dict_type);
}
#let is_subset_of(subset, of) = {
let has_value(value, target, matcher) = {
for target in target {
if matcher(value, target) {
return true;
}
}
false
};
let is_subset(subset, of, matcher) = {
for item in subset {
if not has_value(item, of, matcher) {
return false;
}
}
true
};
let matches(a, b) = {
if type(a) == array {
if a.at(0) != b.at(0) {
return false;
}
let a_type = a.at(0);
if a_type == "dictionary" {
is_subset(a.at(1), b.at(1), matches) and is_subset(a.at(2), b.at(2), matches)
} else if a_type == "array" {
is_subset(a.at(1), b.at(1), matches)
} else {
panic("invalid signature");
}
} else if type(a) != array and type(b) != array {
a == b
} else {
false
}
};
is_subset(subset, of, matches)
}
#let assert_in_arr(needle, arr, item_name) = {
if str(needle) not in arr {
panic(
"unknown " + item_name + " '" + str(needle) +
"', expected one of: " + arr.map((k) => { "'" + str(k) + "'" }).join(", ")
let panic_message = (
"unknown " + item_name + " '" + str(needle) + "', expected one of: " + serialize_array(arr)
);
panic(panic_message);
}
}
@@ -11,6 +200,77 @@
assert_in_arr(needle, dict.keys(), item_name);
}
#let assert_dict_has(needles, dict, item_name) = {
for needle in needles {
if not needle in dict {
let panic_message = item_name + " does not contain an entry for '" + needle + "'";
panic(panic_message);
}
}
}
#let matches_type(value, expected_types) = {
return type(value) in expected_types;
}
#let assert_type(value, expected_types, value_name) = {
if not matches_type(value, expected_types) {
let panic_message = (
"unexpected type for " + value_name + " '" + str(type(value)) + "', expected one of: " +
serialize_array(expected_types)
);
panic(panic_message);
}
}
#let matches_array_type(arr, expected_item_types) = {
for item in arr {
if not matches_type(item, expected_item_types) { return false; }
}
true
}
#let assert_array_type(arr, expected_types, array_name) = {
assert_type(arr, (array), array_name);
for item in arr {
assert_type(item, expected_types, array_name + " item");
}
}
#let matches_dict_type(dict, expected_key_types, expected_value_types) = {
if type(dict) != dictionary {
return false;
}
for (key, value) in dict.pairs() {
if not (matches_type(key, expected_key_types) and matches_type(value, expected_value_types)) {
return false;
}
}
true
}
#let assert_dict_type(dict, expected_key_types, expected_value_types, dict_name) = {
assert_type(dict, (dictionary), dict_name);
for (key, value) in dict.items() {
assert_type(key, expected_key_types, dict_name + " key");
assert_type(value, expected_value_types, dict_name + " value");
}
}
#let assert_type_signature(value, expected_type_signature, value_name) = {
let type_signature = serialize_type_signature(value);
if not is_subset_of(
decompose_type_signature(type_signature),
decompose_type_signature(expected_type_signature)
) {
let panic_message = (
"unexpected " + value_name + " type '" + type_signature +
"' expected at least a subset of '" + expected_type_signature + "'"
);
panic(panic_message);
}
}
#let is_none(thing) = {
if type(thing) == type(none) {
true
@@ -19,6 +279,21 @@
}
}
#let assert_not_none(thing, item_name) = {
if is_none(thing) {
let panic_message = "missing " + item_name;
panic(panic_message);
}
}
#let ok_or(thing, fallback) = {
if is_none(thing) {
fallback
} else {
thing
}
}
#let has_all_none(arr) = {
for item in arr {
if not is_none(item) {
@@ -27,3 +302,10 @@
}
true
}
#let map_none(value, mapper) = {
if is_none(value) {
return none;
}
mapper(value)
}