public/Get-BucketObject.ps1
|
function Get-BucketObject { <# .SYNOPSIS Retrieves objects from one or more buckets. .DESCRIPTION Reads serialized objects from bucket directories. When no bucket is specified, reads from the "default" bucket. Supports exact-match hashtable filtering (-Match) and arbitrary scriptblock filtering (-Filter). Retrieved objects include metadata properties: _BucketName, _BucketKey, _BucketFile. .PARAMETER Key Object key(s) to retrieve (Position 0). Accepts multiple values (e.g. -Key "alpha", "beta"). Case-insensitive prefix match for each key. .PARAMETER Bucket Bucket name(s) to search (Position 1). If omitted, reads from the "default" bucket. Supports wildcards. .PARAMETER Path Root directory for bucket storage. Default: $HOME/.buckets. .PARAMETER Match Hashtable of property-value pairs for exact-match filtering. All pairs must match. Supports $null values. .PARAMETER Filter ScriptBlock for custom filtering. Use $_ to reference object properties (e.g., { $_.Age -gt 30 }). .PARAMETER Recurse Recurse into nested sub-buckets. Without this switch, only returns objects from the specified bucket directory. .PARAMETER Depth Maximum nesting depth when recursing. Default: unlimited. Depth 1 returns objects from the root bucket only (same as no -Recurse). Depth 2 adds immediate sub-buckets. .PARAMETER All Search across all buckets. Implies recursion (all sub-buckets included) unless overridden by -Depth 1. Use with -Bucket to narrow which buckets to scan. .PARAMETER First Return only the first N objects. .PARAMETER Skip Skip the first N objects before returning results. .OUTPUTS Deserialized PSObjects with _BucketName, _BucketKey, and _BucketFile metadata. .EXAMPLE Get-BucketObject -Bucket users .EXAMPLE Get-BucketObject "Alice" users .EXAMPLE Get-BucketObject -Bucket users -Match @{ Role = "admin" } .EXAMPLE Get-BucketObject -Bucket users -Match @{ Deleted = $null } .EXAMPLE Get-BucketObject -Filter { $_.Status -eq "shipped" -and $_.Shipping.Method -eq "Express" } .EXAMPLE Get-BucketObject -Bucket users, orders .EXAMPLE Get-BucketObject -Bucket org .EXAMPLE Get-BucketObject -First 10 -Skip 20 .EXAMPLE Get-BucketObject -All .EXAMPLE Get-BucketObject "config" -All .EXAMPLE Get-BucketObject -Bucket users -All #> [CmdletBinding()] param( [Parameter(Position = 0)][string[]]$Key, [string]$Path, [Parameter(Position = 1)][string[]]$Bucket, [hashtable]$Match, [scriptblock]$Filter, [switch]$Recurse, [int]$Depth = [int]::MaxValue, [int]$First, [int]$Skip, [object]$Funnel, [switch]$Expand, [switch]$All ) if ([string]::IsNullOrWhiteSpace($Path)) { $Path = Get-DefaultPath } $Path = Resolve-SafePath -Path $Path $bucketPaths = @() if ($All) { $allBuckets = Get-Bucket -Path $Path -Recurse -Depth $Depth $selectedBuckets = $allBuckets if ($Bucket -and $Bucket.Count -gt 0) { $selectedBuckets = foreach ($b in $Bucket) { $allBuckets | Where-Object { if ($b -match '[\*\?]') { $_.Name -like $b } else { $_.Name -eq $b -or $_.Name -like "$b/*" } } } } if ($PSBoundParameters.ContainsKey('Depth') -and $Depth -eq 1) { $selectedBuckets = $selectedBuckets | Where-Object { $_.Name -notmatch '[/\\]' } } $bucketPaths = @($selectedBuckets | ForEach-Object { $_.Path }) } elseif ($Bucket -and $Bucket.Count -gt 0) { $cachedBuckets = $null foreach ($b in $Bucket) { if ($b -match '[\*\?]') { if ($null -eq $cachedBuckets) { $cachedBuckets = Get-Bucket -Path $Path -Recurse:$Recurse -Depth $Depth } $matched = $cachedBuckets | Where-Object { $_.Name -like $b } $bucketPaths += $matched | ForEach-Object { $_.Path } } else { $bp = Get-BucketPath -Name $b -Path $Path $bucketPaths += $bp if ($Recurse) { $nested = Get-Bucket -Path $Path -Recurse -Depth $Depth | Where-Object { $_.Name -like "$b/*" } $bucketPaths += $nested | ForEach-Object { $_.Path } } } } } else { $bucketPaths += Get-BucketPath -Name "default" -Path $Path } $funnelDef = Resolve-Funnel $Funnel $allObjects = [System.Collections.ArrayList]::new() $warnedBuckets = @{} $firstLimit = if ($First -gt 0) { $First + [Math]::Max(0, $Skip) } else { 0 } $firstReached = $false :bucketLoop foreach ($bucketPath in $bucketPaths) { if ($firstReached) { break } if (-not [System.IO.Directory]::Exists($bucketPath)) { $bucketLeaf = Split-Path $bucketPath -Leaf if (-not $warnedBuckets.ContainsKey($bucketLeaf)) { Write-Warning "Bucket '$bucketLeaf' not found" $warnedBuckets[$bucketLeaf] = $true } continue } $rel = $bucketPath.Substring($Path.Length).TrimStart([System.IO.Path]::DirectorySeparatorChar) $bucketName = $rel -replace [regex]::Escape([System.IO.Path]::DirectorySeparatorChar), '/' if ($Expand -and ($null -eq $Key -or $Key.Count -eq 0)) { $reconstructed = Reconstruct-Object -DirPath $bucketPath if ($null -ne $reconstructed) { if ($Filter) { if ($null -eq ($reconstructed | Where-Object $Filter)) { continue } } Add-HiddenProperty -Target $reconstructed -Name '_BucketName' -Value $bucketName Add-HiddenProperty -Target $reconstructed -Name '_BucketKey' -Value $null Add-HiddenProperty -Target $reconstructed -Name '_BucketFile' -Value $null $null = $allObjects.Add($reconstructed) if ($firstLimit -gt 0 -and $allObjects.Count -ge $firstLimit) { $firstReached = $true; break } } } else { $files = Get-ObjectFiles -BucketPath $bucketPath -Key $Key :fileLoop foreach ($file in $files) { if ($firstReached) { break } if ($null -eq $file -or -not [System.IO.File]::Exists($file.FullName)) { continue } $obj = Read-BucketFile -File $file if ($null -eq $obj) { continue } if ($Match -and -not (Test-MatchFilter -Object $obj -Match $Match)) { continue } if ($Filter) { if ($null -eq ($obj | Where-Object $Filter)) { continue } } if ($funnelDef) { $matchesAppliesTo = -not $funnelDef.ContainsKey('AppliesTo') -or ($null -ne ($obj | Where-Object $funnelDef.AppliesTo)) if ($matchesAppliesTo) { $funnelItems = @($obj | ForEach-Object $funnelDef.Transform) | Where-Object { $_ -ne $null } foreach ($subItem in $funnelItems) { if ($firstReached) { break fileLoop } $relativePath = $file.FullName.Substring($bucketPath.Length).TrimStart([System.IO.Path]::DirectorySeparatorChar) $keyWithoutExt = [System.IO.Path]::ChangeExtension($relativePath, $null).TrimEnd('.') Add-HiddenProperty -Target $subItem -Name '_BucketName' -Value $bucketName Add-HiddenProperty -Target $subItem -Name '_BucketKey' -Value $keyWithoutExt Add-HiddenProperty -Target $subItem -Name '_BucketFile' -Value $file.FullName $null = $allObjects.Add($subItem) if ($firstLimit -gt 0 -and $allObjects.Count -ge $firstLimit) { $firstReached = $true; break fileLoop } } continue } } $relativePath = $file.FullName.Substring($bucketPath.Length).TrimStart([System.IO.Path]::DirectorySeparatorChar) $keyWithoutExt = [System.IO.Path]::ChangeExtension($relativePath, $null).TrimEnd('.') Add-HiddenProperty -Target $obj -Name '_BucketName' -Value $bucketName Add-HiddenProperty -Target $obj -Name '_BucketKey' -Value $keyWithoutExt Add-HiddenProperty -Target $obj -Name '_BucketFile' -Value $file.FullName $null = $allObjects.Add($obj) if ($firstLimit -gt 0 -and $allObjects.Count -ge $firstLimit) { $firstReached = $true; break fileLoop } } if ($firstReached) { break } if ($Expand -and $Key -and $Key.Count -gt 0) { $subDirs = [System.IO.DirectoryInfo]::new($bucketPath).GetDirectories() | Where-Object { $_.Name -ne '.buckets' } $keysLower = @($Key | ForEach-Object { $_.ToLowerInvariant() }) foreach ($subDir in $subDirs) { $dirName = $subDir.Name.ToLowerInvariant() $matched = $false foreach ($tk in $keysLower) { $matched = if ($tk -match '[\*\?]') { $dirName -like $tk } else { $dirName -eq $tk } if ($matched) { break } } if (-not $matched) { continue } $reconstructed = Reconstruct-Object -DirPath $subDir.FullName if ($null -eq $reconstructed) { continue } if ($Filter) { if ($null -eq ($reconstructed | Where-Object $Filter)) { continue } } Add-HiddenProperty -Target $reconstructed -Name '_BucketName' -Value $bucketName Add-HiddenProperty -Target $reconstructed -Name '_BucketKey' -Value $subDir.Name Add-HiddenProperty -Target $reconstructed -Name '_BucketFile' -Value $null $null = $allObjects.Add($reconstructed) if ($firstLimit -gt 0 -and $allObjects.Count -ge $firstLimit) { $firstReached = $true; break } } } } } $emitted = 0; $skipped = 0 foreach ($obj in $allObjects) { if ($Skip -gt 0 -and $skipped -lt $Skip) { $skipped++; continue } if ($First -gt 0 -and $emitted -ge $First) { break } Write-Output $obj; $emitted++ } } |