xcli,一個(gè)簡單易用的命令行工具

項(xiàng)目地址:https://github.com/kingwel-xie/xcli-rs

xcli是一個(gè)命令行的工具章贞,支持自定義添加命令备埃,每個(gè)命令支持縮寫使用尼桶,同時(shí)也支持tab方式補(bǔ)全命令。

設(shè)計(jì)思路

這個(gè)工具的設(shè)計(jì)初衷是為了能夠提供命令行功能搀庶,同時(shí)可以很容易的添加自定義的命令拐纱。

應(yīng)用場景

目前在libp2p-rs中,xcli提供了命令行的功能哥倔,可對swarm和kad進(jìn)行調(diào)試秸架。

類型轉(zhuǎn)換

從命令行中獲取到的參數(shù)args是一個(gè)引用類型的&str數(shù)組,即&[&str]咆蒿。在xcli中东抹,實(shí)現(xiàn)了一個(gè)名為check_param的宏蚂子,返回的值即為想要轉(zhuǎn)換的對應(yīng)類型。check_param!需要四個(gè)參數(shù)

($param_count:expr, $required:expr, $args:ident, ($($change_type:ty=>$has_from:expr), *))

分別代表參數(shù)總個(gè)數(shù)缭黔、必選參數(shù)個(gè)數(shù)食茎,參數(shù)列表,最后一個(gè)參數(shù)比較特殊馏谨,代表著需要轉(zhuǎn)換的類型别渔。書寫格式形如(String=>1),對于所有輸入?yún)?shù)都需要設(shè)置該轉(zhuǎn)換類型惧互。

需要注意的一點(diǎn)是哎媚,參數(shù)的總個(gè)數(shù)必須與最后的參數(shù)轉(zhuǎn)換類型個(gè)數(shù)相同。譬如總共有5個(gè)參數(shù)喊儡,那么后面的類型轉(zhuǎn)換也需要將這五個(gè)參數(shù)的類型都進(jìn)行設(shè)置拨与。

舉例說明:

let u = check_param!(3, 1, args, (String=>1, String=>1, String=>1))

這段代碼表示總共需要三個(gè)參數(shù),其中一個(gè)是必須的艾猜,另外兩個(gè)是可選的买喧,這三個(gè)參數(shù)都是String類型的。返回值的個(gè)數(shù)最少是1個(gè)(必須參數(shù)一定返回)箩朴,最多是3個(gè)岗喉。

命令補(bǔ)全

由于底層庫使用的是rustyline秋度,它提供了一個(gè)Completer的trait炸庞,實(shí)現(xiàn)fn complete()即可支持tab補(bǔ)全。

在App::run()中荚斯,我們對Command執(zhí)行了一個(gè)方法:

self.rl.borrow_mut().set_helper(Some(PrefixCompleter::new(&self.tree)));

這段代碼的邏輯是將Command單獨(dú)抽離出來形成一個(gè)類似樹的結(jié)構(gòu)埠居。
以下這段代碼是補(bǔ)全功能的核心:

  1. 初始化返回的vector,偏移量事期,下一個(gè)節(jié)點(diǎn)
  2. 循環(huán)當(dāng)前節(jié)點(diǎn)的子命令節(jié)點(diǎn)
    1. 如果輸入的字符串長度大于等于子命令的長度
      1. 字符串開頭是子命令的名稱
        1. 字符串長度與子命令長度相等滥壕,vector加一個(gè)空格
        2. 不相等,將子命令添加到vector中
      2. 記錄子命令長度為偏移量兽泣,將子命令標(biāo)記為下一個(gè)遞歸的起始節(jié)點(diǎn)绎橘。
    2. 如果子命令的開頭與字符串匹配
      1. vector添加字符串,記錄偏移量唠倦,標(biāo)記子命令為下一個(gè)遞歸起始節(jié)點(diǎn)
  3. 如果vector不止一個(gè)數(shù)據(jù)称鳞,說明有多個(gè)匹配的命令,直接返回
  4. 如果滿足執(zhí)行子命令的遞歸情況稠鼻,從字符串的偏移量位開始繼續(xù)執(zhí)行tab completion.
    pub fn _complete_cmd(node: &PrefixNode, line: &str, pos: usize) -> Vec<String> {
        debug!("cli to complete {} for node {}", line, node.name);
        let line = line[..pos].trim_start();
        let mut go_next = false;

        let mut new_line: Vec<String> = vec![];
        let mut offset: usize = 0;
        let mut next_node = None;

        //var lineCompleter PrefixCompleterInterface
        for child in &node.children {
            //debug!("try node {}", child.name);
            if line.len() >= child.name.len() {
                if line.starts_with(&child.name) {
                    if line.len() == child.name.len() {
                        // add a fack new_line " "
                        new_line.push(" ".to_string());
                    } else {
                        new_line.push(child.name.to_string());
                    }
                    offset = child.name.len();
                    next_node = Some(child);

                    // may go next level
                    go_next = true;
                }
            } else if child.name.starts_with(line) {
                new_line.push(child.name[line.len()..].to_string());
                offset = line.len();
                next_node = Some(child);
            }
        }

        // more than 1 candidates?
        if new_line.len() != 1 {
            debug!("offset={}, candidates={:?}", offset, new_line);
            return new_line;
        }

        if go_next {
            let line = line[offset..].trim_start();
            return PrefixCompleter::_complete_cmd(next_node.unwrap(), line, line.len());
        }

        debug!("offset={}, nl={:?}", offset, new_line);
        new_line
    }

