PowerShell.PowerLibrary.DataBinder.psm1

#region Variables
[psobject]$Constants = @{
    DefaultModelName = "Model";
};
#endregion

#region DELEGATES
[System.Text.RegularExpressions.MatchEvaluator]$SqlParameterMatchEvaluator = {
    #region Parameters
    param
    (
        [Parameter(ValueFromPipeline=$true)]
        [Alias('M')]
        [ValidateNotNullOrEmpty()]
        [System.Text.RegularExpressions.Match]
        ${Match}
    )
    #endregion
    $Pattern = '@@';
    $Replacement = '@';
    Write-Debug $Match;
    RETURN [System.Text.RegularExpressions.Regex]::Replace($Match.Value, $Pattern, $Replacement, [System.Text.RegularExpressions.RegexOptions]::CultureInvariant);
};
#endregion

#region FUNCTIONS
FUNCTION Get-JsonFromDynamicModel
{
    #region Parameters
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline=$true)]
        [Alias('M')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Model
    )
    #endregion

    IF ([System.IO.File]::Exists($Model))
    { 
        $Data = [System.IO.File]::ReadAllText($Model);
    }
    ELSE
    {
        $Data = $Model;
    }

    $DataModel = ConvertFrom-Json -InputObject $JSON;

    RETURN $DataModel;
}

FUNCTION Get-DataModel
{
    #region Parameters
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline=$true)]
        [Alias('M')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Model,

        [Parameter(ValueFromPipeline = $true)]
        [Alias('S')]
        [ValidateNotNullOrEmpty()]
        [switch]
        $Silent = $false
    )
    #endregion

    IF(!$Silent)
    {
        Write-Host "Getting Data Model from " -NoNewline -ForegroundColor Cyan;
        Write-Host $Model -ForegroundColor White;
        Write-Host "`n";
    }

    IF ([System.IO.File]::Exists($Model))
    { 
        $JSON = [System.IO.File]::ReadAllText($Model);
    }
    ELSE
    {
        $JSON = $Model;
    }

    $DataModel = ConvertFrom-Json -InputObject $JSON;
    
    RETURN $DataModel;
}

FUNCTION Get-DataModelHashTable
{
    #region Parameters
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline=$true)]
        [Alias('M')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Model,

        [Parameter(ValueFromPipeline = $true)]
        [Alias('S')]
        [ValidateNotNullOrEmpty()]
        [switch]
        $Silent = $false
    )
    #endregion

    $DataModel = Get-DataModel -Model $Model -Silent:$Silent;

    IF($DataModel -eq $null) { RETURN $null; }

    IF(!$Silent)
    {
        Write-Host "Convert Data Model (" -NoNewline -ForegroundColor Cyan;
        Write-Host $Model -NoNewline -ForegroundColor White;
        Write-Host ") into Hash Table" -ForegroundColor Cyan;
        Write-Host "`n";
    }

    $Value = @{};

    $DataModel.psobject.Properties | %{ $Value[$_.Name] = $_.Value; }

    RETURN $Value;
}

FUNCTION Get-TemplateFileInfo
{
    #region Parameters
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline=$true)]
        [Alias('I')]
        [ValidateNotNullOrEmpty()]
        [System.IO.FileInfo]
        $FileInfo
    )
    #endregion

    RETURN @{
        FoamFileFullName = $FileInfo.FullName;
        FileFullName = (Join-Path -Path $FileInfo.DirectoryName -ChildPath $FileInfo.BaseName);
    };
}

FUNCTION Get-TemplateFiles
{
    #region Parameters
    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline=$true, Mandatory = $true)]
        [Alias('P')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Path,

        [Parameter(ValueFromPipeline = $true, Mandatory = $false)]
        [Alias('extensions')]
        [string[]]
        $IncludedExtensions = @('*.foam','*.template'),

        [Parameter(ValueFromPipeline = $true)]
        [Alias('S')]
        [switch]
        $Silent = $false
    )
    #endregion

    IF(!$Silent)
    {
        Write-Host "Enumerating all template files under the following directory: " -ForegroundColor Cyan; 
    }
    $Path | %{ IF(!$Silent){ Write-Host "***" $_ -ForegroundColor White; } }
    IF(!$Silent)
    { 
        Write-Host "`n";
    }

    RETURN Get-ChildItem -Path $Path -Include $IncludedExtensions -Recurse;
}

