Includes/PwSh.Fw.Write.psm1

using namespace System.Management.Automation
<#
 
    .SYNOPSIS
    Resource file to export useful functions to prettify output
 
    .DESCRIPTION
 
    .NOTES
        Author: Charles-Antoine Degennes <cadegenn@gmail.com>
        New-ModuleManifest api.psd1 -RootModule api.psm1 -ModuleVersion "0.0.1" -Author "Charles-Antoine Degennes <cadegenn@gmail.com>"
#>


if (!($Global:QUIET)) { $Global:QUIET = $false }
$Script:NS = (get-item $PSCommandPath).basename

# # Error codes enum
# Enum pwshfwERROR {
# OK = 0
# FAILED = 1
# RUNNING = 2
# MISSED = 3
# SKIPPED = 4
# UNUSED = 5
# UNKNOWN = 6
# DEAD = 7
# NOTFOUND = 8
# }

$Script:indent = ""
$Script:prepend = " * "
$Script:postpend = ""
$Script:DisplaySeverity = $true
$Script:titleChar = "*"
$Script:lineBreakChar = "-"

# Position of return code
class PwshfwValidReturnCodePosition : IValidateSetValuesGenerator {

    static $ValidValues = @('BEGINNIG', 'END')

    ## GetValidValues
    ## @brief used by ValidateSet parameter validator
    [string[]] GetValidValues() {
        # $Values = $this.ValidValues
        return [PwshfwValidReturnCodePosition]::ValidValues
    }
}
$Script:RCPos = 'END'
$Script:RCLength = 8
$Script:RCOpenChar = '['
$Script:RCCloseChar = ']'

