文盘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<SubCmd>
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])。
作者:京东科技 贾世闻
来源:京东云开发者社区 转载请注明来源

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
谈谈JSF业务线程池的大小配置 | 京东物流技术团队
1.简介 JSF业务线程池使用JDK的线程池技术,缺省情况下采用Cached模式(核心线程数20,最大线程数200)。此外,还提供了Fixed固定线程大小的模式,两种模式均可设置请求队列大小。 本文旨在通过一个简化场景(“单服务应用”)下的负载测试,为“JSF业务线程池大小配置”提供基准测试结果,并形成一些普遍适用的结论。 本文的目标读者包括需要合理配置JSF线程大小的压测工程师、开发部署运维工程师以及架构师。本文不涉及JSF服务端的其他配置项,也不针对“复合服务应用”的合理配置进行探讨。你可以利用本文提供的结论,作为设计压测用例或评估业务线程池大小的基本方法的参考,以便在实践中合理配置JSF业务线程池大小。需要注意的是,JSF业务线程池大小的合理配置应该基于高保真的负载测试结果。 “单服务应用”指应用仅包含一个提供接口,且接口中仅有一个方法。 “复合服务应用”则指应用包含多个提供接口或一个接口中含有多个方法。 2.测试用例说明 本次基准测试选取了USF3.0权限系统,将其定制化为一个单一的服务提供者,仅对该提供者的一个方法进行了测试,因此可以看作是一个“单服务应用”。测试中将CPU作...
- 下一篇
弹性数据库连接池探活策略调研(三)——DBCP | 京东云技术团队
前言 在之前的文章中,我们介绍了弹性数据库连接失效的背景,并探讨了HikariCP、Druid连接池探活策略的相关内容。在本文中,我们将会继续探讨另一个线上常用的连接池——DBCP,并为您介绍如何在使用DBCP时实现最佳实践的弹性数据库连接池探活策略。 DBCP DBCP有两个版本:1.x和2.x(也称为DBCP2)。DBCP 2基于Commons Pool 2,相比1.x版本,在性能、JMX支持和其他许多方面都有所提高。由于DBCP 2.x与DBCP 1.x不是二进制兼容,所以升级到2.x的用户应该知道Java包名称已经改变,以及Maven坐标。 首先我们先列出关于DBCP探活相关的参数: 参数名称 说明 默认值 initialSize 初始化时建立物理连接的个数。 0 minIdle 最小空闲连接:连接池中容许保持空闲状态的最小连接数量,低于这个数量将创建新的连接,如果设置为0则不创建 0 maxIdle 最大空闲连接:连接池中容许保持空闲状态的最大连接数量,超过的空闲连接将被释放,如果设置为负数表示不限制 8 maxActive/maxTotal 最大活动连接:连接池在同一时间能...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Hadoop3单机部署,实现最简伪集群
- CentOS关闭SELinux安全模块
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- MySQL8.0.19开启GTID主从同步CentOS8