scripts\gen-wiki.ps1¶
源码路径:
scripts\gen-wiki.ps1· 203 行
# 注意(编码):本文件必须保存为 **UTF-8 with BOM**。
# Windows PowerShell 5.1 读取无 BOM 的脚本时会按系统 ANSI 代码页(此处 GBK)解码,
# 脚本里的中文会变成乱码,进而把索引里的中文标题一起写坏。
# 本机没有 pwsh(PowerShell 7),所以不能用"依赖 pwsh 默认 UTF-8"这条路。
## 把源码逐行导出成 wiki 文档。
#
# 为什么是脚本而不是手写:两万多行没法手写,而且会立刻和源码脱节。
# 生成物是可重建的 —— 源码改了重跑一次即可,不存在「文档说的和代码不一样」。
#
# 为什么一个源文件一份文档,而不是全部拼成一份:
# 全部拼起来是 1 MB / 两万多行的单文件 —— 打不开、没法 diff、编辑器会跪。
[CmdletBinding()]
param([string]$OutDir = 'wiki')
$ErrorActionPreference = 'Stop'
$ws = Split-Path -Parent $PSScriptRoot
$root = Join-Path $ws $OutDir
$codeDir = Join-Path $root 'code'
# 反引号一律用 [char]96 构造,不写字面反引号 ——
# 第一版就是在字符串里写字面反引号挂掉的(报 Missing ')' in method call)。
$bt = [string][char]96
# **Get-ChildItem 不支持 ** 这种 glob** —— 写成 'crates\**\*.rs' 会被当成
# 字面目录名,一个文件都匹配不到(第一版就是这么只导出 17 份的)。
# 正确写法是「目录 + -Filter」。
$specs = @(
@{ Dir = 'crates'; Filter = '*.rs'; Recurse = $true },
@{ Dir = 'crates'; Filter = 'Cargo.toml'; Recurse = $true },
@{ Dir = 'scripts'; Filter = '*.ps1'; Recurse = $true },
@{ Dir = 'installer'; Filter = '*.iss'; Recurse = $true },
@{ Dir = 'android'; Filter = '*.gradle'; Recurse = $true },
@{ Dir = 'android'; Filter = '*.xml'; Recurse = $true },
@{ Dir = '.'; Filter = 'Cargo.toml'; Recurse = $false },
@{ Dir = '.'; Filter = 'rust-toolchain.toml'; Recurse = $false },
# 五个 Android App(Player/Store/HUB/IOT/online)。
# 这一段是本轮补上的:在此之前生成器只认识 crates/ 和老的 android/,
# 而 apps/ 下近一万行 Kotlin 在知识库里**完全不存在** ——
# 一个只剩 Rust 半边、Android 半边空白的 wiki 比没有更容易误导人。
@{ Dir = 'apps'; Filter = '*.kt'; Recurse = $true },
@{ Dir = 'apps'; Filter = '*.kts'; Recurse = $true },
@{ Dir = 'apps'; Filter = '*.toml'; Recurse = $true },
@{ Dir = 'apps'; Filter = '*.xml'; Recurse = $true },
@{ Dir = 'apps'; Filter = '*.properties'; Recurse = $true },
@{ Dir = 'apps'; Filter = '*.ps1'; Recurse = $true },
@{ Dir = 'apps'; Filter = '*.md'; Recurse = $true }
)
$exclude = '\\target\\|\\\.oracle\\|\\dist|\\dist-out\\|\\dist-android\\|\\node_modules\\|\\\.cargo-home\\|\\jniLibs\\|\\build\\|\\\.tools\\|\\\.idea\\|\\\.gradle\\|\\\.kotlin\\'
# 代码块围栏取「内容里最长连续反引号 + 1」,最短 3。
# 不能省:Rust 文档注释里有三反引号块,固定三个会让代码块提前闭合。
function Get-Fence([string]$text) {
$max = 0; $cur = 0
foreach ($ch in $text.ToCharArray()) {
if ($ch -eq [char]96) { $cur++; if ($cur -gt $max) { $max = $cur } } else { $cur = 0 }
}
return ([string][char]96) * ([Math]::Max(3, $max + 1))
}
function Get-Lang([string]$ext) {
switch ($ext.ToLower()) {
'.rs' { 'rust' }
'.ps1' { 'powershell' }
'.toml' { 'toml' }
'.iss' { 'ini' }
'.gradle' { 'groovy' }
'.xml' { 'xml' }
'.kt' { 'kotlin' }
'.kts' { 'kotlin' }
'.properties' { 'ini' }
'.md' { 'markdown' }
default { '' }
}
}
New-Item -ItemType Directory -Force -Path $root | Out-Null
if (Test-Path $codeDir) { Remove-Item $codeDir -Recurse -Force }
New-Item -ItemType Directory -Force -Path $codeDir | Out-Null
$index = New-Object System.Collections.Generic.List[object]
$totalLines = 0
foreach ($spec in $specs) {
$base = Join-Path $ws $spec.Dir
$found = if ($spec.Recurse) { Get-ChildItem $base -Recurse -File -Filter $spec.Filter -ErrorAction SilentlyContinue } else { Get-ChildItem $base -File -Filter $spec.Filter -ErrorAction SilentlyContinue }
# 排除产物目录。**必须分开写**:管道直接接在 if 语句块后面是语法错误(PS 5.1 报 empty pipe element)。
$found = $found | Where-Object { $_.FullName -notmatch $exclude }
foreach ($f in $found) {
$rel = $f.FullName.Substring($ws.Length).TrimStart('\')
$text = [System.IO.File]::ReadAllText($f.FullName, [System.Text.Encoding]::UTF8)
$lines = ($text -split "`n").Count
$totalLines += $lines
$outName = $rel -replace '[\\/]', '_'
$outPath = Join-Path $codeDir ($outName + '.md')
$fence = Get-Fence $text
$lang = Get-Lang $f.Extension
$sb = New-Object System.Text.StringBuilder
[void]$sb.AppendLine("# $rel")
[void]$sb.AppendLine()
[void]$sb.AppendLine("> 源码路径:$bt$rel$bt · $lines 行")
[void]$sb.AppendLine()
[void]$sb.AppendLine("$fence$lang")
[void]$sb.Append($text)
if (-not $text.EndsWith("`n")) { [void]$sb.AppendLine() }
[void]$sb.AppendLine($fence)
[System.IO.File]::WriteAllText($outPath, $sb.ToString(), (New-Object System.Text.UTF8Encoding $false))
$index.Add([pscustomobject]@{ Rel = $rel; Lines = $lines; Doc = "code/$outName.md" })
}
}
$idx = New-Object System.Text.StringBuilder
[void]$idx.AppendLine('# NFarms HUB · 源码 wiki')
[void]$idx.AppendLine()
[void]$idx.AppendLine("由 ${bt}scripts\gen-wiki.ps1${bt} 从源码生成 —— **不要手工改 code\ 下的文件**:")
[void]$idx.AppendLine('改了下次生成就没了。要改内容请改源码,然后重跑脚本。')
[void]$idx.AppendLine()
[void]$idx.AppendLine('| | |')
[void]$idx.AppendLine('|---|---|')
[void]$idx.AppendLine("| 文件数 | $($index.Count) |")
[void]$idx.AppendLine("| 总行数 | $totalLines |")
[void]$idx.AppendLine("| 生成时间 | $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') |")
[void]$idx.AppendLine()
# 顶层覆盖面概览:一眼能看出「哪块没被收录」。
# 这正是本轮补 apps/ 时暴露的问题 —— 旧索引里完全没有 Android 五个 App 的痕迹,
# 但表面上看起来「什么都不缺」。
$areas = @(
@{ Name = 'crates/(桌面 Rust 端)'; Match = '^crates\\' },
@{ Name = 'apps/(五个 Android App)'; Match = '^apps\\' },
@{ Name = 'android/(旧壳工程)'; Match = '^android\\' },
@{ Name = 'scripts/'; Match = '^scripts\\' },
@{ Name = 'installer/'; Match = '^installer\\' },
@{ Name = '(根目录)'; Match = '^(Cargo\.toml|rust-toolchain\.toml)$' }
)
[void]$idx.AppendLine('## 覆盖面')
[void]$idx.AppendLine()
[void]$idx.AppendLine('| 区域 | 文件 | 行数 |')
[void]$idx.AppendLine('|---|---:|---:|')
foreach ($a in $areas) {
$g = $index | Where-Object { $_.Rel -match $a.Match }
$n = @($g).Count
$ln = if ($n -gt 0) { ($g | Measure-Object Lines -Sum).Sum } else { 0 }
[void]$idx.AppendLine("| $($a.Name) | $n | $ln |")
}
[void]$idx.AppendLine()
[void]$idx.AppendLine('## 其他')
[void]$idx.AppendLine()
# 手写文档按**实际存在的文件**列举,不写死文件名。
#
# 旧版无条件写死 `开发过程记录.md` 一行,而那个文件从来没有被放进 wiki\ ——
# 索引里于是长期挂着一个点开就是 404 的链接。
# 而修成"只在存在时链接"又不够:`开发过程记录-移动端.md` 这类**新增**的手写文档
# 依然不会被列出来,等于白写。所以这里改成扫目录。
$handWritten = Get-ChildItem $root -File -Filter '*.md' -ErrorAction SilentlyContinue |
Where-Object { $_.Name -ne 'README.md' } | Sort-Object Name
if ($handWritten) {
foreach ($h in $handWritten) {
$link = $h.Name -replace ' ', '%20'
[void]$idx.AppendLine("- [$($h.Name)]($link)")
}
} else {
[void]$idx.AppendLine('- (暂无手写文档)')
}
# 分组与显示名:
# · apps\<模块>\... → 按**模块**分组,条目显示模块内相对路径。
# 必要性:五个 App 里都有 Models.kt / MainActivity.kt,只显示文件名根本分不清。
# · 其它 → 按父目录分组,条目显示文件名。
foreach ($item in $index) {
if ($item.Rel -match '^apps\\([^\\]+)\\(.+)$') {
$item | Add-Member -NotePropertyName Group -NotePropertyValue ("apps\" + $Matches[1]) -Force
$item | Add-Member -NotePropertyName Label -NotePropertyValue ($Matches[2] -replace '\\', '/') -Force
} else {
$parent = Split-Path $item.Rel -Parent
$item | Add-Member -NotePropertyName Group -NotePropertyValue ($(if ($parent) { $parent } else { '' })) -Force
$item | Add-Member -NotePropertyName Label -NotePropertyValue (Split-Path $item.Rel -Leaf) -Force
}
}
$byDir = $index | Group-Object Group | Sort-Object Name
foreach ($g in $byDir) {
$dirName = if ($g.Name) { $g.Name } else { '(根目录)' }
$sum = ($g.Group | Measure-Object Lines -Sum).Sum
[void]$idx.AppendLine()
[void]$idx.AppendLine("## $dirName")
[void]$idx.AppendLine()
[void]$idx.AppendLine('| 文件 | 行数 |')
[void]$idx.AppendLine('|---|---:|')
foreach ($item in ($g.Group | Sort-Object Label)) {
$name = $item.Label
$link = $item.Doc -replace ' ', '%20'
[void]$idx.AppendLine("| [$name]($link) | $($item.Lines) |")
}
[void]$idx.AppendLine()
[void]$idx.AppendLine("小计:$sum 行")
}
[System.IO.File]::WriteAllText((Join-Path $root 'README.md'), $idx.ToString(), (New-Object System.Text.UTF8Encoding $false))
Write-Host "已生成: $root" -ForegroundColor Green
Write-Host " README.md(索引) + code\ 下 $($index.Count) 份文档,合计 $totalLines 行" -ForegroundColor Cyan