diff --git a/README.md b/README.md index 370b56b..5036e6f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Typst Template for NURE Works -![pz-lb title page](assets/pz-lb_title_page.png) +pz-lb title page ## General Info @@ -16,7 +16,9 @@ This template: This template: - Sets up document styles; - Formats the title, task, calendar plan, and abstract pages; -- Typesets the bibliography, outline, and appendices according to standard requirements. +- Typesets the bibliography according to ДСТУ 3008:2015 using custom CSL style; +- Typesets the outline and appendices according to standard requirements. + ### Utilities - `nheading` - For unnumbered headings, such as "Introduction" and "Conclusion". @@ -25,27 +27,29 @@ This template: - `bold` - Inserts bold text inside functional environments. - `img` - Inserts images with a caption, automatically deriving the label from the image file name. +**Note:** `img()` is provided in `utils.typ` in project's root directory for compatibility, until [path() type](https://github.com/typst/typst/pull/7555) is released. + ## Usage ### As a local typst package 1. Clone this repository into ~/.local/share/typst/packages/: ```bash -git clone -b 0.1.0 https://gitea.linerds.us/pencelheimer/typst_nure_template.git ~/.local/share/typst/packages/local/nure/0.1.0 +git clone -b 0.1.1 https://gitea.linerds.us/pencelheimer/typst_nure_template.git ~/.local/share/typst/packages/local/nure/0.1.1 ``` 2. Init your project with Typst: ```bash -typst init @local/nure:0.1.0 project-name +typst init @local/nure:0.1.1 project-name ``` ### As a standalone file -Copy `lib.typ` to your project's root directory. +Copy `src/` to your project's root directory, optionally renaming `src/` to `lib/`. ### In your project ```typst // Import the template either from a local package... #import "@local/nure:0.1.0": * // ...or by importing a lib.typ directly -// #import "/lib.typ": * +// #import "/lib/lib.typ": * // 1. Setup the document // by setting values directly... @@ -65,9 +69,9 @@ Copy `lib.typ` to your project's root directory. Some text // ...or include your modules -#include "src/intro.typ" -#include "src/chapter1.typ" -#include "src/chapter2.typ" +#include "chapters/intro.typ" +#include "chapters/chapter1.typ" +#include "chapters/chapter2.typ" // NOTE: if you want to use variables or utils provided by the package, // you have to import the package or a lib.typ inside a module. @@ -75,7 +79,7 @@ Some text // If you ever need appendices in pz-lb template use the show rule // WARNING: when using coursework template use its own argument, // so it can put bibliography before appendices -#show: appendices-style +#show: style.appendices = Quote #link("https://youtu.be/bJQj1uKtnus")[ @@ -86,10 +90,13 @@ Some text And a TOML file would look like this: ```toml -title = "Потiк керування та алгоритмічні структури Bash" +# university = "ХНУРЕ" # "ХНУРЕ" is the default +# edu-program = "ПЗПІ" # can be null, sourced from authors.first() by default + subject = "СМП" doctype = "ЛБ" worknumber = 2 +title = "Потiк керування та алгоритмічні структури Bash" [[mentors]] name = "Шевченко Т. Г." @@ -101,33 +108,57 @@ name = "Франко І. Я." degree = "Асистент кафедри ПІ" gender = "m" -edu_program = "ПЗПІ" -university = "ХНУРЕ" - [[authors]] name = "Косач Л. П." -full_name_gen = "Косач Лариси Петрівни" -course = 2 -edu = "ПЗПІ" -gender = "f" +edu-program = "ПЗПІ" group = "23-2" -semester = 4 +gender = "f" variant = 8 +# For coursework +full-name-gen = "Косач Лариси Петрівни" +course = 2 +semester = 4 ``` ### Notes: 1. Use `#v(-spacing)` to remove vertical spacing between titles (this cannot be automatically handled by the template). Variable `spacing` used here is imported from the template. -2. When importing `@local/nure:0.1.0` and specifying file paths in functions handled by the package, the path will relative to package's root directory, e.g. setting `#show: coursework.with(bib_path: "bibl.yml")` will evaluate to `~/.local/share/typst/packages/local/nure/0.1.0/bibl.yml`, the same is for `#img` function, which makes it quite annoying and forces one to import `lib.typ` file. Please open an issue or contact us in any other way if you have any advice. +2. When importing `@local/nure:0.1.1` and specifying file paths in functions handled by the package, the path will relative to package's root directory, e.g. setting `#show: coursework.with(bib-path: "bibl.yml")` will evaluate to `~/.local/share/typst/packages/local/nure/0.1.1/bibl.yml`, the same is for `#img` function, which makes it quite annoying and forces one to import `lib.typ` file. Please open an issue or contact us in any other way if you have any advice. + +### Bibliography Format +The template uses a custom CSL (Citation Style Language) file located at `src/csl/dstu-3008-2015.csl` to format bibliography entries. + +Supported bibliography entry types in `bibl.yml`: +- **Book**: Books with author, title, publisher, year, and page count +- **Web**: Web resources with title, author/organization, URL, and access date + +**Warning:** Other types were added by Kimi K2.5 without any additional checks for compliance. + +Example `bibl.yml`: +```yaml +mysql: + type: Book + title: MySQL Language Reference + author: Ab M. + publisher: MySQL Press + date: 2004 + page-total: 600 + +go: + type: Web + title: The Go Programming Language + author: The Go Programming Language + url: + value: https://go.dev/ + date: 2024-12-10 +``` ### Example Project Structure ``` project/ ├── doc.toml -- for things that don't change across works, i.e. author and mentor metadata ├── main.typ -- for boilerplate code and importing everything -├── config/ -│ ├── universities.yaml -- for user-specific configuration, i.e. education programs and disciplines -│ └── ... -├── src/ +├── utils.typ -- for helper functions +├── chapters/ │ ├── intro.typ │ ├── chapter1.typ │ ├── chapter2.typ diff --git a/src/config/universities.yaml b/src/config/universities.yaml new file mode 100644 index 0000000..8855a07 --- /dev/null +++ b/src/config/universities.yaml @@ -0,0 +1,78 @@ +ХНУРЕ: + name: Харківський національний університет радіоелектроніки + name-en: Kharkiv National University of Radioelectronics + edu-programs: + ПЗПІ: + name-long: Інженерія програмного забезпечення + department-gen: Програмної інженерії + code: 121 # TODO: change to F2? + КУІБ: + name-long: Управління інформаційною безпекою + department-gen: Інфокомунікаційної інженерії ім. В. В. Поповського + code: 125 # TODO: change to F5? + description: Кібербезпека та захист інформації + КНТ: + name-en: CST # computer sciences and technologies + name-long: Комп'ютерні науки та технології + department-gen: Системотехніки + department-en: ST + code: 122 + subjects: + DMT: Decision making theory + ODS: Основи Dаtа Sсіеnсе # NOTE: Eng O here + ІМ: Іноземна мова + ІТР: Information Technologies of Reengineering + ІТРОІ: Інтернет-технології Розподіленої Обробки Інформації + АВпЗ: Аналіз вимог до програмного забезпечення + АДан: Аналітика даних + АКтаК: Архітектура комп'ютера та комп'ютерних мереж + АТСД: Алгоритми та структури даних + АтаРК: Аналіз та рефакторинг коду + БД: Бази даних + БЖД: Безпека життєдіяльності + ВДІТБ: Введення до ІТ-бізнесу # NOTE: all in UA + ВМ: Вища математика + ГТГ: Гіпертекст та гіпермедіа + ДМ: Дискретна математика + ДПК: Динаміка Проектних Команд + ЕРВ: Електрорадіовимірювання + КДМА: Комп'ютерна дискретна математика + КЗВШ: Креативність з використанням штучного інтелекту + КМ: Комп`ютерні мережі + ЛМВ: Людино-машинна взаємодія + ЛМтБ: Локальні мережі та їх безпека # бидло не знає що українською "їхня" + МОКр: Математичні основи криптології + МОТДО: Методи оптимізаціі та дослідження операцій + МППС: Methodologies of designing software systems + МС: Моделювання систем + ОІМ: Основи IP-мереж + ООАПС: Об'єктно-орієнтований аналіз в проектуванні систем + ООП: Об'єктно-орієнтоване програмування + ОП: Основи права + ОПІ: Основи програмноі інженеріі + ОПНJ: Основи програмування на Java + ОПр: Основи програмування + ОРвІТ: Оцінка Ризиків в IT-проектах + ОС: Операційні системи + ОТК: Основи теорії кіл + ПБІП: Проектування та балансування ігрового процесу + ПВJ: Поглиблене вивчення Java + ПЕСЕ: Психологія екстремальних стосунків та ефективної адаптації + ПНП: Програмування на платформі .NЕТ + ПП: Проектний практикум + ПРОГ: Програмування + ПарП: Параллельне програмування + СА: Системний аналіз + СМП: Скриптові мови програмування + СОАПЗ: Сервіс-Орієнтована Архітектура Програмного Забезпечення + СРБД: Серверні рішення баз даних + СхТ: Схемотехніка + ТВО: Технології Високопродуктивних Обчислень + ТЗІ: Технології захисту інформації + ТЙтаМ: Теорія ймовірностей та математична # TODO: what? + ТКП: Технології комп`ютерного проєктування + УФМ: Українське фахове мовлення + ФІЗ: Фізика + ФІЛ: Філософія + ФВС: Фізичне виховання та спорт + ХТ: Хмарні технології diff --git a/src/csl/dstu-3008-2015.csl b/src/csl/dstu-3008-2015.csl new file mode 100644 index 0000000..e5afc48 --- /dev/null +++ b/src/csl/dstu-3008-2015.csl @@ -0,0 +1,357 @@ + + diff --git a/src/helpers.typ b/src/helpers.typ new file mode 100644 index 0000000..99ebdeb --- /dev/null +++ b/src/helpers.typ @@ -0,0 +1,52 @@ +/// month name from its number +#let month-gen(month) = ( + "січня", + "лютого", + "березня", + "квітня", + "травня", + "червня", + "липня", + "серпня", + "вересня", + "жовтня", + "листопада", + "грудня", +).at(month - 1) + +#let is-cyr(c) = regex("[\p{Cyrillic}]") in c + +/// type-safe emptiness check +#let is-empty(val) = { + if val == none { return true } + if type(val) == str { val.len() == 0 } else if type(val) == array { val == [] } else { false } +} + +#let degree-get(m) = if "degree" in m and not is-empty(m.degree) { [#m.degree\ ] } + +/// returns verb form based on gender ("m", "f", or "p" for plural) +#let gender-verb(verb, gender: "p") = { + ( + "author": ("m": "Виконав", "f": "Виконала", "p": "Виконали"), + "mentor": ("m": "Перевірив", "f": "Перевірила", "p": "Перевірили"), + ) + .at(verb) + .at(if gender == "m" or gender == "f" { gender } else { "p" }) +} + +/// returns verb form for dictionary containing gender field +#let gender-form(verb, dict: none) = { + let g = if type(dict) == dictionary and "gender" in dict { dict.gender } else { "p" } + gender-verb(verb, gender: g) +} + +#let pz-lb-title(type, number: none) = { + let type-title = ( + "ЛБ": [Звіт \ з лабораторної роботи], + "ПЗ": [Звіт \ з практичної роботи], + "КР": [Контрольна робота], + "РФ": [Реферат], + "ІДЗ": [Індивідуальне домашнє завдання], + ).at(type, default: type) + if not is-empty(number) { [#type-title №#number] } else { [#type-title] } +} diff --git a/src/lib.typ b/src/lib.typ new file mode 100644 index 0000000..3ab603c --- /dev/null +++ b/src/lib.typ @@ -0,0 +1,114 @@ +#import "./title-pages/main.typ" as tp +#import "./shared.typ": universities +#import "./helpers.typ": * + +#import "./style.typ" +#import "./utils.typ" + +/// Coursework template for NURE +/// - university (str): University code, default "ХНУРЕ" +/// - subject (str): Subject short name +/// - title (str): Work title +/// - authors (array): List of author dictionaries +/// - mentors (array): List of mentor dictionaries +/// - task-list (dict): Task metadata +/// - calendar-plan (dict): Calendar plan table and approval date +/// - abstract (dict): Keywords and abstract text +/// - bib-path (str): Path to bibliography file +/// - appendices (content): Appendix content +#let coursework( + doc, + university: "ХНУРЕ", + subject: none, + title: none, + authors: (), + mentors: (), + task-list: (), + calendar-plan: (), + abstract: (), + bib-path: none, + appendices: (), +) = { + set document(title: title, author: authors.map(c => c.name)) + + show: style.dstu.with(skip: 1) + + tp.cw.nure( + university, + subject, + title, + authors, + mentors, + task-list, + calendar-plan, + abstract, + ) + + doc + + // Bibliography with DSTU formatting + { + show regex("^\\d+\\."): it => [#it#h(0.5cm)] + show block: it => [#it.body#parbreak()] + bibliography(bib-path, title: [Перелік джерел посилання], style: "csl/dstu-3008-2015.csl", full: true) + } + + style.appendices(appendices) +} + +/// Practice and Laboratory works template +/// - layout (str): "default", "minimal", or "complex" +/// - university (str): University code +/// - edu-program (str): Education program code +/// - subject (str): Subject code +/// - type (str): Work type (ЛБ, ПЗ, КР, РФ, ІДЗ) +/// - number (int): Work number +/// - title (str): Work title +/// - authors (array): List of authors +/// - mentors (array): List of mentors +#let pz-lb( + doc, + layout: "default", + university: "ХНУРЕ", + edu-program: none, + subject: none, + type: none, + number: none, + title: none, + authors: (), + mentors: (), +) = { + assert(authors.len() > 0, message: "At least one author required") + + let edu-program = if edu-program != none { edu-program } else { authors.first().edu-program } + let uni = universities.at(university) + + set document(title: title, author: authors.map(c => c.name)) + + show: style.dstu.with(skip: 1) + + // Select layout variant + let layouts = ( + "complex": tp.pz-lb.complex(uni, edu-program, subject, type, number, title, authors, mentors), + "ХНУРЕ": tp.pz-lb.nure(uni, edu-program, subject, type, number, title, authors, mentors), + "default": tp.pz-lb.nure(uni, edu-program, subject, type, number, title, authors, mentors), + ) + + layouts.at(university, default: layouts.default) + + pagebreak(weak: true) + + // Set heading counter based on title/number + if title == none { + if number == none { context counter(heading).update(1) } else { + context counter(heading).update(number) + } + } else { + if number != none { + context counter(heading).update(number - 1) + } + heading(eval(title, mode: "markup")) + } + + doc +} diff --git a/src/shared.typ b/src/shared.typ new file mode 100644 index 0000000..6dd351c --- /dev/null +++ b/src/shared.typ @@ -0,0 +1 @@ +#let universities = yaml("config/universities.yaml") diff --git a/src/style.typ b/src/style.typ new file mode 100644 index 0000000..046862b --- /dev/null +++ b/src/style.typ @@ -0,0 +1,132 @@ +#import "utils.typ": bold +/// Constants for consistent styling +#let spacing = 0.95em +#let indent-size = 1.25cm +#let double-spacing = spacing * 2 +#let double-half-spacing = spacing * 2.5 + +/// Ukrainian alphabet for DSTU 3008:2015 numbering +#let ukr-enum = "абвгдежиклмнпрстуфхцшщюя".split("") + +/// Helper for level 2/3 heading blocks +#let heading-block(it, num: auto) = { + v(double-spacing, weak: true) + block(width: 100%, spacing: 0em)[ + #h(indent-size) + #counter(heading).display(num) + #it.body + ] + v(double-spacing, weak: true) +} + +/// DSTU 3008:2015 Style +#let dstu( + it, + skip: 0, + offset: 0, +) = { + // Page setup + set page( + paper: "a4", + number-align: top + right, + margin: (top: 20mm, right: 10mm, bottom: 20mm, left: 25mm), + numbering: (i, ..) => if i > skip { numbering("1", i + offset) }, + ) + + // Text and paragraph + set text(lang: "uk", size: 14pt, hyphenate: false, font: ("Times New Roman", "Liberation Serif")) + set par(justify: true, spacing: spacing, leading: spacing, first-line-indent: (amount: indent-size, all: true)) + set block(spacing: spacing) + set underline(evade: false) + + // Lists + set enum(indent: indent-size, body-indent: 0.5cm, numbering: i => ukr-enum.at(i) + ")") + show enum: it => { + set enum(indent: 0em, numbering: "1)") + it + } + set list(indent: indent-size + 0.1cm, body-indent: 0.5cm, marker: [--]) + + // Figures + show figure: it => { + v(double-spacing, weak: true) + it + v(double-spacing, weak: true) + } + set figure.caption(separator: [ -- ]) + show figure.where(kind: table): set figure.caption(position: top) + show figure.caption.where(kind: table): set align(left) + show figure.where(kind: raw): set figure.caption(position: top) + show figure.where(kind: raw): set align(left) + + // Numbering reset on level 1 headings + show heading.where(level: 1): it => { + counter(math.equation).update(0) + counter(figure.where(kind: raw)).update(0) + counter(figure.where(kind: image)).update(0) + counter(figure.where(kind: table)).update(0) + it + } + set figure(numbering: i => context numbering("1.1", counter(heading).get().at(0), i)) + set math.equation(numbering: i => context numbering("(1.1)", counter(heading).get().at(0), i)) + + // Headings + set heading(numbering: "1.1") + show heading: it => { + set text(size: 14pt) + if it.level == 1 { + set align(center) + set text(weight: "semibold") + pagebreak(weak: true) + upper(it) + v(double-spacing, weak: true) + } else { + set text(weight: "regular") + heading-block(it, num: if it.level == 3 { it.numbering } else { auto }) + } + } + + // Code listings + show raw.where(block: true): it => { + let code-spacing = 0.5em + set block(spacing: code-spacing) + set par(spacing: code-spacing, leading: code-spacing) + set text(size: 11pt, weight: "semibold", font: ("Courier New", "Liberation Mono")) + v(double-half-spacing, weak: true) + pad(it, left: indent-size) + v(double-half-spacing, weak: true) + } + + it +} + +/// DSTU 3008:2015 Appendices Style +#let appendices(it) = { + counter(heading).update(0) + + context { + let app-letter = upper(ukr-enum.at(counter(heading).get().at(0))) + set heading(numbering: (i, ..n) => upper(ukr-enum.at(i)) + numbering(".1.1", ..n)) + set figure(numbering: i => app-letter + "." + str(i)) + set math.equation(numbering: i => app-letter + "." + str(i)) + set heading(supplement: [Додаток]) + + show heading: h => { + set text(size: 14pt) + if h.level == 1 { + set align(center) + set text(weight: "regular") + pagebreak(weak: true) + bold([ДОДАТОК #counter(heading).display(auto)]) + linebreak() + h.body + v(double-spacing, weak: true) + } else { + set text(weight: "regular") + heading-block(h) + } + } + + it + } +} diff --git a/src/title-pages/coursework/main.typ b/src/title-pages/coursework/main.typ new file mode 100644 index 0000000..52e579b --- /dev/null +++ b/src/title-pages/coursework/main.typ @@ -0,0 +1 @@ +#import "nure.typ": * diff --git a/src/title-pages/coursework/nure.typ b/src/title-pages/coursework/nure.typ new file mode 100644 index 0000000..ebce727 --- /dev/null +++ b/src/title-pages/coursework/nure.typ @@ -0,0 +1,254 @@ +#import "../../shared.typ": universities +#import "../../helpers.typ": * +#import "../../style.typ": * +#import "../../utils.typ": bold, hfill, uline + +#let nure( + university, + subject, + title, + authors, + mentors, + task-list, + calendar-plan, + abstract, +) = { + let bib-count = state("citation-counter", ()) + show cite: it => { + it + bib-count.update(((..c)) => (..c, it.key)) + } + + let author = authors.first() + let head-mentor = mentors.first() + + let uni = universities.at(university) + let edu-prog = uni.edu-programs.at(author.edu-program) + + // page 1 {{{2 + [ + #set align(center) + МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ\ + #upper(uni.name) + + \ + + Кафедра #edu-prog.department-gen + + \ + + ПОЯСНЮВАЛЬНА ЗАПИСКА\ + ДО КУРСОВОЇ РОБОТИ\ + з дисципліни: "#uni.subjects.at(subject, default: subject)"\ + Тема роботи: "#title" + + \ \ \ + + #columns(2, gutter: 4cm)[ + #set align(left) + #set par(first-line-indent: 0pt) + + #gender-form("author", dict: author) ст. гр. #author.edu-program\-#author.group + + \ + Керівник:\ #head-mentor.degree + + \ + Робота захищена на оцінку + + \ + Комісія:\ #for m in mentors { degree-get(m) } + + #colbreak() + #set align(left) + + + #author.name + + \ \ + #head-mentor.name + + \ + #underline(" " * 35) + + \ \ + #for m in mentors [#m.name\ ] + ] + + #v(1fr) + + Харків -- #task-list.done-date.display("[year]") + + #pagebreak() + ] + + // page 2 {{{2 + { + uline[#uni.name] + + linebreak() + linebreak() + + grid( + columns: (100pt, 1fr), + bold[ + Кафедра + Дисципліна + Спеціальність + ], + { + uline(align: left, edu-prog.department-gen) + linebreak() + uline(align: left, uni.subjects.at(subject, default: subject)) + linebreak() + uline(align: left, [#edu-prog.code #edu-prog.name-long]) + }, + ) + grid( + columns: (1fr, 1fr, 1fr), + gutter: 0.3fr, + [#bold[Курс] #uline(author.course)], + [#bold[Група] #uline([#author.edu-program\-#author.group])], + [#bold[Семестр] #uline(author.semester)], + ) + + linebreak() + linebreak() + linebreak() + + align(center, bold[ЗАВДАННЯ \ на курсову роботу студента]) + + linebreak() + + uline(align: left)[_#author.full-name-gen _] + + linebreak() + linebreak() + + bold[\1. Тема роботи:] + uline[#title.] + + linebreak() + + { + bold[\2. Строк здачі закінченої роботи:] + uline(task-list.done-date.display("[day].[month].[year]")) + hfill(10fr) + } + + linebreak() + + bold[\3. Вихідні дані для роботи:] + uline(task-list.source) + + linebreak() + + bold[\4. Зміст розрахунково-пояснювальної записки:] + uline(task-list.content) + + linebreak() + + bold[\5. Перелік графічного матеріалу:] + uline(task-list.graphics) + + linebreak() + + { + bold[\6. Дата видачі завдання:] + uline(task-list.initial-date.display("[day].[month].[year]")) + hfill(10fr) + } + + pagebreak() + } + + // page 3 {{{2 + { + align(center, bold[КАЛЕНДАРНИЙ ПЛАН]) + set par(first-line-indent: 0pt) + + linebreak() + + calendar-plan.plan-table + + linebreak() + + grid( + columns: (5fr, 5fr), + grid( + columns: (1fr, 2fr, 1fr), + gutter: 0.2fr, + [ + Студент \ + Керівник \ + #align(center)["#underline[#calendar-plan.approval-date.day()]"] + ], + [ + #uline(align: center, []) \ + #uline(align: center, []) \ + #uline(align: center, month-gen(calendar-plan.approval-date.month())) + ], + [ + \ \ + #underline[#calendar-plan.approval-date.year()] р. + ], + ), + [ + #author.name, \ + #head-mentor.degree + #head-mentor.name. + ], + ) + + pagebreak() + } + + // page 4 {{{2 + [ + #align(center, bold[РЕФЕРАТ]) \ + + #context [ + #let pages = counter(page).final().at(0) + #let images = query(figure.where(kind: image)).len() + #let tables = query(figure.where(kind: table)).len() + #let bibs = bib-count.final().dedup().len() + /* TODO: why this stopped working? + #let tables = counter(figure.where(kind: table)).final().at(0) + #let images = counter(figure.where(kind: image)).final().at(0)*/ + + #let counters = () + + #if pages != 0 { counters.push[#pages с.] } + #if tables != 0 { counters.push[#tables табл.] } + #if images != 0 { counters.push[#images рис.] } + #if bibs != 0 { counters.push[#bibs джерел] } + + Пояснювальна записка до курсової роботи: #counters.join(", "). + ] + + \ + + #( + abstract + .keywords + .map(upper) + .sorted(by: (a, b) => { + if is-cyr(a) != is-cyr(b) { true } else { a < b } + }) + .join(", ") + ) + + \ + #abstract.text + ] + + // page 5 {{{2 + outline( + title: [ + ЗМІСТ + #v(spacing * 2, weak: true) + ], + depth: 2, + indent: auto, + ) +} diff --git a/src/title-pages/main.typ b/src/title-pages/main.typ new file mode 100644 index 0000000..f28c162 --- /dev/null +++ b/src/title-pages/main.typ @@ -0,0 +1,2 @@ +#import "pz-lb/main.typ" as pz-lb +#import "coursework/main.typ" as cw diff --git a/src/title-pages/pz-lb/complex.typ b/src/title-pages/pz-lb/complex.typ new file mode 100644 index 0000000..1ab6309 --- /dev/null +++ b/src/title-pages/pz-lb/complex.typ @@ -0,0 +1,60 @@ +#import "../../helpers.typ": * +#let complex(uni, edu-program, subject, type, number, title, authors, mentors) = { + align(center)[ + Міністерство освіти і науки України \ + #uni.name + + \ + #set par(first-line-indent: 0pt) + #align(left)[ + #let edu = uni.edu-programs.at(edu-program) + Кафедра #underline(edu.department-gen) \ + Спеціальність #underline([#edu.code #edu.description]) \ + Освітня програма #underline(edu.name-long) + ] + + \ \ + + #pz-lb-title(type, number: number) + + з навчальної дисципліни "#uni.subjects.at(subject, default: subject)"\ + #if title != none [з теми "#eval(title, mode: "markup")"\ ] + #if authors.first().variant != none [\ Варіант №#authors.first().variant\ ] + + \ \ \ + + #align(right)[ + #if authors.len() == 1 { + let a = authors.first() + [#gender-form("author", dict: a):\ + студент групи #a.edu-program\-#a.group\ #a.name\ ] + text(size: 8pt, [(прізвище та ініціали)\ ]) + } else if authors.len() > 1 [ + #gender-verb("author"):\ + #for a in authors [студент групи #a.edu-program\-#a.group\ #a.name\ ] + #text(size: 8pt, [(прізвище та ініціали)\ ]) + ] + + \ + + #if mentors.len() == 1 { + let m = mentors.first() + [#gender-form("mentor", dict: m):\ ] + degree-get(m) + [#m.name\ ] + text(size: 8pt, [(прізвище та ініціали)\ ]) + } else if mentors.len() > 1 [ + #gender-verb("mentor"):\ + #for m in mentors { + degree-get(m) + [#m.name\ ] + } + ] + ] + + #v(1fr) + + Харків\ + #datetime.today().display("[year]") + ] +} diff --git a/src/title-pages/pz-lb/main.typ b/src/title-pages/pz-lb/main.typ new file mode 100644 index 0000000..80af7e9 --- /dev/null +++ b/src/title-pages/pz-lb/main.typ @@ -0,0 +1,2 @@ +#import "complex.typ": * +#import "nure.typ": * diff --git a/src/title-pages/pz-lb/nure.typ b/src/title-pages/pz-lb/nure.typ new file mode 100644 index 0000000..40d5abd --- /dev/null +++ b/src/title-pages/pz-lb/nure.typ @@ -0,0 +1,52 @@ +#import "../../helpers.typ": * +#let nure(uni, edu-program, subject, type, number, title, authors, mentors) = { + align(center)[ + #upper([Міністерство освіти і науки України\ #uni.name]) + + \ \ + Кафедра #uni.edu-programs.at(edu-program).department-gen + + \ \ \ + #pz-lb-title(type, number: number) + + з дисципліни: "#uni.subjects.at(subject, default: subject)" + #if title != none [\ з теми: "#eval(title, mode: "markup")"] + + \ \ \ \ + #columns(2)[ + #set align(left) + #set par(first-line-indent: 0pt) + + #if authors.len() == 1 { + let a = authors.first() + [#gender-form("author", dict: a):\ ] + [ст. гр. #a.edu-program\-#a.group\ ] + [#a.name\ ] + if not is-empty(a.variant) [Варіант: №#a.variant] + } else if authors.len() > 1 [ + #gender-form("author"):\ + #for a in authors [ст. гр. #a.edu-program\-#a.group\ #a.name\ ] + ] + + #colbreak() + #set align(right) + + #if mentors.len() == 1 { + let m = mentors.first() + [#gender-form("mentor", dict: m):\ ] + degree-get(m) + [#m.name\ ] + } else if mentors.len() > 1 [ + #gender-form("mentor"):\ + #for m in mentors { + degree-get(m) + [#m.name\ ] + } + ] + ] + + #v(1fr) + + Харків -- #datetime.today().display("[year]") + ] +} diff --git a/src/utils.typ b/src/utils.typ new file mode 100644 index 0000000..1b81922 --- /dev/null +++ b/src/utils.typ @@ -0,0 +1,56 @@ +/// bold text +#let bold(content) = text(weight: "bold")[#content] + +/// numberless heading +#let nheading(title) = heading(depth: 1, numbering: none, title) + +/// fill horizontal space with a filled box +#let hfill(width) = box(width: width, repeat(" ")) // HAIR SPACE (U+200A) + +/// underlined cell with centered content by default +#let uline(align: center, content) = underline[ + #if align != left { hfill(1fr) } + #content + #if align != right { hfill(1fr) } +] + +/// Extract filename stem without extension +#let stem(path) = path.split("/").last().split(".").first() + +/// Extract parent directory name +#let parent-dir(path) = path.split("/").at(-2, default: "") + +/// Generate label from image path: +/// - "image.png" → "image" +/// - "img/foo/bar.png" → "foo_bar" +#let img-label(path) = { + let name = stem(path) + let parent = parent-dir(path) + + // If parent exists and name doesn't start with parent name, combine them + let base = if parent != "" and not name.starts-with(parent) { + parent + "_" + name + } else { + name + } + + label(base.replace(" ", "_")) +} + +/// Format image caption based on optional source +#let img-caption(base-caption, source) = { + if source == none { + base-caption + " (рисунок виконано самостійно)" + } else if source == () or source == "" { + base-caption + } else { + base-caption + " (за даними " + source + ")" + } +} + +/// captioned image with auto-generated label from path +/// Usage: img("path/to/image.png", "Caption")(optional: "source") +#let img(path, caption, ..sink) = { + let source = sink.pos().at(0, default: ()) + [ #figure(image(path, ..sink.named()), caption: utils.img-caption(caption, source)) #utils.img-label(path) ] +} diff --git a/template/bibl.yml b/template/bibl.yml index 745471e..a05e699 100644 --- a/template/bibl.yml +++ b/template/bibl.yml @@ -6,7 +6,6 @@ go: value: https://go.dev/ date: 2024-12-10 - htmx: type: Web title: Htmx - high power tools for html diff --git a/template/coursework.typ b/template/coursework.typ index f310f1c..b286a40 100644 --- a/template/coursework.typ +++ b/template/coursework.typ @@ -1,16 +1,19 @@ -#import "lib.typ": * +#import "@local/nure:0.1.0": * +#import style: spacing + +#import "utils.typ": img #let authors = ( ( name: "Ситник Є. С.", - full_name_gen: "Ситника Єгора Сергійовича", - edu_program: "ПЗПІ", + full-name-gen: "Ситника Єгора Сергійовича", + edu-program: "ПЗПІ", group: "23-2", gender: "m", course: 2, semester: 3, variant: 13, - ) + ), ) #let mentors = ( @@ -19,54 +22,39 @@ (name: "Широкопетлєва М. С.", degree: "Ст. викл. каф. ПІ"), ) -#let task_list = ( - done_date: datetime(year: 2024, month: 12, day: 27), - initial_date: datetime(year: 2024, month: 9, day: 15), +#let task-list = ( + done-date: datetime(year: 2024, month: 12, day: 27), + initial-date: datetime(year: 2024, month: 9, day: 15), source: "методичні вказівки до виконання курсової роботи, вимоги до інформаційної системи, предметна область, що пов’язана з управлінням класом та класним керівництвом.", content: "вступ, аналіз предметної області; постановка задачі; проектування бази даних; опис програми; висновки; перелік джерел посилання.", graphics: "загальна діаграма класів, ER-діаграма, UML-діаграми, DFD-діаграма, схема БД в 1НФ, 2НФ, 3НФ, копії екранів (“скриншоти”) прикладної програми, приклади звітів прикладної програми.", ) -#let calendar_plan = ( - plan_table: table( +#let calendar-plan = ( + plan-table: table( columns: 4, align: (center, left, center, center), - [Номер], - [Назва етапів курсової роботи], - [Строк виконання етапів роботи], - [Примітки], + [Номер], [Назва етапів курсової роботи], [Строк виконання етапів роботи], [Примітки], [1], [Аналіз предметної області], [15.09.24 – 24.09.24], [Виконано], [2], [Концептуальне моделювання], [24.09.24-30.09.24], [~], [2], [Постановка задачі], [28.09.24 – 2.10.24], [Виконано], [3], [Побудова ER-діаграми та схеми БД], [2.10.24 – 18.10.24], [Виконано], - [4], - [Оформлення розділів 1, 2 та 3.1, 3.2 пояснювальної записки], - [10.10.24 - 18.10.24], - [Виконано], + [4], [Оформлення розділів 1, 2 та 3.1, 3.2 пояснювальної записки], [10.10.24 - 18.10.24], [Виконано], [5], [Перша контрольна точка з курсової роботи], [20.10.24], [Виконано], [6], [Нормалізація бази даних], [20.10.24 - 15.11.24], [Виконано], [7], [Створення програми], [20.10.24 – 20.11.24], [Виконано], - [8], - [Тестування програми, наповнення бази даних], - [20.11.24 - 5.12.24], - [Виконано], + [8], [Тестування програми, наповнення бази даних], [20.11.24 - 5.12.24], [Виконано], [9], [Друга контрольна точка з курсової роботи], [7.12.24], [Виконано], - [10], - [Реалізація остаточної версії програми], - [7.12.24-15.12.24], - [Виконано], + [10], [Реалізація остаточної версії програми], [7.12.24-15.12.24], [Виконано], - [11], - [Оформлення інших розділів пояснювальної записки], - [1.11.24 – 25.12.24], - [Виконано], + [11], [Оформлення інших розділів пояснювальної записки], [1.11.24 – 25.12.24], [Виконано], [12], [Третя контрольна точка з курсової роботи], [27.12.24], [Виконано], ), - approval_date: datetime(year: 2024, month: 12, day: 27), + approval-date: datetime(year: 2024, month: 12, day: 27), ) #let abstract = ( @@ -110,7 +98,7 @@ #v(-spacing) == Частина 1 #lorem(100) - == Частина2 + == Частина 2 #lorem(200) = Приклад звіту 2 @@ -125,10 +113,10 @@ subject: "БД", authors: authors, mentors: mentors, - task_list: task_list, - calendar_plan: calendar_plan, + task-list: task-list, + calendar-plan: calendar-plan, abstract: abstract, - bib_path: "bibl.yml", // NOTE: use `bytes("bibl.yml")` as typst looks in template dir when using just filename + bib-path: bytes(read("bibl.yml")), // NOTE: use `bytes("bibl.yml")` as typst looks in template dir when using just filename appendices: appendices, ) diff --git a/template/lab.typ b/template/lab.typ index 8234295..cedd86f 100644 --- a/template/lab.typ +++ b/template/lab.typ @@ -1,4 +1,7 @@ -#import "lib.typ": * +#import "@local/nure:0.1.1": * +#import "utils.typ": img + +#import style: spacing #show: pz-lb.with( university: "ХНУРЕ", @@ -13,8 +16,8 @@ authors: ( ( name: "Косач Л. П.", - full_name_gen: "Косач Лариси Петрівни", - edu_program: "ПЗПІ", + full-name-gen: "Косач Лариси Петрівни", + edu-program: "КУІБ", group: "23-2", gender: "f", course: 2, @@ -52,7 +55,7 @@ - #lorem(42); - #lorem(27). -#show: appendices-style +#show: style.appendices = Quote #link("https://youtu.be/bJQj1uKtnus")[ @@ -65,7 +68,7 @@ #v(-spacing) == Частина 1 #lorem(100) -== Частина2 +== Частина 2 #lorem(200) = Приклад звіту 2 diff --git a/template/utils.typ b/template/utils.typ new file mode 100644 index 0000000..3ace327 --- /dev/null +++ b/template/utils.typ @@ -0,0 +1,7 @@ +#import "@local/nure:0.1.1": utils +/// captioned image with auto-generated label from path +/// Usage: img("path/to/image.png", "Caption")(optional: "source") +#let img(path, caption, ..sink) = { + let source = sink.pos().at(0, default: ()) + [ #figure(image(path, ..sink.named()), caption: utils.img-caption(caption, source)) #utils.img-label(path) ] +} diff --git a/typst.toml b/typst.toml index c09a9d5..81aa0e0 100644 --- a/typst.toml +++ b/typst.toml @@ -1,7 +1,7 @@ [package] name = "nure" -version = "0.1.0" -entrypoint = "lib.typ" +version = "0.1.1" +entrypoint = "src/lib.typ" authors = ["linerds"] license = "GPL-3.0" description = "Typst NURE package"