#clap #repl #command #command-line #clap-parser #user-interface #rustyline

clap-repl

轻松构建使用 clap 和 reedline 的 REPL,无需额外努力

4 个版本

0.2.0 2024年5月23日
0.1.2 2024年5月22日
0.1.1 2023年12月3日
0.1.0 2023年6月22日

#2#rustyline

Download history 36/week @ 2024-04-25 5/week @ 2024-05-02 99/week @ 2024-05-16 186/week @ 2024-05-23 2/week @ 2024-05-30 6/week @ 2024-06-06 2/week @ 2024-06-13 3/week @ 2024-06-27 13/week @ 2024-07-04 27/week @ 2024-07-25 41/week @ 2024-08-01 3/week @ 2024-08-08

每月 71 次下载

MIT/Apache

18KB
134 行代码(不含注释)

clap-repl

Rust crates.io

repl(读取-评估-打印循环)是命令提示用户界面的典型之一。在 repl 中表示命令的最好方法之一是使用空格分隔的参数,这正是终端外壳所做的事情。在 Rust 中解析此类命令的方法是使用 clap crate。该 crate 使用 clapreedline 以一种只关注您应用逻辑的方式提供此类用户界面。

特性

感谢 clapreedline,此 crate 处理以下功能:

  • 将空格分隔的命令解析到您的数据结构中。
  • 每个命令的帮助标志。
  • 验证命令是否有效,否则生成有用的错误和建议。
  • 命令的自动完成和提示。

示例

use std::path::PathBuf;

use clap::{Parser, ValueEnum};
use clap_repl::ClapEditor;
use reedline::{DefaultPrompt, DefaultPromptSegment, FileBackedHistory, Reedline, Signal};

#[derive(Debug, Parser)]
#[command(name = "")] // This name will show up in clap's error messages, so it is important to set it to "".
enum SampleCommand {
    Download {
        path: PathBuf,
        /// Check the integrity of the downloaded object
        ///
        /// Uses SHA256
        #[arg(long)]
        check_sha: bool,
    },
    /// A command to upload things.
    Upload,
    /// Login into the system.
    Login {
        /// Optional. You will be prompted if you don't provide it.
        #[arg(short, long)]
        username: Option<String>,
        #[arg(short, long, value_enum, default_value_t = Mode::Secure)]
        mode: Mode,
    },
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum Mode {
    /// Encrypt the password
    Secure,
    /// Send the password plain
    ///
    /// This paragraph is ignored because there is no long help text for possible values in clap.
    Insecure,
}

fn main() {
    let mut prompt = DefaultPrompt::default();
    prompt.left_prompt = DefaultPromptSegment::Basic("simple-example".to_owned());
    let mut rl = ClapEditor::<SampleCommand>::new_with_prompt(Box::new(prompt), |reed| {
        // Do custom things with `Reedline` instance here
        reed.with_history(Box::new(
            FileBackedHistory::with_file(10000, "/tmp/clap-repl-simple-example-history".into())
                .unwrap(),
        ))
    });
    loop {
        // Use `read_command` instead of `readline`.
        let Some(command) = rl.read_command() else {
            continue;
        };
        match command {
            SampleCommand::Download { path, check_sha } => {
                println!("Downloaded {path:?} with checking = {check_sha}");
            }
            SampleCommand::Upload => {
                println!("Uploaded");
            }
            SampleCommand::Login { username, mode } => {
                // You can use another `reedline::Reedline` inside the loop.
                let mut rl = Reedline::create();
                let username = username
                    .unwrap_or_else(|| read_line_with_reedline(&mut rl, "What is your username? "));
                let password = read_line_with_reedline(&mut rl, "What is your password? ");
                println!("Logged in with {username} and {password} in mode {mode:?}");
            }
        }
    }
}

fn read_line_with_reedline(rl: &mut Reedline, prompt: &str) -> String {
    let Signal::Success(x) = rl
        .read_line(&DefaultPrompt::new(
            DefaultPromptSegment::Basic(prompt.to_owned()),
            DefaultPromptSegment::Empty,
        ))
        .unwrap()
    else {
        panic!();
    };
    x
}

Screenshot from 2023-06-22 11-32-58 Screenshot from 2023-06-22 11-35-33 image

依赖项

~7–17MB
~234K SLoC