Merge pull request 'feat!: refactor' (#15) from unexplrd/typst_nure_template:0.1.0 into 0.1.0

Reviewed-on: pencelheimer/typst_nure_template#15
Reviewed-by: Anton Bilous <oxidate@tuta.io>
This commit is contained in:
2026-01-12 00:16:45 +02:00
3 changed files with 154 additions and 157 deletions
+106 -130
View File
@@ -5,6 +5,9 @@
// Template formatting functions {{{1 // Template formatting functions {{{1
/// bold text
#let bold(content) = text(weight: "bold")[#content]
/// numberless heading /// numberless heading
#let nheading(title) = heading(depth: 1, numbering: none, title) #let nheading(title) = heading(depth: 1, numbering: none, title)
@@ -21,9 +24,6 @@
#if align != right { hfill(1fr) } #if align != right { hfill(1fr) }
] ]
/// bold text
#let bold(content) = text(weight: "bold")[#content]
/// month name from its number /// month name from its number
#let month_gen(month) = ( #let month_gen(month) = (
"січня", "січня",
@@ -40,6 +40,27 @@
"грудня", "грудня",
).at(month - 1) ).at(month - 1)
#let is-cyr(c) = regex("[\p{Cyrillic}]") in c
#let gender-form(verb, gender: "p") = {
(
"author": ("m": "Виконав", "f": "Виконала", "p": "Виконали"),
"mentor": ("m": "Перевірив", "f": "Перевірила", "p": "Перевірили"),
)
.at(verb)
.at(if gender == "m" or gender == "f" { gender } else { "p" }, default: "p")
}
#let pz-lb-title(type, number: none) = {
let type-title = (
"ЛБ": [Звіт \ з лабораторної роботи],
"ПЗ": [Звіт \ з практичної роботи],
"КР": [Контрольна робота],
"РФ": [Реферат], // зрада
).at(type, default: type)
if number != none { [#type-title #number] } else { [#type-title] }
}
// Helper functions {{{1 // Helper functions {{{1
/// captioned image with label derived from path: /// captioned image with label derived from path:
@@ -229,8 +250,12 @@
upper(num-to-alpha.at(i)) + numbering(".1.1", ..n) upper(num-to-alpha.at(i)) + numbering(".1.1", ..n)
), ),
) )
set figure(numbering: i => [#upper(num-to-alpha.at(counter(heading).get().at(0))).#i]) set figure(
set math.equation(numbering: i => [(#upper(num-to-alpha.at(counter(heading).get().at(0))).#i)]) numbering: i => [#upper(num-to-alpha.at(counter(heading).get().at(0))).#i],
)
set math.equation(
numbering: i => [(#upper(num-to-alpha.at(counter(heading).get().at(0))).#i)],
)
// Heading supplement (Heading name shown when citing with @ref) // Heading supplement (Heading name shown when citing with @ref)
set heading(supplement: [Додаток]) set heading(supplement: [Додаток])
@@ -260,7 +285,6 @@
it it
} // }}} } // }}}
// Coursework template {{{1 // Coursework template {{{1
/// DSTU 3008:2015 Template for NURE /// DSTU 3008:2015 Template for NURE
@@ -270,7 +294,6 @@
/// - subject (str): Subject short name. /// - subject (str): Subject short name.
/// - authors ((name: str, full_name_gen: str, variant: int, course: int, semester: int, group: str, gender: str),): List of authors. /// - authors ((name: str, full_name_gen: str, variant: int, course: int, semester: int, group: str, gender: str),): List of authors.
/// - mentors ((name: str, degree: str),): List of mentors. /// - mentors ((name: str, degree: str),): List of mentors.
/// - edu_program (str): Education program shorthand.
/// - task_list (done_date: datetime, initial_date: datetime, source: (content | str), content: (content | str), graphics: (content | str)): Task list object. /// - task_list (done_date: datetime, initial_date: datetime, source: (content | str), content: (content | str), graphics: (content | str)): Task list object.
/// - calendar_plan ( plan_table: (content | str), approval_date: datetime): Calendar plan object. /// - calendar_plan ( plan_table: (content | str), approval_date: datetime): Calendar plan object.
/// - abstract (keywords: (str, ), text: (content | str)): Abstract object. /// - abstract (keywords: (str, ), text: (content | str)): Abstract object.
@@ -278,19 +301,18 @@
/// - appendices (content): Content with appendices. /// - appendices (content): Content with appendices.
#let coursework( #let coursework(
doc, doc,
title: none,
subject: none,
university: "ХНУРЕ", university: "ХНУРЕ",
author: (), subject: none,
title: none,
authors: (),
mentors: (), mentors: (),
edu_program: none,
task_list: (), task_list: (),
calendar_plan: (), calendar_plan: (),
abstract: (), abstract: (),
bib_path: none, bib_path: none,
appendices: (), appendices: (),
) = { ) = {
set document(title: title, author: author.name) set document(title: title, author: authors.at(0).name)
show: dstu-style.with(skip: 1) show: dstu-style.with(skip: 1)
@@ -304,10 +326,10 @@
it it
} }
let author = authors.at(0)
let head_mentor = mentors.at(0) let head_mentor = mentors.at(0)
let uni = universities.at(university) let uni = universities.at(university)
let edu_prog = uni.edu_programs.at(edu_program) let edu_prog = uni.edu_programs.at(author.edu_program)
// page 1 {{{2 // page 1 {{{2
[ [
@@ -323,15 +345,16 @@
ПОЯСНЮВАЛЬНА ЗАПИСКА\ ПОЯСНЮВАЛЬНА ЗАПИСКА\
ДО КУРСОВОЇ РОБОТИ\ ДО КУРСОВОЇ РОБОТИ\
з дисципліни: "#uni.subjects.at(subject, default: "NONE")"\ з дисципліни: "#uni.subjects.at(subject, default: subject)"\
Тема роботи: "#title" Тема роботи: "#title"
\ \ \ \ \ \
#columns(2, gutter: 4cm)[ #columns(2, gutter: 4cm)[
#set align(left) #set align(left)
#set par(first-line-indent: 0pt)
#if author.gender == "m" { [Виконав\ ] } else { [Виконала\ ] } ст. гр. #edu_program\-#author.group #gender-form("author", gender: author.gender) ст. гр. #author.edu_program\-#author.group
\ \
Керівник:\ Керівник:\
@@ -342,15 +365,12 @@
\ \
Комісія:\ Комісія:\
#for mentor in mentors { #for m in mentors { [#m.degree\ ] }
[#mentor.degree\
]
}
#colbreak() #colbreak()
#set align(left) #set align(left)
\
#author.name #author.name
\ \ \ \
@@ -360,10 +380,7 @@
#underline(" " * 35) #underline(" " * 35)
\ \ \ \
#for mentor in mentors { #for m in mentors { [#m.name\ ] }
[#mentor.name\
]
}
] ]
#v(1fr) #v(1fr)
@@ -390,7 +407,7 @@
{ {
uline(align: left, edu_prog.department_gen) uline(align: left, edu_prog.department_gen)
linebreak() linebreak()
uline(align: left, uni.subjects.at(subject)) uline(align: left, uni.subjects.at(subject, default: subject))
linebreak() linebreak()
uline(align: left, [#edu_prog.code #edu_prog.name_long]) uline(align: left, [#edu_prog.code #edu_prog.name_long])
}, },
@@ -399,7 +416,7 @@
columns: (1fr, 1fr, 1fr), columns: (1fr, 1fr, 1fr),
gutter: 0.3fr, gutter: 0.3fr,
[#bold[Курс] #uline(author.course)], [#bold[Курс] #uline(author.course)],
[#bold[Група] #uline([#edu_program\-#author.group])], [#bold[Група] #uline([#author.edu_program\-#author.group])],
[#bold[Семестр] #uline(author.semester)], [#bold[Семестр] #uline(author.semester)],
) )
@@ -519,31 +536,16 @@
\ \
#{ #(
let keywords = abstract.keywords.map(upper) abstract
let is_cyrillic = word => word.split("").any(char => ("А" <= char and char <= "я")) .keywords
.map(upper)
.sorted(by: (a, b) => {
if is-cyr(a) != is-cyr(b) { true } else { a < b }
})
.join(", ")
)
let n = keywords.len()
for i in range(n) {
for j in range(0, n - i - 1) {
if (
(
not is_cyrillic(keywords.at(j)) and is_cyrillic(keywords.at(j + 1))
)
or (
is_cyrillic(keywords.at(j)) == is_cyrillic(keywords.at(j + 1)) and keywords.at(j) > keywords.at(j + 1)
)
) {
(keywords.at(j), keywords.at(j + 1)) = (
keywords.at(j + 1),
keywords.at(j),
)
}
}
}
keywords.join(", ")
}
\ \
@@ -575,26 +577,18 @@
let bib_data = yaml(bib_path) let bib_data = yaml(bib_path)
let format-entry(citation) = { let format-entry(c) = {
if (citation.type == "Web") { if (c.type == "Web") {
let date_array = citation.url.date.split("-") let date_array = c.url.date.split("-")
let date = datetime( let date = datetime(
year: int(date_array.at(0)), year: int(date_array.at(0)),
month: int(date_array.at(1)), month: int(date_array.at(1)),
day: int(date_array.at(2)), day: int(date_array.at(2)),
) )
[ [#c.title. #c.author. URL: #c.url.value (дата звернення: #date.display("[day].[month].[year]")).]
#citation.title. } else if (
#citation.author. c.type == "Book"
URL: #citation.url.value (дата звернення: #date.display("[day].[month].[year]")). ) [#c.author #c.title. #c.publisher, #c.date. #c.page-total c. ] else [
]
} else if citation.type == "Book" [
#citation.author
#citation.title.
#citation.publisher,
#citation.date.
#citation.page-total c.
] else [
UNSUPPORTED BIBLIOGRAPHY ENTRY TYPE, PLEASE OPEN AN ISSUE UNSUPPORTED BIBLIOGRAPHY ENTRY TYPE, PLEASE OPEN AN ISSUE
] ]
} }
@@ -608,7 +602,10 @@
} }
context { context {
for (i, citation) in query(ref.where(element: none)).map(r => str(r.target)).dedup().enumerate() { for (i, citation) in query(ref.where(element: none))
.map(r => str(r.target))
.dedup()
.enumerate() {
enum.item( enum.item(
i + 1, i + 1,
format-entry(bib_data.at(citation)), format-entry(bib_data.at(citation)),
@@ -625,103 +622,74 @@
/// DSTU 3008:2015 Template for NURE /// DSTU 3008:2015 Template for NURE
/// -> content /// -> content
/// - doc (content): Content to apply the template to. /// - doc (content): Content to apply the template to.
/// - edu_program (str): Education program shorthand. /// - university: "ХНУРЕ": University metadata. Optional.
/// - doctype ("ЛБ" | "ПЗ" | str): Document type. /// - subject: str: Subject shortcode.
/// - title (str or none): Title of the document. Optional. /// - type: ("ЛБ" | "ПЗ" | "КР" | "РФ" | str): Work type.
/// - subject (str): Subject shorthand. /// - number: int or none: Work number. Optional.
/// - worknumber (int or none): Number of the work. Optional. /// - title: str or none: Work title. Optional.
/// - authors ((name: str, full_name_gen: str, group: str, gender: str, variant: int or none),): List of authors. /// - authors ((name: str, full_name_gen: str or none, edu_program: str, group: str, gender: str, variant: int or none),): List of authors.
/// - mentors ((name: str, degree: str, gender: str or none),): List of mentors. Optional. /// - mentors ((name: str, degree: str, gender: ("m" | "f" | "p" | none)),): List of mentors. Optional.
#let pz-lb( #let pz-lb(
doc, doc,
university: "ХНУРЕ", university: "ХНУРЕ",
edu_program: none,
doctype: none,
title: none,
subject: none, subject: none,
worknumber: none, type: none,
number: none,
title: none,
authors: (), authors: (),
mentors: (), mentors: (),
) = { ) = {
assert.ne(edu_program, none, message: "Missing argument: \"edu_program\"") // TODO: add actually relevant asserts
assert.ne(doctype, none, message: "Missing argument: \"doctype\"")
assert.ne(subject, none, message: "Missing argument: \"subject\"") let edu_program = authors.at(0).edu_program
let uni = universities.at(university)
set document(title: title, author: authors.at(0).name) set document(title: title, author: authors.at(0).name)
show: dstu-style.with(skip: 1) show: dstu-style.with(skip: 1)
let uni = universities.at(university)
let edu_prog = uni.edu_programs.at(edu_program)
// page 1 {{{2 // page 1 {{{2
align(center)[ align(center)[
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ \ МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ \
#upper(uni.name) #upper(uni.name)
\ \ \ \
Кафедра #edu_prog.department_gen Кафедра #uni.edu_programs.at(edu_program).department_gen
\ \ \ \ \ \
#if doctype == "ЛБ" [Звіт \ з лабораторної роботи] else if ( #pz-lb-title(type, number: number)
doctype == "ПЗ"
) [Звіт \ з практичної роботи] else [#doctype]
#if worknumber != none {
context counter(heading).update(worknumber - if title == none { 0 } else { 1 })
[№#worknumber]
} else if title != none and worknumber != none {
context counter(heading).update(1)
}
з дисципліни: "#uni.subjects.at(subject)" з дисципліни: "#uni.subjects.at(subject, default: subject)"
#if title != none [\ з теми: "#eval(title, mode: "markup")"]
#if title != none [з теми: "#eval(title, mode: "markup")"]
\ \ \ \ \ \ \ \
#columns(2)[ #columns(2)[
#set align(left) #set align(left)
#set par(first-line-indent: 0pt) #set par(first-line-indent: 0pt)
#if authors.len() == 1 { #if authors.len() == 1 {
let author = authors.at(0) let a = authors.at(0)
if author.gender == "m" [Виконав:\ ] else [Виконала:\ ] [#gender-form("author", gender: if "gender" in a.keys() { a.gender } else { none }):\ ]
[ [ст. гр. #a.edu_program\-#a.group\ #a.name\ ]
ст. гр. #edu_program\-#author.group\ if a.variant != none [Варіант: #a.variant]
#author.name\
]
if (
"variant" in author.keys() and author.variant != none
) [Варіант: #author.variant]
} else if authors.len() > 1 [ } else if authors.len() > 1 [
Виконали:\ #gender-form("author"):\
ст. гр. #edu_program\-#authors.at(0).group\ #for a in authors [ст. гр. #a.edu_program\-#a.group\ #a.name\ ]
#for author in authors [#author.name\ ]
] ]
#colbreak() #colbreak()
#set align(right) #set align(right)
#if type(mentors) == array { #if mentors.len() == 1 {
if mentors.len() == 1 { let m = mentors.at(0)
let mentor = mentors.at(0) [#gender-form("mentor", gender: if "gender" in m.keys() { m.gender } else { none }):\ ]
if "gender" in mentor.keys() { if "degree" in m.keys() and m.degree != none [#m.degree\ ]
if mentor.gender == "m" [Перевірив:\ ] else if ( [#m.name\ ]
mentor.gender == "f"
) [Перевірила:\ ]
} else [Перевірили:\ ]
if (
"degree" in mentor.keys() and mentor.degree != none
) [#mentor.degree\ ]
[#mentor.name\ ]
} else if mentors.len() > 1 [ } else if mentors.len() > 1 [
Перевірили:\ #gender-form("mentor"):\
#for mentor in mentors { #for mentor in mentors { [#mentor.degree\ #mentor.name\ ] }]
[
#mentor.degree\
#mentor.name\
]
}
]
}
] ]
#v(1fr) #v(1fr)
@@ -731,7 +699,15 @@
pagebreak(weak: true) pagebreak(weak: true)
if title != none [#heading(eval(title, mode: "markup"))] // TODO(unexplrd): wrap my head around the old way
if title == none {
if number == none { context counter(heading).update(1) } else {
context counter(heading).update(number)
}
} else {
if number != none { context counter(heading).update(number - 1) }
heading(eval(title, mode: "markup"))
}
doc doc
} }
+34 -12
View File
@@ -1,13 +1,16 @@
#import "@local/nure:0.1.0": * #import "lib.typ": *
#let author = ( #let authors = (
(
name: "Ситник Є. С.", name: "Ситник Є. С.",
full_name_gen: "Ситника Єгора Сергійовича", full_name_gen: "Ситника Єгора Сергійовича",
edu_program: "ПЗПІ",
group: "23-2",
gender: "m",
course: 2, course: 2,
semester: 3, semester: 3,
variant: 13, variant: 13,
group: "23-2", )
gender: "m",
) )
#let mentors = ( #let mentors = (
@@ -28,19 +31,39 @@
plan_table: table( plan_table: table(
columns: 4, columns: 4,
align: (center, left, center, center), align: (center, left, center, center),
[Номер], [Назва етапів курсової роботи], [Строк виконання етапів роботи], [Примітки], [Номер],
[Назва етапів курсової роботи],
[Строк виконання етапів роботи],
[Примітки],
[1], [Аналіз предметної області], [15.09.24 24.09.24], [Виконано], [1], [Аналіз предметної області], [15.09.24 24.09.24], [Виконано],
[2], [Концептуальне моделювання], [24.09.24-30.09.24], [~], [2], [Концептуальне моделювання], [24.09.24-30.09.24], [~],
[2], [Постановка задачі], [28.09.24 2.10.24], [Виконано], [2], [Постановка задачі], [28.09.24 2.10.24], [Виконано],
[3], [Побудова ER-діаграми та схеми БД], [2.10.24 18.10.24], [Виконано], [3], [Побудова ER-діаграми та схеми БД], [2.10.24 18.10.24], [Виконано],
[4], [Оформлення розділів 1, 2 та 3.1, 3.2 пояснювальної записки], [10.10.24 - 18.10.24], [Виконано], [4],
[Оформлення розділів 1, 2 та 3.1, 3.2 пояснювальної записки],
[10.10.24 - 18.10.24],
[Виконано],
[5], [Перша контрольна точка з курсової роботи], [20.10.24], [Виконано], [5], [Перша контрольна точка з курсової роботи], [20.10.24], [Виконано],
[6], [Нормалізація бази даних], [20.10.24 - 15.11.24], [Виконано], [6], [Нормалізація бази даних], [20.10.24 - 15.11.24], [Виконано],
[7], [Створення програми], [20.10.24 20.11.24], [Виконано], [7], [Створення програми], [20.10.24 20.11.24], [Виконано],
[8], [Тестування програми, наповнення бази даних], [20.11.24 - 5.12.24], [Виконано], [8],
[Тестування програми, наповнення бази даних],
[20.11.24 - 5.12.24],
[Виконано],
[9], [Друга контрольна точка з курсової роботи], [7.12.24], [Виконано], [9], [Друга контрольна точка з курсової роботи], [7.12.24], [Виконано],
[10], [Реалізація остаточної версії програми], [7.12.24-15.12.24], [Виконано], [10],
[11], [Оформлення інших розділів пояснювальної записки], [1.11.24 25.12.24], [Виконано], [Реалізація остаточної версії програми],
[7.12.24-15.12.24],
[Виконано],
[11],
[Оформлення інших розділів пояснювальної записки],
[1.11.24 25.12.24],
[Виконано],
[12], [Третя контрольна точка з курсової роботи], [27.12.24], [Виконано], [12], [Третя контрольна точка з курсової роботи], [27.12.24], [Виконано],
), ),
approval_date: datetime(year: 2024, month: 12, day: 27), approval_date: datetime(year: 2024, month: 12, day: 27),
@@ -100,13 +123,12 @@
#show: coursework.with( #show: coursework.with(
title: "Інформаційна система «Помічник класного керівника». Керування класом", title: "Інформаційна система «Помічник класного керівника». Керування класом",
subject: "БД", subject: "БД",
edu_program: "ПЗПІ", authors: authors,
author: author,
mentors: mentors, mentors: mentors,
task_list: task_list, task_list: task_list,
calendar_plan: calendar_plan, calendar_plan: calendar_plan,
abstract: abstract, abstract: abstract,
bib_path: bytes("bibl.yml"), // NOTE: use `bytes` as typst looks in template dir when using just filename bib_path: "bibl.yml", // NOTE: use `bytes("bibl.yml")` as typst looks in template dir when using just filename
appendices: appendices, appendices: appendices,
) )
+8 -9
View File
@@ -1,24 +1,23 @@
#import "@local/nure:0.1.0": * #import "lib.typ": *
#show: pz-lb.with( #show: pz-lb.with(
title: "Потiк керування та алгоритмічні структури Bash", university: "ХНУРЕ",
subject: "СМП", subject: "СМП",
doctype: "ЛБ", type: "ЛБ",
worknumber: 2, number: 2,
title: "Потiк керування та алгоритмічні структури Bash",
mentors: ( mentors: (
(name: "Шевченко Т. Г.", degree: "Доцент кафедри ПІ", gender: "m"), (name: "Шевченко Т. Г.", degree: "Доцент кафедри ПІ", gender: "m"),
(name: "Франко І. Я.", degree: "Асистент кафедри ПІ", gender: "m"), (name: "Франко І. Я.", degree: "Асистент кафедри ПІ", gender: "m"),
), ),
edu_program: "ПЗПІ",
university: "ХНУРЕ",
authors: ( authors: (
( (
name: "Косач Л. П.", name: "Косач Л. П.",
full_name_gen: "Косач Лариси Петрівни", full_name_gen: "Косач Лариси Петрівни",
course: 2, edu_program: "ПЗПІ",
edu: "ПЗПІ",
gender: "f",
group: "23-2", group: "23-2",
gender: "f",
course: 2,
semester: 4, semester: 4,
variant: 8, variant: 8,
), ),