DDO-PSTemplates.psm1





# PUBLIC
function Get-PSTemplateVariables {
    [CmdletBinding()]
    Param(

        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            Position=0
        )]
        [Alias('FullName')]
        [String]
        $Path,

        [Parameter( Position=1 )]
        [Switch]
        $OutHashString,

        [Parameter( DontShow )]
        [switch]
        $OutInvokeTemplate

    )

    BEGIN {

        $baseMessage = "[ $($MyInvocation.InvocationName) ]"
        Write-Verbose "$baseMessage Executing"

    }

    PROCESS {

        $global:uniqueHash = [ordered]@{}

        $templateItemPaths = if (($Path | Get-PSTemplateType) -eq 'TemplateStack') {

            Get-ChildItem -Path $Path -Recurse

        } elseif (($Path | Get-PSTemplateType) -eq 'TemplateFile') {

            $Path

        } else {

            throw("$baseMessage Unexpected PSTemplateType: $($Path | Get-PSTemplateType)")

        }

        #--------------------------------------------------------------------------

        $variablesResult = $templateItemPaths | Foreach-Object {

            $_ | Get-TemplateVariables | Foreach-Object {

                $v = $_
            
                if ( -not ($uniqueHash.Contains($v.Name))) {

                    $uniqueHash.Add($v.Name,[ordered]@{
                        Replace     = $v.Match
                        With        = $null
                        RegExEscape = $v.RegExEscape 
                    })

                }
            
                $v
            
            }

        }

    }

    END {

        if (-not ($PSBoundParameters.ContainsKey('OutInvokeTemplate'))) {

            if (-not ($PSBoundParameters.ContainsKey('OutHashString'))) {

                $variablesResult | Sort-Object -Property Name

            } else {

                $maxDepth = $uniqueHash.Keys | Foreach-Object {$_.Length} | Sort-Object -Descending | Select-Object -First 1

                $sb = [System.Text.StringBuilder]::new()
                [void]$sb.AppendLine('')
                [void]$sb.AppendLine('$VariablesHashTable = @{')

                $uniqueHash.Keys | Foreach-Object {
                    $varName      = $_.ToString()
                    $depthString  = " " * ($maxDepth - $varName.Length)
                    $string       = "`t$varName$($depthString) = `"`""
                    [void]$sb.AppendLine($string)
                }

                [void]$sb.AppendLine('}')

                $sb.ToString()

            }

        } else {

            $uniqueHash

        }

    }

}

# PUBLIC
function Invoke-PSTemplate {
    [CmdletBinding()]
    Param(
      
        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            Position=0
        )]
        [Alias( 'FullName' )]
        [String]
        $Path,

        [Parameter(
            Mandatory,
            Position=1
        )]
        [HashTable]
        $VariablesHashtable,

        [Parameter(
            Mandatory,
            Position=2
        )]
        [System.IO.DirectoryInfo]
        $OutputDirectory

    )

    BEGIN {

        $baseMessage = "[ $($MyInvocation.InvocationName) ]"
        Write-Verbose "$baseMessage Executing"
        
        $regExTemplateName = [regex]::new('^\.pstpl\.')
        $regExVariableName = [regex]::new('\{{2}\s(?<Variable>\w*)\s\}{2}')

    }

    PROCESS {

        Write-Verbose " - Finding all Template Variables"

        $templateVariables = $Path | Get-PSTemplateVariables -OutInvokeTemplate -Verbose:$false


        Write-Verbose " - Resolving Template Variables"

        $params = @{
            TemplateVariables = $templateVariables
            InputVariables    = $VariablesHashtable
            Verbose           = $false
        }

        $process = Resolve-TemplateVariables @params


        switch ($Path | Get-PSTemplateType -Verbose:$false) {

            'TemplateStack' {

                $stackDirectory = [System.IO.DirectoryInfo](Resolve-Path $Path).Path
                $stackName      = $stackDirectory.Name -replace $regExTemplateName,''

                Write-Verbose " - Creating TemplateStack $($stackName)"

                try {

                    Push-Location $stackDirectory

                    $params = @{
                        Path              = $stackDirectory
                        TemplateVariables = $process
                        OutputDirectory   = $outputDirectory
                        Verbose           = $false
                    }
                    
                    Invoke-ProcessTemplateDirectory @params
                        
                    
                    $stackDirectory | Get-ChildItem -Directory -Recurse | Foreach-Object {
    
                        $directory = $_

                        #---------------------------------------------------------

                        $resolvedDirectoryPath = (Resolve-Path $directory -Relative) | ConvertFrom-TemplateFileName -TemplateVariables $process -Verbose:$false
                        $outputDirectoryPath   = Join-Path $outputDirectory ($resolvedDirectoryPath -replace '^\.\\','')
                        
                        #---------------------------------------------------------

                        mkdir $outputDirectoryPath -Force | Out-Null

                        #---------------------------------------------------------

                        $params = @{
                            Path              = $directory
                            TemplateVariables = $process
                            OutputDirectory   = $outputDirectoryPath
                            Verbose           = $false
                        }
                        
                        Invoke-ProcessTemplateDirectory @params

                  
                    }

                } finally {

                    Pop-Location

                }

            }

            'TemplateFile' {
 
                $params = @{
                    Path              = $Path
                    TemplateVariables = $process
                    OutputDirectory   = $OutputDirectory
                    Verbose           = $false
                }

                ConvertFrom-TemplateFile @params


            }


        }


    }

    END {

        Write-Verbose "$baseMessage Execution Complete"

    }

}

# PRIVATE
function ConvertFrom-TemplateFile {
    [CmdletBinding()]
    Param(

        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            Position=0
        )]
        [Alias( 'FullName' )]
        [String]
        $Path,

        [Parameter(
            Mandatory,
            Position=1
        )]
        $TemplateVariables,

        [Parameter(
            Mandatory,
            Position=2
        )]
        [System.IO.DirectoryInfo]
        $OutputDirectory


    )

    BEGIN {

        $baseMessage = "[ $($MyInvocation.InvocationName) ]"
        Write-Verbose "$baseMessage Executing"

        $regExTemplateName = [regex]::new('^\.pstpl\.')
        $regExVariableName = [regex]::new('\{{2}\s(?<Variable>\w*)\s\}{2}')

    }

    PROCESS {

        $templateFile = [System.IO.FileInfo](Resolve-Path $Path).Path
        Write-Verbose " - Creating TemplateFile $($templateFile.Name)"

        $fromName     = $templateFile.Name
        $toName       = ConvertFrom-TemplateFileName    -Name $fromName     -TemplateVariables $TemplateVariables -Verbose:$false
        $content      = ConvertFrom-TemplateFileContent -Path $templateFile -TemplateVariables $TemplateVariables -Verbose:$false

        Write-Verbose " - Converted TemplateFile name $fromName to $toName"

        $outputPath = Join-Path $OutputDirectory $toName

        Write-Verbose " - Exporting TemplateFile $toName to $outputPath"
        $content | Out-File $outputPath -Force

    }

}

# PRIVATE
function ConvertFrom-TemplateFileContent {
    [CmdletBinding()]
    Param(

        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            Position=0
        )]
        [Alias( 'FullName' )]
        [String]
        $Path,

        [Parameter(
            Mandatory,
            Position=1
        )]
        $TemplateVariables

    )

    BEGIN {

        $baseMessage = "[ $($MyInvocation.InvocationName) ]"
        Write-Verbose "$baseMessage Executing"

        $regExTemplateName = [regex]::new('^\.pstpl\.')
        $regExVariableName = [regex]::new('\{{2}\s(?<Variable>\w*)\s\}{2}')

    }

    PROCESS {

        $content = Get-Content -Path $Path -Raw

        $TemplateVariables.Keys | Foreach-Object {
            $variableName = $_
            $replace      = $TemplateVariables[$variableName]

            $content = $content -replace $replace.REgExEscape,$replace.With

        }

        $content

    }

}

# PRIVATE
function ConvertFrom-TemplateFileName {
    [CmdletBinding()]
    Param(
        [Parameter(
            Mandatory,
            ValueFromPipeline,
            Position=0
        )]
        [String]
        $Name,

        [Parameter(
            Mandatory,
            Position=1
        )]
        $TemplateVariables

    )

    BEGIN {

        $baseMessage = "[ $($MyInvocation.InvocationName) ]"
        Write-Verbose "$baseMessage Executing"

        $regExTemplateName = [regex]::new('^\.pstpl\.')
        $regExVariableName = [regex]::new('\{{2}\s(?<Variable>\w*)\s\}{2}')

    }

    PROCESS {

        $Name = $Name -replace $regExTemplateName,''

        $TemplateVariables.Keys | Foreach-Object {
            $variableName = $_
            $replace      = $TemplateVariables[$variableName]

            $Name = $Name -replace $replace.REgExEscape,$replace.With

        }

        $Name

    }

}

# PRIVATE
function Get-PathType {
    [CmdletBinding()]
    Param(
        [Parameter(
            Position=0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('FullName')]
        [string]
        $Path = ".\"
    )

    Begin {

        $baseMessage = "[ $($MyInvocation.InvocationName) ]"
        Write-Verbose "$baseMessage Executing"
    }

    Process {

        if (Test-Path -Path $Path) {
            
            if (Test-Path -Path $Path -PathType Container -ErrorAction SilentlyContinue) {

                'Directory'
                
            } elseif (Test-Path -Path $Path -PathType Leaf -ErrorAction SilentlyContinue) {

                'File'

            } else {

                throw "$baseMessage Path exists, but unknown path type..."

            }

        } else {

            throw "$baseMessage Path does not exist. Path: $Path"

        }

    }

}

# PRIVATE
function Get-PSTemplateType {
    [CmdletBinding()]
    Param(
        [Parameter(
            Position=0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('FullName')]
        [string]
        $Path = ".\"
    )

    Begin {

        $baseMessage = "[ $($MyInvocation.InvocationName) ]"
        Write-Verbose "$baseMessage Executing"

        $regExTemplateName = [regex]::new('^\.pstpl\.')
        $regExVariableName = [regex]::new('\{{2}\s(?<Variable>\w*)\s\}{2}')

    }

    Process {

        switch ($Path | Get-PathType) {

            'Directory' {

                $directory = [System.IO.DirectoryInfo]$Path

                if ($directory.Name -match $regExTemplateName) {

                    'TemplateStack'

                } else {

                    throw("$baseMessage $($directory.FullName) is not a valid TemplateStack directory. The directory name should be prefixed with `".pstpl.`"")
                
                }

            }

            'File' {


                $file = [System.IO.FileInfo]$Path

                if ($file.Name -match $regExTemplateName) {

                    'TemplateFile'

                } else {

                    throw("$baseMessage $($file.FullName) is not a valid TemplateStack directory. The directory name should be prefixed with `".pstpl.`"")
                
                }

            }

        }

    }

}

# PRIVATE
function Get-TemplateVariables {
    [CmdletBinding()]
    Param(

        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('FullName')]
        [String]
        $Path

    )

    BEGIN {

        $baseMessage = "[ $($MyInvocation.InvocationName) ]"
        Write-Verbose "$baseMessage Executing"

        $regExTemplateName = [regex]::new('^\.pstpl\.')
        $regExVariableName = [regex]::new('\{{2}\s(?<Variable>\w*)\s\}{2}')
        

    }

    PROCESS {

        switch ($Path | Get-PathType) {

            'Directory' {
            
                $directory = [System.IO.DirectoryInfo](Resolve-Path $Path).Path

                $regExVariableName.Matches($directory.Name) | Foreach-Object {

                    $m = $_
                    
                    [PSCustomObject]@{
                        Name        = $m.Groups['Variable'].Value
                        LineNumber  = 'InName'
                        Match       = $m.Groups[0].Value
                        RegExEscape = [System.Text.RegularExpressions.Regex]::Escape($m.Groups[0].Value)
                        Path        = $directory.FullName
                    }

                }

            }

            'File' {

                $file = [System.IO.FileInfo](Resolve-Path $Path).Path

                $regExVariableName.Matches($file.Name) | Foreach-Object {
                    
                    $m = $_
                    
                    [PSCustomObject]@{
                        Name        = $m.Groups['Variable'].Value
                        LineNumber  = 'InName'
                        Match       = $m.Groups[0].Value
                        RegExEscape = [System.Text.RegularExpressions.Regex]::Escape($m.Groups[0].Value)
                        Path        = $file.FullName
                    }

                }

                $content = Get-Content -Path $file -Raw

                if ($content) {

                    $regExVariableName.Matches($content) | Foreach-Object {
                        $m          = $_
                        $lineNumber = ($content.Substring(0, $m.Index + 1) | Measure-Object -Line).Lines
                    
                        [PSCustomObject]@{
                            Name        = $m.Groups['Variable'].Value
                            LineNumber  = $lineNumber
                            Match       = $m.Groups[0].Value
                            RegExEscape = [System.Text.RegularExpressions.Regex]::Escape($m.Groups[0].Value)
                            Path        = $file.FullName
                        }

                    }

                }

            }

        }

    }

}

# PRIVATE
function Invoke-ProcessTemplateDirectory {
    [CmdletBinding()]
    Param(

        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            Position=0
        )]
        [Alias( 'FullName' )]
        [String]
        $Path,

        [Parameter(
            Mandatory,
            Position=1
        )]
        $TemplateVariables,

        [Parameter(
            Mandatory,
            Position=2
        )]
        [System.IO.DirectoryInfo]
        $OutputDirectory


    )

    BEGIN {

        $baseMessage = "[ $($MyInvocation.InvocationName) ]"
        Write-Verbose "$baseMessage Executing"

    }

    PROCESS {

        $Path | Get-ChildItem -File | Foreach-Object {

            $file = $_

            if ($file.Name -match $regExTemplateName) {

                Write-Verbose "- Processing TemplateFile $($file.Name)"

                $params = @{
                    Path              = $file
                    TemplateVariables = $TemplateVariables
                    OutputDirectory   = $outputDirectory
                }

                ConvertFrom-TemplateFile @params

            } else {
                
                $updatedFileName = $file.Name | ConvertFrom-TemplateFileName -TemplateVariables $TemplateVariables
                $updatedFilePath = Join-Path $outputDirectory $updatedFileName
                
                Copy-Item -Path $file.FullName -Destination $updatedFilePath -Force


            }

        }

    }

}

# PRIVATE
function Resolve-TemplateVariables {
    [CmdletBinding()]
    Param(

        [Parameter(
            Mandatory,
            ValueFromPipeline,
            Position=0
        )]
        [System.Collections.Specialized.OrderedDictionary]
        $TemplateVariables,

        [Parameter(
            Mandatory,
            Position=1
        )]
        [HashTable]
        $InputVariables

    )
    
    BEGIN {

        $baseMessage = "[ $($MyInvocation.InvocationName) ]"
        Write-Verbose "$baseMessage Executing"

    }

    PROCESS {

        $toProcessHash = [ordered]@{}
        $warnings      = [System.Collections.Generic.List[string]]::new()

        $TemplateVariables.Keys | Foreach-Object {

            $variableName  = $_

            if (-not ($InputVariables.ContainsKey($variableName))) {

                <#
                    The Below is to resolve any conflicts for missing variables or warning of possible case conslicts
                #>


                $TemplateVariables[$variableName].With = Read-Host -Prompt "Enter in the value for the Template Variable: $($variableName)"

                # TODO: Need to add some case sensitivity warnings...

            } else {

                <#
                    This is if the user has supplied a variable name and value correctly
                #>

                
                $TemplateVariables[$variableName].With = $InputVariables[$variableName]

            }

        }

        $TemplateVariables

    }


}