20 Commits
v0.0.0 ... main

Author SHA1 Message Date
92a52fb7a1 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 01:46:07 +02:00
5be6cea4fb fix: fix multiple authors
docs(config): adjust and fix typo
2025-03-15 16:36:16 +02:00
5bc79a196e WIP: introduce yaml config examples in a template 2025-03-14 17:59:18 +02:00
c1ad952c7c fix: fix values and refactor, move yaml to config/ 2025-03-13 18:28:39 +02:00
dc3358d986 WIP: feat!: move university-related info to yaml file 2025-03-13 17:57:46 +02:00
6bf37099b4 refactor: move university name into a variable 2025-03-13 17:31:22 +02:00
bf00b3de5d feat!: add course and semester keys for authors
fix: bring cw-template more in line with pz-lb-template
fix: remove hard-coded values
docs: update a template
2025-03-13 16:52:52 +02:00
c049a9a3ce style: reformat with 120 col width 2025-03-13 13:08:18 +02:00
22fb1de736 docs: fix 2025-03-13 12:56:18 +02:00
1db499dad4 docs(readme): change repo owner 2025-03-13 12:42:14 +02:00
41dcbeb1ec feat!: bump version to 0.1.0
fix: adjust templates
style: reformat lib.typ w/ 120 column width
2025-03-13 12:37:58 +02:00
429f632841 fix!: remove unnecessary gender key in cw_template
fix: add more subjects
docs: unify comments with pz_lb_template
2025-03-13 12:35:12 +02:00
b3214e2150 feat!: rename some parametes to avoid redundancy
fix: avoid hard-coded defaults
docs(readme): add more detail
2025-03-13 12:12:57 +02:00
d60d3a9c89 misc: change .gitignore to ignore all pdf files 2025-03-12 23:21:33 +02:00
63dbd82e4d docs(readme): update structure 2025-03-12 23:15:40 +02:00
0b8ceda4f1 feat!: rename "lab-pz-template" to "pz-lb-template"
feat!: make authors.*.edu_program optional, derive it from edu_program_short instead
docs: minor changes to readme and lab template
2025-03-12 23:02:32 +02:00
cf10e0fbdc style: format in 120 column width 2025-03-12 20:42:17 +02:00
33d067b67e feat!: move department_gen to edu_programs
feat!: rename "shorthand" variables to "short"
WIP: feat!: derive group name from edu_program
docs(template): adjust templates accordingly
style: format with typstyle
2025-03-12 20:24:48 +02:00
0e0dc20e9b fix: remove unnecessary variable 2025-03-12 19:11:06 +02:00
b8a309ad2c feat!: change mentor to mentors for lab-pz
feat: implement plural mentor referral
fix: added subject and edu_program
style: format with typstyle
2025-03-12 19:08:41 +02:00
22 changed files with 1398 additions and 789 deletions

2
.gitignore vendored
View File

@@ -1 +1 @@
template.pdf
*.pdf

