20 Commits

Author SHA1 Message Date
1d5ffd045c fix: typo and other 2026-01-29 18:54:32 +02:00
a77366239b feat: add custom CSL for bibliography 2026-01-29 18:02:12 +02:00
be23837699 1769603703 2026-01-28 14:35:09 +02:00
f2c10c10bd 1769259968 2026-01-24 15:06:08 +02:00
173acfc35a 1769259936 2026-01-24 15:05:36 +02:00
efd9d2c70c add img to utils.typ 2026-01-24 15:03:23 +02:00
f897c6dca0 update readme 2026-01-24 15:01:44 +02:00
8160fa8f10 1769258864 2026-01-24 14:47:44 +02:00
c4e4bbf093 helpers, fixes 2026-01-24 14:45:17 +02:00
3e55bb39a2 1769194824 2026-01-23 21:00:24 +02:00
8b86abb244 1769194548 2026-01-23 20:55:48 +02:00
8ead3f8ebb fixes 2026-01-22 18:07:16 +02:00
ae6bce387c split up again, with some cool stuff 2026-01-22 17:39:47 +02:00
e643c313ba merge back into lib.typ 2026-01-18 01:21:27 +02:00
3865c00ad9 move to dictionary.at(layout) 2026-01-17 18:22:47 +02:00
fa76185b7f add complex page 2026-01-17 17:54:11 +02:00
891d33b236 move lib.typ to src/ 2026-01-17 17:24:32 +02:00
f3adc98f86 rename 2026-01-17 16:41:05 +02:00
7bb0925662 i think we need edu_program for authorless jobs 2026-01-17 16:25:44 +02:00
3cde131ed6 WIP!: break up lib.typ 2026-01-17 15:45:36 +02:00
19 changed files with 376 additions and 1036 deletions

View File

@@ -34,11 +34,11 @@ This template:
### As a local typst package ### As a local typst package
1. Clone this repository into ~/.local/share/typst/packages/: 1. Clone this repository into ~/.local/share/typst/packages/:
```bash ```bash
git clone -b 0.1.1 https://gitea.linerds.us/pencelheimer/typst_nure_template.git ~/.local/share/typst/packages/local/nure/0.1.1 git clone -b 0.1.0 https://gitea.linerds.us/pencelheimer/typst_nure_template.git ~/.local/share/typst/packages/local/nure/0.1.0
``` ```
2. Init your project with Typst: 2. Init your project with Typst:
```bash ```bash
typst init @local/nure:0.1.1 project-name typst init @local/nure:0.1.0 project-name
``` ```
### As a standalone file ### As a standalone file
@@ -47,7 +47,7 @@ Copy `src/` to your project's root directory, optionally renaming `src/` to `lib
### In your project ### In your project
```typst ```typst
// Import the template either from a local package... // Import the template either from a local package...
#import "@local/nure:0.1.1": * #import "@local/nure:0.1.0": *
// ...or by importing a lib.typ directly // ...or by importing a lib.typ directly
// #import "/lib/lib.typ": * // #import "/lib/lib.typ": *
@@ -91,7 +91,7 @@ Some text
And a TOML file would look like this: And a TOML file would look like this:
```toml ```toml
# university = "ХНУРЕ" # "ХНУРЕ" is the default # university = "ХНУРЕ" # "ХНУРЕ" is the default
# edu-program = "ПЗПІ" # can be null, sourced from authors.first() by default # edu_program = "ПЗПІ" # can be null, sourced from authors.first() by default
subject = "СМП" subject = "СМП"
doctype = "ЛБ" doctype = "ЛБ"
@@ -110,19 +110,19 @@ gender = "m"
[[authors]] [[authors]]
name = "Косач Л. П." name = "Косач Л. П."
edu-program = "ПЗПІ" edu_program = "ПЗПІ"
group = "23-2" group = "23-2"
gender = "f" gender = "f"
variant = 8 variant = 8
# For coursework # For coursework
full-name-gen = "Косач Лариси Петрівни" full_name_gen = "Косач Лариси Петрівни"
course = 2 course = 2
semester = 4 semester = 4
``` ```
### Notes: ### Notes:
1. Use `#v(-spacing)` to remove vertical spacing between titles (this cannot be automatically handled by the template). Variable `spacing` used here is imported from the template. 1. Use `#v(-spacing)` to remove vertical spacing between titles (this cannot be automatically handled by the template). Variable `spacing` used here is imported from the template.
2. When importing `@local/nure:0.1.1` and specifying file paths in functions handled by the package, the path will relative to package's root directory, e.g. setting `#show: coursework.with(bib-path: "bibl.yml")` will evaluate to `~/.local/share/typst/packages/local/nure/0.1.1/bibl.yml`, the same is for `#img` function, which makes it quite annoying and forces one to import `lib.typ` file. Please open an issue or contact us in any other way if you have any advice. 2. When importing `@local/nure:0.1.0` and specifying file paths in functions handled by the package, the path will relative to package's root directory, e.g. setting `#show: coursework.with(bib_path: "bibl.yml")` will evaluate to `~/.local/share/typst/packages/local/nure/0.1.0/bibl.yml`, the same is for `#img` function, which makes it quite annoying and forces one to import `lib.typ` file. Please open an issue or contact us in any other way if you have any advice.
### Bibliography Format ### Bibliography Format
The template uses a custom CSL (Citation Style Language) file located at `src/csl/dstu-3008-2015.csl` to format bibliography entries. The template uses a custom CSL (Citation Style Language) file located at `src/csl/dstu-3008-2015.csl` to format bibliography entries.

View File

@@ -1,77 +0,0 @@
ХНУРЕ:
name: Харківський національний університет радіоелектроніки
name_en: Kharkiv National University of Radioelectronics
edu_programs:
ПЗПІ:
name_long: Інженерія програмного забезпечення
department_gen: Програмної інженерії
code: 121 # TODO: change to F2?
КУІБ:
name_long: Управління інформаційною безпекою
department_gen: Інфокомунікаційної інженерії ім. В. В. Поповського
code: 125 # TODO: change to F5?
КНТ:
name_en: CST # computer sciences and technologies
name_long: Комп'ютерні науки та технології
department_gen: Системотехніки
department_en: ST
code: 122
subjects:
DMT: Decision making theory
ODS: Основи Dаtа Sсіеnсе # NOTE: Eng O here
ІМ: Іноземна мова
ІТР: Information Technologies of Reengineering
ІТРОІ: Інтернет-технології Розподіленої Обробки Інформації
АВпЗ: Аналіз вимог до програмного забезпечення
АДан: Аналітика даних
АКтаК: Архітектура комп'ютера та комп'ютерних мереж
АТСД: Алгоритми та структури даних
АтаРК: Аналіз та рефакторинг коду
БД: Бази даних
БЖД: Безпека життєдіяльності
ВДІТБ: Введення до ІТ-бізнесу # NOTE: all in UA
ВМ: Вища математика
ГТГ: Гіпертекст та гіпермедіа
ДМ: Дискретна математика
ДПК: Динаміка Проектних Команд
ЕРВ: Електрорадіовимірювання
КДМА: Комп'ютерна дискретна математика
КЗВШ: Креативність з використанням штучного інтелекту
КМ: Комп`ютерні мережі
ЛМВ: Людино-машинна взаємодія
ЛМтБ: Локальні мережі та їх безпека # бидло не знає що українською "їхня"
МОКр: Математичні основи криптології
МОТДО: Методи оптимізаціі та дослідження операцій
МППС: Methodologies of designing software systems
МС: Моделювання систем
ОІМ: Основи IP-мереж
ООАПС: Об'єктно-орієнтований аналіз в проектуванні систем
ООП: Об'єктно-орієнтоване програмування
ОП: Основи права
ОПІ: Основи програмноі інженеріі
ОПНJ: Основи програмування на Java
ОПр: Основи програмування
ОРвІТ: Оцінка Ризиків в IT-проектах
ОС: Операційні системи
ОТК: Основи теорії кіл
ПБІП: Проектування та балансування ігрового процесу
ПВJ: Поглиблене вивчення Java
ПЕСЕ: Психологія екстремальних стосунків та ефективної адаптації
ПНП: Програмування на платформі .NЕТ
ПП: Проектний практикум
ПРОГ: Програмування
ПарП: Параллельне програмування
СА: Системний аналіз
СМП: Скриптові мови програмування
СОАПЗ: Сервіс-Орієнтована Архітектура Програмного Забезпечення
СРБД: Серверні рішення баз даних
СхТ: Схемотехніка
ТВО: Технології Високопродуктивних Обчислень
ТЗІ: Технології захисту інформації
ТЙтаМ: Теорія ймовірностей та математична # TODO: what?
ТКП: Технології комп`ютерного проєктування
УФМ: Українське фахове мовлення
ФІЗ: Фізика
ФІЛ: Філософія
ФВС: Фізичне виховання та спорт
ХТ: Хмарні технології

