clap/src/derive.rs

373 lines
11 KiB
Rust
Raw Normal View History

2020-04-29 02:30:04 +00:00
//! This module contains traits that are usable with the `#[derive(...)].`
//! macros in [`clap_derive`].
use crate::{App, ArgMatches, ArgValue, Error};
2020-04-27 18:47:08 +00:00
use std::ffi::OsString;
/// Parse command-line arguments into `Self`.
///
2020-04-29 02:30:04 +00:00
/// The primary one-stop-shop trait used to create an instance of a `clap`
/// [`App`], conduct the parsing, and turn the resulting [`ArgMatches`] back
/// into concrete instance of the user struct.
///
/// This trait is primarily a convenience on top of [`FromArgMatches`] +
2020-04-29 02:30:04 +00:00
/// [`IntoApp`] which uses those two underlying traits to build the two
/// fundamental functions `parse` which uses the `std::env::args_os` iterator,
/// and `parse_from` which allows the consumer to supply the iterator (along
/// with fallible options for each).
///
/// See also [`Subcommand`] and [`Args`].
///
2020-04-29 02:30:04 +00:00
/// # Examples
///
/// The following example creates a `Context` struct that would be used
/// throughout the application representing the normalized values coming from
/// the CLI.
///
/// ```rust
/// # use clap::{Clap};
/// /// My super CLI
/// #[derive(Clap)]
/// #[clap(name = "demo")]
/// struct Context {
/// /// More verbose output
/// #[clap(long)]
/// verbose: bool,
/// /// An optional name
/// #[clap(short, long)]
/// name: Option<String>,
/// }
/// ```
///
/// The equivalent [`App`] struct + `From` implementation:
2020-04-29 02:30:04 +00:00
///
/// ```rust
/// # use clap::{App, Arg, ArgMatches};
/// App::new("demo")
/// .about("My super CLI")
2020-08-11 14:08:05 +00:00
/// .arg(Arg::new("verbose")
2020-04-29 02:30:04 +00:00
/// .long("verbose")
2020-08-11 14:08:05 +00:00
/// .about("More verbose output"))
/// .arg(Arg::new("name")
2020-04-29 02:30:04 +00:00
/// .long("name")
/// .short('n')
2020-08-11 14:08:05 +00:00
/// .about("An optional name")
2020-04-29 02:30:04 +00:00
/// .takes_value(true));
///
/// struct Context {
/// verbose: bool,
/// name: Option<String>,
/// }
///
/// impl From<ArgMatches> for Context {
/// fn from(m: ArgMatches) -> Self {
/// Context {
/// verbose: m.is_present("verbose"),
/// name: m.value_of("name").map(|n| n.to_owned()),
/// }
/// }
/// }
/// ```
2020-09-12 17:15:14 +00:00
///
pub trait Clap: FromArgMatches + IntoApp + Sized {
2020-04-29 02:30:04 +00:00
/// Parse from `std::env::args_os()`, exit on error
fn parse() -> Self {
let matches = <Self as IntoApp>::into_app().get_matches();
<Self as FromArgMatches>::from_arg_matches(&matches).expect("IntoApp validated everything")
}
2020-04-29 02:30:04 +00:00
/// Parse from `std::env::args_os()`, return Err on error.
fn try_parse() -> Result<Self, Error> {
let matches = <Self as IntoApp>::into_app().try_get_matches()?;
Ok(<Self as FromArgMatches>::from_arg_matches(&matches)
.expect("IntoApp validated everything"))
}
/// Parse from iterator, exit on error
fn parse_from<I, T>(itr: I) -> Self
where
I: IntoIterator<Item = T>,
// TODO (@CreepySkeleton): discover a way to avoid cloning here
T: Into<OsString> + Clone,
{
let matches = <Self as IntoApp>::into_app().get_matches_from(itr);
<Self as FromArgMatches>::from_arg_matches(&matches).expect("IntoApp validated everything")
}
2020-02-25 16:50:47 +00:00
/// Parse from iterator, return Err on error.
fn try_parse_from<I, T>(itr: I) -> Result<Self, Error>
where
I: IntoIterator<Item = T>,
// TODO (@CreepySkeleton): discover a way to avoid cloning here
T: Into<OsString> + Clone,
{
let matches = <Self as IntoApp>::into_app().try_get_matches_from(itr)?;
Ok(<Self as FromArgMatches>::from_arg_matches(&matches)
.expect("IntoApp validated everything"))
}
/// Update from iterator, exit on error
fn update_from<I, T>(&mut self, itr: I)
where
I: IntoIterator<Item = T>,
// TODO (@CreepySkeleton): discover a way to avoid cloning here
T: Into<OsString> + Clone,
{
// TODO find a way to get partial matches
2020-11-14 09:58:30 +00:00
let matches = <Self as IntoApp>::into_app_for_update().get_matches_from(itr);
<Self as FromArgMatches>::update_from_arg_matches(self, &matches);
}
/// Update from iterator, return Err on error.
fn try_update_from<I, T>(&mut self, itr: I) -> Result<(), Error>
where
I: IntoIterator<Item = T>,
// TODO (@CreepySkeleton): discover a way to avoid cloning here
T: Into<OsString> + Clone,
{
2020-11-14 09:58:30 +00:00
let matches = <Self as IntoApp>::into_app_for_update().try_get_matches_from(itr)?;
<Self as FromArgMatches>::update_from_arg_matches(self, &matches);
Ok(())
}
}
/// Build an [`App`] relevant for a user-defined container.
pub trait IntoApp: Sized {
/// Build an [`App`] that can instantiate `Self`.
///
/// See [`FromArgMatches::from_arg_matches`] for instantiating `Self`.
2020-07-19 17:11:29 +00:00
fn into_app<'help>() -> App<'help>;
/// Build an [`App`] that can update `self`.
///
/// See [`FromArgMatches::update_from_arg_matches`] for updating `self`.
2020-11-14 09:58:30 +00:00
fn into_app_for_update<'help>() -> App<'help>;
}
/// Converts an instance of [`ArgMatches`] to a user-defined container.
pub trait FromArgMatches: Sized {
/// Instantiate `Self` from [`ArgMatches`], parsing the arguments as needed.
2020-04-29 02:42:54 +00:00
///
/// Motivation: If our application had two CLI options, `--name
/// <STRING>` and the flag `--debug`, we may create a struct as follows:
2020-04-29 02:42:54 +00:00
///
/// ```no_run
/// struct Context {
/// name: String,
/// debug: bool
/// }
/// ```
///
/// We then need to convert the `ArgMatches` that `clap` generated into our struct.
/// `from_arg_matches` serves as the equivalent of:
2020-04-29 02:42:54 +00:00
///
/// ```no_run
2020-08-11 14:08:05 +00:00
/// # use clap::ArgMatches;
/// # struct Context {
/// # name: String,
/// # debug: bool
/// # }
2020-04-29 02:42:54 +00:00
/// impl From<ArgMatches> for Context {
/// fn from(m: ArgMatches) -> Self {
/// Context {
2020-08-11 14:08:05 +00:00
/// name: m.value_of("name").unwrap().to_string(),
2020-04-29 02:42:54 +00:00
/// debug: m.is_present("debug"),
/// }
/// }
/// }
/// ```
fn from_arg_matches(matches: &ArgMatches) -> Option<Self>;
/// Assign values from `ArgMatches` to `self`.
fn update_from_arg_matches(&mut self, matches: &ArgMatches);
}
/// Parse arguments into a user-defined container.
///
/// Implementing this trait lets a parent container delegate argument parsing behavior to `Self`.
/// with:
/// - `#[clap(flatten)] args: ChildArgs`: Attribute can only be used with struct fields that impl
/// `Args`.
/// - `Variant(ChildArgs)`: No attribute is used with enum variants that impl `Args`.
///
///
/// # Example
///
/// ```rust
/// #[derive(clap::Clap)]
/// struct Args {
/// #[clap(flatten)]
/// logging: LogArgs,
/// }
///
/// #[derive(clap::Args)]
/// struct LogArgs {
/// #[clap(long, short = 'v', parse(from_occurrences))]
/// verbose: i8,
/// }
/// ```
pub trait Args: FromArgMatches + Sized {
/// Append to [`App`] so it can instantiate `Self`.
///
/// See also [`IntoApp`].
fn augment_args(app: App<'_>) -> App<'_>;
/// Append to [`App`] so it can update `self`.
///
/// This is used to implement `#[clap(flatten)]`
///
/// See also [`IntoApp`].
fn augment_args_for_update(app: App<'_>) -> App<'_>;
}
/// Parse a sub-command into a user-defined enum.
///
2021-08-16 03:02:18 +00:00
/// Implementing this trait lets a parent container delegate subcommand behavior to `Self`.
/// with:
/// - `#[clap(subcommand)] field: SubCmd`: Attribute can be used with either struct fields or enum
/// variants that impl `Subcommand`.
/// - `#[clap(flatten)] Variant(SubCmd)`: Attribute can only be used with enum variants that impl
/// `Subcommand`.
///
/// # Example
///
/// ```rust
/// #[derive(clap::Clap)]
/// struct Args {
/// #[clap(subcommand)]
/// action: Action,
/// }
///
/// #[derive(clap::Subcommand)]
/// enum Action {
/// Add,
/// Remove,
/// }
/// ```
pub trait Subcommand: FromArgMatches + Sized {
/// Append to [`App`] so it can instantiate `Self`.
///
/// See also [`IntoApp`].
fn augment_subcommands(app: App<'_>) -> App<'_>;
/// Append to [`App`] so it can update `self`.
///
/// This is used to implement `#[clap(flatten)]`
///
/// See also [`IntoApp`].
2020-11-14 09:58:30 +00:00
fn augment_subcommands_for_update(app: App<'_>) -> App<'_>;
/// Test whether `Self` can parse a specific subcommand
fn has_subcommand(name: &str) -> bool;
}
/// Parse arguments into enums.
///
/// When deriving [`Clap`], a field whose type implements `ArgEnum` can have the attribute
/// `#[clap(arg_enum)]`. In addition to parsing, help and error messages may report possible
/// variants.
///
/// # Example
///
/// ```rust
/// #[derive(clap::Clap)]
/// struct Args {
/// #[clap(arg_enum)]
/// level: Level,
/// }
///
/// #[derive(clap::ArgEnum, Clone)]
/// enum Level {
/// Debug,
/// Info,
/// Warning,
/// Error,
/// }
/// ```
pub trait ArgEnum: Sized + Clone {
2021-09-27 20:06:17 +00:00
/// All possible argument values, in display order.
fn value_variants<'a>() -> &'a [Self];
2020-04-22 07:25:41 +00:00
/// Parse an argument into `Self`.
fn from_str(input: &str, case_insensitive: bool) -> Result<Self, String> {
Self::value_variants()
.iter()
.find(|v| {
v.to_arg_value()
.expect("ArgEnum::value_variants contains only values with a corresponding ArgEnum::to_arg_value")
.matches(input, case_insensitive)
})
.cloned()
.ok_or_else(|| format!("Invalid variant: {}", input))
}
/// The canonical argument value.
///
/// The value is `None` for skipped variants.
fn to_arg_value<'a>(&self) -> Option<ArgValue<'a>>;
2020-04-22 07:25:41 +00:00
}
2020-02-13 15:21:01 +00:00
impl<T: Clap> Clap for Box<T> {
fn parse() -> Self {
Box::new(<T as Clap>::parse())
}
fn try_parse() -> Result<Self, Error> {
<T as Clap>::try_parse().map(Box::new)
}
fn parse_from<I, It>(itr: I) -> Self
where
I: IntoIterator<Item = It>,
// TODO (@CreepySkeleton): discover a way to avoid cloning here
It: Into<OsString> + Clone,
{
Box::new(<T as Clap>::parse_from(itr))
}
fn try_parse_from<I, It>(itr: I) -> Result<Self, Error>
where
I: IntoIterator<Item = It>,
// TODO (@CreepySkeleton): discover a way to avoid cloning here
It: Into<OsString> + Clone,
{
<T as Clap>::try_parse_from(itr).map(Box::new)
}
}
impl<T: IntoApp> IntoApp for Box<T> {
2020-07-19 17:11:29 +00:00
fn into_app<'help>() -> App<'help> {
2020-02-13 15:21:01 +00:00
<T as IntoApp>::into_app()
}
2020-11-14 09:58:30 +00:00
fn into_app_for_update<'help>() -> App<'help> {
<T as IntoApp>::into_app_for_update()
}
2020-02-13 15:21:01 +00:00
}
impl<T: FromArgMatches> FromArgMatches for Box<T> {
fn from_arg_matches(matches: &ArgMatches) -> Option<Self> {
<T as FromArgMatches>::from_arg_matches(matches).map(Box::new)
2020-02-13 15:21:01 +00:00
}
fn update_from_arg_matches(&mut self, matches: &ArgMatches) {
<T as FromArgMatches>::update_from_arg_matches(self, matches)
}
2020-02-13 15:21:01 +00:00
}
impl<T: Args> Args for Box<T> {
fn augment_args(app: App<'_>) -> App<'_> {
<T as Args>::augment_args(app)
2020-02-13 15:21:01 +00:00
}
fn augment_args_for_update(app: App<'_>) -> App<'_> {
<T as Args>::augment_args_for_update(app)
}
}
impl<T: Subcommand> Subcommand for Box<T> {
2020-02-13 15:21:01 +00:00
fn augment_subcommands(app: App<'_>) -> App<'_> {
<T as Subcommand>::augment_subcommands(app)
}
2020-11-14 09:58:30 +00:00
fn augment_subcommands_for_update(app: App<'_>) -> App<'_> {
<T as Subcommand>::augment_subcommands_for_update(app)
}
fn has_subcommand(name: &str) -> bool {
<T as Subcommand>::has_subcommand(name)
}
2020-02-13 15:21:01 +00:00
}