150
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 guidelines.
- 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,48 +27,153 @@ 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 https://gitea.linerds.us/pencelheimer/typst_nure_template.git ~/.local/share/typst/packages/local/nure/0.0.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.0.0 project-name
typst init @local/nure:0.1.1 project-name
```
### As a file in your project
Include lib.typ in your project and utilize the provided functions:
### As a standalone file
Copy `src/` to your project's root directory, optionally renaming `src/` to `lib/`.
### In your project
```typst
// Import the template
#import "lib.typ": *
// Import the template either from a local package...
#import "@local/nure:0.1.0": *
// ...or by importing a lib.typ directly
// #import "/lib/lib.typ": *
// Setup the document
#show: lab-pz-template.with(
title: "Some title",
// 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
#v(-spacing) // remove spacing between headings
== Purpose
Some text
// ...or include your modules
#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-folder/
├── main.typ
├── template.typ
├── images/
│ ├── figure1.png
│ ├── figure2.png
│ ├── ...
├── ...
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
├── utils.typ -- for helper functions
├── chapters/
│ ├── intro.typ
│ ├── chapter1.typ
│ ├── chapter2.typ
│ └── ...
├── figures/
│ ├── chapter1/
│ │ ├── figure1.png
│ │ ├── figure2.png
│ │ ├── figure3.png
│ │ └── ...
│ ├── chapter2/
│ │ ├── figure1.png
│ │ ├── figure2.png
│ │ ├── figure3.png
│ │ └── ...
│ └── ...
└── ...
```

BIN
assets/pz-lb_title_page.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

707
lib.typ
View File

@@ -1,707 +0,0 @@
// Academic aliases {{{1
/// subject abbreviations to full names
#let subjects = (
"БД": "Бази даних",
"ОПНJ": "Основи програмування на Java",
"ОС": "Операційні системи",
"ПП": "Проектний практикум",
"СПМ": "Скриптові мови програмування",
"Ф": "Філософія",
)
/// education program abbreviations to name & number
#let edu_programs = (
"ПЗПІ": (
name: "Інженерія програмного забезпечення",
number: 121, // TODO: ПЗПІ is "F2" now
),
)
// 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: "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_shorthand (str): Subject short name.
/// - department_gen (str): Department name in genitive form.
/// - authors ((name: str, full_name_gen: str, variant: int, group: str, gender: str),): List of Authors dicts.
/// - mentors ((name: str, gender: str, degree: str),): List of mentors dicts.
/// - edu_program_shorthand (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_shorthand: "NONE",
department_gen: "Програмної інженерії",
author: (),
mentors: (),
edu_program_shorthand: "ПЗПІ",
task_list: (),
calendar_plan: (),
abstract: (),
bib_path: "bibl.yml",
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 edu_program = edu_programs.at(edu_program_shorthand)
// page 1 {{{2
[
#set align(center)
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ
ХАРКІВСЬКИЙ НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ РАДІОЕЛЕКТРОНІКИ
\
Кафедра Програмної інженерії
\
ПОЯСНЮВАЛЬНА ЗАПИСКА
ДО КУРСОВОЇ РОБОТИ
з дисципліни: "#subjects.at(subject_shorthand, default: "NONE")"
Тема роботи: "#title"
\ \ \
#columns(2, gutter: 4cm)[
#set align(left)
#if author.gender == "m" { [Виконав\ ] } else { [Виконала\ ] } ст. гр. #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[Харківський національний університет радіоелектроніки]
linebreak()
linebreak()
grid(
columns: (100pt, 1fr),
bold[
Кафедра
Дисципліна
Спеціальність
],
{
uline(align: left, department_gen)
linebreak()
uline(align: left, subjects.at(subject_shorthand))
linebreak()
uline(align: left, [#edu_program.number #edu_program.name])
},
)
grid(
columns: (1fr, 1fr, 1fr),
gutter: 0.3fr,
[#bold[Курс] #uline(2)], [#bold[Група] #uline(author.group)], [#bold[Семестр] #uline(3)],
)
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.
/// - title (str): Title of the document.
/// - subject_shorthand (str): Subject short name.
/// - department_gen (str): Department name in genitive form.
/// - worknumber (int): Number of the work, can be omitted.
/// - authors ((name: str, full_name_gen: str, variant: int, group: str, gender: str),): List of Authors dicts.
/// - mentor (name: str, gender: str, degree: str): Mentors objects.
#let lab-pz-template(
doc,
doctype: "NONE",
title: "NONE",
subject_shorthand: "NONE",
department_gen: "Програмної інженерії",
worknumber: 1,
authors: (),
mentor: (),
) = {
set document(title: title, author: authors.at(0).name)
show: style
context counter(heading).update(worknumber - 1)
// page 1 {{{2
align(center)[
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ \
ХАРКІВСЬКИЙ НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ РАДІОЕЛЕКТРОНІКИ
\ \
Кафедра #department_gen
\ \ \
Звіт \
з
#if doctype == "ЛБ" [лабораторної роботи] else [практичної роботи]
#if worknumber != none [ #worknumber]
з дисципліни: "#subjects.at(subject_shorthand, default: "UNLNOWN 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 [Виконала:\ ]
[
ст. гр. #author.group\
#author.name\
]
if author.variant != none [Варіант: #author.variant]
} else [
Виконали:\
ст. гр. #authors.at(0).group\
#authors.map(a => [ #a.name\ ])
]
#colbreak()
#set align(right)
#if mentor.gender == "m" { [Перевірив:\ ] } else { [Перевірила:\ ] }
#mentor.degree #if mentor.degree.len() >= 15 [\ ]
#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,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,59 +1,60 @@
#import "@local/nure:0.0.0": *
#import "@local/nure:0.1.0": *
#import style: spacing
#let author = (
name: "Ситник Є. С.",
full_name_gen: "Ситника Єгора Сергійовича",
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 = (
(
name: "Черепанова Ю. Ю.",
gender: "f",
degree: "Ст. викл. каф. ПІ",
),
(
name: "Русакова Н. Є.",
gender: "f",
degree: "Доц. каф. ПІ",
),
(
name: "Широкопетлєва М. С.",
gender: "f",
degree: "Ст. викл. каф. ПІ",
),
(name: "Черепанова Ю. Ю.", degree: "Ст. викл. каф. ПІ"),
(name: "Русакова Н. Є.", degree: "Доц. каф. ПІ"),
(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 = (
@@ -69,11 +70,26 @@
"SQL",
),
text: [
Мета даної роботи -- проєктування та розробка інформаційної системи «Помічник класного керівника. Керування класом», яка спрямована на автоматизацію процесів управління класом, облік даних про учнів, планування та аналіз навчального процесу. Основна задача інформаційної системи спростити роботу класного керівника, забезпечити ефективну організацію документації та взаємодію з учасниками освітнього процесу.
Мета даної роботи -- проєктування та розробка інформаційної системи «Помічник
класного керівника. Керування класом», яка спрямована на автоматизацію процесів
управління класом, облік даних про учнів, планування та аналіз навчального
процесу. Основна задача інформаційної системи спростити роботу класного
керівника, забезпечити ефективну організацію документації та взаємодію з
учасниками освітнього процесу.
Для реалізації системи було використано сучасний стек технологій, а саме: Go -- як основна мова програмування для створення серверної логіки, HTMX -- для динамічного оновлення інтерфейсу без використання складних фреймворків, MySQL -- як СУБД для зберігання даних про учнів, їх оцінки та розклад, Neovim -- як середовище для швидкої та ефективної розробки коду, Go Echo -- веб-фреймворк для створення REST API, Go SQLx -- бібліотека для роботи з базою даних, що забезпечує зручність і гнучкість.
Для реалізації системи було використано сучасний стек технологій, а саме: Go --
як основна мова програмування для створення серверної логіки, HTMX -- для
динамічного оновлення інтерфейсу без використання складних фреймворків, MySQL --
як СУБД для зберігання даних про учнів, їх оцінки та розклад, Neovim -- як
середовище для швидкої та ефективної розробки коду, Go Echo -- веб-фреймворк для
створення REST API, Go SQLx -- бібліотека для роботи з базою даних, що
забезпечує зручність і гнучкість.
Результат роботи веб-додаток, який дозволяє обліковувати особисті дані учнів та їхніх опікунів, включаючи інформацію про успішність, відвідуваність та інші показники; планувати розклад занять; генерувати звіти про успішність учнів та переглядати різну статистику. Інтерфейс, створений з використанням HTMX, легко адаптується під потреби користувача.
Результат роботи веб-додаток, який дозволяє обліковувати особисті дані учнів
та їхніх опікунів, включаючи інформацію про успішність, відвідуваність та інші
показники; планувати розклад занять; генерувати звіти про успішність учнів та
переглядати різну статистику. Інтерфейс, створений з використанням HTMX, легко
адаптується під потреби користувача.
],
)
@@ -82,7 +98,7 @@
#v(-spacing)
== Частина 1
#lorem(100)
== Частина2
== Частина 2
#lorem(200)
= Приклад звіту 2
@@ -92,17 +108,15 @@
#lorem(200)
]
#show: cw-template.with(
#show: coursework.with(
title: "Інформаційна система «Помічник класного керівника». Керування класом",
subject_shorthand: "БД",
department_gen: "Програмної інженерії",
edu_program_shorthand: "ПЗПІ",
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,25 +1,30 @@
#import "@local/nure:0.0.0": *
#import "@local/nure:0.1.1": *
#import "utils.typ": img
#show: lab-pz-template.with(
doctype: "ЛБ",
title: "Інформаційна система «Помічник класного керівника». Керування класом",
subject_shorthand: "БД",
department_gen: "Програмної інженерії",
#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: "Ситника Єгора Сергійовича",
variant: 13,
group: "ПЗПІ-23-2",
gender: "m",
name: "Косач Л. П.",
full-name-gen: "Косач Лариси Петрівни",
edu-program: "КУІБ",
group: "23-2",
gender: "f",
course: 2,
semester: 4,
variant: 8,
),
),
mentor: (
name: "Черепанова Ю. Ю.",
gender: "f",
degree: "Ст. викл. каф. ПІ",
),
worknumber: 1,
)
#v(-spacing)
@@ -49,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.0.0"
entrypoint = "lib.typ"
version = "0.1.1"
entrypoint = "src/lib.typ"
authors = ["linerds"]
license = "GPL-3.0"
description = "Typst NURE package"