Pentia.UserSettings.psm1

<#
.SYNOPSIS
Stores a UserSettings-object to "$SolutionRootPath/.pentia/user-settings.json". Any existing file is overwritten.
 
.PARAMETER SolutionRootPath
OPTIONAL - Settings are saved to "$SolutionRootPath/.pentia/user-settings.json". The current directory ($PWD) is used as a fallback.
 
.PARAMETER Settings
A simple settings object containing webroot, data folder and build configuration settings.
 
.EXAMPLE
Set-UserSettings -Settings @{ webrootOutputPath = "$TestDrive\www"; dataOutputPath = "$TestDrive\data"; BuildConfiguration = "Debug"}
Saves the specified user settings to "<current directory>/.pentia/user-settings.json".
#>

function Set-UserSettings {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(Mandatory = $false)]
        [string]$SolutionRootPath,

        [Parameter(Mandatory = $true)]
        [UserSettings]$Settings
    )
    $settingsFilePath = Get-UserSettingsFilePath -SolutionRootPath $SolutionRootPath
    if (-not $pscmdlet.ShouldProcess($settingsFilePath, "Save and overwrite settings file")) {
        return
    }
    Write-Verbose "Saving user settings to '$settingsFilePath'."
    New-Item -Path $settingsFilePath -Force | Out-Null
    $SERIALIZATION_MAX_DEPTH = 100 # 100 is the maximum supported by "ConvertTo-Json"
    $Settings | ConvertTo-Json -Depth $SERIALIZATION_MAX_DEPTH | Out-File $settingsFilePath -Force
}

<#
.SYNOPSIS
Concatenates the provided path and the relative user settings file path (".\.pentia\user-settings.json").
 
.PARAMETER SolutionRootPath
OPTIONAL: The path to concatenate. If omitted, the current working directory is used.
 
.EXAMPLE
Get-UserSettingsFilePath
Return "<current working directory>\.pentia\user-settings.json"
 
Get-UserSettingsFilePath -SolutionRootPath "C:\Some-Path\"
Return "C:\Some-Path\.pentia\user-settings.json"
#>

function Get-UserSettingsFilePath {
    [CmdletBinding()]
    [OutputType([string])]
    param (
        [Parameter(Mandatory = $false)]
        [string]$SolutionRootPath
    )
    if ([string]::IsNullOrWhiteSpace($SolutionRootPath)) {
        $SolutionRootPath = $PWD
    }
    if (-not (Test-Path $SolutionRootPath)) {
        throw "Path '$SolutionRootPath' not found."
    }
    $absoluteSolutionRootPath = Resolve-Path $SolutionRootPath
    [System.IO.Path]::Combine($absoluteSolutionRootPath, ".pentia", "user-settings.json")
}

<#
.SYNOPSIS
Loads a UserSettings-object from "$SolutionRootPath/.pentia/user-settings.json".
 
.PARAMETER SolutionRootPath
OPTIONAL - Settings are loaded from "$SolutionRootPath/.pentia/user-settings.json". The current directory ($PWD) is used as a fallback.
 
.EXAMPLE
$settings = Get-UserSettings
Returns the previously saved user settings, or an empty object.
#>

function Get-UserSettings {
    [CmdletBinding()]
    [OutputType([UserSettings])]
    param (
        [Parameter(Mandatory = $false)]
        [string]$SolutionRootPath
    )
    $settingsFilePath = Get-UserSettingsFilePath -SolutionRootPath $SolutionRootPath
    if (Test-Path $settingsFilePath) {
        Write-Verbose "Reading user settings from '$settingsFilePath'."
        Get-Content -Path $settingsFilePath | ConvertFrom-Json
    }
    else {        
        Write-Verbose "No user settings found in '$settingsFilePath'."
        New-Object -TypeName UserSettings
    }
}

<#
.SYNOPSIS
Uses the specified settings as a fallback for each parameter that's null, empty or whitespace.
The intent is to provide a mechanism for overriding saved values while minimizing the need for mandatory parameters.
 
.EXAMPLE
Merge-ParametersAndUserSettings -Settings @{webrootOutputPath = "C:\Website\www"} -WebrootOutputPath $null
Returns @{webrootOutputPath = "C:\Website\www"}
 
