写点什么

文盘 Rust——起手式,CLI 程序 | 京东云技术团队

  • 2023-09-07
    北京
  • 本文字数:3009 字

    阅读完需:约 10 分钟

文盘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. ")            .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. ")                    .arg(                        Arg::new("debug")                            .short('d')                            .help("print debug information verbosely")                    )            );        static ref SUBCMDS: Vec = 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) {        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 各种功能是如何实现的。


作者:京东科技 贾世闻

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

发布于: 5 小时前阅读数: 2
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
文盘Rust——起手式,CLI程序 | 京东云技术团队_cli_京东科技开发者_InfoQ写作社区