feat(tui): 清单编辑器循环(第一轮第五块·装配完成)
装配:选行 → 选新方向 → 逐行校验 → 校验后原子保存 + 时间戳备份。输入走 -Driver,所以门禁里能用 -InputScript @('Enter','Down','Enter') 走完整流程,断言看返回值与**文件内容**,不需要捕获控制台输出。
四条"不猜":取消时什么都不写也不产生备份;方向菜单**预选当前值**(不预选的话"连按两次回车"会把字段改成菜单第一项 —— 不是没改,是改错了);方向没变直接返回"没有变化";保存前逐行校验(每行都要能被 ConvertFrom-BackupListLine 解析)—— 这正是 TUI 编辑器比手改安全的地方。
这个函数我试了两次才落地,两次都栽在同一件事上:**用脚本做多行字符串替换时,换行被折成空格**,于是注释与赋值落进同一行、被整段注释掉(症状是"对象没有属性"/"流程莫名走取消分支")。所以这条现在当硬约束执行:多行代码一律整份写文件,不做插入+删除式的拼接。
判据 12 条,覆盖保存 / 取消 / 无变化三条路径,并逐条断言注释行与未编辑行一字不动、备份里存原文、取消时不新增备份。
验收:test.ps1 9/9 全绿(5.1 与 7)。
This commit is contained in:
1 parent
e1fe99671b
commit
5df40d3341
4 files changed
+147
No files matched your search
@@ -19,6 +19,7 @@
|
||||
CompatiblePSEditions = @('Desktop', 'Core')
|
||||
|
||||
FunctionsToExport = @(
|
||||
'Invoke-BakNRetBackupListEditor',
|
||||
'Set-BakNRetBackupListDirection',
|
||||
'Save-BakNRetConfigFile',
|
||||
'Get-BakNRetBackupListRow',
|
||||
|
||||
@@ -164,7 +164,10 @@
|
||||
|
||||
. (Join-Path $PSScriptRoot 'Public\Set-BakNRetBackupListDirection.ps1')
|
||||
|
||||
. (Join-Path $PSScriptRoot 'Public\Invoke-BakNRetBackupListEditor.ps1')
|
||||
|
||||
Export-ModuleMember -Function @(
|
||||
'Invoke-BakNRetBackupListEditor',
|
||||
'Set-BakNRetBackupListDirection',
|
||||
'Save-BakNRetConfigFile',
|
||||
'Get-BakNRetBackupListRow',
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
function Invoke-BakNRetBackupListEditor {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
清单的行编辑器:选一行 → 选新方向 → 逐行校验 → 校验后原子保存(并留时间戳备份)。
|
||||
|
||||
.DESCRIPTION
|
||||
把前面几块装配起来:行表(Get-BakNRetBackupListRow)、单行替换(Set-BakNRetBackupListRow)、
|
||||
改方向(Set-BakNRetBackupListDirection)、校验后原子保存(Save-BakNRetConfigFile)。
|
||||
|
||||
因为输入走 -Driver,整个流程在门禁里可以被 `-InputScript @('Enter','Down','Enter')` 驱动,
|
||||
断言看的是**返回值**与**文件内容**,不需要捕获控制台输出 —— 与菜单循环同一个套路。
|
||||
|
||||
四条"不猜":
|
||||
* 选行或选方向时按 Esc → 什么都不写,也不产生备份;
|
||||
* **方向菜单预选当前值** —— 不预选的话,"连按两次回车"会把字段改成菜单的第一项,
|
||||
那不是"没改",是改错了(实测踩到);
|
||||
* 方向没变 → 直接返回"没有要保存的内容",不做无意义的写盘与备份;
|
||||
* 保存前**逐行校验**(每行都要能被 ConvertFrom-BackupListLine 解析)——这正是 TUI 编辑器
|
||||
比手改安全的地方:手改没有校验。
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Path,
|
||||
[Parameter(Mandatory = $true)]$Driver,
|
||||
[switch]$WhatIf
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
return [pscustomobject]@{ Saved = $false; Changed = $false; Error = "找不到清单文件:$Path"; BackupPath = $null }
|
||||
}
|
||||
|
||||
$text = Get-Content -LiteralPath $Path -Raw -Encoding UTF8
|
||||
$rows = @(Get-BakNRetBackupListRow -Text $text)
|
||||
$entryRows = @($rows | Where-Object { $_.Record })
|
||||
|
||||
if ($entryRows.Count -eq 0) {
|
||||
return [pscustomobject]@{ Saved = $false; Changed = $false; Error = '清单里没有可编辑的条目。'; BackupPath = $null }
|
||||
}
|
||||
|
||||
$items = @(foreach ($row in $entryRows) {
|
||||
[pscustomobject]@{
|
||||
Key = [string]$row.Line
|
||||
Text = ('第 {0,3} 行 {1,-7} {2}' -f $row.Line, $row.Record.Direction, $row.Record.Path)
|
||||
}
|
||||
})
|
||||
|
||||
$picked = Invoke-BakNRetMenu -Title ('清单:{0}' -f $Path) -Items $items -Driver $Driver -MultiSelect $false -Hint '↑↓ 选行 回车 编辑 Esc 取消'
|
||||
if ($picked.Action -ne 'confirm') {
|
||||
return [pscustomobject]@{ Saved = $false; Changed = $false; Error = ''; BackupPath = $null }
|
||||
}
|
||||
|
||||
$lineNumber = [int]$picked.Chosen.Key
|
||||
$target = $rows | Where-Object { $_.Line -eq $lineNumber } | Select-Object -First 1
|
||||
|
||||
$directionItems = @(
|
||||
[pscustomobject]@{ Key = 'both'; Text = 'both 两个方向都处理(不带标记)' },
|
||||
[pscustomobject]@{ Key = 'backup'; Text = 'backup 只备份(行首 +)' },
|
||||
[pscustomobject]@{ Key = 'restore'; Text = 'restore 只恢复(行首 -)' }
|
||||
)
|
||||
|
||||
# 预选当前取值:菜单停在字段现在的值上,用户要改才按方向键。
|
||||
$initialIndex = 0
|
||||
for ($scan = 0; $scan -lt $directionItems.Count; $scan++) {
|
||||
if ($directionItems[$scan].Key -eq [string]$target.Record.Direction) {
|
||||
$initialIndex = $scan
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
$chosen = Invoke-BakNRetMenu -Title '改成哪个方向?' -Items $directionItems -Driver $Driver -MultiSelect $false -InitialIndex $initialIndex -Hint '↑↓ 选方向 回车 应用 Esc 取消'
|
||||
if ($chosen.Action -ne 'confirm') {
|
||||
return [pscustomobject]@{ Saved = $false; Changed = $false; Error = ''; BackupPath = $null }
|
||||
}
|
||||
|
||||
$newLine = Set-BakNRetBackupListDirection -Raw $target.Raw -Direction $chosen.Chosen.Key
|
||||
if ($newLine -eq $target.Raw) {
|
||||
return [pscustomobject]@{ Saved = $false; Changed = $false; Error = '方向没有变化,没有要保存的内容。'; BackupPath = $null }
|
||||
}
|
||||
|
||||
$newText = Set-BakNRetBackupListRow -Text $text -Line $lineNumber -NewText $newLine
|
||||
|
||||
$validate = {
|
||||
param($candidate)
|
||||
$bad = @()
|
||||
$number = 0
|
||||
foreach ($one in ($candidate -split "`r?`n")) {
|
||||
$number++
|
||||
$trimmed = $one.Trim()
|
||||
if (-not $trimmed -or $trimmed.StartsWith('#')) { continue }
|
||||
if (-not (ConvertFrom-BackupListLine -Line $one)) {
|
||||
$bad += ('第 {0} 行解析不了:{1}' -f $number, $trimmed)
|
||||
}
|
||||
}
|
||||
return $bad
|
||||
}
|
||||
|
||||
$saved = Save-BakNRetConfigFile -Path $Path -Text $newText -Validate $validate `
|
||||
-BackupDirectory (Join-Path (Split-Path -Parent $Path) 'logs\config-backups') -WhatIf:$WhatIf
|
||||
|
||||
return [pscustomobject]@{
|
||||
Saved = $saved.Saved
|
||||
Changed = $true
|
||||
Error = $saved.Error
|
||||
BackupPath = $saved.BackupPath
|
||||
}
|
||||
}
|
||||
@@ -1647,6 +1647,44 @@ Test-Case '改方向:只动行首标记,其余逐字节保持,且保留用
|
||||
Assert-True $threw '注释行改方向必须抛错'
|
||||
}
|
||||
|
||||
Test-Case '清单编辑器:脚本驱动走完"选行→改方向→保存",只动一行且留下备份' {
|
||||
$dir = Join-Path $env:TEMP ("bnr-listed-" + [guid]::NewGuid().ToString('N').Substring(0, 8))
|
||||
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
||||
$file = Join-Path $dir 'BackupList.txt'
|
||||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||
$original = "# 注释一`nEdge`n+WindowsTerminal :encrypt`n"
|
||||
[System.IO.File]::WriteAllText($file, $original, $utf8Bom)
|
||||
$backupDir = Join-Path $dir 'logs\config-backups'
|
||||
try {
|
||||
# 1) 选第一行(回车)、方向菜单预选当前值 both、下移到 backup、回车应用
|
||||
$r = Invoke-BakNRetBackupListEditor -Path $file -Driver (New-BakNRetInputDriver -Script @('Enter', 'Down', 'Enter'))
|
||||
Assert-Equal $true $r.Saved '应当保存'
|
||||
$after = @((Get-Content -LiteralPath $file -Raw -Encoding UTF8) -split "`r?`n")
|
||||
Assert-Equal '# 注释一' $after[0] '注释行一字不动'
|
||||
Assert-Equal '+Edge' $after[1] '第一行改成了 +Edge'
|
||||
Assert-Equal '+WindowsTerminal :encrypt' $after[2] '第三行一字不动'
|
||||
Assert-True (Test-Path $r.BackupPath) '应当留下时间戳备份'
|
||||
Assert-Equal $original ([System.IO.File]::ReadAllText($r.BackupPath)) '备份里是原文'
|
||||
|
||||
# 2) 取消(Esc):什么都不写,也不产生新备份
|
||||
$backupsBefore = @(Get-ChildItem $backupDir -File | Measure-Object).Count
|
||||
$contentBefore = [System.IO.File]::ReadAllText($file)
|
||||
$c = Invoke-BakNRetBackupListEditor -Path $file -Driver (New-BakNRetInputDriver -Script @('Esc'))
|
||||
Assert-Equal $false $c.Saved '取消不该保存'
|
||||
Assert-Equal $false $c.Changed '取消不该标记为已改'
|
||||
Assert-Equal $contentBefore ([System.IO.File]::ReadAllText($file)) '取消后文件内容不变'
|
||||
Assert-Equal $backupsBefore @(Get-ChildItem $backupDir -File | Measure-Object).Count '取消不该新增备份'
|
||||
|
||||
# 3) 方向没变:菜单预选当前值 backup,直接回车 → 说明"没有变化",不白写一次盘
|
||||
$same = Invoke-BakNRetBackupListEditor -Path $file -Driver (New-BakNRetInputDriver -Script @('Enter', 'Enter'))
|
||||
Assert-Equal $false $same.Saved '方向没变不该保存'
|
||||
Assert-True ($same.Error -like '*没有变化*') '应当说明原因'
|
||||
Assert-Equal $contentBefore ([System.IO.File]::ReadAllText($file)) '文件内容仍然不变'
|
||||
} finally {
|
||||
Remove-Item -LiteralPath $dir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
Remove-Item -LiteralPath $sandbox -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item -LiteralPath $itemSandbox -Recurse -Force -ErrorAction SilentlyContinue
|
||||
|
||||
Reference in new issue
Block a user