Transpilers/Keywords/All.psx.ps1

using namespace System.Management.Automation.Language
<#
.SYNOPSIS
    all keyword
.DESCRIPTION
    The all keyword is a powerful way to accomplish several useful scenarios with a very natural syntax.

    `all` can get all of a set of things that match a criteria and run one or more post-conditions.
.EXAMPLE
    & {
    $glitters = @{glitters=$true}
    all that glitters
    }.Transpile()
.EXAMPLE
    function mallard([switch]$Quack) { $Quack }
    Get-Command mallard | Get-Member | Select-Object -ExpandProperty TypeName -Unique
    . {all functions that quack are ducks}.Transpile()
    Get-Command mallard | Get-Member | Select-Object -ExpandProperty TypeName -Unique
.EXAMPLE
    
    . {
        $numbers = 1..100
        $null = all $numbers where { ($_ % 2) -eq 1 } are odd
        $null = all $numbers where { ($_ % 2) -eq 0 } are even
    }.Transpile()

    @(
        . { all even $numbers }.Transpile()
    ).Length

    @(
        . { all odd $numbers }.Transpile()
    ).Length
    
    
#>

[ValidateScript({
    $validateVar = $_
    if ($validateVar -is [CommandAst]) {
        $cmdAst = $validateVar
        if ($cmdAst.CommandElements[0].Value -eq 'all') {
            return $true
        }
    }
    return $false
})]
param(
# If set, include all functions in the input.
[Alias('Function')]
[switch]
$Functions,

# If set, include all commands in the input.
[Alias('Command')]
[switch]
$Commands,

# If set, include all cmdlets in the input
[Alias('Cmdlet')]
[switch]
$Cmdlets,

# If set, include all aliases in the input
[Alias('Alias')]
[switch]
$Aliases,

# If set, include all applications in the input
[Alias('Application')]
[switch]
$Applications,

# If set, include all variables in the inputObject.
[Parameter()]
[Alias('Variable')]
[switch]
$Variables,

# If set, will include all of the variables, aliases, functions, and scripts in the current directory.
[Parameter()]
[Alias('Thing')]
[switch]
$Things,

# The input to be searched.
[Parameter(ValueFromPipelineByPropertyName,Position=0)]
[Alias('In','Of', 'The','Object')]
$InputObject,

# An optional condition
[Parameter(ValueFromPipelineByPropertyName,Position=1)]
[Alias('That','Condition')]
$Where,

# The action that will be run
[Parameter(ValueFromPipelineByPropertyName,Position=2)]
[Alias('Is','Are','Foreach','Can','Could','Should')]
$For,

# The Command AST
[Parameter(Mandatory,ParameterSetName='CommandAST',ValueFromPipeline)]
[CommandAst]
$CommandAst
)

