#clap #applications #command #run-command #handlers #command-line #framework

cling

一个轻量级框架,简化使用 clap.rs 构建 complex 命令行应用程序

5 个版本

0.1.2 2024年3月19日
0.1.1 2023年12月1日
0.1.0 2023年9月5日
0.0.1-alpha.12023年8月27日
0.0.0-alpha.2 2023年7月31日

179命令行界面 中排名

Download history 473/week @ 2024-04-02 320/week @ 2024-04-09 432/week @ 2024-04-16 399/week @ 2024-04-23 134/week @ 2024-04-30 40/week @ 2024-05-07 303/week @ 2024-05-14 290/week @ 2024-05-21 304/week @ 2024-05-28 118/week @ 2024-06-04 109/week @ 2024-06-11 545/week @ 2024-06-18 135/week @ 2024-06-25 143/week @ 2024-07-02 72/week @ 2024-07-09 247/week @ 2024-07-16

689 每月下载量
用于 netperf

Apache-2.0 OR MIT

58KB
988 行(不包括注释)

一个轻量级框架,简化使用 clap.rs 构建 complex 命令行应用程序。

license-badge build-status-badge crates-io-badge docs-badge

概述

cling 的名字是 CLI-ng(即下一代)和英文单词 "cling" 的双关语。它使功能处理程序能够 clingclap 用户定义的结构体 😉。

在编写使用出色的 clap crate 的命令行应用程序时,开发人员通常会编写样板函数来找出用户执行了哪个命令,收集输入类型,然后运行执行该工作的处理函数。对于多命令应用程序,这会很快变得重复。Cling 旨在简化此工作流程,并允许开发人员声明性地将命令映射到函数。

关键特性

  • 通过 #[cling(run = "my_function")] 声明性地将 CLI 命令映射到处理程序
  • 将处理程序定义为常规 Rust 单元测试函数
  • 在命令树中的任何级别运行处理程序(中间件样式)
  • 处理程序函数的参数自动从 clap 输入中提取
  • 处理程序可以返回一个 State<T> 值,该值可以由下游处理程序提取
  • 处理程序可以是 syncasync 函数
  • 带有颜色的统一 CLI 友好错误处理
