LeetABit.Build.Logging.psm1

#requires -version 6
using namespace System.Diagnostics.CodeAnalysis
using namespace System.Management.Automation

Set-StrictMode -Version 3.0
Import-LocalizedData -BindingVariable LocalizedData -FileName LeetABit.Build.Logging.Resources.psd1

$LastStepName   = ""
$LastStepFailed = $False


##################################################################################################################
# Public Commands
##################################################################################################################


function Write-Diagnostic {
    <#
    .SYNOPSIS
        Writes a diagnostic message that informs about less relevant script progress.
    .DESCRIPTION
        Write-Diagnostic cmdlet writes a less relevant diagnostic build message to the information stream.
    .EXAMPLE
        Write-Diagnostic "Checking optional features finished."
 
        Writes a diagnostic message to the information stream.
    #>

    [CmdletBinding(PositionalBinding = $False)]

    param (
        # Diagnostic message to be written by the host.
        [Parameter(HelpMessage = 'Enter a diagnostic message.',
                   Position = 0,
                   Mandatory = $True,
                   ValueFromPipeline = $True,
                   ValueFromPipelineByPropertyName = $True)]
        [String[]]
        $Message)

    begin {
        LeetABit.Build.Common\Import-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
    }

    process {
        Write-Message -Message $Message -Color 'DarkGrey'
    }
}


function Write-Failure {
    <#
    .SYNOPSIS
        Writes a message that informs about build failure.
    .DESCRIPTION
        Write-Failure cmdlet writes a failure message to the information stream. It also emits a message in error stream if $ErrorActionPreference is set to 'Stop'.
    .EXAMPLE
        Write-Failure -Message "Could not execute build step." -ErrorAction 'Stop'
 
        Writes a build step failure message to the information stream and emits a message in the error stream.
    .NOTES
        This cmdlet marks most recent build step started as failed.
    .LINK
        Write-Step
    .LINK
        Write-StepFinished
    #>

    [CmdletBinding(PositionalBinding = $False)]

    param (
        # Build failure message.
        [Parameter(HelpMessage = 'Enter message that describes the failure.',
                   Position = 0,
                   Mandatory = $True,
                   ValueFromPipeline = $True,
                   ValueFromPipelineByPropertyName = $True)]
        [String[]]
        $Message)

    begin {
        LeetABit.Build.Common\Import-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
    }

    process {
        $script:LastStepFailed = $True
        Write-Message -Message $Message -Color 'Red'

        if ($ErrorActionPreference -eq 'Stop') {
            Write-Error $LocalizedData.BreakingError
        }
    }
}


function Write-Invocation {
    <#
    .SYNOPSIS
        Writes a verbose message about the specified invocation.
    .DESCRIPTION
        Write-Invocation cmdlet writes a message to a verbose stream that contains information about executing function invocation.
    .EXAMPLE
        Write-Invocation $MyInvocation
 
        Writes a verbose information about current function invocation.
    #>

    [CmdletBinding(PositionalBinding = $False)]

    param (
        # Invocation which information shall be written.
        [Parameter(HelpMessage = "Provide invocation information about the command to write to verbose log.",
                   Position = 0,
                   Mandatory = $True,
                   ValueFromPipeline = $True,
                   ValueFromPipelineByPropertyName = $True)]
        [ValidateNotNull()]
        [InvocationInfo]
        $Invocation
    )

    begin {
        LeetABit.Build.Common\Import-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
    }

    process {
        $message = $LocalizedData.Write_Invocation_ExecutingCommandWithParameters_CommandName
        $message = $message -f ($Invocation.MyCommand.ModuleName, $Invocation.MyCommand.Name)
        Write-Verbose $message

        $Invocation.BoundParameters.Keys | ForEach-Object {
            $value = LeetABit.Build.Common\ConvertTo-ExpressionString $Invocation.BoundParameters[$_]
            Write-Verbose " -$_ = `'$value`'"
        }
    }
}


function Write-Message {
    <#
    .SYNOPSIS
        Writes a specified message string to the information stream with optional preamble and ANSI color escape sequence.
    .DESCRIPTION
        Write-Message cmdlet writes a message to the information stream. An optional preamble is also written on the first line before the actual message. Caller may also specify a color of the message using one of the [System.ConsoleColor] members.
    .EXAMPLE
        Write-Message -Message "Working on updates..." -Preamble "{step:updates}" -Color "Red"
 
        Writes an information in red color perpended with a preamble.
    .EXAMPLE
        Write-Message -Message "Working on updates..."
 
        Writes an information in default foreground color with no preamble.
    .NOTES
        Preamble may be used to decorate a message with a text consumed by the presentation layer. This feature is used by Travis CI for log folding.
    #>


    param (
        # Message to be written by the host.
        [Parameter(Position = 0,
                   Mandatory = $False,
                   ValueFromPipeline = $True,
                   ValueFromPipelineByPropertyName = $True)]
        [String[]]
        $Message = @(),

        # Additional control text to be used for the message.
        [Parameter(Mandatory = $False,
                   ValueFromPipeline = $False,
                   ValueFromPipelineByPropertyName = $True)]
        [String]
        $Preamble = '',

        # Color for the message.
        [Parameter(Mandatory = $False,
                   ValueFromPipeline = $False,
                   ValueFromPipelineByPropertyName = $True)]
        [System.ConsoleColor]
        $Color
    )

    begin {
        LeetABit.Build.Common\Import-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
    }

    process {
        $preambleToWrite = $Preamble
        $colorToWrite = ""
        $resetToWrite = ""

        if ($Color) {
            $colorToWrite = [char]0x001b + '['
            $colorToWrite += if ($Color -ge [System.ConsoleColor]::DarkGray) { if ($env:APPVEYOR) { '1;9' } else { '1;3' } } else { '3' }
            $colorToWrite += ($Color % 8) + 'm'
            $resetToWrite = [char]0x001b + '[0m'
        }

        foreach ($messagePart in $Message) {
            Write-Information "$preambleToWrite$colorToWrite$messagePart$resetToWrite"
            $preambleToWrite = ''
        }
    }
}


