1
0

Merge pull request 'version 0.1.0' (#12) from unexplrd/typst_nure_template:main into 0.1.0

Reviewed-on: pencelheimer/typst_nure_template#12
This commit is contained in:
2025-03-18 12:28:54 +02:00
8 changed files with 239 additions and 142 deletions

2
.gitignore vendored
View File

@ -1 +1 @@
template.pdf *.pdf

View File

@ -9,7 +9,7 @@ This project contains two template functions and some utilities for writing NURE
#### `pz-lb-template` - For Laboratory and Practical Works #### `pz-lb-template` - For Laboratory and Practical Works
This template: This template:
- Sets up document styles; - Sets up document styles;
- Formats the title page according to NURE guidelines. - Formats the title page according to NURE/DSTU guidelines.
#### `cw-template` - For Course Works #### `cw-template` - For Course Works
This template: This template:
@ -29,30 +29,42 @@ This template:
### As a local typst package ### As a local typst package
1. Clone this repository into ~/.local/share/typst/packages/: 1. Clone this repository into ~/.local/share/typst/packages/:
```bash ```bash
git clone https://gitea.linerds.us/pencelheimer/typst_nure_template.git ~/.local/share/typst/packages/local/nure/0.0.0 git clone -b 0.1.0 https://gitea.linerds.us/pencelheimer/typst_nure_template.git ~/.local/share/typst/packages/local/nure/0.1.0
``` ```
2. Init your project with Typst: 2. Init your project with Typst:
```bash ```bash
typst init @local/nure:0.0.0 project-name typst init @local/nure:0.1.0 project-name
``` ```
### As a file in your project ### As a standalone file
Include lib.typ in your project and utilize the provided functions: Copy `lib.typ` to your project's root directory.
### In your project
```typst ```typst
// Import the template // Import the template either from a local package...
#import "lib.typ": * #import "@local/nure:0.1.0": *
// ...or by importing a lib.typ directly
// #import "/lib.typ": *
// Setup the document // Setup the document
#show: lab-pz-template.with( #show: pz-lb-template.with(
title: "Some title", title: "Some title",
// etc: "and so on",
// ...
) )
// this template automatically inserts a `=title` // this template automatically inserts a `=title`
// Write your content // Write your content...
#v(-spacing) // remove spacing between headings #v(-spacing) // remove spacing between headings
== Purpose == Purpose
Some text Some text
// ...or include your modules
#include "src/intro.typ"
#include "src/chapter1.typ"
#include "src/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.
``` ```
### Notes: ### Notes:
@ -60,12 +72,28 @@ Some text
### Example Project Structure ### Example Project Structure
``` ```
project-folder/ project/
├── main.typ ├── main.typ -- for boilerplate code and importing everything
├── template.typ ├── config/
├── images/ │ ├── doc.yaml -- for things that don't change across works, i.e. author and mentor metadata
│ ├── figure1.png │ ├── universities.yaml -- for user-specific configuration, i.e. education programs and disciplines
── figure2.png ── ...
│ ├── ... ├── src/
├── ... │ ├── intro.typ
│ ├── chapter1.typ
│ ├── chapter2.typ
│ └── ...
├── figures/
│ ├── chapter1/
│ │ ├── figure1.png
│ │ ├── figure2.png
│ │ ├── figure3.png
│ │ └── ...
│ ├── chapter2/
│ │ ├── figure1.png
│ │ ├── figure2.png
│ │ ├── figure3.png
│ │ └── ...
│ └── ...
└── ...
``` ```

24
config/universities.yaml Normal file
View File

@ -0,0 +1,24 @@
ХНУРЕ:
name: "Харківський національний університет радіоелектроніки"
edu_programs:
ПЗПІ:
name_long: "Інженерія програмного забезпечення"
department_gen: "Програмної інженерії"
code: 121 # TODO = ПЗПІ is "F2" now
КУІБ:
name_long: "Управління інформаційною безпекою"
department_gen: "Інфокомунікацій"
code: 125
subjects:
БД: "Бази даних"
БЖД: "Безпека життєдіяльності"
ОІМ: "Основи IP-мереж"
"ОПНJ": "Основи програмування на Java"
ОС: "Операційні системи"
ОТК: "Основи теорії кіл"
ПП: "Проектний практикум"
ПРОГ: "Програмування"
СПМ: "Скриптові мови програмування"
УФМ: "Українське фахове мовлення"
Ф: "Філософія"
ФІЗ: "Фізика"

195
lib.typ
View File