701
lib.typ
View File

@@ -1,701 +0,0 @@
// Academic aliases {{{1
#let universities = yaml("config/universities.yaml")
// Template formatting functions {{{1
/// bold text
#let bold(content) = text(weight: "bold")[#content]
/// numberless heading
#let nheading(title) = heading(depth: 1, numbering: none, title)
/// fill horizontal space with a box and not an empty space
#let hfill(width) = box(
width: width,
repeat(""),
) // NOTE: This is a HAIR SPACE (U+200A), not a regular space
/// make underlined cell with filled value
#let uline(align: center, content) = underline[
#if align != left { hfill(1fr) }
#content
#if align != right { hfill(1fr) }
]
/// month name from its number
#let month_gen(month) = (
"січня",
"лютого",
"березня",
"квітня",
"травня",
"червня",
"липня",
"серпня",
"вересня",
"жовтня",
"листопада",
"грудня",
).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
/// captioned image with label derived from path:
/// - "image.png" = @image
/// - "img/image.png" = @image
/// - "img/foo/image.png" = @foo_image
/// - "img/foo/foo_image.png" = @foo_image
/// the caption will be modified based on a conditional positional value:
/// - `none`: no change
/// - some value: "`caption` (за даними `value`)"
/// - no value: "`caption` (рисунок виконано самостійно)"
/// additional named arguments will be passed to original `image` function
#let img(path, caption, ..sink) = {
let parts = path.split(".").first().split("/")
let label_string = if (
parts.len() <= 2 or parts.at(-1).starts-with(parts.at(-2))
) {
// ("image",), (_, "image") and (.., "img", "img_image")
parts.last()
} else {
// (.., "img", "image") = "img_image"
parts.at(-2) + "_" + parts.at(-1)
}.replace(" ", "_")
let caption = if sink.pos().len() == 0 {
caption
} else if sink.pos().first() == none {
caption + " (рисунок виконано самостійно)"
} else {
[#caption (за даними #sink.pos().first())]
}
[#figure(
image(path, ..sink.named()),
caption: caption,
) #label(label_string)]
}
#let spacing = 0.95em // spacing between lines
#let num-to-alpha = "абвгдежиклмнпрстуфхцшщюя".split("") // 0 = "", 1 = "а"
/// DSTU 3008:2015 Style
/// -> content
/// - it (content): Content to apply the style to.
/// - skip (int): Do not show page number for this number of pages.
/// - offset (int): Adjust all page numbers by this amount.
#let dstu-style(
it,
skip: 0,
offset: 0,
) = {
// General Styling {{{1
set page(
paper: "a4",
number-align: top + right,
margin: (top: 20mm, right: 10mm, bottom: 20mm, left: 25mm),
numbering: (i, ..) => if i > skip { numbering("1", i + offset) },
)
set text(
lang: "uk",
size: 14pt,
hyphenate: false,
font: ("Times New Roman", "Liberation Serif"),
)
set par(
justify: true,
spacing: spacing,
leading: spacing,
first-line-indent: (amount: 1.25cm, all: true),
)
set block(spacing: spacing)
set underline(evade: false)
// Enums & Lists {{{1
// First level
set enum(
indent: 1.25cm,
body-indent: 0.5cm,
numbering: i => { num-to-alpha.at(i) + ")" },
)
// Second level and further nesting
show enum: it => {
set enum(indent: 0em, numbering: "1)")
it
}
// Lists are not intended for multiple levels, use `enum`
set list(indent: 1.35cm, body-indent: 0.5cm, marker: [--])
// Figures {{{1
show figure: it => {
v(spacing * 2, weak: true)
it
v(spacing * 2, weak: true)
}
set figure.caption(separator: [ -- ])
show figure.where(kind: table): set figure.caption(position: top)
show figure.caption.where(kind: table): set align(left)
show figure.where(kind: raw): set figure.caption(position: top)
show figure.where(kind: raw): set align(left)
// Numbering {{{1
show heading.where(level: 1): it => {
counter(math.equation).update(0)
counter(figure.where(kind: raw)).update(0)
counter(figure.where(kind: image)).update(0)
counter(figure.where(kind: table)).update(0)
it
}
set figure(numbering: i => numbering("1.1", counter(heading).get().at(0), i))
set math.equation(numbering: i => numbering("(1.1)", counter(heading).get().at(0), i))
// Headings {{{1
set heading(numbering: "1.1")
show heading: it => if it.level == 1 {
set align(center)
set text(size: 14pt, weight: "semibold")
pagebreak(weak: true)
upper(it)
v(spacing * 2, weak: true)
} else {
set text(size: 14pt, weight: "regular")
v(spacing * 2, weak: true)
block(width: 100%, spacing: 0em)[
#h(1.25cm)
#counter(heading).display(auto)
#it.body
]
v(spacing * 2, weak: true)
}
show heading.where(level: 3): it => {
set text(size: 14pt, weight: "regular")
v(spacing * 2, weak: true)
block(width: 100%, spacing: 0em)[
#h(1.25cm)
#counter(heading).display(it.numbering)
#it.body
]
v(spacing * 2, weak: true)
}
// listings {{{3
show raw.where(block: true): it => {
let new_spacing = 0.5em
set block(spacing: new_spacing)
set par(spacing: new_spacing, leading: new_spacing)
set text(
size: 11pt,
weight: "semibold",
font: ("Courier New", "Liberation Mono"),
)
v(spacing * 2.5, weak: true)
pad(it, left: 1.25cm)
v(spacing * 2.5, weak: true)
}
it
// }}}
}
/// DSTU 3008:2015 Appendices Style
/// -> content
/// - it (content): Content to apply the style to.
#let appendices-style(it) = /* {{{ */ {
// Numbering
counter(heading).update(0)
set heading(numbering: (i, ..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 math.equation(numbering: i => upper(num-to-alpha.at(counter(heading).get().at(0))).i)
// Heading supplement (Heading name shown when citing with @ref)
set heading(supplement: [Додаток])
// Headings
show heading: it => if it.level == 1 {
set align(center)
set text(size: 14pt, weight: "regular")
pagebreak(weak: true)
bold([ДОДАТОК #counter(heading).display(auto)])
linebreak()
it.body
v(spacing * 2, weak: true)
} else {
set text(size: 14pt, weight: "regular")
v(spacing * 2, weak: true)
block(width: 100%, spacing: 0em)[
#h(1.25cm)
#counter(heading).display(auto)
#it.body
]
v(spacing * 2, weak: true)
}
it
} // }}}
// Coursework template {{{1
/// DSTU 3008:2015 Template for NURE
/// -> content
/// - doc (content): Content to apply the template to.
/// - title (str): Title of the document.
/// - 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.
/// - mentors ((name: str, degree: str),): List of mentors.
/// - 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.
/// - abstract (keywords: (str, ), text: (content | str)): Abstract object.
/// - bib_path path: Path to the bibliography yaml file.
/// - appendices (content): Content with appendices.
#let coursework(
doc,
university: "ХНУРЕ",
subject: none,
title: none,
authors: (),
mentors: (),
task_list: (),
calendar_plan: (),
abstract: (),
bib_path: none,
appendices: (),
) = {
set document(title: title, author: authors.map(c => c.name))
show: dstu-style.with(skip: 1)
let bib-count = state("citation-counter", ())
show cite: it => {
it
bib-count.update(((..c)) => (..c, it.key))
}
show bibliography: it => {
set text(size: 0pt)
it
}
let author = authors.first()
let head_mentor = mentors.first()
let uni = universities.at(university)
let edu_prog = uni.edu_programs.at(author.edu_program)
// page 1 {{{2
[
#set align(center)
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ\
#upper(uni.name)
\
Кафедра #edu_prog.department_gen
\
ПОЯСНЮВАЛЬНА ЗАПИСКА\
ДО КУРСОВОЇ РОБОТИ\
з дисципліни: "#uni.subjects.at(subject, default: subject)"\
Тема роботи: "#title"
\ \ \
#columns(2, gutter: 4cm)[
#set align(left)
#set par(first-line-indent: 0pt)
#gender-form("author", gender: author.gender) ст. гр. #author.edu_program\-#author.group
\
Керівник:\
#head_mentor.degree
\
Робота захищена на оцінку
\
Комісія:\
#for m in mentors { [#m.degree\ ] }
#colbreak()
#set align(left)
#author.name
\ \
#head_mentor.name
\
#underline(" " * 35)
\ \
#for m in mentors { [#m.name\ ] }
]
#v(1fr)
Харків -- #task_list.done_date.display("[year]")
#pagebreak()
]
// page 2 {{{2
{
uline[#uni.name]
linebreak()
linebreak()
grid(
columns: (100pt, 1fr),
bold[
Кафедра
Дисципліна
Спеціальність
],
{
uline(align: left, edu_prog.department_gen)
linebreak()
uline(align: left, uni.subjects.at(subject, default: subject))
linebreak()
uline(align: left, [#edu_prog.code #edu_prog.name_long])
},
)
grid(
columns: (1fr, 1fr, 1fr),
gutter: 0.3fr,
[#bold[Курс] #uline(author.course)],
[#bold[Група] #uline([#author.edu_program\-#author.group])],
[#bold[Семестр] #uline(author.semester)],
)
linebreak()
linebreak()
linebreak()
align(center, bold[ЗАВДАННЯ \ на курсову роботу студента])
linebreak()
uline(align: left)[_#author.full_name_gen _]
linebreak()
linebreak()
bold[\1. Тема роботи:]
uline[#title.]
linebreak()
{
bold[\2. Строк здачі закінченої роботи:]
uline(task_list.done_date.display("[day].[month].[year]"))
hfill(10fr)
}
linebreak()
bold[\3. Вихідні дані для роботи:]
uline(task_list.source)
linebreak()
bold[\4. Зміст розрахунково-пояснювальної записки:]
uline(task_list.content)
linebreak()
bold[\5. Перелік графічного матеріалу:]
uline(task_list.graphics)
linebreak()
{
bold[\6. Дата видачі завдання:]
uline(task_list.initial_date.display("[day].[month].[year]"))
hfill(10fr)
}
pagebreak()
}
// page 3 {{{2
{
align(center, bold[КАЛЕНДАРНИЙ ПЛАН])
set par(first-line-indent: 0pt)
linebreak()
calendar_plan.plan_table
linebreak()
grid(
columns: (5fr, 5fr),
grid(
columns: (1fr, 2fr, 1fr),
gutter: 0.2fr,
[
Студент \
Керівник \
#align(center)["#underline[#calendar_plan.approval_date.day()]"]
],
[
#uline(align: center, []) \
#uline(align: center, []) \
#uline(align: center, month_gen(calendar_plan.approval_date.month()))
],
[
\ \
#underline[#calendar_plan.approval_date.year()] р.
],
),
[
#author.name, \
#head_mentor.degree
#head_mentor.name.
],
)
pagebreak()
}
// page 4 {{{2
[
#align(center, bold[РЕФЕРАТ]) \
#context [
#let pages = counter(page).final().at(0)
#let images = query(figure.where(kind: image)).len()
#let tables = query(figure.where(kind: table)).len()
#let bibs = bib-count.final().dedup().len()
/* TODO: why this stopped working?
#let tables = counter(figure.where(kind: table)).final().at(0)
#let images = counter(figure.where(kind: image)).final().at(0)*/
#let counters = ()
#if pages != 0 { counters.push[#pages с.] }
#if tables != 0 { counters.push[#tables табл.] }
#if images != 0 { counters.push[#images рис.] }
#if bibs != 0 { counters.push[#bibs джерел] }
Пояснювальна записка до курсової роботи: #counters.join(", ").
]
\
#(
abstract
.keywords
.map(upper)
.sorted(by: (a, b) => {
if is-cyr(a) != is-cyr(b) { true } else { a < b }
})
.join(", ")
)
\
#abstract.text
]
// page 5 {{{2
outline(
title: [
ЗМІСТ
#v(spacing * 2, weak: true)
],
depth: 2,
indent: auto,
)
doc
// bibliography {{{2
{
heading(depth: 1, numbering: none)[Перелік джерел посилання]
bibliography(
bib_path,
style: "ieee",
full: true,
title: none,
)
let bib_data = yaml(bib_path)
let format-entry(c) = {
if (c.type == "Web") {
let date_array = c.url.date.split("-")
let date = datetime(
year: int(date_array.at(0)),
month: int(date_array.at(1)),
day: int(date_array.at(2)),
)
[#c.title. #c.author. URL: #c.url.value (дата звернення: #date.display("[day].[month].[year]")).]
} else if (
c.type == "Book"
) [#c.author #c.title. #c.publisher, #c.date. #c.page-total c. ] else [
UNSUPPORTED BIBLIOGRAPHY ENTRY TYPE, PLEASE OPEN AN ISSUE
]
}
show enum.item: it => {
set par(first-line-indent: 0pt)
box(width: 1.25cm)
box(width: 1em + 0.5cm)[#it.number.]
it.body
linebreak()
}
context {
for (i, citation) in query(ref.where(element: none))
.map(r => str(r.target))
.dedup()
.enumerate() {
enum.item(
i + 1,
format-entry(bib_data.at(citation)),
)
}
}
}
appendices-style(appendices)
}
// Practice and Laboratory works template {{{1
/// DSTU 3008:2015 Template for NURE
/// -> content
/// - doc (content): Content to apply the template to.
/// - university: "ХНУРЕ": University metadata. Optional.
/// - subject: str: Subject shortcode.
/// - type: ("ЛБ" | "ПЗ" | "КР" | "РФ" | str): Work type.
/// - number: int or none: Work number. Optional.
/// - title: str or none: Work title. Optional.
/// - 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: ("m" | "f" | "p" | none)),): List of mentors. Optional.
#let pz-lb(
doc,
university: "ХНУРЕ",
subject: none,
type: none,
number: none,
title: none,
authors: (),
mentors: (),
) = {
// TODO: add actually relevant asserts
let edu_program = authors.first().edu_program
let uni = universities.at(university)
set document(title: title, author: authors.map(c => c.name))
show: dstu-style.with(skip: 1)
// page 1 {{{2
align(center)[
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ \
#upper(uni.name)
\ \
Кафедра #uni.edu_programs.at(edu_program).department_gen
\ \ \
#pz-lb-title(type, number: number)
з дисципліни: "#uni.subjects.at(subject, default: subject)"
#if title != none [\ з теми: "#eval(title, mode: "markup")"]
\ \ \ \
#columns(2)[
#set align(left)
#set par(first-line-indent: 0pt)
#if authors.len() == 1 {
[#gender-form("author", gender: if "gender" in a.keys() { a.gender } else { none }):\ ]
let a = authors.first()
[ст. гр. #a.edu_program\-#a.group\ #a.name\ ]
if a.variant != none [Варіант: #a.variant]
} else if authors.len() > 1 [
#gender-form("author"):\
#for a in authors [ст. гр. #a.edu_program\-#a.group\ #a.name\ ]
]
#colbreak()
#set align(right)
#if mentors.len() == 1 {
[#gender-form("mentor", gender: if "gender" in m.keys() { m.gender } else { none }):\ ]
let m = mentors.first()
if "degree" in m.keys() and m.degree != none [#m.degree\ ]
[#m.name\ ]
} else if mentors.len() > 1 [
#gender-form("mentor"):\
#for mentor in mentors { [#mentor.degree\ #mentor.name\ ] }]
]
#v(1fr)
Харків -- #datetime.today().display("[year]")
]
pagebreak(weak: true)
// 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
}
// vim:sts=2:sw=2:fdm=marker:cms=/*%s*/

View File

@@ -1,21 +1,21 @@
ХНУРЕ: ХНУРЕ:
name: Харківський національний університет радіоелектроніки name: Харківський національний університет радіоелектроніки
name-en: Kharkiv National University of Radioelectronics name_en: Kharkiv National University of Radioelectronics
edu-programs: edu_programs:
ПЗПІ: ПЗПІ:
name-long: Інженерія програмного забезпечення name_long: Інженерія програмного забезпечення
department-gen: Програмної інженерії department_gen: Програмної інженерії
code: 121 # TODO: change to F2? code: 121 # TODO: change to F2?
КУІБ: КУІБ:
name-long: Управління інформаційною безпекою name_long: Управління інформаційною безпекою
department-gen: Інфокомунікаційної інженерії ім. В. В. Поповського department_gen: Інфокомунікаційної інженерії ім. В. В. Поповського
code: 125 # TODO: change to F5? code: 125 # TODO: change to F5?
description: Кібербезпека та захист інформації description: Кібербезпека та захист інформації
КНТ: КНТ:
name-en: CST # computer sciences and technologies name_en: CST # computer sciences and technologies
name-long: Комп'ютерні науки та технології name_long: Комп'ютерні науки та технології
department-gen: Системотехніки department_gen: Системотехніки
department-en: ST department_en: ST
code: 122 code: 122
subjects: subjects:
DMT: Decision making theory DMT: Decision making theory

View File

@@ -9,13 +9,14 @@
<info> <info>
<title>ДСТУ 3008:2015 (DSTU 3008:2015)</title> <title>ДСТУ 3008:2015 (DSTU 3008:2015)</title>
<title-short>ДСТУ 3008:2015</title-short> <title-short>ДСТУ 3008:2015</title-short>
<id>dstu-3008-2015</id> <id>http://www.zotero.org/styles/dstu-3008-2015</id>
<link href="http://www.zotero.org/styles/dstu-3008-2015" rel="self" />
<link <link
href="https://uk.wikipedia.org/wiki/ДСТУ_3008:2015" href="https://uk.wikipedia.org/wiki/ДСТУ_3008:2015"
rel="documentation" rel="documentation"
/> />
<author> <author>
<name>Linerds</name> <name>Automated</name>
</author> </author>
<category citation-format="numeric" /> <category citation-format="numeric" />
<category field="generic-base" /> <category field="generic-base" />

View File

@@ -15,37 +15,28 @@
).at(month - 1) ).at(month - 1)
#let is-cyr(c) = regex("[\p{Cyrillic}]") in c #let is-cyr(c) = regex("[\p{Cyrillic}]") in c
#let is-empty(val) = val == none or str(val).len() == 0 or val == []
#let in-keys(key, dict) = str(key) in dict.keys()
/// type-safe emptiness check #let degree-get(m) = if in-keys("degree", m) and not is-empty(m.degree) { [#m.degree\ ] }
#let is-empty(val) = {
if val == none { return true }
if type(val) == str { val.len() == 0 } else if type(val) == array { val == [] } else { false }
}
#let degree-get(m) = if "degree" in m and not is-empty(m.degree) { [#m.degree\ ] }
/// returns verb form based on gender ("m", "f", or "p" for plural)
#let gender-verb(verb, gender: "p") = { #let gender-verb(verb, gender: "p") = {
( (
"author": ("m": "Виконав", "f": "Виконала", "p": "Виконали"), "author": ("m": "Виконав", "f": "Виконала", "p": "Виконали"),
"mentor": ("m": "Перевірив", "f": "Перевірила", "p": "Перевірили"), "mentor": ("m": "Перевірив", "f": "Перевірила", "p": "Перевірили"),
) )
.at(verb) .at(verb)
.at(if gender == "m" or gender == "f" { gender } else { "p" }) .at(if gender == "m" or gender == "f" { gender } else { "p" }, default: "p")
}
/// returns verb form for dictionary containing gender field
#let gender-form(verb, dict: none) = {
let g = if type(dict) == dictionary and "gender" in dict { dict.gender } else { "p" }
gender-verb(verb, gender: g)
} }
#let gender-get(dict) = if type(dict) == dictionary and in-keys("gender", dict) { dict.gender }
#let gender-form(verb, dict: none) = gender-verb(verb, gender: gender-get(dict))
#let pz-lb-title(type, number: none) = { #let pz-lb-title(type, number: none) = {
let type-title = ( let type-title = (
"ЛБ": [Звіт \ з лабораторної роботи], "ЛБ": [Звіт \ з лабораторної роботи],
"ПЗ": [Звіт \ з практичної роботи], "ПЗ": [Звіт \ з практичної роботи],
"КР": [Контрольна робота], "КР": [Контрольна робота],
"РФ": [Реферат], "РФ": [Реферат], // зрада
"ІДЗ": [Індивідуальне домашнє завдання], "ІДЗ": [Індивідуальне домашнє завдання],
).at(type, default: type) ).at(type, default: type)
if not is-empty(number) { [#type-title #number] } else { [#type-title] } if not is-empty(number) { [#type-title #number] } else { [#type-title] }

View File

@@ -5,17 +5,20 @@
#import "./style.typ" #import "./style.typ"
#import "./utils.typ" #import "./utils.typ"
/// Coursework template for NURE // Coursework template {{{1
/// - university (str): University code, default "ХНУРЕ"
/// - subject (str): Subject short name /// DSTU 3008:2015 Template for NURE
/// - title (str): Work title /// -> content
/// - authors (array): List of author dictionaries /// - doc (content): Content to apply the template to.
/// - mentors (array): List of mentor dictionaries /// - title (str): Title of the document.
/// - task-list (dict): Task metadata /// - subject (str): Subject short name.
/// - calendar-plan (dict): Calendar plan table and approval date /// - authors ((name: str, full_name_gen: str, variant: int, course: int, semester: int, group: str, gender: str),): List of authors.
/// - abstract (dict): Keywords and abstract text /// - mentors ((name: str, degree: str),): List of mentors.
/// - bib-path (str): Path to bibliography file /// - task_list (done_date: datetime, initial_date: datetime, source: (content | str), content: (content | str), graphics: (content | str)): Task list object.
/// - appendices (content): Appendix content /// - calendar_plan ( plan_table: (content | str), approval_date: datetime): Calendar plan object.
/// - abstract (keywords: (str, ), text: (content | str)): Abstract object.
/// - bib_path path: Path to the bibliography yaml file.
/// - appendices (content): Content with appendices.
#let coursework( #let coursework(
doc, doc,
university: "ХНУРЕ", university: "ХНУРЕ",
@@ -23,49 +26,55 @@
title: none, title: none,
authors: (), authors: (),
mentors: (), mentors: (),
task-list: (), task_list: (),
calendar-plan: (), calendar_plan: (),
abstract: (), abstract: (),
bib-path: none, bib_path: none,
appendices: (), appendices: (),
) = { ) = {
set document(title: title, author: authors.map(c => c.name)) set document(title: title, author: authors.map(c => c.name))
show: style.dstu.with(skip: 1) show: style.dstu.with(skip: 1)
tp.cw.nure( tp.cw.coursework(
university, university,
subject, subject,
type,
title, title,
authors, authors,
mentors, mentors,
task-list, task_list,
calendar-plan, calendar_plan,
abstract, abstract,
) )
doc doc
// Bibliography with DSTU formatting // bibliography {{{2
{ {
// shall CSL descend to hell for it's a horrid standard
show regex("^\\d+\\."): it => [#it#h(0.5cm)] show regex("^\\d+\\."): it => [#it#h(0.5cm)]
show block: it => [#it.body#parbreak()] show block: it => [#it.body#parbreak()]
bibliography(bib-path, title: [Перелік джерел посилання], style: "csl/dstu-3008-2015.csl", full: true) bibliography(bib_path, title: [Перелік джерел посилання], style: "csl/dstu-3008-2015.csl", full: true)
} }
style.appendices(appendices) style.appendices(appendices)
} }
/// Practice and Laboratory works template // Practice and Laboratory works template {{{1
/// - layout (str): "default", "minimal", or "complex"
/// - university (str): University code /// DSTU 3008:2015 Template for NURE
/// - edu-program (str): Education program code /// -> content
/// - subject (str): Subject code /// - doc (content): Content to apply the template to.
/// - type (str): Work type (ЛБ, ПЗ, КР, РФ, ІДЗ) /// - layout: ("default" | "simple"): Title page layout variant.
/// - number (int): Work number /// - university: "ХНУРЕ": University metadata. Optional.
/// - title (str): Work title /// - edu-program: (str or none): Education program shortcode. Optional.
/// - authors (array): List of authors /// - subject: str: Subject shortcode.
/// - mentors (array): List of mentors /// - type: ("ЛБ" | "ПЗ" | "КР" | "РФ" | str): Work type.
/// - number: int or none: Work number. Optional.
/// - title: str or none: Work title. Optional.
/// - 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: ("m" | "f" | "p" | none)),): List of mentors. Optional.
#let pz-lb( #let pz-lb(
doc, doc,
layout: "default", layout: "default",
@@ -78,37 +87,36 @@
authors: (), authors: (),
mentors: (), mentors: (),
) = { ) = {
assert(authors.len() > 0, message: "At least one author required") // TODO: add actually relevant asserts
let edu-program = if edu-program != none { edu-program } else { authors.first().edu-program } let edu-program = if edu-program != none { edu-program } else { authors.first().edu_program }
let uni = universities.at(university) let uni = universities.at(university)
set document(title: title, author: authors.map(c => c.name)) set document(title: title, author: authors.map(c => c.name))
show: style.dstu.with(skip: 1) show: style.dstu.with(skip: 1)
// Select layout variant // page 1 {{{2
let layouts = (
"complex": tp.pz-lb.complex(uni, edu-program, subject, type, number, title, authors, mentors),
"ХНУРЕ": tp.pz-lb.nure(uni, edu-program, subject, type, number, title, authors, mentors),
"default": tp.pz-lb.nure(uni, edu-program, subject, type, number, title, authors, mentors),
)
layouts.at(university, default: layouts.default) (
"complex": tp.pz-lb.complex(uni, edu-program, subject, type, number, title, authors, mentors),
"minimal": tp.pz-lb.minimal(uni, edu-program, subject, type, number, title, authors, mentors),
"default": tp.pz-lb.minimal(uni, edu-program, subject, type, number, title, authors, mentors),
).at(layout)
pagebreak(weak: true) pagebreak(weak: true)
// Set heading counter based on title/number // TODO(unexplrd): wrap my head around the old way
if title == none { if title == none {
if number == none { context counter(heading).update(1) } else { if number == none { context counter(heading).update(1) } else {
context counter(heading).update(number) context counter(heading).update(number)
} }
} else { } else {
if number != none { if number != none { context counter(heading).update(number - 1) }
context counter(heading).update(number - 1)
}
heading(eval(title, mode: "markup")) heading(eval(title, mode: "markup"))
} }
doc doc
} }
// vim:sts=2:sw=2:fdm=marker:cms=/*%s*/

View File

@@ -1,31 +1,22 @@
#import "utils.typ": bold #import "./utils.typ": bold
/// Constants for consistent styling
#let spacing = 0.95em
#let indent-size = 1.25cm
#let double-spacing = spacing * 2
#let double-half-spacing = spacing * 2.5
/// Ukrainian alphabet for DSTU 3008:2015 numbering #let spacing = 0.95em // spacing between lines
#let ukr-enum = "абвгдежиклмнпрстуфхцшщюя".split("")
/// Helper for level 2/3 heading blocks /// symbols used for numbering according to DSTU 3008:2015
#let heading-block(it, num: auto) = { #let ukr-enum = "абвгдежиклмнпрстуфхцшщюя".split("") // 0 = "", 1 = "а"
v(double-spacing, weak: true)
block(width: 100%, spacing: 0em)[
#h(indent-size)
#counter(heading).display(num)
#it.body
]
v(double-spacing, weak: true)
}
/// DSTU 3008:2015 Style /// DSTU 3008:2015 Style
/// -> content
/// - it (content): Content to apply the style to.
/// - skip (int): Do not show page number for this number of pages.
/// - offset (int): Adjust all page numbers by this amount.
#let dstu( #let dstu(
it, it,
skip: 0, skip: 0,
offset: 0, offset: 0,
) = { ) = {
// Page setup // General Styling {{{1
set page( set page(
paper: "a4", paper: "a4",
number-align: top + right, number-align: top + right,
@@ -33,33 +24,43 @@
numbering: (i, ..) => if i > skip { numbering("1", i + offset) }, numbering: (i, ..) => if i > skip { numbering("1", i + offset) },
) )
// Text and paragraph
set text(lang: "uk", size: 14pt, hyphenate: false, font: ("Times New Roman", "Liberation Serif")) set text(lang: "uk", size: 14pt, hyphenate: false, font: ("Times New Roman", "Liberation Serif"))
set par(justify: true, spacing: spacing, leading: spacing, first-line-indent: (amount: indent-size, all: true))
set par(justify: true, spacing: spacing, leading: spacing, first-line-indent: (
amount: 1.25cm,
all: true,
))
set block(spacing: spacing) set block(spacing: spacing)
set underline(evade: false) set underline(evade: false)
// Lists // Enums & Lists {{{1
set enum(indent: indent-size, body-indent: 0.5cm, numbering: i => ukr-enum.at(i) + ")") // First level
set enum(indent: 1.25cm, body-indent: 0.5cm, numbering: i => { ukr-enum.at(i) + ")" })
// Second level and further nesting
show enum: it => { show enum: it => {
set enum(indent: 0em, numbering: "1)") set enum(indent: 0em, numbering: "1)")
it it
} }
set list(indent: indent-size + 0.1cm, body-indent: 0.5cm, marker: [--])
// Figures // Lists are not intended for multiple levels, use `enum`
set list(indent: 1.35cm, body-indent: 0.5cm, marker: [--])
// Figures {{{1
show figure: it => { show figure: it => {
v(double-spacing, weak: true) v(spacing * 2, weak: true)
it it
v(double-spacing, weak: true) v(spacing * 2, weak: true)
} }
set figure.caption(separator: [ -- ]) set figure.caption(separator: [ -- ])
show figure.where(kind: table): set figure.caption(position: top) show figure.where(kind: table): set figure.caption(position: top)
show figure.caption.where(kind: table): set align(left) show figure.caption.where(kind: table): set align(left)
show figure.where(kind: raw): set figure.caption(position: top) show figure.where(kind: raw): set figure.caption(position: top)
show figure.where(kind: raw): set align(left) show figure.where(kind: raw): set align(left)
// Numbering reset on level 1 headings // Numbering {{{1
show heading.where(level: 1): it => { show heading.where(level: 1): it => {
counter(math.equation).update(0) counter(math.equation).update(0)
counter(figure.where(kind: raw)).update(0) counter(figure.where(kind: raw)).update(0)
@@ -67,66 +68,95 @@
counter(figure.where(kind: table)).update(0) counter(figure.where(kind: table)).update(0)
it it
} }
set figure(numbering: i => context numbering("1.1", counter(heading).get().at(0), i)) set figure(numbering: i => numbering("1.1", counter(heading).get().at(0), i))
set math.equation(numbering: i => context numbering("(1.1)", counter(heading).get().at(0), i)) set math.equation(numbering: i => numbering("(1.1)", counter(heading).get().at(0), i))
// Headings // Headings {{{1
set heading(numbering: "1.1") set heading(numbering: "1.1")
show heading: it => {
set text(size: 14pt) show heading: it => if it.level == 1 {
if it.level == 1 { set align(center)
set align(center) set text(size: 14pt, weight: "semibold")
set text(weight: "semibold")
pagebreak(weak: true) pagebreak(weak: true)
upper(it) upper(it)
v(double-spacing, weak: true) v(spacing * 2, weak: true)
} else { } else {
set text(weight: "regular") set text(size: 14pt, weight: "regular")
heading-block(it, num: if it.level == 3 { it.numbering } else { auto })
} v(spacing * 2, weak: true)
block(width: 100%, spacing: 0em)[
#h(1.25cm)
#counter(heading).display(auto)
#it.body
]
v(spacing * 2, weak: true)
} }
// Code listings show heading.where(level: 3): it => {
set text(size: 14pt, weight: "regular")
v(spacing * 2, weak: true)
block(width: 100%, spacing: 0em)[
#h(1.25cm)
#counter(heading).display(it.numbering)
#it.body
]
v(spacing * 2, weak: true)
}
// listings {{{3
show raw.where(block: true): it => { show raw.where(block: true): it => {
let code-spacing = 0.5em let new_spacing = 0.5em
set block(spacing: code-spacing) set block(spacing: new_spacing)
set par(spacing: code-spacing, leading: code-spacing) set par(spacing: new_spacing, leading: new_spacing)
set text(size: 11pt, weight: "semibold", font: ("Courier New", "Liberation Mono")) set text(size: 11pt, weight: "semibold", font: ("Courier New", "Liberation Mono"))
v(double-half-spacing, weak: true)
pad(it, left: indent-size) v(spacing * 2.5, weak: true)
v(double-half-spacing, weak: true) pad(it, left: 1.25cm)
v(spacing * 2.5, weak: true)
}
it
// }}}
}
/// DSTU 3008:2015 Appendices Style
/// -> content
/// - it (content): Content to apply the style to.
#let appendices(it) = /* {{{ */ {
// Numbering
counter(heading).update(0)
set heading(numbering: (i, ..n) => upper(ukr-enum.at(i)) + numbering(".1.1", ..n))
set figure(numbering: i => upper(ukr-enum.at(counter(heading).get().at(0))).i)
set math.equation(numbering: i => upper(ukr-enum.at(counter(heading).get().at(0))).i)
// Heading supplement (Heading name shown when citing with @ref)
set heading(supplement: [Додаток])
// Headings
show heading: it => if it.level == 1 {
set align(center)
set text(size: 14pt, weight: "regular")
pagebreak(weak: true)
bold([ДОДАТОК #counter(heading).display(auto)])
linebreak()
it.body
v(spacing * 2, weak: true)
} else {
set text(size: 14pt, weight: "regular")
v(spacing * 2, weak: true)
block(width: 100%, spacing: 0em)[
#h(1.25cm)
#counter(heading).display(auto)
#it.body
]
v(spacing * 2, weak: true)
} }
it it
} }
/// DSTU 3008:2015 Appendices Style // vim:sts=2:sw=2:fdm=marker:cms=/*%s*/
#let appendices(it) = {
counter(heading).update(0)
context {
let app-letter = upper(ukr-enum.at(counter(heading).get().at(0)))
set heading(numbering: (i, ..n) => upper(ukr-enum.at(i)) + numbering(".1.1", ..n))
set figure(numbering: i => app-letter + "." + str(i))
set math.equation(numbering: i => app-letter + "." + str(i))
set heading(supplement: [Додаток])
show heading: h => {
set text(size: 14pt)
if h.level == 1 {
set align(center)
set text(weight: "regular")
pagebreak(weak: true)
bold([ДОДАТОК #counter(heading).display(auto)])
linebreak()
h.body
v(double-spacing, weak: true)
} else {
set text(weight: "regular")
heading-block(h)
}
}
it
}
}

View File

@@ -3,14 +3,15 @@
#import "../../style.typ": * #import "../../style.typ": *
#import "../../utils.typ": bold, hfill, uline #import "../../utils.typ": bold, hfill, uline
#let nure( #let coursework(
university, university,
subject, subject,
type,
title, title,
authors, authors,
mentors, mentors,
task-list, task_list,
calendar-plan, calendar_plan,
abstract, abstract,
) = { ) = {
let bib-count = state("citation-counter", ()) let bib-count = state("citation-counter", ())
@@ -20,10 +21,10 @@
} }
let author = authors.first() let author = authors.first()
let head-mentor = mentors.first() let head_mentor = mentors.first()
let uni = universities.at(university) let uni = universities.at(university)
let edu-prog = uni.edu-programs.at(author.edu-program) let edu_prog = uni.edu_programs.at(author.edu_program)
// page 1 {{{2 // page 1 {{{2
[ [
@@ -33,7 +34,7 @@
\ \
Кафедра #edu-prog.department-gen Кафедра #edu_prog.department_gen
\ \
@@ -48,10 +49,10 @@
#set align(left) #set align(left)
#set par(first-line-indent: 0pt) #set par(first-line-indent: 0pt)
#gender-form("author", dict: author) ст. гр. #author.edu-program\-#author.group #gender-form("author", dict: author) ст. гр. #author.edu_program\-#author.group
\ \
Керівник:\ #head-mentor.degree Керівник:\ #head_mentor.degree
\ \
Робота захищена на оцінку Робота захищена на оцінку
@@ -66,7 +67,7 @@
#author.name #author.name
\ \ \ \
#head-mentor.name #head_mentor.name
\ \
#underline(" " * 35) #underline(" " * 35)
@@ -77,7 +78,7 @@
#v(1fr) #v(1fr)
Харків -- #task-list.done-date.display("[year]") Харків -- #task_list.done_date.display("[year]")
#pagebreak() #pagebreak()
] ]
@@ -97,18 +98,18 @@
Спеціальність Спеціальність
], ],
{ {
uline(align: left, edu-prog.department-gen) uline(align: left, edu_prog.department_gen)
linebreak() linebreak()
uline(align: left, uni.subjects.at(subject, default: 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])
}, },
) )
grid( grid(
columns: (1fr, 1fr, 1fr), columns: (1fr, 1fr, 1fr),
gutter: 0.3fr, gutter: 0.3fr,
[#bold[Курс] #uline(author.course)], [#bold[Курс] #uline(author.course)],
[#bold[Група] #uline([#author.edu-program\-#author.group])], [#bold[Група] #uline([#author.edu_program\-#author.group])],
[#bold[Семестр] #uline(author.semester)], [#bold[Семестр] #uline(author.semester)],
) )
@@ -120,7 +121,7 @@
linebreak() linebreak()
uline(align: left)[_#author.full-name-gen _] uline(align: left)[_#author.full_name_gen _]
linebreak() linebreak()
linebreak() linebreak()
@@ -132,30 +133,30 @@
{ {
bold[\2. Строк здачі закінченої роботи:] bold[\2. Строк здачі закінченої роботи:]
uline(task-list.done-date.display("[day].[month].[year]")) uline(task_list.done_date.display("[day].[month].[year]"))
hfill(10fr) hfill(10fr)
} }
linebreak() linebreak()
bold[\3. Вихідні дані для роботи:] bold[\3. Вихідні дані для роботи:]
uline(task-list.source) uline(task_list.source)
linebreak() linebreak()
bold[\4. Зміст розрахунково-пояснювальної записки:] bold[\4. Зміст розрахунково-пояснювальної записки:]
uline(task-list.content) uline(task_list.content)
linebreak() linebreak()
bold[\5. Перелік графічного матеріалу:] bold[\5. Перелік графічного матеріалу:]
uline(task-list.graphics) uline(task_list.graphics)
linebreak() linebreak()
{ {
bold[\6. Дата видачі завдання:] bold[\6. Дата видачі завдання:]
uline(task-list.initial-date.display("[day].[month].[year]")) uline(task_list.initial_date.display("[day].[month].[year]"))
hfill(10fr) hfill(10fr)
} }
@@ -169,7 +170,7 @@
linebreak() linebreak()
calendar-plan.plan-table calendar_plan.plan_table
linebreak() linebreak()
@@ -181,22 +182,22 @@
[ [
Студент \ Студент \
Керівник \ Керівник \
#align(center)["#underline[#calendar-plan.approval-date.day()]"] #align(center)["#underline[#calendar_plan.approval_date.day()]"]
], ],
[ [
#uline(align: center, []) \ #uline(align: center, []) \
#uline(align: center, []) \ #uline(align: center, []) \
#uline(align: center, month-gen(calendar-plan.approval-date.month())) #uline(align: center, month-gen(calendar_plan.approval_date.month()))
], ],
[ [
\ \ \ \
#underline[#calendar-plan.approval-date.year()] р. #underline[#calendar_plan.approval_date.year()] р.
], ],
), ),
[ [
#author.name, \ #author.name, \
#head-mentor.degree #head_mentor.degree
#head-mentor.name. #head_mentor.name.
], ],
) )