function Write-Modification {
    <#
    .SYNOPSIS
        Writes a message that informs about state change in the current system.
    .DESCRIPTION
        Write-Modification cmdlet writes a message that informs the user about a change that is going to be made to the current system. The message is written to the information stream. This cmdlet shall be used to inform the user about any change that is made to the system in order to give an opportunity to manually revert the changes in case of failure.
    .EXAMPLE
        Write-Modification "Downloading 'archive.zip' file to the repository directory."
 
        Writes an information message about the file download with the information where it is going to be stored.
    #>

    [CmdletBinding(PositionalBinding = $False)]

    param (
        # Modification message to be written by the host.
        [Parameter(HelpMessage = 'Enter a diagnostic message.',
                   Position = 0,
                   Mandatory = $True,
                   ValueFromPipeline = $True,
                   ValueFromPipelineByPropertyName = $True)]
        [String[]]
        $Message
    )

    begin {
        LeetABit.Build.Common\Import-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
    }

    process {
        Write-Message -Message $Message -Color 'Magenta'
    }
}


function Write-Step {
    <#
    .SYNOPSIS
        Writes a specified build step message to the information stream with step name folding when run in Travis CI environment.
    .DESCRIPTION
        Write-Step cmdlet writes a message about a new build step that is about to be started. The message is written to the information stream. This cmdlet also emits a log folding preamble when run in Travis CI environment.
    .EXAMPLE
        Write-Step -StepName "prerequisites" -Message "Installing prerequisites."
 
        Writes an information message about the build step with a folding preamble when run in Travis CI environment.
    .NOTES
        This cmdlet does not support nested steps. To start a new build step the Write-StepFinished cmdlet shall be called. Otherwise folding and error handling for the outer step will not work correctly.
    .LINK
        Write-StepFinished
    #>

    [CmdletBinding(PositionalBinding = $False)]

    param (
        # Name of the step that shall be written as a message preamble.
        [Parameter(HelpMessage = 'Enter a name of the step being reported.',
                   Position = 0,
                   Mandatory = $True,
                   ValueFromPipeline = $False,
                   ValueFromPipelineByPropertyName = $False)]
        [ValidatePattern('^[a-z0-9_]+$')]
        [String]
        $StepName,

        # Step information message to be written by the host.
        [Parameter(HelpMessage = 'Enter a step message.',
                   Position = 1,
                   Mandatory = $True,
                   ValueFromPipeline = $False,
                   ValueFromPipelineByPropertyName = $False)]
        [String[]]
        $Message
    )

    begin {
        LeetABit.Build.Common\Import-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
    }

    process {
        $preamble = if ($env:TRAVIS -and $StepName) { "travis_fold:start:$StepName`r" }
                    else                            { '' }

        Write-Message -Message "$([System.Environment]::NewLine)$Message" -Preamble $preamble -Color 'Cyan'

        $script:LastStepName   = $StepName
        $script:LastStepFailed = $False
    }
}


function Write-StepFinished {
    <#
    .SYNOPSIS
        Writes a message about the result of the most recent build step and closes folding when run in Travis CI environment.
    .DESCRIPTION
        Write-StepFinished cmdlet writes a message about build step failure when Write-Failure cmdlet was called since last Write-Step. Otherwise a success message is being written to the information stream.
    .EXAMPLE
        Write-StepFinished
 
        Writes an information about most recent build step result.
    .LINK
        Write-Step
    .LINK
        Write-Failure
    #>

    [CmdletBinding(PositionalBinding = $False)]

    param ()

    begin {
        LeetABit.Build.Common\Import-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
        $message  = " " + $LocalizedData.Write_StepFinished_Success
    }

    process {
        if (-not $script:LastStepName) {
            throw $LocalizedData.Write_StepFinished_NoStepStarted
        }

        if ($script:LastStepFailed) {
            throw $LocalizedData.Write_StepFinished_BuildStepFailed_StepName -f $script:LastStepName
        }

        $preamble = if ($env:TRAVIS -and $script:LastStepName) { "travis_fold:end:$script:LastStepName`r" } else { '' }

        Write-Message -Message $message -Preamble $preamble -Color 'Green'
        Write-Message
    }
}


Export-ModuleMember -Function '*' -Variable '*' -Alias '*' -Cmdlet '*'