#cmd #interactive #repl #shell #command-line #command-line-arguments #keyboard-shortcuts

clapcmd

一个 readline 包装器,允许创建自定义交互式 shell,类似于 Python 的 cmd 模块

9 个版本

0.3.3 2023 年 8 月 20 日
0.3.2 2023 年 8 月 1 日
0.3.0 2023 年 7 月 31 日
0.2.1 2023 年 7 月 31 日
0.1.1 2023 年 7 月 28 日

#277 in 命令行界面

MIT/Apache

71KB
2K SLoC

ClapCmd

一个库,可快速构建由 CLAP 和 readline(通过 rustyline 提供)支持的完整功能的 REPL

功能

  • 完全支持 readline,并公开 rustyline 的所有自定义选项
    • 默认为 emacs 风格的键盘快捷键(可通过 rustyline 自定义)
    • 命令历史(内存缓冲区中)
  • 与 clap 构建器完全集成,允许使用完整功能的命令
  • Tab 完成支持
    • 命令和(待办)命令别名
    • 参数
    • 子命令
    • 通过 value_parsers(即有效值列表)提供的值
    • 值提示(例如 ValueHint::FilePath
    • 待办:回调和/或演示如何查询 value_parsers 的方法
  • 具有提供状态的回调样式方法
  • 可自定义的提示,可以在执行期间随时更新
  • 支持通过 get_async_writer() 在命令循环外写入 stdout,而不会破坏输入行
  • 创建可加载和卸载的命令组
  • 通过行尾的 '\' 字符支持多行输入
  • 通过
    • 分号(;)进行无条件评估
    • 双 ampersand(&&)用于链式成功评估
    • 双 pipe(||)用于错误处理评估
  • 通过 test-runner 功能进行自动测试

基本示例

以下是一个展示基本 REPL 的最小示例

use clapcmd::{ArgMatches, ClapCmd, ClapCmdResult, Command};

fn do_ping(cmd: &mut ClapCmd, _: ArgMatches) -> ClapCmdResult {
    cmd.output("pong");
    Ok(())
}

fn main() {
    let mut cmd = ClapCmd::default();
    cmd.add_command(
        do_ping,
        Command::new("ping").about("do a ping")
    );
    cmd.run_loop();
}

带有状态

要将状态或持久信息传递给回调,提供类似于以下所示的 State 类。该 State 类必须实现 Clone 特性,并通过 ClapCmd 引用上的 get_state()set_state() 方法进行访问。

use clapcmd::{ArgMatches, ClapCmd, ClapCmdResult, Command};

#[derive(Clone)]
struct State {
    counter: u32,
}

fn do_count(cmd: &mut ClapCmd<State>, _: ArgMatches) -> ClapCmdResult {
    let state = cmd.get_state().ok_or("state missing")?;
    let new_count = state.counter + 1;
    cmd.info(format!("the count is now: {}", new_count));
    cmd.set_state(State { counter: new_count });
    Ok(())
}

fn main() {
    let mut cmd = ClapCmd::with_state(State { counter: 0 });
    cmd.add_command(do_count, Command::new("count").about("increment a counter"));
    cmd.run_loop();
}

使用组

组可以用于在内置的 help 菜单中逻辑上分离命令集合。它们也可以通过 add_groupremove_group 方法快速激活和停用命令。

use clapcmd::{ArgMatches, ClapCmd, ClapCmdResult, Command, HandlerGroup};
use once_cell::sync::Lazy;

static LOADED_GROUP: Lazy<HandlerGroup> = Lazy::new(|| {
    ClapCmd::group("Fruit")
        .description("Commands to do cool fruit things")
        .command(
            do_apple,
            Command::new("apple").about("do the cool apple thing"),
        )
        .command(
            do_banana,
            Command::new("banana").about("do the cool banana thing"),
        )
        .command(
            do_unload,
            Command::new("unload").about("unload the cool fruit group"),
        )
});

static UNLOADED_GROUP: Lazy<HandlerGroup> = Lazy::new(|| {
    ClapCmd::unnamed_group().command(
        do_load,
        Command::new("load").about("load the cool fruit group"),
    )
});

fn do_load(cmd: &mut ClapCmd, _: ArgMatches) -> ClapCmdResult {
    cmd.add_group(&LOADED_GROUP);
    cmd.remove_group(&UNLOADED_GROUP);
    cmd.info("loaded");
    Ok(())
}

fn do_unload(cmd: &mut ClapCmd, _: ArgMatches) -> ClapCmdResult {
    cmd.add_group(&UNLOADED_GROUP);
    cmd.remove_group(&LOADED_GROUP);
    cmd.info("unloaded");
    Ok(())
}

fn do_apple(cmd: &mut ClapCmd, _: ArgMatches) -> ClapCmdResult {
    cmd.output("apple");
    Ok(())
}

fn do_banana(cmd: &mut ClapCmd, _: ArgMatches) -> ClapCmdResult {
    cmd.output("banana");
    Ok(())
}

fn main() {
    let mut cmd = ClapCmd::default();
    cmd.add_group(&UNLOADED_GROUP);
    cmd.run_loop();
}

端到端测试

通过启用 test-runner 功能和使用内置的 outputsuccessinfowarnerror 函数,可以轻松自动化 CLI 的端到端测试。有关更多示例,请参阅 tests/ 文件夹。

use clapcmd::{ArgMatches, ClapCmd, ClapCmdResult, Command};

fn do_hello(cmd: &mut ClapCmd, _: ArgMatches) -> ClapCmdResult {
    cmd.output("hello");
    Ok(())
}

let mut cmd = ClapCmd::default();
cmd.add_command(
    do_hello,
    Command::new("hello").about("simple hello world")
);
let _ = cmd.one_cmd("goodbye");
#[cfg(feature = "test-runner")]
assert!(
    cmd.error.contains("unknown command"),
    "did not detect invalid command",
);
let _ = cmd.one_cmd("hello");
#[cfg(feature = "test-runner")]
assert!(
    cmd.output.contains("hello"),
    "did not run hello world command correctly",
);

其他示例

有关高级用例的更多演示,请参阅 examples/ 文件夹。

MSRV

此库与 Rust 1.65 及其最新版本一起进行了测试。

依赖项

~4.5MB
~79K SLoC