modules/helpers.ps1
|
<#
.SYNOPSIS PSConsoleUI - Helper Functions .DESCRIPTION Core helper functions used by UI components. Includes: Border generation, text centering, value limiting, data validation. #> function Get-ConsoleBorder { <# .SYNOPSIS Generates border characters for console UI elements. .PARAMETER Width The width of the border. .PARAMETER Style The border style: Single, Double, Rounded, or Heavy. .PARAMETER Part The part of the border: Top, Bottom, Left, Right, TopLeft, TopRight, BottomLeft, BottomRight, Horizontal, Vertical. #> [CmdletBinding()] param( [Parameter(Mandatory=$false)] [int]$Width = 60, [Parameter(Mandatory=$false)] [ValidateSet('Single', 'Double', 'Rounded', 'Heavy')] [string]$Style = 'Single', [Parameter(Mandatory=$false)] [ValidateSet('Top', 'Bottom', 'Left', 'Right', 'TopLeft', 'TopRight', 'BottomLeft', 'BottomRight', 'Horizontal', 'Vertical', 'Full')] [string]$Part = 'Horizontal' ) $borders = @{ 'Single' = @{ Horizontal = [char]0x2500 Vertical = [char]0x2502 TopLeft = [char]0x250C TopRight = [char]0x2510 BottomLeft = [char]0x2514 BottomRight = [char]0x2518 } 'Double' = @{ Horizontal = [char]0x2550 Vertical = [char]0x2551 TopLeft = [char]0x2554 TopRight = [char]0x2557 BottomLeft = [char]0x255A BottomRight = [char]0x255D } 'Rounded' = @{ Horizontal = [char]0x2500 Vertical = [char]0x2502 TopLeft = [char]0x256D TopRight = [char]0x256E BottomLeft = [char]0x2570 BottomRight = [char]0x256F } 'Heavy' = @{ Horizontal = [char]0x2501 Vertical = [char]0x2503 TopLeft = [char]0x250F TopRight = [char]0x2513 BottomLeft = [char]0x2517 BottomRight = [char]0x251B } } $b = $borders[$Style] switch ($Part) { 'Horizontal' { return ([string]$b.Horizontal) * $Width } 'Vertical' { return $b.Vertical } 'TopLeft' { return $b.TopLeft } 'TopRight' { return $b.TopRight } 'BottomLeft' { return $b.BottomLeft } 'BottomRight' { return $b.BottomRight } 'Top' { return $b.TopLeft + (([string]$b.Horizontal) * ($Width - 2)) + $b.TopRight } 'Bottom' { return $b.BottomLeft + (([string]$b.Horizontal) * ($Width - 2)) + $b.BottomRight } 'Full' { return $b } } } function Get-CenteredText { <# .SYNOPSIS Centers text within a specified width. .PARAMETER Text The text to center. .PARAMETER Width The total width to center within. #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$Text, [Parameter(Mandatory=$true)] [int]$Width ) # Sanitizar input: eliminar caracteres nulos y no imprimibles que causan bugs visuales $cleanText = $Text -replace '[^\x20-\x7E]', '' $cleanText = $cleanText.Trim() # Prevenir padding negativo if ($cleanText.Length -ge $Width) { return $cleanText } $padding = ' ' * [Math]::Floor(($Width - $cleanText.Length) / 2) return "$padding$cleanText" } function Limit-Value { <# .SYNOPSIS Limits a value to a specified range. .PARAMETER Value The value to limit. .PARAMETER Min The minimum allowed value. .PARAMETER Max The maximum allowed value. #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [int]$Value, [Parameter(Mandatory=$true)] [int]$Min, [Parameter(Mandatory=$true)] [int]$Max ) return [Math]::Max($Min, [Math]::Min($Max, $Value)) } function Test-ConsoleData { <# .SYNOPSIS Validates data for console UI functions. .PARAMETER Data The data to validate. .PARAMETER FunctionName The name of the calling function (for error messages). .PARAMETER AllowEmpty Whether to allow empty data. #> [CmdletBinding()] param( [Parameter(Mandatory=$false)] $Data, [Parameter(Mandatory=$true)] [string]$FunctionName, [Parameter(Mandatory=$false)] [switch]$AllowEmpty ) if (-not $Data) { Write-Warning "${FunctionName}: No data provided" return $false } return $true } function Test-EmojiSupport { <# .SYNOPSIS Tests if the current console supports emoji characters. .DESCRIPTION Attempts to detect if the console can properly display emoji Unicode characters. Returns true if emojis are supported, false otherwise. #> [CmdletBinding()] param() try { # Check PowerShell version (Core 6+ has better emoji support) if ($PSVersionTable.PSVersion.Major -ge 6) { return $true } # Check if OutputEncoding supports UTF-8 if ([Console]::OutputEncoding.CodePage -eq 65001) { return $true } # Default to false for older systems return $false } catch { return $false } } function Set-LayoutBuffer { <# .SYNOPSIS Merges multiple string arrays (columns) into a single line-by-line buffer. #> param( [Parameter(Mandatory=$true)] [array]$Columns, [Parameter(Mandatory=$true)] [array]$Widths ) # Find max depth (lines) across all columns $maxLines = 0 foreach ($col in $Columns) { if ($col.Count -gt $maxLines) { $maxLines = $col.Count } } $mergedLines = @() for ($i = 0; $i -lt $maxLines; $i++) { $line = "" for ($c = 0; $c -lt $Columns.Count; $c++) { $colContent = $Columns[$c] $width = $Widths[$c] # Get specific line or empty space $content = "" if ($null -ne $colContent -and $colContent -is [array] -and $i -lt $colContent.Count) { $content = $colContent[$i].ToString() } # Pad content to fit column width # Remove only non-printable control characters (0-31), keep everything else $cleanContent = $content -replace '[\x00-\x1F]', '' if ($cleanContent.Length -gt $width) { $cleanContent = $cleanContent.Substring(0, $width) } $padding = ' ' * [Math]::Max(0, ($width - $cleanContent.Length)) $line += $cleanContent + $padding + ' ' # 2 space gap between columns } # Pad entire line to ensure it clears previous output residue ("Ghosting") $expectedTotal = ($Widths | Measure-Object -Sum).Sum + (($Columns.Count - 1) * 2) $finalLine = $line.TrimEnd() $linePadding = ' ' * [Math]::Max(0, ($expectedTotal - $finalLine.Length)) $mergedLines += ($finalLine + $linePadding) } return $mergedLines } function Invoke-CapturedOutput { <# .SYNOPSIS Executes a scriptblock and returns its output as an array of lines. Note: This only works with functions updated to support -ReturnLines. #> param([scriptblock]$Code) $output = @() & $Code | ForEach-Object { $output += $_ } return $output } function Get-ComponentIcon { <# .SYNOPSIS Returns appropriate icon for a component based on console capabilities. .DESCRIPTION Returns emoji icons if supported, falls back to safe Unicode icons otherwise. Implements automatic Versión B/C fallback strategy. .PARAMETER ComponentType Type of component: Metadata, Summary, Success, Warning, Error, Info #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [ValidateSet('Metadata', 'Summary', 'Success', 'Warning', 'Error', 'Info')] [string]$ComponentType ) # Safe icons that work everywhere (Unicode BMP) $safeIcons = @{ Metadata = [char]0x2699 # ⚙ Summary = [char]0x25A3 # ▣ Success = [char]0x221A # √ Warning = [char]0x26A0 # ⚠ Error = [char]0x2715 # ✕ Info = '[i]' # Bracketed style as per user preference } # Try to use emoji if supported if (Test-EmojiSupport) { try { switch ($ComponentType) { 'Summary' { return [System.Char]::ConvertFromUtf32(0x1F4CA) } # 📊 'Success' { return [char]0x2714 } # ✔ 'Warning' { return [char]0x26A0 } # ⚠ 'Error' { return [char]0x2716 } # ✖ 'Info' { return '[i]' } # Bracketed style as per user preference default { return $safeIcons[$ComponentType] } } } catch { return $safeIcons[$ComponentType] } } else { return $safeIcons[$ComponentType] } } # No local Export-ModuleMember here - handled by main module |