上次我们聊到 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——子命令提示,提高用户体验的更多相关文章

  1. 巧用Ajax的beforeSend 提高用户体验--防止重复数据

    巧用Ajax的beforeSend 提高用户体验 jQuery是经常使用的一个开源js框架,其中的$.ajax请求中有一个beforeSend方法,用于在向服务器发送请求前执行一些动作.具体可参考jQ ...

  2. 前端如何实现图片懒加载(lazyload) 提高用户体验

    定义 图片懒加载又称图片延时加载.惰性加载,即在用户需要使用图片的时候加载,这样可以减少请求,节省带宽,提高页面加载速度,相对的,也能减少服务器压力. 惰性加载是程序人性化的一种体现,提高用户体验,防 ...

  3. 文盘Rust -- 把程序作为守护进程启动

    当我们写完一个服务端程序,需要上线部署的时候,或多或少都会和操作系统的守护进程打交道,毕竟谁也不希望shell关闭既停服.今天我们就来聊聊这个事儿. 最早大家部署应用的通常操作是 "nohu ...

  4. php fastcgi_finish_request让你的程序由等待时间,瞬间完成,提高用户体验

    当PHP运行在FastCGI模式时,PHP FPM提供了一个名为fastcgi_finish_request的方法.按照文档上的说法,此方法可以提高请求的处理速度,如果有些处理可以在页面生成完后再进行 ...

  5. 菜鸟学SSH(十九)——提高用户体验之404处理

    只要做过WEB开发人对于“404”已经再熟悉不过了吧.当我们访问的资源不存在时,它就会跑出来跟你打招呼啦.但是默认情况下,404页面比较简陋,不是很友好.而且一般用户不知道404是个神马东东,还以为是 ...

  6. 文盘Rust -- struct 中的生命周期

    最近在用rust 写一个redis的数据校验工具.redis-rs中具备 redis::ConnectionLike trait,借助它可以较好的来抽象校验过程.在开发中,不免要定义struct 中的 ...

  7. 文盘Rust -- rust 连接云上数仓 starwift

    作者:京东云 贾世闻 最近想看看 rust 如何集成 clickhouse,又犯了好吃懒做的心理(不想自己建环境),刚好京东云发布了兼容ck 的云原生数仓 Starwfit,于是搞了个实例折腾一番. ...

  8. 文盘Rust -- 给程序加个日志

    作者:贾世闻 日志是应用程序的重要组成部分.无论是服务端程序还是客户端程序都需要日志做为错误输出或者业务记录.在这篇文章中,我们结合[log4rs](https://github.com/estk/l ...

  9. 文盘Rust -- 本地库引发的依赖冲突

    作者:京东科技 贾世闻 问题描述 clickhouse 的原生 rust 客户端目前比较好的有两个clickhouse-rs 和 clickhouse.rs .clickhouse-rs 是 tcp ...

  10. 文盘Rust -- 用Tokio实现简易任务池

    作者:京东科技 贾世闻 Tokio 无疑是 Rust 世界中最优秀的异步Runtime实现.非阻塞的特性带来了优异的性能,但是在实际的开发中我们往往需要在某些情况下阻塞任务来实现某些功能. 我们看看下 ...

随机推荐

  1. 一分钟学一个 Linux 命令 - cd

    前言 大家好,我是 god23bin.欢迎来到这个系列,每天只需一分钟,记住一个 Linux 命令不成问题.今天让我们从 cd 命令开始,掌握在 Linux 系统中切换目录的技巧. 什么是 cd 命令 ...

  2. R画韦恩图之总结

    本文分享自微信公众号 - 生信科技爱好者(bioitee).如有侵权,请联系 support@oschina.cn 删除.本文参与"OSC源创计划",欢迎正在阅读的你也加入,一起分 ...

  3. SQL注入三连实战绕过WTS-WAF

    一键三连,sql注入 一次无意之间发现的sql注入,主要是因为有一个WTS-WAF,在此记录一下 只是友好测试,并非有意为之.... 牛刀小试1 手注 判断字段数 测试到order by 15的时候出 ...

  4. Linux服务器设置虚拟内存

    cd /usrsudo mkdir swapcd swapsudo dd if=/dev/zero of=/usr/swap/swapfile bs=1M count=4096du -sh /usr/ ...

  5. 【前端方案】-表格排序列LRU缓存方案

    目标: 排序后的表格列,页面刷新或者用户重新登录后,能够保持之前的操作排序 完成效果: 解决方案: 利用localstorage对排序后的表格列属性进行存储,页面刷新或者用户重新进入该页面时都先从lo ...

  6. element-ui中Select 选择器异步加载下一页

    场景 当我们使用 Select 选择器存放大量数据的时候. 会发现存在这么2个问题. 1.接口响应时间较长.(因为数据量较多,一次查询的所有)甚至有可能超时. 2.前端下拉框滑动卡顿. 这个时候们如何 ...

  7. 揭秘Spring依赖注入和SpEL表达式

    摘要:在本文中,我们深入探讨了Spring框架中的属性注入技术,包括setter注入.构造器注入.注解式属性注入,以及使用SpEL表达式进行属性注入. 本文分享自华为云社区<Spring高手之路 ...

  8. std::aligned_alloc

    定义于头文件 <cstdlib> (c++) void * aligned_alloc ( std::size_t alignment, std::size_t size); (c++17 ...

  9. facebook搜索中的EBR检索技术-学习笔记

    简介 本文是论文Embedding-based Retrieval in Facebook Search的学习笔记 FB的社交搜索 搜索无处不在,搜索的种类多种多样,谷歌.百度.bing的全网搜索.淘 ...

  10. 如何刷新 DNS 缓存 (macOS, Linux, Windows)

    如何刷新 DNS 缓存 (macOS, Linux, Windows) Unix Linux Windows 如何刷新 DNS 缓存 (macOS, FreeBSD, RHEL, CentOS, De ...