scripts/modules/shared/path-functions.ps1
# strangeloop Setup - Shared Path Functions # Version: 1.0.0 # Cross-platform path handling and conversion functions function Convert-WindowsPathToWSL { <# .SYNOPSIS Converts a Windows path to WSL format .PARAMETER WindowsPath The Windows path to convert .EXAMPLE Convert-WindowsPathToWSL "C:\Users\john\project" # Returns: /mnt/c/users/john/project #> param( [Parameter(Mandatory)] [string]$WindowsPath ) if ([string]::IsNullOrEmpty($WindowsPath)) { return $WindowsPath } # Handle UNC paths if ($WindowsPath.StartsWith('\\')) { Write-Warning "UNC paths are not supported in WSL conversion: $WindowsPath" return $WindowsPath } # Convert drive letter paths (C:\path -> /mnt/c/path) if ($WindowsPath -match '^([A-Za-z]):(.*)$') { $driveLetter = $Matches[1].ToLower() $pathPart = $Matches[2] -replace '\\', '/' return "/mnt/$driveLetter$pathPart" } # If it's already a Unix-style path, return as-is if ($WindowsPath.StartsWith('/') -or $WindowsPath.Contains('/home/')) { return $WindowsPath } # For relative paths, just convert backslashes to forward slashes return $WindowsPath -replace '\\', '/' } function Convert-WSLPathToWindows { <# .SYNOPSIS Converts a WSL path to Windows format .PARAMETER WSLPath The WSL path to convert .EXAMPLE Convert-WSLPathToWindows "/mnt/c/users/john/project" # Returns: C:\users\john\project #> param( [Parameter(Mandatory)] [string]$WSLPath ) if ([string]::IsNullOrEmpty($WSLPath)) { return $WSLPath } # Handle /mnt/ paths if ($WSLPath -match '^/mnt/([a-z])/(.*)$') { $driveLetter = $Matches[1].ToUpper() $pathPart = $Matches[2] -replace '/', '\' return "${driveLetter}:\$pathPart" } # Handle /mnt/ paths without trailing content if ($WSLPath -match '^/mnt/([a-z])/?$') { $driveLetter = $Matches[1].ToUpper() return "${driveLetter}:\" } # If it's already a Windows-style path, return as-is if ($WSLPath -match '^[A-Za-z]:') { return $WSLPath } # For home directory paths (/home/), we can't convert without knowing the user if ($WSLPath.Contains('/home/')) { Write-Warning "Cannot convert WSL home directory path to Windows: $WSLPath" return $WSLPath } # For absolute Unix paths not under /mnt, we can't convert if ($WSLPath.StartsWith('/')) { Write-Warning "Cannot convert WSL absolute path to Windows: $WSLPath" return $WSLPath } # For relative paths, just convert forward slashes to backslashes return $WSLPath -replace '/', '\' } function Get-CrossPlatformPath { <# .SYNOPSIS Gets the appropriate path format for the current or specified platform .PARAMETER Path The path to convert .PARAMETER TargetPlatform The target platform: 'Windows', 'WSL', or 'Auto' (default) .EXAMPLE Get-CrossPlatformPath "C:\temp" -TargetPlatform WSL # Returns: /mnt/c/temp #> param( [Parameter(Mandatory)] [string]$Path, [ValidateSet('Windows', 'WSL', 'Auto')] [string]$TargetPlatform = 'Auto' ) if ([string]::IsNullOrEmpty($Path)) { return $Path } # Auto-detect target platform if not specified if ($TargetPlatform -eq 'Auto') { # Check if we're running in WSL $TargetPlatform = if ($env:WSL_DISTRO_NAME -or $env:WSLENV) { 'WSL' } else { 'Windows' } } # Determine current path format $isWindowsPath = $Path -match '^[A-Za-z]:' $isWSLPath = $Path.StartsWith('/') -or $Path.Contains('/home/') # Convert if needed if ($TargetPlatform -eq 'WSL' -and $isWindowsPath) { return Convert-WindowsPathToWSL $Path } elseif ($TargetPlatform -eq 'Windows' -and $isWSLPath) { return Convert-WSLPathToWindows $Path } else { return $Path } } function Test-WSLPath { <# .SYNOPSIS Tests if a path is in WSL format .PARAMETER Path The path to test #> param( [Parameter(Mandatory)] [string]$Path ) return $Path.StartsWith('/') -or $Path.Contains('/home/') } function Test-WindowsPath { <# .SYNOPSIS Tests if a path is in Windows format .PARAMETER Path The path to test #> param( [Parameter(Mandatory)] [string]$Path ) return $Path -match '^[A-Za-z]:' } function ConvertTo-NormalizedPath { <# .SYNOPSIS Normalizes a path by resolving relative components and standardizing separators .PARAMETER Path The path to normalize .PARAMETER Platform The target platform for separator normalization #> param( [Parameter(Mandatory)] [string]$Path, [ValidateSet('Windows', 'Unix', 'Auto')] [string]$Platform = 'Auto' ) if ([string]::IsNullOrEmpty($Path)) { return $Path } # Auto-detect platform if ($Platform -eq 'Auto') { $Platform = if (Test-WindowsPath $Path) { 'Windows' } else { 'Unix' } } # Normalize separators if ($Platform -eq 'Windows') { $normalizedPath = $Path -replace '/', '\' } else { $normalizedPath = $Path -replace '\\', '/' } # Remove duplicate separators if ($Platform -eq 'Windows') { $normalizedPath = $normalizedPath -replace '\\+', '\' } else { $normalizedPath = $normalizedPath -replace '/+', '/' } # Remove trailing separators (except for root) if ($Platform -eq 'Windows') { $normalizedPath = $normalizedPath -replace '\\$', '' # Keep trailing backslash for drive roots if ($normalizedPath -match '^[A-Za-z]:$') { $normalizedPath += '\' } } else { # Don't remove trailing slash from root / if ($normalizedPath -ne '/') { $normalizedPath = $normalizedPath -replace '/$', '' } } return $normalizedPath } function Get-RelativePath { <# .SYNOPSIS Gets the relative path from one location to another .PARAMETER From The base path .PARAMETER To The target path #> param( [Parameter(Mandatory)] [string]$From, [Parameter(Mandatory)] [string]$To ) try { # Try using built-in Resolve-Path if both paths exist if ((Test-Path $From) -and (Test-Path $To)) { $fromResolved = Resolve-Path $From $toResolved = Resolve-Path $To # Use .NET method for relative path calculation $uri1 = New-Object System.Uri($fromResolved.Path + [System.IO.Path]::DirectorySeparatorChar) $uri2 = New-Object System.Uri($toResolved.Path) $relativePath = $uri1.MakeRelativeUri($uri2).ToString() # Convert URI encoding back to regular characters $relativePath = [System.Uri]::UnescapeDataString($relativePath) # Convert forward slashes to platform-appropriate separators if ($env:OS -match "Windows") { $relativePath = $relativePath -replace '/', '\' } return $relativePath } } catch { # Fall back to manual calculation if built-in method fails } # Manual relative path calculation for cases where paths don't exist $fromParts = $From -split '[/\\]' | Where-Object { $_ -ne '' } $toParts = $To -split '[/\\]' | Where-Object { $_ -ne '' } # Find common prefix $commonLength = 0 for ($i = 0; $i -lt [Math]::Min($fromParts.Length, $toParts.Length); $i++) { if ($fromParts[$i] -eq $toParts[$i]) { $commonLength++ } else { break } } # Build relative path $upLevels = $fromParts.Length - $commonLength $downPath = $toParts[$commonLength..($toParts.Length - 1)] $relativeParts = @() for ($i = 0; $i -lt $upLevels; $i++) { $relativeParts += '..' } $relativeParts += $downPath $separator = if (Test-WindowsPath $From) { '\' } else { '/' } return $relativeParts -join $separator } function Expand-Path { <# .SYNOPSIS Expands environment variables and relative path components in a path .PARAMETER Path The path to expand .PARAMETER UseWSL Whether to use WSL for expansion (for WSL paths) #> param( [Parameter(Mandatory)] [string]$Path, [switch]$UseWSL ) if ([string]::IsNullOrEmpty($Path)) { return $Path } # Handle WSL home directory expansion if ($Path.Contains('/home/') -and $UseWSL) { try { $expandedPath = wsl -- bash -c "echo `"$Path`"" 2>$null if ($LASTEXITCODE -eq 0 -and -not [string]::IsNullOrEmpty($expandedPath)) { return $expandedPath.Trim() } } catch { Write-Warning "Failed to expand WSL path: $Path" } } # Handle Windows environment variable expansion if ($Path.Contains('%') -or $Path.Contains('$env:')) { try { return [System.Environment]::ExpandEnvironmentVariables($Path) } catch { Write-Warning "Failed to expand environment variables in path: $Path" } } # Try to resolve relative paths try { if (Test-Path $Path) { return (Resolve-Path $Path).Path } } catch { # If resolution fails, return the original path } return $Path } function Test-PathExists { <# .SYNOPSIS Tests if a path exists, with support for WSL paths .PARAMETER Path The path to test .PARAMETER UseWSL Whether to use WSL for testing (for WSL paths) #> param( [Parameter(Mandatory)] [string]$Path, [switch]$UseWSL ) if ([string]::IsNullOrEmpty($Path)) { return $false } # For WSL paths, use WSL to test if ((Test-WSLPath $Path) -and $UseWSL) { try { wsl -- test -e "$Path" | Out-Null return $LASTEXITCODE -eq 0 } catch { return $false } } # For Windows paths or when not using WSL return Test-Path $Path } function New-DirectoryIfNotExists { <# .SYNOPSIS Creates a directory if it doesn't exist, with support for WSL paths .PARAMETER Path The directory path to create .PARAMETER UseWSL Whether to use WSL for creation (for WSL paths) #> param( [Parameter(Mandatory)] [string]$Path, [switch]$UseWSL ) if ([string]::IsNullOrEmpty($Path)) { return $false } # For WSL paths, use WSL to create if ((Test-WSLPath $Path) -and $UseWSL) { try { if (-not (Test-PathExists $Path -UseWSL)) { wsl -- mkdir -p "$Path" | Out-Null return $LASTEXITCODE -eq 0 } return $true } catch { return $false } } # For Windows paths or when not using WSL try { if (-not (Test-Path $Path)) { New-Item -ItemType Directory -Path $Path -Force | Out-Null } return $true } catch { return $false } } # Note: These functions are available when this file is dot-sourced # Available functions: # - Convert-WindowsPathToWSL # - Convert-WSLPathToWindows # - Get-CrossPlatformPath # - Test-WSLPath # - Test-WindowsPath # - ConvertTo-NormalizedPath # - Get-RelativePath # - Expand-Path # - Test-PathExists # - New-DirectoryIfNotExists |