#shell #框架 #命令 #clap #控制流 #有意见的 #上下文

clishe

有意见的 CLI(带 shell)框架,用 rust 实现

10 个版本

0.3.0 2023 年 9 月 11 日
0.2.8 2022 年 8 月 29 日
0.2.4 2020 年 6 月 4 日
0.2.1 2020 年 5 月 31 日

#780命令行界面

46 每月下载量
用于 hips

MIT 许可证

32KB
234

Clishé

crates.io badge

Clishé 是一个 Rust 的 CLI 微框架。使用以下价值观编写您的 CLI

  1. 减少重复命令声明的样板代码
  2. 命令实现和定义并排存在
  3. 围绕参数、可变状态(上下文)、返回类型进行控制流骨架
  4. 我们应能够根据 CLI 按需生成 shell
  5. 自动完成是第一公民,在内部 shell 中和外部 shell 中 [wip]

当我在我的 Rust 生态系统中工作时,有时我发现有必要通过调用某些特定的端点或库 API 来“尝试一部分”。创建用于这些目的的小型、可重用的 CLI 应该是微不足道的。这就是 clishé 通过有意见和限制脚手架来实现的目标。

Clishé 是围绕以下技术的一个惊人的薄包装

  • anyhow 用于错误处理
  • clap 用于 CLI 构建
  • rustyline/shellwords 用于 shell 生成

这些库为该框架贡献了大部分功能,这不仅仅是技术的集合,一个薄薄的 DSL 和一个意见。

示例

这是一个具有虚拟命令以展示 clishe 精神的简单应用程序。

#[macro_use]
extern crate clap;
#[macro_use]
extern crate clishe;
use ::clishe::prelude::*;

fn main() {
    let mut ctx = Context("".to_owned());
    // The same static methods available to the ::clap::Parser trait are
    // available here. If you have a vector of arguments, just use
    // `parse_from()`. If you want to capture the parsing errors instead of
    // letting clap print them and exit them, you should use `try_parse()`
    //                      vvvvv
    if let Err(err) = Food::parse().run(&mut ctx) {
        // ^^^^^^^^ We ignore the Ok(_) scenario here since Returned is a
        // useless unit struct, but this is where we would handle it if the
        // returned value was meaningful.
        eprint!("error: {}", err);
    }
}

// Could also be called Database, State, ... depending on the domain of your
// CLI. This is the single object available in commands aside from
// arguments/options. The context has two likely lifetimes:
//
//  - Created right before, handed to this command and dies with this command
//  - Created at the beginning of the shell, passed from one command to another
pub struct Context(String);

// Could be anything. This turns the cli app into a function(Context, args from
// user) = Returned.
//
// This type offers us two approaches for our CLI apps: procedural and
// functional in nature. In the first one, one would apply side-effects inside
// of the application tree. On the other hand, one could aggregate functionally
// all side-effects to the Returned type and execute them at the scope of the
// main.
pub struct Returned;

// Dispatchers are commands which hold sub-commands. The root of a cli-like
// application is often a dispatcher. The application then takes the shape of a
// tree of dispatcher nodes, with commands!{} implementations as leaves.
dispatchers! {
    // Any of the clap attributes you would use on top of the main clap app,
    // we will use here on the Food dispatcher, as we have chosen it to be the
    // root of our cli application.
    #[clap(
        name = "clishe",
        version = "0.2.0",
        about = "Food market",
        before_help = "In case you're hungry, here is a",
        after_help = "For you",
    )]
    Food(self, _: &mut Context) -> Result<Returned> [
        Veggies: veggies::Veggies,
        Meat: meat::Meat,
        // The shell command comes with the clishe library. It usually takes a
        // dispatcher and starts a shell using the rustyline library in which
        // all sub-commands of the dispatcher are available as first-level
        // commands. From the point-of-view of the user of the binary, it will
        // look something like this:
        //
        //     $ cargo run --example complete shell
        //     > veggies lettuce friend
        //     Welcome to the table, friend
        //     > 
        #[clap(alias = "sh", about = "Subcommands of this in a shell")]
        Shell: Shell<Context, Returned, Food>,
    ],
}

mod veggies {
    use ::clishe::prelude::*;

    // All dispatchers are created equal, they
    // could all be used as the root of an app.
    dispatchers! {
        // All clap macro attributes available on
        // top of clap commands can be used here.
        #[clap(about = "Welcome to the Jungle")]
        // The name under which commands are declared inside a dispatcher is
        // the name that is used. This is just the name of the structure
        // vvvv inside of the program. Same for dispatchers.
        Veggies(self, _: &mut crate::Context) -> Result<crate::Returned> [
            Carrots: Carrots,
            Lettuce: Lettuce,
        ],
    }

    // These are clap commands, they contain concrete command implementations.
    // They are used as leaves under the dispatchers. They could also be used
    // as the root of the application, making most of the point of the
    // framework moot!
    commands! {
        Carrots(self, _ctx: &mut crate::Context) -> Result<crate::Returned> {
            Ok(crate::Returned)
        } struct {
            // All clap macro attributes available on top
            // of clap command fields can be used here.
            #[clap(short, long)]
            name: Option<String>,
        },

        // The return type must be the same for every command and dispatcher
        // in the command hierarchy.                       vvvvvvvvvvvvvvv
        Lettuce(self, _ctx: &mut crate::Context) -> Result<crate::Returned> {
            println!("Welcome to the table, {}", self.name.as_ref().map(|s| {
                s.as_ref()
            }).unwrap_or("unknown"));
            Ok(crate::Returned)
        } struct {
            name: Option<String>,
        },
    }
}

mod meat {
    use ::clishe::prelude::*;

    dispatchers! {
        // Overriding the command name at this level is not going to work.
        #[clap(name = "carne", about = "Aimez la viande, mangez-en mieux")]
        Meat(self, _: &mut crate::Context) -> Result<crate::Returned> [
            // All sub-commands' clap attributes are shadowed by
            // attributes applied at higher levels in the command hierarchy.
            #[clap(about = "Le boeuf. C'est ça qu'on mange")]
            // If you want to override the name of a command, do it here.
            Boeuf: Beef,
            // You could use the clap macro attribute. With different abouts.
            #[clap(name = "vaca", about = "Vaca. Lo que vamos a comer")]
            Beef: Beef,
        ],
    }

    commands! {
        // The "about" override here and the "name"
        // override in the Meat dispatcher will combine.
        #[clap(about = "Beef. It's What for Dinner")]
        Beef(self, ctx: &mut crate::Context) -> Result<crate::Returned> {
            // All fields are available as owned in here vvvvvvvvv
            ctx.0 = format!("Welcome to the table, {}!", self.name);
            Ok(crate::Returned)
        } struct {
            name: String,
        },
    }
}

此代码将为您提供以下程序

$ cargo run --example complete
clishe

USAGE:
    complete <SUBCOMMAND>

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

SUBCOMMANDS:
    help       Prints this message or the help of the given subcommand(s)
    meat       Aimez la viande, mangez-en mieux
    shell      The subcommands of this command in a shell
    veggies    Welcome to the Jungle

您还可以调用 shell 并“进入” CLI

$ cargo run --example complete shell
> veggies lettuce friend
Welcome to the table, friend
> _

依赖关系

~3–14MB
~158K SLoC