文盘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实现.非阻塞的特性带来了优异的性能,但是在实际的开发中我们往往需要在某些情况下阻塞任务来实现某些功能. 我们看看下 ...
随机推荐
- cv学习总结(10.31-11.6)
这一周主要焦点在于实现反向传播和全连接两层神经网络的具体代码以及书写博客记录课程学习的心得体会,目前完成了反向传播的具体代码以及相应博客的书写,完成了assignment1中figure的SVM版提取 ...
- 腾讯云 cloudbase 云开发使用笔记
产品概述 云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为开发者提供高可用.自动弹性扩缩的后端云服务,包含计算.存储.托管等 serverless ...
- S32DS学习日志:debug文件和烧录的.hex文件
工程导入之后先clean一下,重新编译生成的文件默认在Production文件下面,得重新设置 折腾半天用jlink烧录没反应,原来是这里错了. production下的文件是用来用来集成bootlo ...
- 区块链的Token机制如何理解?
区块链的Token机制如何理解? 为了更好的理解区块链和Token的关系,今天专门基于互联网中的内容,做了下筛选过滤,从而可以让大家更好的理解,对于Token,如果是从事过开发的同学来说,比如容易理解 ...
- Open AI ChatGPT Prompt 学习之基础篇
碎碎念 2023 年,最火的可能就是 openAI 了,其组织代表的产品 chatGTP,相信大家已经有所耳闻.不少同学已经开始着手使用,并截图晒出 ChatGPT 是多么得智能与神奇.而有的同学在使 ...
- Grafana系列-GaC-1-Grafana即代码的几种实现方式
系列文章 Grafana 系列文章 Terraform 系列文章 概述 GaC(Grafana as Code, Grafana 即代码) 很明显是扩展自 IaC(Infrastructure as ...
- 三路快排Java版(图文并茂思路分析)
快速排序 这里我们直接开始讲相对的最优解 带随机数的三路快排 好了,中间还有很多版本的快排,但是都有一些问题导致在某种极端情况下造成耗费时间极多. 基础快排:在序列本身有序的情况下复杂度为O(n²) ...
- Blazor前后端框架Known-V1.2.1
V1.2.1 Known是基于C#和Blazor开发的前后端分离快速开发框架,开箱即用,跨平台,一处代码,多处运行. 概述 基于C#和Blazor实现的快速开发框架,前后端分离,开箱即用. 跨平台,单 ...
- 一个C#跨平台的机器视觉和机器学习的开源库
大家都知道OpenCV是一个跨平台的机器视觉和机器学习的开源库,可以运行在Linux.Windows.Android和Mac OS操作系统上,由C++开发. 今天给大家介绍一个用C#对OpenCV封装 ...
- 微信小程序 npm包、全局数据共享、分包
[黑马程序员前端微信小程序开发教程,微信小程序从基础到发布全流程_企业级商城实战(含uni-app项目多端部署)] https://www.bilibili.com/video/BV1834y1676 ...