FUNCTION Get-ModelKey
{
    #region Parameters
    [CmdletBinding()]
    param
    (    
        [Parameter(ValueFromPipeline = $true)]
        [Alias('V')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Value,

        [Parameter(ValueFromPipeline = $true, Mandatory = $false)]
        [string]
        $ModelName = $Constants.DefaultModelName
    )
    #endregion

    IF([string]::IsNullOrWhiteSpace($ModelName))
    {
        $ModelName = $Constants.DefaultModelName;
    }

    $KeyPattern = "@$ModelName.";
    $Split = [System.Text.RegularExpressions.Regex]::Split($Value, $KeyPattern, [System.Text.RegularExpressions.RegexOptions]::CultureInvariant);
    IF($Split -ne $null -and $Split.Length -eq 2)
    {
        RETURN $Split[1];
    }
    RETURN $Value;
}

FUNCTION Repair-SqlTemplates
{
    #region Parameters
    [CmdletBinding()]
    param
    (    
        [Parameter(ValueFromPipeline = $true)]
        [Alias('T')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Template,

        [Parameter(ValueFromPipeline = $true, Mandatory = $false)]
        [string]
        $ModelName = $Constants.DefaultModelName
    )
    #endregion

    IF([string]::IsNullOrWhiteSpace($ModelName))
    {
        $ModelName = $Constants.DefaultModelName;
    }

    $SqlParameterPattern = '@+\w+';
    $Template = [System.Text.RegularExpressions.Regex]::Replace($Template, $SqlParameterPattern, $SqlParameterMatchEvaluator, [System.Text.RegularExpressions.RegexOptions]::CultureInvariant);

    RETURN $Template;
}

FUNCTION Get-Matches
{
    #region Parameters
    [CmdletBinding()]
    param
    (    
        [Parameter(ValueFromPipeline = $true)]
        [Alias('T')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Template,

        [Parameter(ValueFromPipeline = $true, Mandatory = $false)]
        [string]
        $ModelName = $Constants.DefaultModelName
    )
    #endregion

    IF([string]::IsNullOrWhiteSpace($ModelName))
    {
        $ModelName = $Constants.DefaultModelName;
    }

    $Template = Repair-SqlTemplates -Template $Template -ModelName $ModelName;

    $ModelPattern = "@$ModelName.\w+";
    $Matches = [System.Text.RegularExpressions.Regex]::Matches($Template, $ModelPattern, [System.Text.RegularExpressions.RegexOptions]::CultureInvariant);    

    RETURN $Matches;
}

FUNCTION Parse-Template
{
    #region Parameters
    [CmdletBinding()]
    param
    (    
        [Parameter(ValueFromPipeline = $true)]
        [Alias('T')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Template,

        [Parameter(ValueFromPipeline = $true)]
        [Alias('D')]
        [ValidateNotNullOrEmpty()]
        [Hashtable]
        $Data,

        [Parameter(ValueFromPipeline = $true, Mandatory = $false)]
        [string]
        $ModelName = $Constants.DefaultModelName
    )
    #endregion

    IF([string]::IsNullOrWhiteSpace($ModelName))
    {
        $ModelName = $Constants.DefaultModelName;
    }

    $Matches = Get-Matches -ModelName $ModelName -Template $Template;

    $IsValid = $true;
    $MissingKeys = @();
    $Patterns = [System.Collections.Generic.SortedSet[string]]::new();

    FOREACH($Match in $Matches)
    {
        IF(!$Patterns.Contains($Match.Value))
        {
            $__ = $Patterns.Add($Match.Value);
        }
    }

    FOREACH($Pattern in $Patterns.Reverse())
    {
        $Key = Get-ModelKey -Value $Pattern -ModelName $ModelName;
        IF(!$Data.ContainsKey($Key))
        {   
            $IsValid = $false;  
            $MissingKeys += $Key;
        }    
        IF($IsValid)
        {
            $Value = $Data[$Key];
            IF($Value -eq $null) { $Value = ''; }
            $Template = [System.Text.RegularExpressions.Regex]::Replace($Template, $Pattern, $Value);
        }
    }

    IF(!$IsValid)
    {        
        Write-Warning "Data Model DOES NOT match the Template.`r`nModel Properties defined in the Template file must match the properties in the data model.";
        $MissingKeys | %{ Write-Warning "Data Model Missing Key: $_"; };
        RETURN $null; 
    }

    RETURN $Template;
}

FUNCTION Get-DataModelKeys
{
    #region Parameters
    [CmdletBinding()]
    param
    (    
        [Parameter(ValueFromPipeline = $true)]
        [Alias('T')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Template,

        [Parameter(ValueFromPipeline = $true)]
        [Alias('D')]
        [ValidateNotNullOrEmpty()]
        [Hashtable]
        $Data,

        [Parameter(ValueFromPipeline = $true, Mandatory = $false)]
        [string]
        $ModelName = $Constants.DefaultModelName
    )
    #endregion

    IF([string]::IsNullOrWhiteSpace($ModelName))
    {
        $ModelName = $Constants.DefaultModelName;
    }

    $Matches = Get-Matches -ModelName $ModelName -Template $Template;
    
    $IsValid = $true;
    $ModelKeys = New-Object 'System.Collections.Generic.HashSet[string]';

    FOREACH($Match in $Matches)
    {
        $Key = Get-ModelKey -Value $Match.Value -ModelName $ModelName;
        IF(![string]::IsNullOrEmpty($Key))
        {
            $__ = $ModelKeys.Add($Key);
        }
    }
    
    RETURN $ModelKeys;
}

FUNCTION Get-FormattedTemplate
{
    <#
    .Synopsis
        Databind all templates as per a certain Data Model.
    .DESCRIPTION
        This Function goes through all paths and match against ModelName to data bind all templates from a certain Data Model.
    .PARAMETER Model
        The Model of Which templates will be bound against.
    .PARAMETER ModelName
        This is Model Name being Used.
    .PARAMETER Path
        This is the URL Paths to loop through.
    .PARAMETER OutputPath
        This is the Path where to save the Formatted Template.
    .PARAMETER IncludedExtensions
        Array of all file extensions need to be looked up.
    .OUTPUTS
        System.String. Get-FormattedTemplate returns a the formatted template string.
    .NOTES
        This Method Pipes the Formated Template and Saves it in the Output Path if Provided.
    #>


    #region Parameters
    [CmdletBinding()]
    param
    (    
        [Parameter(ValueFromPipeline = $true)]
        [Alias('M')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Model, 

        [Parameter(ValueFromPipeline = $true, Mandatory = $false)]
        [string]
        $ModelName,

        [Parameter(ValueFromPipeline = $true)]
        [Alias('P')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Path,

        [Parameter(ValueFromPipeline = $true)]
        [Alias('Out')]
        [string]
        $OutputPath,

        [Parameter(ValueFromPipeline = $true)]
        [Alias('S')]
        [ValidateNotNullOrEmpty()]
        [switch]
        $Silent = $false
    )
    #endregion

    $DataModel = Get-DataModelHashTable -Model $Model -Silent:$Silent;
    IF($DataModel -eq $null)
    {
        Write-Error "Data Model Cannot be NULL." -RecommendedAction "Model must be a VALID JSON";
        RETURN;
    }
    
    IF(![System.IO.File]::Exists($Path))
    {
        Write-Error "Could NOT find '$Path' Template file!!!";
        RETURN;
    } 
        
    IF(!$Silent)
    {
        Write-Host "Formatting..." -ForegroundColor Gray;
        Write-Host "*** From: " -NoNewline -ForegroundColor Yellow;
        Write-Host $Path -ForegroundColor White;
        Write-Host "*** To: " -NoNewline -ForegroundColor Yellow;
        Write-Host $OutputPath -ForegroundColor White;
    }

    $Template = [System.IO.File]::ReadAllText($Path);
    $Template = Parse-Template -Template $Template -Data $DataModel -ModelName $ModelName;

    IF(!$Silent)
    {
        Write-Host "Formatting Result: " -NoNewline -ForegroundColor Gray;
    }

    IF(![string]::IsNullOrWhiteSpace($Template) -and ![string]::IsNullOrWhiteSpace($OutputPath))
    {    
        IF([System.IO.File]::Exists($OutputPath))
        {
            Set-ItemProperty -Path $OutputPath -Name IsReadOnly -Value $false;
        } 
               
        IF(!$Silent)
        {
            Write-Host "Template is being saved" -ForegroundColor Green;
        }

        $FileInfo = [System.IO.FileInfo]::new($OutputPath);
        IF(!$FileInfo.Directory.Exists)
        {
            $FileInfo.Directory.Create();
        }

        [System.IO.File]::WriteAllText($OutputPath, $Template);
    }
    ELSE
    {
        IF(!$Silent)
        {
            Write-Host "Template is Skipped" -ForegroundColor Red;
        }
    }

    IF(!$Silent)
    {
        Write-Host;
    }

    RETURN $Template;
}

FUNCTION Set-DataBinding
{
    <#
    .Synopsis
        Databind all templates as per a certain Data Model.
    .DESCRIPTION
        This Function goes through all paths and match against ModelName to data bind all templates from a certain Data Model.
    .PARAMETER Model
        The Model of Which templates will be bound against.
    .PARAMETER ModelName
        This is Model Name being Used.
    .PARAMETER Path
        This is the URL Paths to loop through.
    .PARAMETER IncludedExtensions
        Array of all file extensions need to be looked up.
    #>


    #region Parameters
    [CmdletBinding()]
    param
    (    
        [Parameter(ValueFromPipeline = $true)]
        [Alias('M')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Model, 

        [Parameter(ValueFromPipeline = $true, Mandatory = $false)]
        [string]
        $ModelName,

        [Parameter(ValueFromPipeline = $true)]
        [Alias('P')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Path,

        [Parameter(ValueFromPipeline = $true, Mandatory = $false)]
        [Alias('extensions')]
        [string[]]
        $IncludedExtensions = @('*.foam','*.template'),

        [Parameter(ValueFromPipeline = $true)]
        [Alias('S')]
        [ValidateNotNullOrEmpty()]
        [switch]
        $Silent = $false
    )
    #endregion

    $DataModel = Get-DataModelHashTable -Model $Model -Silent:$Silent;
    IF($DataModel -eq $null)
    {
        Write-Error "Data Model Cannot be NULL." -RecommendedAction "Model must be a VALID JSON";
        RETURN;
    }

    $Templates = Get-TemplateFiles -Path $Path -IncludedExtensions $IncludedExtensions -Silent:$Silent;
    IF($Templates -eq $null)
    {
        Write-Error "Could NOT find any '$([string]::Join(', ', $IncludedExtensions))' file!!!";
        RETURN;
    }    

    IF(!$Silent)
    {
        Write-Host "Formatting " -NoNewline -ForegroundColor Cyan;
        Write-Host $($Templates.Count) -NoNewline  -ForegroundColor White;
        Write-Host " foamed file(s)." -ForegroundColor Cyan;
    }

    FOREACH($FI in $Templates)
    {
        $Reference = Get-TemplateFileInfo -FileInfo $FI;
        
        IF(!$Silent)
        {
            Write-Host "Formatting..." -ForegroundColor Gray;
            Write-Host "*** From: " -NoNewline -ForegroundColor Yellow;
            Write-Host $($Reference.FoamFileFullName) -ForegroundColor White;
            Write-Host "*** To: " -NoNewline -ForegroundColor Yellow;
            Write-Host $($Reference.FileFullName) -ForegroundColor White;
        }
        IF([System.IO.File]::Exists($Reference.FileFullName))
        {
            Set-ItemProperty -Path $Reference.FileFullName -Name IsReadOnly -Value $false;
        }

        $Template = [System.IO.File]::ReadAllText($Reference.FoamFileFullName);
        $Template = Parse-Template -Template $Template -Data $DataModel -ModelName $ModelName;

        IF(!$Silent)
        {
            Write-Host "Formatting Result: " -NoNewline -ForegroundColor Gray;
        }
        IF(![string]::IsNullOrWhiteSpace($Template))
        {            
            IF(!$Silent)
            {
                Write-Host "Template is being saved" -ForegroundColor Green;
            }

            $FileInfo = [System.IO.FileInfo]::new($Reference.FileFullName);
            IF(!$FileInfo.Directory.Exists)
            {
                $FileInfo.Directory.Create();
            }

            [System.IO.File]::WriteAllText($Reference.FileFullName, $Template);
        }
        ELSE
        {
            IF(!$Silent)
            {
                Write-Host "Template is Skipped" -ForegroundColor Red;
            }
        }
        IF(!$Silent)
        {
            Write-Host;
        }
    }
}

FUNCTION Get-DataModelTemplate
{
    <#
    .Synopsis
        Generates Data Template Model.
    .DESCRIPTION
        This Function goes through all paths and match against ModelName to build up JSON model as a template to be saved in the output Path.
    .PARAMETER ModelName
        This is Model Name being Used.
    .PARAMETER Path
        This is the URL Paths to loop through.
    .PARAMETER OutputPath
        This is the Path where to save the output JSON model.
    .PARAMETER IncludedExtensions
        Array of all file extensions need to be looked up.
    #>


    #region Parameters
    [CmdletBinding()]
    param
    (    
        [Parameter(ValueFromPipeline = $true, Mandatory = $false)]
        [string]
        $ModelName,

        [Parameter(ValueFromPipeline = $true)]
        [Alias('P')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Path,

        [Parameter(ValueFromPipeline = $true)]
        [Alias('Out')]
        [string]
        $OutputPath,

        [Parameter(ValueFromPipeline = $true, Mandatory = $false)]
        [Alias('extensions')]
        [string[]]
        $IncludedExtensions = @('*.foam','*.template'),

        [Parameter(ValueFromPipeline = $true)]
        [Alias('S')]
        [ValidateNotNullOrEmpty()]
        [switch]
        $Silent = $false
    )
    #endregion
    
    $DataModel = @{};

    $Templates = Get-TemplateFiles -Path $Path -IncludedExtensions $IncludedExtensions -Silent:$Silent;
    IF($Templates -eq $null)
    {
        Write-Error "Could NOT find any '$([string]::Join(', ', $IncludedExtensions))' file!!!";
        RETURN;
    }    

    IF(!$Silent)
    {
        Write-Host "Getting Data Model Keys from " -NoNewline -ForegroundColor Cyan;
        Write-Host $($Templates.Count) -NoNewline  -ForegroundColor White;
        Write-Host " foamed file(s)." -ForegroundColor Cyan;
    }

    FOREACH($FI in $Templates)
    {
        $Reference = Get-TemplateFileInfo -FileInfo $FI;
        
        IF(!$Silent)
        {
            Write-Host "Getting Data Model Keys..." -ForegroundColor Gray;
            Write-Host "*** From: " -NoNewline -ForegroundColor Yellow;
            Write-Host $($Reference.FoamFileFullName) -ForegroundColor White;
        }

        $Template = [System.IO.File]::ReadAllText($Reference.FoamFileFullName);
                
        Get-DataModelKeys -ModelName $ModelName -Template $Template | %{ $DataModel[$_] = ''; }

        IF(!$Silent)
        {
            Write-Host;
        }
    }

    $JSON = ConvertTo-Json -InputObject $DataModel;

    IF(![string]::IsNullOrEmpty($OutputPath) -and ![string]::IsNullOrEmpty($JSON))
    {
        IF(![System.IO.Path]::HasExtension($OutputPath))
        {
            $OutputPath = [string]::Concat($OutputPath, '.json');
        }

        $FileInfo = [System.IO.FileInfo]::new($OutputPath);
        IF(!$FileInfo.Directory.Exists)
        {
            $FileInfo.Directory.Create();
        }

        [System.IO.File]::WriteAllText($OutputPath, $JSON);
    }

    RETURN $JSON;
}
#endregion