12 个版本 (重大变更)

0.9.0 2024 年 1 月 9 日
0.8.0 2023 年 5 月 30 日
0.7.0 2022 年 11 月 13 日
0.6.0 2021 年 9 月 25 日
0.5.1 2021 年 7 月 25 日

#129 in 命令行界面

Download history 577/week @ 2024-04-14 632/week @ 2024-04-21 700/week @ 2024-04-28 899/week @ 2024-05-05 744/week @ 2024-05-12 671/week @ 2024-05-19 543/week @ 2024-05-26 864/week @ 2024-06-02 759/week @ 2024-06-09 669/week @ 2024-06-16 731/week @ 2024-06-23 628/week @ 2024-06-30 701/week @ 2024-07-07 607/week @ 2024-07-14 1127/week @ 2024-07-21 1433/week @ 2024-07-28

3,874 每月下载量
用于 2 crates

MIT/Apache

52KB
957

Shellfish

Shellfish 是一个库,可以在程序中包含交互式壳。当构建需要持久状态的终端应用程序时,这可能很有用;但基本 CLI 不够用;而完整的 TUI 超出了项目的范围。Shellfish 提供了一条中间道路,允许交互式命令编辑,同时保存所有命令都可以访问的状态。

默认情况下,壳只包含 3 个内置命令

  • help - 显示帮助信息。
  • quit - 退出壳。
  • exit - 退出壳。

后两个是相同的,只是名称不同。

当用户添加命令时(见下文),会自动生成并显示帮助。请注意,此帮助应保持相对简短,任何额外的帮助都应通过专用帮助选项提供。

特性

以下特性可用

  • rustyline,用于更好的输入。这提供了一个 InputHandler
  • app,用于命令行参数解析。
  • async,用于异步。这可以与 tokioasync_std 配合使用
  • clap,用于与 clap 库集成。

示例

以下代码创建了一个基本的壳,并添加了以下命令

  • greet,问候用户。
  • echo,回显输入。
  • count,增加计数器。
  • cat,它是 cat。

此外,如果带有参数运行,则壳将非交互式运行。

use std::error::Error;
use std::fmt;
use std::ops::AddAssign;

use async_std::prelude::*;
use rustyline::DefaultEditor;
use shellfish::{app, handler::DefaultAsyncHandler, Command, Shell};

#[macro_use]
extern crate shellfish;

#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Define a shell
    let mut shell = Shell::new_with_async_handler(
        0_u64,
        "<[Shellfish Example]>-$ ",
        DefaultAsyncHandler::default(),
        DefaultEditor::new()?,
    );

    // Add some commands
    shell
        .commands
        .insert("greet", Command::new("greets you.".to_string(), greet));

    shell
        .commands
        .insert("echo", Command::new("prints the input.".to_string(), echo));

    shell.commands.insert(
        "count",
        Command::new("increments a counter.".to_string(), count),
    );

    shell.commands.insert(
        "cat",
        Command::new_async(
            "Displays a plaintext file.".to_string(),
            async_fn!(u64, cat),
        ),
    );

    // Check if we have > 2 args, if so no need for interactive shell
    let mut args = std::env::args();
    if args.nth(1).is_some() {
        // Create the app from the shell.
        let mut app = app::App::try_from_async(shell)?;

        // Set the binary name
        app.handler.proj_name = Some("shellfish-example".to_string());
        app.load_cache()?;

        // Run it
        app.run_args_async().await?;
    } else {
        // Run the shell
        shell.run_async().await?;
    }
    Ok(())
}

/// Greets the user
fn greet(_state: &mut u64, args: Vec<String>) -> Result<(), Box<dyn Error>> {
    let arg = args.get(1).ok_or_else(|| Box::new(GreetingError))?;
    println!("Greetings {}, my good friend.", arg);
    Ok(())
}

/// Echos the input
fn echo(_state: &mut u64, args: Vec<String>) -> Result<(), Box<dyn Error>> {
    let mut args = args.iter();
    args.next();
    for arg in args {
        print!("{} ", arg);
    }
    println!();
    Ok(())
}

/// Acts as a counter
fn count(state: &mut u64, _args: Vec<String>) -> Result<(), Box<dyn Error>> {
    state.add_assign(1);
    println!("You have used this counter {} times", state);
    Ok(())
}

/// Asynchronously reads a file
async fn cat(
    _state: &mut u64,
    args: Vec<String>,
) -> Result<(), Box<dyn Error>> {
    use async_std::fs;

    if let Some(file) = args.get(1) {
        let mut contents = String::new();
        let mut file = fs::File::open(file).await?;
        file.read_to_string(&mut contents).await?;
        println!("{}", contents);
    }

    Ok(())
}

/// Greeting error
#[derive(Debug)]
pub struct GreetingError;

impl fmt::Display for GreetingError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "No name specified")
    }
}

impl Error for GreetingError {}

Clap 支持

clap 允许更干净、更简单地处理命令行参数,如下所示

// ... imports ...

/// Simple command to greet a person
///
/// This command will greet the person based of a multitide
/// of option flags, see below.
#[derive(Parser, Debug)]
#[clap(author, version, about)]
struct GreetArgs {
    /// Name of the person to greet
    name: String,

    /// Age of the person to greet
    #[clap(short, long)]
    age: Option<u8>,

    /// Whether to be formal or note
    #[clap(short, long)]
    formal: bool,
}

#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Define a shell
    let mut shell = Shell::new_with_async_handler(
        (),
        "<[Shellfish Example]>-$ ",
        DefaultAsyncHandler::default(),
        DefaultEditor::new()?,
    );
    shell
        .commands
        .insert("greet", clap_command!((), GreetArgs, greet));
    shell.run_async().await?;

    Ok(())
}

fn greet(
    _state: &mut (),
    args: GreetArgs,
) -> Result<(), Box<dyn std::error::Error>> {

    // .. snip .. /
    
    Ok(())
}

对于大型项目,建议使用 clap 来减少样板代码。

依赖关系

~3–18MB
~196K SLoC