playbook/scripts/build_tsl_reference_catalog...

286 lines
9.8 KiB
PowerShell

param(
[string]$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
)
$ErrorActionPreference = "Stop"
$sourceDir = Join-Path $RepoRoot "data/tsl_reference_catalog_source"
$outputDir = Join-Path $RepoRoot "docs/tsl/reference/catalog"
$moduleOrder = @(
[pscustomobject]@{ File = "base"; Summary = "字符串、数组、日期时间、类型转换与常用基础能力" }
[pscustomobject]@{ File = "math"; Summary = "数值计算、统计分析、矩阵处理与数学算法" }
[pscustomobject]@{ File = "system"; Summary = "数据类型、表达式调用、性能与运行时能力" }
[pscustomobject]@{ File = "resource"; Summary = "文件、数据库、网络与外部资源访问" }
[pscustomobject]@{ File = "platform"; Summary = "平台相关功能与系统接口" }
[pscustomobject]@{ File = "client"; Summary = "客户端交互、界面控制与前端协作能力" }
[pscustomobject]@{ File = "graphics"; Summary = "图表、绘图与可视化相关函数" }
[pscustomobject]@{ File = "compression"; Summary = "压缩、解压与归档能力" }
[pscustomobject]@{ File = "digest_encoding"; Summary = "哈希、摘要、编码与转换能力" }
[pscustomobject]@{ File = "third_party"; Summary = "第三方库与外部程序交互能力" }
)
function Get-HeadingSections {
param([string[]]$Lines)
$sections = @()
for ($i = 0; $i -lt $Lines.Count; $i++) {
if ($Lines[$i] -match '^(#{4,7})\s+(.+?)\s*$') {
$sections += [pscustomobject]@{
Level = $Matches[1].Length
Title = $Matches[2].Trim()
Start = $i
}
}
}
for ($i = 0; $i -lt $sections.Count; $i++) {
$nextStart = if ($i + 1 -lt $sections.Count) { $sections[$i + 1].Start } else { $Lines.Count }
$sections[$i] | Add-Member -NotePropertyName End -NotePropertyValue ($nextStart - 1)
}
return $sections
}
function Test-IsFunctionSection {
param(
[string]$Title,
[string[]]$BodyLines
)
if ($null -eq $BodyLines -or $BodyLines.Count -eq 0) {
return $false
}
$body = $BodyLines -join "`n"
$hasStructuredMarkers = (
$body -match '(^|\n)\s*用途:' -or
$body -match '(^|\n)\s*参数:' -or
$body -match '(^|\n)\s*返回:'
)
if ($hasStructuredMarkers) {
return $true
}
$looksLikeFunctionName = $Title -match '^[A-Za-z_][A-Za-z0-9_]*$'
$hasMeaningfulBody = $body -match '\S'
return (
$looksLikeFunctionName -and
$hasMeaningfulBody
)
}
function Add-FunctionToGroup {
param(
[System.Collections.Specialized.OrderedDictionary]$GroupMap,
[string]$Category,
[string]$Subcategory,
[string]$FunctionName
)
if ([string]::IsNullOrWhiteSpace($FunctionName)) {
return
}
$categoryName = if ([string]::IsNullOrWhiteSpace($Category)) { "未分类" } else { $Category }
$groupTitle = if ([string]::IsNullOrWhiteSpace($Subcategory)) {
$categoryName
}
else {
"{0} / {1}" -f $categoryName, $Subcategory
}
if (-not $GroupMap.Contains($groupTitle)) {
$GroupMap[$groupTitle] = [pscustomobject]@{
Title = $groupTitle
Category = $categoryName
Subcategory = $Subcategory
Functions = [System.Collections.Generic.List[string]]::new()
}
}
$functionList = $GroupMap[$groupTitle].Functions
if (-not $functionList.Contains($FunctionName)) {
$functionList.Add($FunctionName)
}
}
function Parse-TslFunctionModule {
param([string]$Path)
$lines = Get-Content -LiteralPath $Path
$sections = Get-HeadingSections -Lines $lines
$moduleTitle = ($sections | Where-Object { $_.Level -eq 4 } | Select-Object -First 1).Title
if ([string]::IsNullOrWhiteSpace($moduleTitle)) {
throw "Unable to detect module title in $Path"
}
$groupMap = [ordered]@{}
$currentCategory = $null
$currentSubcategory = $null
foreach ($section in $sections | Where-Object { $_.Level -ge 5 }) {
$title = $section.Title.Trim()
if ($title -in @("内容", "范例")) {
continue
}
$bodyLines = @()
if ($section.End -gt $section.Start) {
$bodyLines = $lines[($section.Start + 1)..$section.End]
}
$isFunction = Test-IsFunctionSection -Title $title -BodyLines $bodyLines
switch ($section.Level) {
5 {
if ($isFunction) {
Add-FunctionToGroup -GroupMap $groupMap -Category "直接函数" -Subcategory $null -FunctionName $title
}
else {
$currentCategory = $title
$currentSubcategory = $null
}
continue
}
6 {
if ($isFunction) {
Add-FunctionToGroup -GroupMap $groupMap -Category $currentCategory -Subcategory $null -FunctionName $title
}
else {
$currentSubcategory = $title
}
continue
}
7 {
if ($isFunction) {
Add-FunctionToGroup -GroupMap $groupMap -Category $currentCategory -Subcategory $currentSubcategory -FunctionName $title
}
continue
}
}
}
$groups = foreach ($entry in $groupMap.GetEnumerator()) {
[pscustomobject]@{
Title = $entry.Value.Title
Category = $entry.Value.Category
Subcategory = $entry.Value.Subcategory
Functions = @($entry.Value.Functions)
}
}
$functionCount = ($groups | ForEach-Object { $_.Functions } | Sort-Object -Unique).Count
return [pscustomobject]@{
File = [System.IO.Path]::GetFileNameWithoutExtension($Path)
ModuleTitle = $moduleTitle
Groups = $groups
FunctionCount = $functionCount
}
}
function New-MarkdownModulePage {
param(
[pscustomobject]$Module,
[string]$Summary
)
$lines = [System.Collections.Generic.List[string]]::new()
$lines.Add("# $($Module.ModuleTitle)")
$lines.Add("")
$lines.Add("这一页只负责函数定位:先按主题找到模块,再在页内搜索函数名。")
$lines.Add("")
$lines.Add("## 使用方式")
$lines.Add("")
$lines.Add("- 返回总目录:[catalog/index.md](index.md)")
$lines.Add("- 需要基础语法时回到 [../../syntax/index.md](../../syntax/index.md)")
$lines.Add("- 需要金融任务组织方式时回到 [../../finance/index.md](../../finance/index.md)")
$lines.Add("")
$lines.Add("## 模块范围")
$lines.Add("")
$lines.Add("- 说明:$Summary")
$lines.Add("- 主题数:$($Module.Groups.Count)")
$lines.Add("- 函数数:$($Module.FunctionCount)")
$lines.Add("")
$lines.Add("## 主题目录")
$lines.Add("")
foreach ($group in $Module.Groups) {
$lines.Add("### $($group.Title)")
$lines.Add("")
foreach ($functionName in $group.Functions) {
$lines.Add("- ``$functionName``")
}
$lines.Add("")
}
return ($lines -join "`r`n").TrimEnd() + "`r`n"
}
function New-MarkdownCatalogIndex {
param([object[]]$Modules)
$lines = [System.Collections.Generic.List[string]]::new()
$lines.Add("# Function Catalog")
$lines.Add("")
$lines.Add('这里是 canonical 函数目录。它只回答“函数在哪个模块里”,不承担基础语法教学。')
$lines.Add("")
$lines.Add("## 使用顺序")
$lines.Add("")
$lines.Add("1. 不知道函数在哪个模块,先看下面的模块目录。")
$lines.Add("2. 进入模块页后,在页内搜索具体函数名。")
$lines.Add("3. 如果问题是语法怎么写,回到 [../../syntax/index.md](../../syntax/index.md)。")
$lines.Add("4. 如果问题是金融场景如何组织,回到 [../../finance/index.md](../../finance/index.md)。")
$lines.Add("")
$lines.Add("## 模块目录")
$lines.Add("")
$lines.Add("| 模块 | 分类页 | 范围 | 函数数 |")
$lines.Add("| --- | --- | --- | --- |")
foreach ($module in $Modules) {
$pageName = "{0}.md" -f $module.File
$lines.Add("| $($module.ModuleTitle) | [$pageName]($pageName) | $($module.Summary) | $($module.FunctionCount) |")
}
$lines.Add("")
$lines.Add("## 说明")
$lines.Add("")
$lines.Add("- 这套目录页由仓库内的函数语料自动整理生成。")
$lines.Add("- 当前目标是先提供稳定检索层,再逐步补全更细的 canonical 说明。")
$lines.Add("")
return ($lines -join "`r`n").TrimEnd() + "`r`n"
}
if (-not (Test-Path -LiteralPath $sourceDir)) {
throw "Source directory not found: $sourceDir"
}
New-Item -ItemType Directory -Force -Path $outputDir | Out-Null
$modules = foreach ($meta in $moduleOrder) {
$sourcePath = Join-Path $sourceDir ("{0}.md" -f $meta.File)
if (-not (Test-Path -LiteralPath $sourcePath)) {
throw "Missing module source: $sourcePath"
}
$module = Parse-TslFunctionModule -Path $sourcePath
$module | Add-Member -NotePropertyName Summary -NotePropertyValue $meta.Summary
$module
}
$catalogIndex = New-MarkdownCatalogIndex -Modules $modules
Set-Content -LiteralPath (Join-Path $outputDir "index.md") -Value $catalogIndex -Encoding UTF8
foreach ($module in $modules) {
$page = New-MarkdownModulePage -Module $module -Summary $module.Summary
Set-Content -LiteralPath (Join-Path $outputDir ("{0}.md" -f $module.File)) -Value $page -Encoding UTF8
}
Write-Output ("Generated {0} catalog pages in {1}" -f ($modules.Count + 1), $outputDir)