Rust命令行参数解析库clap-adapters的使用,为clap提供扩展适配器和自定义参数处理功能

Rust命令行参数解析库clap-adapters的使用,为clap提供扩展适配器和自定义参数处理功能

clap-adapters是为clap库提供的适配器类型,用于声明式加载配置。

任何实现了FromStr特性的类型都可以用在clap的派生结构体中,这意味着任何可以放入fn(&str) -> Result<T, Error>函数的逻辑都可以在解析时运行。这对于声明式选择配置文件或做其他很酷的事情特别有用。

基本使用示例

以下示例展示了如何使用clap-adapters来解析JSON配置文件:

use clap::Parser;
use clap_adapters::prelude::*;

#[derive(Debug, Parser)]
struct Cli {
    /// 任意Json配置文件的路径
    #[clap(long)]
    config: PathTo<JsonOf<serde_json::Value>>,
}

fn main() {
    // 在临时目录中创建配置文件
    let config_dir = tempfile::tempdir()?;
    let config_path = config_dir.path().join("config.json");
    let config_path_string = config_path.display().to_string();

    // 将测试配置{"hello":"world"}写入配置文件
    let config = serde_json::json!({"hello": "world"});
    let config_string = serde_json::to_string(&config)?;
    std::fs::write(&config_path, &config_string)?;

    // 解析我们的CLI,将配置文件路径传递给--config
    let cli = Cli::parse_from(["app", "--config", &config_path_string]);
    let data = cli.config.data();

    // 我们应该期望得到的值与写入配置的值匹配
    assert_eq!(data, &serde_json::json!({"hello":"world"}));
}

扩展clap-adapters

您可以通过定义实现此crate中特性的新类型来实现额外的可组合适配器。例如,通过实现FromReader,您可以定义一个可以从文件路径构造自身的适配器,将您的适配器嵌套到PathTo<T>中,如PathTo<YourAdapter>

完整自定义示例

下面是一个更完整的示例,展示了如何自定义适配器:

use clap::Parser;
use clap_adapters::{prelude::*, JsonOf, PathTo};
use serde::Deserialize;
use std::path::PathBuf;
use std::str::FromStr;

// 自定义配置结构体
#[derive(Debug, Deserialize)]
struct AppConfig {
    server: String,
    port: u16,
}

// 自定义适配器
struct ConfigAdapter(Result<AppConfig, String>);

impl FromStr for ConfigAdapter {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let path = PathBuf::from(s);
        let contents = std::fs::read_to_string(&path)
            .map_err(|e| format!("Failed to read config file: {}", e))?;
        let config = serde_json::from_str::<AppConfig>(&contents)
            .map_err(|e| format!("Failed to parse config: {}", e))?;
        Ok(ConfigAdapter(Ok(config)))
    }
}

#[derive(Debug, Parser)]
struct Cli {
    /// 自定义配置文件路径
    #[clap(long)]
    config: PathTo<JsonOf<ConfigAdapter>>,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 创建临时目录和配置文件
    let config_dir = tempfile::tempdir()?;
    let config_path = config_dir.path().join("app_config.json");
    
    // 写入示例配置
    let config_content = r#"{"server": "example.com", "port": 8080}"#;
    std::fs::write(&config_path, config_content)?;

    // 解析命令行参数
    let cli = Cli::parse_from(["app", "--config", config_path.to_str().unwrap()]);
    
    // 获取配置数据
    if let JsonOf(ConfigAdapter(Ok(config))) = cli.config.data() {
        println!("Server: {}", config.server);
        println!("Port: {}", config.port);
    }

    Ok(())
}

这个示例展示了:

  1. 如何定义自定义配置结构体AppConfig
  2. 如何创建自定义适配器ConfigAdapter
  3. 如何实现FromStr特性来解析配置文件
  4. 如何将自定义适配器与PathToJsonOf组合使用

安装和使用

要使用这个crate,可以通过以下命令添加到您的项目中:

cargo add clap-adapters

或者在Cargo.toml中添加:

[dependencies]
clap-adapters = "0.2.2"

1 回复

Rust命令行参数解析库clap-adapters使用指南

clap-adapters是一个为Rust命令行参数解析库clap提供扩展功能的库,它添加了适配器和自定义参数处理功能,增强了clap的灵活性。

主要功能

  1. 提供多种参数适配器
  2. 支持自定义参数处理
  3. 简化复杂参数配置
  4. 增强错误处理能力

安装

Cargo.toml中添加依赖:

[dependencies]
clap = { version = "4.0", features = ["derive"] }
clap-adapters = "0.1"

基本使用方法

1. 使用预定义适配器

use clap::{Parser, Subcommand};
use clap_adapters::prelude::*;

