當前位置:網站首頁>文盤Rust——領域交互模式如何實現

文盤Rust——領域交互模式如何實現

2022-05-13 12:41:55京東科技開發者

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系列內容

版權聲明
本文為[京東科技開發者]所創,轉載請帶上原文鏈接,感謝
https://cht.chowdera.com/2022/133/202205131240200846.html

隨機推薦