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

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

实现过程

其实,rustyline 已经为我们提供了基本的helper功能框架,其中包括了completer。我们来看代码,文件位置src/interact/cli.rs

1#[derive(Helper)] 2struct MyHelper { 3    completer: CommandCompleter, 4    highlighter: MatchingBracketHighlighter, 5    validator: MatchingBracketValidator, 6    hinter: HistoryHinter, 7    colored_prompt: String, 8} 9 10pub fn run() { 11    let config = Config::builder() 12        .history_ignore_space(true) 13        .completion_type(CompletionType::List) 14        .output_stream(OutputStreamType::Stdout) 15        .build(); 16 17    let h = MyHelper { 18        completer: get_command_completer(), 19        highlighter: MatchingBracketHighlighter::new(), 20        hinter: HistoryHinter {}, 21        colored_prompt: "".to_owned(), 22        validator: MatchingBracketValidator::new(), 23    }; 24 25    let mut rl = Editor::with_config(config); 26    // let mut rl = Editor::<()>::new(); 27    rl.set_helper(Some(h)); 28 29    ...... 30 31} 32 33 34

首先定义 MyHelper 结构体, 需要实现 Completer + Hinter + Highlighter + Validator trait。然后通过rustyline的set_helper函数加载我们定义好的helper。在MyHelper 结构体中,需要我们自己来实现completer的逻辑。

Sub command autocompleter实现详解

  • SubCmd 结构体
1#[derive(Debug, Clone)] 2pub struct SubCmd { 3    pub level: usize, 4    pub command_name: String, 5    pub subcommands: Vec<String>, 6} 7 8 9

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

  • 在程序启动时遍历所有的command,src/cmd/rootcmd.rs 中的all_subcommand函数负责收集所有命令并转换为Vec<SubCmd>
1pub fn all_subcommand(app: &clap_Command, beginlevel: usize, input: &mut Vec<SubCmd>) { 2  let nextlevel = beginlevel + 1; 3  let mut subcmds = vec![]; 4  for iterm in app.get_subcommands() { 5      subcmds.push(iterm.get_name().to_string()); 6      if iterm.has_subcommands() { 7          all_subcommand(iterm, nextlevel, input); 8      } else { 9          if beginlevel == 0 { 10              all_subcommand(iterm, nextlevel, input); 11          } 12      } 13  } 14  let subcommand = SubCmd { 15      level: beginlevel, 16      command_name: app.get_name().to_string(), 17      subcommands: subcmds, 18  }; 19  input.push(subcommand); 20} 21 22 23
  • CommandCompleter 子命令自动补充功能的核心部分
