Commands/PostProcessing/PostProcess-PartialFunction.ps1


function PipeScript.PostProcess.PartialFunction {

    <#
    .SYNOPSIS
        Expands partial functions
    .DESCRIPTION
        A partial function is a function that will be joined with a function with a matching name.
    .LINK
        Join-PipeScript
    .EXAMPLE
        Import-PipeScript {
            partial function testPartialFunction {
                "This will be added to a command name TestPartialFunction"
            }
            
            function testPartialFunction {}
        }

        testPartialFunction # Should -BeLike '*TestPartialFunction*'
    #>

    param(
    # The function definition.
    [Parameter(Mandatory,ParameterSetName='FunctionDefinition',ValueFromPipeline)]
    [Management.Automation.Language.FunctionDefinitionAst]
    $FunctionDefinitionAst
    )
    

    process {
        $realFunctionName = $FunctionDefinitionAst.Name
        $partialCommands = @(
            if ($realFunctionName -notmatch 'partial\p{P}') {
                if (-not $script:PartialCommands) {
                    $script:PartialCommands =
                    $ExecutionContext.SessionState.InvokeCommand.GetCommands('Partial*',
                        'Function,Alias',$true)
                }
        
                $partialCommands = @(foreach ($partialFunction in $script:PartialCommands) {
                    # Only real partials should be considered.

                    if ($partialFunction -notmatch  'partial\p{P}') { continue }
                    # Partials should not combine with other partials.
                    
                    $partialName = $partialFunction.Name -replace '^Partial\p{P}'
                    if (
                        (
                            # If there's a slash in the name, treat it as a regex
                            $partialName -match '/' -and
                            $realFunctionName -match ($partialName -replace '/')
                        ) -or (
                            # If there's a slash * or ?, treat it as a wildcard
                            $partialName -match '[\*\?]' -and
                            $realFunctionName -like $partialName
                        ) -or (
                            # otherwise, treat it as an exact match.
                            $realFunctionName -eq $partialName
                        )
                    ) {
                        $partialFunction
                    }
                })

                # If there were any partial commands
                if ($partialCommands) {
                    # sort them by rank and name.
                    $partialCommands | Sort-Object Rank, Name
                }
            }
        )
        
        if ((-not $partialCommands)) { return }

        
        $originalDefinition = [ScriptBlock]::Create(($functionDefinitionAst.Body.Extent -replace '^{' -replace '}$'))
        # If there were any partial commands,

        # join them all together first, and skip the help block.
        $partialsToJoin = @(
            $alreadyIncluded = [Ordered]@{} # Keep track of what we've included.
            foreach ($partialCommand in $partialCommands) { # and go over each partial command
                if ($alreadyIncluded["$partialCommand"]) { continue }
                # and get it's ScriptBlock
                if ($partialCommand.ScriptBlock) {
                    $partialCommand.ScriptBlock
                } elseif ($partialCommand.ResolvedCommand) {
                    # (if it's an Alias, keep resolving until we can't resolve anymore).
                    $resolvedAlias = $partialCommand.ResolvedCommand
                    while ($resolvedAlias -is [Management.Automation.AliasInfo]) {
                        $resolvedAlias = $resolvedAlias.ResolvedCommand
                    }
                    if ($resolvedAlias.ScriptBlock) {
                        $resolvedAlias.ScriptBlock
                    }                        
                }
                # Then mark the command as included, just in case.
                $alreadyIncluded["$partialCommand"] = $true
            }
        )

        $joinedPartials = $partialsToJoin | Join-PipeScript -ExcludeBlockType help

        $joinedScriptBlock = @(                
            $originalDefinition # we join them with the transpiled code.
            $joinedPartials
        ) | # Take all of the combined input and pipe in into Join-PipeScript
            Join-PipeScript -Transpile


        $inlineParameters =
            if ($FunctionDefinition.Parameters) {
                "($($FunctionDefinition.Parameters -join ','))"
            } else {
                ''
            }

        $joinedFunction = @(if ($FunctionDefinition.IsFilter) {
            "filter", $realFunctionName, $inlineParameters, '{' -ne '' -join ' '
        } else {
            "function", $realFunctionName, $inlineParameters, '{' -ne '' -join ' '
        }
        $joinedScriptBlock
        "}") -join [Environment]::NewLine
        $joinedFunction = [scriptblock]::Create($joinedFunction)
        $joinedFunction.Ast.EndBlock.Statements[0]        
    }

}