mirror of
https://github.com/StepanovPlaton/SSAUGenerator.git
synced 2026-10-31 10:18:29 +04:00
Rename project to SSAU Generator and restructure content
- 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
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { build } = require("./build");
|
||||
const { ensureScaffold } = require("./scaffold");
|
||||
|
||||
function parseArgs(argv) {
|
||||
const options = { config: "config.yaml" };
|
||||
@@ -20,6 +22,9 @@ function parseArgs(argv) {
|
||||
case "--check":
|
||||
options.check = true;
|
||||
break;
|
||||
case "--init":
|
||||
options.init = true;
|
||||
break;
|
||||
case "--verbose":
|
||||
case "-v":
|
||||
options.verbose = true;
|
||||
@@ -43,15 +48,16 @@ function parseArgs(argv) {
|
||||
}
|
||||
|
||||
function printHelp() {
|
||||
console.log(`lab-gen-pptx — генератор презентаций PPTX из Markdown
|
||||
console.log(`SSAU Generator PPTX — генератор презентаций PPTX из Markdown
|
||||
|
||||
Использование:
|
||||
lab-gen-pptx [config.yaml] [опции]
|
||||
node pptx/src/cli.js [config.yaml] [опции]
|
||||
|
||||
Опции:
|
||||
-c, --config <file> путь к config.yaml (по умолчанию config.yaml)
|
||||
-o, --output <file> переопределить имя выходного файла
|
||||
--check только проверить конфигурацию и ссылки
|
||||
--init создать шаблон config.yaml/slides/assets и выйти
|
||||
-v, --verbose подробный вывод
|
||||
-h, --help показать справку
|
||||
--version показать версию
|
||||
@@ -68,6 +74,26 @@ async function main() {
|
||||
}
|
||||
|
||||
const configPath = path.resolve(options.config);
|
||||
|
||||
if (options.init || !fs.existsSync(configPath)) {
|
||||
const targetDir = path.dirname(configPath);
|
||||
try {
|
||||
const created = await ensureScaffold(targetDir);
|
||||
if (created.length) {
|
||||
console.log("Создан шаблон генератора:");
|
||||
created.forEach((file) => console.log(" " + path.relative(targetDir, file)));
|
||||
} else {
|
||||
console.log("Файлы уже существуют, ничего не создано.");
|
||||
}
|
||||
console.log("");
|
||||
console.log("Отредактируйте config.yaml и файлы в slides/, затем запустите генератор снова.");
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await build({ config: configPath, check: options.check, output: options.output });
|
||||
const { config, issues } = result;
|
||||
|
||||
@@ -24,9 +24,9 @@ const DEFAULTS = {
|
||||
line: "BFD3DD",
|
||||
},
|
||||
backgrounds: {
|
||||
title: "content/assets/title-bg.jpeg",
|
||||
content: "content/assets/content-bg.jpg",
|
||||
final: "content/assets/final-bg.jpeg",
|
||||
title: "assets/title-bg.jpeg",
|
||||
content: "assets/content-bg.jpg",
|
||||
final: "assets/final-bg.jpeg",
|
||||
},
|
||||
slideNumber: {
|
||||
show: true,
|
||||
@@ -110,7 +110,7 @@ const DEFAULTS = {
|
||||
timeoutMs: 30000,
|
||||
},
|
||||
content: {
|
||||
dir: "content",
|
||||
dir: ".",
|
||||
},
|
||||
slides: [],
|
||||
};
|
||||
|
||||
@@ -114,7 +114,7 @@ function cacheFileName(sourceBuffer, cfg) {
|
||||
}
|
||||
|
||||
async function renderCached(sourceBuffer, cfg) {
|
||||
const dir = cfg.cacheDir || path.join(os.tmpdir(), "lab-gen-pptx-plantuml");
|
||||
const dir = cfg.cacheDir || path.join(os.tmpdir(), "ssau-generator-pptx-plantuml");
|
||||
const target = path.join(dir, cacheFileName(sourceBuffer, cfg));
|
||||
|
||||
if (cfg.cache && fs.existsSync(target)) {
|
||||
|
||||
68
pptx/src/png.js
Normal file
68
pptx/src/png.js
Normal file
@@ -0,0 +1,68 @@
|
||||
"use strict";
|
||||
|
||||
const zlib = require("zlib");
|
||||
|
||||
const CRC_TABLE = (() => {
|
||||
const table = new Int32Array(256);
|
||||
for (let n = 0; n < 256; n += 1) {
|
||||
let c = n;
|
||||
for (let k = 0; k < 8; k += 1) {
|
||||
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
||||
}
|
||||
table[n] = c;
|
||||
}
|
||||
return table;
|
||||
})();
|
||||
|
||||
function crc32(buffer) {
|
||||
let crc = -1;
|
||||
for (let i = 0; i < buffer.length; i += 1) {
|
||||
crc = CRC_TABLE[(crc ^ buffer[i]) & 0xff] ^ (crc >>> 8);
|
||||
}
|
||||
return (crc ^ -1) >>> 0;
|
||||
}
|
||||
|
||||
function chunk(type, data) {
|
||||
const length = Buffer.alloc(4);
|
||||
length.writeUInt32BE(data.length, 0);
|
||||
const typeBuf = Buffer.from(type, "ascii");
|
||||
const crc = Buffer.alloc(4);
|
||||
crc.writeUInt32BE(crc32(Buffer.concat([typeBuf, data])), 0);
|
||||
return Buffer.concat([length, typeBuf, data, crc]);
|
||||
}
|
||||
|
||||
/** Кодирует PNG, получая цвет пикселя через getPixel(x, y) -> [r,g,b]. */
|
||||
function encodePng(width, height, getPixel) {
|
||||
const signature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
||||
const ihdr = Buffer.alloc(13);
|
||||
ihdr.writeUInt32BE(width, 0);
|
||||
ihdr.writeUInt32BE(height, 4);
|
||||
ihdr[8] = 8;
|
||||
ihdr[9] = 2;
|
||||
|
||||
const rowLength = width * 3;
|
||||
const raw = Buffer.alloc((rowLength + 1) * height);
|
||||
for (let y = 0; y < height; y += 1) {
|
||||
const offset = y * (rowLength + 1);
|
||||
for (let x = 0; x < width; x += 1) {
|
||||
const [r, g, b] = getPixel(x, y);
|
||||
const px = offset + 1 + x * 3;
|
||||
raw[px] = r;
|
||||
raw[px + 1] = g;
|
||||
raw[px + 2] = b;
|
||||
}
|
||||
}
|
||||
|
||||
return Buffer.concat([
|
||||
signature,
|
||||
chunk("IHDR", ihdr),
|
||||
chunk("IDAT", zlib.deflateSync(raw, { level: 9 })),
|
||||
chunk("IEND", Buffer.alloc(0)),
|
||||
]);
|
||||
}
|
||||
|
||||
function solidPng(width, height, color) {
|
||||
return encodePng(width, height, () => color);
|
||||
}
|
||||
|
||||
module.exports = { solidPng, encodePng };
|
||||
115
pptx/src/scaffold.js
Normal file
115
pptx/src/scaffold.js
Normal file
@@ -0,0 +1,115 @@
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const yaml = require("js-yaml");
|
||||
|
||||
const { DEFAULTS, deepMerge } = require("./config");
|
||||
const { solidPng } = require("./png");
|
||||
|
||||
const CONFIG_HEADER = `# Конфигурация генератора SSAU Generator PPTX.
|
||||
# Все относительные пути считаются от каталога этого файла.
|
||||
# Любой ключ можно опустить — будет использовано значение по умолчанию.
|
||||
|
||||
`;
|
||||
|
||||
const SAMPLE = {
|
||||
"slides/00-title.md": `# Название презентации
|
||||
|
||||
## Подзаголовок
|
||||
`,
|
||||
"slides/01-content.md": `# Первый слайд
|
||||
|
||||
- первый пункт списка;
|
||||
- второй пункт списка:
|
||||
|
||||
- вложенный пункт;
|
||||
|
||||
Обычный абзац с **полужирным**, *курсивом* и ==выделением==.
|
||||
`,
|
||||
"slides/02-final.md": `# Спасибо за внимание
|
||||
`,
|
||||
};
|
||||
|
||||
// Цвета задников: [R, G, B].
|
||||
const BRAND = [27, 135, 201];
|
||||
const BACKGROUNDS = {
|
||||
"title-bg.png": BRAND,
|
||||
"content-bg.png": [255, 255, 255],
|
||||
"final-bg.png": BRAND,
|
||||
};
|
||||
|
||||
function isGeneratorDir(baseDir) {
|
||||
const base = path.basename(baseDir).toLowerCase();
|
||||
return base === "docx" || base === "pptx";
|
||||
}
|
||||
|
||||
/**
|
||||
* Раскладка проекта: имя проекта = имя папки проекта, общие ресурсы в
|
||||
* content/<проект>/assets/, результат — content/<проект>/<проект>.pptx.
|
||||
*/
|
||||
function projectLayout(baseDir) {
|
||||
const isSub = isGeneratorDir(baseDir);
|
||||
const projectDir = isSub ? path.dirname(baseDir) : baseDir;
|
||||
const projectName = path.basename(projectDir) || "project";
|
||||
return {
|
||||
projectDir,
|
||||
projectName,
|
||||
assetsRel: isSub ? "../assets" : "assets",
|
||||
assetsDir: path.join(projectDir, "assets"),
|
||||
outputRel: isSub ? `../${projectName}.pptx` : `${projectName}.pptx`,
|
||||
};
|
||||
}
|
||||
|
||||
function defaultConfigYaml(layout) {
|
||||
const config = deepMerge(DEFAULTS, {
|
||||
presentation: { output: layout.outputRel },
|
||||
theme: {
|
||||
backgrounds: {
|
||||
title: `${layout.assetsRel}/title-bg.png`,
|
||||
content: `${layout.assetsRel}/content-bg.png`,
|
||||
final: `${layout.assetsRel}/final-bg.png`,
|
||||
},
|
||||
},
|
||||
content: { dir: "." },
|
||||
slides: [
|
||||
{ type: "title", file: "slides/00-title.md" },
|
||||
{ type: "content", file: "slides/01-content.md" },
|
||||
{ type: "final", file: "slides/02-final.md" },
|
||||
],
|
||||
});
|
||||
return (
|
||||
CONFIG_HEADER +
|
||||
yaml.dump(config, { noRefs: true, lineWidth: -1, sortKeys: false })
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Создаёт config.yaml, примеры слайдов и общие ресурсы проекта
|
||||
* (content/<проект>/assets/), если их ещё нет.
|
||||
*/
|
||||
async function ensureScaffold(baseDir) {
|
||||
const layout = projectLayout(baseDir);
|
||||
const created = [];
|
||||
const write = (target, content) => {
|
||||
if (fs.existsSync(target)) return;
|
||||
fs.mkdirSync(path.dirname(target), { recursive: true });
|
||||
if (content && typeof content === "object" && content.binary) {
|
||||
fs.writeFileSync(target, content.binary);
|
||||
} else {
|
||||
fs.writeFileSync(target, content, "utf8");
|
||||
}
|
||||
created.push(target);
|
||||
};
|
||||
|
||||
write(path.join(baseDir, "config.yaml"), defaultConfigYaml(layout));
|
||||
for (const [name, content] of Object.entries(SAMPLE)) {
|
||||
write(path.join(baseDir, name), content);
|
||||
}
|
||||
for (const [name, color] of Object.entries(BACKGROUNDS)) {
|
||||
write(path.join(layout.assetsDir, name), { binary: solidPng(1920, 1080, color) });
|
||||
}
|
||||
return created;
|
||||
}
|
||||
|
||||
module.exports = { projectLayout, defaultConfigYaml, ensureScaffold, SAMPLE };
|
||||
Reference in New Issue
Block a user