View File

@@ -1 +1 @@
#import "nure.typ": * #import "coursework.typ": *

View File

@@ -1,5 +1,5 @@
#import "../../helpers.typ": * #import "../../helpers.typ": *
#let complex(uni, edu-program, subject, type, number, title, authors, mentors) = { #let complex(uni, edu_program, subject, type, number, title, authors, mentors) = {
align(center)[ align(center)[
Міністерство освіти і науки України \ Міністерство освіти і науки України \
#uni.name #uni.name
@@ -7,10 +7,10 @@
\ \
#set par(first-line-indent: 0pt) #set par(first-line-indent: 0pt)
#align(left)[ #align(left)[
#let edu = uni.edu-programs.at(edu-program) #let edu = uni.edu_programs.at(edu_program)
Кафедра #underline(edu.department-gen) \ Кафедра #underline(edu.department_gen) \
Спеціальність #underline([#edu.code #edu.description]) \ Спеціальність #underline([#edu.code #edu.description]) \
Освітня програма #underline(edu.name-long) Освітня програма #underline(edu.name_long)
] ]
\ \ \ \
@@ -26,12 +26,12 @@
#align(right)[ #align(right)[
#if authors.len() == 1 { #if authors.len() == 1 {
let a = authors.first() let a = authors.first()
[#gender-form("author", dict: a):\ [#gender-verb("author", gender: gender-get(a)):\
студент групи #a.edu-program\-#a.group\ #a.name\ ] студент групи #a.edu_program\-#a.group\ #a.name\ ]
text(size: 8pt, [(прізвище та ініціали)\ ]) text(size: 8pt, [(прізвище та ініціали)\ ])
} else if authors.len() > 1 [ } else if authors.len() > 1 [
#gender-verb("author"):\ #gender-verb("author"):\
#for a in authors [студент групи #a.edu-program\-#a.group\ #a.name\ ] #for a in authors [студент групи #a.edu_program\-#a.group\ #a.name\ ]
#text(size: 8pt, [(прізвище та ініціали)\ ]) #text(size: 8pt, [(прізвище та ініціали)\ ])
] ]
@@ -39,7 +39,7 @@
#if mentors.len() == 1 { #if mentors.len() == 1 {
let m = mentors.first() let m = mentors.first()
[#gender-form("mentor", dict: m):\ ] [#gender-verb("mentor", gender: gender-get(m)):\ ]
degree-get(m) degree-get(m)
[#m.name\ ] [#m.name\ ]
text(size: 8pt, [(прізвище та ініціали)\ ]) text(size: 8pt, [(прізвище та ініціали)\ ])
@@ -58,3 +58,4 @@
#datetime.today().display("[year]") #datetime.today().display("[year]")
] ]
} }

