文盘Rust——子命令提示,提高用户体验 | 京东云技术团队

上次我们聊到 CLI 的领域交互模式。在领域交互模式中,可能存在多层次的子命令。在使用过程中如果全评记忆的话,命令少还好,多了真心记不住。频繁 --help 也是个很麻烦的事情。如果每次按 ‘tab’ 键就可以提示或补齐命令是不是很方便呢。这一节我们就来说说 ‘autocommplete’ 如何实现。我们还是以interactcli-rs中的实现来解说实现过程

实现过程

其实,rustyline 已经为我们提供了基本的helper功能框架,其中包括了completer。我们来看代码,文件位置src/interact/cli.rs

#[derive(Helper)]
struct MyHelper {completer: CommandCompleter,highlighter: MatchingBracketHighlighter,validator: MatchingBracketValidator,hinter: HistoryHinter,colored_prompt: String,
}pub fn run() {let config = Config::builder().history_ignore_space(true).completion_type(CompletionType::List).output_stream(OutputStreamType::Stdout).build();let h = MyHelper {completer: get_command_completer(),highlighter: MatchingBracketHighlighter::new(),hinter: HistoryHinter {},colored_prompt: "".to_owned(),validator: MatchingBracketValidator::new(),};let mut rl = Editor::with_config(config);// let mut rl = Editor::<()>::new();rl.set_helper(Some(h));......}

首先定义 MyHelper 结构体, 需要实现 Completer + Hinter + Highlighter + Validator trait。然后通过rustyline的set_helper函数加载我们定义好的helper。在MyHelper 结构体中,需要我们自己来实现completer的逻辑。

Sub command autocompleter实现详解

  • SubCmd 结构体
#[derive(Debug, Clone)]
pub struct SubCmd {pub level: usize,pub command_name: String,pub subcommands: Vec<String>,
}

SubCmd 结构体包含:命令级别,命令名称,以及该命令包含的子命令信息,以便在实现实现 autocomplete 时定位命令和子命令的范围

  • 在程序启动时遍历所有的command,src/cmd/rootcmd.rs 中的all_subcommand函数负责收集所有命令并转换为Vec
pub fn all_subcommand(app: &clap_Command, beginlevel: usize, input: &mut Vec<SubCmd>) {let nextlevel = beginlevel + 1;let mut subcmds = vec![];for iterm in app.get_subcommands() {subcmds.push(iterm.get_name().to_string());if iterm.has_subcommands() {all_subcommand(iterm, nextlevel, input);} else {if beginlevel == 0 {all_subcommand(iterm, nextlevel, input);}}}let subcommand = SubCmd {level: beginlevel,command_name: app.get_name().to_string(),subcommands: subcmds,};input.push(subcommand);
}
  • CommandCompleter 子命令自动补充功能的核心部分
#[derive(Debug, Clone)]
pub struct CommandCompleter {subcommands: Vec<SubCmd>,
}impl CommandCompleter {pub fn new(subcmds: Vec<SubCmd>) -> Self {Self {subcommands: subcmds,}}//获取level下所有可能的子命令pub fn level_possible_cmd(&self, level: usize) ->   Vec<String> {let mut subcmds = vec![];let cmds = self.subcommands.clone();for iterm in cmds {if iterm.level == level {subcmds.push(iterm.command_name.clone());}}return subcmds;}//获取level下某字符串开头的子命令pub fn level_prefix_possible_cmd(&self, level: usize,   prefix: &str) -> Vec<String> {let mut subcmds = vec![];let cmds = self.subcommands.clone();for iterm in cmds {if iterm.level == level && iterm.command_name.  starts_with(prefix) {subcmds.push(iterm.command_name);}}return subcmds;}//获取某level 下某subcommand的所有子命令pub fn level_cmd_possible_sub_cmd(&self, level:   usize, cmd: String) -> Vec<String> {let mut subcmds = vec![];let cmds = self.subcommands.clone();for iterm in cmds {if iterm.level == level && iterm.command_name   == cmd {subcmds = iterm.subcommands.clone();}}return subcmds;}//获取某level 下某subcommand的所有prefix子命令pub fn level_cmd_possible_prefix_sub_cmd(&self,level: usize,cmd: String,prefix: &str,) -> Vec<String> {let mut subcmds = vec![];let cmds = self.subcommands.clone();for iterm in cmds {if iterm.level == level && iterm.command_name   == cmd {for i in iterm.subcommands {if i.starts_with(prefix) {subcmds.push(i);}}}}return subcmds;}pub fn complete_cmd(&self, line: &str, pos: usize) ->   Result<(usize, Vec<Pair>)> {let mut entries: Vec<Pair> = Vec::new();let d: Vec<_> = line.split(' ').collect();if d.len() == 1 {if d.last() == Some(&"") {for str in self.level_possible_cmd(1) {let mut replace = str.clone();replace.push_str(" ");entries.push(Pair {display: str.clone(),replacement: replace,});}return Ok((pos, entries));}if let Some(last) = d.last() {for str in self.level_prefix_possible_cmd  (1, *last) {let mut replace = str.clone();replace.push_str(" ");entries.push(Pair {display: str.clone(),replacement: replace,});}return Ok((pos - last.len(), entries));}}if d.last() == Some(&"") {for str in self.level_cmd_possible_sub_cmd(d.len() - 1,   d.get(d.len() - 2).unwrap().to_string()){let mut replace = str.clone();replace.push_str(" ");entries.push(Pair {display: str.clone(),replacement: replace,});}return Ok((pos, entries));}if let Some(last) = d.last() {for str in self.  level_cmd_possible_prefix_sub_cmd(d.len() - 1,d.get(d.len() - 2).unwrap().to_string(),*last,) {let mut replace = str.clone();replace.push_str(" ");entries.push(Pair {display: str.clone(),replacement: replace,});}return Ok((pos - last.len(), entries));}Ok((pos, entries))}
}impl Completer for CommandCompleter {type Candidate = Pair;fn complete(&self, line: &str, pos: usize, _ctx: &  Context<'_>) -> Result<(usize, Vec<Pair>)> {self.complete_cmd(line, pos)}
}

CommandCompleter 的实现部分比较多,大致包括两个部分,前一部分包括:获取某一级别下所有可能的子命令、获取某级别下某字符串开头的子命令、获取某级别下某个命令的所有子命令,等基本功能。这部分代码中有注释就不一一累述。

函数complete_cmd用来计算行中的位置以及在该位置的替换内容。

输入项是命令行的内容以及光标所在位置,输出项为在该位置需要替换的内容。比如,我们在提示符下输入 “root cm” root 下包含 cmd1、cmd2 两个子命令,此时如果按 'tab’键,complete_cmd 函数就会返回 (7,[cmd1,cmd2])。

作者:京东科技 贾世闻

来源:京东云开发者社区 转载请注明来源

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/72983.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【已更新代码图表】2023数学建模国赛E题python代码--黄河水沙监测数据分析

E 题 黄河水沙监测数据分析 黄河是中华民族的母亲河。研究黄河水沙通量的变化规律对沿黄流域的环境治理、气候变 化和人民生活的影响&#xff0c;以及对优化黄河流域水资源分配、协调人地关系、调水调沙、防洪减灾 等方面都具有重要的理论指导意义。 附件 1 给出了位于小浪底水…

【算法题】小红书2023秋招提前批算法真题解析

文章目录 题目来源T1&#xff1a;5900: 【DP】小红书2023秋招提前批-连续子数组最大和5801: 【二分查找】小红书2023秋招提前批-精华帖子解法1——排序滑动窗口解法2——前缀和 二分查找 5000: 【模拟】小红书2023秋招提前批-小红的数组构造解法——数学 5300: 【哈希表】小红…

Spring Cloud(Finchley版本)系列教程(二) 客户端负载均衡Ribbon

Spring Cloud(Finchley版本)系列教程(二) 客户端负载均衡Ribbon 目前主流的负载均衡方案有两种,一种是集中式均衡负载,在消费者与服务提供者之间使用独立的代理方式进行负载,比如F5、Nginx等。另一种则是客户端自己做负载均衡,根据自己的请求做负载,Ribbon就属于客户端自…

(高阶)Redis 7 第10讲 单线程 与 多线程 入门篇

面试题 1.Redis 是单线程还是多线程 最早的版本3.x是单线程。 版本4.x,严格意义不是单线程。负责处理客户端请求的线程是单线程,开始加入异步删除。 6.0.x版本后明确使用全新的多线程来解决问题 2.说说IO多路复用3.Redis 为什么快IO多路复用+epoll函…

stm32f103zet6移植标准库的sdio驱动

sdio移植 st官网给的标准库有给一个用于st出的评估板的sdio外设实现&#xff0c;但一是文件结构有点复杂&#xff0c;二是相比于国内正点原子和野火的板子也有点不同&#xff0c;因此还是需要移植下才能使用。当然也可以直接使用正点原子或野火提供的实例&#xff0c;但为了熟…

高频golang面试题:简单聊聊内存逃逸?

文章目录 问题怎么答举例 问题 知道golang的内存逃逸吗&#xff1f;什么情况下会发生内存逃逸&#xff1f; 怎么答 golang程序变量会携带有一组校验数据&#xff0c;用来证明它的整个生命周期是否在运行时完全可知。如果变量通过了这些校验&#xff0c;它就可以在栈上分配。…

【力扣】96. 不同的二叉搜索树 <动态规划>

【力扣】96. 不同的二叉搜索树 给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的二叉搜索树的种数。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;5 示例 2&#xff1a; 输入&am…

Java并发编程AQS

AQS AQS 是多线程同步器&#xff0c;它是 J.U.C 包中多个组件的底层实现&#xff0c;如 Lock、 CountDownLatch、Semaphore 等都用到了 AQS. 锁机制 从本质上来说&#xff0c;AQS 提供了两种锁机制&#xff0c;分别是排它锁&#xff0c;和共享锁。 排他锁 排它锁&#xff…

MYSQL学习之——管理用户

MYSQL学习之——管理用户&#xff08;DCL&#xff09; 用户这个东西其实是一个和TABLE DATABASE 这种东西一样的并列关键字。 用户的管理无外乎几个操作 查看用户 添加用户 删除用户 更新用户名或密码 改变用户对数据库的操作权限。 MYSQL语句功能备注USE mysql; select * FR…

代码随想录算法训练营第38天 | ● 理论基础 ● 509. 斐波那契数 ● 70. 爬楼梯 ● 746. 使用最小花费爬楼梯

文章目录 前言一、理论基础二、509. 斐波那契数三、70. 爬楼梯四、746. 使用最小花费爬楼梯总结 前言 动态规划 一、理论基础 1.基础 2.背包问题 3.打家劫舍 4.股票问题 5.子序列问题 动态规划&#xff0c;英文&#xff1a;Dynamic Programming&#xff0c;简称DP&#xff0c;…

【美团3.18校招真题1】

大厂笔试真题网址&#xff1a;https://codefun2000.com/ 塔子哥刷题网站博客&#xff1a;https://blog.codefun2000.com/ 小美剪彩带 提交网址&#xff1a;https://codefun2000.com/p/P1088 题意&#xff1a;找出区间内不超过k种数字子数组的最大长度 使用双指针的方式&…

@Builder注解有什么用?怎么用?

在Java中&#xff0c;Builder注解通常与项目构建工具Lombok一起使用&#xff0c;用于自动生成一个建造者&#xff08;Builder&#xff09;模式相关的代码&#xff0c;以简化对象的创建和初始化过程。 使用Builder注解的类会自动生成一个内部静态的建造者类&#xff0c;该建造者…

小程序的使用

微信小程序开发 外部链接别人的总结查看&#xff08;超详细保姆式教程&#xff09; 基础语法 1.数据绑定 1.1 初始化数据 页面.js的data选项中Page({data: {motto: Hello World,id:18} })使用数据 单项数据流&#xff1a;Mustache 语法 a)模板结构中使用双大括号 {{data}} …

安装程序报错“E: Sub-process /usr/bin/dpkg returned an error code (1)”的解决办法

今天在终端使用命令安装程序时出现了如下的报错信息。 E: Sub-process /usr/bin/dpkg returned an error code (1) 这种情况下安装什么程序最终都会报这个错&#xff0c;具体的报错截图如下图所示。 要解决这个问题&#xff0c;首先使用下面的命令进到相应的目录下。 cd /var/…

Java“牵手”唯品会商品列表数据,关键词搜索唯品会商品数据接口,唯品会API申请指南

唯品会商城是一个网上购物平台&#xff0c;售卖各类商品&#xff0c;包括服装、鞋类、家居用品、美妆产品、电子产品等。要获取唯品会商品列表和商品详情页面数据&#xff0c;您可以通过开放平台的接口或者直接访问唯品会商城的网页来获取商品详情信息。以下是两种常用方法的介…

Python bug: TypeError: unhashable type: ‘dict‘ or ‘list‘

Python bug: TypeError: unhashable type: ‘dict‘ or ‘list’ 经过排除&#xff0c;发现我遇到的错误是由于字典的键使用了【字典】或【列表】变量。 由于有时候会将一些变量赋值给字典&#xff0c;通常键的名称直接使用变量名称&#xff0c;但有时不小心键的名称没有变成字…

IDEA快捷键第二版

1、选择当前行和上一行 按住 Shift键 再按两下向上键&#xff08; ↑ &#xff09;&#xff0c;按两下选两行&#xff0c;以此类推 2、将整个方法上移动 文本光标应放在方法的标头处&#xff0c;按住Ctrl Shift 向上键&#xff08; ↑ &#xff09;&#xff0c; 3、解包 …

RabbitMQ的Confirm机制

1.消息的可靠性 RabbitMQ提供了Confirm的确认机制。 Confirm机制用于确认消息是否已经发送给了交换机 2.Java的实现 1.导入依赖 <dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId><version>5.6.0</v…

MVCC究竟是什么?

&#xff11;.MVCC概念 MVCC&#xff0c;全称多版本并发控制 MVCC究竟是什么&#xff1f; 通俗的来说MVCC就是为了在读取数据时不加锁来提高读取效率的一种办法&#xff0c;MVCC解决的是读写时线程安全问题&#xff0c;线程不用去抢占读写锁。MVCC中的读就是快照读&#xff0c…

初识计算机和命令行操作

文章目录 计算机基础知识计算机是什么计算机的组成计算机的使用方式Windows的命令行环境变量&#xff08;Environment Variable&#xff09;PATH环境变量进制文本文件和字符集纯文本和富文本字符集乱码 python专栏推荐&#xff1a;python基础知识&#xff08;0基础入门&#xf…