Refactor SUB STORE YAML ASSEMBLER to fix duplicate headers and streamline options parsing
This commit is contained in:
@@ -1,17 +1,15 @@
|
|||||||
/**
|
/**
|
||||||
* SUB STORE YAML ASSEMBLER (v3: With Cleaning Options)
|
* SUB STORE YAML ASSEMBLER (v4: Fix Duplicate Headers)
|
||||||
* * Arguments (via URL hash or Script args):
|
* * Arguments:
|
||||||
* - clear-comments=true : Remove full-line comments and debug headers.
|
* - clear-comments=true
|
||||||
* - clear-manifest=true : Remove 'manifest' block from x-substore.
|
* - clear-manifest=true
|
||||||
* - clear-replacements=true : Remove 'replacements' block from x-substore.
|
* - clear-replacements=true
|
||||||
* * Requires: Files must have header "# @file: filename.yaml"
|
* * Requires: Header "# @file: filename.yaml" in input files.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// --- OPTIONS PARSING ---
|
// --- OPTIONS PARSING ---
|
||||||
|
|
||||||
function normalizeOptions() {
|
function normalizeOptions() {
|
||||||
const args = (typeof $arguments !== "undefined" && $arguments) ? $arguments : {};
|
const args = (typeof $arguments !== "undefined" && $arguments) ? $arguments : {};
|
||||||
|
|
||||||
const asBool = (v, def = false) => {
|
const asBool = (v, def = false) => {
|
||||||
if (v === undefined || v === null || v === "") return def;
|
if (v === undefined || v === null || v === "") return def;
|
||||||
if (typeof v === "boolean") return v;
|
if (typeof v === "boolean") return v;
|
||||||
@@ -20,7 +18,6 @@ function normalizeOptions() {
|
|||||||
if (["0", "false", "no", "n", "off"].includes(s)) return false;
|
if (["0", "false", "no", "n", "off"].includes(s)) return false;
|
||||||
return def;
|
return def;
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
clearComments: asBool(args['clear-comments'], false),
|
clearComments: asBool(args['clear-comments'], false),
|
||||||
clearManifest: asBool(args['clear-manifest'], false),
|
clearManifest: asBool(args['clear-manifest'], false),
|
||||||
@@ -29,17 +26,13 @@ function normalizeOptions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- UTILS ---
|
// --- UTILS ---
|
||||||
|
|
||||||
function normalizeFiles(rawFiles) {
|
function normalizeFiles(rawFiles) {
|
||||||
const map = new Map();
|
const map = new Map();
|
||||||
if (!rawFiles || !Array.isArray(rawFiles)) return map;
|
if (!rawFiles || !Array.isArray(rawFiles)) return map;
|
||||||
|
|
||||||
rawFiles.forEach((content) => {
|
rawFiles.forEach((content) => {
|
||||||
if (typeof content !== 'string') return;
|
if (typeof content !== 'string') return;
|
||||||
const match = content.match(/^#\s*@file:\s*(.+?)(\s|$)/m);
|
const match = content.match(/^#\s*@file:\s*(.+?)(\s|$)/m);
|
||||||
if (match) {
|
if (match) map.set(match[1].trim(), content);
|
||||||
map.set(match[1].trim(), content);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
@@ -60,7 +53,6 @@ function extractKey(block, indentLevel) {
|
|||||||
return match ? match[1].trim().replace(/['"]/g, "") : null;
|
return match ? match[1].trim().replace(/['"]/g, "") : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove lines that are purely comments
|
|
||||||
function stripFullLineComments(text) {
|
function stripFullLineComments(text) {
|
||||||
return text.split('\n')
|
return text.split('\n')
|
||||||
.filter(line => !line.trim().startsWith('#'))
|
.filter(line => !line.trim().startsWith('#'))
|
||||||
@@ -85,7 +77,6 @@ function splitBlocks(text, type) {
|
|||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const line = lines[i];
|
const line = lines[i];
|
||||||
// Skip internal @file tags
|
|
||||||
if (line.match(/^#\s*@file:/)) continue;
|
if (line.match(/^#\s*@file:/)) continue;
|
||||||
|
|
||||||
if (isStart(line)) {
|
if (isStart(line)) {
|
||||||
@@ -97,18 +88,16 @@ function splitBlocks(text, type) {
|
|||||||
if (currentBuf.length === 0 && !line.trim()) continue;
|
if (currentBuf.length === 0 && !line.trim()) continue;
|
||||||
currentBuf.push(line);
|
currentBuf.push(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentBuf.length > 0) blocks.push(currentBuf.join("\n"));
|
if (currentBuf.length > 0) blocks.push(currentBuf.join("\n"));
|
||||||
return blocks;
|
return blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- MERGE LOGIC ---
|
// --- MERGE LOGIC ---
|
||||||
|
|
||||||
function processSection(sectionName, manifestEntries, fileMap, opts) {
|
function processSection(sectionName, manifestEntries, fileMap, opts) {
|
||||||
let sectionOutput = [];
|
let sectionOutput = [];
|
||||||
const seenKeys = new Set();
|
const seenKeys = new Set();
|
||||||
|
|
||||||
// Header
|
// Добавляем заголовок секции
|
||||||
if (!['root', 'x-substore', 'rules'].includes(sectionName)) {
|
if (!['root', 'x-substore', 'rules'].includes(sectionName)) {
|
||||||
sectionOutput.push(`${sectionName}:`);
|
sectionOutput.push(`${sectionName}:`);
|
||||||
}
|
}
|
||||||
@@ -120,16 +109,20 @@ function processSection(sectionName, manifestEntries, fileMap, opts) {
|
|||||||
let content = fileMap.get(entry.file);
|
let content = fileMap.get(entry.file);
|
||||||
if (!content) throw new Error(`CRITICAL: File "${entry.file}" not found.`);
|
if (!content) throw new Error(`CRITICAL: File "${entry.file}" not found.`);
|
||||||
|
|
||||||
// --- APPLY OPTION: clearComments (Part 1: Skip debug headers) ---
|
|
||||||
if (!opts.clearComments) {
|
if (!opts.clearComments) {
|
||||||
sectionOutput.push(`\n# --- source: ${entry.file} | mode: ${entry.mode || "concat"} ---`);
|
sectionOutput.push(`\n# --- source: ${entry.file} | mode: ${entry.mode || "concat"} ---`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- APPLY OPTION: clearComments (Part 2: Strip content) ---
|
|
||||||
if (opts.clearComments) {
|
if (opts.clearComments) {
|
||||||
content = stripFullLineComments(content);
|
content = stripFullLineComments(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [FIX] Удаляем дублирующий заголовок x-substore из контента файла,
|
||||||
|
// но оставляем содержимое (оно уже имеет правильный отступ 2 пробела)
|
||||||
|
if (sectionName === 'x-substore') {
|
||||||
|
content = content.replace(/^x-substore:\s*(?:#.*)?$/m, '');
|
||||||
|
}
|
||||||
|
|
||||||
const mode = entry.mode || "concat";
|
const mode = entry.mode || "concat";
|
||||||
|
|
||||||
if (mode === 'concat') {
|
if (mode === 'concat') {
|
||||||
@@ -151,8 +144,6 @@ function processSection(sectionName, manifestEntries, fileMap, opts) {
|
|||||||
|
|
||||||
for (const block of blocks) {
|
for (const block of blocks) {
|
||||||
let id = null;
|
let id = null;
|
||||||
|
|
||||||
// Logic to extract ID
|
|
||||||
if (blockType === 'list') {
|
if (blockType === 'list') {
|
||||||
id = extractName(block);
|
id = extractName(block);
|
||||||
} else if (blockType === 'map0') {
|
} else if (blockType === 'map0') {
|
||||||
@@ -161,7 +152,7 @@ function processSection(sectionName, manifestEntries, fileMap, opts) {
|
|||||||
id = extractKey(block, 2);
|
id = extractKey(block, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- APPLY OPTIONS: clearManifest / clearReplacements ---
|
// Apply cleaning options for x-substore content
|
||||||
if (sectionName === 'x-substore' && id) {
|
if (sectionName === 'x-substore' && id) {
|
||||||
if (opts.clearManifest && id === 'manifest') continue;
|
if (opts.clearManifest && id === 'manifest') continue;
|
||||||
if (opts.clearReplacements && id === 'replacements') continue;
|
if (opts.clearReplacements && id === 'replacements') continue;
|
||||||
@@ -174,7 +165,6 @@ function processSection(sectionName, manifestEntries, fileMap, opts) {
|
|||||||
}
|
}
|
||||||
seenKeys.add(id);
|
seenKeys.add(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
sectionOutput.push(block);
|
sectionOutput.push(block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,12 +174,10 @@ function processSection(sectionName, manifestEntries, fileMap, opts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- MAIN EXECUTION ---
|
// --- MAIN EXECUTION ---
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const opts = normalizeOptions();
|
const opts = normalizeOptions();
|
||||||
const fileMap = normalizeFiles($files);
|
const fileMap = normalizeFiles($files);
|
||||||
|
|
||||||
// Find Manifest
|
|
||||||
let manifestKey = null;
|
let manifestKey = null;
|
||||||
for (const k of fileMap.keys()) {
|
for (const k of fileMap.keys()) {
|
||||||
if (k.startsWith("00-manifest")) {
|
if (k.startsWith("00-manifest")) {
|
||||||
@@ -199,7 +187,6 @@ try {
|
|||||||
}
|
}
|
||||||
if (!manifestKey) throw new Error("Manifest file (00-manifest-...) not found.");
|
if (!manifestKey) throw new Error("Manifest file (00-manifest-...) not found.");
|
||||||
|
|
||||||
// Parse Manifest
|
|
||||||
const manifestRaw = fileMap.get(manifestKey);
|
const manifestRaw = fileMap.get(manifestKey);
|
||||||
const manifestObj = ProxyUtils.yaml.safeLoad(manifestRaw);
|
const manifestObj = ProxyUtils.yaml.safeLoad(manifestRaw);
|
||||||
|
|
||||||
@@ -210,7 +197,6 @@ try {
|
|||||||
const manifestList = manifestObj['x-substore'].manifest;
|
const manifestList = manifestObj['x-substore'].manifest;
|
||||||
const replacements = manifestObj['x-substore'].replacements || [];
|
const replacements = manifestObj['x-substore'].replacements || [];
|
||||||
|
|
||||||
// Plan
|
|
||||||
const sectionOrder = [
|
const sectionOrder = [
|
||||||
"x-substore", "root", "hosts", "sniffer", "tun", "dns",
|
"x-substore", "root", "hosts", "sniffer", "tun", "dns",
|
||||||
"proxies", "proxy-providers", "proxy-groups", "rule-providers", "rules"
|
"proxies", "proxy-providers", "proxy-groups", "rule-providers", "rules"
|
||||||
@@ -223,20 +209,15 @@ try {
|
|||||||
plan[entry.section].push(entry);
|
plan[entry.section].push(entry);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assemble
|
|
||||||
const finalChunks = [];
|
const finalChunks = [];
|
||||||
|
|
||||||
for (const sec of sectionOrder) {
|
for (const sec of sectionOrder) {
|
||||||
if (!plan[sec] || plan[sec].length === 0) continue;
|
if (!plan[sec] || plan[sec].length === 0) continue;
|
||||||
const secStr = processSection(sec, plan[sec], fileMap, opts);
|
const secStr = processSection(sec, plan[sec], fileMap, opts);
|
||||||
finalChunks.push(secStr);
|
finalChunks.push(secStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Join with double newline if comments allowed, else single/compact might be preferred,
|
|
||||||
// but double is safer for YAML structure readability.
|
|
||||||
let result = finalChunks.join("\n\n");
|
let result = finalChunks.join("\n\n");
|
||||||
|
|
||||||
// Replacements
|
|
||||||
if (Array.isArray(replacements)) {
|
if (Array.isArray(replacements)) {
|
||||||
replacements.forEach(rep => {
|
replacements.forEach(rep => {
|
||||||
if (rep.from && rep.to) {
|
if (rep.from && rep.to) {
|
||||||
|
|||||||
Reference in New Issue
Block a user