PesterLove.psm1

#Generated at 06/17/2019 14:56:17 by Stephane van Gulick
#Thanks June Blender for this tip
#https://www.sapien.com/blog/2014/10/21/a-better-tostring-method-for-hash-tables/
Update-TypeData -TypeName System.Collections.HashTable `
    -MemberType ScriptMethod `
    -ErrorAction SilentlyContinue `
    -MemberName ToString `
    -Value { $hashstr = "@{"; $keys = $this.keys; foreach ($key in $keys) { $v = $this[$key]; 
             if ($key -match "\s") { $hashstr += "`"$key`"" + "=" + $v}
             else { $hashstr += $key + "=" + $v } }; $hashstr += "}";
             return $hashstr }

Enum PesterType {
    It
    Describe
    Context
    InModuleScope
}


Class PesterBlockFactory {

    [PesterBlock[]] Static Create([System.IO.FileInfo]$File) {
        $Blocks = [ASTHelper]::ConvertToPesterASTBlocks($File)
        $PesterBlocks = @()
        Foreach ($Block in $Blocks) {

            $PesterBlocks += [PesterBlockFactory]::Create($Block)

        }

        return $PesterBlocks
    }

    [PesterBlock[]] Static Create([System.Management.Automation.Language.ConstantExpressionAst]$Block) {
        
        

        switch ($Block.Value) {
            'Describe' {
                $Hash = @{value = ''; content = '' }
                $Hash.ElementType = $Block.Value
                $Hash.Content = $Block.Parent.Extent.Text
                $Hash.Name = $Block.Parent.commandElements.Value[1]
                $Hash.Value = $Block.Parent.CommandElements[2].ScriptBlock.EndBlock.Extent.Text
                $tags = @()
                #$Obj = [PesterDescribeBlock]::New($Hash.Name,$Hash.ItBlocks,[PesterType]::Describe,$Hash.Content,[String[]]$Tags)
                return [PesterDescribeBlock]::New($Block)
                    
                ; Break
            }
            'It' {
                $Hash = @{value = ''; content = '' }
                $Hash.ElementType = $Block.Value
                $Hash.Content = $Block.Parent.Extent.Text
                $Hash.Name = $Block.Parent.commandElements.Value[1]
                $Hash.Value = $Block.Parent.CommandElements[2].ScriptBlock.EndBlock.Extent.Text #$d.Parent.CommandElements.Scriptblock.Extent
                If ($block.Parent.Commandelements | ? { $_.ParameterName -eq 'skip' }) {
                    $Hash.IsSkipped = $True
                }
                else {
            
                    $Hash.IsSkipped = $False
                }
            
                If ($block.Parent.Commandelements | ? { $_.ParameterName -eq 'Pending' }) {
                    $Hash.IsPending = $True
                }
                else {
            
                    $Hash.IsPending = $False
                }
            
                $PassedTestCases = $null
                $TestCasesFound = $False
                Foreach ($par in $Block.Parent.Commandelements) {
                    If ($TestCasesFound -Eq $False) {
            
                        If ($par.ParameterName -eq 'TestCases') {
                            $TestCasesFound = $True 
                        }
                        continue
                    }
                    If ($TestCasesFound) {
                        $PassedTestCases = $par
                    }
                }
            
                   
                If (!($PassedTestCases)) {
            
                    $Hash.TestCases = $Null 
                }
                Else {
                    $Text = $PassedTestCases
                    $Regex = "(@\{(?<key>\w+)\s=\s(?<value>\s*( |'|`")\w*( |'|`"))}){1,}"
                    $AllMatches = [Regex]::Matches($Text, $Regex)
                    [HashTable[]]$ArrHash = @()
                    Foreach ($mat in $AllMatches) {
                        $h = @{ }
                            
                        $key = ($mat.groups | ? { $_.Name -eq 'key' }).Value
                        If (!($key)) {
                            #Not correct capturing group
                            Continue
                        }
                        $Value = ($mat.groups | ? { $_.Name -eq 'Value' }).Value
            
                        $h.$Key = $value
                        $ArrHash += $h
                    }
                    $Hash.TestCases = $ArrHash
            
                }
                #$Hash.TestCases = $d.Parent.Parent.PipelineElements.CommandElements[-1].Elements
            
            
                $Obj = [PesterItBlock]::New($Hash.Name, $Hash.Content, [PesterType]::It, $Hash.Value, $Hash.TestCases)
                $Obj.SetPending($Hash['IsPending'])
                $Obj.SetSkipped($Hash['IsSkipped'])
                    
                $AllChilds = [ASTHelper]::ConvertToPesterASTBlocks([astHelper]::GetChildBlock($Block))
                $PB = @()
                foreach ($Child in $AllChilds) {

                    $PB += [PesterBlockFactory]::Create($Child)
                }

                $obj.AddChild($PB)

                REturn $Obj
                    
            }
            'Context' {
                #$ContextName = $Block.Parent.commandElements.Value[1]
                #$ContextValue = $Block.Parent.CommandElements[2].ScriptBlock.EndBlock.Extent.Text
                Return [PesterContextBlock]::New($Block)
                    
                
            }
            'InModuleScope' {
                Return [PesterInModuleScopeBlock]::New($Block)
            }
            Default {
                Throw "Block $($Block.Value) is of unsupported type."
            }
        }
        Throw "Parameter Block value is currently unsupported."
        
    }

}

