diff --git a/README.md b/README.md
index 449893d..5036e6f 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
# Typst Template for NURE Works
+
## General Info
@@ -6,16 +7,18 @@ This project contains two template functions and some utilities for writing NURE
### Templates
-#### `pz-lb-template` - For Laboratory and Practical Works
+#### `pz-lb` - For Practice and Laboratory Works
This template:
- Sets up document styles;
- Formats the title page according to NURE/DSTU guidelines.
-#### `cw-template` - For Course Works
+#### `coursework` - For Course Works
This template:
- Sets up document styles;
- Formats the title, task, calendar plan, and abstract pages;
-- Typesets the bibliography, outline, and appendices according to standard requirements.
+- Typesets the bibliography according to ДСТУ 3008:2015 using custom CSL style;
+- Typesets the outline and appendices according to standard requirements.
+
### Utilities
- `nheading` - For unnumbered headings, such as "Introduction" and "Conclusion".
@@ -24,61 +27,138 @@ This template:
- `bold` - Inserts bold text inside functional environments.
- `img` - Inserts images with a caption, automatically deriving the label from the image file name.
+**Note:** `img()` is provided in `utils.typ` in project's root directory for compatibility, until [path() type](https://github.com/typst/typst/pull/7555) is released.
+
## Usage
### As a local typst package
1. Clone this repository into ~/.local/share/typst/packages/:
```bash
-git clone -b 0.1.0 https://gitea.linerds.us/pencelheimer/typst_nure_template.git ~/.local/share/typst/packages/local/nure/0.1.0
+git clone -b 0.1.1 https://gitea.linerds.us/pencelheimer/typst_nure_template.git ~/.local/share/typst/packages/local/nure/0.1.1
```
2. Init your project with Typst:
```bash
-typst init @local/nure:0.1.0 project-name
+typst init @local/nure:0.1.1 project-name
```
### As a standalone file
-Copy `lib.typ` to your project's root directory.
+Copy `src/` to your project's root directory, optionally renaming `src/` to `lib/`.
### In your project
```typst
// Import the template either from a local package...
#import "@local/nure:0.1.0": *
// ...or by importing a lib.typ directly
-// #import "/lib.typ": *
+// #import "/lib/lib.typ": *
-// Setup the document
-#show: pz-lb-template.with(
- title: "Some title",
- // etc: "and so on",
- // ...
+// 1. Setup the document
+// by setting values directly...
+#show: pz-lb.with(
+ title: "Some title",
+ // etc: "and so on",
+ // ...
)
+// ...or using a yaml/toml file
+#show: pz-lb.with(..toml("/doc.toml"))
+
// this template automatically inserts a `=title`
-// Write your content...
+// Write your content
#v(-spacing) // remove spacing between headings
== Purpose
Some text
// ...or include your modules
-#include "src/intro.typ"
-#include "src/chapter1.typ"
-#include "src/chapter2.typ"
+#include "chapters/intro.typ"
+#include "chapters/chapter1.typ"
+#include "chapters/chapter2.typ"
// NOTE: if you want to use variables or utils provided by the package,
// you have to import the package or a lib.typ inside a module.
+
+
+// If you ever need appendices in pz-lb template use the show rule
+// WARNING: when using coursework template use its own argument,
+// so it can put bibliography before appendices
+#show: style.appendices
+
+= Quote
+#link("https://youtu.be/bJQj1uKtnus")[
+ The art isn't the art, the art is never the art,
+ the art is the thing that happens inside you when you make it and the feeling in the heart of the beholder.
+]
+```
+
+And a TOML file would look like this:
+```toml
+# university = "ХНУРЕ" # "ХНУРЕ" is the default
+# edu-program = "ПЗПІ" # can be null, sourced from authors.first() by default
+
+subject = "СМП"
+doctype = "ЛБ"
+worknumber = 2
+title = "Потiк керування та алгоритмічні структури Bash"
+
+[[mentors]]
+name = "Шевченко Т. Г."
+degree = "Доцент кафедри ПІ"
+gender = "m"
+
+[[mentors]]
+name = "Франко І. Я."
+degree = "Асистент кафедри ПІ"
+gender = "m"
+
+[[authors]]
+name = "Косач Л. П."
+edu-program = "ПЗПІ"
+group = "23-2"
+gender = "f"
+variant = 8
+# For coursework
+full-name-gen = "Косач Лариси Петрівни"
+course = 2
+semester = 4
```
### Notes:
1. Use `#v(-spacing)` to remove vertical spacing between titles (this cannot be automatically handled by the template). Variable `spacing` used here is imported from the template.
+2. When importing `@local/nure:0.1.1` and specifying file paths in functions handled by the package, the path will relative to package's root directory, e.g. setting `#show: coursework.with(bib-path: "bibl.yml")` will evaluate to `~/.local/share/typst/packages/local/nure/0.1.1/bibl.yml`, the same is for `#img` function, which makes it quite annoying and forces one to import `lib.typ` file. Please open an issue or contact us in any other way if you have any advice.
+
+### Bibliography Format
+The template uses a custom CSL (Citation Style Language) file located at `src/csl/dstu-3008-2015.csl` to format bibliography entries.
+
+Supported bibliography entry types in `bibl.yml`:
+- **Book**: Books with author, title, publisher, year, and page count
+- **Web**: Web resources with title, author/organization, URL, and access date
+
+**Warning:** Other types were added by Kimi K2.5 without any additional checks for compliance.
+
+Example `bibl.yml`:
+```yaml
+mysql:
+ type: Book
+ title: MySQL Language Reference
+ author: Ab M.
+ publisher: MySQL Press
+ date: 2004
+ page-total: 600
+
+go:
+ type: Web
+ title: The Go Programming Language
+ author: The Go Programming Language
+ url:
+ value: https://go.dev/
+ date: 2024-12-10
+```
### Example Project Structure
```
project/
+├── doc.toml -- for things that don't change across works, i.e. author and mentor metadata
├── main.typ -- for boilerplate code and importing everything
-├── config/
-│ ├── doc.yaml -- for things that don't change across works, i.e. author and mentor metadata
-│ ├── universities.yaml -- for user-specific configuration, i.e. education programs and disciplines
-│ └── ...
-├── src/
+├── utils.typ -- for helper functions
+├── chapters/
│ ├── intro.typ
│ ├── chapter1.typ
│ ├── chapter2.typ
diff --git a/assets/pz-lb_title_page.png b/assets/pz-lb_title_page.png
new file mode 100644
index 0000000..f727d4b
Binary files /dev/null and b/assets/pz-lb_title_page.png differ
diff --git a/config/universities.yaml b/config/universities.yaml
deleted file mode 100644
index e21c999..0000000
--- a/config/universities.yaml
+++ /dev/null
@@ -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"
- ОС: "Операційні системи"
- ОТК: "Основи теорії кіл"
- ПП: "Проектний практикум"
- ПРОГ: "Програмування"
- СПМ: "Скриптові мови програмування"
- УФМ: "Українське фахове мовлення"
- Ф: "Філософія"
- ФІЗ: "Фізика"
diff --git a/lib.typ b/lib.typ
deleted file mode 100644
index b51c4ac..0000000
--- a/lib.typ
+++ /dev/null
@@ -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*/
diff --git a/src/config/universities.yaml b/src/config/universities.yaml
new file mode 100644
index 0000000..8855a07
--- /dev/null
+++ b/src/config/universities.yaml
@@ -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?
+ ТКП: Технології комп`ютерного проєктування
+ УФМ: Українське фахове мовлення
+ ФІЗ: Фізика
+ ФІЛ: Філософія
+ ФВС: Фізичне виховання та спорт
+ ХТ: Хмарні технології
diff --git a/src/csl/dstu-3008-2015.csl b/src/csl/dstu-3008-2015.csl
new file mode 100644
index 0000000..e5afc48
--- /dev/null
+++ b/src/csl/dstu-3008-2015.csl
@@ -0,0 +1,357 @@
+
+
diff --git a/src/helpers.typ b/src/helpers.typ
new file mode 100644
index 0000000..99ebdeb
--- /dev/null
+++ b/src/helpers.typ
@@ -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] }
+}
diff --git a/src/lib.typ b/src/lib.typ
new file mode 100644
index 0000000..3ab603c
--- /dev/null
+++ b/src/lib.typ
@@ -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
+}
diff --git a/src/shared.typ b/src/shared.typ
new file mode 100644
index 0000000..6dd351c
--- /dev/null
+++ b/src/shared.typ
@@ -0,0 +1 @@
+#let universities = yaml("config/universities.yaml")
diff --git a/src/style.typ b/src/style.typ
new file mode 100644
index 0000000..046862b
--- /dev/null
+++ b/src/style.typ
@@ -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
+ }
+}
diff --git a/src/title-pages/coursework/main.typ b/src/title-pages/coursework/main.typ
new file mode 100644
index 0000000..52e579b
--- /dev/null
+++ b/src/title-pages/coursework/main.typ
@@ -0,0 +1 @@
+#import "nure.typ": *
diff --git a/src/title-pages/coursework/nure.typ b/src/title-pages/coursework/nure.typ
new file mode 100644
index 0000000..ebce727
--- /dev/null
+++ b/src/title-pages/coursework/nure.typ
@@ -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,
+ )
+}
diff --git a/src/title-pages/main.typ b/src/title-pages/main.typ
new file mode 100644
index 0000000..f28c162
--- /dev/null
+++ b/src/title-pages/main.typ
@@ -0,0 +1,2 @@
+#import "pz-lb/main.typ" as pz-lb
+#import "coursework/main.typ" as cw
diff --git a/src/title-pages/pz-lb/complex.typ b/src/title-pages/pz-lb/complex.typ
new file mode 100644
index 0000000..1ab6309
--- /dev/null
+++ b/src/title-pages/pz-lb/complex.typ
@@ -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]")
+ ]
+}
diff --git a/src/title-pages/pz-lb/main.typ b/src/title-pages/pz-lb/main.typ
new file mode 100644
index 0000000..80af7e9
--- /dev/null
+++ b/src/title-pages/pz-lb/main.typ
@@ -0,0 +1,2 @@
+#import "complex.typ": *
+#import "nure.typ": *
diff --git a/src/title-pages/pz-lb/nure.typ b/src/title-pages/pz-lb/nure.typ
new file mode 100644
index 0000000..40d5abd
--- /dev/null
+++ b/src/title-pages/pz-lb/nure.typ
@@ -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]")
+ ]
+}
diff --git a/src/utils.typ b/src/utils.typ
new file mode 100644
index 0000000..1b81922
--- /dev/null
+++ b/src/utils.typ
@@ -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) ]
+}
diff --git a/template/bibl.yml b/template/bibl.yml
index 745471e..a05e699 100644
--- a/template/bibl.yml
+++ b/template/bibl.yml
@@ -6,7 +6,6 @@ go:
value: https://go.dev/
date: 2024-12-10
-
htmx:
type: Web
title: Htmx - high power tools for html
diff --git a/template/config/doc.yaml b/template/config/doc.yaml
deleted file mode 100644
index 1e6f7d5..0000000
--- a/template/config/doc.yaml
+++ /dev/null
@@ -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
diff --git a/template/coursework.typ b/template/coursework.typ
index 75b80f1..b286a40 100644
--- a/template/coursework.typ
+++ b/template/coursework.typ
@@ -1,13 +1,19 @@
#import "@local/nure:0.1.0": *
+#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,
)
diff --git a/template/lab.typ b/template/lab.typ
index f5bd94e..cedd86f 100644
--- a/template/lab.typ
+++ b/template/lab.typ
@@ -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)
diff --git a/template/utils.typ b/template/utils.typ
new file mode 100644
index 0000000..3ace327
--- /dev/null
+++ b/template/utils.typ
@@ -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) ]
+}
diff --git a/typst.toml b/typst.toml
index c09a9d5..81aa0e0 100644
--- a/typst.toml
+++ b/typst.toml
@@ -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"