Initial commit
This commit is contained in:
commit
c534d5ce80
163 files changed
+11500
No files matched your search
@@ -0,0 +1,77 @@
|
||||
// ==========================================
|
||||
// DOMAIN: ПРИЛОЖЕНИЯ
|
||||
// ==========================================
|
||||
#import "document.typ": fail
|
||||
|
||||
#let attachment(
|
||||
id,
|
||||
title,
|
||||
body,
|
||||
subtitle: none,
|
||||
number: auto,
|
||||
outlined: true,
|
||||
) = {
|
||||
if type(id) != str or id.trim() == "" {
|
||||
fail("attachment.id", "нужна непустая строка")
|
||||
}
|
||||
if type(title) not in (str, content) {
|
||||
fail("attachment.title", "ожидались str или content")
|
||||
}
|
||||
if type(body) not in (content, function) {
|
||||
fail("attachment.body", "ожидались content или function")
|
||||
}
|
||||
if subtitle != none and type(subtitle) not in (str, content) {
|
||||
fail("attachment.subtitle", "ожидались none, str или content")
|
||||
}
|
||||
(
|
||||
kind: "attachment",
|
||||
id: id,
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
body: body,
|
||||
number: number,
|
||||
outlined: outlined,
|
||||
)
|
||||
}
|
||||
|
||||
#let attachment-set(items: (), numbering: "arabic", start: 1) = {
|
||||
if type(items) != array {
|
||||
fail("attachments.items", "ожидался array")
|
||||
}
|
||||
if numbering not in ("arabic", "cyrillic", "none") {
|
||||
fail("attachments.numbering", "допустимы arabic, cyrillic, none")
|
||||
}
|
||||
if type(start) != int or start < 1 {
|
||||
fail("attachments.start", "нужно положительное целое число")
|
||||
}
|
||||
let seen = ()
|
||||
for item in items {
|
||||
if type(item) != dictionary or item.at("kind", default: none) != "attachment" {
|
||||
fail("attachments.items", "каждый элемент должен быть attachment")
|
||||
}
|
||||
if item.id in seen {
|
||||
fail("attachments.items", "повторяющийся id " + repr(item.id))
|
||||
}
|
||||
seen.push(item.id)
|
||||
}
|
||||
(
|
||||
kind: "attachment-set",
|
||||
items: items,
|
||||
numbering: numbering,
|
||||
start: start,
|
||||
)
|
||||
}
|
||||
|
||||
#let validate-attachment-set(value) = {
|
||||
if type(value) == array {
|
||||
attachment-set(items: value)
|
||||
} else if type(value) != dictionary or value.at("kind", default: none) != "attachment-set" {
|
||||
fail("attachments", "ожидались array или attachment-set")
|
||||
} else {
|
||||
attachment-set(
|
||||
items: value.at("items", default: ()),
|
||||
numbering: value.at("numbering", default: "arabic"),
|
||||
start: value.at("start", default: 1),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// ==========================================
|
||||
// DOMAIN: ПРОФИЛЬ ОРГАНИЗАЦИИ
|
||||
// ==========================================
|
||||
#import "document.typ": fail, ensure-dictionary
|
||||
|
||||
#let optional-text(value) = {
|
||||
if value == none {
|
||||
none
|
||||
} else if type(value) != str {
|
||||
value
|
||||
} else if value.trim() == "" {
|
||||
none
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#let fill-defaults(defaults, values) = {
|
||||
let result = defaults
|
||||
for (key, value) in values {
|
||||
result.insert(key, optional-text(value))
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#let company-profile(
|
||||
id,
|
||||
legal,
|
||||
contacts: (:),
|
||||
banking: (:),
|
||||
brand: (:),
|
||||
director: (:),
|
||||
resources: (:),
|
||||
) = {
|
||||
if type(id) != str or id.trim() == "" {
|
||||
fail("company.id", "нужна непустая строка")
|
||||
}
|
||||
let legal = fill-defaults(
|
||||
(
|
||||
name: none,
|
||||
short-name: none,
|
||||
jurisdiction: none,
|
||||
inn: none,
|
||||
kpp: none,
|
||||
ogrn: none,
|
||||
bin: none,
|
||||
kbe: none,
|
||||
),
|
||||
ensure-dictionary(legal, "company.legal"),
|
||||
)
|
||||
let contacts = fill-defaults(
|
||||
(
|
||||
email: none,
|
||||
website: none,
|
||||
phone: none,
|
||||
address: none,
|
||||
address-en: none,
|
||||
city: none,
|
||||
summary: none,
|
||||
),
|
||||
ensure-dictionary(contacts, "company.contacts"),
|
||||
)
|
||||
let banking = fill-defaults(
|
||||
(
|
||||
bank: none,
|
||||
account: none,
|
||||
correspondent-account: none,
|
||||
bik: none,
|
||||
iban: none,
|
||||
),
|
||||
ensure-dictionary(banking, "company.banking"),
|
||||
)
|
||||
let brand = fill-defaults(
|
||||
(color: rgb("e39f49"),),
|
||||
ensure-dictionary(brand, "company.brand"),
|
||||
)
|
||||
let director = fill-defaults(
|
||||
(title: none, name: none),
|
||||
ensure-dictionary(director, "company.director"),
|
||||
)
|
||||
let resources = fill-defaults(
|
||||
(logo: none, signature: none, stamp: none),
|
||||
ensure-dictionary(resources, "company.resources"),
|
||||
)
|
||||
|
||||
let profile = (
|
||||
kind: "company-profile",
|
||||
id: id,
|
||||
legal: legal,
|
||||
contacts: contacts,
|
||||
banking: banking,
|
||||
brand: brand,
|
||||
director: director,
|
||||
resources: resources,
|
||||
)
|
||||
profile
|
||||
}
|
||||
|
||||
#let validate-company(profile) = {
|
||||
let profile = ensure-dictionary(profile, "company")
|
||||
if profile.at("kind", default: none) != "company-profile" {
|
||||
fail("company.kind", "ожидался company-profile")
|
||||
}
|
||||
for key in ("legal", "contacts", "banking", "brand", "director", "resources") {
|
||||
if key not in profile or type(profile.at(key)) != dictionary {
|
||||
fail("company." + key, "ожидался dictionary")
|
||||
}
|
||||
}
|
||||
let name = profile.legal.at("name", default: none)
|
||||
if type(name) != str or name.trim() == "" {
|
||||
fail("company.legal.name", "полное наименование обязательно")
|
||||
}
|
||||
let jurisdiction = profile.legal.at("jurisdiction", default: none)
|
||||
if type(jurisdiction) != str or jurisdiction.trim() == "" {
|
||||
fail("company.legal.jurisdiction", "юрисдикция обязательна")
|
||||
}
|
||||
for key in ("title", "name") {
|
||||
let value = profile.director.at(key, default: none)
|
||||
if type(value) != str or value.trim() == "" {
|
||||
fail("company.director." + key, "поле обязательно")
|
||||
}
|
||||
}
|
||||
if type(profile.brand.color) != color {
|
||||
fail("company.brand.color", "ожидался color")
|
||||
}
|
||||
for key in ("logo", "signature", "stamp") {
|
||||
let resource = profile.resources.at(key, default: none)
|
||||
if resource != none and type(resource) not in (path, content) {
|
||||
fail("company.resources." + key, "ожидались none, path или content")
|
||||
}
|
||||
}
|
||||
profile
|
||||
}
|
||||
|
||||
#let company-display-name(profile, short: false) = {
|
||||
let profile = validate-company(profile)
|
||||
if short {
|
||||
profile.legal.at("short-name", default: profile.legal.name)
|
||||
} else {
|
||||
profile.legal.name
|
||||
}
|
||||
}
|
||||
|
||||
#let company-resource(profile, key) = {
|
||||
validate-company(profile).resources.at(key, default: none)
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// ==========================================
|
||||
// DOMAIN: ДОКУМЕНТ И КОНТРАКТ ПРОФИЛЯ
|
||||
// ==========================================
|
||||
|
||||
#let fail(scope, message) = panic("Scientia / " + scope + ": " + message)
|
||||
|
||||
#let ensure-dictionary(value, scope) = {
|
||||
if type(value) != dictionary {
|
||||
fail(scope, "ожидался dictionary, получено " + repr(type(value)))
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
#let merge-known(defaults, overrides, scope: "options", allow-extra: false) = {
|
||||
let defaults = ensure-dictionary(defaults, scope + ".defaults")
|
||||
let overrides = ensure-dictionary(overrides, scope)
|
||||
let result = defaults
|
||||
for (key, value) in overrides {
|
||||
if not allow-extra and key not in defaults {
|
||||
fail(scope, "неизвестный параметр " + repr(key))
|
||||
}
|
||||
result.insert(key, value)
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#let render-options(options: (:)) = {
|
||||
let resolved = merge-known(
|
||||
(
|
||||
mode: "final",
|
||||
watermark: none,
|
||||
media-policy: "placeholder",
|
||||
diagnostics: true,
|
||||
),
|
||||
options,
|
||||
scope: "render-options",
|
||||
)
|
||||
|
||||
if resolved.mode not in ("final", "draft", "clean-copy") {
|
||||
fail("render-options.mode", "допустимы final, draft, clean-copy")
|
||||
}
|
||||
if resolved.at("media-policy") not in ("hide", "placeholder", "reserve-space") {
|
||||
fail("render-options.media-policy", "допустимы hide, placeholder, reserve-space")
|
||||
}
|
||||
resolved
|
||||
}
|
||||
|
||||
#let document-profile(
|
||||
id,
|
||||
render,
|
||||
metadata: (:),
|
||||
normalize: value => value,
|
||||
validate: value => value,
|
||||
) = {
|
||||
if type(id) != str or id.trim() == "" {
|
||||
fail("document-profile.id", "нужна непустая строка")
|
||||
}
|
||||
if type(metadata) != dictionary {
|
||||
fail("document-profile.metadata", "ожидался dictionary")
|
||||
}
|
||||
if type(normalize) != function {
|
||||
fail("document-profile.normalize", "ожидалась function")
|
||||
}
|
||||
if type(validate) != function {
|
||||
fail("document-profile.validate", "ожидалась function")
|
||||
}
|
||||
if type(render) != function {
|
||||
fail("document-profile.render", "ожидалась function")
|
||||
}
|
||||
(
|
||||
kind: "document-profile",
|
||||
id: id,
|
||||
metadata: metadata,
|
||||
normalize: normalize,
|
||||
validate: validate,
|
||||
render: render,
|
||||
)
|
||||
}
|
||||
|
||||
#let validate-profile-contract(profile) = {
|
||||
let profile = ensure-dictionary(profile, "profile")
|
||||
if profile.at("kind", default: none) != "document-profile" {
|
||||
fail("profile.kind", "ожидался document-profile")
|
||||
}
|
||||
for key in ("id", "metadata", "normalize", "validate", "render") {
|
||||
if key not in profile {
|
||||
fail("profile", "отсутствует обязательное поле " + repr(key))
|
||||
}
|
||||
}
|
||||
if type(profile.normalize) != function or type(profile.validate) != function or type(profile.render) != function {
|
||||
fail("profile", "normalize, validate и render должны быть functions")
|
||||
}
|
||||
profile
|
||||
}
|
||||
|
||||
#let document-context(
|
||||
company,
|
||||
profile,
|
||||
metadata,
|
||||
options,
|
||||
parties: (),
|
||||
attachments: (),
|
||||
bibliographies: (),
|
||||
) = {
|
||||
(
|
||||
kind: "document-context",
|
||||
company: company,
|
||||
profile-id: profile.id,
|
||||
metadata: ensure-dictionary(metadata, "document-context.metadata"),
|
||||
options: render-options(options: options),
|
||||
parties: parties,
|
||||
attachments: attachments,
|
||||
bibliographies: bibliographies,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// ==========================================
|
||||
// DOMAIN: СТОРОНЫ, АДРЕСАТЫ И ПОДПИСАНТЫ
|
||||
// ==========================================
|
||||
#import "document.typ": fail, ensure-dictionary
|
||||
|
||||
#let normalize-text(value, scope, required: false) = {
|
||||
if value == none {
|
||||
if required { fail(scope, "поле обязательно") }
|
||||
none
|
||||
} else if type(value) != str {
|
||||
fail(scope, "ожидалась строка")
|
||||
} else if value.trim() == "" {
|
||||
if required { fail(scope, "поле не может быть пустым") }
|
||||
none
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#let signer(
|
||||
name,
|
||||
title,
|
||||
basis: none,
|
||||
signature: none,
|
||||
stamp: none,
|
||||
) = {
|
||||
for (key, resource) in (("signature", signature), ("stamp", stamp)) {
|
||||
if resource != none and type(resource) not in (path, content) {
|
||||
fail("signer." + key, "ожидались none, path или content")
|
||||
}
|
||||
}
|
||||
(
|
||||
kind: "signer",
|
||||
name: normalize-text(name, "signer.name", required: true),
|
||||
title: normalize-text(title, "signer.title", required: true),
|
||||
basis: normalize-text(basis, "signer.basis"),
|
||||
signature: signature,
|
||||
stamp: stamp,
|
||||
)
|
||||
}
|
||||
|
||||
#let recipient(
|
||||
company: none,
|
||||
title: none,
|
||||
name: none,
|
||||
address: none,
|
||||
) = {
|
||||
let resolved = (
|
||||
kind: "recipient",
|
||||
company: normalize-text(company, "recipient.company"),
|
||||
title: normalize-text(title, "recipient.title"),
|
||||
name: normalize-text(name, "recipient.name"),
|
||||
address: normalize-text(address, "recipient.address"),
|
||||
)
|
||||
if resolved.company == none and resolved.name == none {
|
||||
fail("recipient", "нужно указать организацию или имя адресата")
|
||||
}
|
||||
resolved
|
||||
}
|
||||
|
||||
#let party(
|
||||
id,
|
||||
role,
|
||||
name,
|
||||
legal: (:),
|
||||
contacts: (:),
|
||||
banking: (:),
|
||||
representative: none,
|
||||
) = {
|
||||
if representative != none and (
|
||||
type(representative) != dictionary
|
||||
or representative.at("kind", default: none) != "signer"
|
||||
) {
|
||||
fail("party.representative", "ожидались none или signer")
|
||||
}
|
||||
(
|
||||
kind: "party",
|
||||
id: normalize-text(id, "party.id", required: true),
|
||||
role: normalize-text(role, "party.role", required: true),
|
||||
name: normalize-text(name, "party.name", required: true),
|
||||
legal: ensure-dictionary(legal, "party.legal"),
|
||||
contacts: ensure-dictionary(contacts, "party.contacts"),
|
||||
banking: ensure-dictionary(banking, "party.banking"),
|
||||
representative: representative,
|
||||
)
|
||||
}
|
||||
|
||||
#let approval(signer, date: none, label: "УТВЕРЖДАЮ") = {
|
||||
if type(signer) != dictionary or signer.at("kind", default: none) != "signer" {
|
||||
fail("approval.signer", "ожидался signer")
|
||||
}
|
||||
(
|
||||
kind: "approval",
|
||||
signer: signer,
|
||||
date: normalize-text(date, "approval.date"),
|
||||
label: normalize-text(label, "approval.label", required: true),
|
||||
)
|
||||
}
|
||||
|
||||
#let validate-parties(parties, minimum: 0) = {
|
||||
if type(parties) != array {
|
||||
fail("parties", "ожидался array")
|
||||
}
|
||||
if parties.len() < minimum {
|
||||
fail("parties", "нужно не менее " + str(minimum) + " сторон")
|
||||
}
|
||||
let seen = ()
|
||||
for item in parties {
|
||||
if type(item) != dictionary or item.at("kind", default: none) != "party" {
|
||||
fail("parties", "каждый элемент должен быть party")
|
||||
}
|
||||
if item.id in seen {
|
||||
fail("parties", "повторяющийся id " + repr(item.id))
|
||||
}
|
||||
if item.representative != none and (
|
||||
type(item.representative) != dictionary
|
||||
or item.representative.at("kind", default: none) != "signer"
|
||||
) {
|
||||
fail("parties", "representative должен быть signer")
|
||||
}
|
||||
seen.push(item.id)
|
||||
}
|
||||
parties
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// ==========================================
|
||||
// DOMAIN: БИБЛИОГРАФИЧЕСКИЕ СЕКЦИИ
|
||||
// ==========================================
|
||||
#import "document.typ": fail
|
||||
|
||||
#let bibliography-section(
|
||||
id,
|
||||
sources,
|
||||
title: auto,
|
||||
style: "gost-r-705-2008-numeric",
|
||||
target: auto,
|
||||
group: auto,
|
||||
full: false,
|
||||
page_break: true,
|
||||
) = {
|
||||
if type(id) != str or id.trim() == "" {
|
||||
fail("bibliography.id", "нужна непустая строка")
|
||||
}
|
||||
let normalized-sources = if type(sources) == array { sources } else { (sources,) }
|
||||
if normalized-sources.len() == 0 {
|
||||
fail("bibliography.sources", "нужен хотя бы один источник")
|
||||
}
|
||||
for source in normalized-sources {
|
||||
if type(source) not in (str, path, bytes) {
|
||||
fail("bibliography.sources", "ожидались str, path или bytes")
|
||||
}
|
||||
}
|
||||
if type(full) != bool {
|
||||
fail("bibliography.full", "ожидался bool")
|
||||
}
|
||||
if type(page_break) != bool {
|
||||
fail("bibliography.page-break", "ожидался bool")
|
||||
}
|
||||
(
|
||||
kind: "bibliography-section",
|
||||
id: id,
|
||||
sources: normalized-sources,
|
||||
title: title,
|
||||
style: style,
|
||||
target: target,
|
||||
group: group,
|
||||
full: full,
|
||||
page-break: page_break,
|
||||
)
|
||||
}
|
||||
|
||||
#let validate-bibliographies(sections) = {
|
||||
if type(sections) != array {
|
||||
fail("bibliographies", "ожидался array")
|
||||
}
|
||||
let seen = ()
|
||||
for section in sections {
|
||||
if type(section) != dictionary or section.at("kind", default: none) != "bibliography-section" {
|
||||
fail("bibliographies", "каждый элемент должен быть bibliography-section")
|
||||
}
|
||||
if section.id in seen {
|
||||
fail("bibliographies", "повторяющийся id " + repr(section.id))
|
||||
}
|
||||
seen.push(section.id)
|
||||
}
|
||||
sections
|
||||
}
|
||||
Reference in new issue
Block a user