Class PesterBlock {

    Hidden $Raw
    [System.Management.Automation.Language.ConstantExpressionAst[]]$RawBlocks = @()
    [PesterType]$Type
    
    PesterBlock() { }

    PesterBlock([String]$String) {
        
        $this.ConvertToAST($String)
        
    }
    
    #converts input file or string to AST blocks
    ConvertToAST([String]$String) {
        
        $P = Get-Item -path $String -ErrorAction SilentlyContinue
        IF ($P.Exists) {
            $ParsedAST = [System.Management.Automation.Language.Parser]::ParseFile($P.FullName, [ref]$null, [ref]$Null)
        }
        else {
            $ParsedAST = [System.Management.Automation.Language.Parser]::ParseInput($String, [ref]$null, [ref]$Null)
        }
        $This.Raw = $ParsedAST.FindAll( { $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] }, $true)
        $This.SetRawPesterBlocks() #fills in $this.RawBlocks variable
        
    }

    #Sorts the AST blocks to only return Describe,IT, or Context AST blocks.
    SetRawPesterBlocks() {

        If (!($this.Raw)) {
            Throw "No pester code found."
        }

        $PesterBlocks = $this.Raw | ? { ($_.StringConstantType -eq "BareWord" -and $_.Value -eq 'Describe') -or ($_.StringConstantType -eq "BareWord" -and $_.Value -eq 'It') -or ($_.StringConstantType -eq "BareWord" -and $_.Value -eq 'Context') -or ($_.StringConstantType -eq "BareWord" -and $_.Value -eq 'InModuleScope') }
        $This.RawBlocks = $PesterBlocks
    }

    [System.Management.Automation.Language.ConstantExpressionAst[]] GetRawPesterBlocks() {
        return $This.RawBlocks
    }

    [String] GetChildBlock([System.Management.Automation.Language.StringConstantExpressionAst]$Block) {
        return ($block.Parent.CommandElements | ? { $_.StaticType.Name -eq 'ScriptBlock' }).ScriptBlock.EndBlock.Extent.Text
    }

    [String]ToString() {
        Throw "Not implemented method!"
    }

}

Class PesterInModuleScopeBlock : PesterBlock {
    [String]$Name
    [PesterType]$Type = [PesterType]::InModuleScope

    [PesterBlock[]]$Childs

    PesterInModuleScopeBlock() {
        
    }

    PesterInModuleScopeBlock([System.Management.Automation.Language.StringConstantExpressionAst]$Block) {
        $this.Name = $Block.Parent.commandElements.Value[1]

        #A move dans base constructor?
        $AllChilds = [ASTHelper]::ConvertToPesterASTBlocks([astHelper]::GetChildBlock($block))
        $PB = @()
        foreach ($Child in $AllChilds) {

            $PB += [PesterBlockFactory]::Create($Child)
        }
        $This.Childs = $PB
        
    }

    [String] ToString() {
        $sb = [System.Text.StringBuilder]::new()
        $FormatedString = 'InModuleScope "{0} " {1}' -f $this.Name, "-ScriptBlock { "
        [void]$sb.AppendLine($FormatedString)
        $EndString = "} "
        
        
        foreach($Child in $this.Childs){
            $Sb.AppendLine()
            $sb.AppendLine($Child.ToString())
        }
        [void]$Sb.AppendLine($EndString)
        return $sb.ToString()
    }
}

