Public/ScopedVariables.ps1

<#
.SYNOPSIS
Converts Scoped Variables within a given ScriptBlock.
 
.DESCRIPTION
Converts Scoped Variables within a given ScriptBlock, and returns the updated ScriptBlock back, including any
using-variable values that will need to be supplied as parameters to the ScriptBlock first.
 
.PARAMETER ScriptBlock
The ScriptBlock to be converted.
 
.PARAMETER PSSession
An optional SessionState object, used to retrieve using-variable values.
If not supplied, using-variable values will not be converted.
 
.PARAMETER Exclude
An optional array of one or more Scoped Variable Names to Exclude from converting. (ie: Session, Using, or a Name from Add-PodeScopedVariable)
 
.EXAMPLE
$ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState
 
.EXAMPLE
$ScriptBlock = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -Exclude Session, Using
#>

function Convert-PodeScopedVariables {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    [OutputType([scriptblock])]
    param(
        [Parameter(ValueFromPipeline = $true)]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [System.Management.Automation.SessionState]
        $PSSession,

        [Parameter()]
        [string[]]
        $Exclude
    )

    # do nothing if no scriptblock
    if ($null -eq $ScriptBlock) {
        return $ScriptBlock
    }

    # using vars
    $usingVars = $null

    # loop through each defined scoped variable and convert, unless excluded
    foreach ($key in $PodeContext.Server.ScopedVariables.Keys) {
        # excluded?
        if ($Exclude -icontains $key) {
            continue
        }

        # convert scoped var
        $ScriptBlock, $otherResults = Convert-PodeScopedVariable -Name $key -ScriptBlock $ScriptBlock -PSSession $PSSession

        # using vars?
        if (($null -ne $otherResults) -and ($key -ieq 'using')) {
            $usingVars = $otherResults
        }
    }

    # return just the scriptblock, or include using vars as well
    if ($null -ne $usingVars) {
        return $ScriptBlock, $usingVars
    }

    return $ScriptBlock
}

<#
.SYNOPSIS
Converts a Scoped Variable within a given ScriptBlock.
 
.DESCRIPTION
Converts a Scoped Variable within a given ScriptBlock, and returns the updated ScriptBlock back, including any
other values that will need to be supplied as parameters to the ScriptBlock first.
 
.PARAMETER Name
The Name of the Scoped Variable to convert. (ie: Session, Using, or a Name from Add-PodeScopedVariable)
 
.PARAMETER ScriptBlock
The ScriptBlock to be converted.
 
.PARAMETER PSSession
An optional SessionState object, used to retrieve using-variable values or other values where scope is required.
 
.EXAMPLE
$ScriptBlock = Convert-PodeScopedVariable -Name State -ScriptBlock $ScriptBlock
 
.EXAMPLE
$ScriptBlock, $otherResults = Convert-PodeScopedVariable -Name Using -ScriptBlock $ScriptBlock
#>

