文盘Rust——起手式,CLI程序

rust,起手,cli,程序 · 浏览次数 : 41

小编点评

**实现一个功能齐全的CLI程序的步骤:** **1. 定义命令模块** - 创建 `commands.rs` 文件,定义命令以及相关子命令。 **2. 实现领域交互模式** - 创建 `src/cmd/rootcmd.rs` 文件,定义命令树。 - 使用 `subcommands` 方法注册子命令。 **3. 定义交互模式的命令提示符** - 创建 `src/interact/cli.rs` 文件,定义 `run` 方法。 - 在 `run` 方法中设置交互模式的提示符。 **4. 实现命令解析** - 在 `src/cmd/rootcmd.rs` 中定义 `cmd_match` 方法。 - 使用 `subcommand_matches` 方法解析命令。 **5. 创建子命令树** - 在 `src/cmd/rootcmd.rs` 中使用 `subcommands` 注册子命令。 - 在 `cmd_match` 方法中递归调用自身方法。 **6. 实现交互模式** - 在 `src/interact/cli.rs` 中定义 `run` 方法。 - 在 `run` 方法中使用 `match` 语句处理子命令。 **7. 运行程序** - 通过 `run_app` 或 `run_from` 方法运行程序。 **示例代码:** **commands.rs** ```rust use clap::Command; fn get_baidu_cmd() -> Command { clap::Command::new("baidu").about("request www.baidu.com") } ``` **src/interact/cli.rs** ```rust use clap::Command; pub fn run() { let p = format!("\{}> \", \"interact-rs\"); rl.helper_mut().expect("No helper").colored_prompt = format!(\"\\x1b[1;32m{}\\x1b[0m\", p); } ``` **src/cmd/rootcmd.rs** ```rust use clap::Command; fn subcommands() -> Vec { let mut subcommands: Vec = vec![]; subcommands.push(get_baidu_cmd()); // 注册其他子命令 subcommands } ``` **运行程序:** ```bash cargo run ```

正文

技术的学习从不会到会的过程是最有意思的,也是体会最多的。一旦熟练了,知识变成了常识,可能就失去了记录学习过程的最佳时机。

在我看来学习一门计算机语言和学习人类语言有很多共通之处。我们学习人类语言是从单个的词开始,然后是简单句子,通过不断的与他人交互练习掌握语法和语言习惯。当熟练到一定程度就可以表达思想。计算的语言也差不多,熟悉关键词,基本逻辑,标准库,写应用。只是沟通的对象是机器而已。

既然是学就不能在开始搞的太难。学习本来就是个艰苦的差事。上来就干特别复杂的事情往往会坚持不下去。天下难事必做于易,从简入繁,从易到难,方为正道。

先聊聊最简单的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各种功能是如何实现的。

作者:京东科技 贾世闻

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

与文盘Rust——起手式,CLI程序相似的内容:

文盘Rust——起手式,CLI程序

我们来看看如何通过几个步骤快速的实现一个功能相对齐全的CLI程序。和做饭一样,能够快速获得成就感的方式是找半成品直接下锅炒一盘

文盘Rust -- 把程序作为守护进程启动

当我们写完一个服务端程序,需要上线部署的时候,或多或少都会和操作系统的守护进程打交道,毕竟谁也不希望shell关闭既停服。今天我们就来聊聊这个事儿。 最早大家部署应用的通常操作是 “nohup xxxx &”,别说像weblogic 或者其他java 容器有启动脚本,里面其实也差不多;很喜欢 ngi

文盘Rust -- r2d2 实现redis连接池

作者:贾世闻 我们在开发应用后端系统的时候经常要和各种数据库、缓存等资源打交道。这一期,我们聊聊如何访问redis 并将资源池化。 在一个应用后端程序访问redis主要要做的工作有两个,单例和池化。 在后端应用集成redis,我们主要用到以下几个crate:​ ​once_cell​​​、​ ​re

文盘Rust -- rust 连接云上数仓 starwift

最近想看看 rust 如何集成 clickhouse,又犯了好吃懒做的心理(不想自己建环境),刚好京东云发布了兼容ck 的云原生数仓 Starwfit,于是搞了个实例折腾一番。 Starwfit 是京东云自主研发的新一代云原生数据仓库,通过存算分离降低了存储成本,同时兼具性能和扩展弹性。其写入和查询速度可达到传统数据仓库的数倍,为用户提供实时数据分析能力。广泛应用于流量分析、精准营销、用户画像、广

文盘Rust -- 领域交互模式如何实现

书接上文,上回说到如何通过interactcli-rs四步实现一个命令行程序。但是 shell 交互模式在有些场景下用户体验并不是很好。比如我们要连接某个服务,比如 mysql 或者 redis 这样的服务。如果每次交互都需要输入地址、端口、用户名等信息,交互起来太麻烦。通常的做法是一次性输入和连接相关的信息或者由统一配置文件进行管理,然后进入领域交互模式,所有的命令和反馈都和该领域相关。inte

文盘Rust -- 本地库引发的依赖冲突

clickhouse 的原生 rust 客户端目前比较好的有两个clickhouse-rs 和 clickhouse.rs 。两个库在单独使用时没有任何问题,但是,在同一工程同时引用时会报错。本篇内容主要讲解如何用rust语言解决本地库引发的依赖冲突问题

文盘Rust -- 安全连接 TiDB/Mysql

最近在折腾rust与数据库集成,选了Tidb Cloud Serverless Tier 作为数据源。Tidb 无疑是近五年来最优秀的国产开源分布式数据库,Tidb Cloud Serverless Tier作为pingcap旗下的云产品方便又经济,这次使用还有一些小惊喜。

文盘Rust -- FFI 浅尝

rust FFI 是rust与其他语言互调的桥梁,通过FFI rust 可以有效继承 C 语言的历史资产。本期通过几个例子来聊聊rust与C 语言交互的具体步骤

文盘Rust -- Mutex解决并发写文件乱序问题

在实际开发过程中,我们可能会遇到并发写文件的场景,如果处理不当很可能出现文件内容乱序问题。下面我们通过一个示例程序描述这一过程并给出解决该问题的方法。

文盘Rust -- tokio绑定cpu实践

tokio 是 rust 生态中流行的异步运行时框架。在实际生产中我们如果希望 tokio 应用程序与特定的 cpu core 绑定该怎么处理呢?这次我们来聊聊这个话题。