乐趣区

关于cli:文盘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 各种性能是如何实现的。

作者:京东科技 贾世闻

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

退出移动版