View File

@@ -1,2 +1,2 @@
#import "complex.typ": * #import "complex.typ": *
#import "nure.typ": * #import "minimal.typ": *

View File

@@ -1,10 +1,10 @@
#import "../../helpers.typ": * #import "../../helpers.typ": *
#let nure(uni, edu-program, subject, type, number, title, authors, mentors) = { #let minimal(uni, edu-program, subject, type, number, title, authors, mentors) = {
align(center)[ align(center)[
#upper([Міністерство освіти і науки України\ #uni.name]) #upper([Міністерство освіти і науки України\ #uni.name])
\ \ \ \
Кафедра #uni.edu-programs.at(edu-program).department-gen Кафедра #uni.edu_programs.at(edu-program).department_gen
\ \ \ \ \ \
#pz-lb-title(type, number: number) #pz-lb-title(type, number: number)
@@ -20,12 +20,12 @@
#if authors.len() == 1 { #if authors.len() == 1 {
let a = authors.first() let a = authors.first()
[#gender-form("author", dict: a):\ ] [#gender-form("author", dict: a):\ ]
[ст. гр. #a.edu-program\-#a.group\ ] [ст. гр. #a.edu_program\-#a.group\ ]
[#a.name\ ] [#a.name\ ]
if not is-empty(a.variant) [Варіант: #a.variant] if not is-empty(a.variant) [Варіант: #a.variant]
} else if authors.len() > 1 [ } else if authors.len() > 1 [
#gender-form("author"):\ #gender-form("author"):\
#for a in authors [ст. гр. #a.edu-program\-#a.group\ #a.name\ ] #for a in authors [ст. гр. #a.edu_program\-#a.group\ #a.name\ ]
] ]
#colbreak() #colbreak()
@@ -50,3 +50,4 @@
Харків -- #datetime.today().display("[year]") Харків -- #datetime.today().display("[year]")
] ]
} }

