文盘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])。
作者:京东科技 贾世闻
来源:京东云开发者社区 转载请注明来源
文盘Rust——子命令提示,提高用户体验的更多相关文章
- 巧用Ajax的beforeSend 提高用户体验--防止重复数据
巧用Ajax的beforeSend 提高用户体验 jQuery是经常使用的一个开源js框架,其中的$.ajax请求中有一个beforeSend方法,用于在向服务器发送请求前执行一些动作.具体可参考jQ ...
- 前端如何实现图片懒加载(lazyload) 提高用户体验
定义 图片懒加载又称图片延时加载.惰性加载,即在用户需要使用图片的时候加载,这样可以减少请求,节省带宽,提高页面加载速度,相对的,也能减少服务器压力. 惰性加载是程序人性化的一种体现,提高用户体验,防 ...
- 文盘Rust -- 把程序作为守护进程启动
当我们写完一个服务端程序,需要上线部署的时候,或多或少都会和操作系统的守护进程打交道,毕竟谁也不希望shell关闭既停服.今天我们就来聊聊这个事儿. 最早大家部署应用的通常操作是 "nohu ...
- php fastcgi_finish_request让你的程序由等待时间,瞬间完成,提高用户体验
当PHP运行在FastCGI模式时,PHP FPM提供了一个名为fastcgi_finish_request的方法.按照文档上的说法,此方法可以提高请求的处理速度,如果有些处理可以在页面生成完后再进行 ...
- 菜鸟学SSH(十九)——提高用户体验之404处理
只要做过WEB开发人对于“404”已经再熟悉不过了吧.当我们访问的资源不存在时,它就会跑出来跟你打招呼啦.但是默认情况下,404页面比较简陋,不是很友好.而且一般用户不知道404是个神马东东,还以为是 ...
- 文盘Rust -- struct 中的生命周期
最近在用rust 写一个redis的数据校验工具.redis-rs中具备 redis::ConnectionLike trait,借助它可以较好的来抽象校验过程.在开发中,不免要定义struct 中的 ...
- 文盘Rust -- rust 连接云上数仓 starwift
作者:京东云 贾世闻 最近想看看 rust 如何集成 clickhouse,又犯了好吃懒做的心理(不想自己建环境),刚好京东云发布了兼容ck 的云原生数仓 Starwfit,于是搞了个实例折腾一番. ...
- 文盘Rust -- 给程序加个日志
作者:贾世闻 日志是应用程序的重要组成部分.无论是服务端程序还是客户端程序都需要日志做为错误输出或者业务记录.在这篇文章中,我们结合[log4rs](https://github.com/estk/l ...
- 文盘Rust -- 本地库引发的依赖冲突
作者:京东科技 贾世闻 问题描述 clickhouse 的原生 rust 客户端目前比较好的有两个clickhouse-rs 和 clickhouse.rs .clickhouse-rs 是 tcp ...
- 文盘Rust -- 用Tokio实现简易任务池
作者:京东科技 贾世闻 Tokio 无疑是 Rust 世界中最优秀的异步Runtime实现.非阻塞的特性带来了优异的性能,但是在实际的开发中我们往往需要在某些情况下阻塞任务来实现某些功能. 我们看看下 ...
随机推荐
- Linux,会这些就够了
在测试当中,其实对Linux的要求不高,我们在工作中需要记住常用的一些命令,不常用的实际用到的时候再查在记即可,最重要我们要使用命令可以查看日志,定位bug 目录篇: 可用 pwd 命令查看用 ...
- 常见 Linux 提权
Common Linux Privesc 记录 常见 Linux 提权的 提权方式:水平提权,垂直提权. 水平提权:这是您通过接管与您处于相同权限级别的不同用户来扩大您对受感染系统的影响的地方. 例如 ...
- CKS 考试题整理 (07)-RBAC - RoleBinding
Context 绑定到 Pod 的 ServiceAccount 的 Role 授予过度宽松的权限,完成以下项目以减少权限集. Task 一个名为 web-pod 的现有 Pod 已在 namespa ...
- Python潮流周刊#7:我讨厌用 asyncio
你好,我是猫哥.这里记录每周值得分享的 Python 及通用技术内容,部分为英文,已在小标题注明.(标题取自其中一则分享,不代表全部内容都是该主题,特此声明.) 首发于我的博客:https://pyt ...
- CSS3学习记录之loading动画
loading动画就是在加载一些网页内容的时候呈现出来的小动画,记录一下学到的几种loading动画: 效果:http://39.105.101.122/myhtml/CSS/Loading/load ...
- 微服务bug之:openFeign远程调用返回类型转换异常
楼主是在使用easyexcel导出的时候,获取数据出现这个错误,因为Spring底层是这样处理的使用LinkedhashMap来承接查询结果,导致转换异常 public List<NeedAll ...
- 【WALT】scale_exec_time() 代码详解
@ 目录 [WALT]scale_exec_time() 代码详解 代码展示 代码逻辑: 为什么归一化? ⑴ 将 CPU cycles 转换为 CPU 当前频率 ⑵ 归一化 delta [WALT]s ...
- 多光源渲染方案 - Light Culling
目录 Tile-based Light Culling Culling 流程 Injection Pass Compact Pass 2.5D Culling Cluster-based Light ...
- 数据标注工具 Label-Studio
文档抽取任务Label Studio使用指南 目录 1. 安装 2. 文档抽取任务标注 2.1 项目创建 2.2 数据上传 2.3 标签构建 2.4 任务标注 2.5 数据导出 2.6 数据转换 2. ...
- Linux实现双击自动安装deb文件
在Linux中,有几个常用的软件可以实现双击自动安装.deb文件的功能.以下是一些推荐的软件: 1. GDebi:GDebi是一个轻量级的图形化工具,专门用于安装.deb文件.它可以解决依赖关系,并提 ...