function Set-ReturnCodePosition {
    [CmdletBinding()]
    [OutputType([String])]
    Param (
        [ValidateSet([PwshfwValidReturnCodePosition])]
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][string]$position
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        $Script:RCPos = $position
    }

    End {
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
Set new indentation
 
.DESCRIPTION
Set-Indent permit user to set indentation regardless of where indentation actually is.
 
.PARAMETER String
String parameter must be only composed of ' ' (space) character. It sets the indentation with literal given string
 
.PARAMETER Int
Int is the number of space character to indent
 
.EXAMPLE
Set-Indent -String " "
 
.EXAMPLE
Set-Indent -Int 8
 
.EXAMPLE
Set-Indent -String ""
Resets the indentation string. It is equivalent as calling Reset-Indent
 
.NOTES
General notes
 
.LINK
#>


function Set-Indent {
    [CmdletBinding()][OutputType([String])]Param (
        [ValidatePattern('^ *$')]
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true, ParameterSetName = 'STRING')][string]$String,
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true, ParameterSetName = 'INT')][UInt16]$Int
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        switch ($PSCmdlet.ParameterSetName) {
            'STRING' {
                $Script:indent = $String
            }
            'INTEGER' {
                $Script:indent = " " * $Int
            }
        }
    }

    End {
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
Reset indentation to its default
 
.DESCRIPTION
Seomtimes indentation can be messed up with function that do not return properly, try-catch block, etc...
In this cases it is wishable to reset indentation string.
The default indentation is an empty string.
 
.EXAMPLE
Reset-Indent
 
.NOTES
General notes
 
.LINK
#>


function Reset-Indent {
    # [CmdletBinding()][OutputType([String])]Param (
    # [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][string]$string
    # )
    Begin {
        Write-EnterFunction
    }

    Process {
        $Script:indent = ""
    }

    End {
        Write-LeaveFunction
    }
}

<#
    .SYNOPSIS
    Indent further calls to e*() functions
 
    .DESCRIPTION
    Indent with 2 spaces
 
    .NOTES
        TODO:
            . add parameter to override indent size
 
.LINK
#>

function Write-Indent() {
    [CmdletBinding()]
    [OutputType([String])]
    param(
        [switch]$PassThru
    )
    $Script:indent += " "
    if ($PassThru) { return $Script:indent }
}

<#
    .SYNOPSIS
    Outdent further calls to e*() functions
 
    .DESCRIPTION
    un-indent for 2 spaces
 
    .NOTES
        TODO:
            . add parameter to override indent size
 
.LINK
#>

function Write-Outdent() {
    [CmdletBinding()]
    [OutputType([String])]
    param(
        [switch]$PassThru
    )
    if ($Script:indent.Length -gt 3) {
        $Script:indent = $Script:indent.Substring(0,$Script:indent.Length - 3)
    } else {
        $Script:indent = ""
    }
    if ($PassThru) { return $Script:indent }
}

<#
    .SYNOPSIS
    Print a title
 
    .DESCRIPTION
    Print a title in green
 
 
.LINK
#>

function Write-Title() {
    [CmdletBinding()]param(
        [string]$message,
        [switch]$PassThru
    )
    $hr = "*" * ($message.Length + $indent.length + 6)
    $message = "** " + $indent + $message + " **"
    Write-ToLogFile -NoNewline -Message $hr
    Write-ToLogFile -NoNewline -Message $message
    if ($PassThru) {
        return $message
    } else {
        if ($Global:QUIET -eq $false) { Write-Host -ForegroundColor Green "`n`n$hr" }
        if ($Global:QUIET -eq $false) { Write-Host -NoNewline -ForegroundColor Green $message }
    }
}

<#
    .SYNOPSIS
    Print a message without new line
 
    .DESCRIPTION
    Print a message without new line
 
     .PARAMETER message
    Text to display on screen
 
     .PARAMETER width
    Optional. Used to pad text to the left.
 
 
.LINK
#>

function Write-Begin() {
    [CmdletBinding()]param(
        [string]$message,
        [int32]$width = $Host.UI.RawUI.WindowSize.Width
    )
    $fullMessage = $prepend + $indent + $message + "... "
    #$ht = "." * ($Host.UI.RawUI.WindowSize.Width - $message.Length)
    $width = $width - 16
    if ($Global:QUIET -eq $false) { Write-Host -NoNewline -ForegroundColor DarkGreen $("`n{0,-$width}" -f $fullMessage) }
    Write-ToLogFile -NoNewline -Message $fullMessage
}

<#
    .SYNOPSIS
    Add message to current line.
 
    .DESCRIPTION
    Add text to current line of text. No new line at the beginning, no new line at the end.
 
 
.LINK
#>

function Write-Add() {
    [CmdletBinding()]param(
        [string]$message
    )
    if ($Global:QUIET -eq $false) { Write-Host -NoNewline $Message }
    Write-ToLogFile -NoNewline -NoHeader -Message $message
}

function Write-ReturnCode {
    [CmdletBinding()]
    [OutputType([String])]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true, ParameterSetName = 'STRING')][string]$string,
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true, ParameterSetName = 'CODE')][string]$code,
        [ValidateSet([PwshfwValidReturnCodePosition])][string]$Position = $Script:RCPos
    )

    switch ($PSCmdlet.ParameterSetName) {
        'STRING' {
            $code = Get-ReturnCodeId -id "$string"
        }
        'CODE' {
            $string = Get-ReturnCodeString -code $code
        }
    }
    $color = Get-ReturnCodeColor -code $code
    $message = "{0} {1,-$Script:RCLength} {2} " -f $Script:RCOpenChar, $string, $Script:RCCloseChar
    if ($Global:QUIET -eq $false) {
        if ($Host) {
            switch ($Position) {
                'BEGINNIG' {
                    $X = 0
                    $Host.UI.RawUI.CursorPosition = New-Object System.Management.Automation.Host.Coordinates $X,$Host.UI.RawUI.CursorPosition.Y
                }
                'END' {
                    $Width = $Host.UI.RawUI.WindowSize.Width
                    $X = $Width - $message.length
                    $Host.UI.RawUI.CursorPosition = New-Object System.Management.Automation.Host.Coordinates $X,$Host.UI.RawUI.CursorPosition.Y
                }
            }
        }
        Write-Host -NoNewline -ForegroundColor $color $message
    }
    Write-ToLogFile -NoNewline -NoHeader -Message "`t$message"

}

<#
    .SYNOPSIS
    Print a message depending on return code
 
    .DESCRIPTION
    Print a message of status code.
    All status MUST have the same length. It is used to properly align all messages
 
 
.LINK
#>