process {

    # Gather some information about our calling context
    $myParams = [Ordered]@{} + $PSBoundParameters
    # and attempt to parse it as a sentance (only allowing it to match this particular command)
    $mySentence = $commandAst.AsSentence($MyInvocation.MyCommand)
    $myCmd = $MyInvocation.MyCommand
    $myCmdName = $myCmd.Name

    # Determine how many times we've been recursively called, so we can disambiguate variables later.
    $callstack       = Get-PSCallStack
    $callCount       = @($callstack | 
        Where-Object { $_.InvocationInfo.MyCommand.Name -eq $myCmdName}).count - 1

    # Walk thru all mapped parameters in the sentence
    foreach ($paramName in $mySentence.Parameters.Keys) {
        if (-not $myParams[$paramName]) { # If the parameter was not directly supplied
            $myParams[$paramName] = $mySentence.Parameters[$paramName] # grab it from the sentence.
            foreach ($myParam in $myCmd.Parameters.Values) {
                if ($myParam.Aliases -contains $paramName) { # set any variables that share the name of an alias
                    $ExecutionContext.SessionState.PSVariable.Set($myParam.Name, $mySentence.Parameters[$paramName])
                }
            }
            # and set this variable for this value.
            $ExecutionContext.SessionState.PSVariable.Set($paramName, $mySentence.Parameters[$paramName])
        }
    }
    
    # Now all of the remaining code in this transpiler should act as if we called it from the command line.

    # Nowe we need to set up the input set
    $inputSet = @(        
        $commandTypes = [Management.Automation.CommandTypes]0
        foreach ($myParam in $myCmd.Parameters.Values) {
            if ($myParam.ParameterType -eq [switch] -and 
                $ExecutionContext.SessionState.PSVariable.Get($myParam.Name).Value) {
                if ($myParam.Name -replace 'e?s$' -as [Management.Automation.CommandTypes]) {
                    $commandTypes = $commandTypes -bor [Management.Automation.CommandTypes]($myParam.Name -replace 'e?s$')
                }
                elseif ($myParam.Name -eq 'Things') {
                    $commandTypes = $commandTypes -bor [Management.Automation.CommandTypes]'Alias,Function,Filter,Cmdlet'
                }
                elseif ($myParam.Name -eq 'Scripts') {
                    $commandTypes = $commandTypes -bor [Management.Automation.CommandTypes]'ExternalScript'
                }
            }
        }    

        if ($commandTypes) {
            [ScriptBlock]::create("`$executionContext.SessionState.InvokeCommand.GetCommands('*','$commandTypes',`$true)")
        }
        if ($variables -or $Things) {
            {Get-ChildItem -Path variable:}
        }
        if ($InputObject) {
            if ($InputObject -is [Ast]) {
                if ($InputObject -is [ScriptBlockExpressionAst]) {
                    $InputObject.ConvertFromAST()
                } else {
                    $InputObject.Extent.ToString()
                }
            } else {
                $InputObject
            }
        }
    )

    # If the sentence had unbound arguments
    if ($mySentence.Arguments) {
        if (-not $inputSet) { # and we had not yet set input
            $inputSet = 
                foreach ($sentanceArg in $mySentence.Arguments) {
                    # then anything that is not a [string] or [ScriptBlock] will become input
                    if ($sentanceArg -isnot [string] -and $sentanceArg -isnot [ScriptBlock]) {
                        $sentanceArg
                    } else {
                        # and [strings]s and [ScriptBlock]s will become -Where parameters.
                        if (-not $Where) {
                            $Where = $sentanceArg
                        }
                        else {
                            $where = @($Where) + $sentanceArg
                        }
                    }
                }
        }
    }


    # If we still don't have an inputset, default it to 'things'
    if (-not $InputSet) {
        $InputSet =
            if ($mySentence.Arguments) {
                $mySentence.Arguments
            } else {
                {$ExecutionContext.SessionState.InvokeCommand.GetCommands('*', 'Alias,Function,Filter,Cmdlet', $true)}, 
                {Get-ChildItem -Path variable:}
            }
    }

    # Note: there's still a lot of room for this syntax to grow and become even more natural.
    
    # But with most of our arguments in hand, now we're ready to create the script

    #region Generate Script
    $generatedScript = @(

    # Create an input collection with all of our input
'
# Collect all items into an input collection
$inputCollection ='
 + $(
    @(foreach ($setOfInput in $inputSet) {
        if ($setOfInput -is [ScriptBlock]) {
            '$(' + [Environment]::NewLine + 
            $setOfInput + [Environment]::NewLine + ')'
        } else {
            "`$($setOfInput)"
        }
    }) -join (',' + [Environment]::NewLine + ' ')
)

"
# 'unroll' the collection by iterating over it once.
`$filteredCollection = `$inputCollection =
    @(foreach (`$in in `$inputCollection) { `$in })
"


if ($Where) {
@(
    # If -Where was provided, filter the input

"
# Since filtering conditions have been passed, we must filter item-by-item
`$filteredCollection = foreach (`$item in `$inputCollection) {
    # we set `$this, `$psItem, and `$_ for ease-of-use.
    `$this = `$_ = `$psItem = `$item
    "
    
    foreach ($wh in $where) {
        if ($wh -is [ScriptBlockExpressionAst]) {
            $wh = $wh.ConvertFromAST()
        }
        if ($wh -is [ScriptBlock] -or $wh -is [Ast]) {
            "if (-not `$($($wh.Transpile())
)) { continue } "

        }
        elseif ($wh -is [string]) {
            $safeStr = $($wh -replace "'", "''")
            "if (-not ( # Unless it
                (`$null -ne `$item.'$safeStr') -or # has a '$safeStr' property
                (`$null -ne `$item.value.'$safeStr') -or # or it's value has the property '$safeStr'
                (`$null -ne `$item.Parameters.'$safeStr') -or # or it's parameters have the property '$safeStr'
                (`$item.pstypenames -contains '$safeStr') # or it's typenames have the property '$safeStr'
)) {
    continue # keep moving
}"

        }
    }
"
    `$item
}"

)
}


if ($For) {
# If -For was
"
# Walk over each item in the filtered collection
foreach (`$item in `$filteredCollection) {
    # we set `$this, `$psItem, and `$_ for ease-of-use.
    `$this = `$_ = `$psItem = `$item
"

    foreach ($fo in $for) {
        if ($fo -is [ScriptBlockExpressionAst]) {
            $fo = $fo.ConvertFromAST()
        }
        
        if ($fo -is [ScriptBlock] -or $fo -is [Ast]) {
            $fo.Transpile()
        }

        if ($fo -is [string]) {
            $safeStr = $fo -replace "'", "''"
            "
if (`$item.value -and `$item.value.pstypenames.insert) {
    if (`$item.value.pstypenames -notcontains '$safeStr') {
        `$item.value.pstypenames.insert(0, '$safeStr')
    }
}
elseif (`$item.pstypenames.insert -and `$item.pstypenames -notcontains '$safeStr') {
    `$item.pstypenames.insert(0, '$safeStr')
}
            "

        }

    }

"
`$item
}
"
    
} else {
    "`$filteredCollection"        
}
)

    #endregion Generate Script

    # If the command was assigned or piped from, wrap the script in a subexpression
    if ($CommandAst.IsAssigned -or $CommandAst.PipelinePosition -lt $CommandAst.PipelineLength) {
        $generatedScript = "`$($($generatedScript -join [Environment]::NewLine))"
    }
    # If the command was piped to, wrap the script in a command expression.
    if ($CommandAst.IsPiped) {
        $generatedScript = "& { process {
            $generatedScript
} }"
  
    }
    
    # Generate the scriptblock
    $generatedScript = [ScriptBlock]::create(
        $generatedScript -join [Environment]::NewLine
    )
    
    if (-not $generatedScript) { return } 

    # Rename the variables in the generated script, using our callstack count.
    .>RenameVariable -ScriptBlock $generatedScript -VariableRename @{
        'item' = "$('_' * $callcount)item"
        "filteredCollection" = "$('_' * $callcount)filteredCollection"
        "inputCollection"  = "$('_' * $callcount)inputCollection"
    }
}