✨ feat(vscode): 命令规则加装软件包、装依赖、跑程序(apt-get、apk、pip、npm、pnpm、yarn、corepack、uv、uvicorn)和建用户、密钥、装文件、改属主(useradd、adduser、addgroup、gpg、install、chown);选项写到 4 个以上或写了几遍时排成「选项 | 意思」表;没有自己规则的命令交给管 * 的规则(只问 --version 的读成打印版本);curl -sS 只写不打印进度,source 读成读入里面的变量和函数

This commit is contained in:
cruldra committed 2026-09-28 11:53:54 +08:00
1 parent 149cb51cc0
commit 0665f35821
7 files changed
+764 -37

No files matched your search

@@ -1,44 +1,25 @@
import type { BlockView } from '../blockView'
import type { Inline, PropertyItem } from '../contract'
import { describe, expect, it } from 'vitest'
import { readBlockViews } from '../blockView'
import { commandRuleLookup } from '../commandRules'
import { packageCommandRules } from '../commands/packageCommands'
import { shellCommandRules } from '../commands/shellCommands'
import { systemCommandRules } from '../commands/systemCommands'
import { createBashAdapter } from '../languages/bash'
import { flatSegments, readFixture, runtime, segmentText, WASM_DIR } from '../testSupport'
import { flatSegments, readFixture, runtime, viewLines, WASM_DIR } from '../testSupport'
import { bashZhTranslator } from './bashZh'
const bash = createBashAdapter(WASM_DIR)
function valueText(value: Inline[] | { markdown: string }): string {
return 'markdown' in value ? value.markdown.split('\n')[0] : segmentText(value)
}
/**
* 视图数据写成文字:一行译文的块是「种类 行号: 译文」,属性表块是「种类 行号」下面每行「属性: 值」;
* 表(属性行展开的、译文下面的、格子里套的)每行比上一层多缩进,格子用 | 隔开,格子里的换行写成 ⏎。
*/
function lines(blocks: BlockView[], indent = ''): string[] {
const items = (list: PropertyItem[] | undefined, pad: string): string[] => (list ?? []).flatMap(item => [
pad + item.cells.map(segmentText).join(' | ').replace(/\n/g, ' ⏎ '),
...items(item.items, `${pad} `),
...items(item.table?.items, `${pad} `),
])
return blocks.flatMap((block) => {
const head = 'properties' in block
? [`${indent}${block.kind} ${block.startLine + 1}`, ...block.properties.flatMap(row => [`${indent} ${row.label}: ${valueText(row.value)}`, ...items(row.items, `${indent} `)])]
: [`${indent}${block.kind} ${block.startLine + 1}: ${valueText(block.translation)}`, ...items(block.table?.items, `${indent} `)]
return [...head, ...lines(block.children, `${indent} `)]
})
}
/** 内置的全部命令规则,和插件里注册的一样。 */
const RULES = [...shellCommandRules, ...systemCommandRules, ...packageCommandRules]
async function read(source: string, reports: string[] = []): Promise<BlockView[]> {
const { blocks } = await readBlockViews(runtime, bash, bashZhTranslator, 'shellscript', source, { commandRule: commandRuleLookup(shellCommandRules, text => reports.push(text)) })
const { blocks } = await readBlockViews(runtime, bash, bashZhTranslator, 'shellscript', source, { commandRule: commandRuleLookup(RULES, text => reports.push(text)) })
return blocks
}
async function readLines(source: string): Promise<string[]> {
return lines(await read(source))
return viewLines(await read(source))
}
/** luffy 桌面端 fetch-runtime.sh 读出来的样子(和原型 SHELL_BLOCKS 对得上,原型略去的 95-100、116-121 行也在)。 */
@@ -200,6 +181,10 @@ describe('shell 中文直译', () => {
['rm -rf --one-file-system x', '删除 x(连同里面的东西、不提示) --one-file-system'],
['foo -x "$y"', 'foo -x y'],
['mkdir -p d && cd d', '建目录 d(连同上级目录) 然后 进入 d'],
['mkdir -p d && echo hi > d/a', '建目录 d(连同上级目录) 然后 打印 "hi"(写到 d/a)'],
['curl -sSL -o f https://x', '下载 https://x 到 f(不打印进度、跟着跳转走)'],
['. /etc/os-release', '读入 /etc/os-release 里的变量和函数'],
['pg_repack --version', '打印 pg_repack 的版本'],
['cmd || exit 1', 'cmd,失败就 退出,退出码 1'],
])('命令:%s(规则不认识的选项照抄,没有规则的命令照抄命令名)', async (source, expected) => {
expect(await readLines(`${source}\n`)).toEqual([`command 1: ${expected}`])
@@ -249,7 +234,7 @@ describe('shell 中文直译', () => {
const { blocks } = await readBlockViews(runtime, bash, bashZhTranslator, 'shellscript', 'rm -rf x\nmkdir d\n', {
commandRule: commandRuleLookup([broken, ...shellCommandRules], text => reports.push(text)),
})
expect(lines(blocks)).toEqual(['command 1: rm -rf x', 'command 2: 建目录 d'])
expect(viewLines(blocks)).toEqual(['command 1: rm -rf x', 'command 2: 建目录 d'])
expect(reports).toEqual([expect.stringContaining('坏了')])
})
})