function Write-End() {
    [CmdletBinding()]param(
        $errorCode
    )

    if (-not($errorCode)) { $errorCode = $false }
    $color = "White"  ; $message = $errorCode

    switch -wildcard ($errorCode.GetTYpe().Name) {
        "Bool*" {
            switch ($errorCode) {
                $true  { $color = "Green"   ; $message = " ok " }
                $false { $color = "Red"     ; $message = " failed " }
            }
        }
        "Int*" {
            switch ($errorCode) {
                # 0 is never called since `Write-End 0` goes to the bool switch
                # 0 { $color = "Green"; $message = " ok " }
                1    { $color = "Red";        $message = " failed " }
                2    { $color = "DarkGreen"; $message = " running " }
                3    { $color = "Yellow";    $message = " missed " }
                4    { $color = "Gray";        $message = " skipped " }
                5    { $color = "Gray";        $message = " unused " }
                6    { $color = "Gray";        $message = " unknown " }
                7    { $color = "Red";        $message = " dead " }
                8    { $color = "Gray";        $message = "not found" }
            }
        }
        # "pwshfwERROR" {
        # switch ($errorCode) {
        # ([pwshfwERROR]::OK) { $color = "Green"; $message = " ok " }
        # ([pwshfwERROR]::FAILED) { $color = "Red"; $message = " failed " }
        # ([pwshfwERROR]::RUNNING) { $color = "DarkGreen"; $message = " running " }
        # ([pwshfwERROR]::MISSED) { $color = "Yellow"; $message = " missed " }
        # ([pwshfwERROR]::SKIPPED) { $color = "Gray"; $message = " skipped " }
        # ([pwshfwERROR]::UNUSED) { $color = "Gray"; $message = " unused " }
        # ([pwshfwERROR]::UNKNOWN) { $color = "Gray"; $message = " unknown " }
        # ([pwshfwERROR]::DEAD) { $color = "Red"; $message = " dead " }
        # ([pwshfwERROR]::NOTFOUND) { $color = "Gray"; $message = "not found" }
        # default { $color = "White"; $message = $errorCode }
        # }
        # }
    }
    $message = "[ " + $message + " ]"
    if ($Global:QUIET -eq $false) { Write-Host -NoNewline -ForegroundColor $color $message }
    Write-ToLogFile -NoNewline -NoHeader -Message $message
}

<#
    .SYNOPSIS
    Print a message when entering a function
 
    .DESCRIPTION
    Print a message specifically when entering a function.
 
    .EXAMPLE
    # eenter ""
 
.LINK
 
#>

function Write-EnterFunction() {
    # [CmdletBinding()]param(
    # [string]$message
    # )
    $callStack = Get-PSCallStack
    if ($callStack.Count -gt 1) {
        $message = "$($callStack[1].InvocationInfo.MyCommand.Module)\$($callStack[1].Command)"
        # $callStack[1] | ConvertTo-Json | Set-Content /tmp/callstack.txt
        # $callStack[1].InvocationInfo | ConvertTo-Json | Set-Content /tmp/callstack.InvocationInfo.txt
    }
    $message = ">> " + $message + "()"
    if ($Global:TRACE) {
        Write-Devel ($message)
        eindent
    }
}

<#
    .SYNOPSIS
    Print a message when leaving a function
 
    .DESCRIPTION
    Print a message specifically when entering a function.
 
    .EXAMPLE
    # eenter ""
 
.LINK
 
#>

function Write-LeaveFunction() {
    # [CmdletBinding()]param(
    # [string]$message
    # )
    $callStack = Get-PSCallStack
    if ($callStack.Count -gt 1) {
        $message = "$($callStack[1].InvocationInfo.MyCommand.Module)\$($callStack[1].Command)"
        # $callStack[1] | fl *
    }
    $message = "<< " + $message + "()"
    if ($Global:TRACE) {
        eoutdent
        Write-Devel ($message)
    }
}

<#
    .SYNOPSIS
    Print a message when entering something
 
    .DESCRIPTION
    Print a message and indent output for following messages.
    It is useful when entering a loop, or a module, or an external script.
 
    .EXAMPLE
    # eenter ""
 
.LINK
 
#>

function Write-Enter() {
    [CmdletBinding()]param(
        [string]$message
    )
    $message = ">> " + $message
    Write-Info ($message)
    eindent
}

<#
    .SYNOPSIS
    Print a message when leaving a something
 
    .DESCRIPTION
    Print a message specifically when entering a function.
    It is useful when leaving a loop, or a module, or an external script.
 
    .EXAMPLE
    # eenter ""
 
.LINK
 
#>

function Write-Leave() {
    [CmdletBinding()]param(
        [string]$message
    )
    eoutdent
    $message = "<< " + $message
    Write-Info ($message)
}

<#
    .SYNOPSIS
    Print a devel message
 
    .DESCRIPTION
    Used to print content of command
 
.LINK
 
#>

