Shortcut.psm1

#region Eponym

# Functions and scripts are interchangeable in PowerShell
# So we can make a small module using an eponym file.
# First we need to identify the module name
$moduleName = $MyInvocation.MyCommand.Name -replace '\.psm1$'

# Once we have done this, we can look for an eponymous script:
$eponym = 
    $ExecutionContext.SessionState.InvokeCommand.GetCommand((
        Join-Path $PSScriptRoot "$moduleName.ps1"
    ), 'ExternalScript')

# If we did not find one,
if (-not $eponym) {
    # warn and return.
    Write-Warning "Missing ./$moduleName.ps1"
    return
}

# We want to define two functions from this script

# One is the name of the script
# The other is the "verb" form of the script.

# Collect our list of verbs
$verbs = Get-Verb | 
    Sort-Object { $_.Verb.Length }, {$_.Verb } -Descending |
    Select-Object -ExpandProperty Verb
    
# and craft a regex to see if we start with the verb.
$startsWithVerb = "^(?>$(
    $verbs -join '|'
))"


# Our Exports are:
$exports = 
    $moduleName, # * The Eponym
    $(
        # The `Verb-Noun` form
        if ($moduleName -match $startsWithVerb) {
            "$($matches.0)-$($moduleName -replace "$startsWithVerb\p{P}?")"
        } else {
            "Get-$($ModuleName -replace '\p{P}')"
        }
    )

# We can use the function provider to create functions in this scope.
foreach ($functionName in $exports) {
    # This allows us to dynamically set each export to by the eponym
    $ExecutionContext.SessionState.PSVariable.Set(
        "function:$functionName",
        $eponym.ScriptBlock
    )
}

# We also want to export any aliases
# and add support for argument completers.
$argumentCompleter = $null
$aliasExports = @(
    # walk over all of our attributes
    foreach ($attribute in $eponym.ScriptBlock.Attributes) {
        # and keep track of any argument completers we find.
        if ($attribute -is [ArgumentCompleter]) {
            $argumentCompleter = $attribute
        }
        # Then make our aliases
        foreach ($alias in $attribute.aliasNames) {
            # (unless the alias is already exported as a function)
            if ($alias -in $exports) { continue }
            $ExecutionContext.SessionState.PSVariable.Set(
                "alias:$alias", $moduleName
            )
            $alias
        }
    }
)

# If we had an argument completer
if ($argumentCompleter.ScriptBlock) {
    # now is the time to register it.

    # Argument completers need to be registered for each function
    foreach ($functionExport in $exports) {
        Register-ArgumentCompleter -CommandName $functionExport -ScriptBlock $argumentCompleter.ScriptBlock
    }

    # and alias
    foreach ($aliasExport in $aliasExports) {
        Register-ArgumentCompleter -CommandName $aliasExport -ScriptBlock $argumentCompleter.ScriptBlock
    }
}

# We will also be exporting our eponym as a variable
$ExecutionContext.SessionState.PSVariable.Set($moduleName, $eponym)

# All that's left to do is explicitly export just these functions.
Export-ModuleMember -Function $exports -Alias $aliasExports -Variable $moduleName
#endregion Eponym