PowerShell AST 修改和范围
PowerShell AST Modification and Extents
我目前正在尝试使用 PowerShell 3.0 中引入的 AST 功能来修改 ScriptBlock。我的要求是ScriptBlock的参数块中的所有参数都得到一个[Parameter(Mandatory)]
属性。
基本上代码应该这样修改:
Param([string]$x)
Write-Host $x
对此:
Param([Parameter(Mandatory)][string]$x)
Write-Host $x
但是,我 运行 在添加那个新属性时遇到了问题,因为它需要一个 IScriptExtent
而我不确定我应该如何创建一个新的 IScriptExtent
.
如何创建新的脚本范围?我可以为职位使用什么值?我是否必须更改以下所有范围的位置?
我试着重用我正在修改的每个参数的范围,但不幸的是,这似乎没有产生应有的结果(例如,当我在修改后的 ScriptBlock
上调用 ToString
我看不到任何变化)。
到目前为止,我的实施是基于 ICustomAstVisitor
发现的 here。
最重要的方法如下所示:
public object VisitParameter(ParameterAst parameterAst)
{
var newName = VisitElement(parameterAst.Name);
var extent = // What to do here?
var mandatoryArg = new AttributeAst(extent, new ReflectionTypeName(typeof (ParameterAttribute)),
new ExpressionAst[0],
new[] {new NamedAttributeArgumentAst(extent, "Mandatory", new ConstantExpressionAst(extent, true), true)});
var newAttributes = new[] {mandatoryArg}.Concat(VisitElements(parameterAst.Attributes));
var newDefaultValue = VisitElement(parameterAst.DefaultValue);
return new ParameterAst(parameterAst.Extent, newName, newAttributes, newDefaultValue);
}
以 I
开头的名称通常是接口。它们不是您创建实例的 classes,它们是指定特定 class 实现特定已知功能集的某种契约。
例如,[hashtable]
实现了 IEnumerable
。这意味着任何知道如何使用 IEnumerable
接口并在 class 上操作的东西;您可以创建自己的 class 来实现该接口,并且永远不会知道您的 class 或它所做的事情的代码仍然可以按照 IEnumerable
定义的方式与它交互(这在这种情况下是一种迭代它的方法)。
因此,当函数声明具有接口类型的参数时,它不会寻找任何特定的 class,它会寻找实现该接口的任何 class。
下一步是找到实现该接口的类型。这是我用来找到这些的一些 PowerShell 代码:
[System.AppDomain]::CurrentDomain.GetAssemblies().GetTypes() | Where-Object {
[System.Management.Automation.Language.IScriptExtent].IsAssignableFrom($_)
}
由此可见:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False IScriptExtent
False False InternalScriptExtent System.Object
False False EmptyScriptExtent System.Object
True False ScriptExtent System.Object
第一个列表是界面本身。在其他三个中,其中两个不是 public,因此只剩下 ScriptExtent
.
您可以使用 New-Object
创建其中之一,但您需要提供开始和结束位置作为 [ScriptPosition]
对象。在没有看到更多您的代码的情况下,我不完全确定这些应该是什么。
脚本范围主要用于报错,也用于调试(例如设置行断点)
一般来说,合成脚本(如您的示例)的选项是:
- 重用现有的 ast,大概 near/related 到您要添加的 ast
- 使用一个空的 ast(基本上创建没有文件、空行的 ScriptExtent 和 ScriptPosition 的实例)
- 创建一个以某种方式帮助调试的合成范围,可能包含一些特殊内容
在您的示例中,以上任何一项都适用。第二种选择是最简单的。第三个选项只是第二个选项的变体,但您可以将内容设置为有用的内容,例如
<#Generated: [Parameter(Mandatory)] #>
我目前正在尝试使用 PowerShell 3.0 中引入的 AST 功能来修改 ScriptBlock。我的要求是ScriptBlock的参数块中的所有参数都得到一个[Parameter(Mandatory)]
属性。
基本上代码应该这样修改:
Param([string]$x)
Write-Host $x
对此:
Param([Parameter(Mandatory)][string]$x)
Write-Host $x
但是,我 运行 在添加那个新属性时遇到了问题,因为它需要一个 IScriptExtent
而我不确定我应该如何创建一个新的 IScriptExtent
.
如何创建新的脚本范围?我可以为职位使用什么值?我是否必须更改以下所有范围的位置?
我试着重用我正在修改的每个参数的范围,但不幸的是,这似乎没有产生应有的结果(例如,当我在修改后的 ScriptBlock
上调用 ToString
我看不到任何变化)。
到目前为止,我的实施是基于 ICustomAstVisitor
发现的 here。
最重要的方法如下所示:
public object VisitParameter(ParameterAst parameterAst)
{
var newName = VisitElement(parameterAst.Name);
var extent = // What to do here?
var mandatoryArg = new AttributeAst(extent, new ReflectionTypeName(typeof (ParameterAttribute)),
new ExpressionAst[0],
new[] {new NamedAttributeArgumentAst(extent, "Mandatory", new ConstantExpressionAst(extent, true), true)});
var newAttributes = new[] {mandatoryArg}.Concat(VisitElements(parameterAst.Attributes));
var newDefaultValue = VisitElement(parameterAst.DefaultValue);
return new ParameterAst(parameterAst.Extent, newName, newAttributes, newDefaultValue);
}
以 I
开头的名称通常是接口。它们不是您创建实例的 classes,它们是指定特定 class 实现特定已知功能集的某种契约。
例如,[hashtable]
实现了 IEnumerable
。这意味着任何知道如何使用 IEnumerable
接口并在 class 上操作的东西;您可以创建自己的 class 来实现该接口,并且永远不会知道您的 class 或它所做的事情的代码仍然可以按照 IEnumerable
定义的方式与它交互(这在这种情况下是一种迭代它的方法)。
因此,当函数声明具有接口类型的参数时,它不会寻找任何特定的 class,它会寻找实现该接口的任何 class。
下一步是找到实现该接口的类型。这是我用来找到这些的一些 PowerShell 代码:
[System.AppDomain]::CurrentDomain.GetAssemblies().GetTypes() | Where-Object {
[System.Management.Automation.Language.IScriptExtent].IsAssignableFrom($_)
}
由此可见:
IsPublic IsSerial Name BaseType -------- -------- ---- -------- True False IScriptExtent False False InternalScriptExtent System.Object False False EmptyScriptExtent System.Object True False ScriptExtent System.Object
第一个列表是界面本身。在其他三个中,其中两个不是 public,因此只剩下 ScriptExtent
.
您可以使用 New-Object
创建其中之一,但您需要提供开始和结束位置作为 [ScriptPosition]
对象。在没有看到更多您的代码的情况下,我不完全确定这些应该是什么。
脚本范围主要用于报错,也用于调试(例如设置行断点)
一般来说,合成脚本(如您的示例)的选项是:
- 重用现有的 ast,大概 near/related 到您要添加的 ast
- 使用一个空的 ast(基本上创建没有文件、空行的 ScriptExtent 和 ScriptPosition 的实例)
- 创建一个以某种方式帮助调试的合成范围,可能包含一些特殊内容
在您的示例中,以上任何一项都适用。第二种选择是最简单的。第三个选项只是第二个选项的变体,但您可以将内容设置为有用的内容,例如
<#Generated: [Parameter(Mandatory)] #>