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

dd3299e8a06da6289f17cdd65b261370.gif

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

基本原理

interactcli-rs 实现领域交互模式主要是循环解析输入的每一行,通过rustyline 解析输入的每一行命令,并交由命令解析函数处理响应逻辑

当我们调用 ‘-i’ 参数的时候 实际上是执行了 interact::run() 函数(interact -> cli -> run())。

pub fn run() {
    let config = Config::builder()
        .history_ignore_space(true)
        .completion_type(CompletionType::List)
        .output_stream(OutputStreamType::Stdout)
        .build();

    let h = MyHelper {
        completer: get_command_completer(),
        highlighter: MatchingBracketHighlighter::new(),
        hinter: HistoryHinter {},
        colored_prompt: "".to_owned(),
        validator: MatchingBracketValidator::new(),
    };

    let mut rl = Editor::with_config(config);
    rl.set_helper(Some(h));

    if rl.load_history("/tmp/history").is_err() {
        println!("No previous history.");
    }

    loop {
        let p = format!("{}> ", "interact-rs");
        rl.helper_mut().expect("No helper").colored_prompt = format!("\x1b[1;32m{}\x1b[0m", p);
        let readline = rl.readline(&p);
        match readline {
            Ok(line) => {
                if line.trim_start().is_empty() {
                    continue;
                }

                rl.add_history_entry(line.as_str());
                match split(line.as_str()).as_mut() {
                    Ok(arg) => {
                        if arg[0] == "exit" {
                            println!("bye!");
                            break;
                        }
                        arg.insert(0, "clisample".to_string());
                        run_from(arg.to_vec())
                    }
                    Err(err) => {
                        println!("{}", err)
                    }
                }
            }
            Err(ReadlineError::Interrupted) => {
                println!("CTRL-C");
                break;
            }
            Err(ReadlineError::Eof) => {
                println!("CTRL-D");
                break;
            }
            Err(err) => {
                println!("Error: {:?}", err);
                break;
            }
        }
    }
    rl.append_history("/tmp/history")
        .map_err(|err| error!("{}", err))
        .ok();
}

解析主逻辑

交互逻辑主要集中在 ‘loop’ 循环中,每次循环处理一次输入请求。

处理的逻辑如下:

  • 定义提示符,类似 'mysql> ',提示用户正在使用的程序

let p = format!("{}> ", "interact-rs");
 rl.helper_mut().expect("No helper").colored_prompt = format!("\x1b[1;32m{}\x1b[0m", p);
  • 读取输入行进行解析

  • 将输入的命令行加入到历史文件,执行过的命令可以通过上下键回放来增强用户体验。

rl.add_history_entry(line.as_str());
  • 将输入的行解析为 arg 字符串,交由 cmd::run_from 函数进行命令解析和执行

match split(line.as_str()).as_mut() {
                  Ok(arg) => {
                      if arg[0] == "exit" {
                          println!("bye!");
                          break;
                      }
                      arg.insert(0, "clisample".to_string());
                      run_from(arg.to_vec())
                  }
                  Err(err) => {
                      println!("{}", err)
                  }
              }
  • 解析中断,当用户执行 ctrl-c 或 ctrl-d 时,退出程序。

Err(ReadlineError::Interrupted) => {
              println!("CTRL-C");
              break;
          }
          Err(ReadlineError::Eof) => {
              println!("CTRL-D");
              break;
          }
          Err(err) => {
              println!("Error: {:?}", err);
              break;
          }

run函数中其他代码的作用

  • 配置rustyline 

在run函数最开头 定义了一个config

let config = Config::builder()
  .history_ignore_space(true)
  .completion_type(CompletionType::List)
  .output_stream(OutputStreamType::Stdout)
  .build();

这个config其实是rustyline的配置项,包括输出方式历史记录约束,输出方式等等。

MyHelper用于配置命令的autocomplete

let h = MyHelper {
  completer: get_command_completer(),
  highlighter: MatchingBracketHighlighter::new(),
  hinter: HistoryHinter {},
  colored_prompt: "".to_owned(),
  validator: MatchingBracketValidator::new(),
};

这里卖个关子,下期详细讲讲autocomplete的实现。

  • 配置历史文件 

run函数最后,我们为程序配置了历史文件,应用于存放执行过的历史命令。这样即便程序退出,在此打开程序的时候还是可以利用以前的执行历史。

rl.append_history("/tmp/history")
      .map_err(|err| error!("{}", err))
      .ok();

关于如何构建命令行的领域交互模式就说到这儿,下期详细介绍一下 autocomplete 如何实现。

- End -

►►更多了解◄◄

653595307f4d9ca869376a122175e5fc.png

004b0482cf7688a0dcf368d29de889c0.png

1c85bd60e3b375a090b8fb5974663a3a.png

d0fb195dbaf3303c9ece9556977bde43.png

点击阅读原文 查看文盘Rust系列内容

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值