clap-serde-derive

Crates.ioclap-serde-derive
lib.rsclap-serde-derive
version0.2.1
sourcesrc
created_at2022-10-13 16:35:48.361709
updated_at2023-10-19 14:35:19.455151
descriptionMerge results from clap and serde into struct with derive
homepage
repositoryhttps://gitlab.com/DPDmancul/clap-serde-derive
max_upload_size
id687313
size42,877
Davide Peressoni (DPDmancul)

documentation

README

Clap and Serde derive

Crates.io Crates.io Docs.rs License GitLab pipeline

With the ClapSerde procedural macro both clap and serde can be derived from a struct.
Then the struct can be parsed from clap and serde sources as in a layered config: the last source has the precedence.

Args::from(serde_parsed)
    .merge_clap();

In the snippet the precedence is:

  1. Command line from clap;
  2. Config file from serde;
  3. Default values.

Example

In this example we define a struct which derives both clap and serde. The struct has various parameter type and also various attributes on its fields.

Finally we parse the structure from a YAML file with serde and then from command line with clap. The arguments from clap will override those from serde; the default value will be used if no source contained the field.

use clap_serde_derive::{
    clap::{self, ArgAction},
    serde::Serialize,
    ClapSerde,
};

#[derive(ClapSerde, Serialize)]
#[derive(Debug)]
#[command(author, version, about)]
pub struct Args {
    /// Input files
    pub input: Vec<std::path::PathBuf>,

    /// String argument
    #[arg(short, long)]
    name: String,

    /// Skip serde deserialize
    #[default(13)]
    #[serde(skip_deserializing)]
    #[arg(long = "num")]
    pub clap_num: u32,

    /// Skip clap
    #[serde(rename = "number")]
    #[arg(skip)]
    pub serde_num: u32,

    /// Recursive fields
    #[clap_serde]
    #[command(flatten)]
    pub suboptions: SubConfig,
}

#[derive(ClapSerde, Serialize)]
#[derive(Debug)]
pub struct SubConfig {
    #[default(true)]
    #[arg(long = "no-flag", action = ArgAction::SetFalse)]
    pub flag: bool,
}

let args = Args::from(serde_yaml::from_str::<<Args as ClapSerde>::Opt>("number: 12").unwrap())
    .merge_clap();
assert_eq!(
    serde_yaml::to_string(&args).unwrap(),
    serde_yaml::to_string(&Args {
        serde_num: 12,
        clap_num: 13,
        ..Args::default()
    })
    .unwrap(),
);

Config path from command line

You can easily take the config file path from command line in this way.

use std::{fs::File, io::BufReader};

use clap_serde_derive::{
    clap::{self, Parser},
    ClapSerde,
};

#[derive(Parser)]
#[command(author, version, about)]
struct Args {
    /// Input files
    input: Vec<std::path::PathBuf>,

    /// Config file
    #[arg(short, long = "config", default_value = "config.yml")]
    config_path: std::path::PathBuf,

    /// Rest of arguments
    #[command(flatten)]
    pub config: <Config as ClapSerde>::Opt,
}

#[derive(ClapSerde)]
struct Config {
    /// String argument
    #[arg(short, long)]
    name: String,
}

// Parse whole args with clap
let mut args = Args::parse();

// Get config file
let config = if let Ok(f) = File::open(&args.config_path) {
    // Parse config with serde
    match serde_yaml::from_reader::<_, <Config as ClapSerde>::Opt>(BufReader::new(f)) {
        // merge config already parsed from clap
        Ok(config) => Config::from(config).merge(&mut args.config),
        Err(err) => panic!("Error in configuration file:\n{}", err),
    }
} else {
    // If there is not config file return only config parsed from clap
    Config::from(&mut args.config)
};
Commit count: 14

cargo fmt