feat(tui): 改方向(第一轮第五块·编辑动作)

清单上最常改的就是方向,而它**能在文本层精确完成**:加/去一个行首标记即可,不需要把整行拆成字段再拼回去。后者要在 Overrides / Flags / 引号 / 转义之间做逆向,任何一处不精确都会把用户的清单改花 —— 外科式改写的底线是"只动我要动的那一处"(ADR-0013)。

**保留用户的写法风格**:仓库的真实清单里同时存在贴着写(+WindowsTerminal)与留空格(+ WindowsTerminal)两种,统一成一种意味着顺带改动了本不该碰的行。这就是"改一个字段却多出几十行 diff"的来源。断言里两种风格都钉住了。

注释行没有方向可改,直接抛错:静默返回原样会制造"改了但没变"。

判据 12 条,其中往返那条最强:**把一行设成它当前的方向,必须原样返回**(含带注释与修饰符的行)。

验收:test.ps1 9/9 全绿(5.1 与 7)。
This commit is contained in:
Shuery committed 2026-09-27 16:25:38 +08:00
1 parent 59ec5a21d4
commit e1fe99671b
4 files changed
+74

No files matched your search

+1
View File
@@ -19,6 +19,7 @@
CompatiblePSEditions = @('Desktop', 'Core') CompatiblePSEditions = @('Desktop', 'Core')
FunctionsToExport = @( FunctionsToExport = @(
'Set-BakNRetBackupListDirection',
'Save-BakNRetConfigFile', 'Save-BakNRetConfigFile',
'Get-BakNRetBackupListRow', 'Get-BakNRetBackupListRow',
'Set-BakNRetBackupListRow', 'Set-BakNRetBackupListRow',
+3
View File
@@ -162,7 +162,10 @@
. (Join-Path $PSScriptRoot 'Public\Save-BakNRetConfigFile.ps1') . (Join-Path $PSScriptRoot 'Public\Save-BakNRetConfigFile.ps1')
. (Join-Path $PSScriptRoot 'Public\Set-BakNRetBackupListDirection.ps1')
Export-ModuleMember -Function @( Export-ModuleMember -Function @(
'Set-BakNRetBackupListDirection',
'Save-BakNRetConfigFile', 'Save-BakNRetConfigFile',
'Get-BakNRetBackupListRow', 'Get-BakNRetBackupListRow',
'Set-BakNRetBackupListRow', 'Set-BakNRetBackupListRow',
@@ -0,0 +1,47 @@
function Set-BakNRetBackupListDirection {
<#
.SYNOPSIS
改一行的方向(both / backup / restore),其余部分逐字节保持。
.DESCRIPTION
为什么先只做"方向"这一种编辑:它是清单上最常改的东西,而且**能在文本层精确完成** ——
加/去一个行首标记就行。要改别的字段就得把整行拆成字段再拼回去,而"拼回去"要在
`Overrides` / `Flags` / 引号 / 转义之间做逆向,任何一处不精确都会把用户的清单改花。
外科式改写的底线是"只动我要动的那一处"(ADR-0013)。
**保留用户的写法风格**:仓库的真实清单里同时存在贴着写(`+WindowsTerminal`)与留空格
(`+ WindowsTerminal`)两种。原样保留风格比统一成一种更安全 —— 统一意味着顺带改动了
那些我本来不该碰的行,而那正是"改一个字段却多出几十行 diff"的来源。
注释行与空行**没有方向可改**,所以直接抛错:静默返回原样会制造"改了但没变"。
#>
param(
[Parameter(Mandatory = $true)][AllowEmptyString()][string]$Raw,
[Parameter(Mandatory = $true)][ValidateSet('both', 'backup', 'restore')][string]$Direction
)
$record = ConvertFrom-BackupListLine -Line $Raw
if (-not $record) { throw '这一行不是条目(注释或空行),没有方向可改。' }
# 拆出:行首缩进 + 原有标记的写法风格 + 标记之后的正文
$indent = ''
$rest = $Raw
if ($rest -match '^(\s+)(.+)$') {
$indent = $Matches[1]
$rest = $Matches[2]
}
$spaced = $false
$body = $rest
if ($body.Length -gt 0 -and ($body[0] -eq '+' -or $body[0] -eq '-')) {
$spaced = ($body.Length -gt 1 -and $body[1] -eq ' ')
$body = $body.Substring(1)
if ($spaced) { $body = $body.Substring(1) }
}
if ($Direction -eq 'both') { return $indent + $body }
$marker = if ($Direction -eq 'backup') { '+' } else { '-' }
if ($spaced) { return $indent + $marker + ' ' + $body }
return $indent + $marker + $body
}
+23
View File
@@ -1624,6 +1624,29 @@ Test-Case '保存配置:校验不过什么都不动,过了才备份 + 原子
} }
} }
Test-Case '改方向:只动行首标记,其余逐字节保持,且保留用户写法风格' {
Assert-Equal '+Edge' (Set-BakNRetBackupListDirection -Raw 'Edge' -Direction 'backup') '裸行加 +'
Assert-Equal 'Edge' (Set-BakNRetBackupListDirection -Raw '+Edge' -Direction 'both') '去掉标记回到 both'
Assert-Equal '-Edge' (Set-BakNRetBackupListDirection -Raw '+Edge' -Direction 'restore') '备份改恢复'
Assert-Equal '-Edge :encrypt :- logs\' (Set-BakNRetBackupListDirection -Raw '+Edge :encrypt :- logs\' -Direction 'restore') '其余部分逐字节保持'
# 风格保留:留空格的写法不能被改成贴着写(真实清单里两种都有)
Assert-Equal '+ WindowsTerminal' (Set-BakNRetBackupListDirection -Raw '+ WindowsTerminal' -Direction 'backup') '留空格风格保留'
Assert-Equal '- WindowsTerminal' (Set-BakNRetBackupListDirection -Raw '+ WindowsTerminal' -Direction 'restore') '改完仍是留空格'
Assert-Equal 'WindowsTerminal' (Set-BakNRetBackupListDirection -Raw '+ WindowsTerminal' -Direction 'both') '去掉留空格的标记'
# 往返:设成当前方向 → 一行都不该变
foreach ($line in @('Edge', '+Edge', '-Edge', '+ WindowsTerminal', '+Edge :encrypt # 说明')) {
$current = (ConvertFrom-BackupListLine -Line $line).Direction
Assert-Equal $line (Set-BakNRetBackupListDirection -Raw $line -Direction $current) ("设成当前方向应当原样返回:" + $line)
}
# 注释行没有方向可改:抛错而不是静默返回原样
$threw = $false
try { Set-BakNRetBackupListDirection -Raw '# 注释' -Direction 'backup' | Out-Null } catch { $threw = $true }
Assert-True $threw '注释行改方向必须抛错'
}
# ============================================================================ # ============================================================================
Remove-Item -LiteralPath $sandbox -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $sandbox -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath $itemSandbox -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $itemSandbox -Recurse -Force -ErrorAction SilentlyContinue