Python Click应用中实现以'='触发Shell补全的技术问询
简介
Pallets Click模块支持解析--option=value格式的命令选项,该格式与--option value等效,但在Click中初始化Shell补全时,--option=<TAB><TAB>无法触发补全,而--option <TAB><TAB>却能正常触发,这一现象很奇怪。
我开发了一个命令行界面(CLI),其中一个选项接收长字符串并将其拆分为字典。该字符串以逗号分隔键值对,键与值之间用等号分隔。这个CLI是一个基于数据库的ICS文件生成器,可通过如下命令添加事件:
beadroll add --alarms="alarm=last minute, trigger=-1m, summary=get ready, alarm=five minutes, trigger=-5m, summary=steady time, alarm=feeder, trigger=-15m, summary=start preparations, alarm=soul, trigger=-30m, summary=preparations" \ "Lunch with Rabbit" \ "2023-10-15T01:45:00-04:00" \ "2023-10-15T01:44:00-04:00"
完整项目托管在GitHub仓库。目前功能可用,但缺少补全功能,这正是我正在开发的部分。手动输入上述长字符串十分繁琐,我希望简化操作,但当前的难点是实现无需空格触发的补全。
已尝试方案
我构建了一个自定义类型补全,并暂时应用到--uid选项:
class EnvVarType(click.ParamType): name = "envvar" def shell_complete(self, ctx, param, incomplete): return [ click.shell_completion.CompletionItem(name) for name in os.environ if name.startswith(incomplete) ] def resolve_completer(self, ctx, incomplete): if ctx.args: # Get the last argument from partial_args last_arg = ctx.args[-1] if last_arg.startswith("--") and "=" in last_arg: # Split the argument into option name and value option, value = last_arg.split("=") if ctx.command.get_params(ctx)[option].type is self: # Trigger completion based on the value return self.shell_complete(ctx, None, value) return super().resolve_completer(ctx, incomplete)
@cli.command() @click.option('--alarms', type=EnvVarType(), default=None)
通过该类和装饰器,目前可以补全一些环境变量,我原本打算先用它占位,直到实现预期的补全效果。但我尚未完全理解补全触发符的生成逻辑,我希望实现--alarms="\<<TAB>触发补全,且后续每个,字符都能作为触发符,直到双引号闭合。
似乎我的resolve_completer函数并未与shell_complete函数关联,或者ctx.args并未如预期触发。我想知道是否有人实现过类似功能,以及正确初始化触发符的方法。
我见过开发者使用列表推导遍历命令作用域内的参数,但不确定如何将这种循环与shell_completion函数的行为关联。即使尝试如下条件语句:
class EnvVarType(click.ParamType): name = "envvar" def shell_complete(self, ctx, param, incomplete): if "=" in click.Context(click.Command('add')).args: return [ click.shell_completion.CompletionItem(name) for name in ['alarm=', 'trigger=', 'summary='] if name.startswith(incomplete) ] elif "=" in ctx.args: return [ click.shell_completion.CompletionItem(name) for name in os.environ if name.startswith(incomplete) ] else: return [ click.shell_completion.CompletionItem(name) for name in ['alarm=', 'trigger=', 'summary=', 'other='] if name.startswith(incomplete) ]
也无法改变核心问题:输入--uid=后按Tab会删除所有6个字符,而输入--uid 后按Tab可以触发补全。
或许休息后我能发现问题所在。另一种可行思路是重写基础命令,接受两个选项并后续将其赋值给同一键值对,类似某Stack Overflow回答的方案。
现寻求实现以=作为Shell补全触发符的正确方案,解决当前Click应用中的补全触发问题。
内容的提问来源于stack exchange,提问作者croakouttatune

