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

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

上次我们聊到 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,
}

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

在程序启动时遍历所有的 command,src/cmd/rootcmd.rs 中的 all_subcommand 函数负责收集所有命令并转换为 Vec
pub fn all_subcommand(app: &clap_Command, beginlevel: usize, input: &mut Vec) {
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,
}

impl CommandCompleter {
pub fn new(subcmds: Vec) -> 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])。

相关文章
|
2月前
|
Dart 前端开发 架构师
【01】vs-code如何配置flutter环境-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈-供大大的学习提升
【01】vs-code如何配置flutter环境-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈-供大大的学习提升
218 26
|
1月前
|
监控 关系型数据库 MySQL
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
32 0
|
7月前
|
存储 小程序 数据可视化
小程序开发问题之使用小程序云服务开发个人相册小程序如何解决
小程序开发问题之使用小程序云服务开发个人相册小程序如何解决
|
7月前
|
开发者 存储 API
Xamarin 开发者的社区资源概览:从官方文档到GitHub示例,全面探索提升开发技能与解决问题的多元化渠道与实用工具
【8月更文挑战第31天】Xamarin 开发者社区资源概览旨在提升开发效率与解决问题,涵盖官方文档、社区论坛、GitHub 项目等。官方文档详尽,涵盖 Xamarin.Forms 使用、性能优化等;社区论坛供交流心得;GitHub 提供示例代码。此外,第三方博客、视频教程及 Xamarin University 等资源也丰富多样,适合各阶段开发者学习与提升。通过综合利用这些资源,开发者可不断进步,应对技术挑战。
83 0
|
10月前
|
自然语言处理 iOS开发
海外短剧系统开发功能指南/案例设计/步骤方案/源码程序
The development of overseas short drama systems needs to consider the following main requirements
|
10月前
什么是元宇宙游戏系统开发案例介绍/方案步骤/需求功能/源码指南
Metaverse game system development refers to the creation of a complex system that combines virtual reality, blockchain technology, and game design, allowing players to immerse themselves in a virtual world and interact with other players, trade virtual assets, and more. This type of gaming system ty
|
存储 前端开发 JavaScript
潮玩元宇宙大逃杀app游戏系统开发稳定版/流程设计/功能步骤/需求逻辑/源码程序
需求分析:明确项目的整体目标和功能需求,包括游戏规则、玩法机制、图形界面、用户账号系统、实时交互等。
潮玩元宇宙大逃杀游戏系统开发稳定版/案例设计/详细功能/需求逻辑/源码项目
The development rules of virtual reality game systems, especially the metaverse escape game system, can vary depending on specific designs and requirements. The following are some common development rules and considerations that may include
|
开发框架 运维 测试技术
ARBT(阿尔比特)智能合约系统开发稳定版/详细案例/步骤逻辑/需求方案/成熟技术/源码架构
需求分析:与客户充分沟通,了解其业务需求和期望,明确系统的功能和性能要求。