蘇中建設南京區(qū)域公司上海網(wǎng)站營銷seo電話
上次我們聊到 CLI 的領域交互模式。在領域交互模式中,可能存在多層次的子命令。在使用過程中如果全評記憶的話,命令少還好,多了真心記不住。頻繁 --help 也是個很麻煩的事情。如果每次按 ‘tab’ 鍵就可以提示或補齊命令是不是很方便呢。這一節(jié)我們就來說說 ‘a(chǎn)utocommplete’ 如何實現(xiàn)。我們還是以interactcli-rs中的實現(xiàn)來解說實現(xiàn)過程
實現(xiàn)過程
其實,rustyline 已經(jīng)為我們提供了基本的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 結構體, 需要實現(xiàn) Completer + Hinter + Highlighter + Validator trait。然后通過rustyline的set_helper函數(shù)加載我們定義好的helper。在MyHelper 結構體中,需要我們自己來實現(xiàn)completer的邏輯。
Sub command autocompleter實現(xiàn)詳解
- SubCmd 結構體
#[derive(Debug,?Clone)]
pub?struct?SubCmd?{pub?level:?usize,pub?command_name:?String,pub?subcommands:?Vec<String>,
}
SubCmd 結構體包含:命令級別,命令名稱,以及該命令包含的子命令信息,以便在實現(xiàn)實現(xiàn) autocomplete 時定位命令和子命令的范圍
- 在程序啟動時遍歷所有的command,src/cmd/rootcmd.rs 中的all_subcommand函數(shù)負責收集所有命令并轉換為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 的實現(xiàn)部分比較多,大致包括兩個部分,前一部分包括:獲取某一級別下所有可能的子命令、獲取某級別下某字符串開頭的子命令、獲取某級別下某個命令的所有子命令,等基本功能。這部分代碼中有注釋就不一一累述。
函數(shù)complete_cmd用來計算行中的位置以及在該位置的替換內容。
輸入項是命令行的內容以及光標所在位置,輸出項為在該位置需要替換的內容。比如,我們在提示符下輸入 “root cm” root 下包含 cmd1、cmd2 兩個子命令,此時如果按 'tab’鍵,complete_cmd 函數(shù)就會返回 (7,[cmd1,cmd2])。
作者:京東科技?賈世聞
來源:京東云開發(fā)者社區(qū) 轉載請注明來源