49 Commits
main ... 0.1.1

Author SHA1 Message Date
9578b5e4f1 nuke lib.typ? it's all in src/ now 2026-03-27 22:53:38 +02:00
8df87de797 fix: replace split slice with .clusters() 2026-03-27 22:47:54 +02:00
6d6e94f0ba fix: appendice figure numbering 2026-03-27 22:14:32 +02:00
a9475dbc94 fix: don't evaluate each layout 2026-03-12 15:04:57 +02:00
19c5fdf19c feat: added ПЗПІ semester-6 subject defenitions 2026-03-12 15:04:32 +02:00
833a179ced fix: removed old config 2026-03-12 15:04:12 +02:00
c1f128b528 ST department name change (#18)
Signed-off-by: dxrknesss <dxrkness@linerds.us>
Reviewed-on: pencelheimer/typst_nure_template#18
Co-authored-by: dxrknesss <dxrkness@linerds.us>
Co-committed-by: dxrknesss <dxrkness@linerds.us>
2026-02-16 18:31:06 +02:00
bbdc0d8209 Update README.md 2026-02-12 13:22:18 +02:00
7fb1fd1391 Merge pull request 'feat!: bump to 0.1.1' (#17) from unexplrd/typst_nure_template:0.1.1 into 0.1.1
Reviewed-on: pencelheimer/typst_nure_template#17
Reviewed-by: Sytnyk Yehor <pencelheimer@noreply.linerds.us>
2026-02-09 21:14:08 +02:00
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 1374 additions and 844 deletions

131
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,79 +7,161 @@ 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".
- `hfill` - Fills horizontal space with a filled box instead of just empty space; useful for creating underlines.
- `uline` - Creates underlined fields that need to be filled, such as the name field on the task list.
- `bold` - Inserts bold text inside functional environments.
- `img` - Inserts images with a caption, automatically deriving the label from the image file name.
- `img` - Inserts images with a caption, automatically deriving the label from the image file name (use via `#import "@local/nure:0.1.1": utils` and call `utils.img`).
**Note:** `img()` is provided in `template/utils.typ` so you can copy it into your project root 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/` (then import `src/lib.typ` or `lib/lib.typ` accordingly).
### 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",
// ...
// NOTE: all template arguments use kebab-case.
// 1. Setup the document
// by setting values directly...
#show: pz-lb.with(
title: "Some title",
// ...
)
// ...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.
// you have to import the package or a lib.typ inside a module (e.g. #import "@local/nure:0.1.1": utils).
// 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
// For coursework appendices, pass them via `appendices:` argument instead.
= 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 be relative to the package root, 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 applies to `utils.img` unless you copy `template/utils.typ` into your project root and import from there.
### 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 +0,0 @@
ХНУРЕ:
name: "Харківський національний університет радіоелектроніки"
edu_programs:
ПЗПІ:
name_long: "Інженерія програмного забезпечення"
department_gen: "Програмної інженерії"
code: 121 # TODO = ПЗПІ is "F2" now
КУІБ:
name_long: "Управління інформаційною безпекою"
department_gen: "Інфокомунікацій"
code: 125
subjects:
БД: "Бази даних"
БЖД: "Безпека життєдіяльності"
ОІМ: "Основи IP-мереж"
"ОПНJ": "Основи програмування на Java"
ОС: "Операційні системи"
ОТК: "Основи теорії кіл"
ПП: "Проектний практикум"
ПРОГ: "Програмування"
СПМ: "Скриптові мови програмування"
УФМ: "Українське фахове мовлення"
Ф: "Філософія"
ФІЗ: "Фізика"

746
lib.typ
View File

@@ -1,746 +0,0 @@
// Academic aliases {{{1
#let universities = yaml("config/universities.yaml")
// Template formatting functions {{{1
/// 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) }
]
/// bold text
#let bold(content) = text(weight: "bold")[#content]
/// month name from its number
#let month_gen(month) = (
"січня",
"лютого",
"березня",
"квітня",
"травня",
"червня",
"липня",
"серпня",
"вересня",
"жовтня",
"листопада",
"грудня",
).at(month - 1)
// 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)]
}
// Styling {{{1
/// NOTE: may be wrong
#let ua_alpha_numbering = "абвгдежиклмнпрстуфхцшщюя".split("") // 0 = "", 1 = "а"
// general outlook {{{2
// spacing between lines
#let spacing = 0.95em
#let style(it) = {
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))
}
},
)
set text(
font: ("Times New Roman", "Liberation Serif"),
size: 14pt,
hyphenate: false,
lang: "uk",
)
set par(justify: true, first-line-indent: (amount: 1.25cm, all: true))
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
set enum(
numbering: i => { ua_alpha_numbering.at(i) + ")" },
indent: 1.25cm,
body-indent: 0.5cm,
)
show enum: it => {
set enum(indent: 0em, numbering: "1)")
it
}
set list(indent: 1.35cm, body-indent: 0.5cm, marker: [--])
// figures {{{2
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)
// figure numbering
show heading.where(level: 1): it => {
counter(math.equation).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(),
),
)
// 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
set heading(numbering: "1.1")
show heading.where(level: 1): it => {
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 => {
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)
}
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 {{{2
show raw: it => {
let new_spacing = 0.5em
set block(spacing: new_spacing)
set par(
spacing: new_spacing,
leading: new_spacing,
)
set text(
size: 11pt,
font: ("Iosevka NFM", "Courier New"),
weight: "semibold",
)
v(spacing * 2.5, weak: true)
pad(it, left: 1.25cm)
v(spacing * 2.5, 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.
/// - 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(
doc,
title: none,
subject: none,
university: "ХНУРЕ",
author: (),
mentors: (),
edu_program: none,
task_list: (),
calendar_plan: (),
abstract: (),
bib_path: none,
appendices: (),
) = {
set document(title: title, author: author.name)
show: style
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 head_mentor = mentors.at(0)
let uni = universities.at(university)
let edu_prog = uni.edu_programs.at(edu_program)
// page 1 {{{2
[
#set align(center)
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ\
#upper(uni.name)
\
Кафедра #edu_prog.department_gen
\
ПОЯСНЮВАЛЬНА ЗАПИСКА\
ДО КУРСОВОЇ РОБОТИ\
з дисципліни: "#uni.subjects.at(subject, default: "NONE")"\
Тема роботи: "#title"
\ \ \
#columns(2, gutter: 4cm)[
#set align(left)
#if author.gender == "m" { [Виконав\ ] } else { [Виконала\ ] } ст. гр. #edu_program\-#author.group
\
Керівник:\
#head_mentor.degree
\
Робота захищена на оцінку
\
Комісія:\
#for mentor in mentors {
[#mentor.degree\
]
}
#colbreak()
#set align(left)
\
#author.name
\ \
#head_mentor.name
\
#underline(" " * 35)
\ \
#for mentor in mentors {
[#mentor.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))
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([#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(", ").
]
\
#{
let keywords = abstract.keywords.map(upper)
let is_cyrillic = word => word.split("").any(char => ("А" <= char and char <= "я"))
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(", ")
}
\
#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(citation) = {
if (citation.type == "Web") {
let date_array = citation.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 [
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 {{{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
}
}
// Laboratory work 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(
doc,
doctype: none,
university: "ХНУРЕ",
edu_program: none,
title: none,
subject: none,
worknumber: none,
authors: (),
mentors: (),
) = {
set document(title: title, author: authors.at(0).name)
show: style
let uni = universities.at(university)
let edu_prog = uni.edu_programs.at(edu_program)
// page 1 {{{2
align(center)[
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ \
#upper(uni.name)
\ \
Кафедра #edu_prog.department_gen
\ \ \
Звіт \
з
#if doctype == "ЛБ" [лабораторної роботи] else [практичної роботи]
#if worknumber != none {
context counter(heading).update(worknumber - 1)
[№#worknumber]
}
з дисципліни: "#uni.subjects.at(subject, default: "UNKNOWN SUBJECT, PLEASE OPEN AN ISSUE")"
з теми: "#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\ ]
]
#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\
]
}
]
]
#v(1fr)
Харків -- #datetime.today().display("[year]")
]
pagebreak(weak: true)
heading(title)
doc
}
// vim:sts=2:sw=2:fdl=0:fdm=marker:cms=/*%s*/

View File

@@ -0,0 +1,93 @@
ХНУРЕ:
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?
ТКП: Технології комп`ютерного проєктування
УФМ: Українське фахове мовлення
ФІЗ: Фізика
ФІЛ: Філософія
ФВС: Фізичне виховання та спорт
ХТ: Хмарні технології
NoSQL: NoSQL-системи
АПЗ: Архітектура програмного забезпечення
ВSAP: Введення до SAP-технологій
ВМПтФ: Високорівневі мови програмування та фреймворки
ІМпк: Іноземна мова для професійної комунікації
ІНТ: Історія науки і техніки
Лог: Логіка
МППЗ: Менеджмент проектів програмного забезпечення
МОТІ: Методи оптимізації та теорія ігр
ОІДт: Обробка ігрових даних та звітів
ПвІТ: Підприємництво в ІТ
ПВJS: Поглиблене вивчення JavaScript
ПPyt: Програмування мовою Python
РNet: Робота з даними на платформі .Net
РХЗ: Розробка хмарних застосувань в AZURE

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 = "абвгдежиклмнпрстуфхцшщюя".clusters()
/// 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 - 1) + ")")
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 - 1)) + 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"