Class PesterItBlock :PesterBlock {
    [String]$Name
    [String]$Value
    [PesterType]$Type = [PesterType]::It
    [String]$Test
    [PesterBlock[]]$Childs
    [HashTable[]]$TestCases = @()
    [Bool]$Pending = $false
    [Bool]$Skipped = $False

    PesterITBlock([String]$Name, [String]$TestContent) {
        $this.Name = $Name
        $this.Test = $TestContent
    }

    PesterITBlock([String]$Name, [String]$Value, [PesterType]$Type, [String]$TestContent, [HashTable[]]$TestCases) {
        $this.Name = $Name
        $this.Value = $Value
        $this.Type = $Type
        $this.Test = $TestContent
        $This.TestCases = $TestCases
    }

    SetPending([Bool]$IsPending) {
        $this.Pending = $IsPending
    }

    [Bool] IsPending() {
        return $This.Pending
    }

    SetSkipped([Bool]$IsSkipped) {
        $this.Skipped = $IsSkipped
    }

    [Bool] IsSkipped() {
        return $This.Skipped
    }

    [String]ToString() {
        $sb = [System.Text.StringBuilder]::new()
        $FormatedString = 'It "{0}" {1}' -f $this.Name, "{"
        [void]$sb.AppendLine($FormatedString)
        $EndString = "} "
        
        
        If ($this.TestCases) {
            $EndString += '-TestCases {0} ' -f $this.ConvertTestCasesToHashTableToString()
        }
        If ($this.IsPending()) {
            $EndString += "{0}" -f "-Pending "
        }

        If ($This.IsSkipped()) {
            $EndString += "{0}" -f "-Skip "
        }
            
        [void]$Sb.AppendLine($this.Test)
        [void]$Sb.AppendLine($EndString)
        return $sb.ToString()
    }

    Hidden [String] ConvertTestCasesToHashTableToString() {
        If ($this.TestCases) {

            $TestCaseString = ""
            Foreach ($ht in $this.TestCases) {
                $TestCaseString = $TestCaseString + $ht.ToString() + ","
            }
            $TestCaseString = $TestCaseString.TrimEnd(",")
            return $TestCaseString
        }
        else {
            return ""
        }
    }

    AddChild([PesterBlock[]]$Child) {
        $this.Childs += $Child
    }

    AddTestCase([HashTable[]]$Testcase) {
        $this.TestCases += $Testcase
    }
}
Class PesterDescribeBlock : PesterBlock {
    [String]$Name
    [PesterBlock[]]$Childs
    [PesterType]$Type
    [String]$Fixture
    [String[]]$Tags

    PesterDescribeBlock([System.Management.Automation.Language.StringConstantExpressionAst]$Block) {
        $this.Name = $Block.Parent.commandElements.Value[1]
        $This.Fixture = $Block.Parent.Extent.Text
        $this.Type = [PesterType]::Describe
        #A move dans base constructor?
        $AllChilds = [ASTHelper]::ConvertToPesterASTBlocks([astHelper]::GetChildBlock($block))
        $PB = @()
        foreach ($Child in $AllChilds) {

            $PB += [PesterBlockFactory]::Create($Child)
        }
        $This.Childs = $PB
        
    }

    PesterDescribeBlock([String]$Name, [PesterBlock[]]$Childs, [PesterType]$Type, [String]$Fixture, [String[]]$Tags) {
        $this.Name = $Name
        $this.Childs = $Childs
        $this.Type = $Type
        $this.Fixture = $Fixture
        $This.Tags = $Tags
    }

    [String[]] GetTag() {
        return $this.Tags
    }

    [String[]] GetTag([String]$Tag) {
        return $this.Tags | ? { $_ -eq $Tag }
    }

    [String]ToString() {
        $sb = [System.Text.StringBuilder]::new()
        $FormatedString = 'Describe "{0}" {1} {2}' -f $this.Name, "-Fixture ", "{"
        [void]$sb.AppendLine($FormatedString)
        $EndString = "} "
        

        If ($this.GetTag()) {
            $EndString += "{0}" -f "-Tag "
        }

        If ($This.Childs()) {
            Foreach ($Child in $this.Childs) {
                $sb.AppendLine()
                $Sb.AppendLine($Child.ToString())
            }
        }
            
        [void]$Sb.AppendLine($this.Test)
        [void]$Sb.AppendLine($EndString)
        return $sb.ToString()
    }

}

Class PesterScript {
    [System.IO.FileInfo]$path
    [PesterBlock[]]$PesterBlocks

    PesterScript([System.Io.FileInfo]$Path) {
        
        $this.Path = $Path
        $This.PesterBlocks = [PesterBlockFactory]::Create($This.path.FullName)
    }
}

Class PesterContextBlock : PesterBlock {
    [String]$Name
    [PesterBlock[]]$Childs

    PesterContextBlock([System.Management.Automation.Language.StringConstantExpressionAst]$Block) {
        $this.Name = $Block.Parent.commandElements.Value[1]
        $this.Type = [PesterType]::Context
        #A move dans base constructor?
        $AllChilds = [ASTHelper]::ConvertToPesterASTBlocks([astHelper]::GetChildBlock($block))
        $PB = @()
        foreach ($Child in $AllChilds) {

            $PB += [PesterBlockFactory]::Create($Child)
        }
        $This.Childs = $PB
        #$this.PesterChildBlock = [PesterBlockFactory]::New($Object)
    }

    PesterContextBlock([String]$Name, [System.Management.Automation.Language.StringConstantExpressionAst]$Object) {
        $this.Name = $Name
        $PB = [PesterBlockFactory]::Create($Object)
        $This.PesterBlock = $PB
    }
    
    PesterContextBlock([String]$Name, [PesterBlock[]]$ChildBlock) {
        $this.Name = $Name
        $this.PesterChildBlock = [PesterBlockFactory]::Create($ChildBlock)
    }


}

