ghcups.psm1

Set-StrictMode -Version Latest

# Constant

Set-Variable dataDir -Option Constant -Value "$Env:ProgramData\ghcups"
Set-Variable ghcPathPattern -Option Constant -Value ([Regex]::Escape($Env:ChocolateyInstall) + '\\lib\\ghc\.[0-9]+\.[0-9]+\.[0-9]+\\tools\\ghc-[0-9]+\.[0-9]+\.[0-9]+\\bin')
Set-Variable cabalPathPattern -Option Constant -Value ([Regex]::Escape($Env:ChocolateyInstall) + '\\lib\\cabal.[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\\tools\\cabal-[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
Set-Variable configFileName -Option Constant -Value 'ghcups.yaml'

# Common

Function Find-Config () {
    Param (
        [Parameter(Mandatory)][String] $Dir
    )

    $configPath = ''
    While ($true) {
        If ($Dir -eq $Env:USERPROFILE -or [String]::IsNullOrEmpty($Dir)) {
            break
        }
        $test = Join-Path $Dir $configFileName
        If (Test-Path $test) {
            $configPath = $test
            break
        }
        $Dir = Split-Path $Dir -Parent
    }
    If ([String]::IsNullOrEmpty($configPath)) {
        $test = Join-Path $dataDir $configFileName
        If (Test-Path $test) {
            $configPath = $test
        }
        Else {
            ''
            return
        }
    }
    $configPath
}

Function Get-Config() {
    Param (
        [Parameter(Mandatory)][AllowNull()][AllowEmptyString()][String] $Path
    )

    If ([String]::IsNullOrEmpty($Path)) {
        $null
        return
    }
    ConvertFrom-Yaml (Get-Content $Path -Raw)
}

Function All() {
    Param ([Parameter(ValueFromPipeline)][Boolean[]] $ps)
    Begin { $acc = $true }
    Process { ForEach ($p in $ps) { $acc = $acc -and $p } }
    End { $acc }
}

Function Set-PathEnv() {
    Param (
        [Parameter(Mandatory)][String[]] $patterns,
        [Parameter(Mandatory)][AllowEmptyString()][String] $path
    )

    If (-not [String]::IsNullOrEmpty($path) -and -not (Test-Path $path)) {
        Write-Warning "`"$path`" is not an existing path"
    }
    $restPaths = $Env:Path -Split ';' | Where-Object { $v = $_; $patterns | ForEach-Object { $v -NotMatch $_ } | All }
    $newPaths = ,$path + $restPaths | Where-Object { -not [String]::IsNullOrEmpty($_) }
    Set-Item Env:\Path -Value ($newPaths -Join ';')
}

Function Get-InstalledChocoItems() {
    Param (
        [Parameter(Mandatory)][String] $App
    )

    $path = "$Env:ChocolateyInstall\lib\$App."
    Get-Item "$path*" | ForEach-Object { ([String]$_).Remove(0, "$path".Length) }
}

# .SYNOPSIS
# Creats the ghcups.yaml with the default contents.
function Write-GhcupsConfigTemplate () {
    Param (
        [String] $Path = '.'
    )

    "# The key is the name you want, the value is the path of directory which contains ghc, ghci, etc.`nghc: {}`n`n# The same with ghc for cabal.`ncabal: {}" | Out-File (Join-Path $Path $configFileName) -NoClobber
}

# GHC

Function Get-GhcPatterns() {
    Param (
        [Parameter(Mandatory)][AllowNull()][Hashtable] $Config
    )

    $patterns = ,$ghcPathPattern
    If ($null -ne $Config -and $null -ne $Config['ghc']) {
        ForEach ($path in $Config.ghc.Values) {
            If (-not [String]::IsNullOrEmpty($path)) {
                $patterns += '\A' + [Regex]::Escape($path) + '\Z'
            }
        }
    }
    $patterns
}

Function Get-ChocoGhc() {
    Param (
        [Parameter(Mandatory)][String] $Ghc
    )

    "$Env:ChocolateyInstall\lib\ghc.$Ghc\tools\ghc-$Ghc\bin"
}

# .SYNOPSIS
# Sets the version or variant of GHC to the Path environment variable of the current session.
Function Set-Ghc() {
    Param (
        [Parameter(Mandatory)][String] $Ghc
    )

    $config = Get-Config (Find-Config (Get-Location))
    $ghcDir = ''
    If ($null -eq $config -or $null -eq $config['ghc'] -or $null -eq $config['ghc'][$Ghc]) {
        $ghcDir = Get-ChocoGhc $Ghc
    }
    Else {
        $ghcDir = $config['ghc'][$Ghc]
    }
    Set-PathEnv (Get-GhcPatterns $config) $ghcDir
}

# .SYNOPSIS
# Removes all GHC values from the Path environment variable of the current session.
Function Clear-Ghc() {
    Set-PathEnv (Get-GhcPatterns (Get-Config (Find-Config (Get-Location)))) $null
}

# .SYNOPSIS
# Installs the specified GHC with the Chocolatey.
Function Install-Ghc() {
    Param (
        [Parameter(Mandatory)][String] $Ghc,
        [Switch] $Set = $false
    )

    choco install ghc --version $Ghc --side-by-side

    If ($Set) {
        Set-Ghc -Ghc $Ghc
    }
}

# .SYNOPSIS
# Uninstalls the specified GHC with the Chocolatey.
Function Remove-Ghc() {
    Param (
        [Parameter(Mandatory)][String] $Ghc
    )

    choco uninstall ghc --version $Ghc
}

# .SYNOPSIS
# Shows the GHCs which is specified by the ghcups.yaml, is installed by the Chocolatey and is hosted on the Chocolatey repository.
Function Show-Ghc() {
    $configPath = Find-Config (Get-Location)
    $config = Get-Config $configPath
    $span = $false
    If ($null -ne $config -and $null -ne $config['ghc'] -and 0 -lt $config['ghc'].Count) {
        Write-Host "$configFileName ($(Split-Path $configPath -Parent))"
        ForEach ($k in $config.ghc.Keys) {
            Write-Host " ${k}: $($config.ghc[$k])"
        }
        $span = $true
    }
    $chocoGhcs = Get-InstalledChocoItems 'ghc'
    If ($null -ne $chocoGhcs) {
        If ($span) {
            Write-Host
        }
        Write-Host 'Chocolatey (Installed)'
        ForEach ($g in $chocoGhcs) {
            Write-Host " ${g}: $Env:ChocolateyInstall\lib\ghc.$g\tools\ghc-$g\bin"
        }
        $span = $true
    }
    If ($span) {
        Write-Host
    }
    choco list ghc --by-id-only --all-versions
}

# Cabal

Function Get-CabalPatterns() {
    Param (
        [Parameter(Mandatory)][AllowNull()][Hashtable] $Config
    )

    $patterns = ,$cabalPathPattern
    If ($null -ne $Config -and $null -ne $Config['cabal']) {
        ForEach ($path in $Config.cabal.Values) {
            If (-not [String]::IsNullOrEmpty($path)) {
                $patterns += [Regex]::Escape($path)
            }
        }
    }
    $patterns
}

Function Get-ChocoCabal() {
    Param (
        [Parameter(Mandatory)][String] $Cabal
    )

    "$Env:ChocolateyInstall\lib\cabal.$Cabal\tools\cabal-$Cabal"
}

# .SYNOPSIS
# Sets the version or variant of Cabal to the Path environment variable of the current session.pa
Function Set-Cabal() {
    Param (
        [Parameter(Mandatory)][String] $Cabal
    )

    $config = Get-Config (Get-Location)
    $cabalDir = ''
    If ($null -eq $config -or $null -eq $config['cabal'] -or $null -eq $config['cabal'][$Cabal]) {
        $cabalDir = Get-ChocoCabal $Cabal
    }
    Else {
        $cabalDir = $config['cabal'][$Cabal]
    }
    Set-PathEnv (Get-CabalPatterns $config) $cabalDir
}

# .SYNOPSIS
# Removes all Cabal values from the Path environment variable of the current session.
Function Clear-Cabal() {
    Set-PathEnv (Get-CabalPatterns (Get-Config (Get-Location))) $null
}

# .SYNOPSIS
# Installs the specified Cabal with the Chocolatey.
Function Install-Cabal() {
    Param (
        [Parameter(Mandatory)][String] $Cabal,
        [Switch] $Set = $false
    )

    choco install cabal --version $Cabal --side-by-side

    If ($Set) {
        Set-Cabal -Cabal $Cabal -Method $Method
    }
}

# .SYNOPSIS
# Uninstalls the specified Cabal with the Chocolatey.
Function Remove-Cabal() {
    Param (
        [Parameter(Mandatory)][String] $Cabal
    )

    choco uninstall cabal --version $Cabal
}

# .SYNOPSIS
# Shows the Cabals which is specified by the ghcups.yaml, is installed by the Chocolatey and is hosted on the Chocolatey repository.
Function Show-Cabal() {
    $configPath = Find-Config (Get-Location)
    $config = Get-Config $configPath
    $span = $false
    If ($null -ne $config -and $null -ne $config['cabal'] -and 0 -lt $config['cabal'].Count) {
        Write-Host "$configFileName ($(Split-Path $configPath -Parent))"
        ForEach ($k in $config.cabal.Keys) {
            Write-Host " ${k}: $($config.cabal[$k])"
        }
        $span = $true
    }
    $chocoCabals = Get-InstalledChocoItems 'cabal'
    If ($null -ne $chocoCabals) {
        If ($span) {
            Write-Host
        }
        Write-Host 'Chocolatey (Installed)'
        ForEach ($c in $chocoCabals) {
            Write-Host " ${c}: $Env:ChocolateyInstall\lib\cabal.$c\tools\cabal-$c"
        }
        $span = $true
    }
    If ($span) {
        Write-Host
    }
    choco list cabal --by-id-only --all-versions
}

# Export

Export-ModuleMember -Function 'Set-Ghc', 'Clear-Ghc', 'Install-Ghc', 'Remove-Ghc', 'Show-Ghc', 'Set-Cabal', 'Clear-Cabal', 'Install-Cabal', 'Remove-Cabal', 'Show-Cabal', 'Write-GhcupsConfigTemplate'