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)