Private/DFCatalog.Crates.ps1
|
#Requires -Version 7.0 # crates.io catalog provider (query-cache kind). crates.io REQUIRES a # descriptive User-Agent — anonymous requests are rejected. function Invoke-DFCatalogCratesFetch { <# .SYNOPSIS Live crates.io search, normalized to DotForge.ToolSourceInfo. .PARAMETER Query Normalized query text. #> [CmdletBinding()] param( [Parameter(Mandatory, Position = 0)] [string]$Query ) $uri = "https://crates.io/api/v1/crates?q=$([uri]::EscapeDataString($Query))&per_page=15" $headers = @{ 'User-Agent' = 'DotForge PowerShell module (+https://github.com/simsrw73/DotForge)' } $response = Invoke-RestMethod -Uri $uri -Headers $headers -TimeoutSec 10 foreach ($crate in @($response.crates)) { $published = $null if ($crate.updated_at) { try { $published = [datetime]$crate.updated_at } catch {} } New-DFToolSourceInfo -Source 'crates' ` -PackageId $crate.name ` -Name $crate.name ` -Description ([string]$crate.description) ` -LatestVersion ([string]($crate.max_stable_version ?? $crate.max_version)) ` -Homepage ([string]($crate.homepage ?? $crate.repository)) ` -PublishedAt $published ` -MatchKind ($crate.name -eq $Query ? 'exact-name' : 'keyword') } } function Search-DFCatalogCrates { <# .SYNOPSIS Cache-first crates.io search. .PARAMETER Query Name or keywords. .PARAMETER Fresh Block on a live fetch. #> [CmdletBinding()] param( [Parameter(Mandatory, Position = 0)] [string]$Query, [switch]$Fresh ) Search-DFCatalogQueryCache -Provider 'crates' -Query $Query -Fresh:$Fresh ` -Fetch { param($q) Invoke-DFCatalogCratesFetch -Query $q } } function Get-DFCatalogCratesInstalled { <# .SYNOPSIS Lists cargo-installed binaries by parsing ~/.cargo/.crates2.json — no cargo.exe invocation. .PARAMETER CargoHome Cargo home directory (defaults to $Env:CARGO_HOME or ~/.cargo). #> [CmdletBinding()] param( [string]$CargoHome = ($Env:CARGO_HOME ? $Env:CARGO_HOME : (Join-Path $HOME '.cargo')) ) $file = Join-Path $CargoHome '.crates2.json' if (-not (Test-Path $file)) { return } try { $data = Get-Content $file -Raw | ConvertFrom-Json -AsHashtable } catch { return } if (-not $data.installs) { return } foreach ($spec in $data.installs.Keys) { # Keys look like: "ripgrep 14.1.1 (registry+https://github.com/rust-lang/crates.io-index)" $parts = $spec -split ' ' if ($parts.Count -ge 2) { [pscustomobject]@{ Source = 'crates' Name = $parts[0] PackageId = $parts[0] InstalledVersion = $parts[1] } } } } function Invoke-DFCatalogCratesDetailFetch { <# .SYNOPSIS Live crates.io crate-detail lookup, mapped to DotForge.ToolSourceDetail. .PARAMETER PackageId The crate name. #> [CmdletBinding()] param( [Parameter(Mandatory, Position = 0)] [string]$PackageId ) $headers = @{ 'User-Agent' = 'DotForge PowerShell module (+https://github.com/simsrw73/DotForge)' } $doc = Invoke-RestMethod -Uri "https://crates.io/api/v1/crates/$([uri]::EscapeDataString($PackageId))" -Headers $headers -TimeoutSec 10 $crate = $doc.crate if (-not $crate.name) { return $null } $extra = [ordered]@{} if ($null -ne $crate.recent_downloads) { $extra['recent_downloads'] = [long]$crate.recent_downloads } New-DFToolSourceDetail -Source 'crates' ` -PackageId $crate.name ` -Tags @(@(@($crate.keywords) + @($crate.categories)) | ForEach-Object { [string]$_ } | Where-Object { $_ }) ` -Downloads ([nullable[long]]$crate.downloads) ` -RepositoryUrl ([string]$crate.repository) ` -DocsUrl ([string]$crate.documentation) ` -InstallHint "cargo install $($crate.name)" ` -Extra $extra } function Get-DFCatalogCratesDetail { <# .SYNOPSIS Cache-first crates.io detail lookup. .PARAMETER PackageId The crate name. .PARAMETER Fresh Force a live fetch. #> [CmdletBinding()] param( [Parameter(Mandatory, Position = 0)] [string]$PackageId, [switch]$Fresh ) Get-DFCatalogDetailCache -Provider 'crates' -PackageId $PackageId -Fresh:$Fresh ` -Fetch { param($id) Invoke-DFCatalogCratesDetailFetch -PackageId $id } } if (-not (Get-Variable -Name DFCatalogProviders -Scope Script -ErrorAction Ignore)) { $script:DFCatalogProviders = @{} } $script:DFCatalogProviders['crates'] = @{ Name = 'crates' Kind = 'query-cache' Test = { $true } # pure web registry — searchable without a local toolchain Search = { param($Query, $Fresh) Search-DFCatalogCrates -Query $Query -Fresh:$Fresh } GetInstalled = { Get-DFCatalogCratesInstalled } Refresh = { param($Query) if ($Query) { $null = Search-DFCatalogCrates -Query $Query -Fresh } } Detail = { param($PackageId, $Fresh) Get-DFCatalogCratesDetail -PackageId $PackageId -Fresh:$Fresh } } |