PipeScript.psm1
:ToIncludeFiles foreach ($file in (Get-ChildItem -Path "$psScriptRoot" -Filter "*-*" -Recurse)) { if ($file.Extension -ne '.ps1') { continue } # Skip if the extension is not .ps1 foreach ($exclusion in '\.[^\.]+\.ps1$') { if (-not $exclusion) { continue } if ($file.Name -match $exclusion) { continue ToIncludeFiles # Skip excluded files } } . $file.FullName } $transpilerNames = @(@(Get-Transpiler).DisplayName) -ne '' $aliasList += @(foreach ($alias in @($transpilerNames)) { Set-Alias ".>$alias" "Use-PipeScript" -PassThru:$True }) $aliasList += @(foreach ($alias in @($transpilerNames)) { Set-Alias ".<$alias>" "Use-PipeScript" -PassThru:$True }) $pipeScriptKeywords = @( foreach ($transpiler in Get-Transpiler) { if ($transpiler.Metadata.'PipeScript.Keyword' -and $transpiler.DisplayName) { $transpiler.DisplayName } } ) $aliasList += @(foreach ($alias in @($pipeScriptKeywords)) { Set-Alias "$alias" "Use-PipeScript" -PassThru:$True }) $MyModule = $MyInvocation.MyCommand.ScriptBlock.Module $aliasList += @( if ($MyModule -isnot [Management.Automation.PSModuleInfo]) { Write-Error "'$MyModule' must be a [Management.Automation.PSModuleInfo]" } elseif ($MyModule.ExportedCommands.Count) { foreach ($cmd in $MyModule.ExportedCommands.Values) { if ($cmd.CommandType -in 'Alias') { $cmd } } } else { foreach ($cmd in $ExecutionContext.SessionState.InvokeCommand.GetCommands('*', 'Function,Cmdlet', $true)) { if ($cmd.Module -ne $MyModule) { continue } if ('Alias' -contains 'Alias' -and $cmd.ScriptBlock.Attributes.AliasNames) { foreach ($aliasName in $cmd.ScriptBlock.Attributes.AliasNames) { $ExecutionContext.SessionState.InvokeCommand.GetCommand($aliasName, 'Alias') } } if ('Alias' -contains $cmd.CommandType) { $cmd } } }) try { $ExecutionContext.SessionState.PSVariable.Set( $MyModule.Name, $MyModule ) } catch { # There is the slimmest of chances we might not be able to set the variable, because it was already constrained by something else. # If this happens, we still want to load the module, and we still want to know, so put it out to Verbose. Write-Verbose "Could not assign module variable: $($_ | Out-String)" } # If New-PSDrive exists if ($ExecutionContext.SessionState.InvokeCommand.GetCommand('New-PSDrive', 'Cmdlet')) { try { # mount the module as a drive New-PSDrive -Name $MyModule.Name -PSProvider FileSystem -Root ($MyModule.Path | Split-Path) -Description $MyModule.Description -Scope Global -ErrorAction Ignore } catch { Write-Verbose "Could not add drive: $($_ | Out-String)" } } $typesFilePath = (Join-Path $psScriptRoot "PipeScript.types.ps1xml") Update-TypeData -AppendPath $typesFilePath $typesXmlNoteProperties = Select-Xml -Path $typesFilePath -XPath '//NoteProperty' $typeAccelerators = [psobject].assembly.gettype("System.Management.Automation.TypeAccelerators") foreach ($typesXmlNoteProperty in $typesXmlNoteProperties){ if ($typesXmlNoteProperty.Node.Name -notmatch '\.class\.ps1$') { continue } $classScriptBlock = try { ([scriptblock]::create($typesXmlNoteProperty.Node.Value)) } catch { Write-Warning "Could not define '$($typesXmlNoteProperty.Node.Name)': $($_ | Out-String)" } if (-not $classScriptBlock) { continue } $typeDefinitionsAst = $classScriptBlock.Ast.FindAll({param($ast) $ast -is [Management.Automation.Language.TypeDefinitionAst]}, $true) if (-not $typeDefinitionsAst) { continue } . $classScriptBlock foreach ($typeDefinitionAst in $typeDefinitionsAst) { $resolvedType = $typeDefinitionAst.Name -as [type] if (-not $resolvedType) { continue } $typeAccelerators::Add("$($MyModule.Name).$($typeDefinitionAst.Name)", $resolvedType) $typeAccelerators::Add("$($typeDefinitionAst.Name)", $resolvedType) } } # A few extension types we want to publish as variables $PipeScript.Extensions | . { begin { # Languages will populate `$psLanguage(s)` $LanguagesByName = [Ordered]@{} # Interpreters will populate `$psInterpreter(s)` $InterpretersByName = [Ordered]@{} # Parsers will populate `$psParsers` $ParsersByName = [Ordered]@{} } process { if ($_.Name -notlike 'Language*') { if ($_.pstypenames -contains 'Parser.Command') { $ParsersByName[$_.Name] = $_ } return } $languageObject = & $_ if (-not $languageObject.LanguageName) { return } $LanguagesByName[$languageObject.LanguageName] = $languageObject if ($languageObject.Interpreter) { $InterpretersByName[$languageObject.LanguageName] = $languageObject } } end { $PSLanguage = $PSLanguages = [PSCustomObject]$LanguagesByName $PSLanguage.pstypenames.clear() $PSLanguage.pstypenames.insert(0,'PipeScript.Languages') $PSInterpreter = $PSInterpreters = [PSCustomObject]$InterpretersByName $PSInterpreter.pstypenames.clear() $PSInterpreter.pstypenames.insert(0,'PipeScript.Interpreters') $PSParser = $PSParsers = [PSCustomObject]$ParsersByName $PSParser.pstypenames.clear() $PSParser.pstypenames.insert(0,'PipeScript.Parsers') } } Export-ModuleMember -Function * -Alias * -Variable $MyInvocation.MyCommand.ScriptBlock.Module.Name, 'PSLanguage', 'PSLanguages', 'PSInterpreter', 'PSInterpreters', 'PSParser','PSParsers' $PreCommandAction = { param($LookupArgs) if (-not $global:NewModule -or -not $global:ImportModule) { $global:ImportModule, $global:NewModule = $global:ExecutionContext.SessionState.InvokeCommand.GetCommands('*-Module', 'Cmdlet', $true) -match '^(?>New|Import)' } if (-not $global:AllFunctionsAndAliases) { $global:AllFunctionsAndAliases = $global:ExecutionContext.SessionState.InvokeCommand.GetCommands('*', 'Alias,Function', $true) } $invocationName = $LookupArgs if ($PSInterpreters) { $interpreterForName = $PSInterpreters.ForFile($invocationName) if ($interpreterForName -and -not ($global:AllFunctionsAndAliases -match $([Regex]::Escape($invocationName)))) { foreach ($maybeInterprets in $interpreterForName) { $adHocModule = & $newModule -ScriptBlock ( [ScriptBlock]::Create( @( "Set-Alias '$($invocationName -replace "'","''")' 'Invoke-Interpreter'" "Export-ModuleMember -Alias *" ) -join ';' ) ) -Name @($invocationName -split '[\\/]')[-1] | & $importModule -Global -PassThru $null = New-Event -SourceIdentifier "PipeScript.Interpreter.Found" -Sender $maybeInterprets -EventArguments $adHocModule, $invocationName -MessageData $adHocModule, $invocationName return $invocationName } } } } $global:ExecutionContext.InvokeCommand.PreCommandLookupAction = $PreCommandAction $CommandNotFoundAction = { param($sender, $eventArgs) # Rather than be the only thing that can handle command not found, we start by broadcasting an event. $null = New-Event -SourceIdentifier "PowerShell.CommandNotFound" -MessageData $eventArgs -Sender $sender -EventArguments $eventArgs # Then we determine our own script block. $myScriptBlock = $MyInvocation.MyCommand.ScriptBlock # Then, we do a bit of callstack peeking $callstack = @(Get-PSCallStack) $myCallCount = 0 foreach ($call in $callstack) { if ($call.InvocationInfo.MyCommand.ScriptBlock -eq $myScriptBlock) { $myCallCount++ } } # If we're being called more than once if ($myCallCount -gt 1) { return # we're done. } $callstackPeek = $callstack[-1] # When peeking in on a dynamic script block, the offsets may lie. $column = [Math]::Max($callstackPeek.InvocationInfo.OffsetInLine, 1) $line = [Math]::Max($callstackPeek.InvocationInfo.ScriptLineNumber, 1) $callingScriptBlock = $callstackPeek.InvocationInfo.MyCommand.ScriptBlock # Now find all of the AST elements at this location. $astFound = @($callingScriptBlock.Ast.FindAll({ param($ast) $ast.Extent.StartLineNumber -eq $line -and $ast.Extent.StartColumnNumber -eq $column }, $true)) if (-not $script:LastCommandNotFoundScript) { $script:LastCommandNotFoundScript = $callingScriptBlock } elseif ($script:LastCommandNotFoundScript -eq $callingScriptBlock) { return } else { $script:LastCommandNotFoundScript = $callingScriptBlock } if (-not $callingScriptBlock) { return } $transpiledScriptBlock = try { $callingScriptBlock.Transpile() } catch { Write-Error $_ return } if ($transpiledScriptBlock -and ($transpiledScriptBlock.ToString().Length -ne $callingScriptBlock.ToString().Length)) { $endStatements = $transpiledScriptBlock.Ast.EndBlock.Statements $FirstExpression = if ($endStatements -and ( $endStatements[0] -is [Management.Automation.Language.PipelineAst] ) -and ( $endStatements[0].PipelineElements[0] -is [Management.Automation.Language.CommandExpressionAst] ) ) { $endStatements[0].PipelineElements[0].Expression } else { $null } if ($astFound -and $astFound[-1].Parent -is [Management.Automation.Language.AssignmentStatementAst] -and ( $FirstExpression -is [Management.Automation.Language.BinaryExpressionAst] -or $FirstExpression -is [Management.Automation.Language.ParenExpressionAst] ) ) { Write-Error " Will not interactively transpile {$callingScriptBlock} ( because it would overwrite $($astFound[-1].Parent.Left.Extent) )" return } if ($astFound -and $astFound[-1].Parent -is [Management.Automation.Language.AssignmentStatementAst] -and $endStatements -and $endStatements[0] -is [Management.Automation.Language.AssignmentStatementAst] -and $astFound[-1].Parent.Left.ToString() -eq $endStatements[0].Left.ToString()) { $eventArgs.CommandScriptBlock = [ScriptBlock]::Create($endStatements[0].Right.ToString()) $eventArgs.StopSearch = $true } else { $eventArgs.CommandScriptBlock = $transpiledScriptBlock $eventArgs.StopSearch = $true } } return } $global:ExecutionContext.SessionState.InvokeCommand.CommandNotFoundAction = $CommandNotFoundAction $MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { $global:ExecutionContext.SessionState.InvokeCommand.CommandNotFoundAction = $null $global:ExecutionContext.SessionState.InvokeCommand.PreCommandLookupAction = $null } |