6.7.0 - Alpha6 - 重构 scrapers 工具并增加中英双语注释内容
This commit is contained in:
@@ -1,35 +1,49 @@
|
||||
// utils/anchors.mjs
|
||||
|
||||
/**
|
||||
* @typedef {Object} AnchoredBlockUpdateOption
|
||||
* @property {'map' | 'list' | 'custom'} type
|
||||
* @property {string} anchorTag
|
||||
* @property {string} [mapName]
|
||||
* @property {string} [listName]
|
||||
* @property {string[]} lines
|
||||
* @property {number} [linesIndent=4]
|
||||
* @property {string} [updatedLabel]
|
||||
* @property {(srcInBlock: string, options: { toUpdatedStamp?: (date?: Date) => string }) => { newBlock: string, changed: boolean }} [replacer]
|
||||
*/
|
||||
|
||||
import * as fsp from 'node:fs/promises';
|
||||
import * as path from 'node:path';
|
||||
import { escapeRegExp } from './format.mjs';
|
||||
import { toUpdatedStamp } from './date.mjs';
|
||||
|
||||
/**
|
||||
* @param {string} s
|
||||
* @returns {string}
|
||||
*/
|
||||
const normalize = s => String(s).replace(/\s+/g, '');
|
||||
const normalize = (s) => String(s).replace(/\s+/g, '');
|
||||
|
||||
/**
|
||||
* 在指定 Anchor 块中, 用给定的替换函数生成新块内容.
|
||||
* Generate new block content with the given replacement function in the specified Anchor block.<br>
|
||||
* zh-CN: 在指定 Anchor 块中, 用给定的替换函数生成新块内容.
|
||||
*
|
||||
* @param {string} src
|
||||
* @param {string} anchorTag
|
||||
* @param {(block: string) => { newBlock: string, changed: boolean }} replaceBlockFn
|
||||
* @returns {{ src: string, changed: boolean }} - 返回 { src: 新源码, changed: 是否发生变更 }. 若找不到锚点, 原样返回.
|
||||
* @returns {{ src: string, changed: boolean }}
|
||||
*/
|
||||
export function replaceInAnchoredBlock(src, anchorTag, replaceBlockFn) {
|
||||
const beginTag = `// @AnchorBegin ${anchorTag}`;
|
||||
const endTag = `// @AnchorEnd ${anchorTag}`;
|
||||
|
||||
const beginIdx = src.indexOf(beginTag);
|
||||
if (beginIdx === -1) return { src, changed: false };
|
||||
if (beginIdx === -1) throw new Error(`Anchor tag "${anchorTag}" not found in the source code`);
|
||||
|
||||
const endIdx = src.indexOf(endTag, beginIdx + beginTag.length);
|
||||
if (endIdx === -1) return { src, changed: false };
|
||||
if (endIdx === -1) throw new Error(`Anchor tag "${anchorTag}" not found in the source code`);
|
||||
|
||||
const before = src.slice(0, beginIdx);
|
||||
const block = src.slice(beginIdx, endIdx); // 不包含 endTag
|
||||
const block = src.slice(beginIdx, endIdx);
|
||||
const after = src.slice(endIdx);
|
||||
|
||||
const { newBlock, changed } = replaceBlockFn(block) || {};
|
||||
@@ -39,16 +53,18 @@ export function replaceInAnchoredBlock(src, anchorTag, replaceBlockFn) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换锚点块中的某个 map 声明 (如 mapOf(...)), 并在变更时自动刷新 @Updated 日期.
|
||||
* Replace a map declaration (like mapOf(...)) in an anchor block
|
||||
* and automatically refresh the @Updated date when changed.<br>
|
||||
* zh-CN: 替换锚点块中的某个 map 声明 (如 mapOf(...)), 并在变更时自动刷新 @Updated 日期.
|
||||
*
|
||||
* @param {string} src
|
||||
* @param {Object} options
|
||||
* @param {string} options.anchorTag - 块的锚点名
|
||||
* @param {string} options.mapName - 变量名, 如 agpVersionMap
|
||||
* @param {string[]} options.lines - map 体内的每行 (不含缩进, 由函数自动缩进)
|
||||
* @param {string} options.anchorTag
|
||||
* @param {string} options.mapName
|
||||
* @param {string[]} options.lines
|
||||
* @param {number} [options.linesIndent=4]
|
||||
* @param {(date?: Date) => string} [options.toUpdatedStamp=toUpdatedStamp] - 自定义时间戳函数 (可选)
|
||||
* @returns {{ src: string, changed: boolean }}}
|
||||
* @param {(date?: Date) => string} [options.toUpdatedStamp=toUpdatedStamp]
|
||||
* @returns {{ src: string, changed: boolean }}
|
||||
*/
|
||||
export function replaceAnchoredMapBlock(src, {
|
||||
anchorTag,
|
||||
@@ -60,7 +76,8 @@ export function replaceAnchoredMapBlock(src, {
|
||||
return replaceInAnchoredBlock(src, anchorTag, (block) => {
|
||||
let changed = false;
|
||||
|
||||
const re = new RegExp(`([\\t\\x20]*)(va[lr]\\s+)?${mapName}\\s*=\\s*mapOf\\([\\s\\S]*?\\)(,?)`, 'm');
|
||||
const re = new RegExp(String.raw`([\t ]*)(va[lr]\s+)?${escapeRegExp(mapName)}\s*=\s*mapOf\(.*?\)(,?)`, 's');
|
||||
|
||||
let updatedBlock = block.replace(re, (/** @type {string} */ original, /** @type {string} */ indent, /** @type {string} */ keyword, /** @type {string} */ comma) => {
|
||||
const kw = keyword ?? '';
|
||||
const body = lines.map(l => `${' '.repeat(linesIndent)}${indent}${l}`).join('\n');
|
||||
@@ -80,16 +97,19 @@ export function replaceAnchoredMapBlock(src, {
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a list declaration (like listOf(...)) in an anchor block
|
||||
* and automatically refresh the @Updated date when changed.<br>
|
||||
* zh-CN:<br>
|
||||
* 替换锚点块中的某个 list 声明 (如 listOf(...)), 并在变更时自动刷新 @Updated 日期.
|
||||
*
|
||||
* @param {string} src
|
||||
* @param {Object} options
|
||||
* @param {string} options.anchorTag - 块的锚点名
|
||||
* @param {string} options.listName - 变量名, 如 modules 或 libs
|
||||
* @param {string[]} options.lines - list 体内的每行 (不含缩进, 由函数自动缩进)
|
||||
* @param {string} options.anchorTag
|
||||
* @param {string} options.listName
|
||||
* @param {string[]} options.lines
|
||||
* @param {number} [options.linesIndent=4]
|
||||
* @param {(date?: Date) => string} [options.toUpdatedStamp=toUpdatedStamp] - 自定义时间戳函数 (可选)
|
||||
* @returns {{ src: string, changed: boolean }}}
|
||||
* @param {(date?: Date) => string} [options.toUpdatedStamp=toUpdatedStamp]
|
||||
* @returns {{ src: string, changed: boolean }}
|
||||
*/
|
||||
export function replaceAnchoredListBlock(src, {
|
||||
anchorTag,
|
||||
@@ -121,16 +141,14 @@ export function replaceAnchoredListBlock(src, {
|
||||
}
|
||||
|
||||
/**
|
||||
* 高层封装: 读取文件 -> 替换锚点 map -> 若有变更则写回 -> 打印日志.
|
||||
*
|
||||
* @param {string} filePath
|
||||
* @param {Object} options
|
||||
* @param {string} options.anchorTag - 块的锚点名
|
||||
* @param {string} options.mapName - 变量名, 如 agpVersionMap
|
||||
* @param {string[]} options.lines - map 体内的每行 (不含缩进, 由函数自动缩进)
|
||||
* @param {string} options.anchorTag
|
||||
* @param {string} options.mapName
|
||||
* @param {string[]} options.lines
|
||||
* @param {number} [options.linesIndent=4]
|
||||
* @param {string} [options.updatedLabel='']
|
||||
* @param {(date?: Date) => string} [options.toUpdatedStamp=toUpdatedStamp] - 自定义时间戳函数 (可选)
|
||||
* @param {(date?: Date) => string} [options.toUpdatedStamp=toUpdatedStamp]
|
||||
* @param {Console} [options.logger=console]
|
||||
* @returns {Promise<{ changed: boolean, content: string }>}
|
||||
*/
|
||||
@@ -149,24 +167,22 @@ export async function updateAnchoredMapInFile(filePath, {
|
||||
|
||||
if (changed) {
|
||||
await fsp.writeFile(filePath, updated, 'utf8');
|
||||
logger.log(`[${filename}] 已更新` + (updatedLabel ? ` (${updatedLabel})` : ''));
|
||||
logger.log(`[${filename}] Updated` + (updatedLabel ? ` (${updatedLabel})` : ''));
|
||||
} else {
|
||||
// logger.log(`[${filename}] 无需更新` + (updatedLabel ? ` (${updatedLabel})` : ''));
|
||||
// logger.log(`[${filename}] No update needed` + (updatedLabel ? ` (${updatedLabel})` : ''));
|
||||
}
|
||||
return { changed, content: updated };
|
||||
}
|
||||
|
||||
/**
|
||||
* 高层封装: 读取文件 -> 替换锚点 list -> 若有变更则写回 -> 打印日志.
|
||||
*
|
||||
* @param {string} filePath
|
||||
* @param {Object} options
|
||||
* @param {string} options.anchorTag - 块的锚点名
|
||||
* @param {string} options.listName - 变量名, 如 modules 或 libs
|
||||
* @param {string[]} options.lines - list 体内的每行 (不含缩进, 由函数自动缩进)
|
||||
* @param {string} options.anchorTag
|
||||
* @param {string} options.listName
|
||||
* @param {string[]} options.lines
|
||||
* @param {number} [options.linesIndent=4]
|
||||
* @param {string} [options.updatedLabel='']
|
||||
* @param {(date?: Date) => string} [options.toUpdatedStamp=toUpdatedStamp] - 自定义时间戳函数 (可选)
|
||||
* @param {(date?: Date) => string} [options.toUpdatedStamp=toUpdatedStamp]
|
||||
* @param {Console} [options.logger=console]
|
||||
* @returns {Promise<{ changed: boolean, content: string }>}
|
||||
*/
|
||||
@@ -185,31 +201,22 @@ export async function updateAnchoredListInFile(filePath, {
|
||||
|
||||
if (changed) {
|
||||
await fsp.writeFile(filePath, updated, 'utf8');
|
||||
logger.log(`[${filename}] 已更新` + (updatedLabel ? ` (${updatedLabel})` : ''));
|
||||
logger.log(`[${filename}] Updated` + (updatedLabel ? ` (${updatedLabel})` : ''));
|
||||
} else {
|
||||
// logger.log(`[${filename}] 无需更新` + (updatedLabel ? ` (${updatedLabel})` : ''));
|
||||
// logger.log(`[${filename}] No update needed` + (updatedLabel ? ` (${updatedLabel})` : ''));
|
||||
}
|
||||
return { changed, content: updated };
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} AnchoredBlockUpdateOption
|
||||
* @property {'map' | 'list' | 'custom'} type
|
||||
* @property {string} anchorTag
|
||||
* @property {string} [mapName]
|
||||
* @property {string} [listName]
|
||||
* @property {string[]} lines
|
||||
* @property {number} [linesIndent=4]
|
||||
* @property {string} [updatedLabel]
|
||||
* @property {(srcInBlock: string, options: { toUpdatedStamp?: (date?: Date) => string }) => { newBlock: string, changed: boolean }} [replacer]
|
||||
*/
|
||||
/**
|
||||
* 批量在同一文件内进行多锚点替换 (map 与 list 都支持, 读一次/写一次).
|
||||
* Batch replace multiple anchors within the same file
|
||||
* (supports both map and list, requiring only one read/write operation).<br>
|
||||
* zh-CN: 批量在同一文件内进行多锚点替换 (同时支持 map 与 list, 读写仅需一次).
|
||||
*
|
||||
* @param {string} filePath
|
||||
* @param {AnchoredBlockUpdateOption[]} optionList
|
||||
* @param {Object} [extraOptions={}]
|
||||
* @param {(date?: Date) => string} [extraOptions.toUpdatedStamp=toUpdatedStamp] - 自定义时间戳函数 (可选)
|
||||
* @param {(date?: Date) => string} [extraOptions.toUpdatedStamp=toUpdatedStamp]
|
||||
* @param {Console} [extraOptions.logger=console]
|
||||
* @returns {Promise<{ changed: boolean, content: string }>}
|
||||
*/
|
||||
@@ -221,38 +228,38 @@ export async function batchUpdateAnchoredBlocks(filePath, optionList, {
|
||||
let raw = await fsp.readFile(filePath, 'utf8');
|
||||
let changedAny = false;
|
||||
|
||||
for (const op of optionList) {
|
||||
for (const opt of optionList) {
|
||||
let res = { src: raw, changed: false };
|
||||
|
||||
if (op.type === 'map') {
|
||||
if (opt.type === 'map') {
|
||||
res = replaceAnchoredMapBlock(raw, {
|
||||
anchorTag: op.anchorTag,
|
||||
mapName: op.mapName,
|
||||
lines: op.lines,
|
||||
linesIndent: op.linesIndent,
|
||||
anchorTag: opt.anchorTag,
|
||||
mapName: opt.mapName,
|
||||
lines: opt.lines,
|
||||
linesIndent: opt.linesIndent,
|
||||
toUpdatedStamp: toStamp,
|
||||
});
|
||||
} else if (op.type === 'list') {
|
||||
} else if (opt.type === 'list') {
|
||||
res = replaceAnchoredListBlock(raw, {
|
||||
anchorTag: op.anchorTag,
|
||||
listName: op.listName,
|
||||
lines: op.lines,
|
||||
linesIndent: op.linesIndent,
|
||||
anchorTag: opt.anchorTag,
|
||||
listName: opt.listName,
|
||||
lines: opt.lines,
|
||||
linesIndent: opt.linesIndent,
|
||||
toUpdatedStamp: toStamp,
|
||||
});
|
||||
} else if (op.type === 'custom' && typeof op.replacer === 'function') {
|
||||
res = replaceInAnchoredBlock(raw, op.anchorTag, (block) => op.replacer(block, { toUpdatedStamp: toStamp }));
|
||||
} else if (opt.type === 'custom' && typeof opt.replacer === 'function') {
|
||||
res = replaceInAnchoredBlock(raw, opt.anchorTag, (block) => opt.replacer(block, { toUpdatedStamp: toStamp }));
|
||||
} else {
|
||||
logger.warn(`[${filename}] 未知操作类型或缺少参数:`, op);
|
||||
logger.warn(`[${filename}] Unknown operation type or missing parameters:`, opt);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (res.changed) {
|
||||
changedAny = true;
|
||||
raw = res.src;
|
||||
logger.log(`[${filename}] 已更新 (${op['updatedLabel'] ?? op.anchorTag})`);
|
||||
logger.log(`[${filename}] Updated (${opt.updatedLabel ?? opt.anchorTag})`);
|
||||
} else {
|
||||
// logger.log(`[${filename}] 无需更新 (${op['updatedLabel'] ?? op.anchorTag})`);
|
||||
// logger.log(`[${filename}] No update needed (${op['updatedLabel'] ?? op.anchorTag})`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user