function Write-Devel() {
    [CmdletBinding()]param(
        [string]$message
    )
    $fullmessage = $prepend + "DEV: " + $indent + $message
    Write-ToLogFile -NoNewline -Message $fullmessage
    if ($Global:DEVEL -eq $false) { return }
    if ($Global:QUIET -eq $false) { Write-Host -NoNewline -ForegroundColor DarkGray ("`n" + $fullmessage) }
    #Write-Debug ($indent + " " + $message)
}

<#
    .SYNOPSIS
    Print a debug message
 
    .DESCRIPTION
    Override Write-Debug() powershell function
    Mainly used to print Key = Valu pair
 
.LINK
 
#>

function Write-Debug() {
    [CmdletBinding()]param(
        [string]$message
    )
    $fullmessage = $prepend + "DBG: " + $indent + $message
    Write-ToLogFile -NoNewline -Message $fullmessage
    if ($Global:DEBUG -eq $false) { return }
    if ($Global:QUIET -eq $false) { Write-Host -NoNewline -ForegroundColor Gray ("`n" + $fullmessage) }
    #Write-Debug ($indent + " " + $message)
}

<#
    .SYNOPSIS
    Print a verbose message
 
    .DESCRIPTION
    Override Write-Verbose() powershell function
 
.LINK
 
#>

function Write-Verbose() {
    [CmdletBinding()]param(
        [string]$message
    )
    $fullmessage = $prepend + $indent + $message
    Write-ToLogFile -NoNewline -Message $fullmessage
    if ($VERBOSE -eq $false) { return }
    if ($Global:QUIET -eq $false) { Write-Host -NoNewline -ForegroundColor White  ("`n" + $fullmessage) }
    #Write-Verbose ($indent + " " + $message)
}

<#
    .SYNOPSIS
    Print a warning message
 
    .DESCRIPTION
    Override Write-Warning() powershell function
 
.LINK
 
#>

function Write-Warning() {
    [CmdletBinding()]param(
        [string]$message
    )
    $fullmessage = $prepend + "WRN: " + $indent + $message
    if ($Global:QUIET -eq $false) { Write-Host -NoNewline -ForegroundColor Yellow ("`n" + $fullmessage) }
    Write-ToLogFile -NoNewline -Message $fullmessage
}

<#
    .SYNOPSIS
    Print an error message
 
    .DESCRIPTION
    Override Write-Error() powershell function
 
.LINK
 
#>

function Write-Error() {
    [CmdletBinding()]param(
        [string]$message
    )
    $fullmessage = $prepend + "ERR: " + $indent + $message
    if ($Global:QUIET -eq $false) { Write-Host -NoNewline -ForegroundColor Red ("`n" + $fullmessage) }
    Write-ToLogFile -NoNewline -Message ($fullmessage)
}

<#
    .SYNOPSIS
    Print an information message
 
    .DESCRIPTION
    Override Write-Information() powershell function
 
.LINK
 
#>

function Write-Info() {
    [CmdletBinding()]param(
        [string]$message
    )
    $fullmessage = $prepend + $indent + $message
    if ($Global:QUIET -eq $false) { Write-Host -NoNewline -ForegroundColor Gray ("`n" + $fullmessage) }
    Write-ToLogFile -NoNewline -Message ($fullmessage)
}

<#
    .SYNOPSIS
    Print a fatal error message then exist script
 
    .DESCRIPTION
    Print an error message before terminate current script
 
    .EXAMPLE
    Write-Fatal "fatal error. Abort."
 
.LINK
 
#>

function Write-Fatal() {
    [CmdletBinding()]param(
        [string]$message
    )
    Write-Error -ErrorAction:Stop -Message "$message. Aborting."
    try {
        Throw " Aborting."
    } catch {
        $lines = $_.ScriptStackTrace -split "`n"
        # $lines.GetType()
        [array]::reverse($lines)
        edevel ("Showing stack trace :")
        $lines | Select-Object -SkipLast 1 | ForEach-Object { edevel $_ }
    }
    Write-Host
    if (!$DEVEL) { eerror "Please run your script in devel logging with -dev parameter to see a full stack trace of the exception." }
    Throw "$message. Aborting."
}

<#
    .SYNOPSIS
    Wrapper to PwSh.Fw.Log's Write-ToLogFile().
 
    .DESCRIPTION
    All the Write-*() functions from this module use Write-ToLogFile(). This wrapper is here just in case the PwSh.Fw.Log module is not loaded/available.
 
    .PARAMETER Append
    Append message to the log file. Do not overwrite it.
    Append = $true is the default. If you want to overwrite or initiate the file, call
    Write-ToLogFile -message "Logfile initialized" -Append=$false
 
    .PARAMETER NoNewline
    Do not append a new line at the end of file.
 
    .PARAMETER NoHeader
    Do not print header informations : "date hostname scriptname". Usefull to append text to an existing line.
 
    .PARAMETER Message
    The message to write to the logfile
 
    .PARAMETER LogFile
    Full path to the logfile
 
    .EXAMPLE
    Write-ToLogFile -message "a log entry" -append
 
