Skip to content

Commit c32abb8

Browse files
author
James Brundage
committed
Use-PipeScript: Allowing keywords to be run interactively (Fixes #263)
1 parent 98f4b02 commit c32abb8

File tree

1 file changed

+88
-16
lines changed

1 file changed

+88
-16
lines changed

Use-PipeScript.ps1

Lines changed: 88 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,23 @@
1616

1717
dynamicParam {
1818
# If we didn't have a Converter library, create one.
19-
if (-not $PipeScriptConverters) { $script:PipeScriptConverters = @{} }
19+
if (-not $script:PipeScriptConverters) { $script:PipeScriptConverters = @{} }
2020

2121
$myInv = $MyInvocation
2222
# Then, determine what the name of the pattern in the library would be.
2323
$NameRegex = '[=\.\<\>@\$\!\*]+(?<Name>[\p{L}\p{N}\-\.\+]+)[=\.\<\>@\$\!\*]{0,}'
2424

25+
$myInvocationName = ''
26+
2527
$mySafeName =
2628
if ('.', '&' -contains $myInv.InvocationName -and
27-
(
28-
$myInv.Line.Substring($MyInvocation.OffsetInLine) -match
29-
"^\s{0,}$NameRegex"
30-
) -or (
31-
$myInv.Line.Substring($MyInvocation.OffsetInLine) -match
32-
"^\s{0,}\$\{$NameRegex}"
29+
$(
30+
$myInvocationName = $myInv.Line.Substring($MyInvocation.OffsetInLine)
31+
$myInvocationName -match "^\s{0,}$NameRegex"
32+
) -or
33+
$(
34+
$myInvocationName = $myInv.Line.Substring($MyInvocation.OffsetInLine) -match
35+
$myInvocationName -match "^\s{0,}\$\{$NameRegex}"
3336
)
3437
)
3538
{
@@ -38,6 +41,7 @@
3841
elseif ($MyInv.InvocationName)
3942
{
4043
$myInv.InvocationName -replace $NameRegex, '${Name}'
44+
$myInvocationName = $myInv.InvocationName
4145
}
4246
else {
4347
$callstackPeek = @(Get-PSCallStack)[-1]
@@ -50,6 +54,7 @@
5054
}
5155

5256
if ($callerName) {
57+
$myInvocationName = $CallerName
5358
$callerName -replace $NameRegex, '${Name}'
5459
}
5560
}
@@ -58,30 +63,81 @@
5863
$mySafeName = 'PipeScript'
5964
}
6065

61-
# Find the Converter in the library.
62-
$converter = Get-Transpiler | Where-Object DisplayName -eq $mySafeName
63-
if ($converter) {
66+
# Find the Converter in the library
67+
if (-not $script:PipeScriptConverters[$mySafeName]) {
68+
$converter = Get-Transpiler | Where-Object DisplayName -eq $mySafeName
69+
$script:PipeScriptConverters[$mySafeName] = $converter
70+
}
71+
72+
$converter = $script:PipeScriptConverters[$mySafeName]
73+
74+
75+
if ($converter.Metadata.'PipeScript.Keyword') {
76+
$keywordDynamicParameters = [Management.Automation.RuntimeDefinedParameterDictionary]::new()
77+
$keywordDynamicParameters.Add('ArgumentList', [Management.Automation.RuntimeDefinedParameter]::new(
78+
'ArgumentList',
79+
([PSObject[]]),
80+
@(
81+
$paramAttr = [Management.Automation.ParameterAttribute]::new()
82+
$paramAttr.ValueFromRemainingArguments = $true
83+
$paramAttr
84+
)
85+
))
86+
$keywordDynamicParameters.Add('InputObject', [Management.Automation.RuntimeDefinedParameter]::new(
87+
'InputObject',
88+
([PSObject]),
89+
@(
90+
$paramAttr = [Management.Automation.ParameterAttribute]::new()
91+
$paramAttr.ValueFromPipeline = $true
92+
$paramAttr
93+
)
94+
))
95+
$keywordDynamicParameters
96+
} elseif ($converter) {
6497
$converter.GetDynamicParameters()
6598
}
6699

67100
}
68101

69102
begin {
70103
$steppablePipelines =
71-
@(if (-not $mySafeName -and $psBoundParameters['Name']) {
72-
$names = $psBoundParameters['Name']
73-
$null = $psBoundParameters.Remove('Name')
104+
@(if ($mySafeName -or $psBoundParameters['Name']) {
105+
$names = @($mySafeName) + $psBoundParameters['Name']
106+
if ($names) {
107+
$null = $psBoundParameters.Remove('Name')
108+
}
74109
foreach ($cmd in $script:PipeScriptConverters[$names]) {
110+
if ($cmd.Metadata.'PipeScript.Keyword') {
111+
continue
112+
}
75113
$steppablePipeline = {& $cmd @PSBoundParameters }.GetSteppablePipeline($MyInvocation.CommandOrigin)
76114
$null = $steppablePipeline.Begin($PSCmdlet)
77115
$steppablePipeline
78116
}
79117
$psBoundParameters['Name'] = $names
80118
})
119+
120+
$keywordScript =
121+
if (-not $steppablePipelines) {
122+
$myInv = $myinvocation
123+
$callstackPeek = @(Get-PSCallStack)[1]
124+
$CommandAst = if ($callstackPeek.InvocationInfo.MyCommand.ScriptBlock) {
125+
@($callstackPeek.InvocationInfo.MyCommand.ScriptBlock.Ast.FindAll({
126+
param($ast)
127+
$ast.Extent.StartLineNumber -eq $myInv.ScriptLineNumber -and
128+
$ast.Extent.StartColumnNumber -eq $myInv.OffsetInLine -and
129+
$ast -is [Management.Automation.Language.CommandAst]
130+
},$true))
131+
}
132+
$commandAst | & $converter
133+
}
134+
135+
$accumulatedInput = [Collections.Queue]::new()
81136
}
82137

83138
process {
84-
if (-not $mySafeName -and -not $steppablePipelines) {
139+
$myParams = @{} + $psBoundParameters
140+
if (-not $mySafeName) {
85141
Write-Error "Must call Use-Pipescript with one of it's aliases."
86142
return
87143
}
@@ -91,12 +147,28 @@
91147
$sp.Process($in)
92148
}
93149
return
150+
} elseif (-not $steppablePipelines) {
151+
152+
if ($myParams["InputObject"]) {
153+
$accumulatedInput.Enqueue($myParams["InputObject"])
154+
} else {
155+
& $keywordScript
156+
}
94157
}
95-
$paramCopy = [Ordered]@{} + $psBoundParameters
96-
& $Converter @paramCopy
158+
97159
}
98160

99161
end {
162+
if ($accumulatedInput.Count -and $keywordScript) {
163+
# When a script is returned for a given keyword, it will likely be wrapped in a process block
164+
# because that is what allows the transpiled code to run the most effeciently.
165+
# Since we're running this ad-hoc, we need to change the statement slightly,
166+
# so we're left with a script block that has a process block.
167+
if ($keywordScript -match '^[\.\&]\s{0,}\{') {
168+
$keywordScript = [ScriptBlock]::Create(($keywordScript -replace '^[\.\&]\s{0,}\{' -replace '\}$'))
169+
}
170+
$accumulatedInput.ToArray() | & $keywordScript
171+
}
100172
foreach ($sp in $steppablePipelines) {
101173
$sp.End()
102174
}

0 commit comments

Comments
 (0)