View File

@@ -1,56 +1,61 @@
// Template formatting functions {{{1
/// bold text /// bold text
#let bold(content) = text(weight: "bold")[#content] #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)
/// fill horizontal space with a filled box /// fill horizontal space with a box and not an empty space
#let hfill(width) = box(width: width, repeat("")) // HAIR SPACE (U+200A) #let hfill(width) = box(
width: width,
repeat(""),
) // NOTE: This is a HAIR SPACE (U+200A), not a regular space
/// underlined cell with centered content by default /// make underlined cell with filled value
#let uline(align: center, content) = underline[ #let uline(align: center, content) = underline[
#if align != left { hfill(1fr) } #if align != left { hfill(1fr) }
#content #content
#if align != right { hfill(1fr) } #if align != right { hfill(1fr) }
] ]
/// Extract filename stem without extension // Helper functions {{{1
#let stem(path) = path.split("/").last().split(".").first()
/// Extract parent directory name /// captioned image with label derived from path:
#let parent-dir(path) = path.split("/").at(-2, default: "") /// - "image.png" = @image
/// - "img/image.png" = @image
/// Generate label from image path: /// - "img/foo/image.png" = @foo_image
/// - "image.png" → "image" /// - "img/foo/foo_image.png" = @foo_image
/// - "img/foo/bar.png" → "foo_bar" /// the caption will be modified based on a conditional positional value:
#let img-label(path) = { /// - `none`: no change
let name = stem(path) /// - some value: "`caption` (за даними `value`)"
let parent = parent-dir(path) /// - no value: "`caption` (рисунок виконано самостійно)"
/// additional named arguments will be passed to original `image` function
// If parent exists and name doesn't start with parent name, combine them
let base = if parent != "" and not name.starts-with(parent) {
parent + "_" + name
} else {
name
}
label(base.replace(" ", "_"))
}
/// Format image caption based on optional source
#let img-caption(base-caption, source) = {
if source == none {
base-caption + " (рисунок виконано самостійно)"
} else if source == () or source == "" {
base-caption
} else {
base-caption + " (за даними " + source + ")"
}
}
/// captioned image with auto-generated label from path
/// Usage: img("path/to/image.png", "Caption")(optional: "source")
#let img(path, caption, ..sink) = { #let img(path, caption, ..sink) = {
let source = sink.pos().at(0, default: ()) let parts = path.split(".").first().split("/")
[ #figure(image(path, ..sink.named()), caption: utils.img-caption(caption, source)) #utils.img-label(path) ]
let label_string = if (
parts.len() <= 2 or parts.at(-1).starts-with(parts.at(-2))
) {
// ("image",), (_, "image") and (.., "img", "img_image")
parts.last()
} else {
// (.., "img", "image") = "img_image"
parts.at(-2) + "_" + parts.at(-1)
}.replace(" ", "_")
let caption = if sink.pos().len() == 0 {
caption
} else if sink.pos().first() == none {
caption + " (рисунок виконано самостійно)"
} else {
[#caption (за даними #sink.pos().first())]
}
[#figure(
image(path, ..sink.named()),
caption: caption,
) #label(label_string)]
} }

