Transpilers/Syntax/NamespacedObject.psx.ps1
<# .SYNOPSIS Namespaced functions .DESCRIPTION Allows the declaration of a object or singleton in a namespace. Namespaces are used to logically group functionality and imply standardized behavior. .EXAMPLE Invoke-PipeScript { My Object Precious { $IsARing = $true; $BindsThemAll = $true } My.Precious } #> [Reflection.AssemblyMetaData('Order', -10)] [ValidateScript({ # This only applies to a command AST $cmdAst = $_ -as [Management.Automation.Language.CommandAst] if (-not $cmdAst) { return $false } # It must have at 4-5 elements. if ($cmdAst.CommandElements.Count -lt 4 -or $cmdAst.CommandElements.Count -gt 5) { return $false } # The second element must be a function or filter. if ($cmdAst.CommandElements[1].Value -notin 'object', 'singleton') { return $false } # The third element must be a bareword if ($cmdAst.CommandElements[1].StringConstantType -ne 'Bareword') { return $false } # The last element must be a ScriptBlock or HashtableAst if ( $cmdAst.CommandElements[-1] -isnot [Management.Automation.Language.ScriptBlockExpressionAst] -and $cmdAst.CommandElements[-1] -isnot [Management.Automation.Language.HashtableAst] ) { return $false } # Attempt to resolve the command if (-not $global:AllCommands) { $global:AllCommands = $executionContext.SessionState.InvokeCommand.GetCommands('*','Alias,Function,Cmdlet', $true) } $potentialCmdName = "$($cmdAst.CommandElements[0])" return -not ($global:AllCommands.Name -eq $potentialCmdName) })] param( # The CommandAST that will be transformed. [Parameter(Mandatory,ValueFromPipeline)] [Management.Automation.Language.CommandAst] $CommandAst ) process { # Namespaced functions are really simple: # We use multiple assignment to pick out the parts of the function $namespace, $objectType, $functionName, $objectDefinition = $CommandAst.CommandElements # Then, we determine the last punctuation. $namespaceSeparatorPattern = [Regex]::new('[\p{P}<>]{1,}','RightToLeft') $namespaceSeparator = $namespaceSeparatorPattern.Match($namespace).Value # If there was no punctuation, the namespace separator will be a '.' if (-not $namespaceSeparator) {$namespaceSeparator = '.'} # If the pattern was empty brackets `[]`, make the separator `[`. elseif ($namespaceSeparator -eq '[]') { $namespaceSeparator = '[' } # If the pattern was `<>`, make the separator `<`. elseif ($namespaceSeparator -eq '<>') { $namespaceSeparator = '<' } # Replace any trailing separators from the namespace. $namespace = $namespace -replace "$namespaceSeparatorPattern$" $blockComments = '' $defineInstance = if ($objectDefinition -is [Management.Automation.Language.HashtableAst]) { "[PSCustomObject][Ordered]$($objectDefinition)" } elseif ($objectDefinition -is [Management.Automation.Language.ScriptBlockExpressionAst]) { $findBlockComments = [Regex]::New(" \<\# # The opening tag (?<Block> (?:.|\s)+?(?=\z|\#>) # anything until the closing tag ) \#\> # the closing tag ", 'IgnoreCase,IgnorePatternWhitespace', '00:00:01') $foundBlockComments = $objectDefinition -match $findBlockComments if ($foundBlockComments -and $matches.Block) { $blockComments = $null,"<#",$($matches.Block),"#>",$null -join [Environment]::Newline } "New-Module -ArgumentList @(@(`$input) + @(`$args)) -AsCustomObject $objectDefinition" } # Join the parts back together to get the new function name. $NewFunctionName = $namespace,$namespaceSeparator,$functionName,$( # If the namespace separator ends with `[` or `<`, try to close it if ($namespaceSeparator -match '[\[\<]$') { if ($matches.0 -eq '[') { ']' } elseif ($matches.0 -eq '<') { '>' } } ) -ne '' -join '' $objectDefinition = "{ $($objectDefinition -replace '^\{' -replace '\}$') Export-ModuleMember -Function * -Alias * -Cmdlet * -Variable * }" $objectDefinition = if ($objectType -eq "singleton") { "{$(if ($blockComments) {$blockComments}) $( @('$this = $myInvocation.MyCommand' 'if (-not $this.Instance) {' "`$singletonInstance = $defineInstance" '$singletonInstance.pstypenames.clear()' "`$singletonInstance.pstypenames.add('$($NewFunctionName -replace "'","''")')" "`$singletonInstance.pstypenames.add('$($namespace -replace $namespaceSeparatorPattern -replace "'","''")')" 'Add-Member -InputObject `$this -MemberType NoteProperty -Name Instance -Value $singletonInstance -Force' '}' '$this.Instance' ) -join [Environment]::newLine ) }" } else { "{ $(if ($blockComments) {$blockComments}) `$Instance = $defineInstance `$Instance.pstypenames.clear() `$Instance.pstypenames.add('$($NewFunctionName -replace "'","''")') `$Instance.pstypenames.add('$($namespace -replace $namespaceSeparatorPattern -replace "'","''")') `$Instance }" } # Redefine the function $redefined = [ScriptBlock]::Create(" function $NewFunctionName $objectDefinition ") # Return the transpiled redefinition. $redefined | .>Pipescript } |