#[derive(Parser)]
struct Args {
    #[clap(adapter = Trim)]
    name: String,
    
    #[clap(adapter = Lowercase)]
    category: String,
    
    #[clap(adapter = ParseNumber)]
    count: u32,
}

fn main() {
    let args = Args::parse();
    println!("Name: {}, Category: {}, Count: {}", args.name, args.category, args.count);
}

2. 自定义适配器

use clap::{Parser, Subcommand};
use clap_adapters::{Adapter, AdapterResult};

struct Capitalize;

impl Adapter for Capitalize {
    type Target = String;
    
    fn adapt(&self, input: &str) -> AdapterResult<Self::Target> {
        if input.is_empty() {
            return Err("Input cannot be empty".into());
        }
        let mut chars = input.chars();
        let first = chars.next().unwrap().to_uppercase().collect::<String>();
        let rest = chars.collect::<String>();
        Ok(format!("{}{}", first, rest))
    }
}

#[derive(Parser)]
struct Args {
    #[clap(adapter = Capitalize)]
    username: String,
}

fn main() {
    let args = Args::parse();
    println!("Username: {}", args.username);  // 输入"john"会输出"John"
}

3. 组合适配器

use clap::Parser;
use clap-adapters::{prelude::*, AdapterChain};

#[derive(Parser)]
struct Args {
    #[clap(adapter = AdapterChain::new()
        .add(Trim)
        .add(Lowercase)
        .add(Capitalize))]
    city: String,
}

内置适配器

clap-adapters提供了一些常用的内置适配器:

  • Trim: 去除字符串两端的空白字符
  • Lowercase: 转换为小写
  • Uppercase: 转换为大写
  • ParseNumber: 解析为数字类型
  • ParseBool: 解析为布尔值
  • Split: 按分隔符分割字符串
  • Collect: 收集多个值到集合

高级用法

自定义错误处理

use clap_adapters::{Adapter, AdapterResult};

struct EvenNumber;

impl Adapter for EvenNumber {
    type Target = i32;
    
    fn adapt(&self, input: &str) -> AdapterResult<Self::Target> {
        let num = input.parse::<i32>()?;
        if num % 2 == 0 {
            Ok(num)
        } else {
            Err("Number must be even".into())
        }
    }
}

复杂参数处理

use clap::Parser;
use clap_adapters::{prelude::*, AdapterChain};
use serde_json::Value;

#[derive(Parser)]
struct ConfigArgs {
    #[clap(
        long,
        adapter = AdapterChain::new()
            .add(Trim)
            .add(ParseJson)
    )]
    config: Value,
}

完整示例代码

下面是一个结合了多种功能的完整示例:

use clap::Parser;
use clap_adapters::{prelude::*, Adapter, AdapterResult, AdapterChain};
use serde_json::Value;

// 自定义适配器 - 将字符串转为大写并添加前缀
struct PrefixAdapter;

impl Adapter for PrefixAdapter {
    type Target = String;
    
    fn adapt(&self, input: &str) -> AdapterResult<Self::Target> {
        Ok(format!("USER_{}", input.to_uppercase()))
    }
}

// 主命令参数结构体
#[derive(Parser, Debug)]
#[command(name = "app", version = "1.0", about = "clap-adapters演示程序")]
struct Cli {
    // 使用预定义Trim适配器
    #[clap(short, long, adapter = Trim)]
    name: String,
    
    // 使用预定义ParseNumber适配器
    #[clap(short, long, adapter = ParseNumber)]
    age: u32,
    
    // 使用自定义适配器
    #[clap(short, long, adapter = PrefixAdapter)]
    id: String,
    
    // 使用适配器链
    #[clap(
        long,
        adapter = AdapterChain::new()
            .add(Trim)
            .add(Lowercase)
    )]
    city: String,
    
    // 复杂参数处理
    #[clap(
        long,
        adapter = AdapterChain::new()
            .add(Trim)
            .add(ParseJson)
    )]
    config: Option<Value>,
}

fn main() {
    let args = Cli::parse();
    println!("解析后的参数: {:?}", args);
    
    println!("\n示例用法:");
    println!("cargo run -- --name \"  John Doe  \" --age 30 --id johndoe --city \"NEW YORK\" --config '{\"key\":\"value\"}'");
}

注意事项

  1. 适配器会按照添加顺序依次执行
  2. 错误消息会传递给clap的错误处理系统
  3. 复杂的适配器链可能会影响性能
  4. 适配器只能用于字段参数,不能用于命令或子命令

clap-adaptersclap提供了更强大的参数处理能力,特别适合需要复杂参数转换和验证的场景。

回到顶部