40 Commits
main ... 0.1.1

Author SHA1 Message Date
cee212ae0a chore!: bump to 0.1.1
refactor: break up into multiple files
feat: csl style
refactor!: rename variables
Update template, readme, and more
2026-02-06 02:08:17 +02:00
549d7f060f fix formatting, use .first() instead of .at(0) 2026-01-13 12:45:44 +02:00
73ab5c8f08 feat: set multiple authors in coursework 2026-01-12 01:13:56 +02:00
96f5070167 feat: put all author names in metadata 2026-01-12 01:06:54 +02:00
5cc95f17c7 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>
2026-01-12 00:16:45 +02:00
c49e911aad fix: use regex for is-cyr
fix: remove unused properties from coursework
2026-01-12 00:01:45 +02:00
c1490ce893 fix: update templates, remove comment 2026-01-11 22:07:29 +02:00
79579208fa fix: fix new line 2026-01-11 21:41:26 +02:00
5bd47ac4c1 refactor!: add functions, break things 2026-01-11 21:22:32 +02:00
6cb71054cf feat!: remove chapters 2025-11-11 15:31:56 +02:00
b28578be34 feat: make doc.yaml optional
docs: update README.md
2025-11-11 15:25:05 +02:00
3ece994ced fix: left align for listings 2025-11-11 14:50:10 +02:00
6e10c4ae2b feat: add new subjects 2025-10-18 19:44:58 +03:00
3e6f3bf7c4 fix: set correct name for appendice heading 2025-10-18 19:44:30 +03:00
8dcbee291c Added new subjects, sorted existing subjects 2025-09-25 11:56:44 +03:00
4a78f8f3ee add new subjects 2025-09-22 17:54:04 +03:00
648824fd46 fail compilation if subject is unknown 2025-09-22 17:53:46 +03:00
1d6412c60f chore(lib): fixed vim modeline 2025-07-25 12:31:43 +03:00
3ba9990dc1 style(lib): fixed styling (maybe not) 2025-07-25 12:31:02 +03:00
2e65baae70 refactor: backport changes from noatu's fork
feat(style)!: remove reference styling & fix figure numbering for appendices
feat!: swap appending on 'none' being the first position in ..sink
feat: add chapters to pz-lb()
feat: add asserts
fix: better control flow

img() function now requires `none` to be the first position in ..sink,
e.g. `#img("/fig/1.png", "Caption", none)` to get "Caption (рисунок виконано самостійно)" caption.

Use `@some_label[див. рис.]` to get a "див. рис. 42" link.

