Skip to content

Commit 27f93fd

Browse files
Merge pull request #237 from StartAutomating/PipeScriptInheritance
PipeScript inheritance
2 parents 0001b44 + fa9a36f commit 27f93fd

File tree

16 files changed

+1406
-152
lines changed

16 files changed

+1406
-152
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.1.5:
2+
* Support for [inherit]ing a command (Fixes #235) (finally/wow)
3+
* Join-PipeScript: Overhauling (Fixes #231 Fixes #232 Fixes #233 Fixes #236)
4+
* [Management.Automation.Language] type extensions: Adding .Script property and .ToString() scriptmethod (Fixes #234)
5+
---
6+
17
## 0.1.4:
28
* ValidateScriptBlock improvements
39
* Adding -NoLoop/-NoWhileLoop (Fixes #227)

Join-PipeScript.ps1

Lines changed: 279 additions & 135 deletions
Large diffs are not rendered by default.

ListOfTranspilers.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ These are all of the transpilers currently included in PipeScript:
1818
|[Help](Transpilers/Help.psx.ps1) |Help Transpiler |
1919
|[Http.Protocol](Transpilers/Protocols/Http.Protocol.psx.ps1) |http protocol |
2020
|[Include](Transpilers/Include.psx.ps1) |Includes Files |
21+
|[Inherit](Transpilers/Inherit.psx.ps1) |Inherits a Command |
2122
|[Inline.ADA](Transpilers/Inline/Inline.ADA.psx.ps1) |ADA PipeScript Transpiler. |
2223
|[Inline.ATOM](Transpilers/Inline/Inline.ATOM.psx.ps1) |ATOM Inline PipeScript Transpiler. |
2324
|[Inline.Bash](Transpilers/Inline/Inline.Bash.psx.ps1) |Bash PipeScript Transpiler. |

PipeScript.psd1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919
BuildModule = @('EZOut','Piecemeal','PipeScript','HelpOut', 'PSDevOps')
2020
Tags = 'PipeScript','PowerShell', 'Transpilation', 'Compiler'
2121
ReleaseNotes = @'
22+
## 0.1.5:
23+
* Support for [inherit]ing a command (Fixes #235) (finally/wow)
24+
* Join-PipeScript: Overhauling (Fixes #231 Fixes #232 Fixes #233 Fixes #236)
25+
* [Management.Automation.Language] type extensions: Adding .Script property and .ToString() scriptmethod (Fixes #234)
26+
---
27+
2228
## 0.1.4:
2329
* ValidateScriptBlock improvements
2430
* Adding -NoLoop/-NoWhileLoop (Fixes #227)

PipeScript.types.ps1xml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,53 @@ elseif ($TranspilerWarnings) {
522522
</ScriptMethod>
523523
</Members>
524524
</Type>
525+
<Type>
526+
<Name>System.Management.Automation.Language.ScriptRequirements</Name>
527+
<Members>
528+
<ScriptMethod>
529+
<Name>ToString</Name>
530+
<Script>
531+
$this.Script.ToString()
532+
533+
</Script>
534+
</ScriptMethod>
535+
<ScriptProperty>
536+
<Name>Script</Name>
537+
<GetScriptBlock>
538+
$requirement = $this
539+
[ScriptBlock]::create(
540+
@(if ($requirement.RequirementPSVersion) {
541+
"#requires -Version $($requirement.RequirementPSVersion)"
542+
}
543+
if ($requirement.IsElevationRequired) {
544+
"#requires -RunAsAdministrator"
545+
}
546+
if ($requirement.RequiredModules) {
547+
"#requires -Module $(@(foreach ($reqModule in $requirement.RequiredModules) {
548+
if ($reqModule.Version -or $req.RequiredVersion -or $req.MaximumVersion) {
549+
'@{' + $(@(foreach ($prop in $reqModule.PSObject.Properties) {
550+
if (-not $prop.Value) { continue }
551+
if ($prop.Name -in 'Name', 'Version') {
552+
"Module$($prop.Name)='$($prop.Value.ToString().Replace("'","''"))'"
553+
} elseif ($prop.Name -eq 'RequiredVersion') {
554+
"MinimumVersion='$($prop.Value)'"
555+
} else {
556+
"$($prop.Name)='$($prop.Value)'"
557+
}
558+
}) -join ';') + '}'
559+
} else {
560+
$reqModule.Name
561+
}
562+
}) -join ',')"
563+
}
564+
if ($requirement.RequiredAssemblies) {
565+
"#requires -Assembly $($requirement.RequiredAssemblies -join ',')"
566+
}) -join [Environment]::NewLine
567+
)
568+
</GetScriptBlock>
569+
</ScriptProperty>
570+
</Members>
571+
</Type>
525572
<Type>
526573
<Name>System.Management.Automation.Language.TypeConstraintAst</Name>
527574
<Members>

Transpilers/Inherit.psx.ps1

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
<#
2+
.SYNOPSIS
3+
Inherits a Command
4+
.DESCRIPTION
5+
Inherits a given command.
6+
7+
This acts similarily to inheritance in Object Oriented programming.
8+
9+
By default, inheriting a function will join the contents of that function with the -ScriptBlock.
10+
11+
Your ScriptBlock will come first, so you can override any of the behavior of the underlying command.
12+
13+
You can "abstractly" inherit a command, that is, inherit only the command's parameters.
14+
15+
Inheritance can also be -Abstract.
16+
17+
When you use Abstract inheritance, you get only the function definition and header from the inherited command.
18+
19+
You can also -Override the command you are inheriting.
20+
21+
This will add an [Alias()] attribute containing the original command name.
22+
23+
One interesting example is overriding an application
24+
25+
26+
.EXAMPLE
27+
Invoke-PipeScript {
28+
[inherit("Get-Command")]
29+
param()
30+
}
31+
.EXAMPLE
32+
{
33+
[inherit("gh",Overload)]
34+
param()
35+
begin { "ABOUT TO CALL GH"}
36+
end { "JUST CALLED GH" }
37+
}.Transpile()
38+
.EXAMPLE
39+
# Inherit Get-Transpiler abstractly and make it output the parameters passed in.
40+
{
41+
[inherit("Get-Transpiler", Abstract)]
42+
param() process { $psBoundParameters }
43+
}.Transpile()
44+
.EXAMPLE
45+
{
46+
[inherit("Get-Transpiler", Dynamic, Abstract)]
47+
param()
48+
} | .>PipeScript
49+
#>
50+
param(
51+
[Parameter(Mandatory,Position=0)]
52+
[string]
53+
$Command,
54+
55+
# If provided, will abstractly inherit a function.
56+
# This include the function's parameters, but not it's content
57+
# It will also define a variable within a dynamicParam {} block that contains the base command.
58+
[switch]
59+
$Abstract,
60+
61+
# If provided, will set an alias on the function to replace the original command.
62+
[Alias('Overload')]
63+
[switch]
64+
$Override,
65+
66+
# If set, will dynamic overload commands.
67+
# This will use dynamic parameters instead of static parameters, and will use a proxy command to invoke the inherited command.
68+
[switch]
69+
$Dynamic,
70+
71+
# If set, will always inherit commands as proxy commands.
72+
# This is implied by -Dynamic.
73+
[switch]
74+
$Proxy,
75+
76+
# The Command Type. This can allow you to specify the type of command you are overloading.
77+
# If the -CommandType includes aliases, and another command is also found, that command will be used.
78+
# (this ensures you can continue to overload commands)
79+
[Alias('CommandTypes')]
80+
[string[]]
81+
$CommandType = 'All',
82+
83+
# A list of block types to be excluded during a merge of script blocks.
84+
# By default, no blocks will be excluded.
85+
[ValidateSet('using', 'requires', 'help','header','param','dynamicParam','begin','process','end')]
86+
[Alias('SkipBlockType','SkipBlockTypes','ExcludeBlockTypes')]
87+
[string[]]
88+
$ExcludeBlockType,
89+
90+
# A list of block types to include during a merge of script blocks.
91+
[ValidateSet('using', 'requires', 'help','header','param','dynamicParam','begin','process','end')]
92+
[Alias('BlockType','BlockTypes','IncludeBlockTypes')]
93+
[string[]]
94+
$IncludeBlockType = @('using', 'requires', 'help','header','param','dynamicParam','begin','process','end'),
95+
96+
# A list of parameters to include. Can contain wildcards.
97+
# If -IncludeParameter is provided without -ExcludeParameter, all other parameters will be excluded.
98+
[string[]]
99+
$IncludeParameter,
100+
101+
# A list of parameters to exclude. Can contain wildcards.
102+
# Excluded parameters with default values will declare the default value at the beginnning of the command.
103+
[string[]]
104+
$ExcludeParameter,
105+
106+
[Parameter(ValueFromPipeline,ParameterSetName='ScriptBlock')]
107+
[scriptblock]
108+
$ScriptBlock = {}
109+
)
110+
111+
process {
112+
# To start off with, let's resolve the command we're inheriting.
113+
$commandTypes = [Management.Automation.CommandTypes]$($CommandType -join ',')
114+
$resolvedCommand = $ExecutionContext.SessionState.InvokeCommand.GetCommand($Command, $commandTypes)
115+
# If it is an alias
116+
if ($resolvedCommand -is [Management.Automation.AliasInfo]) {
117+
# check for other commands by this name
118+
$otherCommandExists = $ExecutionContext.SessionState.InvokeCommand.GetCommand($Command,
119+
$commandTypes -bxor ([Management.Automation.CommandTypes]'Alias'))
120+
if ($otherCommandExists) {
121+
# and use those instead (otherwise, a command can only be overloaded once).
122+
Write-Verbose "Using $otherCommandExists instead of alias"
123+
$resolvedCommand = $otherCommandExists
124+
}
125+
}
126+
127+
# If we could not resolve the command
128+
if (-not $resolvedCommand) {
129+
Write-Error "Could not resolve -Command '$Command'" # error out.
130+
return
131+
}
132+
133+
# Prepare parameters for Join-ScriptBlock
134+
$joinSplat = @{}
135+
foreach ($key in 'IncludeBlockType', 'ExcludeBlockType','IncludeParameter', 'ExcludeParameter') {
136+
if ($PSBoundParameters[$key]) {
137+
$joinSplat[$key] = $PSBoundParameters[$key]
138+
}
139+
}
140+
141+
# and determine the name of the command as a variable
142+
$commandVariable = $Command -replace '\W'
143+
144+
# If -Dynamic was passed
145+
if ($Dynamic) {
146+
$Proxy = $true # it implies -Proxy.
147+
}
148+
149+
# Now we get the script block that we're going to inherit.
150+
$resolvedScriptBlock =
151+
# If we're inheriting an application
152+
if ($resolvedCommand -is [Management.Automation.ApplicationInfo]) {
153+
# make a light wrapper for it.
154+
[scriptblock]::Create(@"
155+
param(
156+
[Parameter(ValueFromRemainingArguments)]
157+
[string[]]
158+
`$ArgumentList
159+
)
160+
161+
process {
162+
& `$baseCommand @ArgumentList
163+
}
164+
"@)
165+
} elseif ($resolvedCommand -is [Management.Automation.CmdletInfo] -or $Proxy) {
166+
# If we're inheriting a Cmdlet or -Proxy was passed, inherit from a proxy command.
167+
.>ProxyCommand -CommandName $Command
168+
}
169+
elseif (
170+
$resolvedCommand -is [Management.Automation.FunctionInfo] -or
171+
$resolvedCommand -is [management.Automation.ExternalScriptInfo]
172+
) {
173+
# Otherwise, inherit the command's contents.
174+
$resolvedCommand.ScriptBlock
175+
}
176+
177+
# Now we're passing a series of script blocks to Join-PipeScript
178+
179+
$(
180+
# If we do not have a resolved command,
181+
if (-not $resolvedCommand) {
182+
{} # the first script block is empty.
183+
} else {
184+
# If we have a resolvedCommand, fully qualify it.
185+
$fullyQualifiedCommand =
186+
if ($resolvedCommand.Module) {
187+
"$($resolvedCommand.Module)\$command"
188+
} else {
189+
"$command"
190+
}
191+
192+
# Then create a dynamicParam block that will set `$baseCommand,
193+
# as well as a `$script: scoped variable for the command name.
194+
195+
[scriptblock]::create(@"
196+
dynamicParam {
197+
`$baseCommand =
198+
if (-not `$script:$commandVariable) {
199+
`$script:$commandVariable =
200+
`$executionContext.SessionState.InvokeCommand.GetCommand('$($command -replace "'", "''")','$($resolvedCommand.CommandType)')
201+
`$script:$commandVariable
202+
} else {
203+
`$script:$commandVariable
204+
}
205+
$(
206+
# Embed -IncludeParameter
207+
if ($IncludeParameter) {
208+
"`$IncludeParameter = '$($IncludeParameter -join "','")'"
209+
} else {
210+
"`$IncludeParameter = @()"
211+
})
212+
$(
213+
# Embed -ExcludeParameter
214+
if ($ExcludeParameter) {
215+
"`$ExcludeParameter = '$($ExcludeParameter -join "','")'"
216+
}
217+
else {
218+
"`$ExcludeParameter = @()"
219+
})
220+
}
221+
"@)
222+
}
223+
),
224+
# Next is our [ScriptBlock]. This will come before almost everything else.
225+
$scriptBlock,
226+
$(
227+
# If we are -Overriding, create an [Alias]
228+
if ($Override) {
229+
[ScriptBlock]::create("[Alias('$($command)')]param()")
230+
} else {
231+
{}
232+
}
233+
),$(
234+
# Now we include the resolved script
235+
if ($Abstract -or $Dynamic) {
236+
# If we're using -Abstract or -Dynamic, we will strip out a few blocks first.
237+
$excludeFromInherited = 'begin','process', 'end'
238+
if ($Dynamic) {
239+
if (-not $Abstract) {
240+
$excludeFromInherited = 'param'
241+
} else {
242+
$excludeFromInherited += 'param'
243+
}
244+
}
245+
$resolvedScriptBlock | Join-PipeScript -ExcludeBlockType $excludeFromInherited
246+
} else {
247+
# otherwise, we embed the script as-is.
248+
$resolvedScriptBlock
249+
}
250+
), $(
251+
if ($Dynamic) {
252+
# If -Dynamic was passed, generate dynamic parameters.
253+
{
254+
255+
dynamicParam {
256+
$DynamicParameters = [Management.Automation.RuntimeDefinedParameterDictionary]::new()
257+
:nextInputParameter foreach ($paramName in ([Management.Automation.CommandMetaData]$baseCommand).Parameters.Keys) {
258+
if ($ExcludeParameter) {
259+
foreach ($exclude in $ExcludeParameter) {
260+
if ($paramName -like $exclude) { continue nextInputParameter}
261+
}
262+
}
263+
if ($IncludeParameter) {
264+
$shouldInclude =
265+
foreach ($include in $IncludeParameter) {
266+
if ($paramName -like $include) { $true;break}
267+
}
268+
if (-not $shouldInclude) { continue nextInputParameter }
269+
}
270+
271+
$DynamicParameters.Add($paramName, [Management.Automation.RuntimeDefinedParameter]::new(
272+
$baseCommand.Parameters[$paramName].Name,
273+
$baseCommand.Parameters[$paramName].ParameterType,
274+
$baseCommand.Parameters[$paramName].Attributes
275+
))
276+
}
277+
$DynamicParameters
278+
}
279+
}
280+
} else {
281+
{}
282+
}
283+
) |
284+
Join-PipeScript @joinSplat # join the scripts together and return one [ScriptBlock]
285+
286+
287+
}
288+
289+
290+

0 commit comments

Comments
 (0)