Credit: The handler design of cling is largely inspired by the excellent work done in [Axum](https://github.com/tokio-rs/axum).

更多详细信息,请参阅

编译器支持:需要 rustc 1.74+

演示

一个快速而简单的示例,以展示 cling 的实际应用

# Cargo.toml
[package]
name = "cling-example"
version = "0.1.0"
edition = "2021"

[dependencies]
clap = { version = "4.4.1", features = ["derive", "env"] }
cling = { version = "0.1" }
tokio = { version = "1.13.0", features = ["full"] }

我们的 main.rs 可能看起来像这样

use cling::prelude::*;

// -- args --

#[derive(Run, Parser, Debug, Clone)]
#[command(author, version, long_about = None)]
/// A simple multi-command example using cling
struct MyApp {
    #[clap(flatten)]
    pub opts: CommonOpts,
    #[clap(subcommand)]
    pub cmd: Command,
}

#[derive(Collect, Parser, Debug, Clone)]
struct CommonOpts {
    /// Turn debugging information on
    #[arg(short, long, global = true, action = clap::ArgAction::Count)]
    pub verbose: u8,
}

// Commands for the app are defined here.
#[derive(Run, Subcommand, Debug, Clone)]
enum Command {
    /// Honk the horn!
    Honk(HonkOpts),
    #[cling(run = "beep")]
    /// Beep beep!
    Beep,
}

// Options of "honk" command. We define cling(run=...) here to call the
// function when this command is executed.
#[derive(Run, Collect, Parser, Debug, Clone)]
#[cling(run = "honk")]
struct HonkOpts {
    /// How many times to honk?
    times: u8,
}

// -- Handlers --

// We can access &CommonOpts because it derives [Collect]
fn honk(common_opts: &CommonOpts, HonkOpts { times }: &HonkOpts) {
    if common_opts.verbose > 0 {
        println!("Honking {} times", times);
    }

    (0..*times).for_each(|_| {
        print!("Honk ");
    });
    println!("!");
}

// Maybe beeps need to be async!
async fn beep() -> anyhow::Result<()> {
    println!("Beep, Beep!");
    Ok(())
}


// -- main --

#[tokio::main]
async fn main() -> ClingFinished<MyApp> {
    Cling::parse_and_run().await
}

现在,让我们运行它并验证它是否按预期工作

$ simple-multi-command -v honk 5
Honking 5 times
Honk Honk Honk Honk Honk !

概念

  1. 可运行对象
  2. 处理程序

可运行对象

可运行对象指的是代表 CLI 命令的 clap 结构体。在 cling 中,任何编码命令树的 struct 或 enum 都必须继承 Run,这包括程序的最高级 struct(例如上面示例中的 MyApp)。Run trait 告诉 cling,这种类型应该附加到一个 处理程序 函数。本质上,继承自 ParserSubcommand 的 struct 将需要继承 Run

对于任何继承自 Run 的 struct/enum,可以使用 #[cling(run = ...)] 属性将其与一个 处理程序 关联起来。可以设计多级 clap 程序,其中每个级别都运行处理程序,让我们看看一些例子来了解可能做到什么程度。

单命令,单处理程序

use cling::prelude::*;

#[derive(Run, Collect, Parser, Debug, Clone)]
// standard clap attributes
#[command(author, version, about)]
#[cling(run = "my_only_handler")]
pub struct MyApp {
    #[clap(short, long)]
    /// User name
    pub name: String,
}

fn my_only_handler(app: &MyApp) {
    println!("User is {}", app.name);
}

#[tokio::main]
async fn main() -> ClingFinished<MyApp> {
    Cling::parse_and_run().await
}

注意:我们在 MyApp 上继承 CollectClone,以便将其作为共享引用 app: &MyApp 传递给处理程序 my_only_handler。如果处理程序不需要访问 &MyApp,则不需要继承 Collect

我们的程序现在将运行 my_only_handler 中的代码

$ sample-1 --name=Steve
User is Steve

具有处理程序的子命令

给定一个类似于这样的命令结构

MyApp [CommonOpts]
  - projects
    |- create [CreateOpts]
    |- list
  - whoami
use cling::prelude::*;

#[derive(Run, Parser, Debug, Clone)]
#[command(author, version, about, long_about = None)]
pub struct MyApp {
    #[clap(flatten)]
    pub common: CommonOpts,
    #[command(subcommand)]
    pub cmd: Commands,
}

#[derive(Args, Collect, Debug, Clone)]
pub struct CommonOpts {
    /// Access token
    #[arg(long, global = true)]
    pub access_token: Option<String>,
}

#[derive(Run, Subcommand, Debug, Clone)]
pub enum Commands {
    /// Manage projects
    #[command(subcommand)]
    Projects(ProjectCommands),
    /// Self identification
    #[cling(run = "handlers::whoami")]
    WhoAmI,
}

#[derive(Run, Subcommand, Debug, Clone)]
pub enum ProjectCommands {
    /// Create new project
    Create(CreateOpts),
    /// List all projects
    #[cling(run = "handlers::list_projects")]
    List,
}

#[derive(Run, Args, Collect, Debug, Clone)]
#[cling(run = "handlers::create_project")]
pub struct CreateOpts {
    /// Project name
    pub name: String,
}

mod handlers {
    pub fn whoami() {}
    pub fn list_projects() {}
    pub fn create_project() {}
}

  • CommonOpts 程序范围内的选项。任何处理程序都可以选择访问它。
  • ProjectOpts 适用于 projects [OPTIONS]
  • CreateOpts 适用于 projects create 命令
  • ListOpts 适用于 projects list 命令

为了了解这是如何工作的,考虑 projects list 子命令可以传递的不同选项集

Usage: myapp [COMMON-OPTIONS] projects create <NAME>

在此设计中,我们的可运行对象是:MyAppCommandsProjectCommandsCreateOpts

我们可以通过使用#[cling(run = "...")]属性,将处理器附加到任何Run类型。这个处理器将在任何子命令处理器(如果有)之前运行。对于实现了Subcommand的枚举,我们必须在没有任何参数的枚举变体(如ProjectCommands::List)上附加#[cling(run = "...")]。然而,对于任何带有参数的枚举变体,参数类型本身Run

处理程序

处理器是常规的Rust函数,当运行clap命令时执行。处理器通过使用#[cling(run = "function::path")]属性附加到实现了Run的任何类型。

每个具有#[cling(run = ...)]的类型,将在运行内部可运行的处理器的处理器之前运行其处理器函数。

示例

use cling::prelude::*;

#[derive(Run, Parser, Debug, Clone)]
#[command(author, version, about, long_about = None)]
#[cling(run = "init")]
pub struct MyApp {
    #[clap(flatten)]
    pub common: CommonOpts,
    #[command(subcommand)]
    pub cmd: Commands,
}

#[derive(Args, Collect, Debug, Clone)]
pub struct CommonOpts {
    /// Access token
    #[arg(long, global = true)]
    pub access_token: Option<String>,
}

#[derive(Run, Subcommand, Debug, Clone)]
pub enum Commands {
    #[cling(run = "run_beep")]
    Beep,
    /// Self identification
    #[cling(run = "run_whoami")]
    WhoAmI,
}

fn init() {
    println!("init handler!");
}

fn run_whoami() {
    println!("I'm groot!");
}

fn run_beep() {
    println!("Beep beep!");
}

#[tokio::main]
async fn main() -> ClingFinished<MyApp> {
    Cling::parse_and_run().await
}

init处理器将始终在该结构中的其他任何处理器之前运行。

$ many-handlers beep
init handler!
Beep beep!

功能标志

功能 激活 效果
派生 默认 启用#[derive(Run)]#[derive(Collect)]
shlex "shlex"功能 启用从文本解析,当构建REPL时很有用

支持的Rust版本

Cling的最小支持Rust版本是1.74.0

许可证

双许可,根据Apache 2.0MIT

贡献

除非您明确声明,否则您有意提交以包含在作品中的任何贡献都应双重许可,如上所述,不附加任何额外条款或条件。

依赖项

~2–10MB
~93K SLoC