Class ASTHelper {

    static [System.Management.Automation.Language.StringConstantExpressionAst[]] ConvertToAST([String]$String) {
        
        $Arr = @()
        $P = Get-Item -path $String -ErrorAction SilentlyContinue
        IF ($P.Exists) {
            $ParsedAST = [System.Management.Automation.Language.Parser]::ParseFile($P.FullName, [ref]$null, [ref]$Null)
        }
        else {
            $ParsedAST = [System.Management.Automation.Language.Parser]::ParseInput($String, [ref]$null, [ref]$Null)
        }
        $arr += $ParsedAST.FindAll( { $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] }, $true)
        return $Arr
        
    }

    static [System.Management.Automation.Language.StringConstantExpressionAst[]] ConvertToPesterASTBlocks([String]$String) {
        $bl = [astHelper]::ConvertToAST($string)
        $arr = @()

        foreach ($b in $bl) {
            $arr += [astHelper]::GetPesterBlock($b)
        }

        return $arr
    }

    static [System.Management.Automation.Language.StringConstantExpressionAst[]] GetPesterBlock([System.Management.Automation.Language.StringConstantExpressionAst]$Block) {

        $PesterBlocks = @()


        $PesterBlocks += $Block | ? { ($_.StringConstantType -eq "BareWord" -and $_.Value -eq 'Describe') -or ($_.StringConstantType -eq "BareWord" -and $_.Value -eq 'It') -or ($_.StringConstantType -eq "BareWord" -and $_.Value -eq 'Context') -or ($_.StringConstantType -eq "BareWord" -and $_.Value -eq 'InModuleScope') }
        Return $PesterBlocks
    }

    static [String] GetChildBlock([System.Management.Automation.Language.StringConstantExpressionAst]$Block) {
        return ($block.Parent.CommandElements | ? { $_.StaticType.Name -eq 'ScriptBlock' }).ScriptBlock.EndBlock.Extent.Text
    }

    
}