View File

@@ -1,4 +1,4 @@
#import "@local/nure:0.1.1": * #import "@local/nure:0.1.0": *
#import style: spacing #import style: spacing
#import "utils.typ": img #import "utils.typ": img
@@ -6,8 +6,8 @@
#let authors = ( #let authors = (
( (
name: "Ситник Є. С.", name: "Ситник Є. С.",
full-name-gen: "Ситника Єгора Сергійовича", full_name_gen: "Ситника Єгора Сергійовича",
edu-program: "ПЗПІ", edu_program: "ПЗПІ",
group: "23-2", group: "23-2",
gender: "m", gender: "m",
course: 2, course: 2,
@@ -22,16 +22,16 @@
(name: "Широкопетлєва М. С.", degree: "Ст. викл. каф. ПІ"), (name: "Широкопетлєва М. С.", degree: "Ст. викл. каф. ПІ"),
) )
#let task-list = ( #let task_list = (
done-date: datetime(year: 2024, month: 12, day: 27), done_date: datetime(year: 2024, month: 12, day: 27),
initial-date: datetime(year: 2024, month: 9, day: 15), initial_date: datetime(year: 2024, month: 9, day: 15),
source: "методичні вказівки до виконання курсової роботи, вимоги до інформаційної системи, предметна область, що пов’язана з управлінням класом та класним керівництвом.", source: "методичні вказівки до виконання курсової роботи, вимоги до інформаційної системи, предметна область, що пов’язана з управлінням класом та класним керівництвом.",
content: "вступ, аналіз предметної області; постановка задачі; проектування бази даних; опис програми; висновки; перелік джерел посилання.", content: "вступ, аналіз предметної області; постановка задачі; проектування бази даних; опис програми; висновки; перелік джерел посилання.",
graphics: "загальна діаграма класів, ER-діаграма, UML-діаграми, DFD-діаграма, схема БД в 1НФ, 2НФ, 3НФ, копії екранів (“скриншоти”) прикладної програми, приклади звітів прикладної програми.", graphics: "загальна діаграма класів, ER-діаграма, UML-діаграми, DFD-діаграма, схема БД в 1НФ, 2НФ, 3НФ, копії екранів (“скриншоти”) прикладної програми, приклади звітів прикладної програми.",
) )
#let calendar-plan = ( #let calendar_plan = (
plan-table: table( plan_table: table(
columns: 4, columns: 4,
align: (center, left, center, center), align: (center, left, center, center),
[Номер], [Назва етапів курсової роботи], [Строк виконання етапів роботи], [Примітки], [Номер], [Назва етапів курсової роботи], [Строк виконання етапів роботи], [Примітки],
@@ -54,7 +54,7 @@
[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),
) )
#let abstract = ( #let abstract = (
@@ -113,10 +113,10 @@
subject: "БД", subject: "БД",
authors: authors, authors: authors,
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(read("bibl.yml")), // NOTE: use `bytes("bibl.yml")` as typst looks in template dir when using just filename bib_path: bytes(read("bibl.yml")), // NOTE: use `bytes("bibl.yml")` as typst looks in template dir when using just filename
appendices: appendices, appendices: appendices,
) )

