TFL.Powershell.Format-Task.psm1

# Copyright 2018 Transport for London

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

function Write-Good {

    $green = @{
        Object          = [Char]8730
        ForegroundColor = "Green"
        NoNewLine       = $true
    }

    Write-Host -NoNewLine "["
    Write-Host @green
    Write-Host -NoNewline "]"
}

function Write-Bad {

    <#
    .Synopsis
    Writes a negative output
    .Description
    Writes a negative output with an exception message
    #>

    param (
        [String]
        $exceptionMessage
    )
    
    $red = @{
        Object          = "x"
        ForegroundColor = "Red"
        NoNewLine       = $true
    }

    Write-Host -NoNewLine "["
    Write-Host @red
    Write-Host "] `($($exceptionMessage)`)" -NoNewline
}

function AreWeNotVSTS {
    return $false
    
    # We should consider acting differently based on other environments
    # return $env:VSTS_PROCESS_LOOKUP_ID -eq $null
}


function Format-ElapsedTimeString {
    <#
    .Synopsis
    Formats the elapsed time object
    #>

    param (
        [TimeSpan]
        $timeSpan
    )

    # TODO: Make this scale better to longer times
    return "$([System.Math]::Round($timeSpan.TotalMilliseconds, 2)) ms"
}

function Format-Task {
    <#
    .Synopsis
    Formats a sub-task neatly
    .Description
    Formats a sub-task neatly with additional output and better handling of returned objects and exceptions
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [AllowEmptyCollection()]
        [AllowNull()]
        [String]
        $Message,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $ScriptBlock,

        [Parameter(Mandatory = $false)]
        [switch]
        $NoResult,

        [Parameter(Mandatory = $false)]
        [switch]
        $NoReturn
    )

    $pre = $ErrorActionPreference

    if (-Not ($script:TabLevel)) {
        $script:TabLevel = 0
    }

    $tabPrefix = " " * $script:TabLevel

    $ret = $null

    $dots = "-" * ([Math]::Max($message.Length, $host.UI.RawUI.BufferSize.Width) - ($tabPrefix.Length))

    $stopWatch = New-Object -TypeName System.Diagnostics.Stopwatch

    try {
        $script:TabLevel = $script:TabLevel + 1

        # Override the error-action to help us pick up exceptions
        $ErrorActionPreference = "Stop"
        
        # Print header
        if (AreWeNotVSTS -and $host.UI.SupportsVirtualTerminal) {
            Write-Host "$($tabPrefix)$($Message): " -NoNewline
            $origPos = $host.UI.RawUI.CursorPosition
            Write-Host
        }
        else {
            Write-Host
            Write-Host "$($tabPrefix)$($Message)"
            Write-Host "$($tabPrefix)$($dots)"
        }

        # Execute the script
        $stopWatch.Start()
        $ret = Invoke-Command -ScriptBlock $ScriptBlock
        $stopWatch.Stop()

        # Derive the time-taken
        $elapsedTime = Format-ElapsedTimeString $stopWatch.Elapsed

        # Print footer
        if (AreWeNotVSTS -and $host.UI.SupportsVirtualTerminal) {
            $pre = $host.UI.RawUI.CursorPosition
            $host.UI.RawUI.CursorPosition = $origPos

            Write-Good

            if (($null -ne $ret) -and ($noResult -eq $false)) {
                Write-Host " - Result: $($ret), Time: $($elapsedTime)"
            }

            $host.UI.RawUI.CursorPosition = $pre

            
        }
        else {
            
            # If there is a result, and we are supposed to output it
            if (($null -ne $ret) -and ($NoResult -eq $false)) {
                Write-Host "$($tabPrefix)Result: $($ret)"
            }

            Write-Host "$($tabPrefix)Time: $($elapsedTime)"
            Write-Host "$($tabPrefix)Success: " -NoNewline
            Write-Good
            Write-Host
            Write-Host
        }

        if ($NoReturn) {
            return
        }
        else {
            return $ret
        }
    }
    catch {
        $stopWatch.Stop()

        # Derive the time-taken
        $elapsedTime = Format-ElapsedTimeString $stopWatch.Elapsed

        # Print footer
        if (AreWeNotVSTS -and $host.UI.SupportsVirtualTerminal) {
            $pre = $host.UI.RawUI.CursorPosition
            $host.UI.RawUI.CursorPosition = $origPos

            Write-Bad $_.Exception
            
            Write-Host " - Time: $($elapsedTime)"

            $host.UI.RawUI.CursorPosition = $pre
        }
        else {
            Write-Host "$($tabPrefix)Time: $($elapsedTime)"
            Write-Host "$($tabPrefix)Fail: " -NoNewline
            Write-Bad $_.Exception
            Write-Host
            Write-Host
        }

        throw
    }
    finally {
        $ErrorActionPreference = $pre
        $script:TabLevel = $script:TabLevel - 1
    }
}

Export-ModuleMember -Function Format-Task