Skip to content

Commit 13570be

Browse files
author
James Brundage
committed
Adding requires keyword (Fixes #293)
1 parent feadb76 commit 13570be

File tree

1 file changed

+251
-0
lines changed

1 file changed

+251
-0
lines changed
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
<#
2+
.SYNOPSIS
3+
requires one or more modules, variables, or types.
4+
.DESCRIPTION
5+
Requires will require on or more modules, variables, or types to exist.
6+
#>
7+
8+
using namespace System.Management.Automation.Language
9+
10+
[ValidateScript({
11+
$validateVar = $_
12+
if ($validateVar -is [CommandAst]) {
13+
$cmdAst = $validateVar
14+
if ($cmdAst.CommandElements[0].Value -in 'require', 'requires') {
15+
return $true
16+
}
17+
}
18+
return $false
19+
})]
20+
[Reflection.AssemblyMetadata("PipeScript.Keyword",$true)]
21+
[Alias('Require')]
22+
param(
23+
# One or more required modules.
24+
[Parameter(ValueFromPipelineByPropertyName)]
25+
$Module,
26+
27+
# If set, will require the latest version of a module.
28+
[Parameter(ValueFromPipelineByPropertyName)]
29+
[switch]
30+
$Latest,
31+
32+
# A ModuleLoader script can be used to dynamically load unresolved modules.
33+
# This script will be passed the unloaded module as an argument, and should return a module.
34+
[Parameter(ValueFromPipelineByPropertyName)]
35+
[Alias('ModuleResolver', 'Module Loader', 'Module Resolver')]
36+
[ScriptBlock]
37+
$ModuleLoader,
38+
39+
# One or more required types.
40+
[Parameter(ValueFromPipelineByPropertyName)]
41+
$Type,
42+
43+
# A TypeLoader script can be used to dynamically load unresolved types.
44+
# This script will be passed the unloaded type as an argument.
45+
[Parameter(ValueFromPipelineByPropertyName)]
46+
[Alias('TypeResolver', 'Type Loader', 'Type Resolver')]
47+
[ScriptBlock]
48+
$TypeLoader,
49+
50+
# One or more required variables.
51+
[Parameter(ValueFromPipelineByPropertyName)]
52+
[Alias('Variable')]
53+
$Variables,
54+
55+
# A VariableLoader script can be used to dynamically load unresolved variable.
56+
# This script will be passed the unloaded variable as an argument.
57+
[Parameter(ValueFromPipelineByPropertyName)]
58+
[Alias('VariableResolver', 'Variable Loader', 'Variable Resolver')]
59+
[ScriptBlock]
60+
$VariableLoader,
61+
62+
# The Command AST. This will be provided when using the transpiler as a keyword.
63+
[Parameter(Mandatory,ParameterSetName='CommandAST',ValueFromPipeline)]
64+
[CommandAst]
65+
$CommandAst,
66+
67+
# The ScriptBlock. This will be provided when using the transpiler as an attribute.
68+
[Parameter(Mandatory,ParameterSetName='ScriptBlock',ValueFromPipeline)]
69+
[ScriptBlock]
70+
$ScriptBlock = {}
71+
)
72+
73+
process {
74+
# If we were called as a CommandAST
75+
if ($PSCmdlet.ParameterSetName -eq 'CommandAst') {
76+
# attempt to parse the command as a sentence.
77+
$mySentence = $commandAst.AsSentence($MyInvocation.MyCommand)
78+
79+
# If the sentence had parameters
80+
if ($mySentence.Parameter.Count) {
81+
82+
foreach ($clause in $mySentence.Clauses) {
83+
if ($clause.ParameterName) {
84+
$ExecutionContext.SessionState.PSVariable.Set($clause.ParameterName, $mySentence.Parameter[$clause.Name])
85+
}
86+
}
87+
}
88+
89+
# If the sentence only any remaining arguments, treat them as modules
90+
if ($mySentence.Argument) {
91+
if ($Module) {
92+
$module = @($module) + $mySentence.Argument
93+
} else {
94+
$module = $mySentence.Argument
95+
}
96+
}
97+
}
98+
99+
#region Module Requirements
100+
$moduleRequirementScript = {}
101+
if ($Module) {
102+
$moduleRequirementsList = @(foreach ($mod in $module) {
103+
if ($mod -is [string]) {
104+
"'$($mod -replace "'","''")'"
105+
} else {
106+
"$mod"
107+
}
108+
}) -join ','
109+
$moduleRequirementScript = [ScriptBlock]::Create(
110+
("foreach (`$moduleRequirement in $moduleRequirementsList) {
111+
`$requireLatest = $(if ($Latest) { '$true' } else { '$false' })
112+
`$ModuleLoader = $(if ($moduleLoader) { "{
113+
$moduleLoader
114+
}" } else { '$null'})
115+
" + {
116+
# If the module requirement was a string
117+
if ($moduleRequirement -is [string]) {
118+
# see if it's already loaded
119+
$foundModuleRequirement = Get-Module $moduleRequirement
120+
if (-not $foundModuleRequirement) {
121+
# If it wasn't,
122+
$foundModuleRequirement = try { # try loading it
123+
Import-Module -Name $moduleRequirement -PassThru -Global -ErrorAction SilentlyContinue
124+
} catch {
125+
$null
126+
}
127+
}
128+
129+
# If we found a version but require the latest version,
130+
if ($foundModuleRequirement -and $requireLatest) {
131+
# then find if there is a more recent version.
132+
Write-Verbose "Searching for a more recent version of $($foundModuleRequirement.Name)@$($foundModuleRequirement.Version)"
133+
$foundModuleInGallery = Find-Module -Name $foundModuleRequirement.Name
134+
if ($foundModuleInGallery -and
135+
([Version]$foundModuleInGallery.Version -gt [Version]$foundModuleRequirement.Version)) {
136+
Write-Verbose "$($foundModuleInGallery.Name)@$($foundModuleInGallery.Version)"
137+
# If there was a more recent version, unload the one we already have
138+
$foundModuleRequirement | Remove-Module # Unload the existing module
139+
$foundModuleRequirement = $null
140+
} else {
141+
Write-Verbose "$($foundModuleRequirement.Name)@$($foundModuleRequirement.Version) is the latest"
142+
}
143+
}
144+
145+
# If we have no found the required module at this point
146+
if (-not $foundModuleRequirement) {
147+
if ($moduleLoader) { # load it using a -ModuleLoader (if provided)
148+
$foundModuleRequirement = . $moduleLoader $moduleRequirement
149+
} else {
150+
# or install it from the gallery.
151+
Install-Module -Name $moduleRequirement -Scope CurrentUser -Force -AllowClobber
152+
if ($?) {
153+
# Provided the installation worked, try importing it
154+
$foundModuleRequirement =
155+
Import-Module -Name $moduleRequirement -PassThru -Global -ErrorAction SilentlyContinue
156+
}
157+
}
158+
} else {
159+
$foundModuleRequirement
160+
}
161+
}
162+
} + "}")
163+
)
164+
}
165+
#endregion Module Requirements
166+
167+
#region Variable Requirements
168+
$variableRequirementScript = {}
169+
if ($Variables) {
170+
# Translate variables into their string names.
171+
$variableRequirementsList = @(foreach ($var in $Variables) {
172+
if ($var -is [string]) {
173+
"'$($var -replace "'","''")'"
174+
}
175+
elseif ($var -is [VariableExpressionAst]) {
176+
"'$($var.variablepath.ToString() -replace "'", "''")'"
177+
}
178+
}) -join ','
179+
$variableRequirementScript =
180+
"foreach (`$variableRequirement in $variableRequirementsList) {
181+
`$variableLoader = $(if ($VariableLoader) { "{
182+
$variableLoader
183+
}"} else { '$null'})
184+
" + {
185+
if (-not $ExecutionContext.SessionState.PSVariable.Get($variableRequirement)) {
186+
if ($VariableLoader) {
187+
. $VariableLoader $variableRequirement
188+
if (-not $ExecutionContext.SessionState.PSVariable.Get($variableRequirement)) {
189+
190+
}
191+
} else {
192+
Write-Error "Missing required variable $variableRequirement"
193+
}
194+
}
195+
} +
196+
"}"
197+
$variableRequirementScript = [scriptblock]::Create($variableRequirementScript)
198+
}
199+
#endregion Variable Requirements
200+
201+
#region Type Requirements
202+
$typeRequirementScript = {}
203+
if ($type) {
204+
$typeRequirementsList = @(foreach ($typeName in $Type) {
205+
if ($typeName -is [string]) {
206+
"'$($typeName -replace "^\[" -replace '\]$')'"
207+
} elseif ($typeName.TypeName) {
208+
"'$($typeName.TypeName.ToString())'"
209+
}
210+
}) -join ','
211+
$typeRequirementScript =
212+
"foreach (`$typeRequirement in $typeRequirementsList) {
213+
`$typeLoader = $(if ($TypeLoader) {
214+
"{$typeLoader}"
215+
} else { '$null'})
216+
" + {
217+
if (-not ($typeRequirement -as [type])) {
218+
if ($TypeLoader) {
219+
. $TypeLoader $typeName
220+
if (-not ($typeRequirement -as [type])) {
221+
Write-Error "Type [$typeRequirement] could not be loaded"
222+
}
223+
} else {
224+
Write-Error "Type [$typeRequirement] is not loaded"
225+
}
226+
}
227+
} + "}"
228+
$typeRequirementScript = [scriptblock]::Create($typeRequirementScript)
229+
}
230+
#endregion Type Requirements
231+
232+
if ($PSCmdlet.ParameterSetName -eq 'CommandAst') {
233+
$moduleRequirementScript, $variableRequirementScript, $typeRequirementScript | Join-PipeScript
234+
} else {
235+
$declareInBlock = 'begin'
236+
if ($scriptBlock.Ast.dynamicParam) {
237+
$declareInBlock = 'dynamicParam'
238+
}
239+
240+
$moduleRequirementScript = [ScriptBlock]::Create("$declareInBlock {
241+
$ModuleRequirementScript
242+
}")
243+
$variableRequirementScript = [ScriptBlock]::Create("$declareInBlock {
244+
$variableRequirementScript
245+
}")
246+
$typeRequirementScript = [ScriptBlock]::Create("$declareInBlock {
247+
$typeRequirementScript
248+
}")
249+
$ScriptBlock, $moduleRequirementScript, $variableRequirementScript, $typeRequirementScript | Join-PipeScript
250+
}
251+
}

0 commit comments

Comments
 (0)