View File

@@ -1,4 +1,4 @@
#import "@local/nure:0.1.1": * #import "@local/nure:0.1.0": *
#import "utils.typ": img #import "utils.typ": img
#import style: spacing #import style: spacing
@@ -16,8 +16,8 @@
authors: ( authors: (
( (
name: "Косач Л. П.", name: "Косач Л. П.",
full-name-gen: "Косач Лариси Петрівни", full_name_gen: "Косач Лариси Петрівни",
edu-program: "КУІБ", edu_program: "КУІБ",
group: "23-2", group: "23-2",
gender: "f", gender: "f",
course: 2, course: 2,
@@ -68,7 +68,7 @@
#v(-spacing) #v(-spacing)
== Частина 1 == Частина 1
#lorem(100) #lorem(100)
== Частина 2 == Частина2
#lorem(200) #lorem(200)
= Приклад звіту 2 = Приклад звіту 2

View File

@@ -1,7 +1,36 @@
#import "@local/nure:0.1.1": utils /// captioned image with label derived from path:
/// captioned image with auto-generated label from path /// - "image.png" = @image
/// Usage: img("path/to/image.png", "Caption")(optional: "source") /// - "img/image.png" = @image
/// - "img/foo/image.png" = @foo_image
/// - "img/foo/foo_image.png" = @foo_image
/// the caption will be modified based on a conditional positional value:
/// - `none`: no change
/// - some value: "`caption` (за даними `value`)"
/// - no value: "`caption` (рисунок виконано самостійно)"
/// additional named arguments will be passed to original `image` function
#let img(path, caption, ..sink) = { #let img(path, caption, ..sink) = {
let source = sink.pos().at(0, default: ()) let parts = path.split(".").first().split("/")
[ #figure(image(path, ..sink.named()), caption: utils.img-caption(caption, source)) #utils.img-label(path) ]
let label_string = if (
parts.len() <= 2 or parts.at(-1).starts-with(parts.at(-2))
) {
// ("image",), (_, "image") and (.., "img", "img_image")
parts.last()
} else {
// (.., "img", "image") = "img_image"
parts.at(-2) + "_" + parts.at(-1)
}.replace(" ", "_")
let caption = if sink.pos().len() == 0 {
caption
} else if sink.pos().first() == none {
caption + " (рисунок виконано самостійно)"
} else {
[#caption (за даними #sink.pos().first())]
}
[#figure(
image(path, ..sink.named()),
caption: caption,
) #label(label_string)]
} }

51
test-layouts.typ Normal file
View File

@@ -0,0 +1,51 @@
#import "@local/test-multifile:0.1.0" as nure
#import nure.utils: *
#import nure.style
// #set document(title: "Тест лейаутів", author: "Іванов І.І.")
#let authors = (
(
name: "Іванов І.І.",
full_name_gen: "Іванова Івана Івановича",
edu_program: "КУІБ",
group: "23-1",
gender: "m",
variant: 5,
),
)
#let mentors = (
(name: "Петров П.П.", degree: "доцент кафедри ІКІ", gender: "m"),
)
// Тест default лейауту
#show: nure.pz-lb.with(
layout: "complex",
subject: "БД",
edu-program: "КУІБ",
type: "ЛБ",
number: 2,
title: "SQL запити",
authors: authors,
mentors: mentors,
)
#bold(lorem(10))
#pagebreak()
// Тест simple лейауту
// #show: pz-lb.with(
// layout: "minimal",
// subject: "БД",
// type: "ЛБ",
// number: 2,
// title: "SQL запити",
// authors: authors,
// mentors: mentors,
// )
#show: style.appendices
= #lorem(5)

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "nure" name = "nure"
version = "0.1.1" version = "0.1.0"
entrypoint = "src/lib.typ" entrypoint = "src/lib.typ"
authors = ["linerds"] authors = ["linerds"]
license = "GPL-3.0" license = "GPL-3.0"