grilling 三轮把设计树走完了,这里把它落成文件,避免决策只活在对话里。 ADR-0010 零依赖自研 TUI:三个候选库全部实测排除 —— ConsoleGuiTools 的 PSGallery 元数据声明最低 7.2(5.1 装不上);Spectre.Console 0.49.1 能在 5.1 加载但要塞 4 个第三方 DLL 且不支持鼠标;Terminal.Gui 1.15.0 能反射接线到真的渲染出窗口,但 Application.Shutdown() 在两端都抛 NRE。同时记下两条实测坑:无控制台时 $Host.UI.SupportsVirtualTerminal 会撒谎返回 True(第一道闸门必须是 [Console]::IsOutputRedirected),以及 5.1 的 RawUI.LengthInBufferCells 对中文返回 2、对边框字符返回 6(都不能用来排版)。 ADR-0011 进度回调这个例外:它是注入点不是状态,无头路径为 $null 时行为与今天完全一致。 ADR-0012 入口改名与垫片:这次留垫片而 Common.psm1 直接删,是因为前者是外部接口(断了是静默没用)后者是内部实现(断了当场报错)。并纠正了自己的一个伪前提 —— 本机实测没有注册任何 BakNRet 计划任务。 ADR-0013 TUI 写配置:外科式改写 + 校验通过才原子替换。硬事实是 SoftwareCatalog.psd1 根本不能被 Import-PowerShellDataFile 读入(动态表达式),任何"解析成对象再序列化"的方案都会毁掉表达式与注释。 .scratch/tui/spec.md 是这次改造的验收锚点:9 条已定决策 + 三轮的判据 + 风险表。 验收:Parse 两版仍绿。
78 lines
5.5 KiB
PowerShell
78 lines
5.5 KiB
PowerShell
param([Parameter(Mandatory=$true)][string]$OutFile, [Parameter(Mandatory=$true)][string]$Work)
|
|
$lines = New-Object System.Collections.Generic.List[string]
|
|
function L($s) { $lines.Add($s); [System.IO.File]::WriteAllLines($OutFile, $lines, (New-Object System.Text.UTF8Encoding($false))) }
|
|
L ("PSVersion: " + $PSVersionTable.PSVersion + " IsOutputRedirected=" + [Console]::IsOutputRedirected)
|
|
$nstack = Join-Path $Work 'nstack111\lib\netstandard2.0\NStack.dll'
|
|
$tg = Join-Path $Work 'tg115\lib\net472\Terminal.Gui.dll'
|
|
$sc = Join-Path $Work 'sc049\lib\netstandard2.0\Spectre.Console.dll'
|
|
function Dump($asm, $tag) {
|
|
L (" [" + $tag + "] FullName = " + $asm.FullName)
|
|
try { L (" [$tag] Location = " + $asm.Location) } catch { L (" [$tag] Location: dynamic") }
|
|
try {
|
|
$ts = $asm.GetTypes()
|
|
L (" [$tag] GetTypes() = " + $ts.Count + " types")
|
|
$pub = $ts | Where-Object { $_.IsPublic } | Select-Object -First 4
|
|
L (" [$tag] sample public: " + (($pub | ForEach-Object { $_.FullName }) -join ' | '))
|
|
$app = $ts | Where-Object { $_.FullName -eq 'Terminal.Gui.Application' }
|
|
L (" [$tag] type named Terminal.Gui.Application in GetTypes(): " + $(if ($app) { 'YES -> ' + $app.AssemblyQualifiedName } else { 'NO' }))
|
|
} catch { L (" [$tag] GetTypes() THREW: " + $_.Exception.Message) }
|
|
}
|
|
L ""
|
|
L "=== STEP 1: Add-Type -Path (no prior load) ==="
|
|
Add-Type -Path $nstack
|
|
L " NStack Add-Type OK"
|
|
Add-Type -Path $tg
|
|
L " Terminal.Gui Add-Type OK"
|
|
$loaded = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -in @('NStack','Terminal.Gui') }
|
|
foreach ($a in $loaded) { Dump $a $a.GetName().Name }
|
|
L ""
|
|
L "=== STEP 2: type resolution methods ==="
|
|
$tgAsm = $loaded | Where-Object { $_.GetName().Name -eq 'Terminal.Gui' }
|
|
L (" asm.GetType('Terminal.Gui.Application') -> " + $(try { $r = $tgAsm.GetType('Terminal.Gui.Application'); if ($r) { 'FOUND ' + $r.FullName } else { 'null' } } catch { 'THREW ' + $_.Exception.Message }))
|
|
L (" [Type]::GetType('Terminal.Gui.Application, Terminal.Gui') -> " + $(try { $r = [Type]::GetType('Terminal.Gui.Application, Terminal.Gui'); if ($r) { 'FOUND ' + $r.FullName } else { 'null' } } catch { 'THREW ' + $_.Exception.Message }))
|
|
try {
|
|
$aq = $tgAsm.GetName().FullName
|
|
L (" asm fullname = " + $aq)
|
|
$r2 = [Type]::GetType('Terminal.Gui.Application, ' + $aq)
|
|
L (" [Type]::GetType(full qualified) -> " + $(if ($r2) { 'FOUND' } else { 'null' }))
|
|
} catch { L (" full-qualified Type.GetType THREW: " + $_.Exception.Message) }
|
|
L ""
|
|
L "=== STEP 3: Application static method overloads (real API) ==="
|
|
$appType = $tgAsm.GetType('Terminal.Gui.Application')
|
|
if ($appType) {
|
|
foreach ($m in ($appType.GetMethods([System.Reflection.BindingFlags]'Public,Static') | Where-Object { $_.Name -in @('Init','Shutdown','Run','Refresh','Begin','End') })) {
|
|
L (" " + $m.ReturnType.Name + " " + $m.Name + "(" + (($m.GetParameters() | ForEach-Object { $_.ParameterType.Name + ' ' + $_.Name }) -join ', ') + ")")
|
|
}
|
|
}
|
|
L ""
|
|
L "=== STEP 4: Spectre.Console type resolution ==="
|
|
Add-Type -Path $sc
|
|
L " Spectre.Console Add-Type OK"
|
|
$scAsm = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq 'Spectre.Console' }
|
|
Dump $scAsm 'Spectre.Console'
|
|
$ansiT = $scAsm.GetType('Spectre.Console.AnsiConsole')
|
|
L (" scAsm.GetType('Spectre.Console.AnsiConsole') -> " + $(if ($ansiT) { 'FOUND' } else { 'null' }))
|
|
if ($ansiT) {
|
|
L (" static methods of AnsiConsole: " + (($ansiT.GetMethods([System.Reflection.BindingFlags]'Public,Static') | Select-Object -First 25 | ForEach-Object { $_.Name }) -join ', '))
|
|
}
|
|
L ""
|
|
L "=== STEP 5: is Table.AddRow an EXTENSION method on IGridColumn/Table? ==="
|
|
$tableT = $scAsm.GetType('Spectre.Console.Table')
|
|
L (" Table type: " + [bool]$tableT)
|
|
if ($tableT) {
|
|
L (" Table instance methods containing 'Add': " + (($tableT.GetMethods() | Where-Object { $_.Name -like '*Add*' } | ForEach-Object { $_.Name + '(' + (($_.GetParameters() | ForEach-Object { $_.ParameterType.Name }) -join ',') + ')' }) -join ' | '))
|
|
L (" Table properties: " + (($tableT.GetProperties() | ForEach-Object { $_.PropertyType.Name + ' ' + $_.Name }) -join ', '))
|
|
}
|
|
$extT = $scAsm.GetType('Spectre.Console.TableExtensions')
|
|
L (" TableExtensions type: " + [bool]$extT)
|
|
if ($extT) {
|
|
$em = $extT.GetMethods([System.Reflection.BindingFlags]'Public,Static') | Where-Object { $_.Name -like '*Row*' -or $_.Name -like '*Column*' }
|
|
foreach ($m in $em) { L (" ext: " + $m.ReturnType.Name + ' ' + $m.Name + '(' + (($m.GetParameters() | ForEach-Object { $_.ParameterType.Name + ' ' + $_.Name }) -join ', ') + ')') }
|
|
}
|
|
L ""
|
|
L "=== STEP 6: Spectre prompts / progress classes available in netstandard2.0 build ==="
|
|
foreach ($n in @('Spectre.Console.ConfirmationPrompt','Spectre.Console.SelectionPrompt`1','Spectre.Console.MultiSelectionPrompt`1','Spectre.Console.TextPrompt`1','Spectre.Console.Progress','Spectre.Console.Status','Spectre.Console.Live','Spectre.Console.Layout','Spectre.Console.Panel','Spectre.Console.Rule','Spectre.Console.Tree','Spectre.Console.BarChart','Spectre.Console.BreakdownChart','Spectre.Console.ProgressBar','Spectre.Console.ProgressColumn','Spectre.Console.SpinnerColumn','Spectre.Console.Columns','Spectre.Console.Rows','Spectre.Console.Grid','Spectre.Console.Padder','Spectre.Console.Align','Spectre.Console.Canvas','Spectre.Console.Json','Spectre.Console.Calendar','Spectre.Console.ExceptionFormats','Spectre.Console.AnsiConsoleSettings','Spectre.Console.AnsiConsoleFactory','Spectre.Console.IAnsiConsole')) {
|
|
$tt = $scAsm.GetType($n)
|
|
L (" " + $n.PadRight(44) + " -> " + $(if ($tt) { 'AVAILABLE' } else { 'MISSING' }))
|
|
}
|