mirror of
https://github.com/StepanovPlaton/SSAUGenerator.git
synced 2026-10-31 10:18:29 +04:00
- Move projects into content/<project>/{docx,pptx} with shared assets/
- Flatten config paths; output = project name in project folder
- Add pptx --init scaffold; remove docx exe build
- Add feature-complete content/example; ignore content/* except example
- Rewrite README and AGENTS.md
124 lines
4.7 KiB
JavaScript
124 lines
4.7 KiB
JavaScript
'use strict';
|
||
|
||
const fs = require('fs');
|
||
|
||
const SECTION_TYPES = ['toc', 'structural', 'numbered', 'bibliography', 'appendix'];
|
||
const NUMBERING_SCOPES = ['global', 'section'];
|
||
|
||
function isPositiveInt(value) {
|
||
return Number.isInteger(value) && value > 0;
|
||
}
|
||
|
||
function validateConfig(config) {
|
||
const errors = [];
|
||
|
||
const { document: doc, typography, page, toc, appendix, numbering } = config;
|
||
|
||
if (!Array.isArray(config.sections) || config.sections.length === 0) {
|
||
errors.push('sections: должен быть непустой список частей документа');
|
||
} else {
|
||
const letters = new Set();
|
||
config.sections.forEach((entry, index) => {
|
||
const where = `sections[${index}]`;
|
||
if (!entry || typeof entry !== 'object') {
|
||
errors.push(`${where}: ожидался объект с полем type`);
|
||
return;
|
||
}
|
||
if (!SECTION_TYPES.includes(entry.type)) {
|
||
errors.push(
|
||
`${where}: неизвестный type "${entry.type}" (допустимо: ${SECTION_TYPES.join(', ')})`
|
||
);
|
||
return;
|
||
}
|
||
if (entry.type === 'toc') {
|
||
if (entry.file) errors.push(`${where}: часть type=toc не должна иметь file`);
|
||
return;
|
||
}
|
||
if (!entry.file) {
|
||
errors.push(`${where}: отсутствует file`);
|
||
} else if (!fs.existsSync(entry.file)) {
|
||
errors.push(`${where}: файл не найден: ${entry.file}`);
|
||
}
|
||
if (entry.type === 'structural' && !entry.title) {
|
||
errors.push(`${where}: для structural требуется title`);
|
||
}
|
||
if (entry.type === 'appendix') {
|
||
if (!entry.letter || String(entry.letter).length !== 1) {
|
||
errors.push(`${where}: для appendix требуется одиночная буква в letter`);
|
||
} else {
|
||
const letter = String(entry.letter).toUpperCase();
|
||
if (appendix.forbiddenLetters.map((l) => l.toUpperCase()).includes(letter)) {
|
||
errors.push(`${where}: буква приложения "${letter}" запрещена СТО`);
|
||
}
|
||
if (letters.has(letter)) errors.push(`${where}: повтор буквы приложения "${letter}"`);
|
||
letters.add(letter);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
if (doc.titlePage && !fs.existsSync(doc.titlePage)) {
|
||
errors.push(`document.titlePage: файл не найден: ${doc.titlePage}`);
|
||
}
|
||
|
||
for (const key of ['sizeBody', 'sizeTable', 'sizeSmall']) {
|
||
if (!isPositiveInt(typography[key])) {
|
||
errors.push(`typography.${key}: ожидалось положительное целое (half-points)`);
|
||
}
|
||
}
|
||
if (!(typeof typography.lineSpacing === 'number' && typography.lineSpacing > 0)) {
|
||
errors.push('typography.lineSpacing: ожидалось положительное число');
|
||
}
|
||
if (typography.language != null && typeof typography.language !== 'string') {
|
||
errors.push('typography.language: ожидалась строка (например, ru-RU)');
|
||
}
|
||
|
||
for (const [key, value] of Object.entries(page.margins)) {
|
||
if (!Number.isInteger(value) || value < 0) {
|
||
errors.push(`page.margins.${key}: ожидалось неотрицательное целое (twips)`);
|
||
}
|
||
}
|
||
|
||
if (!isPositiveInt(toc.depth) || toc.depth > 3) {
|
||
errors.push('toc.depth: ожидалось 1..3');
|
||
}
|
||
if (typeof toc.enabled !== 'boolean') {
|
||
errors.push('toc.enabled: ожидалось true/false');
|
||
}
|
||
|
||
for (const key of ['figures', 'tables', 'formulas']) {
|
||
if (!NUMBERING_SCOPES.includes(numbering[key])) {
|
||
errors.push(`numbering.${key}: допустимо ${NUMBERING_SCOPES.join(' | ')}`);
|
||
}
|
||
}
|
||
|
||
if (config.plantuml) {
|
||
const p = config.plantuml;
|
||
if (typeof p.enabled !== 'boolean') {
|
||
errors.push('plantuml.enabled: ожидалось true/false');
|
||
}
|
||
if (p.format !== 'png') {
|
||
errors.push(`plantuml.format: поддерживается только "png", получено "${p.format}"`);
|
||
}
|
||
if (typeof p.jar !== 'string' || !p.jar) {
|
||
errors.push('plantuml.jar: ожидался путь к plantuml.jar');
|
||
}
|
||
if (typeof p.java !== 'string' || !p.java) {
|
||
errors.push('plantuml.java: ожидалось имя или путь к java (или "auto")');
|
||
}
|
||
if (p.cacheDir != null && typeof p.cacheDir !== 'string') {
|
||
errors.push('plantuml.cacheDir: ожидалась строка');
|
||
}
|
||
if (!isPositiveInt(p.timeoutMs)) {
|
||
errors.push('plantuml.timeoutMs: ожидалось положительное целое (мс)');
|
||
}
|
||
if (typeof p.cache !== 'boolean') {
|
||
errors.push('plantuml.cache: ожидалось true/false');
|
||
}
|
||
}
|
||
|
||
return errors;
|
||
}
|
||
|
||
module.exports = { validateConfig, SECTION_TYPES };
|