4 releases

new 0.1.3 Dec 14, 2024
0.1.2 Dec 12, 2024
0.1.1 Dec 9, 2024
0.1.0 Dec 9, 2024

#219 in Procedural macros

Download history 168/week @ 2024-12-05 374/week @ 2024-12-12

542 downloads per month

MIT/Apache

35KB
656 lines

continuous-integration

clappen

Flatten prefix for clap

About

Integrate flatten prefix in your clap parsers easily.

For more details, see:

Basic usage

use clap::Parser;

// export is the name of the macro generated
#[clappen::clappen(export = nested)]
// this mod definition does not appear in the
// final generated code, it's just a convenient
// wrapper for item definitions
mod unused_mod {
    #[derive(clap::Args, Debug, Clone)]
    pub struct Remote {
        #[arg(env, long)]
        id: String,
    }

    impl Remote {
        fn a_function(&self) -> String {
            // this `self` reference will be rewritten
            // to field prefixed with `test`, ie `self.test_id`.
            format!("id: {:?}", self.id)
        }
    }
}

// export is the name of the macro generated
#[clappen::clappen(export = prefixed_struct_generator)]
// mod definition not used too
mod unused_mod {
    #[derive(clap::Parser, Debug, Clone)]
    #[command(version, about)]
    pub struct Options {
        #[arg(env, long)]
        url: String,

        #[command(flatten)]
        // `apply` is the macro export used
        // (defined with `Remote` struct earlier)
        // `prefix` is optional
        #[clappen_command(apply = nested, prefix = "test")]
        nested: Remote,
    }
}

// generate the default struct without prefix
prefixed_struct_generator!();

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let _ = Options::parse();

    Ok(())
}

Motivation

clap unfortunately doesn't preserve field structure and prefix when flattening commands.
See this issue for more in-depth explanation.

This crate allows fixing that while not requiring custom wrapper to the end clap parser used. It's just a struct macro generator that uses clap default behaviour around arg attribute.

See clap documentation for arg.

Limitations

  • Providing custom long or env to your fields is not supported. See Roadmap.

  • References to fields with more than 1 nesting level won't work - like self.my_field_level1.my_field_level2.

    Generally this should not be needed because you want to get something out of your reusable struct parser (and Rust might get in your way for borrow-related things).

    If you still want to do that, you can write custom getter functions, and the renamed fields will be picked in the impl blocks.

  • Probably some edge cases are not covered. For example, clap subcommands should be working, but it was not tested extensively as my use case is mainly reusing structs in a mono repository fashion.

Roadmap

  • Maybe add support for long / env prefix injection.
    This would make the integration with clap tighter and the code more complicated though.

FAQ

Why is clappen_command required with command(flatten) even without prefix ?

Because people work with copy/paste.

For example, if this was not required, you might write this

#[clappen::clappen(export = nested)]
mod m {
    #[derive(clap::Args)]
    pub struct Nested {}
}

#[clappen::clappen(export = copyable_opts)]
mod m {
    #[derive(clap::Parser)]
    #[command(version, about)]
    pub struct CopyableOptions {
        #[command(flatten)]
        nested: Nested,
    }
}

But then if you copy paste this and turn it to a reusable parser, you get this

#[clappen::clappen(export = nested)]
mod m {
    #[derive(clap::Args)]
    pub struct Nested {}
}

#[clappen::clappen(export = copyable_opts)]
mod m {
    #[derive(clap::Args)]
    pub struct CopyableOptions {
        #[command(flatten)]
        nested: Nested,
    }
}

#[clappen::clappen(export = parser)]
mod m {
    #[derive(clap::Parser)]
    #[command(version, about)]
    pub struct Options {
        #[command(flatten)]
        // You're back to original issue, prefix is 
        // not maintained because `clappen` was not provided
        opts: CopyableOptions,
    }
}

Making clappen_command required for all flatten items avoids having to think about that when refactoring, you know that your prefix will be maintained even when using a single struct without a prefix.

Special thanks

  • Big up to @dtolnay and its wonderful work in syn, quote and paste.
    The initial implementation used paste but it is sadly now unmaintained.
  • Kudos to @epage for tireless maintaining clap and all its great features.

Dependencies

~245–700KB
~17K SLoC