Signed-off-by: unexplrd <unexplrd@linerds.us>
2025-07-09 17:17:53 +03:00
d338c21965 Merge pull request 'add name of educational program && names of subjects for this semester' (#14) from add-program-and-subject-names into 0.1.0
Reviewed-on: pencelheimer/typst_nure_template#14
LGTM
2025-05-25 04:11:28 +03:00
7e15ea09d4 add name of educational program && names of subjects for this semester 2025-05-25 04:08:53 +03:00
3b3d62fd9b lib.typ: too lazy to name it
Signed-off-by: unexplrd <unexplrd@linerds.us>
2025-05-20 20:46:32 +03:00
04c5b283e7 lib.typ: better code branching
Signed-off-by: unexplrd <unexplrd@linerds.us>
2025-05-20 20:31:41 +03:00
3ca8409e58 fix: heading numbering when no title 2025-04-26 18:38:36 +03:00
e0811afaae fix: move appendix styling out of the coursework template
It is now possible to style headings as appendices in the pz-lb template via `#show: appendices_style`.
2025-04-20 21:21:16 +03:00
7ec79ee898 fix: make variant in authors truly optional
In `pz-lb` template the `authors.variant` may be omitted now.
2025-04-16 22:05:22 +03:00
47117749cf fix: set fallback monospace font
Signed-off-by: unexplrd <unexplrd@linerds.us>
2025-04-08 21:22:16 +03:00
e214656166 docs(readme): update readme, add a note
Signed-off-by: unexplrd <unexplrd@linerds.us>
2025-03-24 00:27:50 +02:00
99234a73a9 README.md: add an image
assets/pz-lb_title_page.png: the image added

Signed-off-by: unexplrd <unexplrd@linerds.us>
2025-03-24 00:10:21 +02:00
901d670a1c Merge pull request 'feat: add some random subjects to config/universities.yaml' (#13) from sekomi/typst_nure_template:0.1.0 into 0.1.0
Reviewed-on: pencelheimer/typst_nure_template#13
2025-03-23 23:58:41 +02:00
594e908bb1 feat: add some random subjects to config/universities.yaml 2025-03-23 23:41:32 +02:00
32c6a1663c feat: make mentor.degree in pz-lb optional
fix: remove personal font preference

Signed-off-by: unexplrd <unexplrd@linerds.us>
2025-03-23 16:15:49 +02:00
9cc7229851 style: reformat with "typstyle -c 120"
Signed-off-by: unexplrd <unexplrd@linerds.us>
2025-03-20 16:45:59 +02:00
1646f5249b fix: update templates accordingly
Signed-off-by: unexplrd <unexplrd@linerds.us>
2025-03-20 16:44:46 +02:00
2de5cb4c58 feat!: rename "cw-template" to "coursework"
feat!: rename "pz-lb-template" to "pz-lb"

Signed-off-by: unexplrd <unexplrd@linerds.us>
2025-03-20 16:41:59 +02:00
dec7c58db2 lib.typ: make title in pz-lb-template optional
Signed-off-by: unexplrd <unexplrd@linerds.us>
2025-03-20 15:52:22 +02:00
2653e7dba2 style: remove quotes
fix: fix 125 department name

Signed-off-by: unexplrd <unexplrd@linerds.us>
2025-03-20 15:42:23 +02:00
816d07f745 fix!: change "СПМ" to "СМП"
feat: add more subjects, sort them
2025-03-20 14:41:15 +02:00
7799435039 Merge pull request 'version 0.1.0' (#12) from unexplrd/typst_nure_template:main into 0.1.0
Reviewed-on: pencelheimer/typst_nure_template#12
2025-03-18 12:28:54 +02:00
23 changed files with 1626 additions and 336 deletions

124
README.md
View File

@@ -1,4 +1,5 @@
# Typst Template for NURE Works
<img src="assets/pz-lb_title_page.png" alt="pz-lb title page" width=350>
## General Info
@@ -6,16 +7,18 @@ This project contains two template functions and some utilities for writing NURE
### Templates
#### `pz-lb-template` - For Laboratory and Practical Works
#### `pz-lb` - For Practice and Laboratory Works
This template:
- Sets up document styles;
- Formats the title page according to NURE/DSTU guidelines.
#### `cw-template` - For Course Works
#### `coursework` - For Course Works
This template:
- Sets up document styles;
- Formats the title, task, calendar plan, and abstract pages;
- Typesets the bibliography, outline, and appendices according to standard requirements.
- Typesets the bibliography according to ДСТУ 3008:2015 using custom CSL style;
- Typesets the outline and appendices according to standard requirements.
### Utilities
- `nheading` - For unnumbered headings, such as "Introduction" and "Conclusion".
@@ -24,61 +27,138 @@ This template:
- `bold` - Inserts bold text inside functional environments.
- `img` - Inserts images with a caption, automatically deriving the label from the image file name.
**Note:** `img()` is provided in `utils.typ` in project's root directory for compatibility, until [path() type](https://github.com/typst/typst/pull/7555) is released.
## Usage
### As a local typst package
1. Clone this repository into ~/.local/share/typst/packages/:
```bash
git clone -b 0.1.0 https://gitea.linerds.us/pencelheimer/typst_nure_template.git ~/.local/share/typst/packages/local/nure/0.1.0
git clone -b 0.1.1 https://gitea.linerds.us/pencelheimer/typst_nure_template.git ~/.local/share/typst/packages/local/nure/0.1.1
```
2. Init your project with Typst:
```bash
typst init @local/nure:0.1.0 project-name
typst init @local/nure:0.1.1 project-name
```
### As a standalone file
Copy `lib.typ` to your project's root directory.
Copy `src/` to your project's root directory, optionally renaming `src/` to `lib/`.
### In your project
```typst
// Import the template either from a local package...
#import "@local/nure:0.1.0": *
#import "@local/nure:0.1.1": *
// ...or by importing a lib.typ directly
// #import "/lib.typ": *
// #import "/lib/lib.typ": *
// Setup the document
#show: pz-lb-template.with(
title: "Some title",
// etc: "and so on",
// ...
// 1. Setup the document
// by setting values directly...
#show: pz-lb.with(
title: "Some title",
// etc: "and so on",
// ...
)
// ...or using a yaml/toml file
#show: pz-lb.with(..toml("/doc.toml"))
// this template automatically inserts a `=title`
// Write your content...
// Write your content
#v(-spacing) // remove spacing between headings
== Purpose
Some text
// ...or include your modules
#include "src/intro.typ"
#include "src/chapter1.typ"
#include "src/chapter2.typ"
#include "chapters/intro.typ"
#include "chapters/chapter1.typ"
#include "chapters/chapter2.typ"
// NOTE: if you want to use variables or utils provided by the package,
// you have to import the package or a lib.typ inside a module.
// If you ever need appendices in pz-lb template use the show rule
// WARNING: when using coursework template use its own argument,
// so it can put bibliography before appendices
#show: style.appendices
= Quote
#link("https://youtu.be/bJQj1uKtnus")[
The art isn't the art, the art is never the art,
the art is the thing that happens inside you when you make it and the feeling in the heart of the beholder.
]
```
And a TOML file would look like this:
```toml
# university = "ХНУРЕ" # "ХНУРЕ" is the default
# edu-program = "ПЗПІ" # can be null, sourced from authors.first() by default
subject = "СМП"
doctype = "ЛБ"
worknumber = 2
title = отiк керування та алгоритмічні структури Bash"
[[mentors]]
name = "Шевченко Т. Г."
degree = "Доцент кафедри ПІ"
gender = "m"
[[mentors]]
name = "Франко І. Я."
degree = "Асистент кафедри ПІ"
gender = "m"
[[authors]]
name = "Косач Л. П."
edu-program = "ПЗПІ"
group = "23-2"
gender = "f"
variant = 8
# For coursework
full-name-gen = "Косач Лариси Петрівни"
course = 2
semester = 4
```
### 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.
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.
### Bibliography Format
The template uses a custom CSL (Citation Style Language) file located at `src/csl/dstu-3008-2015.csl` to format bibliography entries.
Supported bibliography entry types in `bibl.yml`:
- **Book**: Books with author, title, publisher, year, and page count
- **Web**: Web resources with title, author/organization, URL, and access date
**Warning:** Other types were added by Kimi K2.5 without any additional checks for compliance.
Example `bibl.yml`:
```yaml
mysql:
type: Book
title: MySQL Language Reference
author: Ab M.
publisher: MySQL Press
date: 2004
page-total: 600
go:
type: Web
title: The Go Programming Language
author: The Go Programming Language
url:
value: https://go.dev/
date: 2024-12-10
```
### Example Project Structure
```
project/
├── doc.toml -- for things that don't change across works, i.e. author and mentor metadata
├── main.typ -- for boilerplate code and importing everything
├── config/
│ ├── doc.yaml -- for things that don't change across works, i.e. author and mentor metadata
│ ├── universities.yaml -- for user-specific configuration, i.e. education programs and disciplines
│ └── ...
├── src/
├── utils.typ -- for helper functions
├── chapters/
│ ├── intro.typ
│ ├── chapter1.typ
│ ├── chapter2.typ

BIN
assets/pz-lb_title_page.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

View File

@@ -1,24 +1,77 @@
ХНУРЕ:
name: "Харківський національний університет радіоелектроніки"
name: Харківський національний університет радіоелектроніки
name_en: Kharkiv National University of Radioelectronics
edu_programs:
ПЗПІ:
name_long: "Інженерія програмного забезпечення"
department_gen: "Програмної інженерії"
code: 121 # TODO = ПЗПІ is "F2" now
name_long: Інженерія програмного забезпечення
department_gen: Програмної інженерії
code: 121 # TODO: change to F2?
КУІБ:
name_long: "Управління інформаційною безпекою"
department_gen: "Інфокомунікацій"
code: 125
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:
БД: "Бази даних"
БЖД: "Безпека життєдіяльності"
ОІМ: "Основи IP-мереж"
"ОПНJ": "Основи програмування на Java"
ОС: "Операційні системи"
ОТК: "Основи теорії кіл"
ПП: "Проектний практикум"
ПРОГ: "Програмування"
СПМ: "Скриптові мови програмування"
УФМ: "Українське фахове мовлення"
Ф: "Філософія"
ФІЗ: "Фізика"
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?
ТКП: Технології комп`ютерного проєктування
УФМ: Українське фахове мовлення
ФІЗ: Фізика
ФІЛ: Філософія
ФВС: Фізичне виховання та спорт
ХТ: Хмарні технології

445
lib.typ
View File

@@ -5,6 +5,9 @@
// Template formatting functions {{{1
/// bold text
#let bold(content) = text(weight: "bold")[#content]
/// numberless heading
#let nheading(title) = heading(depth: 1, numbering: none, title)
@@ -21,9 +24,6 @@
#if align != right { hfill(1fr) }
]
/// bold text
#let bold(content) = text(weight: "bold")[#content]
/// month name from its number
#let month_gen(month) = (
"січня",
@@ -40,6 +40,27 @@
"грудня",
).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:
@@ -66,9 +87,9 @@
}.replace(" ", "_")
let caption = if sink.pos().len() == 0 {
caption + " (рисунок виконано самостійно)"
} else if sink.pos().first() == none {
caption
} else if sink.pos().first() == none {
caption + " (рисунок виконано самостійно)"
} else {
[#caption (за даними #sink.pos().first())]
}
@@ -79,54 +100,62 @@
) #label(label_string)]
}
// Styling {{{1
/// NOTE: may be wrong
#let ua_alpha_numbering = "абвгдежиклмнпрстуфхцшщюя".split("") // 0 = "", 1 = "а"
#let spacing = 0.95em // spacing between lines
#let num-to-alpha = "абвгдежиклмнпрстуфхцшщюя".split("") // 0 = "", 1 = "а"
// general outlook {{{2
// spacing between lines
#let spacing = 0.95em
#let style(it) = {
/// 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",
margin: (top: 20mm, right: 10mm, bottom: 20mm, left: 25mm),
number-align: top + right,
numbering: (..numbers) => {
if numbers.pos().at(0) != 1 {
numbering("1", numbers.pos().at(0))
}
},
margin: (top: 20mm, right: 10mm, bottom: 20mm, left: 25mm),
numbering: (i, ..) => if i > skip { numbering("1", i + offset) },
)
set text(
font: ("Times New Roman", "Liberation Serif"),
lang: "uk",
size: 14pt,
hyphenate: false,
lang: "uk",
font: ("Times New Roman", "Liberation Serif"),
)
set par(justify: true, first-line-indent: (amount: 1.25cm, all: true))
set par(
justify: true,
spacing: spacing,
leading: spacing,
first-line-indent: (amount: 1.25cm, all: true),
)
set block(spacing: spacing)
set underline(evade: false)
// set 1.5 line spacing
set block(spacing: spacing)
set par(spacing: spacing)
set par(leading: spacing)
// enums and lists {{{2
// Enums & Lists {{{1
// First level
set enum(
numbering: i => { ua_alpha_numbering.at(i) + ")" },
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 {{{2
// Figures {{{1
show figure: it => {
v(spacing * 2, weak: true)
it
@@ -136,75 +165,37 @@
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)
// figure numbering
// 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)
counter(figure.where(kind: raw)).update(0)
it
}
set math.equation(
numbering: (..num) => numbering(
"(1.1)",
counter(heading).get().at(0),
num.pos().first(),
),
)
set figure(
numbering: (..num) => numbering(
"1.1",
counter(heading).get().at(0),
num.pos().first(),
),
)
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))
// appearance of references to images and tables {{{2
set ref(
supplement: it => {
if it == none or not it.has("kind") {
it
} else if it.kind == image {
"див. рис."
} else if it.kind == table {
"див. таблицю"
} else {
it
}
},
)
show ref: it => {
let el = it.element
if el == none or not el.has("kind") {
return it
}
if el.kind != image and el.kind != table {
return it
}
[(#it)]
}
// headings {{{2
// Headings {{{1
set heading(numbering: "1.1")
show heading.where(level: 1): it => {
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)
}
show heading.where(level: 2): it => {
} else {
set text(size: 14pt, weight: "regular")
v(spacing * 2, weak: true)
block(width: 100%, spacing: 0em)[
#h(1.25cm)
#counter(heading).display(it.numbering)
#counter(heading).display(auto)
#it.body
]
v(spacing * 2, weak: true)
@@ -222,18 +213,15 @@
v(spacing * 2, weak: true)
}
// listings {{{2
show raw: it => {
// 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 par(spacing: new_spacing, leading: new_spacing)
set text(
size: 11pt,
font: ("Iosevka NFM", "Courier New"),
weight: "semibold",
font: ("Courier New", "Liberation Mono"),
)
v(spacing * 2.5, weak: true)
@@ -242,8 +230,47 @@
}
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
@@ -253,29 +280,27 @@
/// - 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.
/// - 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.
/// - 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 cw-template(
#let coursework(
doc,
title: none,
subject: none,
university: "ХНУРЕ",
author: (),
subject: none,
title: none,
authors: (),
mentors: (),
edu_program: none,
task_list: (),
calendar_plan: (),
abstract: (),
bib_path: none,
appendices: (),
) = {
set document(title: title, author: author.name)
set document(title: title, author: authors.map(c => c.name))
show: style
show: dstu-style.with(skip: 1)
let bib-count = state("citation-counter", ())
show cite: it => {
@@ -287,10 +312,10 @@
it
}
let head_mentor = mentors.at(0)
let author = authors.first()
let head_mentor = mentors.first()
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
[
@@ -306,15 +331,16 @@
ПОЯСНЮВАЛЬНА ЗАПИСКА\
ДО КУРСОВОЇ РОБОТИ\
з дисципліни: "#uni.subjects.at(subject, default: "NONE")"\
з дисципліни: "#uni.subjects.at(subject, default: subject)"\
Тема роботи: "#title"
\ \ \
#columns(2, gutter: 4cm)[
#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
\
Керівник:\
@@ -325,15 +351,12 @@
\
Комісія:\
#for mentor in mentors {
[#mentor.degree\
]
}
#for m in mentors { [#m.degree\ ] }
#colbreak()
#set align(left)
\
#author.name
\ \
@@ -343,10 +366,7 @@
#underline(" " * 35)
\ \
#for mentor in mentors {
[#mentor.name\
]
}
#for m in mentors { [#m.name\ ] }
]
#v(1fr)
@@ -373,7 +393,7 @@
{
uline(align: left, edu_prog.department_gen)
linebreak()
uline(align: left, uni.subjects.at(subject))
uline(align: left, uni.subjects.at(subject, default: subject))
linebreak()
uline(align: left, [#edu_prog.code #edu_prog.name_long])
},
@@ -382,7 +402,7 @@
columns: (1fr, 1fr, 1fr),
gutter: 0.3fr,
[#bold[Курс] #uline(author.course)],
[#bold[Група] #uline([#edu_program\-#author.group])],
[#bold[Група] #uline([#author.edu_program\-#author.group])],
[#bold[Семестр] #uline(author.semester)],
)
@@ -502,31 +522,16 @@
\
#{
let keywords = abstract.keywords.map(upper)
let is_cyrillic = word => word.split("").any(char => ("А" <= char and char <= "я"))
#(
abstract
.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(", ")
}
\
@@ -558,26 +563,18 @@
let bib_data = yaml(bib_path)
let format-entry(citation) = {
if (citation.type == "Web") {
let date_array = citation.url.date.split("-")
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)),
)
[
#citation.title.
#citation.author.
URL: #citation.url.value (дата звернення: #date.display("[day].[month].[year]")).
]
} else if citation.type == "Book" [
#citation.author
#citation.title.
#citation.publisher,
#citation.date.
#citation.page-total c.
] else [
[#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
]
}
@@ -591,7 +588,10 @@
}
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(
i + 1,
format-entry(bib_data.at(citation)),
@@ -600,136 +600,82 @@
}
}
// appendices {{{2
{
counter(heading).update(0)
set heading(
numbering: (i, ..nums) => {
let char = upper(ua_alpha_numbering.at(i))
if nums.pos().len() == 0 { char } else {
char + "." + nums.pos().map(str).join(".")
}
},
)
show heading.where(level: 1): it => {
set align(center)
set text(size: 14pt, weight: "regular")
pagebreak(weak: true)
bold[ДОДАТОК #counter(heading).display(it.numbering)]
linebreak()
it.body
v(spacing * 2, weak: true)
}
show heading.where(level: 2): 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)
}
appendices
}
appendices-style(appendices)
}
// Laboratory work template {{{1
// Practice and Laboratory works template {{{1
/// DSTU 3008:2015 Template for NURE
/// -> content
/// - doc (content): Content to apply the template to.
/// - doctype ("ЛБ" | "ПЗ"): Document type.
/// - edu_program (str): Education program shorthand.
/// - title (str): Title of the document.
/// - subject (str): Subject shorthand.
/// - authors ((name: str, full_name_gen: str, group: str, gender: str, variant: int or none),): List of authors.
/// - mentors ((name: str, degree: str, gender: str or none),): List of mentors.
/// - worknumber (int or none): Number of the work. Optional.
#let pz-lb-template(
/// - 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,
doctype: none,
university: "ХНУРЕ",
edu_program: none,
title: none,
subject: none,
worknumber: none,
type: none,
number: none,
title: none,
authors: (),
mentors: (),
) = {
set document(title: title, author: authors.at(0).name)
show: style
// TODO: add actually relevant asserts
let edu_program = authors.first().edu_program
let uni = universities.at(university)
let edu_prog = uni.edu_programs.at(edu_program)
set document(title: title, author: authors.map(c => c.name))
show: dstu-style.with(skip: 1)
// page 1 {{{2
align(center)[
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ \
#upper(uni.name)
\ \
Кафедра #edu_prog.department_gen
Кафедра #uni.edu_programs.at(edu_program).department_gen
\ \ \
Звіт \
з
#if doctype == "ЛБ" [лабораторної роботи] else [практичної роботи]
#if worknumber != none {
context counter(heading).update(worknumber - 1)
[№#worknumber]
}
#pz-lb-title(type, number: number)
з дисципліни: "#uni.subjects.at(subject, default: "UNKNOWN SUBJECT, PLEASE OPEN AN ISSUE")"
з дисципліни: "#uni.subjects.at(subject, default: subject)"
#if title != none [\ з теми: "#eval(title, mode: "markup")"]
з теми: "#title"
\ \ \ \
#columns(2)[
#set align(left)
#set par(first-line-indent: 0pt)
#if authors.len() == 1 {
let author = authors.at(0)
if author.gender == "m" [Виконав:\ ] else [Виконала:\ ]
[
ст. гр. #edu_program\-#author.group\
#author.name\
]
if author.variant != none [Варіант: #author.variant]
} else [
Виконали:\
ст. гр. #edu_program\-#authors.at(0).group\
#for author in authors [#author.name\ ]
[#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 {
let mentor = mentors.at(0)
if mentor.gender == none [Перевірили:\ ] else if (
mentor.gender == "m"
) [Перевірив:\ ] else [Перевірилa:\ ]
[
#mentor.degree\
#mentor.name\
]
} else [
Перевірили:\
#for mentor in mentors {
[
#mentor.degree\
#mentor.name\
]
}
]
[#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)
@@ -739,8 +685,17 @@
pagebreak(weak: true)
heading(title)
// 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:fdl=0:fdm=marker:cms=/*%s*/
// vim:sts=2:sw=2:fdm=marker:cms=/*%s*/

View File

@@ -0,0 +1,78 @@
ХНУРЕ:
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?
description: Кібербезпека та захист інформації
КНТ:
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?
ТКП: Технології комп`ютерного проєктування
УФМ: Українське фахове мовлення
ФІЗ: Фізика
ФІЛ: Філософія
ФВС: Фізичне виховання та спорт
ХТ: Хмарні технології

357
src/csl/dstu-3008-2015.csl Normal file
View File

@@ -0,0 +1,357 @@
<?xml version="1.0" encoding="UTF-8" ?>
<style
xmlns="http://purl.org/net/xbiblio/csl"
class="in-text"
version="1.0"
demote-non-dropping-particle="display-and-sort"
default-locale="uk-UA"
>
<info>
<title>ДСТУ 3008:2015 (DSTU 3008:2015)</title>
<title-short>ДСТУ 3008:2015</title-short>
<id>dstu-3008-2015</id>
<link
href="https://uk.wikipedia.org/wiki/ДСТУ_3008:2015"
rel="documentation"
/>
<author>
<name>Linerds</name>
</author>
<category citation-format="numeric" />
<category field="generic-base" />
<summary
>Український стандарт бібліографічного опису ДСТУ 3008:2015</summary>
<updated>2024-01-01T00:00:00+00:00</updated>
<rights
license="http://creativecommons.org/licenses/by-sa/3.0/"
>This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
</info>
<!-- Locale definitions -->
<locale xml:lang="uk">
<terms>
<term name="accessed">дата звернення</term>
<term name="available at">URL</term>
<term name="page" form="short">с</term>
<term name="page" form="symbol">с</term>
<term name="et-al">та ін.</term>
<term name="and">,</term>
<term name="editor" form="short">ред.</term>
<term name="translator" form="short">пер.</term>
<term name="edition" form="short">вид.</term>
<term name="volume" form="short">т.</term>
<term name="issue" form="short">вип.</term>
<term name="number" form="short">№</term>
</terms>
</locale>
<!-- Macros -->
<!-- Authors/Editors macro -->
<macro name="author">
<names variable="author">
<name initialize-with=". " delimiter=", " delimiter-precedes-last="never">
<name-part name="family" text-case="capitalize-first" />
<name-part name="given" />
</name>
<substitute>
<names variable="editor">
<name
initialize-with=". "
delimiter=", "
delimiter-precedes-last="never"
>
<name-part name="family" text-case="capitalize-first" />
<name-part name="given" />
</name>
<label form="short" prefix=" (" suffix=")" />
</names>
<names variable="translator">
<name
initialize-with=". "
delimiter=", "
delimiter-precedes-last="never"
>
<name-part name="family" text-case="capitalize-first" />
<name-part name="given" />
</name>
<label form="short" prefix=" (" suffix=")" />
</names>
<text macro="title" />
</substitute>
</names>
</macro>
<!-- Title macro -->
<macro name="title">
<choose>
<if type="book thesis report" match="any">
<text variable="title" font-style="italic" />
</if>
<else-if type="webpage post post-weblog" match="any">
<text variable="title" />
</else-if>
<else>
<text variable="title" />
</else>
</choose>
</macro>
<!-- Publisher info -->
<macro name="publisher">
<group delimiter=": ">
<text variable="publisher-place" />
<text variable="publisher" />
</group>
</macro>
<!-- Year -->
<macro name="year">
<date variable="issued">
<date-part name="year" />
</date>
</macro>
<!-- Pages -->
<macro name="pages">
<choose>
<if type="book thesis report" match="any">
<text variable="number-of-pages" />
<text term="page" form="symbol" />
</if>
<else>
<group delimiter=" ">
<text term="page" form="short" />
<text variable="page" />
</group>
</else>
</choose>
</macro>
<!-- Access date and URL for web resources -->
<macro name="access">
<choose>
<if type="webpage post post-weblog" match="any">
<group delimiter=" " prefix=". ">
<text term="available at" suffix=":" />
<text variable="URL" />
<group delimiter=": " prefix="(" suffix=")">
<text term="accessed" />
<date variable="accessed">
<date-part name="day" form="numeric-leading-zeros" suffix="." />
<date-part name="month" form="numeric-leading-zeros" suffix="." />
<date-part name="year" />
</date>
</group>
</group>
</if>
</choose>
</macro>
<!-- Volume/Issue -->
<macro name="volume-issue">
<group delimiter=". ">
<group delimiter=" ">
<text term="volume" form="short" />
<text variable="volume" />
</group>
<group delimiter=" ">
<text term="issue" form="short" />
<text variable="issue" />
</group>
</group>
</macro>
<!-- Main citation format (numeric) -->
<citation collapse="citation-number">
<sort>
<key variable="citation-number" />
</sort>
<layout delimiter=", " prefix="[" suffix="]">
<text variable="citation-number" />
</layout>
</citation>
<!-- Bibliography format -->
<bibliography hanging-indent="false">
<sort>
<key variable="citation-number" />
</sort>
<layout>
<group display="block">
<text display="left-margin" variable="citation-number" suffix=". " />
<!-- Book -->
<choose>
<if type="book">
<group delimiter=". " suffix=".">
<text macro="author" />
<text macro="title" />
<text macro="publisher" />
<text macro="year" />
<text macro="pages" />
</group>
</if>
<!-- Thesis -->
<else-if type="thesis">
<group delimiter=". ">
<text macro="author" />
<text macro="title" />
<text variable="genre" />
<text macro="publisher" />
<text macro="year" />
<text macro="pages" />
</group>
</else-if>
<!-- Report -->
<else-if type="report">
<group delimiter=". ">
<text macro="author" />
<text macro="title" />
<text variable="genre" />
<text macro="publisher" />
<text macro="year" />
<text macro="pages" />
</group>
</else-if>
<!-- Article in journal -->
<else-if type="article-journal">
<group delimiter=". ">
<text macro="author" />
<group delimiter=" // ">
<text variable="title" />
<text variable="container-title" font-style="italic" />
</group>
<group delimiter=". ">
<text macro="year" />
<text macro="volume-issue" />
<text macro="pages" />
</group>
</group>
</else-if>
<!-- Article in newspaper -->
<else-if type="article-newspaper">
<group delimiter=". ">
<text macro="author" />
<group delimiter=" // ">
<text variable="title" />
<text variable="container-title" />
</group>
<group delimiter=". ">
<date variable="issued">
<date-part name="day" suffix="." />
<date-part name="month" suffix="." />
<date-part name="year" />
</date>
<text macro="pages" />
</group>
</group>
</else-if>
<!-- Conference paper -->
<else-if type="paper-conference">
<group delimiter=". ">
<text macro="author" />
<text variable="title" />
<group delimiter=" // ">
<text term="in" />
<text variable="container-title" />
</group>
<group delimiter=". ">
<text macro="publisher" />
<text macro="year" />
<text macro="pages" />
</group>
</group>
</else-if>
<!-- Chapter in book -->
<else-if type="chapter">
<group delimiter=". ">
<text macro="author" />
<text variable="title" />
<group delimiter=" // ">
<text term="in" />
<names variable="editor">
<name
initialize-with=". "
delimiter=", "
delimiter-precedes-last="never"
>
<name-part name="family" text-case="capitalize-first" />
<name-part name="given" />
</name>
<label form="short" prefix=" (" suffix=")" />
</names>
<text variable="container-title" font-style="italic" />
</group>
<group delimiter=". ">
<text macro="publisher" />
<text macro="year" />
<text macro="pages" />
</group>
</group>
</else-if>
<!-- Webpage/Online -->
<else-if type="webpage post post-weblog" match="any">
<group delimiter=". ">
<text variable="title" />
<text macro="author" />
<text variable="container-title" />
<date variable="issued">
<date-part name="day" suffix="." />
<date-part name="month" suffix="." />
<date-part name="year" />
</date>
</group>
<text macro="access" suffix="." />
</else-if>
<!-- Patent -->
<else-if type="patent">
<group delimiter=". ">
<text macro="author" />
<text variable="title" />
<text variable="number" />
<date variable="issued">
<date-part name="day" suffix="." />
<date-part name="month" suffix="." />
<date-part name="year" />
</date>
</group>
</else-if>
<!-- Legislation -->
<else-if type="legislation">
<group delimiter=". ">
<text variable="title" />
<date variable="issued">
<date-part name="day" suffix="." />
<date-part name="month" suffix="." />
<date-part name="year" />
</date>
<text variable="number" />
</group>
</else-if>
<!-- Generic fallback -->
<else>
<group delimiter=". ">
<text macro="author" />
<text macro="title" />
<text variable="container-title" />
<text macro="publisher" />
<text macro="year" />
<text macro="pages" />
</group>
</else>
</choose>
</group>
</layout>
</bibliography>
</style>

52
src/helpers.typ Normal file
View File

@@ -0,0 +1,52 @@
/// month name from its number
#let month-gen(month) = (
"січня",
"лютого",
"березня",
"квітня",
"травня",
"червня",
"липня",
"серпня",
"вересня",
"жовтня",
"листопада",
"грудня",
).at(month - 1)
#let is-cyr(c) = regex("[\p{Cyrillic}]") in c
/// type-safe emptiness check
#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") = {
(
"author": ("m": "Виконав", "f": "Виконала", "p": "Виконали"),
"mentor": ("m": "Перевірив", "f": "Перевірила", "p": "Перевірили"),
)
.at(verb)
.at(if gender == "m" or gender == "f" { gender } else { "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 pz-lb-title(type, number: none) = {
let type-title = (
"ЛБ": [Звіт \ з лабораторної роботи],
"ПЗ": [Звіт \ з практичної роботи],
"КР": [Контрольна робота],
"РФ": [Реферат],
"ІДЗ": [Індивідуальне домашнє завдання],
).at(type, default: type)
if not is-empty(number) { [#type-title #number] } else { [#type-title] }
}

114
src/lib.typ Normal file
View File

@@ -0,0 +1,114 @@
#import "./title-pages/main.typ" as tp
#import "./shared.typ": universities
#import "./helpers.typ": *
#import "./style.typ"
#import "./utils.typ"
/// Coursework template for NURE
/// - university (str): University code, default "ХНУРЕ"
/// - subject (str): Subject short name
/// - title (str): Work title
/// - authors (array): List of author dictionaries
/// - mentors (array): List of mentor dictionaries
/// - task-list (dict): Task metadata
/// - calendar-plan (dict): Calendar plan table and approval date
/// - abstract (dict): Keywords and abstract text
/// - bib-path (str): Path to bibliography file
/// - appendices (content): Appendix content
#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: style.dstu.with(skip: 1)
tp.cw.nure(
university,
subject,
title,
authors,
mentors,
task-list,
calendar-plan,
abstract,
)
doc
// Bibliography with DSTU formatting
{
show regex("^\\d+\\."): it => [#it#h(0.5cm)]
show block: it => [#it.body#parbreak()]
bibliography(bib-path, title: [Перелік джерел посилання], style: "csl/dstu-3008-2015.csl", full: true)
}
style.appendices(appendices)
}
/// Practice and Laboratory works template
/// - layout (str): "default", "minimal", or "complex"
/// - university (str): University code
/// - edu-program (str): Education program code
/// - subject (str): Subject code
/// - type (str): Work type (ЛБ, ПЗ, КР, РФ, ІДЗ)
/// - number (int): Work number
/// - title (str): Work title
/// - authors (array): List of authors
/// - mentors (array): List of mentors
#let pz-lb(
doc,
layout: "default",
university: "ХНУРЕ",
edu-program: none,
subject: none,
type: none,
number: none,
title: none,
authors: (),
mentors: (),
) = {
assert(authors.len() > 0, message: "At least one author required")
let edu-program = if edu-program != none { edu-program } else { authors.first().edu-program }
let uni = universities.at(university)
set document(title: title, author: authors.map(c => c.name))
show: style.dstu.with(skip: 1)
// Select layout variant
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)
pagebreak(weak: true)
// Set heading counter based on title/number
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
}

1
src/shared.typ Normal file
View File

@@ -0,0 +1 @@
#let universities = yaml("config/universities.yaml")

132
src/style.typ Normal file
View File

@@ -0,0 +1,132 @@
#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 ukr-enum = "абвгдежиклмнпрстуфхцшщюя".split("")
/// Helper for level 2/3 heading blocks
#let heading-block(it, num: auto) = {
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
#let dstu(
it,
skip: 0,
offset: 0,
) = {
// Page setup
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) },
)
// Text and paragraph
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 block(spacing: spacing)
set underline(evade: false)
// Lists
set enum(indent: indent-size, body-indent: 0.5cm, numbering: i => ukr-enum.at(i) + ")")
show enum: it => {
set enum(indent: 0em, numbering: "1)")
it
}
set list(indent: indent-size + 0.1cm, body-indent: 0.5cm, marker: [--])
// Figures
show figure: it => {
v(double-spacing, weak: true)
it
v(double-spacing, 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 reset on level 1 headings
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 => context numbering("1.1", counter(heading).get().at(0), i))
set math.equation(numbering: i => context numbering("(1.1)", counter(heading).get().at(0), i))
// Headings
set heading(numbering: "1.1")
show heading: it => {
set text(size: 14pt)
if it.level == 1 {
set align(center)
set text(weight: "semibold")
pagebreak(weak: true)
upper(it)
v(double-spacing, weak: true)
} else {
set text(weight: "regular")
heading-block(it, num: if it.level == 3 { it.numbering } else { auto })
}
}
// Code listings
show raw.where(block: true): it => {
let code-spacing = 0.5em
set block(spacing: code-spacing)
set par(spacing: code-spacing, leading: code-spacing)
set text(size: 11pt, weight: "semibold", font: ("Courier New", "Liberation Mono"))
v(double-half-spacing, weak: true)
pad(it, left: indent-size)
v(double-half-spacing, weak: true)
}
it
}
/// DSTU 3008:2015 Appendices Style
#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

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

View File

@@ -0,0 +1,254 @@
#import "../../shared.typ": universities
#import "../../helpers.typ": *
#import "../../style.typ": *
#import "../../utils.typ": bold, hfill, uline
#let nure(
university,
subject,
title,
authors,
mentors,
task-list,
calendar-plan,
abstract,
) = {
let bib-count = state("citation-counter", ())
show cite: it => {
it
bib-count.update(((..c)) => (..c, it.key))
}
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", dict: author) ст. гр. #author.edu-program\-#author.group
\
Керівник:\ #head-mentor.degree
\
Робота захищена на оцінку
\
Комісія:\ #for m in mentors { degree-get(m) }
#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,
)
}

2
src/title-pages/main.typ Normal file
View File

@@ -0,0 +1,2 @@
#import "pz-lb/main.typ" as pz-lb
#import "coursework/main.typ" as cw

View File

@@ -0,0 +1,60 @@
#import "../../helpers.typ": *
#let complex(uni, edu-program, subject, type, number, title, authors, mentors) = {
align(center)[
Міністерство освіти і науки України \
#uni.name
\
#set par(first-line-indent: 0pt)
#align(left)[
#let edu = uni.edu-programs.at(edu-program)
Кафедра #underline(edu.department-gen) \
Спеціальність #underline([#edu.code #edu.description]) \
Освітня програма #underline(edu.name-long)
]
\ \
#pz-lb-title(type, number: number)
з навчальної дисципліни "#uni.subjects.at(subject, default: subject)"\
#if title != none [з теми "#eval(title, mode: "markup")"\ ]
#if authors.first().variant != none [\ Варіант #authors.first().variant\ ]
\ \ \
#align(right)[
#if authors.len() == 1 {
let a = authors.first()
[#gender-form("author", dict: a):\
студент групи #a.edu-program\-#a.group\ #a.name\ ]
text(size: 8pt, [(прізвище та ініціали)\ ])
} else if authors.len() > 1 [
#gender-verb("author"):\
#for a in authors [студент групи #a.edu-program\-#a.group\ #a.name\ ]
#text(size: 8pt, [(прізвище та ініціали)\ ])
]
\
#if mentors.len() == 1 {
let m = mentors.first()
[#gender-form("mentor", dict: m):\ ]
degree-get(m)
[#m.name\ ]
text(size: 8pt, [(прізвище та ініціали)\ ])
} else if mentors.len() > 1 [
#gender-verb("mentor"):\
#for m in mentors {
degree-get(m)
[#m.name\ ]
}
]
]
#v(1fr)
Харків\
#datetime.today().display("[year]")
]
}

View File

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

View File

@@ -0,0 +1,52 @@
#import "../../helpers.typ": *
#let nure(uni, edu-program, subject, type, number, title, authors, mentors) = {
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 {
let a = authors.first()
[#gender-form("author", dict: a):\ ]
[ст. гр. #a.edu-program\-#a.group\ ]
[#a.name\ ]
if not is-empty(a.variant) [Варіант: #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 {
let m = mentors.first()
[#gender-form("mentor", dict: m):\ ]
degree-get(m)
[#m.name\ ]
} else if mentors.len() > 1 [
#gender-form("mentor"):\
#for m in mentors {
degree-get(m)
[#m.name\ ]
}
]
]
#v(1fr)
Харків -- #datetime.today().display("[year]")
]
}

56
src/utils.typ Normal file
View File

@@ -0,0 +1,56 @@
/// 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 filled box
#let hfill(width) = box(width: width, repeat("")) // HAIR SPACE (U+200A)
/// underlined cell with centered content by default
#let uline(align: center, content) = underline[
#if align != left { hfill(1fr) }
#content
#if align != right { hfill(1fr) }
]
/// Extract filename stem without extension
#let stem(path) = path.split("/").last().split(".").first()
/// Extract parent directory name
#let parent-dir(path) = path.split("/").at(-2, default: "")
/// Generate label from image path:
/// - "image.png" → "image"
/// - "img/foo/bar.png" → "foo_bar"
#let img-label(path) = {
let name = stem(path)
let parent = parent-dir(path)
// 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 source = sink.pos().at(0, default: ())
[ #figure(image(path, ..sink.named()), caption: utils.img-caption(caption, source)) #utils.img-label(path) ]
}

View File

@@ -6,7 +6,6 @@ go:
value: https://go.dev/
date: 2024-12-10
htmx:
type: Web
title: Htmx - high power tools for html

View File

@@ -1,22 +0,0 @@
title: Потiк керування та алгоритмічні структури Bash
subject: СПМ
doctype: ЛБ
worknumber: 2
mentors:
- name: Шевченко Т. Г.
degree: Доцент кафедри ПІ
gender: m
- name: Франко І. Я.
degree: Асистент кафедри ПІ
gender: m
edu_program: &EDU ПЗПІ
university: ХНУРЕ
authors:
- name: Косач Л. П.
full_name_gen: Косач Лариси Петрівни
course: 2
edu: *EDU
gender: f
group: 23-2
semester: 4
variant: 8

View File

@@ -1,13 +1,19 @@
#import "@local/nure:0.1.0": *
#import "@local/nure:0.1.1": *
#import style: spacing
#let author = (
name: "Ситник Є. С.",
full_name_gen: "Ситника Єгора Сергійовича",
course: 2,
semester: 3,
variant: 13,
group: "23-2",
gender: "m",
#import "utils.typ": img
#let authors = (
(
name: "Ситник Є. С.",
full-name-gen: "Ситника Єгора Сергійовича",
edu-program: "ПЗПІ",
group: "23-2",
gender: "m",
course: 2,
semester: 3,
variant: 13,
),
)
#let mentors = (
@@ -16,34 +22,39 @@
(name: "Широкопетлєва М. С.", degree: "Ст. викл. каф. ПІ"),
)
#let task_list = (
done_date: datetime(year: 2024, month: 12, day: 27),
initial_date: datetime(year: 2024, month: 9, day: 15),
#let task-list = (
done-date: datetime(year: 2024, month: 12, day: 27),
initial-date: datetime(year: 2024, month: 9, day: 15),
source: "методичні вказівки до виконання курсової роботи, вимоги до інформаційної системи, предметна область, що пов’язана з управлінням класом та класним керівництвом.",
content: "вступ, аналіз предметної області; постановка задачі; проектування бази даних; опис програми; висновки; перелік джерел посилання.",
graphics: "загальна діаграма класів, ER-діаграма, UML-діаграми, DFD-діаграма, схема БД в 1НФ, 2НФ, 3НФ, копії екранів (“скриншоти”) прикладної програми, приклади звітів прикладної програми.",
)
#let calendar_plan = (
plan_table: table(
#let calendar-plan = (
plan-table: table(
columns: 4,
align: (center, left, center, center),
[Номер], [Назва етапів курсової роботи], [Строк виконання етапів роботи], [Примітки],
[1], [Аналіз предметної області], [15.09.24 24.09.24], [Виконано],
[2], [Концептуальне моделювання], [24.09.24-30.09.24], [~],
[2], [Постановка задачі], [28.09.24 2.10.24], [Виконано],
[3], [Побудова ER-діаграми та схеми БД], [2.10.24 18.10.24], [Виконано],
[4], [Оформлення розділів 1, 2 та 3.1, 3.2 пояснювальної записки], [10.10.24 - 18.10.24], [Виконано],
[5], [Перша контрольна точка з курсової роботи], [20.10.24], [Виконано],
[6], [Нормалізація бази даних], [20.10.24 - 15.11.24], [Виконано],
[7], [Створення програми], [20.10.24 20.11.24], [Виконано],
[8], [Тестування програми, наповнення бази даних], [20.11.24 - 5.12.24], [Виконано],
[9], [Друга контрольна точка з курсової роботи], [7.12.24], [Виконано],
[10], [Реалізація остаточної версії програми], [7.12.24-15.12.24], [Виконано],
[11], [Оформлення інших розділів пояснювальної записки], [1.11.24 25.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 = (
@@ -87,7 +98,7 @@
#v(-spacing)
== Частина 1
#lorem(100)
== Частина2
== Частина 2
#lorem(200)
= Приклад звіту 2
@@ -97,16 +108,15 @@
#lorem(200)
]
#show: cw-template.with(
#show: coursework.with(
title: "Інформаційна система «Помічник класного керівника». Керування класом",
subject_short: "БД",
edu_program_short: "ПЗПІ",
author: author,
subject: "БД",
authors: authors,
mentors: mentors,
task_list: task_list,
calendar_plan: calendar_plan,
task-list: task-list,
calendar-plan: calendar-plan,
abstract: abstract,
bib_path: "bibl.yml",
bib-path: bytes(read("bibl.yml")), // NOTE: use `bytes("bibl.yml")` as typst looks in template dir when using just filename
appendices: appendices,
)

View File

@@ -1,6 +1,31 @@
#import "@local/nure:0.1.0": *
#import "@local/nure:0.1.1": *
#import "utils.typ": img
#show: pz-lb-template.with(..yaml("config/doc.yaml"))
#import style: spacing
#show: pz-lb.with(
university: "ХНУРЕ",
subject: "СМП",
type: "ЛБ",
number: 2,
title: отiк керування та алгоритмічні структури Bash",
mentors: (
(name: "Шевченко Т. Г.", degree: "Доцент кафедри ПІ", gender: "m"),
(name: "Франко І. Я.", degree: "Асистент кафедри ПІ", gender: "m"),
),
authors: (
(
name: "Косач Л. П.",
full-name-gen: "Косач Лариси Петрівни",
edu-program: "КУІБ",
group: "23-2",
gender: "f",
course: 2,
semester: 4,
variant: 8,
),
),
)
#v(-spacing)
@@ -29,3 +54,25 @@
- #lorem(25);
- #lorem(42);
- #lorem(27).
#show: style.appendices
= Quote
#link("https://youtu.be/bJQj1uKtnus")[
The art isn't the art, the art is never the art,
the art is the thing that happens inside you when you make it and the feeling in the heart of the beholder.
]
= Приклад звіту 1
#v(-spacing)
== Частина 1
#lorem(100)
== Частина 2
#lorem(200)
= Приклад звіту 2
#lorem(200)
= Приклад звіту 3
#lorem(200)

7
template/utils.typ Normal file
View File

@@ -0,0 +1,7 @@
#import "@local/nure:0.1.1": utils
/// captioned image with auto-generated label from path
/// Usage: img("path/to/image.png", "Caption")(optional: "source")
#let img(path, caption, ..sink) = {
let source = sink.pos().at(0, default: ())
[ #figure(image(path, ..sink.named()), caption: utils.img-caption(caption, source)) #utils.img-label(path) ]
}

View File

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