17 Commits

Author SHA1 Message Date
0x1D8 74d5651876 fix(style): make reference number use target location 2026-06-07 12:26:11 +03:00
pencelheimer 8833ef2383 feat(dstu-8302-2015): removed italics from more bibl types 2026-06-06 09:33:17 +03:00
ilyaoc d70e9df912 fix: handle dstu-table refs and counting 2026-06-03 13:59:58 +02:00
0x1D8 0162eb8c40 fix(helpers): use first char to detect cyrillic in is-cyr 2026-05-27 23:40:59 +03:00
unexplrd 1ff68050bc template(unexplrd-mise): bump package rev 2026-05-27 18:51:10 +03:00
ilyaoc d6037872dd feat(coursework-v2): refine abstract, committee lines, and bibliography 2026-05-26 21:28:02 +02:00
pencelheimer 6bcc345717 fix(coursework-v2): proper apostrophe 2026-05-24 22:30:50 +03:00
pencelheimer 568e84729c fix(coursework-v2): proper department casing 2026-05-24 22:30:50 +03:00
unexplrd e066e96310 feat: add Про{1,2,3} subject keys 2026-05-22 15:20:36 +03:00
unexplrd 82732e7084 template(unexplrd-mise): bump package rev 2026-05-22 14:56:55 +03:00
unexplrd f60fefc7b6 fix: МОТІ subject name typo 2026-05-22 14:55:38 +03:00
unexplrd c3be6125b4 fix: check for student group and variant in keys 2026-05-22 14:47:46 +03:00
bosiiandgolii 983224a633 enhance README: add installation instructions for Windows and clarify usage for local Typst package 2026-05-22 11:46:00 +03:00
0x1D8 f70669a2b7 Merge pull request #1 from ilyaoc/add-coursework-v2-template
feat: add coursework v2 template
2026-05-20 15:44:39 +02:00
ilyaoc bddbd59557 fix coursework v2 example task lines 2026-05-20 12:44:07 +02:00
ilyaoc cf8c2c8338 refine coursework v2 task fields 2026-05-20 12:37:09 +02:00
ilyaoc c9c0577ec8 add coursework v2 template 2026-05-20 12:37:09 +02:00
14 changed files with 1309 additions and 11 deletions
+52 -2
View File
@@ -3,7 +3,7 @@
## 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
@@ -19,6 +19,8 @@ This template:
- 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".
@@ -32,7 +34,10 @@ This template:
## Usage
### As a local typst package
1. Clone this repository into ~/.local/share/typst/packages/:
You can install the template as a local Typst package. Choose the instructions for your platform below.
#### Unix (Linux / macOS)
1. Clone this repository into your local Typst packages folder:
```bash
git clone -b 0.1.1 https://gitea.linerds.us/pencelheimer/typst_nure_template.git ~/.local/share/typst/packages/local/nure/0.1.1
```
@@ -41,6 +46,51 @@ git clone -b 0.1.1 https://gitea.linerds.us/pencelheimer/typst_nure_template.git
typst init @local/nure:0.1.1 project-name
```
#### Windows (symlink method)
This method creates a symbolic link between the cloned repository and Typst's local packages folder in `%APPDATA%`. The benefit: when the template is updated on GitHub you only need to `git pull` in the cloned folder — VS Code (and Typst) will use the updated files automatically.
Step 1 — clone the template (for example, into your Documents folder):
```powershell
cd $HOME\Documents
git clone https://github.com/linerds/typst_nure_template.git
```
Step 2 — create the symlink (PowerShell must be run as Administrator):
1. Open Start, find PowerShell, right-click and choose "Run as Administrator".
2. Run the following commands as a single block:
```powershell
# Create local packages folder (if it doesn't exist)
New-Item -ItemType Directory -Force -Path "$env:APPDATA\typst\packages\local\nure"
# Point to the folder you cloned
$gitFolder = "$HOME\Documents\typst_nure_template"
# Create a symbolic link for the package version (match the version in this repo)
New-Item -ItemType SymbolicLink -Path "$env:APPDATA\typst\packages\local\nure\0.1.1" -Target $gitFolder
```
Step 3 — use in VS Code
Open your project folder (for example, your coursework or lab folder) in VS Code with the Tinymist Typst extension installed. Create `main.typ` and import the package using the official `@local` import:
```typst
#import "@local/nure:0.1.1": nure-report, style
// If you need specific style variables from the package:
#import "@local/nure:0.1.1/src/style.typ": spacing
#show: nure-report.with(
title: "Report for Lab Work",
type: "Lab Work #1",
discipline: "Object-Oriented Programming",
author: "Surname I.I.",
supervisor: "Assoc. Prof. Kuznetsov O.V.",
)
= Work progress
Your content...
```
The Windows steps above are intentionally minimal and focused on the symlink workflow. If you prefer not to use symlinks, you can copy `src/` to your project root and import `lib.typ` directly (see the "As a standalone file" section).
### As a standalone file
Copy `src/` to your project's root directory, optionally renaming `src/` to `lib/` (then import `src/lib.typ` or `lib/lib.typ` accordingly).
+4 -1
View File
@@ -54,7 +54,7 @@
МКр: Мережна криміналістика
МОКр: Математичні основи криптології
МОТДО: Методи оптимізаціі та дослідження операцій
МОТІ: Методи оптимізації та теорія ігр
МОТІ: Методи оптимізації та теорія ігор
МППЗ: Менеджмент проектів програмного забезпечення
МППС: Methodologies of designing software systems
МС: Моделювання систем
@@ -79,6 +79,9 @@
ПЕСЕ: Психологія екстремальних стосунків та ефективної адаптації
ПНП: Програмування на платформі .NЕТ
ПП: Проектний практикум
Про1: Програмування (ч. 1)
Про2: Програмування (ч. 2)
Про3: Програмування (ч. 3)
Прог: Програмування # NOTE: was "ПРОГ" before
РNet: Робота з даними на платформі .Net
РХЗ: Розробка хмарних застосувань в AZURE
+420
View File
@@ -0,0 +1,420 @@
<?xml version="1.0" encoding="UTF-8"?>
<style xmlns="http://purl.org/net/xbiblio/csl"
class="in-text"
version="1.0"
default-locale="uk-UA"
demote-non-dropping-particle="never">
<info>
<title>ДСТУ 8302:2015 — бібліографічні посилання</title>
<title-short>ДСТУ 8302:2015</title-short>
<id>dstu-8302-2015-typst</id>
<link href="https://online.budstandart.com/ua/catalog/doc-page.html?id_doc=64411" rel="documentation"/>
<author>
<name>Custom Typst CSL</name>
</author>
<category citation-format="numeric"/>
<category field="generic-base"/>
<summary>CSL для оформлення переліку джерел за логікою ДСТУ 8302:2015 у Typst/Pandoc: числові посилання, українські скорочення, DOI/URL, дата звернення, коректні вебджерела, статті та нормативні акти.</summary>
<updated>2026-05-25T00:00:00+00:00</updated>
<rights license="http://creativecommons.org/licenses/by-sa/4.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 License</rights>
</info>
<locale xml:lang="uk-UA">
<terms>
<term name="accessed">дата звернення</term>
<term name="page" form="short">С.</term>
<term name="page" form="symbol">с.</term>
<term name="volume" form="short">Т.</term>
<term name="issue" form="short">№</term>
<term name="edition" form="short">вид.</term>
<term name="editor" form="short">ред.</term>
<term name="translator" form="short">пер.</term>
<term name="et-al">та ін.</term>
<term name="and">,</term>
</terms>
</locale>
<!-- Ініціали після прізвища: Vergara L. M., Park M. -->
<macro name="contributors">
<names variable="author">
<name name-as-sort-order="all"
sort-separator=" "
initialize-with=". "
delimiter=", "
delimiter-precedes-last="never"/>
<substitute>
<names variable="editor">
<name name-as-sort-order="all"
sort-separator=" "
initialize-with=". "
delimiter=", "
delimiter-precedes-last="never"/>
<label form="short" prefix=" (" suffix=")"/>
</names>
<names variable="translator">
<name name-as-sort-order="all"
sort-separator=" "
initialize-with=". "
delimiter=", "
delimiter-precedes-last="never"/>
<label form="short" prefix=" (" suffix=")"/>
</names>
</substitute>
</names>
</macro>
<macro name="title">
<text variable="title"/>
</macro>
<macro name="container">
<text variable="container-title"/>
</macro>
<macro name="site-or-publisher">
<choose>
<if variable="container-title">
<text variable="container-title"/>
</if>
<else>
<text variable="publisher"/>
</else>
</choose>
</macro>
<macro name="issued-year">
<date variable="issued">
<date-part name="year"/>
</date>
</macro>
<macro name="issued-date">
<date variable="issued">
<date-part name="day" form="numeric-leading-zeros" suffix="."/>
<date-part name="month" form="numeric-leading-zeros" suffix="."/>
<date-part name="year"/>
</date>
</macro>
<macro name="accessed-date">
<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>
</macro>
<macro name="publisher-place">
<group delimiter=" : ">
<text variable="publisher-place"/>
<text variable="publisher"/>
</group>
</macro>
<macro name="edition">
<group delimiter=" ">
<text variable="edition"/>
<label variable="edition" form="short"/>
</group>
</macro>
<macro name="book-pages">
<choose>
<if variable="number-of-pages">
<group delimiter=" ">
<text variable="number-of-pages"/>
<text term="page" form="symbol"/>
</group>
</if>
<else-if variable="page">
<group delimiter=" ">
<text variable="page"/>
<text term="page" form="symbol"/>
</group>
</else-if>
</choose>
</macro>
<macro name="pages">
<choose>
<if variable="page">
<group delimiter=" ">
<text term="page" form="short"/>
<text variable="page"/>
</group>
</if>
</choose>
</macro>
<macro name="volume-issue">
<group delimiter=", ">
<choose>
<if variable="volume">
<group delimiter=" ">
<text value="Т."/>
<text variable="volume"/>
</group>
</if>
</choose>
<choose>
<if variable="issue">
<group delimiter=" ">
<text value="№"/>
<text variable="issue"/>
</group>
</if>
</choose>
</group>
</macro>
<macro name="url-access">
<choose>
<if variable="URL">
<group delimiter=" ">
<text variable="URL" prefix="URL: "/>
<choose>
<if variable="accessed">
<group delimiter=": " prefix="(" suffix=")">
<text term="accessed"/>
<text macro="accessed-date"/>
</group>
</if>
</choose>
</group>
</if>
</choose>
</macro>
<macro name="doi-or-url">
<choose>
<if variable="DOI">
<text variable="DOI" prefix="DOI: "/>
</if>
<else>
<text macro="url-access"/>
</else>
</choose>
</macro>
<macro name="legal-details">
<choose>
<if variable="genre">
<group delimiter=" : ">
<text macro="title"/>
<group delimiter=" ">
<text variable="genre"/>
<choose>
<if variable="issued">
<group delimiter=" ">
<text value="від"/>
<text macro="issued-date"/>
</group>
</if>
</choose>
<choose>
<if variable="number">
<group delimiter=" ">
<text value="№"/>
<text variable="number"/>
</group>
</if>
</choose>
</group>
</group>
</if>
<else>
<group delimiter=". ">
<text macro="title"/>
<group delimiter=" ">
<choose>
<if variable="issued">
<group delimiter=" ">
<text value="від"/>
<text macro="issued-date"/>
</group>
</if>
</choose>
<choose>
<if variable="number">
<group delimiter=" ">
<text value="№"/>
<text variable="number"/>
</group>
</if>
</choose>
</group>
</group>
</else>
</choose>
</macro>
<citation collapse="citation-number">
<sort>
<key variable="citation-number"/>
</sort>
<layout delimiter=", " prefix="[" suffix="]">
<text variable="citation-number"/>
</layout>
</citation>
<bibliography hanging-indent="false" entry-spacing="0" line-spacing="1">
<sort>
<key variable="citation-number"/>
</sort>
<layout>
<group display="block">
<text display="left-margin" variable="citation-number" suffix=". "/>
<choose>
<!-- Нормативні акти: Цивільний кодекс України : Закон України від 16.01.2003 № 435-IV. URL: ... -->
<if type="legislation bill legal_case treaty" match="any">
<group delimiter=". " suffix=".">
<text macro="legal-details"/>
<text macro="url-access"/>
</group>
</if>
<!-- Наукова стаття -->
<else-if type="article-journal">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<text macro="container"/>
<text macro="issued-year"/>
<text macro="volume-issue"/>
<text macro="pages"/>
<text macro="doi-or-url"/>
</group>
</else-if>
<!-- Газетна / журнальна стаття без DOI -->
<else-if type="article-newspaper article-magazine" match="any">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<text variable="container-title" font-style="italic"/>
<text macro="issued-date"/>
<text macro="pages"/>
<text macro="url-access"/>
</group>
</else-if>
<!-- Вебсторінки: без дублювання назви замість автора -->
<else-if type="webpage post post-weblog" match="any">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<text macro="site-or-publisher"/>
<text macro="issued-date"/>
<text macro="url-access"/>
</group>
</else-if>
<!-- Книга -->
<else-if type="book">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<text macro="edition"/>
<group delimiter=", ">
<text macro="publisher-place"/>
<text macro="issued-year"/>
</group>
<text macro="book-pages"/>
<text macro="doi-or-url"/>
</group>
</else-if>
<!-- Розділ у книзі -->
<else-if type="chapter">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<group delimiter=" ">
<text value="In:"/>
<text variable="container-title" font-style="italic"/>
</group>
<names variable="editor">
<name name-as-sort-order="all"
sort-separator=" "
initialize-with=". "
delimiter=", "
delimiter-precedes-last="never"/>
<label form="short" prefix=" (" suffix=")"/>
</names>
<group delimiter=", ">
<text macro="publisher-place"/>
<text macro="issued-year"/>
</group>
<text macro="pages"/>
<text macro="doi-or-url"/>
</group>
</else-if>
<!-- Матеріали конференції -->
<else-if type="paper-conference">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<group delimiter=" ">
<text value="In:"/>
<text variable="container-title"/>
</group>
<group delimiter=", ">
<text macro="publisher-place"/>
<text macro="issued-year"/>
</group>
<text macro="pages"/>
<text macro="doi-or-url"/>
</group>
</else-if>
<!-- Дисертації, звіти -->
<else-if type="thesis report" match="any">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<text variable="genre"/>
<group delimiter=", ">
<text macro="publisher-place"/>
<text macro="issued-year"/>
</group>
<text macro="book-pages"/>
<text macro="doi-or-url"/>
</group>
</else-if>
<!-- Патенти -->
<else-if type="patent">
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<group delimiter=" ">
<text value="№"/>
<text variable="number"/>
</group>
<text macro="issued-date"/>
<text macro="url-access"/>
</group>
</else-if>
<!-- Універсальний fallback -->
<else>
<group delimiter=". " suffix=".">
<text macro="contributors"/>
<text macro="title"/>
<text variable="container-title" font-style="italic"/>
<group delimiter=", ">
<text macro="publisher-place"/>
<text macro="issued-year"/>
</group>
<text macro="pages"/>
<text macro="doi-or-url"/>
</group>
</else>
</choose>
</group>
</layout>
</bibliography>
</style>
+1 -1
View File
@@ -14,7 +14,7 @@
"грудня",
).at(month - 1)
#let is-cyr(c) = regex("[\p{Cyrillic}]") in c
#let is-cyr(c) = regex("^\p{Cyrillic}") in c
/// type-safe emptiness check
#let is-empty(val) = {
+85 -1
View File
@@ -5,6 +5,9 @@
#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
@@ -29,7 +32,8 @@
bib-path: none,
appendices: (),
) = {
set document(title: title, author: authors.map(c => c.name))
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)
@@ -56,6 +60,86 @@
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
/// - committee-members (array): Optional list of commission member dictionaries for the title page
/// - 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: (),
committee-members: none,
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
}
let committee_members = committee-members
tp.cw-v2.nure(
university,
title,
authors,
mentors,
committee_members,
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-8302-2015.csl", full: true)
}
style.appendices(appendices)
}
/// Practice and Laboratory works template
/// - layout (str): "default", "minimal", or "complex"
/// - university (str): University code
+196 -1
View File
@@ -8,6 +8,10 @@
/// 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)
@@ -19,6 +23,125 @@
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,
tag: 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)
[#metadata((kind: "dstu-table", number: num)) #if tag != none { label(tag) }]
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,
@@ -35,7 +158,10 @@
// 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 par(justify: true, spacing: spacing, leading: spacing, first-line-indent: (
amount: indent-size,
all: true,
))
set block(spacing: spacing)
set underline(evade: false)
@@ -48,6 +174,63 @@
set list(indent: indent-size + 0.1cm, body-indent: 0.5cm, marker: [--])
// Figures
show ref: it => {
let target = it.element
if target == none {
return it
}
// dstu-table refs carry a number already resolved at the table location.
if (
target.func() == metadata
and type(target.value) == dictionary
and target.value.at("kind", default: none) == "dstu-table"
) {
return link(target.location())[#target.value.at("number")]
}
if target.func() != figure and target.func() != math.equation {
return it
}
// A figure or equation number must be read at the element itself, not at the citation site.
// The default ref display re-runs the numbering function here, where its inner `counter(heading)`
// context resolves to the citing chapter and prints the wrong section or appendix letter.
// We sample every counter at the target location instead.
let loc = target.location()
let appendix = dstu-table-appendix.at(loc)
let section = if appendix == none {
str(counter(heading).at(loc).first())
} else {
upper(ukr-enum.at(appendix - 1))
}
let number = if target.func() == figure {
let idx = counter(figure.where(kind: target.kind)).at(loc).first()
section + "." + str(idx)
} else {
let idx = counter(math.equation).at(loc).first()
"(" + section + "." + str(idx) + ")"
}
let supplement = if it.supplement == auto {
target.supplement
} else {
it.supplement
}
let body = if supplement != auto and supplement != none {
[#supplement #number]
} else {
[#number]
}
link(loc, body)
}
show figure: it => {
v(double-spacing, weak: true)
it
@@ -65,6 +248,7 @@
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)
it
}
set figure(numbering: i => context numbering("1.1", counter(heading).get().at(0), i))
@@ -97,6 +281,15 @@
v(double-half-spacing, weak: true)
}
// blocks `like this` aren't welcome, so ` is replaced with "
show raw.where(block: false): it => text(
lang: "uk",
size: 14pt,
hyphenate: false,
weight: "regular",
font: ("Times New Roman", "Liberation Serif"),
)["#it.text"]
it
}
@@ -118,6 +311,8 @@
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")
+1
View File
@@ -0,0 +1 @@
#import "nure.typ": *
+394
View File
@@ -0,0 +1,394 @@
#import "../../shared.typ": universities
#import "../../helpers.typ": *
#import "../../style.typ": spacing
#import "../../utils.typ": bold, uline, filled-lines, hfill
#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, value.join())
uline(align: center, [])
note[(тема)]
}
#let commission-lines(members) = {
if members.len() > 0 {
for (i, member) in members.enumerate() {
if i > 0 {
linebreak()
}
let member-display-name = member.at("display-name", default: member.name)
let member-degree = member.at("degree", default: "")
uline(align: left, [#member-degree #member-display-name])
}
} else {
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)
}
}
#let nure(
university,
title,
authors,
mentors,
committee_members,
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 commission-members = if committee_members == none {
mentors.slice(1)
} else {
committee_members
}
let uni = universities.at(university)
let edu-prog = uni.edu-programs.at(author.edu-program)
let program-name = if program-name == none {
edu-prog.at("program-name", default: edu-prog.name-long)
} else {
program-name
}
let group-name = if str(author.group).starts-with(author.edu-program) {
str(author.group)
} else {
author.edu-program + "-" + str(author.group)
}
let executor-label = if author.gender == "f" or author.gender == "female" or author.gender == "ж" {
"Виконала:"
} else {
"Виконав:"
}
let author-display-name = author.at("display-name", default: author.name)
let author-full-name-dat = author.at("full-name-dat", default: author.full-name-gen)
let mentor-display-name = head-mentor.at("display-name", default: head-mentor.name)
let mentor-degree = head-mentor.at("degree", default: "")
[
#set par(first-line-indent: 0pt, justify: false, leading: 0.45em)
#set text(size: 14pt)
#set align(center)
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ\
#uni.name
#v(0.7em)
#set align(left)
#inline-label-line(
[Факультет],
faculty,
)
#note([(повна назва)])
#v(0.8em)
#inline-label-line(
[Кафедра],
lower(edu-prog.department-gen),
)
#note([(повна назва)])
#v(1.8em)
#set align(center)
#text(size: 20pt, weight: "bold")[КОМПЛЕКСНИЙ КУРСОВИЙ ПРОЄКТ]\
#text(size: 20pt, weight: "bold")[Пояснювальна записка]
#v(0.9em)
#set align(left)
#label-line([рівень вищої освіти], education-level, label-width: 120pt)
#v(1.0em)
#title-field(title)
#v(3em)
#grid(
columns: (0.36fr, 0.64fr),
[],
[
#executor-label\
здобувач #underline([#author.course]) курсу, групи #underline(group-name)\
#uline(align: center, author-display-name)
#note[(Власне ім'я, ПРІЗВИЩЕ)]
#v(0.2em)
#inline-label-line([Спеціальність], [#edu-prog.code -- #edu-prog.name-long])
#note[(код і повна назва спеціальності)]
#inline-label-line([Тип програми], program-type)
#inline-label-line([Освітня програма], program-name)
#note[(повна назва освітньої програми)]
#v(0.3em)
#inline-label-line([Керівник], [#mentor-degree #mentor-display-name])
#note[(посада, Власне ім'я, ПРІЗВИЩЕ)]
#v(0.3em)
#pad(left: 75pt)[
#set par(first-line-indent: 0pt)
Члени комісії (#text(size: 10pt)[Власне ім'я, ПРІЗВИЩЕ, підпис])
#v(0.15em)
#commission-lines(commission-members)
]
],
)
#v(1fr)
#set align(center)
#task-list.done-date.display("[year]") р.
#pagebreak()
]
[
#set par(first-line-indent: 0pt, justify: false)
#align(center)[#uni.name]
#v(1.1em)
#task-head-fields((
(
[Факультет],
(
faculty,
),
),
([Кафедра], lower(edu-prog.department-gen)),
([Рівень вищої освіти], education-level),
([Спеціальність], [#edu-prog.code -- #edu-prog.name-long]),
([Тип програми], program-type),
([Освітня програма], program-name),
))
#note[(шифр і назва)]
#v(1.7em)
#grid(
columns: (1.1fr, 1.1fr, 1.1fr, 1.7fr, 1.3fr, 1.1fr),
gutter: 0pt,
align: center + horizon,
[Курс], uline(author.course), [Група], uline(group-name), [Семестр], uline(author.semester),
)
#v(2.6em)
#align(center)[
#bold[ЗАВДАННЯ]\
#text(style: "italic", weight: "bold")[на курсовий проєкт (роботу) студента]
]
#v(1.0em)
#label-line([здобувачеві], author-full-name-dat, caption: [(прізвище, ім'я, по батькові)], label-width: 95pt)
#v(1.0em)
#task-num(1) Тема роботи #uline(align: left, filled-lines(title))
#v(0.4em)
#task-num(2) Термін здачі студентом закінченої роботи
“#underline(task-list.done-date.display("[day]"))” #underline(month-gen(task-list.done-date.month())) #task-list.done-date.display("[year]")р.
#v(0.4em)
#task-num(3) Вихідні дані до проєкту #uline(align: left, filled-lines(task-list.at("source", default: [])))
#v(0.4em)
#uline(align: left, [])
#v(0.4em)
#task-num(4) Перелік питань, що потрібно опрацювати в роботі\
#uline(align: left, filled-lines(task-list.at("content", default: [])))
#v(0.4em)
#uline(align: left, [])
#pagebreak()
]
[
#align(center, bold[КАЛЕНДАРНИЙ ПЛАН])
#set par(first-line-indent: 0pt)
#v(1.4em)
#calendar-plan.plan-table
#v(5.0em)
Дата видачі завдання “#underline(task-list.initial-date.display("[day]"))” #underline(month-gen(task-list.initial-date.month())) #task-list.initial-date.display("[year]") р.
#v(1.4em)
Здобувач #underline([#hfill(6cm)])
#note[(підпис) #h(8cm)]
#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 dstu-tables = query(metadata).filter(it => type(it.value) == dictionary and it.value.at("kind", default: none) == "dstu-table").len()
#let tables = query(figure.where(kind: table)).len() + dstu-tables
#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(", ").
]
\
#let keyword-pairs = if abstract.keywords.len() > 0 and type(abstract.keywords.first()) == array {
abstract.keywords.map(pair => (
uk: pair.at(0),
en: pair.at(1),
))
} else if abstract.at("en", default: none) != none and abstract.en.at("keywords", default: none) != none {
abstract.keywords.enumerate().map(((i, uk)) => (
uk: uk,
en: abstract.en.keywords.at(i),
))
} else {
abstract.keywords.map(uk => (uk: uk))
}
#let sorted-keyword-pairs = keyword-pairs.sorted(by: (a, b) => {
if is-cyr(a.uk) != is-cyr(b.uk) { is-cyr(a.uk) } else { a.uk < b.uk }
})
#(sorted-keyword-pairs.map(pair => upper(pair.uk)).join(", "))
\
#abstract.text
#if abstract.at("en", default: none) != none or (keyword-pairs.len() > 0 and keyword-pairs.first().at("en", default: none) != none) [
\
#(sorted-keyword-pairs.map(pair => upper(pair.en)).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,
)
}
}
+2 -1
View File
@@ -210,7 +210,8 @@
#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 dstu-tables = query(metadata).filter(it => type(it.value) == dictionary and it.value.at("kind", default: none) == "dstu-table").len()
#let tables = query(figure.where(kind: table)).len() + dstu-tables
#let bibs = bib-count.final().dedup().len()
/* TODO: why this stopped working?
#let tables = counter(figure.where(kind: table)).final().at(0)
+1
View File
@@ -1,2 +1,3 @@
#import "pz-lb/main.typ" as pz-lb
#import "coursework/main.typ" as cw
#import "coursework-v2/main.typ" as cw-v2
+2 -2
View File
@@ -20,9 +20,9 @@
#if authors.len() == 1 {
let a = authors.first()
[#gender-form("author", dict: a):\ ]
[ст. гр. #a.edu-program\-#a.group\ ]
if ("edu-program" in a) and ("group" in a) [ст. гр. #a.edu-program\-#a.group\ ]
[#a.name\ ]
if not is-empty(a.variant) [Варіант: #a.variant]
if ("variant" in a) and (not is-empty(a.variant)) [Варіант: #a.variant]
} else if authors.len() > 1 [
#gender-form("author"):\
#for a in authors [
+7
View File
@@ -7,6 +7,13 @@
/// 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) }
+143
View File
@@ -0,0 +1,143 @@
#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 committee_members = (
(name: "Груздо І.В.", degree: "Доц."),
(name: "Зибіна К.В.", degree: "Ст. викл."),
(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: (
("веб-застосунок", "WEB APPLICATION"),
("інформаційна система", "INFORMATION SYSTEM"),
("курсова робота", "COURSEWORK"),
("програмна інженерія", "SOFTWARE ENGINEERING"),
("тестовий приклад", "DEMO SAMPLE"),
),
text: [
Мета даної роботи -- продемонструвати оформлення пояснювальної записки
для комплексного курсового проєкту з використанням нового варіанта шаблону.
Приклад містить узагальнену тему, типові сторінки завдання, календарного плану,
реферату, змісту, переліку джерел посилання та додатків.
У роботі наведено умовну структуру програмної системи, що може бути
адаптована під конкретну предметну область. Основний акцент зроблено на
перевірці полів титульної сторінки, сторінки завдання, службових підписів,
нумерації розділів і коректної роботи додатків.
Результатом є демонстраційний документ, який показує очікуване використання
шаблону без прив'язки до реального студента, викладача або завершеної роботи.
],
)
#let abstract_en = (
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,
committee-members: committee_members,
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 -2
View File
@@ -19,8 +19,7 @@ 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_rev = "d6037872ddb6391afec147a679fa05f08cba6bb0"
nure_package_name = "vendor/nure"
nure_package_ver = "{{vars.nure_package_ref}}"