51 lines
1.6 KiB
JavaScript
51 lines
1.6 KiB
JavaScript
'use strict';
|
|
|
|
const path = require('path');
|
|
const { isSpecialKey } = require('../../chapter-identity');
|
|
|
|
const RESERVED = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;
|
|
|
|
function cleanPart(value, max = 120) {
|
|
let result = String(value || '')
|
|
.normalize('NFC')
|
|
.replace(/[<>:"/\\|?*\x00-\x1f]/g, '')
|
|
.replace(/[\[\]]/g, '')
|
|
.replace(/\s+/g, ' ')
|
|
.trim()
|
|
.replace(/[ .]+$/g, '');
|
|
if (!result) result = 'Untitled';
|
|
if (RESERVED.test(result)) result += '-manga';
|
|
return result.slice(0, max).replace(/[ .]+$/g, '');
|
|
}
|
|
|
|
function chapterKey(value) {
|
|
const raw = String(value).trim();
|
|
if (isSpecialKey(raw)) return raw.toLowerCase();
|
|
if (/^\d+(?:\.\d+)?$/.test(raw)) {
|
|
const [integer, decimal = ''] = raw.split('.');
|
|
const normalized = decimal.replace(/0+$/g, '');
|
|
return `c${integer.padStart(4, '0')}${normalized ? `.${normalized}` : ''}`;
|
|
}
|
|
const extra = raw.match(/^x(\d+)$/i);
|
|
if (extra) return `x${extra[1].padStart(2, '0')}`;
|
|
throw new Error(`Unsupported chapter number: ${value}`);
|
|
}
|
|
|
|
function chapterFolder(chapter) {
|
|
const parts = [chapterKey(chapter.number)];
|
|
if (chapter.title) parts.push(cleanPart(chapter.title));
|
|
parts.push(`[${cleanPart(chapter.source.name, 50)}]`);
|
|
return parts.join(' ');
|
|
}
|
|
|
|
function assertInside(parent, child) {
|
|
const root = path.resolve(parent);
|
|
const target = path.resolve(child);
|
|
if (target !== root && !target.startsWith(root + path.sep)) {
|
|
throw new Error(`Path escapes library root: ${target}`);
|
|
}
|
|
return target;
|
|
}
|
|
|
|
module.exports = { cleanPart, chapterKey, chapterFolder, assertInside };
|