src/UI/BreadcrumbBuilder.ps1
|
# BreadcrumbBuilder — Convierte un path en segmentos para el AppHeader. # # Responsabilidad ÚNICA: dado un path file-system, devolver # @{ Segs = [string[]] ancestros; Current = [string] último segmento } # con colapso opcional cuando el path es muy profundo (>4 ancestros) — preserva # el drive + '…' + 2 últimos para mantener contexto sin romper layout horizontal. # # Función pura. No conoce UI ni Renderer. Sin state — solo métodos estáticos. # # Cross-platform: detecta paths POSIX (que empiezan con '/') y los procesa con # separador '/'. Paths con drive Windows (C:\, D:/) o relativos usan '\'. class BreadcrumbBuilder { # Por defecto colapsa cuando hay más de 4 ancestros. Caller puede pasar otro # límite si querés más detalle (e.g. screens con sidebar narrow). static [int] $DefaultMaxAncestors = 4 static [hashtable] Build([string]$path) { return [BreadcrumbBuilder]::Build($path, [BreadcrumbBuilder]::DefaultMaxAncestors) } static [hashtable] Build([string]$path, [int]$maxAncestors) { $tokens = [BreadcrumbBuilder]::_Tokenize($path) $parts = $tokens.Parts if ($parts.Count -eq 0) { return @{ Segs = @(); Current = '' } } if ($parts.Count -eq 1) { return @{ Segs = @(); Current = $parts[0] } } $current = $parts[-1] $ancestors = $parts[0..($parts.Count - 2)] if ($maxAncestors -ge 0 -and $ancestors.Count -gt $maxAncestors) { # Conservar drive + '…' + 2 últimos ancestros. $ancestors = @($ancestors[0], '…', $ancestors[-2], $ancestors[-1]) } return @{ Segs = $ancestors; Current = $current } } # Variante extendida: devuelve también AbsolutePaths — paths absolutos # reconstruidos hasta cada segmento. Sirve para hacer "drill" a un segmento # específico (Enter sobre el breadcrumb interactivo). El último elemento # siempre corresponde al Current. # # NO colapsa con … porque la navegación necesita TODOS los segmentos — # el caller decide si rendear todos o algunos. # # Resultado: # @{ # Segs = [string[]] todos los segmentos en orden (incluyendo current) # Current = último segmento # AbsolutePaths = [string[]] path absoluto correspondiente a cada segmento # } static [hashtable] BuildWithPaths([string]$path) { $tokens = [BreadcrumbBuilder]::_Tokenize($path) $parts = $tokens.Parts if ($parts.Count -eq 0) { return @{ Segs = @(); Current = ''; AbsolutePaths = @() } } $absolutePaths = @() if ($tokens.IsPosix) { # POSIX absoluto: cada AbsolutePath es '/' + parts unidos con '/'. $acc = '' foreach ($p in $parts) { $acc = "$acc/$p" $absolutePaths += $acc } } else { # Windows o relativo. Concatenación manual con '\' — NO Join-Path, # que tira DriveNotFoundException en POSIX cuando el primer parte # no parece un drive. for ($i = 0; $i -lt $parts.Count; $i++) { if ($i -eq 0) { # Primer segmento: si parece drive (termina en :), agregar separador. $absolutePaths += if ($parts[0] -match ':$') { $parts[0] + '\' } else { $parts[0] } } else { $prev = $absolutePaths[$i - 1].TrimEnd('\') $absolutePaths += "$prev\$($parts[$i])" } } } return @{ Segs = $parts Current = $parts[-1] AbsolutePaths = $absolutePaths } } # Tokeniza un path en segmentos detectando si es POSIX absoluto, Windows # con drive, o relativo. Centraliza la lógica de detección para que Build # y BuildWithPaths la compartan. hidden static [hashtable] _Tokenize([string]$path) { if ([string]::IsNullOrWhiteSpace($path)) { return @{ Parts = @(); IsPosix = $false } } # POSIX absoluto: empieza con '/' y NO es un path con drive Windows. $isPosix = $path.StartsWith('/') -and ($path -notmatch '^[A-Za-z]:') if ($isPosix) { $parts = @($path -split '/' | Where-Object { $_ -ne '' }) return @{ Parts = $parts; IsPosix = $true } } # Windows o relativo: normalizamos forward slashes a backslash y # spliteamos descartando vacíos (trailing slash, doble). $norm = $path -replace '/', '\' $parts = @($norm -split '\\' | Where-Object { $_ -ne '' }) return @{ Parts = $parts; IsPosix = $false } } } |