文盤 Rust——起手式,CLI 程序
學習一門計算機語言和學習人類語言有很多共通之處。我們學習人類語言是從單個的詞開始,然後是簡單句子,通過不斷的與他人交互練習掌握語法和語言習慣。當熟練到一定程度就可以表達思想。計算的語言也差不多,熟悉關鍵詞,基本邏輯,標準庫,寫應用。只是溝通的對象是機器而已。
既然是學就不能在開始搞的太難。學習本來就是個艱苦的差事。上來就幹特別複雜的事情往往會堅持不下去。天下難事必做於易,從簡入繁,從易到難,方爲正道。
先聊聊最簡單的 CLI(Command Line Interface) 程序。其實我們每學習一門語言的 hello world 程序就是 CLI,只是沒那麼多交互而已。
做命令行程序最繁瑣的事情是處理交互。交互大體分兩種。一種是我們最熟悉 shell 下的交互模式,每次一個命令,配合參數實現一次處理返回一組結果。這種模式處理起來比較容易 Rust 也有相當優秀的第三方 lib (clap)。第二種是領域交互,就像我是使用 MySql 或者 redis 的客戶端程序。這種程序可以玩兒的東西就比較多了像如何實現交互,如何來做子命令的提示。這些東西 clap 並沒有提供,需要我們自己來實現。
interactcli-rs 是我在工作過程中做的一個交互模式命令行腳手架。實現了一些常用功能。
下面我們來看看如何通過幾個步驟快速的實現一個功能相對齊全的 CLI 程序。和做飯一樣,能夠快速獲得成就感的方式是找半成品直接下鍋炒一盤:)。
下面我們具體看看,如何通過 interactcli-rs 實現一個功能齊全的命令行程序
來點感性認識
先把項目 clone 下來運行個例子
-
clone 項目
git clone https://github.com/jiashiwen/interactcli-rs.git cd interactcli-rs
-
命令行模式
cargo run requestsample baidu
-
交互模式
cargo run -- -i interact-rs> requestsample baidu
運行上面的命令是通過 http 來請求百度
四步做個 CLI
首先我們先來看看框架的目錄結構
.
├── examples
├── log
├── logs
└── src
├── cmd
├── commons
├── configure
├── interact
├── logger
└── request
cmd 目錄是我們做自己功能時要動的主要目錄,下面我們一步一步的實現 requestsample 命令。
-
定義命令 cmd 模塊用於定義命令以及相關子命令, requestsample.rs 中定義了訪問百度的命令
use clap::Command; pub fn new_requestsample_cmd() -> Command<'static> { clap::Command::new("requestsample") .about("requestsample") .subcommand(get_baidu_cmd()) } pub fn get_baidu_cmd() -> Command<'static> { clap::Command::new("baidu").about("request www.baidu.com") }
new_requestsample_cmd 函數定義了命令 "requestsample",get_baidu_cmd 函數定義了 requestsample 的子命令 baidu
-
註冊命令 src/cmd/rootcmd.rs 文件中定義了命令樹,可以在此註冊定義好的子命令
lazy_static! { static ref CLIAPP: clap::Command<'static> = clap::Command::new("interact-rs") .version("1.0") .author("Your Name. <yourmail@xxx.com>") .about("command line sample") .arg_required_else_help(true) .arg( Arg::new("config") .short('c') .long("config") .value_name("FILE") .help("Sets a custom config file") .takes_value(true) ) .arg( Arg::new("daemon") .short('d') .long("daemon") .help("run as daemon") ) .arg( Arg::new("interact") .short('i') .long("interact") .conflicts_with("daemon") .help("run as interact mod") ) .arg( Arg::new("v") .short('v') .multiple_occurrences(true) .takes_value(true) .help("Sets the level of verbosity") ) .subcommand(new_requestsample_cmd()) .subcommand(new_config_cmd()) .subcommand(new_multi_cmd()) .subcommand(new_task_cmd()) .subcommand(new_loop_cmd()) .subcommand( clap::Command::new("test") .about("controls testing features") .version("1.3") .author("Someone E. <someone_else@other.com>") .arg( Arg::new("debug") .short('d') .help("print debug information verbosely") ) ); static ref SUBCMDS: Vec<SubCmd> = subcommands(); } pub fn run_app() { let matches = CLIAPP.clone().get_matches(); if let Some(c) = matches.value_of("config") { println!("config path is:{}", c); set_config_file_path(c.to_string()); } set_config(&get_config_file_path()); cmd_match(&matches); } pub fn run_from(args: Vec<String>) { match clap_Command::try_get_matches_from(CLIAPP.to_owned(), args.clone()) { Ok(matches) => { cmd_match(&matches); } Err(err) => { err.print().expect("Error writing Error"); } }; }
定義好的命令不需其他處理,框架會在系統運行時生成子命令樹,用於在領域交互模式下命令提示的支持
-
命令解析 src/cmd/rootcmd.rs 中的 cmd_match 負責解析命令,可以把解析邏輯寫在該函數中
fn cmd_match(matches: &ArgMatches) { if let Some(ref matches) = matches.subcommand_matches("requestsample") { if let Some(_) = matches.subcommand_matches("baidu") { let rt = tokio::runtime::Runtime::new().unwrap(); let async_req = async { let result = req::get_baidu().await; println!("{:?}", result); }; rt.block_on(async_req); }; } }
-
修改交互模式的命令提示 提示符可以在 src/interact/cli.rs 中定義
pub fn run() { ... loop { let p = format!("{}> ", "interact-rs"); rl.helper_mut().expect("No helper").colored_prompt = format!("\x1b[1;32m{}\x1b[0m", p); ... } ... }
下次爲大家介紹一下 interactcli-rs 各種功能是如何實現的。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/_RbxNcaRBQCLicLjCgmOtw