Commands/PipeScript/Get-PipeScript.ps.ps1

function Get-PipeScript
{
    <#
    .SYNOPSIS
        Gets PipeScript.
    .DESCRIPTION
        Gets PipeScript and it's extended commands.

        Because 'Get' is the default verb in PowerShell,
        Get-PipeScript also allows you to run other commands in noun-oriented syntax.
    .EXAMPLE
        # Get every specialized PipeScript command
        Get-PipeScript
    .EXAMPLE
        # Get all transpilers
        Get-PipeScript -PipeScriptType Transpiler
    .EXAMPLE
        # Get all template files within the current directory.
        Get-PipeScript -PipeScriptType Template -PipeScriptPath $pwd
    .EXAMPLE
        # You can use `noun verb` to call any core PipeScript command.
        PipeScript Invoke { "hello world" } # Should -Be 'Hello World'
    .EXAMPLE
        # You can still use the object pipeline with `noun verb`
        { partial function f { } } |
            PipeScript Import -PassThru # Should -BeOfType ([Management.Automation.PSModuleInfo])
    #>

    [CmdletBinding(PositionalBinding=$false)]
    param(
    # The path containing pipescript files.
    # If this parameter is provided, only PipeScripts in this path will be outputted.
    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias('Fullname','FilePath','Source')]
    [string]
    $PipeScriptPath,

    # One or more PipeScript Command Types.
    [Parameter(ValueFromPipelineByPropertyName)]
    [ValidValues(Values={
        @((Aspect.ModuleExtensionType -Module PipeScript).psobject.properties).Name | Sort-Object        
    })]
    [string[]]
    $PipeScriptType,

    # Any positional arguments that are not directly bound.
    # This parameter primarily exists to allow Get-PipeScript to pass it down to other commands.
    [Parameter(ValueFromRemainingArguments)]
    [Alias('Args')]
    $Argument,

    # The InputObject.
    # This parameter primarily exists to allow Get-PipeScript to pass it down to other commands.
    [Parameter(ValueFromPipeline)]
    [Alias('Input','In')]    
    $InputObject,

    # If set, will force a refresh of the loaded Pipescripts.
    [switch]
    $Force
    )

    dynamicParam {

             

        $myModule = Get-Module PipeScript
        $myInv    = $MyInvocation
        
        # Fun PowerShell fact: 'Get' is the default verb.
        # So when someone uses a noun-centric form of PipeScript, this command will be called.
        if ($MyInvocation.InvocationName -eq 'PipeScript') {
            # In this way, we can 'trick' the command a bit.
            $myCmdAst  = $myCommandAst
            
            if (-not ($myCmdAst -or $MyInvocation.Line -match '(?<w1>PipeScript)\s(?<w2>\S+)')) { return }
                
            $FirstWord, $secondWord, $restOfLine = 
                if ($myCmdAst.CommandElements) {
                    $myCmdAst.CommandElements
                } elseif ($matches) {
                    $matches.w1, $matches.w2
                }

            # If the second word is a verb and the first is a noun
            if ($myModule.ExportedCommands["$SecondWord-$FirstWord"] -and # and we export the command
                $myModule.ExportedCommands["$SecondWord-$FirstWord"] -ne $myInv.MyCommand # (and it's not this command)
            ) {
                # Then we could do something, like:
                $myModule.ExportedCommands["$SecondWord-$FirstWord"] |
                    Aspect.DynamicParameter -PositionOffset 1 -ExcludeParameter @($myInv.MyCommand.Parameters.Keys) -BlankParameterName Verb                                
            }                                    
        }
    }

    begin {
        #region Declare Internal Functions and Filters
        function SyncPipeScripts {
            param($Path,$Force)

            # If we do not have a commands at path collection, create it.
            if (-not $script:CachedCommandsAtPath) {
                $script:CachedCommandsAtPath = @{}
            }

            
            if ($Force) { # If we are using -Force,
                if ($path) { # Then check if a -Path was provided,
                    # and clear that path's cache.
                    $script:CachedCommandsAtPath[$path] = @()
                } else {
                    # If no -Path was provided,
                    $script:CachedPipeScripts = $null # clear the command cache.
                }                
            }
            
            # If we have not cached all pipescripts.
            if (-not $script:CachedPipeScripts -and -not $Path) {                
                $script:CachedPipeScripts = @(
                    # Find the extended commands for PipeScript
                    Aspect.ModuleExtendedCommand -Module $myModule -PSTypeName PipeScript

                    # Determine the related modules for PipeScript.
                    $moduleRelationships = [ModuleRelationships()]$myModule
                    $relatedPaths = @(foreach ($relationship in $moduleRelationships) {
                        $relationship.RelatedModule.Path | Split-Path
                    })
                    
                    # then find all commands within those paths.
                    Aspect.ModuleExtendedCommand -Module PipeScript -FilePath $relatedPaths -PSTypeName PipeScript
                )
            }

            if ($path -and -not $script:CachedCommandsAtPath[$path]) {
                $script:CachedCommandsAtPath[$path] = @(
                    Aspect.ModuleExtendedCommand -Module PipeScript -FilePath $path -PSTypeName PipeScript
                )
            }
        }

        filter CheckPipeScriptType
        {
            if ($PipeScriptType) {
                $OneOfTheseTypes = "(?>$($PipeScriptType -join '|'))"
                $in = $_
                if (-not ($in.pstypenames -match $OneOfTheseTypes)) {
                    return
                }
            }
            $_
        }

        filter unroll { $_ }   
        #endregion Declare Internal Functions and Filters
        
        $steppablePipeline = $null
        if ($MyInvocation.InvocationName -eq 'PipeScript') {
            $mySplat = [Ordered]@{} + $PSBoundParameters
            $myCmdAst  = $myCommandAst
            if ($myCmdAst -or $MyInvocation.Line -match '(?<w1>PipeScript)\s(?<w2>\S+)') {
                
                $FirstWord, $secondWord, $restOfLine = 
                    if ($myCmdAst.CommandElements) {
                        $myCmdAst.CommandElements
                    } elseif ($matches) {
                        $matches.w1, $matches.w2
                    }
                
                # If the second word is a verb and the first is a noun
                if ($myModule.ExportedCommands["$SecondWord-$FirstWord"] -and # and we export the command
                    $myModule.ExportedCommands["$SecondWord-$FirstWord"] -ne $myInv.MyCommand # (and it's not this command)
                ) {
                    # Remove the -Verb parameter,
                    $mySplat.Remove('Verb')
                    # get the export,
                    $myExport = $myModule.ExportedCommands["$SecondWord-$FirstWord"]
                    # turn positional arguments into an array,
                    $myArgs = @(
                        if ($mySplat.Argument) {
                            $mySplat.Argument
                            $mySplat.Remove('Argument')
                        }
                    )
                    
                    # create a steppable pipeline command,
                    $steppablePipelineCmd = {& $myExport @mySplat @myArgs}
                    # get a steppable pipeline,
                    $steppablePipeline = $steppablePipelineCmd.GetSteppablePipeline($MyInvocation.CommandOrigin)
                    # and start the steppable pipeline.
                    $steppablePipeline.Begin($PSCmdlet)
                }                
            }            
        }
        
        # If there was no steppable pipeline
        if (-not $steppablePipeline -and 
            $Argument -and # and we had arguments
            $(
                $argPattern = "(?>$($argument -join '|'))" -as [Regex]
                $validArgs = $myInv.MyCommand.Parameters['PipeScriptType'].Attributes.ValidValues -match $argPattern # that could be types
                $validArgs
            )
        ) {
            # imply the -PipeScriptType parameter positionally.
            $PipeScriptType = $validArgs
        }
    }
    process {
        $myInv = $MyInvocation
        if ($steppablePipeline) {
            $steppablePipeline.Process($_)
            return
        }

        # If the invocation name is PipeScript (but we have no steppable pipeline or spaced in the name)
        if ($myInv.InvocationName -eq 'PipeScript' -and $myInv.Line -notmatch 'PipeScript\s[\S]+') {
            # return the module
            return $myModule
        }
        
        if ($inputObject -and $InputObject -is [Management.Automation.CommandInfo]) {
            Aspect.ModuleExtensionCommand -Module PipeScript -Commands $inputObject
        }
        elseif ($InputObject -and $InputObject -is [IO.FileInfo]) {
            $inputObjectCommand = $ExecutionContext.SessionState.InvokeCommand.GetCommand($InputObject.FullName, 'ExternalScript,Application')
            Aspect.ModuleExtensionCommand -Module PipeScript -Commands $inputObjectCommand
        }
        elseif ($PipeScriptPath) {
            SyncPipeScripts -Force:$Force -Path $PipeScriptPath
            $script:CachedCommandsAtPath[$PipeScriptPath] | unroll | CheckPipeScriptType
        } else {            
            SyncPipeScripts -Force:$Force

            $script:CachedPipeScripts | unroll | CheckPipeScriptType
        }
    }

    end {
        if ($steppablePipeline) {
            $steppablePipeline.End()
        }
    }
}