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

rust,领域,交互,模式,如何,实现 · 浏览次数 : 26

小编点评

**领域交互模式** 领域交互模式是一种命令行程序的交互模式,通过循环解析输入的每一行,并通过 `rustyline` 解析输入的每一行命令,实现与主程序交互的功能。 **实现** 1. **循环解析输入** - 使用 `loop` 循环处理输入的每一行。 - 通过 `format!()` 生成提示符,例如 `mysql> `。 - 从输入中解析出命令行参数,并将其添加到历史文件。 2. **解析命令** - 使用 `split()` 将输入的命令行分解为字符串数组。 - 通过 `cmd::run_from()` 解析命令并执行。 3. **处理中断** - 使用 `ReadlineError` 检查输入是否被中断。 - 当中断时,打印提示符,并退出程序。 4. **配置历史文件** - 为程序配置历史文件,保存执行过的命令。 - 在程序退出时加载历史文件,提供以前执行的命令选项。 **代码示例** ```rust use interactcli_rs::{ Config, MyHelper, Parser, }; fn run() { // 配置配置项 let config = Config::builder() .history_ignore_space(true) .completion_type(CompletionType::List) .output_stream(OutputStreamType::Stdout) .build(); // 创建 MyHelper 对象 let h = MyHelper { completer: get_command_completer(), highlighter: MatchingBracketHighlighter::new(), hinter: HistoryHinter {}, colored_prompt: "\".to_owned(), validator: MatchingBracketValidator::new(), }; // 初始化 Editor let mut rl = Editor::with_config(config); // 解析输入并执行命令 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; } } } } ``` **其他说明** * `MyHelper` 用于配置命令的 autocomplete。 * `autocomplete` 的实现不在本文中提供。 * 此代码示例仅供参考,可能需要根据实际需求进行调整。

正文

作者:京东科技 贾世闻

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

书接上文,上回说到如何通过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 如何实现。

与文盘Rust -- 领域交互模式如何实现相似的内容:

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

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

文盘Rust——子命令提示,提高用户体验

上次我们聊到 CLI 的领域交互模式。在领域交互模式中,可能存在多层次的子命令。在使用过程中如果全评记忆的话,命令少还好,多了真心记不住。频繁 --help 也是个很麻烦的事情。如果每次按 'tab' 键就可以提示或补齐命令是不是很方便呢。这一节我们就来说说 'autocommplete' 如何实现。我们还是以interactcli-rs中的实现来解说实现过程

文盘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 -- 本地库引发的依赖冲突

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 绑定该怎么处理呢?这次我们来聊聊这个话题。