function Convert-PodeScopedVariable {
    [CmdletBinding()]
    [OutputType([scriptblock])]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

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

        [Parameter()]
        [System.Management.Automation.SessionState]
        $PSSession
    )

    # do nothing if no scriptblock
    if ($null -eq $ScriptBlock) {
        return $ScriptBlock
    }

    # check if scoped var defined
    if (!(Test-PodeScopedVariable -Name $Name)) {
        # Scoped Variable not found
        throw ($PodeLocale.scopedVariableNotFoundExceptionMessage -f $Name)
    }

    # get the scoped var metadata
    $scopedVar = $PodeContext.Server.ScopedVariables[$Name]

    # invoke the logic for the appropriate conversion type required - internal function map, custom scriptblock, or simple replace
    switch ($scopedVar.Type) {
        'internal' {
            switch ($scopedVar.Name) {
                'using' {
                    return Convert-PodeScopedVariableInbuiltUsing -ScriptBlock $ScriptBlock -PSSession $PSSession
                }
            }
        }

        'scriptblock' {
            return Invoke-PodeScriptBlock `
                -ScriptBlock $scopedVar.ScriptBlock `
                -Arguments $ScriptBlock, $PSSession, $scopedVar.Get.Pattern, $scopedVar.Set.Pattern `
                -Splat `
                -Return `
                -NoNewClosure
        }

        'replace' {
            # convert scriptblock to string
            $strScriptBlock = "$($ScriptBlock)"

            # see if the script contains any form of the scoped variable, and if not just return
            $found = $strScriptBlock -imatch "\`$$($Name)\:"
            if (!$found) {
                return $ScriptBlock
            }

            # loop and replace "set" syntax if replace template supplied
            if (![string]::IsNullOrEmpty($scopedVar.Set.Replace)) {
                while ($strScriptBlock -imatch $scopedVar.Set.Pattern) {
                    $setReplace = $scopedVar.Set.Replace.Replace('{{name}}', $Matches['name'])
                    $strScriptBlock = $strScriptBlock.Replace($Matches['full'], $setReplace)
                }
            }

            # loop and replace "get" syntax
            while ($strScriptBlock -imatch $scopedVar.Get.Pattern) {
                $getReplace = $scopedVar.Get.Replace.Replace('{{name}}', $Matches['name'])
                $strScriptBlock = $strScriptBlock.Replace($Matches['full'], "($($getReplace))")
            }

            # convert update scriptblock back
            return [scriptblock]::Create($strScriptBlock)
        }
    }
}

<#
.SYNOPSIS
Adds a new Scoped Variable.
 
.DESCRIPTION
Adds a new Scoped Variable, to make calling certain functions simpler.
For example "$state:Name" instead of "Get-PodeState" and "Set-PodeState".
 
.PARAMETER Name
The Name of the Scoped Variable.
 
.PARAMETER GetReplace
A template to be used when converting "$var = $SV:<name>" to a "Get-SVValue -Name <name>" syntax.
You can use the "{{name}}" placeholder to show where the <name> would be placed in the conversion. The result will also be automatically wrapped in brackets.
For example, "$var = $state:<name>" to "Get-PodeState -Name <name>" would need a GetReplace value of "Get-PodeState -Name '{{name}}'".
 
.PARAMETER SetReplace
An optional template to be used when converting "$SV:<name> = <value>" to a "Set-SVValue -Name <name> -Value <value>" syntax.
You can use the "{{name}}" placeholder to show where the <name> would be placed in the conversion. The <value> will automatically be appended to the end.
For example, "$state:<name> = <value>" to "Set-PodeState -Name <name> -Value <value>" would need a SetReplace value of "Set-PodeState -Name '{{name}}' -Value ".
 
.PARAMETER ScriptBlock
For more advanced conversions, that aren't as simple as a simple find/replace, you can supply a ScriptBlock instead.
This ScriptBlock will be supplied ScriptBlock to convert, followed by a SessionState object, and the Get/Set regex patterns, as parameters.
The ScriptBlock should returned a converted ScriptBlock that works, plus an optional array of values that should be supplied to the ScriptBlock when invoked.
 
.EXAMPLE
Add-PodeScopedVariable -Name 'cache' -SetReplace "Set-PodeCache -Key '{{name}}' -InputObject " -GetReplace "Get-PodeCache -Key '{{name}}'"
 
.EXAMPLE
Add-PodeScopedVariable -Name 'config' -ScriptBlock {
    param($ScriptBlock, $SessionState, $GetPattern, $SetPattern)
    $strScriptBlock = "$($ScriptBlock)"
    $template = "(Get-PodeConfig).'{{name}}'"
 
    # allows "$port = $config:port" instead of "$port = (Get-PodeConfig).port"
    while ($strScriptBlock -imatch $GetPattern) {
        $getReplace = $template.Replace('{{name}}', $Matches['name'])
        $strScriptBlock = $strScriptBlock.Replace($Matches['full'], "($($getReplace))")
    }
 
    return [scriptblock]::Create($strScriptBlock)
}
#>

function Add-PodeScopedVariable {
    [CmdletBinding(DefaultParameterSetName = 'Replace')]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true, ParameterSetName = 'Replace')]
        [string]
        $GetReplace,

        [Parameter(ParameterSetName = 'Replace')]
        [string]
        $SetReplace = $null,

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

    Add-PodeScopedVariableInternal @PSBoundParameters
}

<#
.SYNOPSIS
Removes a Scoped Variable.
 
.DESCRIPTION
Removes a Scoped Variable.
 
.PARAMETER Name
The Name of a Scoped Variable to remove.
 
.EXAMPLE
Remove-PodeScopedVariable -Name State
#>

function Remove-PodeScopedVariable {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    $null = $PodeContext.Server.ScopedVariables.Remove($Name)
}

<#
.SYNOPSIS
Tests if a Scoped Variable exists.
 
.DESCRIPTION
Tests if a Scoped Variable exists.
 
.PARAMETER Name
The Name of the Scoped Variable to check.
 
.EXAMPLE
if (Test-PodeScopedVariable -Name $Name) { ... }
#>

function Test-PodeScopedVariable {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )

    return $PodeContext.Server.ScopedVariables.Contains($Name)
}

<#
.SYNOPSIS
Removes all Scoped Variables.
 
.DESCRIPTION
Removes all Scoped Variables.
 
.EXAMPLE
Clear-PodeScopedVariables
#>

function Clear-PodeScopedVariables {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param()

    $null = $PodeContext.Server.ScopedVariables.Clear()
}

<#
.SYNOPSIS
Get a Scoped Variable(s).
 
.DESCRIPTION
Get a Scoped Variable(s).
 
.PARAMETER Name
The Name of the Scoped Variable(s) to retrieve.
 
.EXAMPLE
Get-PodeScopedVariable -Name State
 
.EXAMPLE
Get-PodeScopedVariable -Name State, Using
#>

function Get-PodeScopedVariable {
    [CmdletBinding()]
    [OutputType([System.Object[]])]
    param(
        [Parameter()]
        [string[]]
        $Name
    )

    # return all if no Name
    if ([string]::IsNullOrEmpty($Name) -or ($Name.Length -eq 0)) {
        return $PodeContext.Server.ScopedVariables.Values
    }

    # return filtered
    return @(foreach ($n in $Name) {
            $PodeContext.Server.ScopedVariables[$n]
        })
}

<#
.SYNOPSIS
Automatically loads Scoped Variable ps1 files
 
.DESCRIPTION
Automatically loads Scoped Variable ps1 files from either a /scoped-vars folder, or a custom folder. Saves space dot-sourcing them all one-by-one.
 
.PARAMETER Path
Optional Path to a folder containing ps1 files, can be relative or literal.
 
.EXAMPLE
Use-PodeScopedVariables
 
.EXAMPLE
Use-PodeScopedVariables -Path './my-vars'
#>

function Use-PodeScopedVariables {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path
    )

    Use-PodeFolder -Path $Path -DefaultPath 'scoped-vars'
}