使用方法

以help命令為例冈止,實(shí)現(xiàn)一個(gè)顯示可用命令的功能:

    app.add_subcommand(
        Command::new_with_alias("help", "h")
            .about("displays help information")
            .usage("help [command]")
            .action(cli_help)),
    );
    
    /// Action of help command
    fn cli_help(app: &App, args: &[&str]) -> XcliResult {
        if args.is_empty() {
            app.tree.show_subcommand_help();
        } else if let Some(cmd) = app.tree.locate_subcommand(args) {
            cmd.show_command_help();
        } else {
            println!("Unrecognized command {:?}", args)
        }
        Ok(CmdExeCode::Ok)
    }

調(diào)用add_subcommand()向cli實(shí)例中添加一個(gè)help命令,action方法參數(shù)是一個(gè)返回值為XcliResult的fn候齿。XcliResult是一個(gè)T為CmdExeCode熙暴,E為XcliError的Result類型:

pub type XcliResult = stdResult<CmdExeCode, XcliError>;

在這里我們定義了cli_help函數(shù)闺属,正常運(yùn)行時(shí)返回值為Ok。實(shí)現(xiàn)的命令效果如圖所示:


image

userdata

add_subcommand_with_userdata()是在v0.5.0新增支持的一個(gè)方法周霉。有時(shí)候使用者可能希望測試一些自定義的數(shù)據(jù)結(jié)構(gòu)掂器,這個(gè)方法可以支持用戶注冊自己的數(shù)據(jù)到xcli中,后續(xù)可以通過命令行的方式進(jìn)行調(diào)試俱箱。方法聲明如下:

pub fn add_subcommand_with_userdata(&mut self, subcmd: Command<'a>, value: IAny) {
    self.handlers.insert(subcmd.name.clone(), value);
    self.tree.subcommands.push(subcmd);
}

這段代碼的邏輯是將value添加到全局的handler中唉匾,handler是一個(gè)HashMap,key為命令名稱匠楚,value是傳入的IAny類型值巍膘。

方法的第一個(gè)參數(shù)是Command,定義了命令的名稱芋簿、子命令峡懈、對應(yīng)的執(zhí)行函數(shù)等等屬性;第二個(gè)參數(shù)是相關(guān)的用戶數(shù)據(jù)与斤,IAny是Box<dyn std::any::Any>肪康,意味著可以放入絕大多數(shù)的自定義類型參數(shù)。

使用的邏輯也較為簡單撩穿,以下是示例代碼:

    app.add_subcommand_with_userdata(
        Command::new_with_alias("userdata", "ud")
            .about("controls testing features")
            .action(|app, _args| -> XcliResult {
                let data_any = app.get_handler("userdata").unwrap();

                let data = data_any.downcast_ref::<usize>().expect("usize");

                println!("userdata = {}", data);
                Ok(CmdExeCode::Ok)
            }),
        Box::new(100usize)
    );

