Files
mihomo-patchbay/shared/yaml-cosmetics.ts
T
im0o f7db96fb0e 搭起项目骨架,验证配置文件往返
核心是确认「改配置而不弄乱文件」这件事成立:新增一路入站之后,其余 52 行逐字节不变。

搬进来的改写引擎用默认输出参数时会重排整个文件——行尾注释的对齐空格消失、流式数组被加上内边距——
连根本没改动的段落也一起波及。不修的话「改一处只动一处」这个承诺直接落空,所以关掉了它的自动排版,
并在写回之后补了一步逐行还原。
2026-08-10 02:43:04 +08:00

58 lines
2.4 KiB
TypeScript

/**
* 写回 YAML 之后的格式修复。
*
* 背景:`yaml` 库把行尾注释的文字存在节点上,但不存注释前面那几个对齐空格。
* 于是即便某一段落根本没被改动,重新输出时 `interval: 86400 # 每 24 小时…`
* 也会变成 `interval: 86400 # 每 24 小时…`。信息没丢,但 diff 里会多出十几行噪音,
* 而「改一处只动一处」正是这个工具赖以取信于人的地方。
*
* 做法:逐行比对。如果输出的某一行与原文某一行「注释前的内容」和「注释文字」都相同,
* 只是中间的空格数不同,就把原文那一行原样放回去。
*/
/** 把一行拆成注释前的内容和注释文字。没有行尾注释则返回 null。 */
function splitInlineComment(line: string): { content: string; comment: string } | null {
const hashIndex = line.indexOf('#')
// 整行注释(# 前面只有空白)不在处理范围内——那种注释的缩进本来就会被保留。
if (hashIndex <= 0) return null
if (line.slice(0, hashIndex).trim() === '') return null
// 注释符号前必须有空白,否则 `a#b` 这种不是注释。
if (!/\s$/.test(line.slice(0, hashIndex))) return null
return {
content: line.slice(0, hashIndex).trimEnd(),
comment: line.slice(hashIndex),
}
}
const keyOf = (content: string, comment: string) => `${content}${comment}`
/**
* 把 `output` 里因为重新输出而丢失对齐的行,还原成 `source` 里对应的那一行。
*
* 只在「注释前内容」和「注释文字」都完全一致时才还原,所以不会改变任何语义;
* 匹配不上的行原样保留。同样的内容在原文里出现多次时按出现顺序依次配对。
*/
export function restoreInlineCommentAlignment(source: string, output: string): string {
const pending = new Map<string, string[]>()
for (const line of source.split('\n')) {
const parts = splitInlineComment(line)
if (!parts) continue
const key = keyOf(parts.content, parts.comment)
const bucket = pending.get(key)
if (bucket) bucket.push(line)
else pending.set(key, [line])
}
if (pending.size === 0) return output
return output
.split('\n')
.map((line) => {
const parts = splitInlineComment(line)
if (!parts) return line
const bucket = pending.get(keyOf(parts.content, parts.comment))
if (!bucket || bucket.length === 0) return line
return bucket.shift()!
})
.join('\n')
}