1#[derive(Debug, Clone)] 2pub struct CommandCompleter { 3    subcommands: Vec<SubCmd>, 4} 5 6impl CommandCompleter { 7    pub fn new(subcmds: Vec<SubCmd>) -> Self { 8        Self { 9            subcommands: subcmds, 10        } 11    } 12 13    //获取level下所有可能的子命令 14    pub fn level_possible_cmd(&self, level: usize) ->   Vec<String> { 15        let mut subcmds = vec![]; 16        let cmds = self.subcommands.clone(); 17        for iterm in cmds { 18            if iterm.level == level { 19                subcmds.push(iterm.command_name.clone()); 20            } 21        } 22        return subcmds; 23    } 24    //获取level下某字符串开头的子命令 25    pub fn level_prefix_possible_cmd(&self, level: usize,   prefix: &str) -> Vec<String> { 26        let mut subcmds = vec![]; 27        let cmds = self.subcommands.clone(); 28        for iterm in cmds { 29            if iterm.level == level && iterm.command_name.  starts_with(prefix) { 30                subcmds.push(iterm.command_name); 31            } 32        } 33        return subcmds; 34    } 35 36    //获取某level 下某subcommand的所有子命令 37    pub fn level_cmd_possible_sub_cmd(&self, level:   usize, cmd: String) -> Vec<String> { 38        let mut subcmds = vec![]; 39        let cmds = self.subcommands.clone(); 40        for iterm in cmds { 41            if iterm.level == level && iterm.command_name   == cmd { 42                subcmds = iterm.subcommands.clone(); 43            } 44        } 45        return subcmds; 46    } 47 48    //获取某level 下某subcommand的所有prefix子命令 49    pub fn level_cmd_possible_prefix_sub_cmd( 50        &self, 51        level: usize, 52        cmd: String, 53        prefix: &str, 54    ) -> Vec<String> { 55        let mut subcmds = vec![]; 56        let cmds = self.subcommands.clone(); 57        for iterm in cmds { 58            if iterm.level == level && iterm.command_name   == cmd { 59                for i in iterm.subcommands { 60                    if i.starts_with(prefix) { 61                        subcmds.push(i); 62                    } 63                } 64            } 65        } 66        return subcmds; 67    } 68 69    pub fn complete_cmd(&self, line: &str, pos: usize) ->   Result<(usize, Vec<Pair>)> { 70        let mut entries: Vec<Pair> = Vec::new(); 71        let d: Vec<_> = line.split(' ').collect(); 72 73        if d.len() == 1 { 74            if d.last() == Some(&"") { 75                for str in self.level_possible_cmd(1) { 76                    let mut replace = str.clone(); 77                    replace.push_str(" "); 78                    entries.push(Pair { 79                        display: str.clone(), 80                        replacement: replace, 81                    }); 82                } 83                return Ok((pos, entries)); 84            } 85 86            if let Some(last) = d.last() { 87                for str in self.level_prefix_possible_cmd  (1, *last) { 88                    let mut replace = str.clone(); 89                    replace.push_str(" "); 90                    entries.push(Pair { 91                        display: str.clone(), 92                        replacement: replace, 93                    }); 94                } 95                return Ok((pos - last.len(), entries)); 96            } 97        } 98 99        if d.last() == Some(&"") { 100            for str in self 101                .level_cmd_possible_sub_cmd(d.len() - 1,   d.get(d.len() - 2).unwrap().to_string()) 102            { 103                let mut replace = str.clone(); 104                replace.push_str(" "); 105                entries.push(Pair { 106                    display: str.clone(), 107                    replacement: replace, 108                }); 109            } 110            return Ok((pos, entries)); 111        } 112 113        if let Some(last) = d.last() { 114            for str in self.  level_cmd_possible_prefix_sub_cmd( 115                d.len() - 1, 116                d.get(d.len() - 2).unwrap().to_string(), 117                *last, 118            ) { 119                let mut replace = str.clone(); 120                replace.push_str(" "); 121                entries.push(Pair { 122                    display: str.clone(), 123                    replacement: replace, 124                }); 125            } 126            return Ok((pos - last.len(), entries)); 127        } 128 129        Ok((pos, entries)) 130    } 131} 132 133impl Completer for CommandCompleter { 134    type Candidate = Pair; 135 136    fn complete(&self, line: &str, pos: usize, _ctx: &  Context<'_>) -> Result<(usize, Vec<Pair>)> { 137        self.complete_cmd(line, pos) 138    } 139} 140 141 142

CommandCompleter 的实现部分比较多,大致包括两个部分,前一部分包括:获取某一级别下所有可能的子命令、获取某级别下某字符串开头的子命令、获取某级别下某个命令的所有子命令,等基本功能。这部分代码中有注释就不一一累述。

函数complete_cmd用来计算行中的位置以及在该位置的替换内容。

输入项是命令行的内容以及光标所在位置,输出项为在该位置需要替换的内容。比如,我们在提示符下输入 "root cm" root 下包含 cmd1、cmd2 两个子命令,此时如果按 'tab'键,complete_cmd 函数就会返回 (7,[cmd1,cmd2])。

作者:京东科技 贾世闻

来源:京东云开发者社区 转载请注明来源

点赞
收藏

评论区

加载中...

相关推荐

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

文盘Rust -- 领域交互模式如何实现

书接上文,上回说到如何通过interactclirs四步实现一个命令行程序。但是shell交互模式在有些场景下用户体验并不是很好。比如我们要连接某个服务,比如mysql或者redis这样的服务。如果每次交互都需要输入地址、端口、用户名等信息,交互起来太麻烦。通常的做法是一次性输入和连接相关的信息或者由统一配置文件进行管理,然后进入领域交互模式,所有的命令和反馈都和该领域相关。interactclirs通过i参数实现领域交互模式。这回我们探索一下这一模式是如何实现的。

vi 编译器使用

打开/创建文件bashvitest.py打开后进行修改注:下面的命令需要使用英文输入法。1、需要按行快速移动光标时,可以使用键盘上的编辑键Home,快速将光标移动至当前行的行首。除此之外,也可以在命令模式中使用快捷键"^"(即Shift6)或0(数字0)。2、如果要快速移动光标至当前行的行尾,可以使用编辑键End。也可以在命令模式中使用快

FLV文件格式

1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.