在這里磷支,我們注冊了一個(gè)叫userdata的子命令,其中value設(shè)置為了100食寡。執(zhí)行userdata命令時(shí)雾狈,從handler中取出userdata對應(yīng)的值,downcast_ref解析出usize抵皱,再進(jìn)行println善榛。實(shí)現(xiàn)效果如圖所示:


image

libp2p-rs中的使用

由于userdata命令的存在,我們可以使用自己的數(shù)據(jù)去定義子命令呻畸。例如在libp2p-rs中移盆,提供有swarm和kad的controller與主循環(huán)交互,因此我們可以用這兩個(gè)controller去定義命令:

app.add_subcommand_with_userdata(swarm_cli_commands(), Box::new(swarm_control.clone()));
app.add_subcommand_with_userdata(dht_cli_commands(), Box::new(kad_control.clone()));

實(shí)現(xiàn)效果圖:

  1. swarm peer伤为,無參即展示當(dāng)前連接peer


    image
  2. swarm peer咒循,有參顯示對應(yīng)peer信息


    image
  3. dht stats顯示當(dāng)前狀態(tài)


    image

Netwarps 由國內(nèi)資深的云計(jì)算和分布式技術(shù)開發(fā)團(tuán)隊(duì)組成,該團(tuán)隊(duì)在金融绞愚、電力叙甸、通信及互聯(lián)網(wǎng)行業(yè)有非常豐富的落地經(jīng)驗(yàn)。Netwarps 目前在深圳爽醋、北京均設(shè)立了研發(fā)中心蚁署,團(tuán)隊(duì)規(guī)模30+,其中大部分為具備十年以上開發(fā)經(jīng)驗(yàn)的技術(shù)人員蚂四,分別來自互聯(lián)網(wǎng)光戈、金融哪痰、云計(jì)算、區(qū)塊鏈以及科研機(jī)構(gòu)等專業(yè)領(lǐng)域久妆。Netwarps 專注于安全存儲(chǔ)技術(shù)產(chǎn)品的研發(fā)與應(yīng)用晌杰,主要產(chǎn)品有去中心化文件系統(tǒng)(DFS)、去中心化計(jì)算平臺(tái)(DCP)筷弦,致力于提供基于去中心化網(wǎng)絡(luò)技術(shù)實(shí)現(xiàn)的分布式存儲(chǔ)和分布式計(jì)算平臺(tái)肋演,具有高可用、低功耗和低網(wǎng)絡(luò)的技術(shù)特點(diǎn)烂琴,適用于物聯(lián)網(wǎng)爹殊、工業(yè)互聯(lián)網(wǎng)等場景。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奸绷,一起剝皮案震驚了整個(gè)濱河市梗夸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌号醉,老刑警劉巖反症,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異畔派,居然都是意外死亡铅碍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門线椰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胞谈,“玉大人,你說我怎么就攤上這事士嚎∥仄牵” “怎么了悔叽?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵莱衩,是天一觀的道長。 經(jīng)常有香客問我娇澎,道長笨蚁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任趟庄,我火速辦了婚禮括细,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘戚啥。我一直安慰自己奋单,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布猫十。 她就那樣靜靜地躺著览濒,像睡著了一般呆盖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贷笛,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天应又,我揣著相機(jī)與錄音,去河邊找鬼乏苦。 笑死株扛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的汇荐。 我是一名探鬼主播洞就,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼掀淘!你這毒婦竟也來了奖磁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤繁疤,失蹤者是張志新(化名)和其女友劉穎咖为,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稠腊,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡躁染,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了架忌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吞彤。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖叹放,靈堂內(nèi)的尸體忽然破棺而出饰恕,到底是詐尸還是另有隱情,我是刑警寧澤井仰,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布埋嵌,位于F島的核電站,受9級特大地震影響俱恶,放射性物質(zhì)發(fā)生泄漏雹嗦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一合是、第九天 我趴在偏房一處隱蔽的房頂上張望了罪。 院中可真熱鬧,春花似錦聪全、人聲如沸泊藕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽娃圆。三九已至汽久,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間踊餐,已是汗流浹背景醇。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吝岭,地道東北人三痰。 一個(gè)月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像窜管,于是被迫代替她去往敵國和親散劫。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容