.LINK
 
#>

function Write-ToLogFile() {
    [CmdletBinding()]param(
        [switch]$Append,
        [switch]$NoNewLine,
        [switch]$NoHeader,
        [string]$message,
        [string]$logFile = $Global:LOG
    )
    # old method using ubounded arguments, but I failed to make it work
    # # Write-Host("`n >> " + $MyInvocation.MyCommand)
    # Write-Host($MyInvocation.PSBoundParameters | Convertto-Json)
    # Write-Host($MyInvocation.UnboundArguments | Convertto-Json)
    # $module = Get-Module PwSh.Fw.Log -ErrorAction SilentlyContinue
    # if ($null -ne $module) {
    # PwSh.Fw.Log\Write-ToLogFile $MyInvocation.PSBoundParameters
    # PwSh.Fw.Log\Write-ToLogFile ($MyInvocation.UnboundArguments).ToString()
    # }
    # # Write-Host("`n << " + $MyInvocation.MyCommand)

    # new method with bounded parameters
    $module = Get-Module PwSh.Fw.Log -ErrorAction SilentlyContinue
    if ($null -ne $module) {
        PwSh.Fw.Log\Write-ToLogFile -Append:$true -NoNewLine:$NoNewLine -NoHeader:$NoHeader -Message "$Message" -logFile $logFile
    }

}

function Write-Question {
    [CmdletBinding()]
    [OutputType([String], [Int])]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][string]$Prompt,
        [Parameter(Mandatory = $false, ValueFromPipeLine = $true)][string]$DefaultValue,
        [Parameter(Mandatory = $false, ValueFromPipeLine = $true)][string]$DefaultAnswer
    )
    Begin {
        # Write-EnterFunction
    }

    Process {
        if ($DefaultAnswer) { return $DefaultAnswer }
        $value = Read-Host -Prompt "$Prompt [$DefaultValue]: "
        if ([string]::IsNullOrWhiteSpace($value)) { $value = $DefaultValue }
        return $value
    }

    End {
        # Write-LeaveFunction
    }
}


Set-Alias -Force -Confirm:$false -Name eindent        -Value Write-Indent
Set-Alias -Force -Confirm:$false -Name eoutdent        -Value Write-Outdent
Set-Alias -Force -Confirm:$false -Name etitle        -Value Write-Title
Set-Alias -Force -Confirm:$false -Name ebegin        -Value Write-Begin
Set-Alias -Force -Confirm:$false -Name eadd            -Value Write-Add
Set-Alias -Force -Confirm:$false -Name eend            -Value Write-End
Set-Alias -Force -Confirm:$false -Name fenter        -Value Write-EnterFunction
Set-Alias -Force -Confirm:$false -Name fleave        -Value Write-LeaveFunction
Set-Alias -Force -Confirm:$false -Name eenter        -Value Write-Enter
Set-Alias -Force -Confirm:$false -Name eleave        -Value Write-Leave
Set-Alias -Force -Confirm:$false -Name einfo        -Value Write-Info
Set-Alias -Force -Confirm:$false -Name everbose        -Value Write-Verbose
Set-Alias -Force -Confirm:$false -Name edebug        -Value Write-Debug
Set-Alias -Force -Confirm:$false -Name edevel        -Value Write-Devel
Set-Alias -Force -Confirm:$false -Name ewarn        -Value Write-Warning
Set-Alias -Force -Confirm:$false -Name eerror        -Value Write-Error
Set-Alias -Force -Confirm:$false -Name efatal        -Value Write-Fatal
Set-Alias -Force -Confirm:$false -Name erc            -Value Write-ReturnCode
Set-Alias -Force -Confirm:$false -Name equestion    -Value Write-Question

# obsoletes aliases
Set-Alias -Force -Confirm:$false -Name Write-MyVerbose        -Value Write-Verbose
Set-Alias -Force -Confirm:$false -Name Write-MyDebug        -Value Write-Debug
Set-Alias -Force -Confirm:$false -Name Write-MyWarning        -Value Write-Warning
Set-Alias -Force -Confirm:$false -Name Write-MyError        -Value Write-Error