66 Commits

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

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

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

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

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

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

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

Signed-off-by: unexplrd <unexplrd@linerds.us>
2025-03-20 15:42:23 +02:00
0x1D8 816d07f745 fix!: change "СПМ" to "СМП"
feat: add more subjects, sort them
2025-03-20 14:41:15 +02:00
unexplrd 7799435039 Merge pull request 'version 0.1.0' (#12) from unexplrd/typst_nure_template:main into 0.1.0
Reviewed-on: #12
2025-03-18 12:28:54 +02:00
33 changed files with 2662 additions and 875 deletions
+110 -25
View File
@@ -1,84 +1,169 @@
# Typst Template for NURE Works
<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
#### `pz-lb-template` - For Laboratory and Practical Works
#### `pz-lb` - For Practice and Laboratory Works
This template:
- Sets up document styles;
- Formats the title page according to NURE/DSTU guidelines.
#### `cw-template` - For Course Works
#### `coursework` - For Course Works
This template:
- Sets up document styles;
- Formats the title, task, calendar plan, and abstract pages;
- Typesets the bibliography, outline, and appendices according to standard requirements.
- Typesets the bibliography according to ДСТУ 3008:2015 using custom CSL style;
- Typesets the outline and appendices according to standard requirements.
#### `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/:
```bash
git clone -b 0.1.0 https://gitea.linerds.us/pencelheimer/typst_nure_template.git ~/.local/share/typst/packages/local/nure/0.1.0
git clone -b 0.1.1 https://gitea.linerds.us/pencelheimer/typst_nure_template.git ~/.local/share/typst/packages/local/nure/0.1.1
```
2. Init your project with Typst:
```bash
typst init @local/nure:0.1.0 project-name
typst init @local/nure:0.1.1 project-name
```
### As a standalone file
Copy `lib.typ` to your project's root directory.
Copy `src/` to your project's root directory, optionally renaming `src/` to `lib/` (then import `src/lib.typ` or `lib/lib.typ` accordingly).
### In your project
```typst
// Import the template either from a local package...
#import "@local/nure:0.1.0": *
#import "@local/nure:0.1.1": *
// ...or by importing a lib.typ directly
// #import "/lib.typ": *
// #import "/lib/lib.typ": *
// Setup the document
#show: pz-lb-template.with(
title: "Some title",
// etc: "and so on",
// ...
// NOTE: all template arguments use kebab-case.
// 1. Setup the document
// by setting values directly...
#show: pz-lb.with(
title: "Some title",
// ...
)
// ...or using a yaml/toml file
#show: pz-lb.with(..toml("/doc.toml"))
// this template automatically inserts a `=title`
// Write your content...
// Write your content
#v(-spacing) // remove spacing between headings
== Purpose
Some text
// ...or include your modules
#include "src/intro.typ"
#include "src/chapter1.typ"
#include "src/chapter2.typ"
#include "chapters/intro.typ"
#include "chapters/chapter1.typ"
#include "chapters/chapter2.typ"
// NOTE: if you want to use variables or utils provided by the package,
// you have to import the package or a lib.typ inside a module.
// you have to import the package or a lib.typ inside a module (e.g. #import "@local/nure:0.1.1": utils).
// If you ever need appendices in pz-lb template use the show rule
// WARNING: when using coursework template use its own argument,
// so it can put bibliography before appendices
#show: style.appendices
// For coursework appendices, pass them via `appendices:` argument instead.
= Quote
#link("https://youtu.be/bJQj1uKtnus")[
The art isn't the art, the art is never the art,
the art is the thing that happens inside you when you make it and the feeling in the heart of the beholder.
]
```
And a TOML file would look like this:
```toml
# university = "ХНУРЕ" # "ХНУРЕ" is the default
# edu-program = "ПЗПІ" # can be null, sourced from authors.first() by default
subject = "СМП"
doctype = "ЛБ"
worknumber = 2
title = "Потiк керування та алгоритмічні структури Bash"
[[mentors]]
name = "Шевченко Т. Г."
degree = "Доцент кафедри ПІ"
gender = "m"
[[mentors]]
name = "Франко І. Я."
degree = "Асистент кафедри ПІ"
gender = "m"
[[authors]]
name = "Косач Л. П."
edu-program = "ПЗПІ"
group = "23-2"
gender = "f"
variant = 8
# For coursework
full-name-gen = "Косач Лариси Петрівни"
course = 2
semester = 4
```
### Notes:
1. Use `#v(-spacing)` to remove vertical spacing between titles (this cannot be automatically handled by the template). Variable `spacing` used here is imported from the template.
2. When importing `@local/nure:0.1.1` and specifying file paths in functions handled by the package, the path will be relative to the package root, e.g. setting `#show: coursework.with(bib-path: "bibl.yml")` will evaluate to `~/.local/share/typst/packages/local/nure/0.1.1/bibl.yml`. The same applies to `utils.img` unless you copy `template/utils.typ` into your project root and import from there.
### Bibliography Format
The template uses a custom CSL (Citation Style Language) file located at `src/csl/dstu-3008-2015.csl` to format bibliography entries.
Supported bibliography entry types in `bibl.yml`:
- **Book**: Books with author, title, publisher, year, and page count
- **Web**: Web resources with title, author/organization, URL, and access date
**Warning:** Other types were added by Kimi K2.5 without any additional checks for compliance.
Example `bibl.yml`:
```yaml
mysql:
type: Book
title: MySQL Language Reference
author: Ab M.
publisher: MySQL Press
date: 2004
page-total: 600
go:
type: Web
title: The Go Programming Language
author: The Go Programming Language
url:
value: https://go.dev/
date: 2024-12-10
```
### Example Project Structure
```
project/
├── doc.toml -- for things that don't change across works, i.e. author and mentor metadata
├── main.typ -- for boilerplate code and importing everything
├── config/
│ ├── doc.yaml -- for things that don't change across works, i.e. author and mentor metadata
│ ├── universities.yaml -- for user-specific configuration, i.e. education programs and disciplines
│ └── ...
├── src/
├── utils.typ -- for helper functions
├── chapters/
│ ├── intro.typ
│ ├── chapter1.typ
│ ├── chapter2.typ
Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

-24
View File
@@ -1,24 +0,0 @@
ХНУРЕ:
name: "Харківський національний університет радіоелектроніки"
edu_programs:
ПЗПІ:
name_long: "Інженерія програмного забезпечення"
department_gen: "Програмної інженерії"
code: 121 # TODO = ПЗПІ is "F2" now
КУІБ:
name_long: "Управління інформаційною безпекою"
department_gen: "Інфокомунікацій"
code: 125
subjects:
БД: "Бази даних"
БЖД: "Безпека життєдіяльності"
ОІМ: "Основи IP-мереж"
"ОПНJ": "Основи програмування на Java"
ОС: "Операційні системи"
ОТК: "Основи теорії кіл"
ПП: "Проектний практикум"
ПРОГ: "Програмування"
СПМ: "Скриптові мови програмування"
УФМ: "Українське фахове мовлення"
Ф: "Філософія"
ФІЗ: "Фізика"
-746
View File
@@ -1,746 +0,0 @@
// Academic aliases {{{1
#let universities = yaml("config/universities.yaml")
// Template formatting functions {{{1
/// numberless heading
#let nheading(title) = heading(depth: 1, numbering: none, title)
/// fill horizontal space with a box and not an empty space
#let hfill(width) = box(
width: width,
repeat(""),
) // NOTE: This is a HAIR SPACE (U+200A), not a regular space
/// make underlined cell with filled value
#let uline(align: center, content) = underline[
#if align != left { hfill(1fr) }
#content
#if align != right { hfill(1fr) }
]
/// bold text
#let bold(content) = text(weight: "bold")[#content]
/// month name from its number
#let month_gen(month) = (
"січня",
"лютого",
"березня",
"квітня",
"травня",
"червня",
"липня",
"серпня",
"вересня",
"жовтня",
"листопада",
"грудня",
).at(month - 1)
// Helper functions {{{1
/// captioned image with label derived from path:
/// - "image.png" = @image
/// - "img/image.png" = @image
/// - "img/foo/image.png" = @foo_image
/// - "img/foo/foo_image.png" = @foo_image
/// the caption will be modified based on a conditional positional value:
/// - `none`: no change
/// - some value: "`caption` (за даними `value`)"
/// - no value: "`caption` (рисунок виконано самостійно)"
/// additional named arguments will be passed to original `image` function
#let img(path, caption, ..sink) = {
let parts = path.split(".").first().split("/")
let label_string = if (
parts.len() <= 2 or parts.at(-1).starts-with(parts.at(-2))
) {
// ("image",), (_, "image") and (.., "img", "img_image")
parts.last()
} else {
// (.., "img", "image") = "img_image"
parts.at(-2) + "_" + parts.at(-1)
}.replace(" ", "_")
let caption = if sink.pos().len() == 0 {
caption + " (рисунок виконано самостійно)"
} else if sink.pos().first() == none {
caption
} else {
[#caption (за даними #sink.pos().first())]
}
[#figure(
image(path, ..sink.named()),
caption: caption,
) #label(label_string)]
}
// Styling {{{1
/// NOTE: may be wrong
#let ua_alpha_numbering = "абвгдежиклмнпрстуфхцшщюя".split("") // 0 = "", 1 = "а"
// general outlook {{{2
// spacing between lines
#let spacing = 0.95em
#let style(it) = {
set page(
paper: "a4",
margin: (top: 20mm, right: 10mm, bottom: 20mm, left: 25mm),
number-align: top + right,
numbering: (..numbers) => {
if numbers.pos().at(0) != 1 {
numbering("1", numbers.pos().at(0))
}
},
)
set text(
font: ("Times New Roman", "Liberation Serif"),
size: 14pt,
hyphenate: false,
lang: "uk",
)
set par(justify: true, first-line-indent: (amount: 1.25cm, all: true))
set underline(evade: false)
// set 1.5 line spacing
set block(spacing: spacing)
set par(spacing: spacing)
set par(leading: spacing)
// enums and lists {{{2
set enum(
numbering: i => { ua_alpha_numbering.at(i) + ")" },
indent: 1.25cm,
body-indent: 0.5cm,
)
show enum: it => {
set enum(indent: 0em, numbering: "1)")
it
}
set list(indent: 1.35cm, body-indent: 0.5cm, marker: [--])
// figures {{{2
show figure: it => {
v(spacing * 2, weak: true)
it
v(spacing * 2, weak: true)
}
set figure.caption(separator: [ -- ])
show figure.where(kind: table): set figure.caption(position: top)
show figure.caption.where(kind: table): set align(left)
// figure numbering
show heading.where(level: 1): it => {
counter(math.equation).update(0)
counter(figure.where(kind: image)).update(0)
counter(figure.where(kind: table)).update(0)
counter(figure.where(kind: raw)).update(0)
it
}
set math.equation(
numbering: (..num) => numbering(
"(1.1)",
counter(heading).get().at(0),
num.pos().first(),
),
)
set figure(
numbering: (..num) => numbering(
"1.1",
counter(heading).get().at(0),
num.pos().first(),
),
)
// appearance of references to images and tables {{{2
set ref(
supplement: it => {
if it == none or not it.has("kind") {
it
} else if it.kind == image {
"див. рис."
} else if it.kind == table {
"див. таблицю"
} else {
it
}
},
)
show ref: it => {
let el = it.element
if el == none or not el.has("kind") {
return it
}
if el.kind != image and el.kind != table {
return it
}
[(#it)]
}
// headings {{{2
set heading(numbering: "1.1")
show heading.where(level: 1): it => {
set align(center)
set text(size: 14pt, weight: "semibold")
pagebreak(weak: true)
upper(it)
v(spacing * 2, weak: true)
}
show heading.where(level: 2): it => {
set text(size: 14pt, weight: "regular")
v(spacing * 2, weak: true)
block(width: 100%, spacing: 0em)[
#h(1.25cm)
#counter(heading).display(it.numbering)
#it.body
]
v(spacing * 2, weak: true)
}
show heading.where(level: 3): it => {
set text(size: 14pt, weight: "regular")
v(spacing * 2, weak: true)
block(width: 100%, spacing: 0em)[
#h(1.25cm)
#counter(heading).display(it.numbering)
#it.body
]
v(spacing * 2, weak: true)
}
// listings {{{2
show raw: it => {
let new_spacing = 0.5em
set block(spacing: new_spacing)
set par(
spacing: new_spacing,
leading: new_spacing,
)
set text(
size: 11pt,
font: ("Iosevka NFM", "Courier New"),
weight: "semibold",
)
v(spacing * 2.5, weak: true)
pad(it, left: 1.25cm)
v(spacing * 2.5, weak: true)
}
it
}
// Coursework template {{{1
/// DSTU 3008:2015 Template for NURE
/// -> content
/// - doc (content): Content to apply the template to.
/// - title (str): Title of the document.
/// - subject (str): Subject short name.
/// - authors ((name: str, full_name_gen: str, variant: int, course: int, semester: int, group: str, gender: str),): List of authors.
/// - mentors ((name: str, degree: str),): List of mentors.
/// - edu_program (str): Education program shorthand.
/// - task_list (done_date: datetime, initial_date: datetime, source: (content | str), content: (content | str), graphics: (content | str)): Task list object.
/// - calendar_plan ( plan_table: (content | str), approval_date: datetime): Calendar plan object.
/// - abstract (keywords: (str, ), text: (content | str)): Abstract object.
/// - bib_path path: Path to the bibliography yaml file.
/// - appendices (content): Content with appendices.
#let cw-template(
doc,
title: none,
subject: none,
university: "ХНУРЕ",
author: (),
mentors: (),
edu_program: none,
task_list: (),
calendar_plan: (),
abstract: (),
bib_path: none,
appendices: (),
) = {
set document(title: title, author: author.name)
show: style
let bib-count = state("citation-counter", ())
show cite: it => {
it
bib-count.update(((..c)) => (..c, it.key))
}
show bibliography: it => {
set text(size: 0pt)
it
}
let head_mentor = mentors.at(0)
let uni = universities.at(university)
let edu_prog = uni.edu_programs.at(edu_program)
// page 1 {{{2
[
#set align(center)
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ\
#upper(uni.name)
\
Кафедра #edu_prog.department_gen
\
ПОЯСНЮВАЛЬНА ЗАПИСКА\
ДО КУРСОВОЇ РОБОТИ\
з дисципліни: "#uni.subjects.at(subject, default: "NONE")"\
Тема роботи: "#title"
\ \ \
#columns(2, gutter: 4cm)[
#set align(left)
#if author.gender == "m" { [Виконав\ ] } else { [Виконала\ ] } ст. гр. #edu_program\-#author.group
\
Керівник:\
#head_mentor.degree
\
Робота захищена на оцінку
\
Комісія:\
#for mentor in mentors {
[#mentor.degree\
]
}
#colbreak()
#set align(left)
\
#author.name
\ \
#head_mentor.name
\
#underline(" " * 35)
\ \
#for mentor in mentors {
[#mentor.name\
]
}
]
#v(1fr)
Харків -- #task_list.done_date.display("[year]")
#pagebreak()
]
// page 2 {{{2
{
uline[#uni.name]
linebreak()
linebreak()
grid(
columns: (100pt, 1fr),
bold[
Кафедра
Дисципліна
Спеціальність
],
{
uline(align: left, edu_prog.department_gen)
linebreak()
uline(align: left, uni.subjects.at(subject))
linebreak()
uline(align: left, [#edu_prog.code #edu_prog.name_long])
},
)
grid(
columns: (1fr, 1fr, 1fr),
gutter: 0.3fr,
[#bold[Курс] #uline(author.course)],
[#bold[Група] #uline([#edu_program\-#author.group])],
[#bold[Семестр] #uline(author.semester)],
)
linebreak()
linebreak()
linebreak()
align(center, bold[ЗАВДАННЯ \ на курсову роботу студента])
linebreak()
uline(align: left)[_#author.full_name_gen _]
linebreak()
linebreak()
bold[\1. Тема роботи:]
uline[#title.]
linebreak()
{
bold[\2. Строк здачі закінченої роботи:]
uline(task_list.done_date.display("[day].[month].[year]"))
hfill(10fr)
}
linebreak()
bold[\3. Вихідні дані для роботи:]
uline(task_list.source)
linebreak()
bold[\4. Зміст розрахунково-пояснювальної записки:]
uline(task_list.content)
linebreak()
bold[\5. Перелік графічного матеріалу:]
uline(task_list.graphics)
linebreak()
{
bold[\6. Дата видачі завдання:]
uline(task_list.initial_date.display("[day].[month].[year]"))
hfill(10fr)
}
pagebreak()
}
// page 3 {{{2
{
align(center, bold[КАЛЕНДАРНИЙ ПЛАН])
set par(first-line-indent: 0pt)
linebreak()
calendar_plan.plan_table
linebreak()
grid(
columns: (5fr, 5fr),
grid(
columns: (1fr, 2fr, 1fr),
gutter: 0.2fr,
[
Студент \
Керівник \
#align(center)["#underline[#calendar_plan.approval_date.day()]"]
],
[
#uline(align: center, []) \
#uline(align: center, []) \
#uline(align: center, month_gen(calendar_plan.approval_date.month()))
],
[
\ \
#underline[#calendar_plan.approval_date.year()] р.
],
),
[
#author.name, \
#head_mentor.degree
#head_mentor.name.
],
)
pagebreak()
}
// page 4 {{{2
[
#align(center, bold[РЕФЕРАТ]) \
#context [
#let pages = counter(page).final().at(0)
#let images = query(figure.where(kind: image)).len()
#let tables = query(figure.where(kind: table)).len()
#let bibs = bib-count.final().dedup().len()
/* TODO: why this stopped working?
#let tables = counter(figure.where(kind: table)).final().at(0)
#let images = counter(figure.where(kind: image)).final().at(0)*/
#let counters = ()
#if pages != 0 { counters.push[#pages с.] }
#if tables != 0 { counters.push[#tables табл.] }
#if images != 0 { counters.push[#images рис.] }
#if bibs != 0 { counters.push[#bibs джерел] }
Пояснювальна записка до курсової роботи: #counters.join(", ").
]
\
#{
let keywords = abstract.keywords.map(upper)
let is_cyrillic = word => word.split("").any(char => ("А" <= char and char <= "я"))
let n = keywords.len()
for i in range(n) {
for j in range(0, n - i - 1) {
if (
(
not is_cyrillic(keywords.at(j)) and is_cyrillic(keywords.at(j + 1))
)
or (
is_cyrillic(keywords.at(j)) == is_cyrillic(keywords.at(j + 1)) and keywords.at(j) > keywords.at(j + 1)
)
) {
(keywords.at(j), keywords.at(j + 1)) = (
keywords.at(j + 1),
keywords.at(j),
)
}
}
}
keywords.join(", ")
}
\
#abstract.text
]
// page 5 {{{2
outline(
title: [
ЗМІСТ
#v(spacing * 2, weak: true)
],
depth: 2,
indent: auto,
)
doc
// bibliography {{{2
{
heading(depth: 1, numbering: none)[Перелік джерел посилання]
bibliography(
bib_path,
style: "ieee",
full: true,
title: none,
)
let bib_data = yaml(bib_path)
let format-entry(citation) = {
if (citation.type == "Web") {
let date_array = citation.url.date.split("-")
let date = datetime(
year: int(date_array.at(0)),
month: int(date_array.at(1)),
day: int(date_array.at(2)),
)
[
#citation.title.
#citation.author.
URL: #citation.url.value (дата звернення: #date.display("[day].[month].[year]")).
]
} else if citation.type == "Book" [
#citation.author
#citation.title.
#citation.publisher,
#citation.date.
#citation.page-total c.
] else [
UNSUPPORTED BIBLIOGRAPHY ENTRY TYPE, PLEASE OPEN AN ISSUE
]
}
show enum.item: it => {
set par(first-line-indent: 0pt)
box(width: 1.25cm)
box(width: 1em + 0.5cm)[#it.number.]
it.body
linebreak()
}
context {
for (i, citation) in query(ref.where(element: none)).map(r => str(r.target)).dedup().enumerate() {
enum.item(
i + 1,
format-entry(bib_data.at(citation)),
)
}
}
}
// appendices {{{2
{
counter(heading).update(0)
set heading(
numbering: (i, ..nums) => {
let char = upper(ua_alpha_numbering.at(i))
if nums.pos().len() == 0 { char } else {
char + "." + nums.pos().map(str).join(".")
}
},
)
show heading.where(level: 1): it => {
set align(center)
set text(size: 14pt, weight: "regular")
pagebreak(weak: true)
bold[ДОДАТОК #counter(heading).display(it.numbering)]
linebreak()
it.body
v(spacing * 2, weak: true)
}
show heading.where(level: 2): it => {
set text(size: 14pt, weight: "regular")
v(spacing * 2, weak: true)
block(width: 100%, spacing: 0em)[
#h(1.25cm)
#counter(heading).display(it.numbering)
#it.body
]
v(spacing * 2, weak: true)
}
appendices
}
}
// Laboratory work template {{{1
/// DSTU 3008:2015 Template for NURE
/// -> content
/// - doc (content): Content to apply the template to.
/// - doctype ("ЛБ" | "ПЗ"): Document type.
/// - edu_program (str): Education program shorthand.
/// - title (str): Title of the document.
/// - subject (str): Subject shorthand.
/// - authors ((name: str, full_name_gen: str, group: str, gender: str, variant: int or none),): List of authors.
/// - mentors ((name: str, degree: str, gender: str or none),): List of mentors.
/// - worknumber (int or none): Number of the work. Optional.
#let pz-lb-template(
doc,
doctype: none,
university: "ХНУРЕ",
edu_program: none,
title: none,
subject: none,
worknumber: none,
authors: (),
mentors: (),
) = {
set document(title: title, author: authors.at(0).name)
show: style
let uni = universities.at(university)
let edu_prog = uni.edu_programs.at(edu_program)
// page 1 {{{2
align(center)[
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ \
#upper(uni.name)
\ \
Кафедра #edu_prog.department_gen
\ \ \
Звіт \
з
#if doctype == "ЛБ" [лабораторної роботи] else [практичної роботи]
#if worknumber != none {
context counter(heading).update(worknumber - 1)
[№#worknumber]
}
з дисципліни: "#uni.subjects.at(subject, default: "UNKNOWN SUBJECT, PLEASE OPEN AN ISSUE")"
з теми: "#title"
\ \ \ \
#columns(2)[
#set align(left)
#set par(first-line-indent: 0pt)
#if authors.len() == 1 {
let author = authors.at(0)
if author.gender == "m" [Виконав:\ ] else [Виконала:\ ]
[
ст. гр. #edu_program\-#author.group\
#author.name\
]
if author.variant != none [Варіант: #author.variant]
} else [
Виконали:\
ст. гр. #edu_program\-#authors.at(0).group\
#for author in authors [#author.name\ ]
]
#colbreak()
#set align(right)
#if mentors.len() == 1 {
let mentor = mentors.at(0)
if mentor.gender == none [Перевірили:\ ] else if (
mentor.gender == "m"
) [Перевірив:\ ] else [Перевірилa:\ ]
[
#mentor.degree\
#mentor.name\
]
} else [
Перевірили:\
#for mentor in mentors {
[
#mentor.degree\
#mentor.name\
]
}
]
]
#v(1fr)
Харків -- #datetime.today().display("[year]")
]
pagebreak(weak: true)
heading(title)
doc
}
// vim:sts=2:sw=2:fdl=0:fdm=marker:cms=/*%s*/
+99
View File
@@ -0,0 +1,99 @@
ХНУРЕ:
name: Харківський національний університет радіоелектроніки
name-en: Kharkiv National University of Radioelectronics
edu-programs:
ПЗПІ:
name-long: Інженерія програмного забезпечення
department-gen: Програмної інженерії
code: 121 # TODO: change to F2?
КУІБ:
name-long: Управління інформаційною безпекою
department-gen: Інфокомунікаційної інженерії ім. В. В. Поповського
code: 125 # TODO: change to F5?
description: Кібербезпека та захист інформації
КНТ:
name-en: CST # computer sciences and technologies
name-long: Комп'ютерні науки та технології
department-gen: Системотехніки
department-en: ST
code: 122
subjects:
DMT: Decision making theory
NoSQL: NoSQL-системи
ODS: Основи Dаtа Sсіеnсе # NOTE: Eng O here
АВпЗ: Аналіз вимог до програмного забезпечення
АДан: Аналітика даних
АКС: Архітектура комп'ютерних систем
АКтаК: Архітектура комп'ютера та комп'ютерних мереж
АПЗ: Архітектура програмного забезпечення
АтаРК: Аналіз та рефакторинг коду
АТСД: Алгоритми та структури даних
БД: Бази даних
БЖД: Безпека життєдіяльності
В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ЕТ
ПП: Проектний практикум
Прог: Програмування # NOTE: was "ПРОГ" before
РNet: Робота з даними на платформі .Net
РХЗ: Розробка хмарних застосувань в AZURE
СА: Системний аналіз
СМП: Скриптові мови програмування
СОАПЗ: Сервіс-Орієнтована Архітектура Програмного Забезпечення
СРБД: Серверні рішення баз даних
СхТ: Схемотехніка
ТВО: Технології Високопродуктивних Обчислень
ТЗІ: Технології захисту інформації
ТІК: Теорія інформації та кодування
ТЙтаМ: Теорія ймовірностей та математична # 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": *
+358
View File
@@ -0,0 +1,358 @@
#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([Кафедра], 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,
),
),
([Кафедра], 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):\ ]
[ст. гр. #a.edu-program\-#a.group\ ]
[#a.name\ ]
if not is-empty(a.variant) [Варіант: #a.variant]
} else if authors.len() > 1 [
#gender-form("author"):\
#for a in authors [
#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) ]
}
-22
View File
@@ -1,22 +0,0 @@
title: Потiк керування та алгоритмічні структури Bash
subject: СПМ
doctype: ЛБ
worknumber: 2
mentors:
- name: Шевченко Т. Г.
degree: Доцент кафедри ПІ
gender: m
- name: Франко І. Я.
degree: Асистент кафедри ПІ
gender: m
edu_program: &EDU ПЗПІ
university: ХНУРЕ
authors:
- name: Косач Л. П.
full_name_gen: Косач Лариси Петрівни
course: 2
edu: *EDU
gender: f
group: 23-2
semester: 4
variant: 8
@@ -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,13 +1,19 @@
#import "@local/nure:0.1.0": *
#import "@local/nure:0.1.1": *
#import style: spacing
#let author = (
name: "Ситник Є. С.",
full_name_gen: "Ситника Єгора Сергійовича",
course: 2,
semester: 3,
variant: 13,
group: "23-2",
gender: "m",
#import "utils.typ": img
#let authors = (
(
name: "Ситник Є. С.",
full-name-gen: "Ситника Єгора Сергійовича",
edu-program: "ПЗПІ",
group: "23-2",
gender: "m",
course: 2,
semester: 3,
variant: 13,
),
)
#let mentors = (
@@ -16,34 +22,39 @@
(name: "Широкопетлєва М. С.", degree: "Ст. викл. каф. ПІ"),
)
#let task_list = (
done_date: datetime(year: 2024, month: 12, day: 27),
initial_date: datetime(year: 2024, month: 9, day: 15),
#let task-list = (
done-date: datetime(year: 2024, month: 12, day: 27),
initial-date: datetime(year: 2024, month: 9, day: 15),
source: "методичні вказівки до виконання курсової роботи, вимоги до інформаційної системи, предметна область, що пов’язана з управлінням класом та класним керівництвом.",
content: "вступ, аналіз предметної області; постановка задачі; проектування бази даних; опис програми; висновки; перелік джерел посилання.",
graphics: "загальна діаграма класів, ER-діаграма, UML-діаграми, DFD-діаграма, схема БД в 1НФ, 2НФ, 3НФ, копії екранів (“скриншоти”) прикладної програми, приклади звітів прикладної програми.",
)
#let calendar_plan = (
plan_table: table(
#let calendar-plan = (
plan-table: table(
columns: 4,
align: (center, left, center, center),
[Номер], [Назва етапів курсової роботи], [Строк виконання етапів роботи], [Примітки],
[1], [Аналіз предметної області], [15.09.24 24.09.24], [Виконано],
[2], [Концептуальне моделювання], [24.09.24-30.09.24], [~],
[2], [Постановка задачі], [28.09.24 2.10.24], [Виконано],
[3], [Побудова ER-діаграми та схеми БД], [2.10.24 18.10.24], [Виконано],
[4], [Оформлення розділів 1, 2 та 3.1, 3.2 пояснювальної записки], [10.10.24 - 18.10.24], [Виконано],
[5], [Перша контрольна точка з курсової роботи], [20.10.24], [Виконано],
[6], [Нормалізація бази даних], [20.10.24 - 15.11.24], [Виконано],
[7], [Створення програми], [20.10.24 20.11.24], [Виконано],
[8], [Тестування програми, наповнення бази даних], [20.11.24 - 5.12.24], [Виконано],
[9], [Друга контрольна точка з курсової роботи], [7.12.24], [Виконано],
[10], [Реалізація остаточної версії програми], [7.12.24-15.12.24], [Виконано],
[11], [Оформлення інших розділів пояснювальної записки], [1.11.24 25.12.24], [Виконано],
[12], [Третя контрольна точка з курсової роботи], [27.12.24], [Виконано],
),
approval_date: datetime(year: 2024, month: 12, day: 27),
approval-date: datetime(year: 2024, month: 12, day: 27),
)
#let abstract = (
@@ -87,7 +98,7 @@
#v(-spacing)
== Частина 1
#lorem(100)
== Частина2
== Частина 2
#lorem(200)
= Приклад звіту 2
@@ -97,16 +108,15 @@
#lorem(200)
]
#show: cw-template.with(
#show: coursework.with(
title: "Інформаційна система «Помічник класного керівника». Керування класом",
subject_short: "БД",
edu_program_short: "ПЗПІ",
author: author,
subject: "БД",
authors: authors,
mentors: mentors,
task_list: task_list,
calendar_plan: calendar_plan,
task-list: task-list,
calendar-plan: calendar-plan,
abstract: abstract,
bib_path: "bibl.yml",
bib-path: bytes(read("bibl.yml")), // NOTE: use `bytes("bibl.yml")` as typst looks in template dir when using just filename
appendices: appendices,
)
+78
View File
@@ -0,0 +1,78 @@
#import "@local/nure:0.1.1": *
#import "utils.typ": img
#import style: spacing
#show: pz-lb.with(
university: "ХНУРЕ",
subject: "СМП",
type: "ЛБ",
number: 2,
title: "Потiк керування та алгоритмічні структури Bash",
mentors: (
(name: "Шевченко Т. Г.", degree: "Доцент кафедри ПІ", gender: "m"),
(name: "Франко І. Я.", degree: "Асистент кафедри ПІ", gender: "m"),
),
authors: (
(
name: "Косач Л. П.",
full-name-gen: "Косач Лариси Петрівни",
edu-program: "КУІБ",
group: "23-2",
gender: "f",
course: 2,
semester: 4,
variant: 8,
),
),
)
#v(-spacing)
== Мета роботи
#lorem(100)
== Хід роботи
#v(-spacing)
=== Підготовка
#lorem(150)
=== Виконання дослідження
#lorem(300)
=== Підрахунок результатів
#lorem(250)
== Висновки
#lorem(100)
== Контрольні запитання
#lorem(100):
- #lorem(20);
- #lorem(30);
- #lorem(15);
- #lorem(25);
- #lorem(42);
- #lorem(27).
#show: style.appendices
= Quote
#link("https://youtu.be/bJQj1uKtnus")[
The art isn't the art, the art is never the art,
the art is the thing that happens inside you when you make it and the feeling in the heart of the beholder.
]
= Приклад звіту 1
#v(-spacing)
== Частина 1
#lorem(100)
== Частина 2
#lorem(200)
= Приклад звіту 2
#lorem(200)
= Приклад звіту 3
#lorem(200)
+7
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) ]
}
-31
View File
@@ -1,31 +0,0 @@
#import "@local/nure:0.1.0": *
#show: pz-lb-template.with(..yaml("config/doc.yaml"))
#v(-spacing)
== Мета роботи
#lorem(100)
== Хід роботи
#v(-spacing)
=== Підготовка
#lorem(150)
=== Виконання дослідження
#lorem(300)
=== Підрахунок результатів
#lorem(250)
== Висновки
#lorem(100)
== Контрольні запитання
#lorem(100):
- #lorem(20);
- #lorem(30);
- #lorem(15);
- #lorem(25);
- #lorem(42);
- #lorem(27).
+67
View File
@@ -0,0 +1,67 @@
[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 = "9abbc7c1e161523145c27a098add79886968547c"
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"