Merge-ParametersAndUserSettings -Settings @{webrootOutputPath = "C:\Website\www"} -WebrootOutputPath "C:\SomeOtherFolder\www"
Returns @{webrootOutputPath = "C:\SomeOtherFolder\www"}
#>

function Merge-ParametersAndUserSettings {
    [CmdletBinding()]
    [OutputType([UserSettings])]
    param (
        [Parameter(Mandatory = $true)]
        [UserSettings]$Settings,

        [Parameter(Mandatory = $false)]
        [string]$WebrootOutputPath,

        [Parameter(Mandatory = $false)]
        [string]$DataOutputPath,

        [Parameter(Mandatory = $false)]
        [string]$BuildConfiguration
    )

    if ([string]::IsNullOrWhiteSpace($WebrootOutputPath)) {
        Write-Verbose "`$WebrootOutputPath is null or empty. Using setting value '$($Settings.webrootOutputPath)'."
    }
    else {
        $Settings.webrootOutputPath = $WebrootOutputPath        
    }

    if ([string]::IsNullOrWhiteSpace($DataOutputPath)) {
        Write-Verbose "`$DataOutputPath is null or empty. Using setting value '$($Settings.dataOutputPath)'."
    }
    else {
        $Settings.dataOutputPath = $DataOutputPath
    }

    if ([string]::IsNullOrWhiteSpace($BuildConfiguration)) {
        Write-Verbose "`$BuildConfiguration is null or empty. Using setting value '$($Settings.buildConfiguration)'."
    }
    else {
        $Settings.buildConfiguration = $BuildConfiguration        
    }
    
    $Settings
}

<#
.SYNOPSIS
1. Loads the settings found in "$SolutionRootPath\.pentia\user-settings.json", and uses them as a fallback values for each parameter that's null, empty or whitespace.
The intent is to provide a mechanism for overriding saved values while minimizing the need for mandatory parameters.
 
2. The merged settings are saved to "$SolutionRootPath\.pentia\user-settings.json".
 
3. Returns a settings object.
 
.EXAMPLE
$currentSettings = Get-MergedParametersAndUserSettings -SolutionRootPath "D:\Projects\SI\" -WebrootOutputPath "D:\MyNewOutputPath"
Returns the settings found in "D:\Projects\SI\.pentia\user-settings.json", with the $WebrootOutputPath overwritten to "D:\MyNewOutputPath", and saves these changed settings.
#>

function Get-MergedParametersAndUserSettings {
    [CmdletBinding()]
    [OutputType([UserSettings])]
    param (
        [Parameter(Mandatory = $true)]
        [string]$SolutionRootPath,

        [Parameter(Mandatory = $false)]
        [string]$WebrootOutputPath,

        [Parameter(Mandatory = $false)]
        [string]$DataOutputPath,

        [Parameter(Mandatory = $false)]
        [string]$BuildConfiguration
    )

    $userSettings = Get-UserSettings -SolutionRootPath $SolutionRootPath
    $mergedSettings = Merge-ParametersAndUserSettings -Settings $userSettings -WebrootOutputPath $WebrootOutputPath -DataOutputPath $DataOutputPath -BuildConfiguration $BuildConfiguration
    if ([string]::IsNullOrWhiteSpace($mergedSettings.webrootOutputPath)) {
        $mergedSettings.webrootOutputPath = Read-Host "Enter value for `$WebrootOutputPath"
    }
    if ([string]::IsNullOrWhiteSpace($mergedSettings.dataOutputPath)) {
        $mergedSettings.dataOutputPath = Read-Host "Enter value for `$DataOutputPath"
    }
    if ([string]::IsNullOrWhiteSpace($mergedSettings.buildConfiguration)) {
        $mergedSettings.buildConfiguration = Read-Host "Enter value for `$BuildConfiguration"
    }
    Set-UserSettings -SolutionRootPath $SolutionRootPath -Settings $mergedSettings
    $mergedSettings
}

Class UserSettings {
    [string]$webrootOutputPath
    [string]$dataOutputPath
    [string]$buildConfiguration
}

Export-ModuleMember -Function Get-UserSettingsFilePath, Get-UserSettings, Set-UserSettings, Merge-ParametersAndUserSettings, Get-MergedParametersAndUserSettings