34 Commits

Author SHA1 Message Date
pencelheimer 6bcc345717 fix(coursework-v2): proper apostrophe 2026-05-24 22:30:50 +03:00
pencelheimer 568e84729c fix(coursework-v2): proper department casing 2026-05-24 22:30:50 +03:00
unexplrd e066e96310 feat: add Про{1,2,3} subject keys 2026-05-22 15:20:36 +03:00
unexplrd 82732e7084 template(unexplrd-mise): bump package rev 2026-05-22 14:56:55 +03:00
unexplrd f60fefc7b6 fix: МОТІ subject name typo 2026-05-22 14:55:38 +03:00
unexplrd c3be6125b4 fix: check for student group and variant in keys 2026-05-22 14:47:46 +03:00
bosiiandgolii 983224a633 enhance README: add installation instructions for Windows and clarify usage for local Typst package 2026-05-22 11:46:00 +03:00
0x1D8 f70669a2b7 Merge pull request #1 from ilyaoc/add-coursework-v2-template
feat: add coursework v2 template
2026-05-20 15:44:39 +02:00
ilyaoc bddbd59557 fix coursework v2 example task lines 2026-05-20 12:44:07 +02:00
ilyaoc cf8c2c8338 refine coursework v2 task fields 2026-05-20 12:37:09 +02:00
ilyaoc c9c0577ec8 add coursework v2 template 2026-05-20 12:37:09 +02:00
pencelheimer 4242825b32 fix(pz-lb): skip the group for the student on the title page 2026-05-12 17:51:18 +03:00
unexplrd 228c77d54f update readme at unexplrd-mise template 2026-04-12 13:16:38 +03:00
unexplrd ad48360c38 update unexplrd-mise template 2026-03-30 12:55:24 +03:00
unexplrd 651f7ed293 fix!: typos, new subjects, sort universities.yaml
note: ПРОГ was renamed to Прог
2026-03-30 12:48:59 +03:00
unexplrd 9abbc7c1e1 update .mise/config.toml in unexplrd-mise 2026-03-29 17:30:31 +03:00
unexplrd ffa3ce0bdc update unexplrd-mise template 2026-03-29 17:00:20 +03:00
unexplrd ea4a5a007b fix import package name 2026-03-29 16:37:09 +03:00
unexplrd 1e3b1ce114 update unexplrd-mise template 2026-03-29 16:35:19 +03:00
unexplrd 1723c5f051 update readme in unexplrd-mise 2026-03-29 16:24:25 +03:00
unexplrd 1a7fe4a394 add unexplrd-mise template 2026-03-29 16:12:12 +03:00
unexplrd af2ba45a16 fix: correct equation numbering in appendices 2026-03-29 14:09:42 +03:00
pencelheimer 450b94cede fix appendice figure numbering 2026-03-29 13:33:30 +03:00
pencelheimer f698299a2d feat: skip heading 2026-03-29 13:33:16 +03:00
unexplrd 9578b5e4f1 nuke lib.typ? it's all in src/ now 2026-03-27 22:53:38 +02:00
unexplrd 8df87de797 fix: replace split slice with .clusters() 2026-03-27 22:47:54 +02:00
unexplrd 6d6e94f0ba fix: appendice figure numbering 2026-03-27 22:14:32 +02:00
pencelheimer a9475dbc94 fix: don't evaluate each layout 2026-03-12 15:04:57 +02:00
pencelheimer 19c5fdf19c feat: added ПЗПІ semester-6 subject defenitions 2026-03-12 15:04:32 +02:00
pencelheimer 833a179ced fix: removed old config 2026-03-12 15:04:12 +02:00
dxrknesss c1f128b528 ST department name change (#18)
Signed-off-by: dxrknesss <dxrkness@linerds.us>
Reviewed-on: #18
Co-authored-by: dxrknesss <dxrkness@linerds.us>
Co-committed-by: dxrknesss <dxrkness@linerds.us>
2026-02-16 18:31:06 +02:00
unexplrd bbdc0d8209 Update README.md 2026-02-12 13:22:18 +02:00
pencelheimer 7fb1fd1391 Merge pull request 'feat!: bump to 0.1.1' (#17) from unexplrd/typst_nure_template:0.1.1 into 0.1.1
Reviewed-on: #17
Reviewed-by: Sytnyk Yehor <pencelheimer@noreply.linerds.us>
2026-02-09 21:14:08 +02:00
unexplrd cee212ae0a chore!: bump to 0.1.1
refactor: break up into multiple files
feat: csl style
refactor!: rename variables
Update template, readme, and more
2026-02-06 02:08:17 +02:00
29 changed files with 2534 additions and 795 deletions
+114 -30
View File
@@ -1,9 +1,9 @@
# Typst Template for NURE Works
![pz-lb title page](assets/pz-lb_title_page.png)
<img src="assets/pz-lb_title_page.png" alt="pz-lb title page" width=350>
## General Info
This project contains two template functions and some utilities for writing NURE works. All functions include documentation comments inside them, so you can explore all possibilities using LSP.
This project contains template functions and some utilities for writing NURE works. All functions include documentation comments inside them, so you can explore all possibilities using LSP.
### Templates
@@ -16,42 +16,97 @@ This template:
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.
#### `coursework-v2` - New Coursework Variant
This template keeps the legacy coursework template intact while offering the newer title/task-page layout. See `template/default/coursework-v2.typ` for an example entrypoint.
### Utilities
- `nheading` - For unnumbered headings, such as "Introduction" and "Conclusion".
- `hfill` - Fills horizontal space with a filled box instead of just empty space; useful for creating underlines.
- `uline` - Creates underlined fields that need to be filled, such as the name field on the task list.
- `bold` - Inserts bold text inside functional environments.
- `img` - Inserts images with a caption, automatically deriving the label from the image file name.
- `img` - Inserts images with a caption, automatically deriving the label from the image file name (use via `#import "@local/nure:0.1.1": utils` and call `utils.img`).
**Note:** `img()` is provided in `template/utils.typ` so you can copy it into your project root for compatibility, until [path() type](https://github.com/typst/typst/pull/7555) is released.
## Usage
### As a local typst package
1. Clone this repository into ~/.local/share/typst/packages/:
You can install the template as a local Typst package. Choose the instructions for your platform below.
#### Unix (Linux / macOS)
1. Clone this repository into your local Typst packages folder:
```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
```
#### Windows (symlink method)
This method creates a symbolic link between the cloned repository and Typst's local packages folder in `%APPDATA%`. The benefit: when the template is updated on GitHub you only need to `git pull` in the cloned folder — VS Code (and Typst) will use the updated files automatically.
Step 1 — clone the template (for example, into your Documents folder):
```powershell
cd $HOME\Documents
git clone https://github.com/linerds/typst_nure_template.git
```
Step 2 — create the symlink (PowerShell must be run as Administrator):
1. Open Start, find PowerShell, right-click and choose "Run as Administrator".
2. Run the following commands as a single block:
```powershell
# Create local packages folder (if it doesn't exist)
New-Item -ItemType Directory -Force -Path "$env:APPDATA\typst\packages\local\nure"
# Point to the folder you cloned
$gitFolder = "$HOME\Documents\typst_nure_template"
# Create a symbolic link for the package version (match the version in this repo)
New-Item -ItemType SymbolicLink -Path "$env:APPDATA\typst\packages\local\nure\0.1.1" -Target $gitFolder
```
Step 3 — use in VS Code
Open your project folder (for example, your coursework or lab folder) in VS Code with the Tinymist Typst extension installed. Create `main.typ` and import the package using the official `@local` import:
```typst
#import "@local/nure:0.1.1": nure-report, style
// If you need specific style variables from the package:
#import "@local/nure:0.1.1/src/style.typ": spacing
#show: nure-report.with(
title: "Report for Lab Work",
type: "Lab Work #1",
discipline: "Object-Oriented Programming",
author: "Surname I.I.",
supervisor: "Assoc. Prof. Kuznetsov O.V.",
)
= Work progress
Your content...
```
The Windows steps above are intentionally minimal and focused on the symlink workflow. If you prefer not to use symlinks, you can copy `src/` to your project root and import `lib.typ` directly (see the "As a standalone file" section).
### As a standalone file
Copy `lib.typ` to your project's root directory.
Copy `src/` to your project's root directory, optionally renaming `src/` to `lib/` (then import `src/lib.typ` or `lib/lib.typ` accordingly).
### In your project
```typst
// Import the template either from a local package...
#import "@local/nure:0.1.0": *
#import "@local/nure:0.1.1": *
// ...or by importing a lib.typ directly
// #import "/lib.typ": *
// #import "/lib/lib.typ": *
// NOTE: all template arguments use kebab-case.
// 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
@@ -65,17 +120,19 @@ Copy `lib.typ` to your project's root directory.
Some text
// ...or include your modules
#include "src/intro.typ"
#include "src/chapter1.typ"
#include "src/chapter2.typ"
#include "chapters/intro.typ"
#include "chapters/chapter1.typ"
#include "chapters/chapter2.typ"
// NOTE: if you want to use variables or utils provided by the package,
// you have to import the package or a lib.typ inside a module.
// you have to import the package or a lib.typ inside a module (e.g. #import "@local/nure:0.1.1": utils).
// If you ever need appendices in pz-lb template use the show rule
// WARNING: when using coursework template use its own argument,
// so it can put bibliography before appendices
#show: appendices-style
#show: style.appendices
// For coursework appendices, pass them via `appendices:` argument instead.
= Quote
#link("https://youtu.be/bJQj1uKtnus")[
@@ -86,10 +143,13 @@ Some text
And a TOML file would look like this:
```toml
title = "Потiк керування та алгоритмічні структури Bash"
# university = "ХНУРЕ" # "ХНУРЕ" is the default
# edu-program = "ПЗПІ" # can be null, sourced from authors.first() by default
subject = "СМП"
doctype = "ЛБ"
worknumber = 2
title = "Потiк керування та алгоритмічні структури Bash"
[[mentors]]
name = "Шевченко Т. Г."
@@ -101,33 +161,57 @@ name = "Франко І. Я."
degree = "Асистент кафедри ПІ"
gender = "m"
edu_program = "ПЗПІ"
university = "ХНУРЕ"
[[authors]]
name = "Косач Л. П."
full_name_gen = "Косач Лариси Петрівни"
course = 2
edu = "ПЗПІ"
gender = "f"
edu-program = "ПЗПІ"
group = "23-2"
semester = 4
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.0` and specifying file paths in functions handled by the package, the path will relative to package's root directory, e.g. setting `#show: coursework.with(bib_path: "bibl.yml")` will evaluate to `~/.local/share/typst/packages/local/nure/0.1.0/bibl.yml`, the same is for `#img` function, which makes it quite annoying and forces one to import `lib.typ` file. Please open an issue or contact us in any other way if you have any advice.
2. When importing `@local/nure:0.1.1` and specifying file paths in functions handled by the package, the path will be relative to the package root, e.g. setting `#show: coursework.with(bib-path: "bibl.yml")` will evaluate to `~/.local/share/typst/packages/local/nure/0.1.1/bibl.yml`. The same applies to `utils.img` unless you copy `template/utils.typ` into your project root and import from there.
### Bibliography Format
The template uses a custom CSL (Citation Style Language) file located at `src/csl/dstu-3008-2015.csl` to format bibliography entries.
Supported bibliography entry types in `bibl.yml`:
- **Book**: Books with author, title, publisher, year, and page count
- **Web**: Web resources with title, author/organization, URL, and access date
**Warning:** Other types were added by Kimi K2.5 without any additional checks for compliance.
Example `bibl.yml`:
```yaml
mysql:
type: Book
title: MySQL Language Reference
author: Ab M.
publisher: MySQL Press
date: 2004
page-total: 600
go:
type: Web
title: The Go Programming Language
author: The Go Programming Language
url:
value: https://go.dev/
date: 2024-12-10
```
### Example Project Structure
```
project/
├── doc.toml -- for things that don't change across works, i.e. author and mentor metadata
├── main.typ -- for boilerplate code and importing everything
├── config/
│ ├── 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
-701
View File
@@ -1,701 +0,0 @@
// Academic aliases {{{1
#let universities = yaml("config/universities.yaml")
// Template formatting functions {{{1
/// bold text
#let bold(content) = text(weight: "bold")[#content]
/// numberless heading
#let nheading(title) = heading(depth: 1, numbering: none, title)
/// fill horizontal space with a box and not an empty space
#let hfill(width) = box(
width: width,
repeat(""),
) // NOTE: This is a HAIR SPACE (U+200A), not a regular space
/// make underlined cell with filled value
#let uline(align: center, content) = underline[
#if align != left { hfill(1fr) }
#content
#if align != right { hfill(1fr) }
]
/// month name from its number
#let month_gen(month) = (
"січня",
"лютого",
"березня",
"квітня",
"травня",
"червня",
"липня",
"серпня",
"вересня",
"жовтня",
"листопада",
"грудня",
).at(month - 1)
#let is-cyr(c) = regex("[\p{Cyrillic}]") in c
#let gender-form(verb, gender: "p") = {
(
"author": ("m": "Виконав", "f": "Виконала", "p": "Виконали"),
"mentor": ("m": "Перевірив", "f": "Перевірила", "p": "Перевірили"),
)
.at(verb)
.at(if gender == "m" or gender == "f" { gender } else { "p" }, default: "p")
}
#let pz-lb-title(type, number: none) = {
let type-title = (
"ЛБ": [Звіт \ з лабораторної роботи],
"ПЗ": [Звіт \ з практичної роботи],
"КР": [Контрольна робота],
"РФ": [Реферат], // зрада
).at(type, default: type)
if number != none { [#type-title #number] } else { [#type-title] }
}
// Helper functions {{{1
/// captioned image with label derived from path:
/// - "image.png" = @image
/// - "img/image.png" = @image
/// - "img/foo/image.png" = @foo_image
/// - "img/foo/foo_image.png" = @foo_image
/// the caption will be modified based on a conditional positional value:
/// - `none`: no change
/// - some value: "`caption` (за даними `value`)"
/// - no value: "`caption` (рисунок виконано самостійно)"
/// additional named arguments will be passed to original `image` function
#let img(path, caption, ..sink) = {
let parts = path.split(".").first().split("/")
let label_string = if (
parts.len() <= 2 or parts.at(-1).starts-with(parts.at(-2))
) {
// ("image",), (_, "image") and (.., "img", "img_image")
parts.last()
} else {
// (.., "img", "image") = "img_image"
parts.at(-2) + "_" + parts.at(-1)
}.replace(" ", "_")
let caption = if sink.pos().len() == 0 {
caption
} else if sink.pos().first() == none {
caption + " (рисунок виконано самостійно)"
} else {
[#caption (за даними #sink.pos().first())]
}
[#figure(
image(path, ..sink.named()),
caption: caption,
) #label(label_string)]
}
#let spacing = 0.95em // spacing between lines
#let num-to-alpha = "абвгдежиклмнпрстуфхцшщюя".split("") // 0 = "", 1 = "а"
/// DSTU 3008:2015 Style
/// -> content
/// - it (content): Content to apply the style to.
/// - skip (int): Do not show page number for this number of pages.
/// - offset (int): Adjust all page numbers by this amount.
#let dstu-style(
it,
skip: 0,
offset: 0,
) = {
// General Styling {{{1
set page(
paper: "a4",
number-align: top + right,
margin: (top: 20mm, right: 10mm, bottom: 20mm, left: 25mm),
numbering: (i, ..) => if i > skip { numbering("1", i + offset) },
)
set text(
lang: "uk",
size: 14pt,
hyphenate: false,
font: ("Times New Roman", "Liberation Serif"),
)
set par(
justify: true,
spacing: spacing,
leading: spacing,
first-line-indent: (amount: 1.25cm, all: true),
)
set block(spacing: spacing)
set underline(evade: false)
// Enums & Lists {{{1
// First level
set enum(
indent: 1.25cm,
body-indent: 0.5cm,
numbering: i => { num-to-alpha.at(i) + ")" },
)
// Second level and further nesting
show enum: it => {
set enum(indent: 0em, numbering: "1)")
it
}
// Lists are not intended for multiple levels, use `enum`
set list(indent: 1.35cm, body-indent: 0.5cm, marker: [--])
// Figures {{{1
show figure: it => {
v(spacing * 2, weak: true)
it
v(spacing * 2, weak: true)
}
set figure.caption(separator: [ -- ])
show figure.where(kind: table): set figure.caption(position: top)
show figure.caption.where(kind: table): set align(left)
show figure.where(kind: raw): set figure.caption(position: top)
show figure.where(kind: raw): set align(left)
// Numbering {{{1
show heading.where(level: 1): it => {
counter(math.equation).update(0)
counter(figure.where(kind: raw)).update(0)
counter(figure.where(kind: image)).update(0)
counter(figure.where(kind: table)).update(0)
it
}
set figure(numbering: i => numbering("1.1", counter(heading).get().at(0), i))
set math.equation(numbering: i => numbering("(1.1)", counter(heading).get().at(0), i))
// Headings {{{1
set heading(numbering: "1.1")
show heading: it => if it.level == 1 {
set align(center)
set text(size: 14pt, weight: "semibold")
pagebreak(weak: true)
upper(it)
v(spacing * 2, weak: true)
} else {
set text(size: 14pt, weight: "regular")
v(spacing * 2, weak: true)
block(width: 100%, spacing: 0em)[
#h(1.25cm)
#counter(heading).display(auto)
#it.body
]
v(spacing * 2, weak: true)
}
show heading.where(level: 3): it => {
set text(size: 14pt, weight: "regular")
v(spacing * 2, weak: true)
block(width: 100%, spacing: 0em)[
#h(1.25cm)
#counter(heading).display(it.numbering)
#it.body
]
v(spacing * 2, weak: true)
}
// listings {{{3
show raw.where(block: true): it => {
let new_spacing = 0.5em
set block(spacing: new_spacing)
set par(spacing: new_spacing, leading: new_spacing)
set text(
size: 11pt,
weight: "semibold",
font: ("Courier New", "Liberation Mono"),
)
v(spacing * 2.5, weak: true)
pad(it, left: 1.25cm)
v(spacing * 2.5, weak: true)
}
it
// }}}
}
/// DSTU 3008:2015 Appendices Style
/// -> content
/// - it (content): Content to apply the style to.
#let appendices-style(it) = /* {{{ */ {
// Numbering
counter(heading).update(0)
set heading(numbering: (i, ..n) => upper(num-to-alpha.at(i)) + numbering(".1.1", ..n))
set figure(numbering: i => upper(num-to-alpha.at(counter(heading).get().at(0))).i)
set math.equation(numbering: i => upper(num-to-alpha.at(counter(heading).get().at(0))).i)
// Heading supplement (Heading name shown when citing with @ref)
set heading(supplement: [Додаток])
// Headings
show heading: it => if it.level == 1 {
set align(center)
set text(size: 14pt, weight: "regular")
pagebreak(weak: true)
bold([ДОДАТОК #counter(heading).display(auto)])
linebreak()
it.body
v(spacing * 2, weak: true)
} else {
set text(size: 14pt, weight: "regular")
v(spacing * 2, weak: true)
block(width: 100%, spacing: 0em)[
#h(1.25cm)
#counter(heading).display(auto)
#it.body
]
v(spacing * 2, weak: true)
}
it
} // }}}
// Coursework template {{{1
/// DSTU 3008:2015 Template for NURE
/// -> content
/// - doc (content): Content to apply the template to.
/// - title (str): Title of the document.
/// - subject (str): Subject short name.
/// - authors ((name: str, full_name_gen: str, variant: int, course: int, semester: int, group: str, gender: str),): List of authors.
/// - mentors ((name: str, degree: str),): List of mentors.
/// - task_list (done_date: datetime, initial_date: datetime, source: (content | str), content: (content | str), graphics: (content | str)): Task list object.
/// - calendar_plan ( plan_table: (content | str), approval_date: datetime): Calendar plan object.
/// - abstract (keywords: (str, ), text: (content | str)): Abstract object.
/// - bib_path path: Path to the bibliography yaml file.
/// - appendices (content): Content with appendices.
#let coursework(
doc,
university: "ХНУРЕ",
subject: none,
title: none,
authors: (),
mentors: (),
task_list: (),
calendar_plan: (),
abstract: (),
bib_path: none,
appendices: (),
) = {
set document(title: title, author: authors.map(c => c.name))
show: dstu-style.with(skip: 1)
let bib-count = state("citation-counter", ())
show cite: it => {
it
bib-count.update(((..c)) => (..c, it.key))
}
show bibliography: it => {
set text(size: 0pt)
it
}
let author = authors.first()
let head_mentor = mentors.first()
let uni = universities.at(university)
let edu_prog = uni.edu_programs.at(author.edu_program)
// page 1 {{{2
[
#set align(center)
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ\
#upper(uni.name)
\
Кафедра #edu_prog.department_gen
\
ПОЯСНЮВАЛЬНА ЗАПИСКА\
ДО КУРСОВОЇ РОБОТИ\
з дисципліни: "#uni.subjects.at(subject, default: subject)"\
Тема роботи: "#title"
\ \ \
#columns(2, gutter: 4cm)[
#set align(left)
#set par(first-line-indent: 0pt)
#gender-form("author", gender: author.gender) ст. гр. #author.edu_program\-#author.group
\
Керівник:\
#head_mentor.degree
\
Робота захищена на оцінку
\
Комісія:\
#for m in mentors { [#m.degree\ ] }
#colbreak()
#set align(left)
#author.name
\ \
#head_mentor.name
\
#underline(" " * 35)
\ \
#for m in mentors { [#m.name\ ] }
]
#v(1fr)
Харків -- #task_list.done_date.display("[year]")
#pagebreak()
]
// page 2 {{{2
{
uline[#uni.name]
linebreak()
linebreak()
grid(
columns: (100pt, 1fr),
bold[
Кафедра
Дисципліна
Спеціальність
],
{
uline(align: left, edu_prog.department_gen)
linebreak()
uline(align: left, uni.subjects.at(subject, default: subject))
linebreak()
uline(align: left, [#edu_prog.code #edu_prog.name_long])
},
)
grid(
columns: (1fr, 1fr, 1fr),
gutter: 0.3fr,
[#bold[Курс] #uline(author.course)],
[#bold[Група] #uline([#author.edu_program\-#author.group])],
[#bold[Семестр] #uline(author.semester)],
)
linebreak()
linebreak()
linebreak()
align(center, bold[ЗАВДАННЯ \ на курсову роботу студента])
linebreak()
uline(align: left)[_#author.full_name_gen _]
linebreak()
linebreak()
bold[\1. Тема роботи:]
uline[#title.]
linebreak()
{
bold[\2. Строк здачі закінченої роботи:]
uline(task_list.done_date.display("[day].[month].[year]"))
hfill(10fr)
}
linebreak()
bold[\3. Вихідні дані для роботи:]
uline(task_list.source)
linebreak()
bold[\4. Зміст розрахунково-пояснювальної записки:]
uline(task_list.content)
linebreak()
bold[\5. Перелік графічного матеріалу:]
uline(task_list.graphics)
linebreak()
{
bold[\6. Дата видачі завдання:]
uline(task_list.initial_date.display("[day].[month].[year]"))
hfill(10fr)
}
pagebreak()
}
// page 3 {{{2
{
align(center, bold[КАЛЕНДАРНИЙ ПЛАН])
set par(first-line-indent: 0pt)
linebreak()
calendar_plan.plan_table
linebreak()
grid(
columns: (5fr, 5fr),
grid(
columns: (1fr, 2fr, 1fr),
gutter: 0.2fr,
[
Студент \
Керівник \
#align(center)["#underline[#calendar_plan.approval_date.day()]"]
],
[
#uline(align: center, []) \
#uline(align: center, []) \
#uline(align: center, month_gen(calendar_plan.approval_date.month()))
],
[
\ \
#underline[#calendar_plan.approval_date.year()] р.
],
),
[
#author.name, \
#head_mentor.degree
#head_mentor.name.
],
)
pagebreak()
}
// page 4 {{{2
[
#align(center, bold[РЕФЕРАТ]) \
#context [
#let pages = counter(page).final().at(0)
#let images = query(figure.where(kind: image)).len()
#let tables = query(figure.where(kind: table)).len()
#let bibs = bib-count.final().dedup().len()
/* TODO: why this stopped working?
#let tables = counter(figure.where(kind: table)).final().at(0)
#let images = counter(figure.where(kind: image)).final().at(0)*/
#let counters = ()
#if pages != 0 { counters.push[#pages с.] }
#if tables != 0 { counters.push[#tables табл.] }
#if images != 0 { counters.push[#images рис.] }
#if bibs != 0 { counters.push[#bibs джерел] }
Пояснювальна записка до курсової роботи: #counters.join(", ").
]
\
#(
abstract
.keywords
.map(upper)
.sorted(by: (a, b) => {
if is-cyr(a) != is-cyr(b) { true } else { a < b }
})
.join(", ")
)
\
#abstract.text
]
// page 5 {{{2
outline(
title: [
ЗМІСТ
#v(spacing * 2, weak: true)
],
depth: 2,
indent: auto,
)
doc
// bibliography {{{2
{
heading(depth: 1, numbering: none)[Перелік джерел посилання]
bibliography(
bib_path,
style: "ieee",
full: true,
title: none,
)
let bib_data = yaml(bib_path)
let format-entry(c) = {
if (c.type == "Web") {
let date_array = c.url.date.split("-")
let date = datetime(
year: int(date_array.at(0)),
month: int(date_array.at(1)),
day: int(date_array.at(2)),
)
[#c.title. #c.author. URL: #c.url.value (дата звернення: #date.display("[day].[month].[year]")).]
} else if (
c.type == "Book"
) [#c.author #c.title. #c.publisher, #c.date. #c.page-total c. ] else [
UNSUPPORTED BIBLIOGRAPHY ENTRY TYPE, PLEASE OPEN AN ISSUE
]
}
show enum.item: it => {
set par(first-line-indent: 0pt)
box(width: 1.25cm)
box(width: 1em + 0.5cm)[#it.number.]
it.body
linebreak()
}
context {
for (i, citation) in query(ref.where(element: none))
.map(r => str(r.target))
.dedup()
.enumerate() {
enum.item(
i + 1,
format-entry(bib_data.at(citation)),
)
}
}
}
appendices-style(appendices)
}
// Practice and Laboratory works template {{{1
/// DSTU 3008:2015 Template for NURE
/// -> content
/// - doc (content): Content to apply the template to.
/// - university: "ХНУРЕ": University metadata. Optional.
/// - subject: str: Subject shortcode.
/// - type: ("ЛБ" | "ПЗ" | "КР" | "РФ" | str): Work type.
/// - number: int or none: Work number. Optional.
/// - title: str or none: Work title. Optional.
/// - authors ((name: str, full_name_gen: str or none, edu_program: str, group: str, gender: str, variant: int or none),): List of authors.
/// - mentors ((name: str, degree: str, gender: ("m" | "f" | "p" | none)),): List of mentors. Optional.
#let pz-lb(
doc,
university: "ХНУРЕ",
subject: none,
type: none,
number: none,
title: none,
authors: (),
mentors: (),
) = {
// TODO: add actually relevant asserts
let edu_program = authors.first().edu_program
let uni = universities.at(university)
set document(title: title, author: authors.map(c => c.name))
show: dstu-style.with(skip: 1)
// page 1 {{{2
align(center)[
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ \
#upper(uni.name)
\ \
Кафедра #uni.edu_programs.at(edu_program).department_gen
\ \ \
#pz-lb-title(type, number: number)
з дисципліни: "#uni.subjects.at(subject, default: subject)"
#if title != none [\ з теми: "#eval(title, mode: "markup")"]
\ \ \ \
#columns(2)[
#set align(left)
#set par(first-line-indent: 0pt)
#if authors.len() == 1 {
[#gender-form("author", gender: if "gender" in a.keys() { a.gender } else { none }):\ ]
let a = authors.first()
[ст. гр. #a.edu_program\-#a.group\ #a.name\ ]
if a.variant != none [Варіант: #a.variant]
} else if authors.len() > 1 [
#gender-form("author"):\
#for a in authors [ст. гр. #a.edu_program\-#a.group\ #a.name\ ]
]
#colbreak()
#set align(right)
#if mentors.len() == 1 {
[#gender-form("mentor", gender: if "gender" in m.keys() { m.gender } else { none }):\ ]
let m = mentors.first()
if "degree" in m.keys() and m.degree != none [#m.degree\ ]
[#m.name\ ]
} else if mentors.len() > 1 [
#gender-form("mentor"):\
#for mentor in mentors { [#mentor.degree\ #mentor.name\ ] }]
]
#v(1fr)
Харків -- #datetime.today().display("[year]")
]
pagebreak(weak: true)
// TODO(unexplrd): wrap my head around the old way
if title == none {
if number == none { context counter(heading).update(1) } else {
context counter(heading).update(number)
}
} else {
if number != none { context counter(heading).update(number - 1) }
heading(eval(title, mode: "markup"))
}
doc
}
// vim:sts=2:sw=2:fdm=marker:cms=/*%s*/
@@ -1,66 +1,90 @@
ХНУРЕ:
name: Харківський національний університет радіоелектроніки
name_en: Kharkiv National University of Radioelectronics
edu_programs:
name-en: Kharkiv National University of Radioelectronics
edu-programs:
ПЗПІ:
name_long: Інженерія програмного забезпечення
department_gen: Програмної інженерії
name-long: Інженерія програмного забезпечення
department-gen: Програмної інженерії
code: 121 # TODO: change to F2?
КУІБ:
name_long: Управління інформаційною безпекою
department_gen: Інфокомунікаційної інженерії ім. В. В. Поповського
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
name-en: CST # computer sciences and technologies
name-long: Комп'ютерні науки та технології
department-gen: Системотехніки
department-en: ST
code: 122
subjects:
DMT: Decision making theory
NoSQL: NoSQL-системи
ODS: Основи Dаtа Sсіеnсе # NOTE: Eng O here
ІМ: Іноземна мова
ІТР: Information Technologies of Reengineering
ІТРОІ: Інтернет-технології Розподіленої Обробки Інформації
АВпЗ: Аналіз вимог до програмного забезпечення
АДан: Аналітика даних
АКС: Архітектура комп'ютерних систем
АКтаК: Архітектура комп'ютера та комп'ютерних мереж
АТСД: Алгоритми та структури даних
АПЗ: Архітектура програмного забезпечення
АтаРК: Аналіз та рефакторинг коду
АТСД: Алгоритми та структури даних
БД: Бази даних
БЖД: Безпека життєдіяльності
ВSAP: Введення до SAP-технологій
ВДІТБ: Введення до ІТ-бізнесу # NOTE: all in UA
ВМ: Вища математика
ВМПтФ: Високорівневі мови програмування та фреймворки
ГТГ: Гіпертекст та гіпермедіа
ДМ: Дискретна математика
ДПК: Динаміка Проектних Команд
ЕРВ: Електрорадіовимірювання
ІКС: Інформаційно-комунікаційні системи
ІМ: Іноземна мова
ІМпк: Іноземна мова для професійної комунікації
ІНТ: Історія науки і техніки
ІТР: Information Technologies of Reengineering
ІТРОІ: Інтернет-технології Розподіленої Обробки Інформації
КДМА: Комп'ютерна дискретна математика
КЗВШ: Креативність з використанням штучного інтелекту
КМ: Комп`ютерні мережі
КМ: Комп'ютерні мережі
ЛМВ: Людино-машинна взаємодія
ЛМтБ: Локальні мережі та їх безпека # бидло не знає що українською "їхня"
ЛМтБ: Локальні мережі та їх безпека
Лог: Логіка
МБ: Мережна безпека
МКр: Мережна криміналістика
МОКр: Математичні основи криптології
МОТДО: Методи оптимізаціі та дослідження операцій
МОТІ: Методи оптимізації та теорія ігор
МППЗ: Менеджмент проектів програмного забезпечення
МППС: Methodologies of designing software systems
МС: Моделювання систем
ОІДт: Обробка ігрових даних та звітів
ОІМ: Основи IP-мереж
ОКЗІ: Основи криптографічного захисту інформації
ООАПС: Об'єктно-орієнтований аналіз в проектуванні систем
ООП: Об'єктно-орієнтоване програмування
ОП: Основи права
ОПІ: Основи програмноі інженеріі
ОПНJ: Основи програмування на Java
ОП: Основи права
ОПр: Основи програмування
ОРвІТ: Оцінка Ризиків в IT-проектах
ОС: Операційні системи
ОТК: Основи теорії кіл
ПPyt: Програмування мовою Python
ПарП: Параллельне програмування
ПБІП: Проектування та балансування ігрового процесу
ПВJS: Поглиблене вивчення JavaScript
ПВJ: Поглиблене вивчення Java
ПвІТ: Підприємництво в ІТ
ПЕСЕ: Психологія екстремальних стосунків та ефективної адаптації
ПНП: Програмування на платформі .NЕТ
ПП: Проектний практикум
ПРОГ: Програмування
ПарП: Параллельне програмування
Про1: Програмування (ч. 1)
Про2: Програмування (ч. 2)
Про3: Програмування (ч. 3)
Прог: Програмування # NOTE: was "ПРОГ" before
РNet: Робота з даними на платформі .Net
РХЗ: Розробка хмарних застосувань в AZURE
СА: Системний аналіз
СМП: Скриптові мови програмування
СОАПЗ: Сервіс-Орієнтована Архітектура Програмного Забезпечення
@@ -68,10 +92,11 @@
СхТ: Схемотехніка
ТВО: Технології Високопродуктивних Обчислень
ТЗІ: Технології захисту інформації
ТІК: Теорія інформації та кодування
ТЙтаМ: Теорія ймовірностей та математична # TODO: what?
ТКП: Технології комп`ютерного проєктування
ТКП: Технології комп'ютерного проєктування
УФМ: Українське фахове мовлення
ФВС: Фізичне виховання та спорт
ФІЗ: Фізика
ФІЛ: Філософія
ФВС: Фізичне виховання та спорт
ХТ: Хмарні технології
+357
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
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] }
}
+196
View File
@@ -0,0 +1,196 @@
#import "./title-pages/main.typ" as tp
#import "./shared.typ": universities
#import "./helpers.typ": *
#import "./style.typ"
#import "./utils.typ"
#let dstu-table = style.dstu-table
#let hfill = utils.hfill
/// 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: (),
) = {
let doc-title = if type(title) == array { title.join(" ") } else { title }
set document(title: doc-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)
}
/// Alternative coursework template for NURE.
/// - university (str): University code, default "ХНУРЕ"
/// - 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
/// - abstract (dict): Keywords and abstract text
/// - abstract-en (dict): Optional English keywords and abstract text
/// - bib-path (str): Path to bibliography file
/// - appendices (content): Appendix content
#let coursework-v2(
doc,
university: "ХНУРЕ",
title: none,
authors: (),
mentors: (),
task-list: (),
calendar-plan: (),
abstract: (),
abstract-en: none,
bib-path: none,
appendices: (),
faculty: "комп’ютерних наук",
education-level: "перший (бакалаврський)",
program-type: "освітньо-професійна",
program-name: none,
) = {
assert(authors.len() > 0, message: "At least one author required")
assert(mentors.len() > 0, message: "At least one mentor required")
let doc-title = if type(title) == array { title.join(" ") } else { title }
set document(title: doc-title, author: authors.map(c => c.name))
show: style.dstu.with(skip: 1)
let bib-count = state("citation-counter", ())
show cite: it => {
it
bib-count.update(((..c)) => (..c, it.key))
}
let abstract = if abstract-en != none {
abstract + (en: abstract-en)
} else {
abstract
}
tp.cw-v2.nure(
university,
title,
authors,
mentors,
task-list,
calendar-plan,
abstract,
bib-count,
faculty: faculty,
education-level: education-level,
program-type: program-type,
program-name: program-name,
)
doc
{
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: (),
skip-heading: false,
) = {
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))()
if not skip-heading {
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
View File
@@ -0,0 +1 @@
#let universities = yaml("config/universities.yaml")
+259
View File
@@ -0,0 +1,259 @@
#import "utils.typ": bold
/// Constants for consistent styling
#let spacing = 0.95em
#let indent-size = 1.25cm
#let double-spacing = spacing * 2
#let double-half-spacing = spacing * 2.5
/// Ukrainian alphabet for DSTU 3008:2015 numbering
#let ukr-enum = "абвгдежиклмнпрстуфхцшщюя".clusters()
#let dstu-table-counter = counter("dstu-table")
#let dstu-table-appendix = state("dstu-table-appendix", none)
#let dstu-table-caption-gap = 0.65em
/// 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)
}
#let _col-count(columns) = {
if type(columns) == int {
columns
} else if type(columns) == array {
columns.len()
} else {
panic("dstu-table: columns must be an int or array, e.g. 2 or (1fr, 3fr)")
}
}
#let _required(name, value) = {
if value == none {
panic("dstu-table: " + name + " is required")
}
value
}
#let dstu-table-label(it) = {
set par(first-line-indent: 0pt)
align(left)[#it]
}
#let dstu-table(
caption: none,
columns: none,
header: none,
..args,
) = {
let caption = _required("caption", caption)
let columns = _required("columns", columns)
let header = _required("header", header)
if type(header) != array {
panic("dstu-table: header must be an array, e.g. ([A], [B])")
}
dstu-table-counter.step()
let named = args.named()
let body = args.pos()
context {
let h = counter(heading).get()
let section = if h.len() > 0 { h.at(0) } else { 0 }
let n = dstu-table-counter.get().first()
let appendix = dstu-table-appendix.get()
let num = if appendix == none {
numbering("1.1", section, n)
} else {
upper(ukr-enum.at(appendix - 1)) + "." + str(n)
}
let id = "dstu-table-" + str(section) + "-" + str(n)
let start-marker = "start-" + id
let end-marker = "end-" + id
let cols = _col-count(columns)
v(double-spacing, weak: true)
{
set block(spacing: dstu-table-caption-gap)
block(sticky: true)[
#dstu-table-label[Таблиця #num -- #caption]
]
table(
columns: columns,
..named,
table.header(
repeat: true,
table.cell(
colspan: cols,
stroke: none,
inset: 0pt,
)[
#metadata(start-marker)
#context {
let starts = query(metadata.where(value: start-marker))
let ends = query(metadata.where(value: end-marker))
if starts.len() > 0 and ends.len() > 0 {
let start-page = starts.first().location().page()
let end-page = ends.first().location().page()
let current-page = here().page()
if current-page != start-page {
let label = if current-page == end-page {
[Кінець таблиці #num]
} else {
[Продовження таблиці #num]
}
pad(top: dstu-table-caption-gap, bottom: dstu-table-caption-gap)[
#dstu-table-label[#label]
]
}
}
}
],
..header,
),
..body,
)
}
metadata(end-marker)
v(double-spacing, weak: true)
}
}
/// DSTU 3008:2015 Style
#let dstu(
it,
skip: 0,
offset: 0,
) = {
// Page setup
set page(
paper: "a4",
number-align: top + right,
margin: (top: 20mm, right: 10mm, bottom: 20mm, left: 25mm),
numbering: (i, ..) => if i > skip { numbering("1", i + offset) },
)
// Text and paragraph
set text(lang: "uk", size: 14pt, hyphenate: false, font: ("Times New Roman", "Liberation Serif"))
set par(justify: true, spacing: spacing, leading: spacing, first-line-indent: (amount: indent-size, all: true))
set block(spacing: spacing)
set underline(evade: false)
// Lists
set enum(indent: indent-size, body-indent: 0.5cm, numbering: i => ukr-enum.at(i - 1) + ")")
show enum: it => {
set enum(indent: 0em, numbering: "1)")
it
}
set list(indent: indent-size + 0.1cm, body-indent: 0.5cm, marker: [--])
// Figures
show figure: it => {
v(double-spacing, weak: true)
it
v(double-spacing, weak: true)
}
set figure.caption(separator: [ -- ])
show figure.where(kind: table): set figure.caption(position: top)
show figure.caption.where(kind: table): set align(left)
show figure.where(kind: raw): set figure.caption(position: top)
show figure.where(kind: raw): set align(left)
// Numbering reset on level 1 headings
show heading.where(level: 1): it => {
counter(math.equation).update(0)
counter(figure.where(kind: raw)).update(0)
counter(figure.where(kind: image)).update(0)
counter(figure.where(kind: table)).update(0)
it
}
set figure(numbering: i => context numbering("1.1", counter(heading).get().at(0), i))
set math.equation(numbering: i => context numbering("(1.1)", counter(heading).get().at(0), i))
// Headings
set heading(numbering: "1.1")
show heading: it => {
set text(size: 14pt)
if it.level == 1 {
set align(center)
set text(weight: "semibold")
pagebreak(weak: true)
upper(it)
v(double-spacing, weak: true)
} else {
set text(weight: "regular")
heading-block(it, num: if it.level == 3 { it.numbering } else { auto })
}
}
// Code listings
show raw.where(block: true): it => {
let code-spacing = 0.5em
set block(spacing: code-spacing)
set par(spacing: code-spacing, leading: code-spacing)
set text(size: 11pt, weight: "semibold", font: ("Courier New", "Liberation Mono"))
v(double-half-spacing, weak: true)
pad(it, left: indent-size)
v(double-half-spacing, weak: true)
}
it
}
/// DSTU 3008:2015 Appendices Style
#let appendices(it) = {
counter(heading).update(0)
set heading(numbering: (i, ..n) => upper(ukr-enum.at(i - 1)) + numbering(".1.1", ..n))
set heading(supplement: [Додаток])
let app-letter = context upper(ukr-enum.at(counter(heading).get().at(0) - 1))
set figure(numbering: i => app-letter + "." + str(i))
set math.equation(numbering: i => [(#app-letter.#str(i))])
show heading: h => {
set text(size: 14pt)
if h.level == 1 {
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)
dstu-table-counter.update(0)
dstu-table-appendix.update(counter(heading).get().at(0))
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
}
+1
View File
@@ -0,0 +1 @@
#import "nure.typ": *
+361
View File
@@ -0,0 +1,361 @@
#import "../../shared.typ": universities
#import "../../helpers.typ": *
#import "../../style.typ": spacing
#import "../../utils.typ": bold, uline, filled-lines
#let note(content) = block(width: 100%, above: 5pt, below: 0pt)[
#set text(size: 10pt)
#set par(first-line-indent: 0pt, spacing: 0pt)
#align(center)[#content]
]
#let form-field(alignment: center, content) = box(
width: 100%,
stroke: (bottom: 0.5pt),
inset: (bottom: 1.5pt),
)[
#align(alignment)[#content]
]
#let label-line(label, value, caption: none, label-width: auto) = {
set par(first-line-indent: 0pt)
if label-width == auto {
[#label #form-field(alignment: center, value)]
} else {
grid(
columns: (label-width, 1fr),
gutter: 0pt,
align: horizon,
label, form-field(alignment: center, value),
)
}
if caption != none {
note(caption)
}
}
#let inline-field(value) = form-field(alignment: center, value)
#let inline-label-line(label, value) = {
set par(first-line-indent: 0pt)
block(width: 100%, below: 0pt)[
#label #uline(align: center, value)
]
}
#let task-head-fields(fields) = {
set par(first-line-indent: 0pt)
let cells = ()
for (label, value) in fields {
if type(value) == array {
for (i, line) in value.enumerate() {
cells.push(if i == 0 { label } else { [] })
cells.push(inline-field(line))
}
} else {
cells.push(label)
cells.push(inline-field(value))
}
}
grid(
columns: (auto, 1fr),
gutter: 0pt,
row-gutter: 0.65em,
align: top,
..cells,
)
}
#let task-num(n) = box(str(n) + ".")
#let title-field(value) = {
uline(align: center, filled-lines(value))
uline(align: center, [])
note[(тема)]
}
#let nure(
university,
title,
authors,
mentors,
task-list,
calendar-plan,
abstract,
bib-count,
faculty: "комп'ютерних наук",
education-level: "перший (бакалаврський)",
program-type: "освітньо-професійна",
program-name: none,
) = {
let author = authors.first()
let head-mentor = mentors.first()
let uni = universities.at(university)
let edu-prog = uni.edu-programs.at(author.edu-program)
let program-name = if program-name == none {
edu-prog.at("program-name", default: edu-prog.name-long)
} else {
program-name
}
let group-name = if str(author.group).starts-with(author.edu-program) {
str(author.group)
} else {
author.edu-program + "-" + str(author.group)
}
let executor-label = if author.gender == "f" or author.gender == "female" or author.gender == "ж" {
"Виконала:"
} else {
"Виконав:"
}
let author-display-name = author.at("display-name", default: author.name)
let author-full-name-dat = author.at("full-name-dat", default: author.full-name-gen)
let mentor-display-name = head-mentor.at("display-name", default: head-mentor.name)
let mentor-degree = head-mentor.at("degree", default: "")
[
#set par(first-line-indent: 0pt, justify: false, leading: 0.45em)
#set text(size: 14pt)
#set align(center)
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ\
#uni.name
#v(0.7em)
#set align(left)
#inline-label-line(
[Факультет],
faculty,
)
#note([(повна назва)])
#v(0.8em)
#inline-label-line(
[Кафедра],
lower(edu-prog.department-gen),
)
#note([(повна назва)])
#v(1.8em)
#set align(center)
#text(size: 20pt, weight: "bold")[КОМПЛЕКСНИЙ КУРСОВИЙ ПРОЄКТ]\
#text(size: 20pt, weight: "bold")[Пояснювальна записка]
#v(0.9em)
#set align(left)
#label-line([рівень вищої освіти], education-level, label-width: 120pt)
#v(1.0em)
#title-field(title)
#v(3em)
#grid(
columns: (0.36fr, 0.64fr),
[],
[
#executor-label\
здобувач #underline([#author.course]) курсу, групи #underline(group-name)\
#uline(align: center, author-display-name)
#note[(Власне ім'я, ПРІЗВИЩЕ)]
#v(0.2em)
#inline-label-line([Спеціальність], [#edu-prog.code -- #edu-prog.name-long])
#note[(код і повна назва спеціальності)]
#inline-label-line([Тип програми], program-type)
#inline-label-line([Освітня програма], program-name)
#note[(повна назва освітньої програми)]
#v(0.3em)
#inline-label-line([Керівник], [#mentor-degree #mentor-display-name])
#note[(посада, Власне ім'я, ПРІЗВИЩЕ)]
#v(0.3em)
#pad(left: 75pt)[
#set par(first-line-indent: 0pt)
Члени комісії (#text(size: 10pt)[Власне ім'я, ПРІЗВИЩЕ, підпис])
#v(0.55em)
#line(length: 100%, stroke: 0.5pt)
#v(0.55em)
#line(length: 100%, stroke: 0.5pt)
#v(0.55em)
#line(length: 100%, stroke: 0.5pt)
]
],
)
#v(1fr)
#set align(center)
#task-list.done-date.display("[year]") р.
#pagebreak()
]
[
#set par(first-line-indent: 0pt, justify: false)
#align(center)[#uni.name]
#v(1.1em)
#task-head-fields((
(
[Факультет],
(
faculty,
),
),
([Кафедра], lower(edu-prog.department-gen)),
([Рівень вищої освіти], education-level),
([Спеціальність], [#edu-prog.code -- #edu-prog.name-long]),
([Тип програми], program-type),
([Освітня програма], program-name),
))
#note[(шифр і назва)]
#v(1.7em)
#grid(
columns: (1.1fr, 1.1fr, 1.1fr, 1.7fr, 1.3fr, 1.1fr),
gutter: 0pt,
align: center + horizon,
[Курс], uline(author.course), [Група], uline(group-name), [Семестр], uline(author.semester),
)
#v(2.6em)
#align(center)[
#bold[ЗАВДАННЯ]\
#text(style: "italic", weight: "bold")[на курсовий проєкт (роботу) студента]
]
#v(1.0em)
#label-line([здобувачеві], author-full-name-dat, caption: [(прізвище, ім'я, по батькові)], label-width: 95pt)
#v(1.0em)
#task-num(1) Тема роботи #uline(align: left, filled-lines(title))
#v(0.4em)
#task-num(2) Термін здачі студентом закінченої роботи
“#underline(task-list.done-date.display("[day]"))” #underline(month-gen(task-list.done-date.month())) #task-list.done-date.display("[year]")р.
#v(0.4em)
#task-num(3) Вихідні дані до проєкту #uline(align: left, filled-lines(task-list.at("source", default: [])))
#v(0.4em)
#uline(align: left, [])
#v(0.4em)
#task-num(4) Перелік питань, що потрібно опрацювати в роботі\
#uline(align: left, filled-lines(task-list.at("content", default: [])))
#v(0.4em)
#uline(align: left, [])
#pagebreak()
]
[
#align(center, bold[КАЛЕНДАРНИЙ ПЛАН])
#set par(first-line-indent: 0pt)
#v(1.4em)
#calendar-plan.plan-table
#v(5.0em)
Дата видачі завдання “#underline(task-list.initial-date.display("[day]"))” #underline(month-gen(task-list.initial-date.month())) #task-list.initial-date.display("[year]")р.
#v(1.4em)
Здобувач #uline(align: center, [])
#note[(підпис)]
#v(1.4em)
Керівник роботи #uline(align: center, []) #h(1cm) #underline[#mentor-degree #mentor-display-name]
#note[(підпис) #h(4.5cm) (посада, Власне ім'я, ПРІЗВИЩЕ)]
#pagebreak()
]
[
#let header = if abstract.at("en", default: none) != none {
bold[РЕФЕРАТ / ABSTRACT]
} else {
bold[РЕФЕРАТ]
}
#align(center, header) \
#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()
#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) { is-cyr(a) } else { a < b }
})
.join(", ")
)
\
#abstract.text
#if abstract.at("en", default: none) != none [
\
#(abstract.en.keywords.map(upper).join(", "))
\
#abstract.en.text
]
]
{
show outline.entry: it => {
let el = it.element
if el.func() == heading and el.supplement == [Додаток] {
if el.level > 1 {
none
} else {
block(width: 100%)[
#link(el.location())[
ДОДАТОК #it.prefix()#h(0.5em)#it.inner()
]
]
}
} else {
it
}
}
outline(
title: [
ЗМІСТ
#v(spacing * 2, weak: true)
],
depth: 2,
indent: auto,
)
}
}
+1
View File
@@ -0,0 +1 @@
#import "nure.typ": *
+254
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,
)
}
+3
View File
@@ -0,0 +1,3 @@
#import "pz-lb/main.typ" as pz-lb
#import "coursework/main.typ" as cw
#import "coursework-v2/main.typ" as cw-v2
+60
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]")
]
}
+2
View File
@@ -0,0 +1,2 @@
#import "complex.typ": *
#import "nure.typ": *
+55
View File
@@ -0,0 +1,55 @@
#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):\ ]
if ("edu-program" in a) and ("group" in a) [ст. гр. #a.edu-program\-#a.group\ ]
[#a.name\ ]
if ("variant" in a) and (not is-empty(a.variant)) [Варіант: #a.variant]
} else if authors.len() > 1 [
#gender-form("author"):\
#for a in authors [
#if ("edu-program" in a) and ("group" in a) [ ст. гр. #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]")
]
}
+63
View File
@@ -0,0 +1,63 @@
/// 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)
/// convert an array of lines into filled lines for underlined task fields
#let filled-lines(content) = if type(content) == array {
content.map(line => [#line #hfill(1fr)]).join(linebreak())
} else {
content
}
/// 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) ]
}
@@ -6,7 +6,6 @@ go:
value: https://go.dev/
date: 2024-12-10
htmx:
type: Web
title: Htmx - high power tools for html
+144
View File
@@ -0,0 +1,144 @@
#import "@local/nure:0.1.1": *
#import style: spacing
#let authors = (
(
name: "Сокорчук І. П.",
display-name: "Ігор СОКОРЧУК",
full-name-gen: "Сокорчука Ігоря Петровича",
full-name-dat: "Сокорчуку Ігорю Петровичу",
edu-program: "ПЗПІ",
group: "23-3",
gender: "m",
course: 3,
semester: 6,
variant: 13,
),
)
#let mentors = (
(name: "Сокорчук І. П.", display-name: "Ігор СОКОРЧУК", degree: "ст.викл. кафедри ПІ"),
)
#let task-list = (
done-date: datetime(year: 2026, month: 12, day: 27),
initial-date: datetime(year: 2026, month: 9, day: 15),
source: (
[Узагальнити модель предметної області,],
[визначити основні сутності та зв'язки між ними.],
[Підготувати демонстраційний набір технологій для шаблону.],
),
content: (
[Опис предметної галузі, формування вимог,],
[архітектурне проєктування, тестування та],
[перевірка верстки пояснювальної записки.],
),
)
#let calendar-plan = (
plan-table: table(
columns: (0.7fr, 5.8fr, 2.5fr, 1.6fr),
align: (center, left, center, center),
[], [Назва етапів роботи], [Термін виконання етапів роботи], [Примітка],
[1], [Аналіз предметної галузі], [], [виконано],
[2], [Розробка постановки задачі], [], [виконано],
[3], [Проєктування ПЗ], [], [виконано],
[4], [Програмна реалізація], [], [виконано],
[5], [Аналіз результатів], [], [виконано],
[6], [Підготовка пояснювальної записки.], [], [виконано],
[7], [Перевірка на наявність ознак академічного плагіату], [], [виконано],
[8], [Захист роботи], [], [виконано],
),
)
#let abstract = (
keywords: (
"веб-застосунок",
"інформаційна система",
"курсова робота",
"програмна інженерія",
"тестовий приклад",
),
text: [
Мета даної роботи -- продемонструвати оформлення пояснювальної записки
для комплексного курсового проєкту з використанням нового варіанта шаблону.
Приклад містить узагальнену тему, типові сторінки завдання, календарного плану,
реферату, змісту, переліку джерел посилання та додатків.
У роботі наведено умовну структуру програмної системи, що може бути
адаптована під конкретну предметну область. Основний акцент зроблено на
перевірці полів титульної сторінки, сторінки завдання, службових підписів,
нумерації розділів і коректної роботи додатків.
Результатом є демонстраційний документ, який показує очікуване використання
шаблону без прив'язки до реального студента, викладача або завершеної роботи.
],
)
#let abstract_en = (
keywords: (
"WEB APPLICATION",
"INFORMATION SYSTEM",
"COURSEWORK",
"SOFTWARE ENGINEERING",
"DEMO SAMPLE",
),
text: [
The purpose of this work is to demonstrate the formatting of a coursework
explanatory note using the new template variant.
The sample contains a generalized topic, typical pages for the assignment,
calendar plan, abstract, contents, bibliography, and appendices.
The work presents an illustrative structure of a software system that can be
adapted to a specific subject area. The main focus is on checking title-page
fields, assignment-page fields, signature blocks, section numbering, and
appendix handling.
The result is a demonstration document that shows the expected template usage
without being tied to a real student, supervisor, or completed project.
],
)
#let appendices = [
= Приклад звіту 1
#v(-spacing)
== Частина 1
#lorem(100)
== Частина 2
#lorem(200)
= Приклад звіту 2
#lorem(200)
= Приклад звіту 3
#lorem(200)
]
#show: coursework-v2.with(
title: (
"Демонстраційна інформаційна система для комплексного ",
"курсового проєкту",
),
authors: authors,
mentors: mentors,
task-list: task-list,
calendar-plan: calendar-plan,
abstract: abstract,
abstract-en: abstract_en,
bib-path: bytes(read("bibl.yml")),
appendices: appendices,
)
= Моделювання
#lorem(250)
= Імплементація
#v(-spacing)
== Підготовка
#lorem(200)
== Процес
#lorem(500)
= Тестування
#lorem(300)
@@ -1,16 +1,19 @@
#import "lib.typ": *
#import "@local/nure:0.1.1": *
#import style: spacing
#import "utils.typ": img
#let authors = (
(
name: "Ситник Є. С.",
full_name_gen: "Ситника Єгора Сергійовича",
edu_program: "ПЗПІ",
full-name-gen: "Ситника Єгора Сергійовича",
edu-program: "ПЗПІ",
group: "23-2",
gender: "m",
course: 2,
semester: 3,
variant: 13,
)
),
)
#let mentors = (
@@ -19,54 +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],
[Виконано],
[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],
[Виконано],
[8], [Тестування програми, наповнення бази даних], [20.11.24 - 5.12.24], [Виконано],
[9], [Друга контрольна точка з курсової роботи], [7.12.24], [Виконано],
[10],
[Реалізація остаточної версії програми],
[7.12.24-15.12.24],
[Виконано],
[10], [Реалізація остаточної версії програми], [7.12.24-15.12.24], [Виконано],
[11],
[Оформлення інших розділів пояснювальної записки],
[1.11.24 25.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 = (
@@ -110,7 +98,7 @@
#v(-spacing)
== Частина 1
#lorem(100)
== Частина2
== Частина 2
#lorem(200)
= Приклад звіту 2
@@ -125,10 +113,10 @@
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", // NOTE: use `bytes("bibl.yml")` as typst looks in template dir when using just filename
bib-path: bytes(read("bibl.yml")), // NOTE: use `bytes("bibl.yml")` as typst looks in template dir when using just filename
appendices: appendices,
)
@@ -1,4 +1,7 @@
#import "lib.typ": *
#import "@local/nure:0.1.1": *
#import "utils.typ": img
#import style: spacing
#show: pz-lb.with(
university: "ХНУРЕ",
@@ -13,8 +16,8 @@
authors: (
(
name: "Косач Л. П.",
full_name_gen: "Косач Лариси Петрівни",
edu_program: "ПЗПІ",
full-name-gen: "Косач Лариси Петрівни",
edu-program: "КУІБ",
group: "23-2",
gender: "f",
course: 2,
@@ -52,7 +55,7 @@
- #lorem(42);
- #lorem(27).
#show: appendices-style
#show: style.appendices
= Quote
#link("https://youtu.be/bJQj1uKtnus")[
@@ -65,7 +68,7 @@
#v(-spacing)
== Частина 1
#lorem(100)
== Частина2
== Частина 2
#lorem(200)
= Приклад звіту 2
+7
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) ]
}
+66
View File
@@ -0,0 +1,66 @@
[settings]
quiet = true
env_shell_expand = true
lockfile = true
[tools]
typst = "latest"
typstyle = "latest"
tinymist = "latest"
yq = "latest"
[vars]
vendor_dir = "{{config_root}}/vendor"
package_dir = "{{vars.vendor_dir}}/typst-packages"
work_doc_config = "src/doc.toml"
work_input_file = "src/main.typ"
work_output_file = "{{config_root}}/main.pdf"
nure_package_repo = "https://gitea.linerds.us/pencelheimer/typst_nure_template.git"
nure_package_ref = "0.1.1"
nure_package_rev = "f60fefc7b6f4df5b6fcb835bfc9742dc92dd9abf"
nure_package_name = "vendor/nure"
nure_package_ver = "{{vars.nure_package_ref}}"
nure_package_path = "{{vars.package_dir}}/{{vars.nure_package_name}}/{{vars.nure_package_ver}}"
[env]
TYPST_PACKAGE_DIR = "{{vars.package_dir}}"
TYPST_PACKAGE_PATH = "$TYPST_PACKAGE_DIR"
[tasks]
compile.depends = ["fetch-nure-package"]
watch.depends = ["fetch-nure-package"]
compile.run = "mise exec -- typst compile {{vars.work_input_file}} {{vars.work_output_file}}"
watch.run = "mise exec -- typst watch {{vars.work_input_file}} {{vars.work_output_file}}"
format.run = "mise exec -- typstyle -l 120 -i $MISE_PROJECT_ROOT/src"
clean.run = "rm -rvif *.pdf"
update-package-rev.run = "mise config set vars.nure_package_rev $(git ls-remote {{vars.nure_package_repo}} refs/heads/{{vars.nure_package_ref}} | cut -f1)"
[tasks.fetch-nure-package]
silent = "stdout"
run = """
#!/usr/bin/env bash
if [ ! -d {{vars.nure_package_path}}/.git ]; then
git clone --depth 1 --revision {{vars.nure_package_rev}} {{vars.nure_package_repo}} {{vars.nure_package_path}}
else
git -C {{vars.nure_package_path}} fetch --depth 1 origin {{vars.nure_package_rev}}
git -C {{vars.nure_package_path}} checkout {{vars.nure_package_rev}}
fi
"""
[tasks.rename]
depends = ["compile"]
run = """
#!/usr/bin/env bash
# Generates the following name from doc.toml: ПЗ1_Прізвище_ПЗПІ-23-2_ПарП.pdf
# Assumes authors.at(0).name to be in the following format: "Прізвище І. Б."
final_name=$(mise exec -- yq '.type + (.number | tostring) + "_" + (.authors[0].name | split(" "))[0] + "_" + (.authors[0].edu-program + "-" + .authors[0].group) + "_" + .subject + ".pdf"' "{{vars.work_doc_config}}");
cp -v "{{vars.work_output_file}}" "$final_name"
"""
+35
View File
@@ -0,0 +1,35 @@
[[tools.tinymist]]
version = "0.14.14"
backend = "aqua:Myriad-Dreamin/tinymist"
"platforms.linux-arm64" = { checksum = "sha256:39eb99d290fd1aa0e4508e5d5bbbf5a49648cf26da8e7bc3ff49f75c5fd0dc6c", url = "https://github.com/Myriad-Dreamin/tinymist/releases/download/v0.14.14/tinymist-linux-arm64"}
"platforms.linux-x64" = { checksum = "sha256:cda2ba76a455f3577cf2a1bd6340b75491f0c673770f631cf4d3232663860776", url = "https://github.com/Myriad-Dreamin/tinymist/releases/download/v0.14.14/tinymist-linux-x64"}
"platforms.macos-arm64" = { checksum = "sha256:ea7a22af0d54a71fbd1ee6c8c9f8434b521549926ae0f53a82391adeea741aba", url = "https://github.com/Myriad-Dreamin/tinymist/releases/download/v0.14.14/tinymist-darwin-arm64"}
"platforms.macos-x64" = { checksum = "sha256:f640889414d5030de1f03b966ccb10a84941bc1176980ff956d66714508ccd85", url = "https://github.com/Myriad-Dreamin/tinymist/releases/download/v0.14.14/tinymist-darwin-x64"}
"platforms.windows-x64" = { checksum = "sha256:3d39c7b417287d8689c69159b65fbc62427e141b9489500ad81039ee783f2549", url = "https://github.com/Myriad-Dreamin/tinymist/releases/download/v0.14.14/tinymist-win32-x64.exe"}
[[tools.typst]]
version = "0.14.2"
backend = "aqua:typst/typst"
"platforms.linux-arm64" = { checksum = "sha256:491b101aa40a3a7ea82a3f8a6232cabb4e6a7e233810082e5ac812d43fdcd47a", url = "https://github.com/typst/typst/releases/download/v0.14.2/typst-aarch64-unknown-linux-musl.tar.xz"}
"platforms.linux-x64" = { checksum = "sha256:a6044cbad2a954deb921167e257e120ac0a16b20339ec01121194ff9d394996d", url = "https://github.com/typst/typst/releases/download/v0.14.2/typst-x86_64-unknown-linux-musl.tar.xz"}
"platforms.macos-arm64" = { checksum = "sha256:470aa49a2298d20b65c119a10e4ff8808550453e0cb4d85625b89caf0cedf048", url = "https://github.com/typst/typst/releases/download/v0.14.2/typst-aarch64-apple-darwin.tar.xz"}
"platforms.macos-x64" = { checksum = "sha256:4e91d8e1e33ab164f949c5762e01ee3faa585c8615a2a6bd5e3677fa8506b249", url = "https://github.com/typst/typst/releases/download/v0.14.2/typst-x86_64-apple-darwin.tar.xz"}
"platforms.windows-x64" = { checksum = "sha256:51353994ac83218c3497052e89b2c432c53b9d4439cdc1b361e2ea4798ebfc13", url = "https://github.com/typst/typst/releases/download/v0.14.2/typst-x86_64-pc-windows-msvc.zip"}
[[tools.typstyle]]
version = "0.14.4"
backend = "aqua:Enter-tainer/typstyle"
"platforms.linux-arm64" = { checksum = "sha256:b8c220c0d940d7690fb15ef10415de35fdc115465a7534d8102459c68aeec74d", url = "https://github.com/typstyle-rs/typstyle/releases/download/v0.14.4/typstyle-aarch64-unknown-linux-gnu"}
"platforms.linux-x64" = { checksum = "sha256:48d9a9a3885855f1b4f6c2b8ea9739623bc458b99c015b77c4f50a1f342ea091", url = "https://github.com/typstyle-rs/typstyle/releases/download/v0.14.4/typstyle-x86_64-unknown-linux-musl"}
"platforms.macos-arm64" = { checksum = "sha256:dda3a162f2457a40570c3552cc9c01004138b33f2f921112b117d72f48bd6d56", url = "https://github.com/typstyle-rs/typstyle/releases/download/v0.14.4/typstyle-aarch64-apple-darwin"}
"platforms.macos-x64" = { checksum = "sha256:2343f7a4801d9bbda5d7441e8fea95f98b590d6aa3083da5c4e4ca3de885350f", url = "https://github.com/typstyle-rs/typstyle/releases/download/v0.14.4/typstyle-x86_64-apple-darwin"}
"platforms.windows-x64" = { checksum = "sha256:b16aebaed0aa296ef3e055d611f1e917f5849d3eb0f953bbcaa6f09f0503fb5b", url = "https://github.com/typstyle-rs/typstyle/releases/download/v0.14.4/typstyle-x86_64-pc-windows-msvc.exe"}
[[tools.yq]]
version = "4.52.2"
backend = "aqua:mikefarah/yq"
"platforms.linux-arm64" = { checksum = "sha256:c82856ac30da522f50dcdd4f53065487b5a2927e9b87ff637956900986f1f7c2", url = "https://github.com/mikefarah/yq/releases/download/v4.52.2/yq_linux_arm64"}
"platforms.linux-x64" = { checksum = "sha256:a74bd266990339e0c48a2103534aef692abf99f19390d12c2b0ce6830385c459", url = "https://github.com/mikefarah/yq/releases/download/v4.52.2/yq_linux_amd64"}
"platforms.macos-arm64" = { checksum = "sha256:34613ea97c4c77e1894a8978dbf72588d187a69a6292c10dab396c767a1ecde7", url = "https://github.com/mikefarah/yq/releases/download/v4.52.2/yq_darwin_arm64"}
"platforms.macos-x64" = { checksum = "sha256:54a63555210e73abed09108097072e28bf82a6bb20439a72b55509c4dd42378d", url = "https://github.com/mikefarah/yq/releases/download/v4.52.2/yq_darwin_amd64"}
"platforms.windows-x64" = { checksum = "sha256:2b6cd8974004fa0511f6b6b359d2698214fadeb4599f0b00e8d85ae62b3922d4", url = "https://github.com/mikefarah/yq/releases/download/v4.52.2/yq_windows_amd64.exe"}
+265
View File
@@ -0,0 +1,265 @@
# unexplrd's example setup with mise
This example has the following structure:
```
.
├── .mise/
│ ├── config.toml
│ └── mise.lock
├── vendor/
│ └── typst-packages/
│ └── ...
└── src/
├── assets/
│ ├── foo.csv
│ ├── bar.c
│ └── ...
├── figures/
│ ├── clojure-logo.png
│ ├── error-log.jpg
│ └── ...
├── doc.toml
├── main.typ
└── utils.typ
```
## Advantages
- Minimal
- All you need is [`mise`](https://mise.jdx.dev/) and `git`
- Declarative
- Sets up the environment and dependencies automatically
- Every work config is separate to avoid breaking changes in Typst and `nure` package
- Customizable
- Add your own mise tasks, change existing tasks, it's just a `.toml` file
## Project structure
All work contents are stored in `src/` to avoid clutter.
### Files
- `src/doc.toml`: work-specific settings, could be auto-filled, useful for scripts, see (TODO: add a demo nushell script)
- `src/utils.typ`: utilities and functions used across the project
- `src/main.typ`: main entry file
### Directories
- `src/chapters/`: for breaking up the project into multiple files, if necessary
- `src/figures/`: for images and pictures
- `src/assets/`: other non-image, non-typst files
## Mise
### Tasks
- `compile`: Execute `typst compile` on `src/main.typ`
- `watch`: Execute `typst watch` on `src/main.typ`
- `rename`: Copy output `.pdf` file with an auto-generated name from metadata in `doc.toml`
- `fetch-nure-package`: clone package git repository to a directory specified in `[vars]`
- `update-package-rev`: updates `vars.nure_package_rev` with the last commit id from the specified branch (`vars.nure_package_ref`)
## Examples
### Templating
I have a following per-semester directory structure:
```
/home/user/Documents/nure/
└── .config/
├── defaults.yaml
├── subjects.yaml
├── mise/
│ ├── config.toml
│ └── mise.lock
└── template/
├── .mise
│ ├── config.toml
│ └── mise.lock
└── src
├── appendices.typ
├── assets/
├── figures/
├── main.typ
└── utils.typ
```
<details><summary>Contents of <code>.config/default.yaml</code></summary><p>
```yaml
university: ХНУРЕ
default_author:
name: Косач Л. П.
edu-program: ПЗПІ
group: 23-2
gender: f
variant: 8
full-name-gen: Косач Лариси Петрівни
course: 2
semester: 4
work_types:
lb: ЛБ
pz: ПЗ
kr: КР
```
</p></details>
<details><summary>Contents of <code>.config/subjects.yaml</code></summary><p>
```yaml
smp:
name: СМП
full_name: Скриптові мови програмування
mentors:
- name: Шевченко Т. Г.
degree: Доцент кафедри ПІ
gender: m
- name: Франко І. Я.
degree: Асистент кафедри ПІ
gender: m
iks:
name: ІКС
full_name: Інформаційно-комунікаційні системи
mentors:
- name: Мартинчук О. О.
degree: Доцент кафедри ІКІ
gender: m
# etc...
```
</p></details>
<details><summary>Contents of <code>.config/mise/config.toml</code></summary><p>
```toml
[settings]
quiet = true
env_shell_expand = true
lockfile = true
[tools]
"aqua:nushell/nushell" = "latest"
[tasks.work]
usage = '''
arg "<subject>" help="Target environment"
arg "<type>" help="Target environment"
arg "<number>" help="Target environment"
'''
run = '''
#!/usr/bin/env nu
let template_dir = ".config/template"
let config_dir = ".config"
# let out_dir = $"(pwd)"
let out_dir = $"(pwd)/works"
def not_main [subject: string, shortcode: string, number?: int] {
let subject_lower = ($subject | str downcase)
let shortcode_lower = ($shortcode | str downcase)
validate-input $subject $shortcode $number
let temp_dir = $"/tmp/nure-work-(random chars -l 8)"
let final_dir = $"($out_dir)/($subject_lower)/($shortcode_lower)($number)"
let subject_dir = $"($out_dir)/($subject_lower)"
# TODO?: replace with git clone
cp -r ($config_dir)/template $temp_dir
generate-doc-toml $shortcode_lower $subject_lower $temp_dir $number
check-mkdir $out_dir
if ($final_dir | path exists) {
error make -u {msg: $"Directory ($final_dir) already exists"}
}
check-mkdir $subject_dir; cp -r $temp_dir $final_dir; rm -rf $temp_dir
print $"=> Created new work: ($final_dir)"
}
def check-mkdir [path: string] { if not ($path | path exists) { mkdir $path } }
def validate-input [subject: string, shortcode: string, number?: int] {
if not ($config_dir | path exists) {
error make -u {msg: $"($config_dir) not found"}
}
let defaults_yaml = $"($config_dir)/defaults.yaml"
let subjects_yaml = $"($config_dir)/subjects.yaml"
if not ($defaults_yaml | path exists) {
error make -u {msg: $"defaults.yaml not found in ($config_dir)"}
}
if not ($subjects_yaml | path exists) {
error make -u {msg: $"subjects.yaml not found in ($config_dir)"}
}
if ($number != nothing and $number < 1 ) {
error make -u {msg: "Work number should be greater than 0"}
}
let subject_lower = ($subject | str downcase)
let subjects = (open $subjects_yaml | columns)
if $subject_lower not-in $subjects {
error make -u {
msg: $"Subject '($subject)' not found in configuration"
help: $"Available subjects: ($subjects | str join ', ')"
}
}
}
def generate-doc-toml [shortcode: string, subject: string, target_dir: string, number?: int] {
let defaults = (open $"($config_dir)/defaults.yaml")
let subjects = (open $"($config_dir)/subjects.yaml")
let subject_data = ($subjects | get $subject)
let work_type = ($defaults | get work_types | try { get $shortcode } catch { $shortcode })
{
university: ($defaults | get university)
subject: ($subject_data | get name), type: $work_type, number: $number
mentors: ($subject_data | get mentors), authors: [($defaults | get default_author)]
} | compact --empty | to toml | save $"($target_dir)/src/doc.toml" --force
}
not_main {{usage.subject}} {{usage.type}} {{usage.number}}
'''
```
</p></details>
`template/` is this empty template without `doc.toml`
Nushell script does two things:
- Creates a new work from a template: `works/smp/lb1`
- Auto-fills `doc.toml` with data from `defaults.yaml` and mentor data from `subjects.yaml`
### Patching the package
A patch to add new subjects:
```diff
diff --git a/src/config/universities.yaml b/src/config/universities.yaml
index 8855a07..1aefc96 100644
--- a/src/config/universities.yaml
+++ b/src/config/universities.yaml
@@ -60,7 +60,6 @@
ПЕСЕ: Психологія екстремальних стосунків та ефективної адаптації
ПНП: Програмування на платформі .NЕТ
ПП: Проектний практикум
- ПРОГ: Програмування
ПарП: Параллельне програмування
СА: Системний аналіз
СМП: Скриптові мови програмування
@@ -76,3 +75,10 @@
ФІЛ: Філософія
ФВС: Фізичне виховання та спорт
ХТ: Хмарні технології
+ АКС: Архітектура комп'ютерних систем
+ ІКС: Інформаційно-комунікаційні системи
+ МБ: Мережна безпека
+ МКр: Мережна криміналістика
+ ОКЗІ: Основи криптографічного захисту інформації
+ Прог: Програмування
+ ТІК: Теорія інформації та кодування
```
To apply it, add the following command to the end of `fetch_nure_package` task:
```sh
git -C {{vars.nure_package_path}} apply --check --apply --quiet $MISE_PROJECT_ROOT/custom-subjects.patch || exit 0
```
+19
View File
@@ -0,0 +1,19 @@
university = "ХНУРЕ"
subject = "АКС"
type = "ЛБ"
number = 1
[[mentors]]
name = ""
degree = ""
gender = ""
[[authors]]
name = ""
edu-program = "КУІБ"
group = "24-1"
gender = "m"
variant = 6
full-name-gen = ""
course = 2
semester = 4
+28
View File
@@ -0,0 +1,28 @@
#import "@vendor/nure:0.1.1": *
#import "utils.typ": *
#import style: spacing
// Apply custom rule from utils.typ
// #show: correctly-indent-list-and-enum-items
// #show: style.dstu
#show: pz-lb.with(..toml("doc.toml"), title: "") // set title to none if empty
/// Useful snippets
/// Import a .csv table
// #figure(
// caption: [],
// table(
// columns: 4,
// table.header([], [], [], []),
// ..csv("assets/table.csv").flatten(),
// ),
// )
/// Appendices
// #style.appendices(include "chapters/appendices.typ")
// or
// #show: style.appendices
// = ...
+112
View File
@@ -0,0 +1,112 @@
#import "@vendor/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) ]
}
/// takes in a string of code, e.g. #code(read("foo.c"))
#let code(content) = raw(block: true, theme: none, content)
/// read path as bytes
#let b(path) = bytes(read(path, encoding: none))
/// include chapters by file names from /chapters
#let chapters(ch) = (
array(ch).map(chapter => include str(chapter) + ".typ").join()
)
/// https://forum.typst.app/t/how-to-make-bullet-list-item-bodies-flow-like-paragraphs/3756/3?u=andrew
/// Spacing doesn't work the same way as native solution if par leading and
/// spacing are different.
#let correctly-indent-list-and-enum-items(doc) = {
let first-line-indent() = if type(par.first-line-indent) == dictionary {
par.first-line-indent.amount
} else {
par.first-line-indent
}
show list: li => {
for (i, it) in li.children.enumerate() {
let nesting = state("list-nesting", 0)
let indent = context h((nesting.get() + 1) * li.indent)
let get-nesting() = calc.div-euclid(nesting.get(), 10)
let marker = context {
let n = get-nesting()
if type(li.marker) == array {
li.marker.at(calc.rem-euclid(n, li.marker.len()))
} else if type(li.marker) == content {
li.marker
} else {
li.marker(n)
}
}
let parents = state("enum-parents", ()) // Support enum nesting.
let body = {
parents.update(arr => arr + (-1,))
nesting.update(x => x + 10)
it.body + parbreak()
nesting.update(x => x - 10)
parents.update(arr => arr.slice(0, -1))
}
let content = {
marker
h(li.body-indent)
body
}
context pad(left: int(nesting.get() != 0) * li.indent, content)
}
}
show enum: en => {
let start = if en.start == auto {
if en.children.first().has("number") {
if en.reversed { en.children.first().number } else { 1 }
} else {
if en.reversed { en.children.len() } else { 1 }
}
} else {
en.start
}
let number = start
for (i, it) in en.children.enumerate() {
number = if it.number != auto { it.number } else { number }
if en.reversed { number = start - i }
let parents = state("enum-parents", ())
let get-parents() = parents.get().filter(x => x >= 0)
let indent = context h((get-parents().len() + 1) * en.indent)
let num = if en.full {
context numbering(en.numbering, ..get-parents(), number)
} else {
numbering(en.numbering, number)
}
let max-num = if en.full {
context numbering(en.numbering, ..get-parents(), en.children.len())
} else {
numbering(en.numbering, en.children.len())
}
num = context box(
width: measure(max-num).width,
align(right, text(overhang: false, num)),
)
let list-nesting = state("list-nesting", 0) // Support list nesting.
let body = {
parents.update(arr => arr + (number,))
list-nesting.update(x => x + 1)
it.body + parbreak()
list-nesting.update(x => x - 1)
parents.update(arr => arr.slice(0, -1))
}
if not en.reversed { number += 1 }
let content = {
num
h(en.body-indent)
body
}
context pad(left: int(parents.get().len() != 0) * en.indent, content)
}
}
doc
}
+3 -3
View File
@@ -1,11 +1,11 @@
[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"
[template]
path = "template"
path = "template/default"
entrypoint = "lab.typ"