@ -1,23 +1,7 @@
// Academic aliases {{{1 // Academic aliases {{{1
/// subject abbreviations to full names #let universities = yaml("config/universities.yaml")
#let subjects = (
"БД": "Бази даних",
"ОПНJ": "Основи програмування на Java",
"ОС": "Операційні системи",
"ПП": "Проектний практикум",
"СПМ": "Скриптові мови програмування",
"Ф": "Філософія",
)
/// education program abbreviations to name & number
#let edu_programs = (
"ПЗПІ": (
name: "Інженерія програмного забезпечення",
number: 121, // TODO: ПЗПІ is "F2" now
),
)
// Template formatting functions {{{1 // Template formatting functions {{{1
@ -25,7 +9,10 @@
#let nheading(title) = heading(depth: 1, numbering: none, title) #let nheading(title) = heading(depth: 1, numbering: none, title)
/// fill horizontal space with a box and not an empty space /// 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 #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 /// make underlined cell with filled value
#let uline(align: center, content) = underline[ #let uline(align: center, content) = underline[
@ -68,7 +55,9 @@
#let img(path, caption, ..sink) = { #let img(path, caption, ..sink) = {
let parts = path.split(".").first().split("/") let parts = path.split(".").first().split("/")
let label_string = if parts.len() <= 2 or parts.at(-1).starts-with(parts.at(-2)) { let label_string = if (
parts.len() <= 2 or parts.at(-1).starts-with(parts.at(-2))
) {
// ("image",), (_, "image") and (.., "img", "img_image") // ("image",), (_, "image") and (.., "img", "img_image")
parts.last() parts.last()
} else { } else {
@ -84,7 +73,10 @@
[#caption (за даними #sink.pos().first())] [#caption (за даними #sink.pos().first())]
} }
[#figure(image(path, ..sink.named()), caption: caption) #label(label_string)] [#figure(
image(path, ..sink.named()),
caption: caption,
) #label(label_string)]
} }
// Styling {{{1 // Styling {{{1
@ -107,7 +99,12 @@
}, },
) )
set text(font: ("Times New Roman", "Liberation Serif"), size: 14pt, hyphenate: false, lang: "uk") 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 par(justify: true, first-line-indent: (amount: 1.25cm, all: true))
set underline(evade: false) set underline(evade: false)
@ -117,7 +114,11 @@
set par(leading: spacing) set par(leading: spacing)
// enums and lists {{{2 // enums and lists {{{2
set enum(numbering: i => { ua_alpha_numbering.at(i) + ")" }, indent: 1.25cm, body-indent: 0.5cm) set enum(
numbering: i => { ua_alpha_numbering.at(i) + ")" },
indent: 1.25cm,
body-indent: 0.5cm,
)
show enum: it => { show enum: it => {
set enum(indent: 0em, numbering: "1)") set enum(indent: 0em, numbering: "1)")
it it
@ -144,8 +145,20 @@
counter(figure.where(kind: raw)).update(0) counter(figure.where(kind: raw)).update(0)
it it
} }
set math.equation(numbering: (..num) => numbering("(1.1)", counter(heading).get().at(0), num.pos().first())) set math.equation(
set figure(numbering: (..num) => numbering("1.1", counter(heading).get().at(0), num.pos().first())) 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 // appearance of references to images and tables {{{2
set ref( set ref(
@ -217,7 +230,11 @@
spacing: new_spacing, spacing: new_spacing,
leading: new_spacing, leading: new_spacing,
) )
set text(size: 11pt, font: "Courier New", weight: "semibold") set text(
size: 11pt,
font: ("Iosevka NFM", "Courier New"),
weight: "semibold",
)
v(spacing * 2.5, weak: true) v(spacing * 2.5, weak: true)
pad(it, left: 1.25cm) pad(it, left: 1.25cm)
@ -233,11 +250,10 @@
/// -> content /// -> content
/// - doc (content): Content to apply the template to. /// - doc (content): Content to apply the template to.
/// - title (str): Title of the document. /// - title (str): Title of the document.
/// - subject_shorthand (str): Subject short name. /// - subject (str): Subject short name.
/// - department_gen (str): Department name in genitive form. /// - authors ((name: str, full_name_gen: str, variant: int, course: int, semester: int, group: str, gender: str),): List of authors.
/// - authors ((name: str, full_name_gen: str, variant: int, group: str, gender: str),): List of Authors dicts. /// - mentors ((name: str, degree: str),): List of mentors.
/// - mentors ((name: str, gender: str, degree: str),): List of mentors dicts. /// - edu_program (str): Education program shorthand.
/// - edu_program_shorthand (str): Education program shorthand.
/// - task_list (done_date: datetime, initial_date: datetime, source: (content | str), content: (content | str), graphics: (content | str)): Task list object. /// - 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. /// - calendar_plan ( plan_table: (content | str), approval_date: datetime): Calendar plan object.
/// - abstract (keywords: (str, ), text: (content | str)): Abstract object. /// - abstract (keywords: (str, ), text: (content | str)): Abstract object.
@ -245,16 +261,16 @@
/// - appendices (content): Content with appendices. /// - appendices (content): Content with appendices.
#let cw-template( #let cw-template(
doc, doc,
title: "NONE", title: none,
subject_shorthand: "NONE", subject: none,
department_gen: "Програмної інженерії", university: "ХНУРЕ",
author: (), author: (),
mentors: (), mentors: (),
edu_program_shorthand: "ПЗПІ", edu_program: none,
task_list: (), task_list: (),
calendar_plan: (), calendar_plan: (),
abstract: (), abstract: (),
bib_path: "bibl.yml", bib_path: none,
appendices: (), appendices: (),
) = { ) = {
set document(title: title, author: author.name) set document(title: title, author: author.name)
@ -273,27 +289,24 @@
let head_mentor = mentors.at(0) let head_mentor = mentors.at(0)
let edu_program = edu_programs.at(edu_program_shorthand) let uni = universities.at(university)
let edu_prog = uni.edu_programs.at(edu_program)
// page 1 {{{2 // page 1 {{{2
[ [
#set align(center) #set align(center)
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ\
#upper(uni.name)
ХАРКІВСЬКИЙ НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ РАДІОЕЛЕКТРОНІКИ
\ \
Кафедра Програмної інженерії Кафедра #edu_prog.department_gen
\ \
ПОЯСНЮВАЛЬНА ЗАПИСКА ПОЯСНЮВАЛЬНА ЗАПИСКА\
ДО КУРСОВОЇ РОБОТИ\
ДО КУРСОВОЇ РОБОТИ з дисципліни: "#uni.subjects.at(subject, default: "NONE")"\
з дисципліни: "#subjects.at(subject_shorthand, default: "NONE")"
Тема роботи: "#title" Тема роботи: "#title"
\ \ \ \ \ \
@ -301,7 +314,7 @@
#columns(2, gutter: 4cm)[ #columns(2, gutter: 4cm)[
#set align(left) #set align(left)
#if author.gender == "m" { [Виконав\ ] } else { [Виконала\ ] } ст. гр. #author.group #if author.gender == "m" { [Виконав\ ] } else { [Виконала\ ] } ст. гр. #edu_program\-#author.group
\ \
Керівник:\ Керівник:\
@ -345,7 +358,7 @@
// page 2 {{{2 // page 2 {{{2
{ {
uline[Харківський національний університет радіоелектроніки] uline[#uni.name]
linebreak() linebreak()
linebreak() linebreak()
@ -358,17 +371,19 @@
Спеціальність Спеціальність
], ],
{ {
uline(align: left, department_gen) uline(align: left, edu_prog.department_gen)
linebreak() linebreak()
uline(align: left, subjects.at(subject_shorthand)) uline(align: left, uni.subjects.at(subject))
linebreak() linebreak()
uline(align: left, [#edu_program.number #edu_program.name]) uline(align: left, [#edu_prog.code #edu_prog.name_long])
}, },
) )
grid( grid(
columns: (1fr, 1fr, 1fr), columns: (1fr, 1fr, 1fr),
gutter: 0.3fr, gutter: 0.3fr,
[#bold[Курс] #uline(2)], [#bold[Група] #uline(author.group)], [#bold[Семестр] #uline(3)], [#bold[Курс] #uline(author.course)],
[#bold[Група] #uline([#edu_program\-#author.group])],
[#bold[Семестр] #uline(author.semester)],
) )
linebreak() linebreak()
@ -495,12 +510,17 @@
for i in range(n) { for i in range(n) {
for j in range(0, n - i - 1) { for j in range(0, n - i - 1) {
if ( if (
(not is_cyrillic(keywords.at(j)) and is_cyrillic(keywords.at(j + 1))) (
not is_cyrillic(keywords.at(j)) and is_cyrillic(keywords.at(j + 1))
)
or ( or (
is_cyrillic(keywords.at(j)) == is_cyrillic(keywords.at(j + 1)) and keywords.at(j) > keywords.at(j + 1) 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.at(j), keywords.at(j + 1)) = (
keywords.at(j + 1),
keywords.at(j),
)
} }
} }
} }
@ -626,43 +646,47 @@
/// -> content /// -> content
/// - doc (content): Content to apply the template to. /// - doc (content): Content to apply the template to.
/// - doctype ("ЛБ" | "ПЗ"): Document type. /// - doctype ("ЛБ" | "ПЗ"): Document type.
/// - edu_program (str): Education program shorthand.
/// - title (str): Title of the document. /// - title (str): Title of the document.
/// - subject_shorthand (str): Subject short name. /// - subject (str): Subject shorthand.
/// - department_gen (str): Department name in genitive form. /// - authors ((name: str, full_name_gen: str, group: str, gender: str, variant: int or none),): List of authors.
/// - worknumber (int): Number of the work, can be omitted. /// - mentors ((name: str, degree: str, gender: str or none),): List of mentors.
/// - authors ((name: str, full_name_gen: str, variant: int, group: str, gender: str),): List of Authors dicts. /// - worknumber (int or none): Number of the work. Optional.
/// - mentor (name: str, gender: str, degree: str): Mentors objects. #let pz-lb-template(
#let lab-pz-template(
doc, doc,
doctype: "NONE", doctype: none,
title: "NONE", university: "ХНУРЕ",
subject_shorthand: "NONE", edu_program: none,
department_gen: "Програмної інженерії", title: none,
worknumber: 1, subject: none,
worknumber: none,
authors: (), authors: (),
mentor: (), mentors: (),
) = { ) = {
set document(title: title, author: authors.at(0).name) set document(title: title, author: authors.at(0).name)
show: style show: style
context counter(heading).update(worknumber - 1) let uni = universities.at(university)
let edu_prog = uni.edu_programs.at(edu_program)
// page 1 {{{2 // page 1 {{{2
align(center)[ align(center)[
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ \ МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ \
ХАРКІВСЬКИЙ НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ РАДІОЕЛЕКТРОНІКИ #upper(uni.name)
\ \ \ \
Кафедра #department_gen Кафедра #edu_prog.department_gen
\ \ \ \ \ \
Звіт \ Звіт \
з з
#if doctype == "ЛБ" [лабораторної роботи] else [практичної роботи] #if doctype == "ЛБ" [лабораторної роботи] else [практичної роботи]
#if worknumber != none [ #worknumber] #if worknumber != none {
context counter(heading).update(worknumber - 1)
[№#worknumber]
}
з дисципліни: "#subjects.at(subject_shorthand, default: "UNLNOWN SUBJECT, PLEASE OPEN AN ISSUE")" з дисципліни: "#uni.subjects.at(subject, default: "UNKNOWN SUBJECT, PLEASE OPEN AN ISSUE")"
з теми: "#title" з теми: "#title"
@ -675,22 +699,37 @@
let author = authors.at(0) let author = authors.at(0)
if author.gender == "m" [Виконав:\ ] else [Виконала:\ ] if author.gender == "m" [Виконав:\ ] else [Виконала:\ ]
[ [
ст. гр. #author.group\ ст. гр. #edu_program\-#author.group\
#author.name\ #author.name\
] ]
if author.variant != none [Варіант: #author.variant] if author.variant != none [Варіант: #author.variant]
} else [ } else [
Виконали:\ Виконали:\
ст. гр. #authors.at(0).group\ ст. гр. #edu_program\-#authors.at(0).group\
#authors.map(a => [ #a.name\ ]) #for author in authors [#author.name\ ]
] ]
#colbreak() #colbreak()
#set align(right) #set align(right)
#if mentor.gender == "m" { [Перевірив:\ ] } else { [Перевірила:\ ] } #if mentors.len() == 1 {
#mentor.degree #if mentor.degree.len() >= 15 [\ ] let mentor = mentors.at(0)
#mentor.name\ 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) #v(1fr)

22
template/config/doc.yaml Normal file
View File

@ -0,0 +1,22 @@
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

View File

@ -1,29 +1,19 @@
#import "@local/nure:0.0.0": * #import "@local/nure:0.1.0": *
#let author = ( #let author = (
name: "Ситник Є. С.", name: "Ситник Є. С.",
full_name_gen: "Ситника Єгора Сергійовича", full_name_gen: "Ситника Єгора Сергійовича",
course: 2,
semester: 3,
variant: 13, variant: 13,
group: "ПЗПІ-23-2", group: "23-2",
gender: "m", gender: "m",
) )
#let mentors = ( #let mentors = (
( (name: "Черепанова Ю. Ю.", degree: "Ст. викл. каф. ПІ"),
name: "Черепанова Ю. Ю.", (name: "Русакова Н. Є.", degree: "Доц. каф. ПІ"),
gender: "f", (name: "Широкопетлєва М. С.", degree: "Ст. викл. каф. ПІ"),
degree: "Ст. викл. каф. ПІ",
),
(
name: "Русакова Н. Є.",
gender: "f",
degree: "Доц. каф. ПІ",
),
(
name: "Широкопетлєва М. С.",
gender: "f",
degree: "Ст. викл. каф. ПІ",
),
) )
#let task_list = ( #let task_list = (
@ -69,11 +59,26 @@
"SQL", "SQL",
), ),
text: [ text: [
Мета даної роботи -- проєктування та розробка інформаційної системи «Помічник класного керівника. Керування класом», яка спрямована на автоматизацію процесів управління класом, облік даних про учнів, планування та аналіз навчального процесу. Основна задача інформаційної системи спростити роботу класного керівника, забезпечити ефективну організацію документації та взаємодію з учасниками освітнього процесу. Мета даної роботи -- проєктування та розробка інформаційної системи «Помічник
класного керівника. Керування класом», яка спрямована на автоматизацію процесів
управління класом, облік даних про учнів, планування та аналіз навчального
процесу. Основна задача інформаційної системи спростити роботу класного
керівника, забезпечити ефективну організацію документації та взаємодію з
учасниками освітнього процесу.
Для реалізації системи було використано сучасний стек технологій, а саме: Go -- як основна мова програмування для створення серверної логіки, HTMX -- для динамічного оновлення інтерфейсу без використання складних фреймворків, MySQL -- як СУБД для зберігання даних про учнів, їх оцінки та розклад, Neovim -- як середовище для швидкої та ефективної розробки коду, Go Echo -- веб-фреймворк для створення REST API, Go SQLx -- бібліотека для роботи з базою даних, що забезпечує зручність і гнучкість. Для реалізації системи було використано сучасний стек технологій, а саме: Go --
як основна мова програмування для створення серверної логіки, HTMX -- для
динамічного оновлення інтерфейсу без використання складних фреймворків, MySQL --
як СУБД для зберігання даних про учнів, їх оцінки та розклад, Neovim -- як
середовище для швидкої та ефективної розробки коду, Go Echo -- веб-фреймворк для
створення REST API, Go SQLx -- бібліотека для роботи з базою даних, що
забезпечує зручність і гнучкість.
Результат роботи веб-додаток, який дозволяє обліковувати особисті дані учнів та їхніх опікунів, включаючи інформацію про успішність, відвідуваність та інші показники; планувати розклад занять; генерувати звіти про успішність учнів та переглядати різну статистику. Інтерфейс, створений з використанням HTMX, легко адаптується під потреби користувача. Результат роботи веб-додаток, який дозволяє обліковувати особисті дані учнів
та їхніх опікунів, включаючи інформацію про успішність, відвідуваність та інші
показники; планувати розклад занять; генерувати звіти про успішність учнів та
переглядати різну статистику. Інтерфейс, створений з використанням HTMX, легко
адаптується під потреби користувача.
], ],
) )
@ -94,9 +99,8 @@
#show: cw-template.with( #show: cw-template.with(
title: "Інформаційна система «Помічник класного керівника». Керування класом", title: "Інформаційна система «Помічник класного керівника». Керування класом",
subject_shorthand: "БД", subject_short: "БД",
department_gen: "Програмної інженерії", edu_program_short: "ПЗПІ",
edu_program_shorthand: "ПЗПІ",
author: author, author: author,
mentors: mentors, mentors: mentors,
task_list: task_list, task_list: task_list,

View File

@ -1,26 +1,6 @@
#import "@local/nure:0.0.0": * #import "@local/nure:0.1.0": *
#show: lab-pz-template.with( #show: pz-lb-template.with(..yaml("config/doc.yaml"))
doctype: "ЛБ",
title: "Інформаційна система «Помічник класного керівника». Керування класом",
subject_shorthand: "БД",
department_gen: "Програмної інженерії",
authors: (
(
name: "Ситник Є. С.",
full_name_gen: "Ситника Єгора Сергійовича",
variant: 13,
group: "ПЗПІ-23-2",
gender: "m",
),
),
mentor: (
name: "Черепанова Ю. Ю.",
gender: "f",
degree: "Ст. викл. каф. ПІ",
),
worknumber: 1,
)
#v(-spacing) #v(-spacing)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "nure" name = "nure"
version = "0.0.0" version = "0.1.0"
entrypoint = "lib.typ" entrypoint = "lib.typ"
authors = ["linerds"] authors = ["linerds"]
license = "GPL-3.0" license = "GPL-3.0"