Class PesterResultsDocument {
    [System.IO.FileInfo]$Path
    $Data

    PesterResultsDocument([String]$Path) {
        $this.Path = $Path
        $This.Data = $this.GetData()
    }

    [Object]GetData() {
        $Item = Get-Item $this.Path.FullName
        If ($Item -is [System.IO.FileInfo]) {
            If ($Item.Extension -eq '.xml') {
                [xml]$x = Get-Content -Path $Item.FullName
                Return $x.'test-results' | ConvertTo-Json -Depth 8
            }
            ElseIf ($Item.Extension -eq '.Json') {
                Return Get-Content $Item.FullName | ConvertFrom-Json
            }
        }

        Throw 'Folder type is currently not supported'
        
    }
}
#>
Function Get-PLPesterBlock {
    [CmdletBinding()]
    Param(
        $Path, 
        $InputObject
    )
    if($Path){
        $P = Get-Item -path $Path
        $Raw = [System.Management.Automation.Language.Parser]::ParseFile($p.FullName, [ref]$null, [ref]$Null)

    }elseif($InputObject){
        $Raw = [System.Management.Automation.Language.Parser]::ParseInput($InputObject,[ref]$null, [ref]$Null)
    }
    $String = $Raw.FindAll( {$args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst]}, $true)
    $Data = @()
    $ContextData = @()
    $BareWords = @()
    
    $Blocks +=  $String | ? {$_.StringConstantType -eq "BareWord"}

    foreach($block in $Blocks){
        switch($block.Value){
            'Describe'{

                ;Break
            }
            'It'{;Break}
            'Context'{;Break}
        }
    }

    $Data += $String | ? {$_.StringConstantType -eq "BareWord" -and $_.Value -eq 'it'}
    $ContextData += $String | ? {$_.StringConstantType -eq "BareWord" -and $_.Value -eq 'Context'}
    $AllItBlocks = @()
    Foreach($d in $data){

        $Hash = @{value='';content=''}
        $Hash.ElementType = $d.Value
        $Hash.Content = $d.Parent.Extent.Text
        $Hash.Name = $d.Parent.commandElements.Value[1]
        $Hash.Value =  $d.Parent.CommandElements[2].ScriptBlock.EndBlock.Extent.Text #$d.Parent.CommandElements.Scriptblock.Extent
        If($d.Parent.Commandelements | ? {$_.ParameterName -eq 'skip'}){
            $Hash.IsSkipped = $True
        }else{

            $Hash.IsSkipped = $False
        }

        If($d.Parent.Commandelements | ? {$_.ParameterName -eq 'Pending'}){
            $Hash.IsPending = $True
        }else{

            $Hash.IsPending = $False
        }

        $PassedTestCases = $null
        $TestCasesFound = $False
        Foreach($par in $d.Parent.Commandelements){
            If($TestCasesFound -Eq $False){

                If($par.ParameterName -eq 'TestCases'){
                   $TestCasesFound = $True 
                }
                continue
            }
            If($TestCasesFound){
                $PassedTestCases = $par
            }
        }

        #$PassedTestCases = $d.Parent.Commandelements | ? {$_.ParameterName -eq 'TestCases'}
        #$d.Parent.CommandElements[4].Extent.Text
        If(!($PassedTestCases)){

            $Hash.TestCases = $Null 
        }Else{
            $Text = $PassedTestCases
            $Regex = "(@\{(?<key>\w+)\s=\s(?<value>\s*( |'|`")\w*( |'|`"))}){1,}"
            $AllMatches = [Regex]::Matches($Text,$Regex)
            [HAshTable[]]$ArrHash = @()
            Foreach($mat in $AllMatches){
                $h = @{}
                
                $key = ($mat.groups | ? {$_.Name -eq 'key'}).Value
                If(!($key)){
                    #Not correct capturing group
                    Continue
                }
                $Value = ($mat.groups | ? {$_.Name -eq 'Value'}).Value

                $h.$Key = $value
                $ArrHash += $h
            }
            $Hash.TestCases = $ArrHash

        }
        #$Hash.TestCases = $d.Parent.Parent.PipelineElements.CommandElements[-1].Elements


        $Obj = [PesterItBlock]::New($Hash.Name,$Hash.Value,[PesterType]::It,$Hash.Content,$Hash.TestCases)
        $Obj.SetPending($Hash['IsPending'])
        $Obj.SetSkipped($Hash['IsSkipped'])
        $AllItBlocks += $Obj
    }

    return $AllItBlocks
}
Function Get-CUPesterScript {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [System.IO.FileInfo]$path
    )
    $Hash = @{}
    $Hash.Path = $Path.FullName
    $Hash.Data = Get-CUPesterDescribeBlock -Path $path.FullName

    $obj = [PesterScript]::New($Path)
    return $obj
}
Function Get-PLPesterContextBlock {
    <#
    .SYNOPSIS
        Get all context blocks present in a pester script.
    .DESCRIPTION
        Returns all context blocks, and it's child blocks.
    .EXAMPLE
        PS C:\> <example usage>
        Explanation of what the example does
    .INPUTS
        Inputs (if any)
    .OUTPUTS
        Output (if any)
    .NOTES
        Author: Stéphane van Gulick
    #>

    [CmdletBinding()]
    Param(
        $Path,
        $InputObject
    )
    if($Path){
        $P = Get-Item -path $Path
        $Raw = [System.Management.Automation.Language.Parser]::ParseFile($p.FullName, [ref]$null, [ref]$Null)

    }elseif($InputObject){
        $Raw = [System.Management.Automation.Language.Parser]::ParseInput($InputObject,[ref]$null, [ref]$Null)
    }
    $String = $Raw.FindAll( {$args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst]}, $true)
    $Data = @()
    $Data += $String | ? {$_.StringConstantType -eq "BareWord" -and $_.Value -eq 'Context'}
    $AllDescribeBlocks = @()
    Foreach($d in $data){

        $Hash = @{}
        $Hash.ElementType = $d.Value
        $Hash.ItBlocks = @()
        $Hash.Tags = ""
        $Hash.ItBlocks += Get-PLPesterITBlock -InputObject $d.Parent.Extent.Text
        $Hash.ContextBlocks = @()
        $Hash.ContextBlocks += Get-PLPesterContextBlock -InputObject $d.Parent.Extent.Text
        $Hash.Content = $d.Parent.Extent.Text
       
        $Obj = [PesterDescribeBlock]::New($Hash.Name,$Hash.ItBlocks,[PesterType]::Describe,$Hash.Content,[String[]]$Tags)
        
            $Pattern = '^.*-tag(?<Tags>.*$)'
            $Options = @()
            $Options += [System.Text.RegularExpressions.RegexOptions]::Multiline
            $Options += [System.Text.RegularExpressions.RegexOptions]::IgnoreCase
            $rgx = [regex]::New($Pattern,$Options)
            $MyMatches = $rgx.Match($d.Parent.Extent.Text)
            $Hash.Tags = $MyMatches.Groups['Tags'].Value
        $Obj.Tags = $Hash.Tags

        $AllDescribeBlocks += $Obj
    }

    Return $AllDescribeBlocks
}
Function Get-PLPesterDescribeBlock {
    <#
    .SYNOPSIS
        Rturns all the Describe Bloks present in a pester script.
    .DESCRIPTION
       Will get all script blocks and it's child blocks.
    .EXAMPLE
      Get-PLPesterDescribeBlock -Path C:\Code\001.ps1
 
 
        Name :
        Childs : {}
        Type : Describe
        Fixture : Describe "My Describe Block"{
 
                            it "simple it block"{
                                $true | should be $false
                            }
 
                            it "Second it block"{
                                $true | should be $false
                            } -Pending
 
                        }
        Tags : {}
        RawBlocks : {}
 
    .PARAMETER Path
        Path to the script file to process
      
     .PARAMETER INPUTObject
        Send content directly via this parameter.
    .INPUTS
        Inputs (if any)
    .OUTPUTS
        Output (if any)
    .NOTES
        Author: Stéphane van Gulick
    #>

    [CmdletBinding()]
    Param(
        $Path,
        $InputObject
    )
    if($Path){
        $P = Get-Item -path $Path
        $Raw = [System.Management.Automation.Language.Parser]::ParseFile($p.FullName, [ref]$null, [ref]$Null)

    }elseif($InputObject){
        $Raw = [System.Management.Automation.Language.Parser]::ParseInput($InputObject,[ref]$null, [ref]$Null)
    }
    $String = $Raw.FindAll( {$args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst]}, $true)
    $Data = @()
    $Data += $String | ? {$_.StringConstantType -eq "BareWord" -and $_.Value -eq 'Describe'}
    $AllDescribeBlocks = @()
    Foreach($d in $data){

        $Hash = @{}
        $Hash.ElementType = $d.Value
        $Hash.ItBlocks = @()
        $Hash.Tags = ""
        $Hash.ItBlocks += Get-PLPesterITBlock -InputObject $d.Parent.Extent.Text
        $Hash.ContextBlocks = @()
        $Hash.ContextBlocks += Get-PLPesterContextBlock -InputObject $d.Parent.Extent.Text
        $Hash.Content = $d.Parent.Extent.Text
        $Hash.NAme = $d.Parent.commandElements.Value[1]
        $Obj = [PesterDescribeBlock]::New($Hash.Name,$Hash.ItBlocks,[PesterType]::Describe,$Hash.Content,[String[]]$Tags)
        
            $Pattern = '^.*-tag(?<Tags>.*$)'
            $Options = @()
            $Options += [System.Text.RegularExpressions.RegexOptions]::Multiline
            $Options += [System.Text.RegularExpressions.RegexOptions]::IgnoreCase
            $rgx = [regex]::New($Pattern,$Options)
            $MyMatches = $rgx.Match($d.Parent.Extent.Text)
            $Hash.Tags = $MyMatches.Groups['Tags'].Value
        $Obj.Tags = $Hash.Tags

        $AllDescribeBlocks += $Obj
    }

    Return $AllDescribeBlocks
}
Function Get-PLPesterITBlock {
    <#
    .SYNOPSIS
        Get all the Pester IT blocks.
    .DESCRIPTION
        Returns all the Pester IT blocks, with any child pester blocks which it may contain.
    .EXAMPLE
        Get-PLPesterITBlock -Path C:\Code\Test01.ps1
 
 
            Name : Context Block
            Value : Describe "My Describe Block"{
 
                                it "simple it block"{
                                    $true | should be $false
                                }
 
                                it "Second it block"{
                                    $true | should be $false
                                } -Pending
 
                            }
            Type : It
            Test : Context "Context Block"{
 
                            Describe "My Describe Block"{
 
                                it "simple it block"{
                                    $true | should be $false
                                }
 
                                it "Second it block"{
                                    $true | should be $false
                                } -Pending
 
                            }
                        }
            Childs :
            TestCases :
            Pending : False
            Skipped : False
            RawBlocks : {}
    .PARAMETER Path
        Path to the script file to process
      
     .PARAMETER INPUTObject
        Send content directly via this parameter.
    .INPUTS
        Inputs (if any)
    .OUTPUTS
        PesterITBlock
    .NOTES
        Author: Stéphane van Gulick
    #>

    [CmdletBinding()]
    Param(
        [Parameter(ParameterSetName='path')]
        $Path,

        [Parameter(ParameterSetName='InputObject')]
        $InputObject
    )
    if($Path){
        $P = Get-Item -path $Path
        $Raw = [System.Management.Automation.Language.Parser]::ParseFile($p.FullName, [ref]$null, [ref]$Null)

    }elseif($InputObject){
        $Raw = [System.Management.Automation.Language.Parser]::ParseInput($InputObject,[ref]$null, [ref]$Null)
    }
    $String = $Raw.FindAll( {$args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst]}, $true)
    $Data = @()
    $ContextData = @()
    $Data += $String | ? {$_.StringConstantType -eq "BareWord" -and $_.Value -eq 'it' -or $_.StringConstantType -eq "BareWord" -and $_.Value -eq 'Context'}
    #$ContextData += $String | ? {$_.StringConstantType -eq "BareWord" -and $_.Value -eq 'Context'}
    $AllItBlocks = @()
    Foreach($d in $data){

        $Hash = @{value='';content=''}
        $Hash.ElementType = $d.Value
        $Hash.Content = $d.Parent.Extent.Text
        $Hash.Name = $d.Parent.commandElements.Value[1]
        $Hash.Value =  $d.Parent.CommandElements[2].ScriptBlock.EndBlock.Extent.Text #$d.Parent.CommandElements.Scriptblock.Extent
        If($d.Parent.Commandelements | ? {$_.ParameterName -eq 'skip'}){
            $Hash.IsSkipped = $True
        }else{

            $Hash.IsSkipped = $False
        }

        If($d.Parent.Commandelements | ? {$_.ParameterName -eq 'Pending'}){
            $Hash.IsPending = $True
        }else{

            $Hash.IsPending = $False
        }

        $PassedTestCases = $null
        $TestCasesFound = $False
        Foreach($par in $d.Parent.Commandelements){
            If($TestCasesFound -Eq $False){

                If($par.ParameterName -eq 'TestCases'){
                   $TestCasesFound = $True 
                }
                continue
            }
            If($TestCasesFound){
                $PassedTestCases = $par
            }
        }

        #$PassedTestCases = $d.Parent.Commandelements | ? {$_.ParameterName -eq 'TestCases'}
        #$d.Parent.CommandElements[4].Extent.Text
        If(!($PassedTestCases)){

            $Hash.TestCases = $Null 
        }Else{
            $Text = $PassedTestCases
            $Regex = "(@\{(?<key>\w+)\s=\s(?<value>\s*( |'|`")\w*( |'|`"))}){1,}"
            $AllMatches = [Regex]::Matches($Text,$Regex)
            [HAshTable[]]$ArrHash = @()
            Foreach($mat in $AllMatches){
                $h = @{}
                
                $key = ($mat.groups | ? {$_.Name -eq 'key'}).Value
                If(!($key)){
                    #Not correct capturing group
                    Continue
                }
                $Value = ($mat.groups | ? {$_.Name -eq 'Value'}).Value

                $h.$Key = $value
                $ArrHash += $h
            }
            $Hash.TestCases = $ArrHash

        }
        #$Hash.TestCases = $d.Parent.Parent.PipelineElements.CommandElements[-1].Elements


        $Obj = [PesterItBlock]::New($Hash.Name,$Hash.Value,[PesterType]::It,$Hash.Content,$Hash.TestCases)
        $Obj.SetPending($Hash['IsPending'])
        $Obj.SetSkipped($Hash['IsSkipped'])
        $AllItBlocks += $Obj
    }

    return $AllItBlocks
}
function Get-PLPesterResultsDocument {
    [CmdletBinding()]
    param (
        [String]$Path
    )
    
    begin {
        $Items = Get-ITem $Path
        $Documents = @()
    }
    
    process {
        Foreach($Item in $Items){

            $Documents += [PesterResultsDocument]::New($Item)
        }
    }
    
    end {
        return $Documents
    }
}
Function Get-PLPesterScript {
    <#
    .SYNOPSIS
        Gets the contents of a pester script
    .DESCRIPTION
        Allows to returns the contents of a pester script. It gets the following elements of a pester script:
        InmoduleScope,Context,Describe and IT blocks.
 
        It will also return recursiveley get any child pester block that a parent pester block contains.
    .EXAMPLE
        path PesterBlocks
        ---- ------------
        C:\Code\PSHTML\Tests\Address.Tests.ps1 {Testing PSHTML, Testing Address, It "Should contain opening and closing tags" {.
    .INPUTS
        System.IO.FileInfo
    .OUTPUTS
        PesterScript
    .NOTES
        Author: Stéphane van Gulick
    #>

    [CmdletBinding()]
    Param(
        [ValidateScript({
            test-Path $_
        })]
        $Path
    )
    $e = [PesterScript]::New($Path)


    return $e
}
Function Write-PLReport {
    [CmdletBinding()]
    Param(
        $Path,
        [Parameter(ParameterSetName='ExportToFile')]
        $ExportPath,
        [Parameter(ParameterSetName='ExportToHTML')]
        [Switch]$ReturnHTMLOnly,
        [Parameter(ParameterSetName='ExportToFile')]
        [Switch] $Show
    )


        #Check PSHTML
        try{
            Import-Module 'PSHTML' -Force
        }Catch{
            Write-warning "PSHTML is a prerequisite for this cmdlet to work. It was not found on this system and therefor cannot work. You can add it to the system using 'install-module PSHTML'"
            Exit 1
        }

        #Grab NunitFiles
        $PesterDoc = Get-PLPesterResultsDocument -Path $Path
        #Support for other files types?? (JSON?)

        $Html = html -Attributes @{lang = "en" } -Content {
            head {
                meta -charset 'UTF-8'
                meta -name 'author' -content "Stephane van Gulick"
                Title -Content "Pester Report"
                Write-PSHTMLAsset -Name JQuery 
                Write-PSHTMLAsset -Name Bootstrap 
                Write-PSHTMLAsset -Name ChartJs
        
                Style {
                    $chartColors = @{
                        red    = 'rgb(255, 99, 132)'
                        orange = 'rgb(255, 159, 64)'
                        yellow = 'rgb(255, 205, 86)'
                        green  = 'rgb(75, 192, 192)'
                        blue   = 'rgb(54, 162, 235)'
                        purple = 'rgb(153, 102, 255)'
                        grey   = 'rgb(231,233,237)'
                    }
@"
        .theadfailed {
            color: #401500;
            background-color: #FFDDCC;
            border-color: #792700;
        }
        td{
            word-break: break-all;
        }
"@

                }
            }
            $TableClasses = "table table-bordered table-hover"
            $TableHeaders = "thead-dark"
            $CanvasFixturesId = 'Chart_FixtureID'
            Body {
                Div -Class 'container' {
        
                    Div -Class 'Jumbotron' {
                        H1 -Class "display-4" {
                            "Pester Report Summary"
                        }
                        p -Class 'lead' {
                            "Detail run statistics from last pester runs. "
                        }
                    }
        
                    Div -Class 'Summary' {
                        H1 {
                            "Overview"
                        }
        
                        Table -Class $TableClasses -content {
                            tr {
                                td {
                                    'Source File'
                                }
                                td {
                                    $($PEsterDoc.Path.FullName)
                                }
                            }
                        }
        
                        ConvertTo-PSHTMLTable -Object $PesterDoc.Data -Properties TotalCount, PassedCount, FailedCount, SkippedCount, PendingCount, InconclusiveCount -TableClass $TableClasses -TheadClass $TableHeaders
                    
                        Div -Class "Container" -Style "text-align:center" -Content {
                            Div -Id 'ChartSum' -Style 'display:inline-block' -content {
        
                                $CanvasId = "Chart_Summary"
                                canvas -Id $CanvasId -Height 400px -Width 400px -Content {
                
                                }
                            }
                            Div -id 'chartFixture' -Style 'display:inline-block' -content {
                
                                canvas -Id $CanvasFixturesId -Height 400px -Width 400px -Content {
                
                                }
        
                            }
                        }
        
                    }
        
                    Div -id 'Time' {
        
                        p {
                            "Pester Run took {0} days {1} hours {2} minutes {3} seconds to execute" -f $PesterDoc.Data.Time.Days, $PesterDoc.Data.Time.hours, $PesterDoc.Data.Time.minutes, $PesterDoc.Data.time.Seconds
                        }
                    
                    } -Class "alert alert-primary"
        
                    $GroupedResults = $PesterDoc.Data.TestResult | Group-Object Result
                    #$Failed = $GroupedResults | Where-Object { $_.Name -eq 'Failed' }
        
                    #$Passed = $GroupedResults | Where-Object { $_.Name -eq 'Passed' }
        
                    

                    Div -Class 'FailedTests' -Id "TestDetails" -Content {
                        h1 {
                            "Test results: Details"
                        }
                        p {
                            "Total failed tests: {0}" -f $Failed.Count
                        }
                        $TableTop = @('Name', 'Result', 'Describe', 'Parameters', 'FailureMessage', 'Time', 'StackTrace')
                        Table -Class $TableClasses -Id 'table_failed_tests' {
                            tr -Content {
                                Foreach ($t in $TableTop) {
        
                                    th {
                                        $t
                                    }
                                }
                            }
                            Foreach ($ftest in $PesterDoc.Data.TestResult) {
            
                                #ConvertTo-PSHTMLTable -Object $ftest -Properties Name,Result,Describe,Parameters,FailureMessage,Time,StackTrace -TableClass $TableClasses -TheadClass $TableHeaders
                                #$ft | gm -MemberType Noteproperty | select Name
        
                                tr {
                                    foreach ($ft in $ftest) {
                                        
                                        foreach ($th in $TableTop) {
        
                                            if ($th -eq 'Result') {
                                                $ResultType = $null
                                                If ($ft.$th -eq 'Failed') {
                                                    $ResultType = 'badge badge-danger'
                                                }
                                                else {
                                                    $ResultType = 'badge badge-success'
                                                }
                                                td -Class $ResultType -Content {
                                                    $ft.$th
                                                }
                                            }
                                            elseIf($th -eq 'Time') {
        
                                                td {
                                                    [Math]::Round($ft.$Th.TotalSeconds,2)
                                                }
                                            }else {
        
                                                td {
                                                    $ft.$th
                                                }
                                            }
                                        }
                                    }
                                }
                            
                            }
                        }
                    }
        
        
        
                    script -content {
                        $PieData = @($PesterDoc.Data.PassedCount, $PesterDoc.Data.FailedCount, $PesterDoc.Data.SkippedCount, $PesterDoc.Data.InconclusiveCount)
                        $Labels = @("Passed", "Failed", "Skipped", "Inconclusive")
                        $colors = @("LightGreen", "Red", "Yellow", "Orange")
                        $BarDataSet = New-PSHTMLChartPieDataSet -Data $PieData -label "Pester Data" -backgroundColor $Colors
                        New-PSHTMLChart -Type Pie -DataSet $BarDataSet -Labels $Labels -CanvasID "Chart_Summary" -Title "Pester run summary (Failed 'It' Blocks)"
                    }
        
        
                    script -content {
                        $Fixtures = $PesterDoc.Data.TestResult | Group-Object Describe
                        $FixtureSuccess = 0
                        $FixtureFailed = 0
                        Foreach ($Fixture in $Fixtures) {
                            If (($Fixture.Group | Where-Object { $_.Result -eq 'Failed' } | Measure-Object).Count -eq 0) {
                            
                                $FixtureFailed++
                            }
                            else {
                                $FixtureSuccess++
                            }
                        }
                    
                        $PieData = @($FixtureSuccess, $FixtureFailed)
                        $FixtureLabels = @("Passed", "Failed")
                        $colors = @("LightGreen", "Red")
                        $FixtureDAtaSet = New-PSHTMLChartPieDataSet -Data $PieData -label "Pester Data" -backgroundColor $Colors
                        New-PSHTMLChart -Type Pie -DataSet $FixtureDAtaSet -Labels $FixtureLabels -CanvasID $CanvasFixturesId -Title "Failed Fixtures (Failed 'Describe' blocks)"
                    }
        
                }
                Footer {
                    $PSHTMLlink = a {"PSHTML"} -href "https://github.com/Stephanevg/PSHTML"  
                    h6 "Generated with &#x2764 using $($PSHTMLlink)" -Class "text-center"
                }
            }
        } 

        If($ReturnHTMLOnly){
            Return $Html    
        }Else{
            out-File -InputObject $html -Encoding utf8 -FilePath $ExportPath -Force
            If($Show){
                Start $ExportPath
            }
        }
}