如何在Powershell完成助手中获得令牌化命令

发布于 2025-02-12 08:31:10 字数 919 浏览 1 评论 0原文

我正在尝试为PowerShell编写完成功能。我的策略是将可执行文件推迟以返回有关命令行的完整信息。

目前,我有:

$scriptblock = {
    param($wordToComplete, $commandAst, $cursorPosition)
        $command = $commandAst.toString();
        test-command-completions powershell from-json $cursorPosition -- $command | ForEach-Object {
            [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
        }
}

Register-ArgumentCompleter -Native -CommandName test-command -ScriptBlock $scriptblock

它试图为test-Command设置完成,并为test-command-completions可执行文件辩护以返回给定的信息。

不幸的是,$命令是带有命令的单个字符串,例如。 test-command install-he,但这意味着test-command-completions将其作为单个参数而不是一组参数接收,而当前的实现则依赖于拥有一个参数一系列论点是bash& ZSH提供信息。

我还有其他方法可以解决它,但是我很想知道我是否可以从命令AST或从AST构造的命令字符串中获得一系列值。我可以做$ command.split(“”),但我想要一种可以处理原始命令中引用和逃脱的方法。

I am attempting to write a completion function for powershell. My strategy is to defer to an executable to return the completions given information about the command line.

Currently I have:

$scriptblock = {
    param($wordToComplete, $commandAst, $cursorPosition)
        $command = $commandAst.toString();
        test-command-completions powershell from-json $cursorPosition -- $command | ForEach-Object {
            [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
        }
}

Register-ArgumentCompleter -Native -CommandName test-command -ScriptBlock $scriptblock

Where it is attempting to set up completions for test-command and it defers to the test-command-completions executable to return the completions given the information provided.

Unfortunately $command is a single string with the command, eg. test-command install --he but that means test-command-completions receives it as a single argument rather than as a set of arguments and the current implementation relies on having a set of arguments as that is how bash & zsh provide the information.

There are other ways I can work around it but I'd be keen to know if I can get an array of values from the command AST or the command string constructed from the AST. I could do $command.Split(" ") but I want an approach that will handle quoting and escapes in the original command.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

拍不死你 2025-02-19 08:31:10

如注释中所述,您需要与$ commandast参数进行交互 - PowerShell将通过代表解析的命令表达式的抽象语法树(又名AST) - 我将尝试展示如何显示检查它!

PowerShell中的所有AST类型都带有搜索机制,您可以用来发现命令的各个元素 - 使用find()来派遣搜索第一个匹配的子树元素,请使用findall()要搜索多个元素 - 两种方法都接受基于脚本块的谓词作为其第一个参数:

# This part is just to emulate `$commandAst`
$commandAst = { Some-Command -ParamName argument $anotherArgument -SwitchParam }.Find({$args[0] -is [System.Management.Automation.Language.CommandAst]}, $false)

# Now you have a `CommandAst` object you can explore - this will give you all descendant syntax elements for example
$commandAst.FindAll({ $true }, $false)

# But you can also inspect the arguments of the command expression as a list via the `CommandElements` property:
$commandName,$arguments = $commandAst.CommandElements

# As indicated by the variable name, the first element in the expression is the command name
Write-Host "Command name is '$commandName'"

# ... but the rest are the arguments:
$arguments |ForEach-Object {
  Write-Host "Found command element of type '$($_.GetType().Name)' with input text '$($_.Extent)'"
}

如果直接与AST合作会使您头痛PowerShell为您提供令牌的平坦列表:

$tokens = @()
$null = [System.Management.Automation.Language.Parser]::ParseInput($commandAst.Extent.ToString(), [ref]$tokens, [ref]$null)

$令牌现在保留了解析器识别的所有单个令牌的列表。

祝你好运!

As mentioned in the comments, you'll want to interact with the $commandAst argument - PowerShell will pass an Abstract Syntax Tree (aka. AST) representing the parsed command expression - I'll try and show how to inspect it!

All AST types in PowerShell come with a search mechanism you can use to discover the individual elements of the command - use Find() to dispatch a search for the first matching sub-tree element, use FindAll() to search for multiple elements - both methods accept a scriptblock-based predicate as their first argument:

# This part is just to emulate `$commandAst`
$commandAst = { Some-Command -ParamName argument $anotherArgument -SwitchParam }.Find({$args[0] -is [System.Management.Automation.Language.CommandAst]}, $false)

# Now you have a `CommandAst` object you can explore - this will give you all descendant syntax elements for example
$commandAst.FindAll({ $true }, $false)

# But you can also inspect the arguments of the command expression as a list via the `CommandElements` property:
$commandName,$arguments = $commandAst.CommandElements

# As indicated by the variable name, the first element in the expression is the command name
Write-Host "Command name is '$commandName'"

# ... but the rest are the arguments:
$arguments |ForEach-Object {
  Write-Host "Found command element of type '$($_.GetType().Name)' with input text '$($_.Extent)'"
}

If working directly with the AST is giving you a headache, you can also manually re-parse the input and have PowerShell provide you with a flat list of tokens:

$tokens = @()
$null = [System.Management.Automation.Language.Parser]::ParseInput($commandAst.Extent.ToString(), [ref]$tokens, [ref]$null)

$tokens now holds a list of all individual tokens recognized by the parser.

Good luck!

许久 2025-02-19 08:31:10

由于Mathias R. Jessen的帮助并抛弃了Kubectrl PowerShell完成脚本,我最终获得了以下代码。

$scriptblock = {
    param($wordToComplete, $commandAst, $cursorPosition)
        # Get command line elements as string array
        $elements = $commandAst.CommandElements | ForEach-Object { "$_" };

        # Search the ast of the command to find the node that contains the
        # cursor position provided to this completion function. We increment
        # the script-scope index variable to track the index of the node that
        # matches the cursor position so that we can provide that to the
        # completion code. This is because the completion executable expects
        # the index of the word in the command line to be completed whereas
        # PowerShell provides the character index itno the string of the
        # command line where the cursor is.
        #
        # We start the index at -2 as there is a parent node to the AST which
        # we visit and there we increment to -1 and then there is the command
        # name itself where we increment to 0 which is the appropriate index
        # for the command name.
        $script:index = -2;
        $target = $commandAst.FindAll({
            if ( $args[0].Extent.StartColumnNumber -le $cursorPosition ) {
                $script:index++;
            }

            $args[0].Extent.StartColumnNumber -le $cursorPosition -and $args[0].Extent.EndColumnNumber -ge $cursorPosition
        }, $false)


        # Run the completion helper and create completion items from the result
        # "@" splats the array as arguments to the command
        test-command-completions power-shell from-json $wordToComplete $script:index -- @elements | ForEach-Object {
            [System.Management.Automation.CompletionResult]::new($_)
        }
}

Register-ArgumentCompleter -Native -CommandName test-command -ScriptBlock $scriptblock

我认为有更干净的方法可以实现它,但这是我与Powershell有史以来的第一次互动,因此我不太适合判断。

I ended up with the following code thanks to Mathias R. Jessen's help and dumping out the kubectrl powershell completion script.

$scriptblock = {
    param($wordToComplete, $commandAst, $cursorPosition)
        # Get command line elements as string array
        $elements = $commandAst.CommandElements | ForEach-Object { "$_" };

        # Search the ast of the command to find the node that contains the
        # cursor position provided to this completion function. We increment
        # the script-scope index variable to track the index of the node that
        # matches the cursor position so that we can provide that to the
        # completion code. This is because the completion executable expects
        # the index of the word in the command line to be completed whereas
        # PowerShell provides the character index itno the string of the
        # command line where the cursor is.
        #
        # We start the index at -2 as there is a parent node to the AST which
        # we visit and there we increment to -1 and then there is the command
        # name itself where we increment to 0 which is the appropriate index
        # for the command name.
        $script:index = -2;
        $target = $commandAst.FindAll({
            if ( $args[0].Extent.StartColumnNumber -le $cursorPosition ) {
                $script:index++;
            }

            $args[0].Extent.StartColumnNumber -le $cursorPosition -and $args[0].Extent.EndColumnNumber -ge $cursorPosition
        }, $false)


        # Run the completion helper and create completion items from the result
        # "@" splats the array as arguments to the command
        test-command-completions power-shell from-json $wordToComplete $script:index -- @elements | ForEach-Object {
            [System.Management.Automation.CompletionResult]::new($_)
        }
}

Register-ArgumentCompleter -Native -CommandName test-command -ScriptBlock $scriptblock

I imagine there are cleaner ways to achieve it but this is my first ever interaction with PowerShell so I'm not well placed to judge.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文