205 lines
6.8 KiB
PowerShell
205 lines
6.8 KiB
PowerShell
param(
|
|
[string]$Root = (Get-Location).Path,
|
|
[string]$IndexPath,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Query,
|
|
[int]$Top = 10,
|
|
[switch]$Json,
|
|
[switch]$Rebuild
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
if ([string]::IsNullOrWhiteSpace($Query)) {
|
|
throw "Query must not be empty."
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $Root)) {
|
|
throw "Root path does not exist: $Root"
|
|
}
|
|
|
|
$rootPath = (Resolve-Path -LiteralPath $Root).Path.TrimEnd([char[]]"\/")
|
|
|
|
if ([string]::IsNullOrWhiteSpace($IndexPath)) {
|
|
$IndexPath = Join-Path (Join-Path $rootPath "_indexes") "requirements-index.json"
|
|
}
|
|
|
|
if ($Rebuild -or -not (Test-Path -LiteralPath $IndexPath)) {
|
|
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$buildScript = Join-Path $scriptDir "build-requirement-index.ps1"
|
|
& $buildScript -Root $rootPath -OutputPath $IndexPath -Quiet
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $IndexPath)) {
|
|
throw "Index file does not exist: $IndexPath"
|
|
}
|
|
|
|
function Get-QueryTerms {
|
|
param([string]$Value)
|
|
|
|
$normalized = $Value.ToLowerInvariant()
|
|
$terms = New-Object System.Collections.Generic.List[string]
|
|
|
|
foreach ($part in [regex]::Split($normalized, "[\s,;,;、]+")) {
|
|
$trimmed = $part.Trim()
|
|
if (-not [string]::IsNullOrWhiteSpace($trimmed)) {
|
|
[void]$terms.Add($trimmed)
|
|
}
|
|
}
|
|
|
|
return @($terms)
|
|
}
|
|
|
|
function Test-ContainsText {
|
|
param(
|
|
[string]$Haystack,
|
|
[string]$Needle
|
|
)
|
|
|
|
if ([string]::IsNullOrWhiteSpace($Haystack) -or [string]::IsNullOrWhiteSpace($Needle)) {
|
|
return $false
|
|
}
|
|
|
|
return $Haystack.ToLowerInvariant().Contains($Needle)
|
|
}
|
|
|
|
function Get-TextValue {
|
|
param($Value)
|
|
|
|
if ($null -eq $Value) {
|
|
return ""
|
|
}
|
|
|
|
if ($Value -is [array]) {
|
|
return (@($Value) -join " ")
|
|
}
|
|
|
|
return [string]$Value
|
|
}
|
|
|
|
function Add-Score {
|
|
param(
|
|
[int]$Current,
|
|
[string]$FieldValue,
|
|
[string]$Term,
|
|
[int]$Weight,
|
|
[string]$FieldName,
|
|
[System.Collections.Generic.List[string]]$MatchedFields
|
|
)
|
|
|
|
if (Test-ContainsText -Haystack $FieldValue -Needle $Term) {
|
|
[void]$MatchedFields.Add($FieldName)
|
|
return ($Current + $Weight)
|
|
}
|
|
|
|
return $Current
|
|
}
|
|
|
|
$terms = @(Get-QueryTerms -Value $Query)
|
|
if ($terms.Count -eq 0) {
|
|
throw "Query must contain at least one searchable term."
|
|
}
|
|
|
|
$rawItems = Get-Content -Raw -Encoding UTF8 -LiteralPath $IndexPath | ConvertFrom-Json
|
|
$items = @()
|
|
if ($null -ne $rawItems) {
|
|
if ($rawItems -is [array]) {
|
|
$items = @($rawItems)
|
|
}
|
|
else {
|
|
$items = @($rawItems)
|
|
}
|
|
}
|
|
$results = New-Object System.Collections.Generic.List[object]
|
|
|
|
foreach ($item in $items) {
|
|
$score = 0
|
|
$matchedTerms = New-Object System.Collections.Generic.HashSet[string]
|
|
$matchedFields = New-Object System.Collections.Generic.List[string]
|
|
|
|
foreach ($term in $terms) {
|
|
$termScore = 0
|
|
|
|
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.title) -Term $term -Weight 80 -FieldName "title" -MatchedFields $matchedFields
|
|
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.slug) -Term $term -Weight 60 -FieldName "slug" -MatchedFields $matchedFields
|
|
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.tags) -Term $term -Weight 55 -FieldName "tags" -MatchedFields $matchedFields
|
|
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.domain) -Term $term -Weight 35 -FieldName "domain" -MatchedFields $matchedFields
|
|
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.category) -Term $term -Weight 25 -FieldName "category" -MatchedFields $matchedFields
|
|
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.feature) -Term $term -Weight 45 -FieldName "feature" -MatchedFields $matchedFields
|
|
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.businessRules) -Term $term -Weight 40 -FieldName "businessRules" -MatchedFields $matchedFields
|
|
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.acceptance) -Term $term -Weight 25 -FieldName "acceptance" -MatchedFields $matchedFields
|
|
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.flow) -Term $term -Weight 18 -FieldName "flow" -MatchedFields $matchedFields
|
|
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.dataTables) -Term $term -Weight 12 -FieldName "dataTables" -MatchedFields $matchedFields
|
|
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.dictionaries) -Term $term -Weight 12 -FieldName "dictionaries" -MatchedFields $matchedFields
|
|
$termScore = Add-Score -Current $termScore -FieldValue (Get-TextValue $item.sourceEvidence) -Term $term -Weight 8 -FieldName "sourceEvidence" -MatchedFields $matchedFields
|
|
|
|
if ($termScore -gt 0) {
|
|
[void]$matchedTerms.Add($term)
|
|
$score += $termScore
|
|
}
|
|
}
|
|
|
|
if ($matchedTerms.Count -eq $terms.Count) {
|
|
$score += 30
|
|
}
|
|
|
|
if ($score -gt 0) {
|
|
[void]$results.Add([pscustomobject][ordered]@{
|
|
score = $score
|
|
title = $item.title
|
|
domain = $item.domain
|
|
packageType = $item.packageType
|
|
slug = $item.slug
|
|
relativePath = $item.relativePath
|
|
fullPath = $item.fullPath
|
|
tags = @($item.tags)
|
|
updated = $item.updated
|
|
matchedTerms = @($matchedTerms)
|
|
matchedFields = @($matchedFields | Select-Object -Unique)
|
|
feature = $item.feature
|
|
businessRules = $item.businessRules
|
|
})
|
|
}
|
|
}
|
|
|
|
$resultArray = @($results.ToArray())
|
|
$ranked = @($resultArray |
|
|
Sort-Object `
|
|
@{ Expression = "score"; Descending = $true },
|
|
@{ Expression = "updated"; Descending = $true },
|
|
@{ Expression = "relativePath"; Descending = $false } |
|
|
Select-Object -First $Top)
|
|
|
|
if ($Json) {
|
|
if ($ranked.Count -eq 0) {
|
|
Write-Output "[]"
|
|
}
|
|
elseif ($ranked.Count -eq 1) {
|
|
Write-Output "[$($ranked[0] | ConvertTo-Json -Depth 8)]"
|
|
}
|
|
else {
|
|
ConvertTo-Json -InputObject $ranked -Depth 8
|
|
}
|
|
return
|
|
}
|
|
|
|
if ($ranked.Count -eq 0) {
|
|
Write-Host "No matching requirement packages found."
|
|
return
|
|
}
|
|
|
|
foreach ($result in $ranked) {
|
|
Write-Host "[$($result.score)] $($result.title) ($($result.domain)/$($result.slug))"
|
|
Write-Host " path: $($result.relativePath)"
|
|
if ($result.tags.Count -gt 0) {
|
|
Write-Host " tags: $(@($result.tags) -join ', ')"
|
|
}
|
|
Write-Host " matched: $(@($result.matchedTerms) -join ', ') in $(@($result.matchedFields) -join ', ')"
|
|
if (-not [string]::IsNullOrWhiteSpace($result.feature)) {
|
|
Write-Host " feature: $($result.feature)"
|
|
}
|
|
Write-Host ""
|
|
}
|
|
|
|
|