aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Walbran <qwandor@google.com>2023-01-11 11:18:52 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-01-11 11:18:52 +0000
commite7287d31f587010ebdac1c795ff44079b5b49b08 (patch)
tree3cd2a248ac6dda3eb872c30f53df9b5d36624d81
parentace57039a8e8586b21df23ce07a9c4c5215d1b33 (diff)
parent5cbdc0a33f96b84f961365b1db908c7566afc258 (diff)
downloadclap_derive-e7287d31f587010ebdac1c795ff44079b5b49b08.tar.gz
Merge "Upgrade clap_derive to 4.0.21" am: 5cbdc0a33f
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/clap_derive/+/2318777 Change-Id: Ibc8e1a520fa6f0d09deb56877f43322d715dd549 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--.cargo_vcs_info.json2
-rw-r--r--Android.bp4
-rw-r--r--Cargo.toml16
-rw-r--r--Cargo.toml.orig11
-rw-r--r--METADATA13
-rw-r--r--README.md4
-rw-r--r--src/attr.rs222
-rw-r--r--src/attrs.rs1412
-rw-r--r--src/derives/args.rs753
-rw-r--r--src/derives/into_app.rs60
-rw-r--r--src/derives/parser.rs90
-rw-r--r--src/derives/subcommand.rs485
-rw-r--r--src/derives/value_enum.rs85
-rw-r--r--src/dummies.rs15
-rw-r--r--src/item.rs1449
-rw-r--r--src/lib.rs20
-rw-r--r--src/parse.rs288
-rw-r--r--src/utils/doc_comments.rs87
-rw-r--r--src/utils/mod.rs4
-rw-r--r--src/utils/spanned.rs9
-rw-r--r--src/utils/ty.rs26
21 files changed, 2477 insertions, 2578 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index d3a77cb..faf15b2 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,6 +1,6 @@
{
"git": {
- "sha1": "19bc3b75f66876bdd57bbc52c21f03b708879a3f"
+ "sha1": "bc457b179f4e4145d1bd4c61c1fddd871f8839b8"
},
"path_in_vcs": "clap_derive"
} \ No newline at end of file
diff --git a/Android.bp b/Android.bp
index 92bad73..e9ae34e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,8 +1,6 @@
// This file is generated by cargo2android.py --config cargo2android.json.
// Do not modify this file as changes will be overridden on upgrade.
-
-
package {
default_applicable_licenses: ["external_rust_crates_clap_derive_license"],
}
@@ -43,7 +41,7 @@ rust_proc_macro {
name: "libclap_derive",
crate_name: "clap_derive",
cargo_env_compat: true,
- cargo_pkg_version: "3.2.18",
+ cargo_pkg_version: "4.0.21",
srcs: ["src/lib.rs"],
edition: "2021",
features: ["default"],
diff --git a/Cargo.toml b/Cargo.toml
index f74476e..f63c7f0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,9 +11,9 @@
[package]
edition = "2021"
-rust-version = "1.56.1"
+rust-version = "1.60.0"
name = "clap_derive"
-version = "3.2.18"
+version = "4.0.21"
include = [
"build.rs",
"src/**/*",
@@ -38,7 +38,6 @@ categories = [
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/clap-rs/clap/tree/master/clap_derive"
-resolver = "2"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
@@ -48,13 +47,6 @@ shared-version = true
dependent-version = "upgrade"
tag-name = "v{{version}}"
-[[package.metadata.release.pre-release-replacements]]
-file = "README.md"
-search = "github.com/clap-rs/clap/blob/[^/]+/"
-replace = "github.com/clap-rs/clap/blob/{{tag_name}}/"
-exactly = 2
-prerelease = true
-
[lib]
bench = false
proc-macro = true
@@ -66,7 +58,7 @@ version = "0.4.0"
version = "1"
[dependencies.proc-macro2]
-version = "1.0.28"
+version = "1.0.42"
[dependencies.quote]
version = "1.0.9"
@@ -80,4 +72,4 @@ debug = []
default = []
deprecated = []
raw-deprecated = ["deprecated"]
-unstable-v4 = ["deprecated"]
+unstable-v5 = ["deprecated"]
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index 9c9d489..27bf1e5 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,6 +1,6 @@
[package]
name = "clap_derive"
-version = "3.2.18"
+version = "4.0.21"
description = "Parse command line argument by defining a struct, derive crate."
repository = "https://github.com/clap-rs/clap/tree/master/clap_derive"
categories = ["command-line-interface", "development-tools::procedural-macro-helpers"]
@@ -13,7 +13,7 @@ keywords = [
]
license = "MIT OR Apache-2.0"
edition = "2021"
-rust-version = "1.56.1" # MSRV
+rust-version = "1.60.0" # MSRV
include = [
"build.rs",
"src/**/*",
@@ -31,9 +31,6 @@ targets = ["x86_64-unknown-linux-gnu"]
shared-version = true
dependent-version = "upgrade"
tag-name = "v{{version}}"
-pre-release-replacements = [
- {file="README.md", search="github.com/clap-rs/clap/blob/[^/]+/", replace="github.com/clap-rs/clap/blob/{{tag_name}}/", exactly=2, prerelease = true},
-]
[lib]
proc-macro = true
@@ -42,13 +39,13 @@ bench = false
[dependencies]
syn = { version = "1.0.74", features = ["full"] }
quote = "1.0.9"
-proc-macro2 = "1.0.28"
+proc-macro2 = "1.0.42"
heck = "0.4.0"
proc-macro-error = "1"
[features]
default = []
debug = []
-unstable-v4 = ["deprecated"]
+unstable-v5 = ["deprecated"]
deprecated = []
raw-deprecated = ["deprecated"]
diff --git a/METADATA b/METADATA
index f1167d9..d3d0aa2 100644
--- a/METADATA
+++ b/METADATA
@@ -1,3 +1,7 @@
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update rust/crates/clap_derive
+# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md
+
name: "clap_derive"
description: "Parse command line argument by defining a struct, derive crate."
third_party {
@@ -7,14 +11,13 @@ third_party {
}
url {
type: ARCHIVE
- value: "https://static.crates.io/crates/clap_derive/clap_derive-3.2.18.crate"
+ value: "https://static.crates.io/crates/clap_derive/clap_derive-4.0.21.crate"
}
- version: "3.2.18"
- # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same.
+ version: "4.0.21"
license_type: NOTICE
last_upgrade_date {
year: 2022
- month: 9
- day: 7
+ month: 11
+ day: 30
}
}
diff --git a/README.md b/README.md
index 17f649e..1ca9ce6 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,8 @@
Macro implementation for clap's derives.
[docs.rs](https://docs.rs/clap)
-- [Derive Tutorial](https://github.com/clap-rs/clap/blob/v3.2.18/examples/tutorial_derive/README.md)
-- [Derive Reference](https://github.com/clap-rs/clap/blob/v3.2.18/examples/derive_ref/README.md)
+- [Derive Tutorial](https://docs.rs/clap/latest/clap/_derive/_tutorial/index.html)
+- [Derive Reference](https://docs.rs/clap/latest/clap/_derive/index.html)
## License
diff --git a/src/attr.rs b/src/attr.rs
new file mode 100644
index 0000000..5ea49a1
--- /dev/null
+++ b/src/attr.rs
@@ -0,0 +1,222 @@
+use std::iter::FromIterator;
+
+use proc_macro2::TokenStream;
+use proc_macro_error::abort;
+use proc_macro_error::ResultExt;
+use quote::quote;
+use quote::ToTokens;
+use syn::spanned::Spanned;
+use syn::{
+ parenthesized,
+ parse::{Parse, ParseStream},
+ punctuated::Punctuated,
+ Attribute, Expr, Ident, LitStr, Token,
+};
+
+use crate::utils::Sp;
+
+#[derive(Clone)]
+pub struct ClapAttr {
+ pub kind: Sp<AttrKind>,
+ pub name: Ident,
+ pub magic: Option<MagicAttrName>,
+ pub value: Option<AttrValue>,
+}
+
+impl ClapAttr {
+ pub fn parse_all(all_attrs: &[Attribute]) -> Vec<Self> {
+ all_attrs
+ .iter()
+ .filter_map(|attr| {
+ let kind = if attr.path.is_ident("clap") {
+ Some(Sp::new(AttrKind::Clap, attr.path.span()))
+ } else if attr.path.is_ident("structopt") {
+ Some(Sp::new(AttrKind::StructOpt, attr.path.span()))
+ } else if attr.path.is_ident("command") {
+ Some(Sp::new(AttrKind::Command, attr.path.span()))
+ } else if attr.path.is_ident("group") {
+ Some(Sp::new(AttrKind::Group, attr.path.span()))
+ } else if attr.path.is_ident("arg") {
+ Some(Sp::new(AttrKind::Arg, attr.path.span()))
+ } else if attr.path.is_ident("value") {
+ Some(Sp::new(AttrKind::Value, attr.path.span()))
+ } else {
+ None
+ };
+ kind.map(|k| (k, attr))
+ })
+ .flat_map(|(k, attr)| {
+ attr.parse_args_with(Punctuated::<ClapAttr, Token![,]>::parse_terminated)
+ .unwrap_or_abort()
+ .into_iter()
+ .map(move |mut a| {
+ a.kind = k;
+ a
+ })
+ })
+ .collect()
+ }
+
+ pub fn value_or_abort(&self) -> &AttrValue {
+ self.value
+ .as_ref()
+ .unwrap_or_else(|| abort!(self.name, "attribute `{}` requires a value", self.name))
+ }
+
+ pub fn lit_str_or_abort(&self) -> &LitStr {
+ let value = self.value_or_abort();
+ match value {
+ AttrValue::LitStr(tokens) => tokens,
+ AttrValue::Expr(_) | AttrValue::Call(_) => {
+ abort!(
+ self.name,
+ "attribute `{}` can only accept string litersl",
+ self.name
+ )
+ }
+ }
+ }
+}
+
+impl Parse for ClapAttr {
+ fn parse(input: ParseStream) -> syn::Result<Self> {
+ let name: Ident = input.parse()?;
+ let name_str = name.to_string();
+
+ let magic = match name_str.as_str() {
+ "rename_all" => Some(MagicAttrName::RenameAll),
+ "rename_all_env" => Some(MagicAttrName::RenameAllEnv),
+ "skip" => Some(MagicAttrName::Skip),
+ "next_display_order" => Some(MagicAttrName::NextDisplayOrder),
+ "next_help_heading" => Some(MagicAttrName::NextHelpHeading),
+ "default_value_t" => Some(MagicAttrName::DefaultValueT),
+ "default_values_t" => Some(MagicAttrName::DefaultValuesT),
+ "default_value_os_t" => Some(MagicAttrName::DefaultValueOsT),
+ "default_values_os_t" => Some(MagicAttrName::DefaultValuesOsT),
+ "long" => Some(MagicAttrName::Long),
+ "short" => Some(MagicAttrName::Short),
+ "value_parser" => Some(MagicAttrName::ValueParser),
+ "action" => Some(MagicAttrName::Action),
+ "env" => Some(MagicAttrName::Env),
+ "flatten" => Some(MagicAttrName::Flatten),
+ "value_enum" => Some(MagicAttrName::ValueEnum),
+ "from_global" => Some(MagicAttrName::FromGlobal),
+ "subcommand" => Some(MagicAttrName::Subcommand),
+ "external_subcommand" => Some(MagicAttrName::ExternalSubcommand),
+ "verbatim_doc_comment" => Some(MagicAttrName::VerbatimDocComment),
+ "about" => Some(MagicAttrName::About),
+ "long_about" => Some(MagicAttrName::LongAbout),
+ "long_help" => Some(MagicAttrName::LongHelp),
+ "author" => Some(MagicAttrName::Author),
+ "version" => Some(MagicAttrName::Version),
+ _ => None,
+ };
+
+ let value = if input.peek(Token![=]) {
+ // `name = value` attributes.
+ let assign_token = input.parse::<Token![=]>()?; // skip '='
+ if input.peek(LitStr) {
+ let lit: LitStr = input.parse()?;
+ Some(AttrValue::LitStr(lit))
+ } else {
+ match input.parse::<Expr>() {
+ Ok(expr) => Some(AttrValue::Expr(expr)),
+
+ Err(_) => abort! {
+ assign_token,
+ "expected `string literal` or `expression` after `=`"
+ },
+ }
+ }
+ } else if input.peek(syn::token::Paren) {
+ // `name(...)` attributes.
+ let nested;
+ parenthesized!(nested in input);
+
+ let method_args: Punctuated<_, Token![,]> = nested.parse_terminated(Expr::parse)?;
+ Some(AttrValue::Call(Vec::from_iter(method_args)))
+ } else {
+ None
+ };
+
+ Ok(Self {
+ kind: Sp::new(AttrKind::Clap, name.span()),
+ name,
+ magic,
+ value,
+ })
+ }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub enum MagicAttrName {
+ Short,
+ Long,
+ ValueParser,
+ Action,
+ Env,
+ Flatten,
+ ValueEnum,
+ FromGlobal,
+ Subcommand,
+ VerbatimDocComment,
+ ExternalSubcommand,
+ About,
+ LongAbout,
+ LongHelp,
+ Author,
+ Version,
+ RenameAllEnv,
+ RenameAll,
+ Skip,
+ DefaultValueT,
+ DefaultValuesT,
+ DefaultValueOsT,
+ DefaultValuesOsT,
+ NextDisplayOrder,
+ NextHelpHeading,
+}
+
+#[derive(Clone)]
+#[allow(clippy::large_enum_variant)]
+pub enum AttrValue {
+ LitStr(LitStr),
+ Expr(Expr),
+ Call(Vec<Expr>),
+}
+
+impl ToTokens for AttrValue {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ match self {
+ Self::LitStr(t) => t.to_tokens(tokens),
+ Self::Expr(t) => t.to_tokens(tokens),
+ Self::Call(t) => {
+ let t = quote!(#(#t),*);
+ t.to_tokens(tokens)
+ }
+ }
+ }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub enum AttrKind {
+ Clap,
+ StructOpt,
+ Command,
+ Group,
+ Arg,
+ Value,
+}
+
+impl AttrKind {
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Self::Clap => "clap",
+ Self::StructOpt => "structopt",
+ Self::Command => "command",
+ Self::Group => "group",
+ Self::Arg => "arg",
+ Self::Value => "value",
+ }
+ }
+}
diff --git a/src/attrs.rs b/src/attrs.rs
deleted file mode 100644
index 2c5b47d..0000000
--- a/src/attrs.rs
+++ /dev/null
@@ -1,1412 +0,0 @@
-// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
-// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
-// Ana Hobden (@hoverbear) <operator@hoverbear.org>
-//
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-//
-// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
-// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
-// MIT/Apache 2.0 license.
-
-use crate::{
- parse::*,
- utils::{inner_type, is_simple_ty, process_doc_comment, Sp, Ty},
-};
-
-use std::env;
-
-use heck::{ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
-use proc_macro2::{self, Span, TokenStream};
-use proc_macro_error::abort;
-use quote::{quote, quote_spanned, ToTokens};
-use syn::{
- self, ext::IdentExt, spanned::Spanned, Attribute, Expr, Field, Ident, LitStr, MetaNameValue,
- Type, Variant,
-};
-
-/// Default casing style for generated arguments.
-pub const DEFAULT_CASING: CasingStyle = CasingStyle::Kebab;
-
-/// Default casing style for environment variables
-pub const DEFAULT_ENV_CASING: CasingStyle = CasingStyle::ScreamingSnake;
-
-#[derive(Clone)]
-pub struct Attrs {
- name: Name,
- casing: Sp<CasingStyle>,
- env_casing: Sp<CasingStyle>,
- ty: Option<Type>,
- doc_comment: Vec<Method>,
- methods: Vec<Method>,
- value_parser: Option<ValueParser>,
- action: Option<Action>,
- parser: Option<Sp<Parser>>,
- verbatim_doc_comment: Option<Ident>,
- next_display_order: Option<Method>,
- next_help_heading: Option<Method>,
- help_heading: Option<Method>,
- is_enum: bool,
- is_positional: bool,
- kind: Sp<Kind>,
-}
-
-impl Attrs {
- pub fn from_struct(
- span: Span,
- attrs: &[Attribute],
- name: Name,
- argument_casing: Sp<CasingStyle>,
- env_casing: Sp<CasingStyle>,
- ) -> Self {
- let mut res = Self::new(span, name, None, argument_casing, env_casing);
- res.push_attrs(attrs);
- res.push_doc_comment(attrs, "about");
-
- if let Some(value_parser) = res.value_parser.as_ref() {
- abort!(
- value_parser.span(),
- "`value_parser` attribute is only allowed on fields"
- );
- }
- if let Some(action) = res.action.as_ref() {
- abort!(
- action.span(),
- "`action` attribute is only allowed on fields"
- );
- }
- if let Some(parser) = res.parser.as_ref() {
- abort!(parser.span(), "`parse` attribute is only allowed on fields");
- }
- match &*res.kind {
- Kind::Subcommand(_) => abort!(res.kind.span(), "subcommand is only allowed on fields"),
- Kind::Skip(_) => abort!(res.kind.span(), "skip is only allowed on fields"),
- Kind::Arg(_) => res,
- Kind::FromGlobal(_) => abort!(res.kind.span(), "from_global is only allowed on fields"),
- Kind::Flatten => abort!(res.kind.span(), "flatten is only allowed on fields"),
- Kind::ExternalSubcommand => abort!(
- res.kind.span(),
- "external_subcommand is only allowed on fields"
- ),
- }
- }
-
- pub fn from_variant(
- variant: &Variant,
- struct_casing: Sp<CasingStyle>,
- env_casing: Sp<CasingStyle>,
- ) -> Self {
- let name = variant.ident.clone();
- let mut res = Self::new(
- variant.span(),
- Name::Derived(name),
- None,
- struct_casing,
- env_casing,
- );
- res.push_attrs(&variant.attrs);
- res.push_doc_comment(&variant.attrs, "about");
-
- match &*res.kind {
- Kind::Flatten => {
- if let Some(value_parser) = res.value_parser.as_ref() {
- abort!(
- value_parser.span(),
- "`value_parser` attribute is not allowed for flattened entry"
- );
- }
- if let Some(action) = res.action.as_ref() {
- abort!(
- action.span(),
- "`action` attribute is not allowed for flattened entry"
- );
- }
- if let Some(parser) = res.parser.as_ref() {
- abort!(
- parser.span(),
- "parse attribute is not allowed for flattened entry"
- );
- }
- if res.has_explicit_methods() {
- abort!(
- res.kind.span(),
- "methods are not allowed for flattened entry"
- );
- }
-
- // ignore doc comments
- res.doc_comment = vec![];
- }
-
- Kind::ExternalSubcommand => (),
-
- Kind::Subcommand(_) => {
- if let Some(value_parser) = res.value_parser.as_ref() {
- abort!(
- value_parser.span(),
- "`value_parser` attribute is not allowed for subcommand"
- );
- }
- if let Some(action) = res.action.as_ref() {
- abort!(
- action.span(),
- "`action` attribute is not allowed for subcommand"
- );
- }
- if let Some(parser) = res.parser.as_ref() {
- abort!(
- parser.span(),
- "parse attribute is not allowed for subcommand"
- );
- }
-
- use syn::Fields::*;
- use syn::FieldsUnnamed;
- let field_ty = match variant.fields {
- Named(_) => {
- abort!(variant.span(), "structs are not allowed for subcommand");
- }
- Unit => abort!(variant.span(), "unit-type is not allowed for subcommand"),
- Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
- &unnamed[0].ty
- }
- Unnamed(..) => {
- abort!(
- variant,
- "non single-typed tuple is not allowed for subcommand"
- )
- }
- };
- let ty = Ty::from_syn_ty(field_ty);
- match *ty {
- Ty::OptionOption => {
- abort!(
- field_ty,
- "Option<Option<T>> type is not allowed for subcommand"
- );
- }
- Ty::OptionVec => {
- abort!(
- field_ty,
- "Option<Vec<T>> type is not allowed for subcommand"
- );
- }
- _ => (),
- }
-
- res.kind = Sp::new(Kind::Subcommand(ty), res.kind.span());
- }
- Kind::Skip(_) => (),
- Kind::FromGlobal(_) => {
- abort!(res.kind.span(), "from_global is not supported on variants");
- }
- Kind::Arg(_) => (),
- }
-
- res
- }
-
- pub fn from_value_enum_variant(
- variant: &Variant,
- argument_casing: Sp<CasingStyle>,
- env_casing: Sp<CasingStyle>,
- ) -> Self {
- let mut res = Self::new(
- variant.span(),
- Name::Derived(variant.ident.clone()),
- None,
- argument_casing,
- env_casing,
- );
- res.push_attrs(&variant.attrs);
- res.push_doc_comment(&variant.attrs, "help");
-
- if let Some(value_parser) = res.value_parser.as_ref() {
- abort!(
- value_parser.span(),
- "`value_parser` attribute is only allowed on fields"
- );
- }
- if let Some(action) = res.action.as_ref() {
- abort!(
- action.span(),
- "`action` attribute is only allowed on fields"
- );
- }
- if let Some(parser) = res.parser.as_ref() {
- abort!(parser.span(), "`parse` attribute is only allowed on fields");
- }
- match &*res.kind {
- Kind::Subcommand(_) => abort!(res.kind.span(), "subcommand is only allowed on fields"),
- Kind::Skip(_) => res,
- Kind::Arg(_) => res,
- Kind::FromGlobal(_) => abort!(res.kind.span(), "from_global is only allowed on fields"),
- Kind::Flatten => abort!(res.kind.span(), "flatten is only allowed on fields"),
- Kind::ExternalSubcommand => abort!(
- res.kind.span(),
- "external_subcommand is only allowed on fields"
- ),
- }
- }
-
- pub fn from_field(
- field: &Field,
- struct_casing: Sp<CasingStyle>,
- env_casing: Sp<CasingStyle>,
- ) -> Self {
- let name = field.ident.clone().unwrap();
- let mut res = Self::new(
- field.span(),
- Name::Derived(name),
- Some(field.ty.clone()),
- struct_casing,
- env_casing,
- );
- res.push_attrs(&field.attrs);
- res.push_doc_comment(&field.attrs, "help");
-
- match &*res.kind {
- Kind::Flatten => {
- if let Some(value_parser) = res.value_parser.as_ref() {
- abort!(
- value_parser.span(),
- "`value_parser` attribute is not allowed for flattened entry"
- );
- }
- if let Some(action) = res.action.as_ref() {
- abort!(
- action.span(),
- "`action` attribute is not allowed for flattened entry"
- );
- }
- if let Some(parser) = res.parser.as_ref() {
- abort!(
- parser.span(),
- "parse attribute is not allowed for flattened entry"
- );
- }
- if res.has_explicit_methods() {
- abort!(
- res.kind.span(),
- "methods are not allowed for flattened entry"
- );
- }
-
- // ignore doc comments
- res.doc_comment = vec![];
- }
-
- Kind::ExternalSubcommand => {
- abort! { res.kind.span(),
- "`external_subcommand` can be used only on enum variants"
- }
- }
-
- Kind::Subcommand(_) => {
- if let Some(value_parser) = res.value_parser.as_ref() {
- abort!(
- value_parser.span(),
- "`value_parser` attribute is not allowed for subcommand"
- );
- }
- if let Some(action) = res.action.as_ref() {
- abort!(
- action.span(),
- "`action` attribute is not allowed for subcommand"
- );
- }
- if let Some(parser) = res.parser.as_ref() {
- abort!(
- parser.span(),
- "parse attribute is not allowed for subcommand"
- );
- }
- if res.has_explicit_methods() {
- abort!(
- res.kind.span(),
- "methods in attributes are not allowed for subcommand"
- );
- }
-
- let ty = Ty::from_syn_ty(&field.ty);
- match *ty {
- Ty::OptionOption => {
- abort!(
- field.ty,
- "Option<Option<T>> type is not allowed for subcommand"
- );
- }
- Ty::OptionVec => {
- abort!(
- field.ty,
- "Option<Vec<T>> type is not allowed for subcommand"
- );
- }
- _ => (),
- }
-
- res.kind = Sp::new(Kind::Subcommand(ty), res.kind.span());
- }
- Kind::Skip(_) => {
- if res.has_explicit_methods() {
- abort!(
- res.kind.span(),
- "methods are not allowed for skipped fields"
- );
- }
- }
- Kind::FromGlobal(orig_ty) => {
- let ty = Ty::from_syn_ty(&field.ty);
- res.kind = Sp::new(Kind::FromGlobal(ty), orig_ty.span());
- }
- Kind::Arg(_) => {
- let mut ty = Ty::from_syn_ty(&field.ty);
- if res.parser.is_some() {
- if let Some(value_parser) = res.value_parser.as_ref() {
- abort!(
- value_parser.span(),
- "`value_parser` attribute conflicts with `parse` attribute"
- );
- }
- if let Some(action) = res.action.as_ref() {
- abort!(
- action.span(),
- "`action` attribute conflicts with `parse` attribute"
- );
- }
- match *ty {
- Ty::Option | Ty::Vec | Ty::OptionVec => (),
- _ => ty = Sp::new(Ty::Other, ty.span()),
- }
- }
-
- match *ty {
- Ty::Option => {
- if let Some(m) = res.find_default_method() {
- abort!(m.name, "default_value is meaningless for Option")
- }
- }
- Ty::OptionOption => {
- if res.is_positional() {
- abort!(
- field.ty,
- "Option<Option<T>> type is meaningless for positional argument"
- )
- }
- }
- Ty::OptionVec => {
- if res.is_positional() {
- abort!(
- field.ty,
- "Option<Vec<T>> type is meaningless for positional argument"
- )
- }
- }
-
- _ => (),
- }
- res.kind = Sp::new(
- Kind::Arg(ty),
- field
- .ident
- .as_ref()
- .map(|i| i.span())
- .unwrap_or_else(|| field.ty.span()),
- );
- }
- }
-
- res
- }
-
- fn new(
- default_span: Span,
- name: Name,
- ty: Option<Type>,
- casing: Sp<CasingStyle>,
- env_casing: Sp<CasingStyle>,
- ) -> Self {
- Self {
- name,
- ty,
- casing,
- env_casing,
- doc_comment: vec![],
- methods: vec![],
- value_parser: None,
- action: None,
- parser: None,
- verbatim_doc_comment: None,
- next_display_order: None,
- next_help_heading: None,
- help_heading: None,
- is_enum: false,
- is_positional: true,
- kind: Sp::new(Kind::Arg(Sp::new(Ty::Other, default_span)), default_span),
- }
- }
-
- fn push_method(&mut self, name: Ident, arg: impl ToTokens) {
- if name == "name" || name == "id" {
- self.name = Name::Assigned(quote!(#arg));
- } else if name == "value_parser" {
- self.value_parser = Some(ValueParser::Explicit(Method::new(name, quote!(#arg))));
- } else if name == "action" {
- self.action = Some(Action::Explicit(Method::new(name, quote!(#arg))));
- } else {
- if name == "short" || name == "long" {
- self.is_positional = false;
- }
- self.methods.push(Method::new(name, quote!(#arg)));
- }
- }
-
- fn push_attrs(&mut self, attrs: &[Attribute]) {
- use ClapAttr::*;
-
- let parsed = parse_clap_attributes(attrs);
- for attr in &parsed {
- let attr = attr.clone();
- match attr {
- Short(ident) => {
- self.push_method(ident, self.name.clone().translate_char(*self.casing));
- }
-
- Long(ident) => {
- self.push_method(ident, self.name.clone().translate(*self.casing));
- }
-
- ValueParser(ident) => {
- use crate::attrs::ValueParser;
- self.value_parser = Some(ValueParser::Implicit(ident));
- }
-
- Action(ident) => {
- use crate::attrs::Action;
- self.action = Some(Action::Implicit(ident));
- }
-
- Env(ident) => {
- self.push_method(ident, self.name.clone().translate(*self.env_casing));
- }
-
- ValueEnum(_) => self.is_enum = true,
-
- FromGlobal(ident) => {
- let ty = Sp::call_site(Ty::Other);
- let kind = Sp::new(Kind::FromGlobal(ty), ident.span());
- self.set_kind(kind);
- }
-
- Subcommand(ident) => {
- let ty = Sp::call_site(Ty::Other);
- let kind = Sp::new(Kind::Subcommand(ty), ident.span());
- self.set_kind(kind);
- }
-
- ExternalSubcommand(ident) => {
- let kind = Sp::new(Kind::ExternalSubcommand, ident.span());
- self.set_kind(kind);
- }
-
- Flatten(ident) => {
- let kind = Sp::new(Kind::Flatten, ident.span());
- self.set_kind(kind);
- }
-
- Skip(ident, expr) => {
- let kind = Sp::new(Kind::Skip(expr), ident.span());
- self.set_kind(kind);
- }
-
- VerbatimDocComment(ident) => self.verbatim_doc_comment = Some(ident),
-
- DefaultValueT(ident, expr) => {
- let ty = if let Some(ty) = self.ty.as_ref() {
- ty
- } else {
- abort!(
- ident,
- "#[clap(default_value_t)] (without an argument) can be used \
- only on field level";
-
- note = "see \
- https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
- };
-
- let val = if let Some(expr) = expr {
- quote!(#expr)
- } else {
- quote!(<#ty as ::std::default::Default>::default())
- };
-
- let val = if parsed.iter().any(|a| matches!(a, ValueEnum(_))) {
- quote_spanned!(ident.span()=> {
- {
- let val: #ty = #val;
- clap::ValueEnum::to_possible_value(&val).unwrap().get_name()
- }
- })
- } else {
- quote_spanned!(ident.span()=> {
- static DEFAULT_VALUE: clap::__macro_refs::once_cell::sync::Lazy<String> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
- let val: #ty = #val;
- ::std::string::ToString::to_string(&val)
- });
- &*DEFAULT_VALUE
- })
- };
-
- let raw_ident = Ident::new("default_value", ident.span());
- self.methods.push(Method::new(raw_ident, val));
- }
-
- DefaultValuesT(ident, expr) => {
- let ty = if let Some(ty) = self.ty.as_ref() {
- ty
- } else {
- abort!(
- ident,
- "#[clap(default_values_t)] (without an argument) can be used \
- only on field level";
-
- note = "see \
- https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
- };
-
- let container_type = Ty::from_syn_ty(ty);
- if *container_type != Ty::Vec {
- abort!(
- ident,
- "#[clap(default_values_t)] can be used only on Vec types";
-
- note = "see \
- https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
- }
- let inner_type = inner_type(ty);
-
- // Use `Borrow<#inner_type>` so we accept `&Vec<#inner_type>` and
- // `Vec<#inner_type>`.
- let val = if parsed.iter().any(|a| matches!(a, ValueEnum(_))) {
- quote_spanned!(ident.span()=> {
- {
- fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> Vec<&'static str>
- where
- T: ::std::borrow::Borrow<#inner_type>
- {
- iterable
- .into_iter()
- .map(|val| {
- clap::ValueEnum::to_possible_value(val.borrow()).unwrap().get_name()
- })
- .collect()
-
- }
-
- static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy<Vec<&str>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
- iter_to_vals(#expr)
- });
- &*DEFAULT_VALUES.as_slice()
- }
- })
- } else {
- quote_spanned!(ident.span()=> {
- {
- fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> Vec<String>
- where
- T: ::std::borrow::Borrow<#inner_type>
- {
- iterable.into_iter().map(|val| val.borrow().to_string()).collect()
-
- }
-
- static DEFAULT_STRINGS: clap::__macro_refs::once_cell::sync::Lazy<Vec<::std::string::String>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
- iter_to_vals(#expr)
- });
-
- static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy<Vec<&str>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
- DEFAULT_STRINGS.iter().map(::std::string::String::as_str).collect()
- });
- &*DEFAULT_VALUES.as_slice()
- }
- })
- };
-
- self.methods
- .push(Method::new(Ident::new("default_values", ident.span()), val));
- }
-
- DefaultValueOsT(ident, expr) => {
- let ty = if let Some(ty) = self.ty.as_ref() {
- ty
- } else {
- abort!(
- ident,
- "#[clap(default_value_os_t)] (without an argument) can be used \
- only on field level";
-
- note = "see \
- https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
- };
-
- let val = if let Some(expr) = expr {
- quote!(#expr)
- } else {
- quote!(<#ty as ::std::default::Default>::default())
- };
-
- let val = if parsed.iter().any(|a| matches!(a, ValueEnum(_))) {
- quote_spanned!(ident.span()=> {
- {
- let val: #ty = #val;
- clap::ValueEnum::to_possible_value(&val).unwrap().get_name()
- }
- })
- } else {
- quote_spanned!(ident.span()=> {
- static DEFAULT_VALUE: clap::__macro_refs::once_cell::sync::Lazy<::std::ffi::OsString> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
- let val: #ty = #val;
- ::std::ffi::OsString::from(val)
- });
- &*DEFAULT_VALUE
- })
- };
-
- let raw_ident = Ident::new("default_value_os", ident.span());
- self.methods.push(Method::new(raw_ident, val));
- }
-
- DefaultValuesOsT(ident, expr) => {
- let ty = if let Some(ty) = self.ty.as_ref() {
- ty
- } else {
- abort!(
- ident,
- "#[clap(default_values_os_t)] (without an argument) can be used \
- only on field level";
-
- note = "see \
- https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
- };
-
- let container_type = Ty::from_syn_ty(ty);
- if *container_type != Ty::Vec {
- abort!(
- ident,
- "#[clap(default_values_os_t)] can be used only on Vec types";
-
- note = "see \
- https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
- }
- let inner_type = inner_type(ty);
-
- // Use `Borrow<#inner_type>` so we accept `&Vec<#inner_type>` and
- // `Vec<#inner_type>`.
- let val = if parsed.iter().any(|a| matches!(a, ValueEnum(_))) {
- quote_spanned!(ident.span()=> {
- {
- fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> Vec<&'static ::std::ffi::OsStr>
- where
- T: ::std::borrow::Borrow<#inner_type>
- {
- iterable
- .into_iter()
- .map(|val| {
- clap::ValueEnum::to_possible_value(val.borrow()).unwrap().get_name()
- })
- .map(::std::ffi::OsStr::new)
- .collect()
-
- }
-
- static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy<Vec<&::std::ffi::OsStr>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
- iter_to_vals(#expr)
- });
- &*DEFAULT_VALUES.as_slice()
- }
- })
- } else {
- quote_spanned!(ident.span()=> {
- {
- fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> Vec<::std::ffi::OsString>
- where
- T: ::std::borrow::Borrow<#inner_type>
- {
- iterable.into_iter().map(|val| val.borrow().into()).collect()
-
- }
-
- static DEFAULT_OS_STRINGS: clap::__macro_refs::once_cell::sync::Lazy<Vec<::std::ffi::OsString>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
- iter_to_vals(#expr)
- });
-
- static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy<Vec<&::std::ffi::OsStr>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
- DEFAULT_OS_STRINGS.iter().map(::std::ffi::OsString::as_os_str).collect()
- });
- &*DEFAULT_VALUES.as_slice()
- }
- })
- };
-
- self.methods.push(Method::new(
- Ident::new("default_values_os", ident.span()),
- val,
- ));
- }
-
- NextDisplayOrder(ident, expr) => {
- self.next_display_order = Some(Method::new(ident, quote!(#expr)));
- }
-
- HelpHeading(ident, expr) => {
- self.help_heading = Some(Method::new(ident, quote!(#expr)));
- }
- NextHelpHeading(ident, expr) => {
- self.next_help_heading = Some(Method::new(ident, quote!(#expr)));
- }
-
- About(ident) => {
- if let Some(method) = Method::from_env(ident, "CARGO_PKG_DESCRIPTION") {
- self.methods.push(method);
- }
- }
-
- Author(ident) => {
- if let Some(method) = Method::from_env(ident, "CARGO_PKG_AUTHORS") {
- self.methods.push(method);
- }
- }
-
- Version(ident) => {
- if let Some(method) = Method::from_env(ident, "CARGO_PKG_VERSION") {
- self.methods.push(method);
- }
- }
-
- NameLitStr(name, lit) => {
- self.push_method(name, lit);
- }
-
- NameExpr(name, expr) => {
- self.push_method(name, expr);
- }
-
- MethodCall(name, args) => self.push_method(name, quote!(#(#args),*)),
-
- RenameAll(_, casing_lit) => {
- self.casing = CasingStyle::from_lit(casing_lit);
- }
-
- RenameAllEnv(_, casing_lit) => {
- self.env_casing = CasingStyle::from_lit(casing_lit);
- }
-
- Parse(ident, spec) => {
- self.parser = Some(Parser::from_spec(ident, spec));
- }
- }
- }
- }
-
- fn push_doc_comment(&mut self, attrs: &[Attribute], name: &str) {
- use syn::Lit::*;
- use syn::Meta::*;
-
- let comment_parts: Vec<_> = attrs
- .iter()
- .filter(|attr| attr.path.is_ident("doc"))
- .filter_map(|attr| {
- if let Ok(NameValue(MetaNameValue { lit: Str(s), .. })) = attr.parse_meta() {
- Some(s.value())
- } else {
- // non #[doc = "..."] attributes are not our concern
- // we leave them for rustc to handle
- None
- }
- })
- .collect();
-
- self.doc_comment =
- process_doc_comment(comment_parts, name, self.verbatim_doc_comment.is_none());
- }
-
- fn set_kind(&mut self, kind: Sp<Kind>) {
- if let Kind::Arg(_) = *self.kind {
- self.kind = kind;
- } else {
- abort!(
- kind.span(),
- "`subcommand`, `flatten`, `external_subcommand` and `skip` cannot be used together"
- );
- }
- }
-
- pub fn find_method(&self, name: &str) -> Option<&Method> {
- self.methods.iter().find(|m| m.name == name)
- }
-
- pub fn find_default_method(&self) -> Option<&Method> {
- self.methods
- .iter()
- .find(|m| m.name == "default_value" || m.name == "default_value_os")
- }
-
- /// generate methods from attributes on top of struct or enum
- pub fn initial_top_level_methods(&self) -> TokenStream {
- let next_display_order = self.next_display_order.as_ref().into_iter();
- let next_help_heading = self.next_help_heading.as_ref().into_iter();
- let help_heading = self.help_heading.as_ref().into_iter();
- quote!(
- #(#next_display_order)*
- #(#next_help_heading)*
- #(#help_heading)*
- )
- }
-
- pub fn final_top_level_methods(&self) -> TokenStream {
- let methods = &self.methods;
- let doc_comment = &self.doc_comment;
-
- quote!( #(#doc_comment)* #(#methods)*)
- }
-
- /// generate methods on top of a field
- pub fn field_methods(&self, supports_long_help: bool) -> proc_macro2::TokenStream {
- let methods = &self.methods;
- let help_heading = self.help_heading.as_ref().into_iter();
- match supports_long_help {
- true => {
- let doc_comment = &self.doc_comment;
- quote!( #(#doc_comment)* #(#help_heading)* #(#methods)* )
- }
- false => {
- let doc_comment = self
- .doc_comment
- .iter()
- .filter(|mth| mth.name != "long_help");
- quote!( #(#doc_comment)* #(#help_heading)* #(#methods)* )
- }
- }
- }
-
- pub fn next_display_order(&self) -> TokenStream {
- let next_display_order = self.next_display_order.as_ref().into_iter();
- quote!( #(#next_display_order)* )
- }
-
- pub fn next_help_heading(&self) -> TokenStream {
- let next_help_heading = self.next_help_heading.as_ref().into_iter();
- let help_heading = self.help_heading.as_ref().into_iter();
- quote!( #(#next_help_heading)* #(#help_heading)* )
- }
-
- #[cfg(feature = "unstable-v4")]
- pub fn id(&self) -> TokenStream {
- self.name.clone().raw()
- }
-
- #[cfg(not(feature = "unstable-v4"))]
- pub fn id(&self) -> TokenStream {
- self.cased_name()
- }
-
- pub fn cased_name(&self) -> TokenStream {
- self.name.clone().translate(*self.casing)
- }
-
- pub fn value_name(&self) -> TokenStream {
- self.name.clone().translate(CasingStyle::ScreamingSnake)
- }
-
- pub fn value_parser(&self, field_type: &Type) -> Method {
- self.value_parser
- .clone()
- .map(|p| {
- let inner_type = inner_type(field_type);
- p.resolve(inner_type)
- })
- .unwrap_or_else(|| {
- if let Some(action) = self.action.as_ref() {
- let inner_type = inner_type(field_type);
- let span = action.span();
- default_value_parser(inner_type, span)
- } else if !self.ignore_parser() || cfg!(not(feature = "unstable-v4")) {
- self.parser(field_type).value_parser()
- } else {
- let inner_type = inner_type(field_type);
- let span = self
- .action
- .as_ref()
- .map(|a| a.span())
- .unwrap_or_else(|| self.kind.span());
- default_value_parser(inner_type, span)
- }
- })
- }
-
- pub fn action(&self, field_type: &Type) -> Method {
- self.action
- .clone()
- .map(|p| p.resolve(field_type))
- .unwrap_or_else(|| {
- if let Some(value_parser) = self.value_parser.as_ref() {
- let span = value_parser.span();
- default_action(field_type, span)
- } else if !self.ignore_parser() || cfg!(not(feature = "unstable-v4")) {
- self.parser(field_type).action()
- } else {
- let span = self
- .value_parser
- .as_ref()
- .map(|a| a.span())
- .unwrap_or_else(|| self.kind.span());
- default_action(field_type, span)
- }
- })
- }
-
- #[cfg(feature = "unstable-v4")]
- pub fn ignore_parser(&self) -> bool {
- self.parser.is_none()
- }
-
- #[cfg(not(feature = "unstable-v4"))]
- pub fn ignore_parser(&self) -> bool {
- self.value_parser.is_some() || self.action.is_some()
- }
-
- pub fn explicit_parser(&self) -> bool {
- self.parser.is_some()
- }
-
- pub fn parser(&self, field_type: &Type) -> Sp<Parser> {
- self.parser
- .clone()
- .unwrap_or_else(|| Parser::from_type(field_type, self.kind.span()))
- }
-
- pub fn kind(&self) -> Sp<Kind> {
- self.kind.clone()
- }
-
- pub fn is_enum(&self) -> bool {
- self.is_enum
- }
-
- pub fn is_positional(&self) -> bool {
- self.is_positional
- }
-
- pub fn ignore_case(&self) -> TokenStream {
- let method = self.find_method("ignore_case");
-
- if let Some(method) = method {
- method.args.clone()
- } else {
- quote! { false }
- }
- }
-
- pub fn casing(&self) -> Sp<CasingStyle> {
- self.casing.clone()
- }
-
- pub fn env_casing(&self) -> Sp<CasingStyle> {
- self.env_casing.clone()
- }
-
- pub fn has_explicit_methods(&self) -> bool {
- self.methods
- .iter()
- .any(|m| m.name != "help" && m.name != "long_help")
- }
-}
-
-#[derive(Clone)]
-enum ValueParser {
- Explicit(Method),
- Implicit(Ident),
-}
-
-impl ValueParser {
- fn resolve(self, inner_type: &Type) -> Method {
- match self {
- Self::Explicit(method) => method,
- Self::Implicit(ident) => default_value_parser(inner_type, ident.span()),
- }
- }
-
- fn span(&self) -> Span {
- match self {
- Self::Explicit(method) => method.name.span(),
- Self::Implicit(ident) => ident.span(),
- }
- }
-}
-
-fn default_value_parser(inner_type: &Type, span: Span) -> Method {
- let func = Ident::new("value_parser", span);
- Method::new(
- func,
- quote_spanned! { span=>
- clap::value_parser!(#inner_type)
- },
- )
-}
-
-#[derive(Clone)]
-pub enum Action {
- Explicit(Method),
- Implicit(Ident),
-}
-
-impl Action {
- pub fn resolve(self, field_type: &Type) -> Method {
- match self {
- Self::Explicit(method) => method,
- Self::Implicit(ident) => default_action(field_type, ident.span()),
- }
- }
-
- pub fn span(&self) -> Span {
- match self {
- Self::Explicit(method) => method.name.span(),
- Self::Implicit(ident) => ident.span(),
- }
- }
-}
-
-fn default_action(field_type: &Type, span: Span) -> Method {
- let ty = Ty::from_syn_ty(field_type);
- let args = match *ty {
- Ty::Vec | Ty::OptionVec => {
- quote_spanned! { span=>
- clap::ArgAction::Append
- }
- }
- Ty::Option | Ty::OptionOption => {
- quote_spanned! { span=>
- clap::ArgAction::Set
- }
- }
- _ => {
- if is_simple_ty(field_type, "bool") {
- quote_spanned! { span=>
- clap::ArgAction::SetTrue
- }
- } else {
- quote_spanned! { span=>
- clap::ArgAction::Set
- }
- }
- }
- };
-
- let func = Ident::new("action", span);
- Method::new(func, args)
-}
-
-#[allow(clippy::large_enum_variant)]
-#[derive(Clone)]
-pub enum Kind {
- Arg(Sp<Ty>),
- FromGlobal(Sp<Ty>),
- Subcommand(Sp<Ty>),
- Flatten,
- Skip(Option<Expr>),
- ExternalSubcommand,
-}
-
-#[derive(Clone)]
-pub struct Method {
- name: Ident,
- args: TokenStream,
-}
-
-impl Method {
- pub fn new(name: Ident, args: TokenStream) -> Self {
- Method { name, args }
- }
-
- fn from_env(ident: Ident, env_var: &str) -> Option<Self> {
- let mut lit = match env::var(env_var) {
- Ok(val) => {
- if val.is_empty() {
- return None;
- }
- LitStr::new(&val, ident.span())
- }
- Err(_) => {
- abort!(ident,
- "cannot derive `{}` from Cargo.toml", ident;
- note = "`{}` environment variable is not set", env_var;
- help = "use `{} = \"...\"` to set {} manually", ident, ident;
- );
- }
- };
-
- if ident == "author" {
- let edited = process_author_str(&lit.value());
- lit = LitStr::new(&edited, lit.span());
- }
-
- Some(Method::new(ident, quote!(#lit)))
- }
-
- pub(crate) fn args(&self) -> &TokenStream {
- &self.args
- }
-}
-
-impl ToTokens for Method {
- fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
- let Method { ref name, ref args } = self;
-
- let tokens = quote!( .#name(#args) );
-
- tokens.to_tokens(ts);
- }
-}
-
-/// replace all `:` with `, ` when not inside the `<>`
-///
-/// `"author1:author2:author3" => "author1, author2, author3"`
-/// `"author1 <http://website1.com>:author2" => "author1 <http://website1.com>, author2"
-fn process_author_str(author: &str) -> String {
- let mut res = String::with_capacity(author.len());
- let mut inside_angle_braces = 0usize;
-
- for ch in author.chars() {
- if inside_angle_braces > 0 && ch == '>' {
- inside_angle_braces -= 1;
- res.push(ch);
- } else if ch == '<' {
- inside_angle_braces += 1;
- res.push(ch);
- } else if inside_angle_braces == 0 && ch == ':' {
- res.push_str(", ");
- } else {
- res.push(ch);
- }
- }
-
- res
-}
-
-#[derive(Clone)]
-pub struct Parser {
- pub kind: Sp<ParserKind>,
- pub func: TokenStream,
-}
-
-impl Parser {
- fn from_type(field_type: &Type, span: Span) -> Sp<Self> {
- if is_simple_ty(field_type, "bool") {
- let kind = Sp::new(ParserKind::FromFlag, span);
- let func = quote_spanned!(span=> ::std::convert::From::from);
- Sp::new(Parser { kind, func }, span)
- } else {
- let kind = Sp::new(ParserKind::TryFromStr, span);
- let func = quote_spanned!(span=> ::std::str::FromStr::from_str);
- Sp::new(Parser { kind, func }, span)
- }
- }
-
- fn from_spec(parse_ident: Ident, spec: ParserSpec) -> Sp<Self> {
- use self::ParserKind::*;
-
- let kind = match &*spec.kind.to_string() {
- "from_str" => FromStr,
- "try_from_str" => TryFromStr,
- "from_os_str" => FromOsStr,
- "try_from_os_str" => TryFromOsStr,
- "from_occurrences" => FromOccurrences,
- "from_flag" => FromFlag,
- s => abort!(spec.kind.span(), "unsupported parser `{}`", s),
- };
-
- let func = match spec.parse_func {
- None => match kind {
- FromStr | FromOsStr => {
- quote_spanned!(spec.kind.span()=> ::std::convert::From::from)
- }
- TryFromStr => quote_spanned!(spec.kind.span()=> ::std::str::FromStr::from_str),
- TryFromOsStr => abort!(
- spec.kind.span(),
- "you must set parser for `try_from_os_str` explicitly"
- ),
- FromOccurrences => quote_spanned!(spec.kind.span()=> { |v| v as _ }),
- FromFlag => quote_spanned!(spec.kind.span()=> ::std::convert::From::from),
- },
-
- Some(func) => match func {
- Expr::Path(_) => quote!(#func),
- _ => abort!(func, "`parse` argument must be a function path"),
- },
- };
-
- let kind = Sp::new(kind, spec.kind.span());
- let parser = Parser { kind, func };
- Sp::new(parser, parse_ident.span())
- }
-
- fn value_parser(&self) -> Method {
- let func = Ident::new("value_parser", self.kind.span());
- match *self.kind {
- ParserKind::FromStr | ParserKind::TryFromStr => Method::new(
- func,
- quote_spanned! { self.kind.span()=>
- clap::builder::ValueParser::string()},
- ),
- ParserKind::FromOsStr | ParserKind::TryFromOsStr => Method::new(
- func,
- quote_spanned! { self.kind.span()=> clap::builder::ValueParser::os_string()},
- ),
- ParserKind::FromOccurrences => Method::new(
- func,
- quote_spanned! { self.kind.span()=> clap::value_parser!(u64)},
- ),
- ParserKind::FromFlag => Method::new(
- func,
- quote_spanned! { self.kind.span()=> clap::builder::ValueParser::bool()},
- ),
- }
- }
-
- fn action(&self) -> Method {
- let func = Ident::new("action", self.kind.span());
- match *self.kind {
- ParserKind::FromStr
- | ParserKind::TryFromStr
- | ParserKind::FromOsStr
- | ParserKind::TryFromOsStr => Method::new(
- func,
- quote_spanned! { self.kind.span()=> clap::ArgAction::StoreValue},
- ),
- ParserKind::FromOccurrences | ParserKind::FromFlag => Method::new(
- func,
- quote_spanned! { self.kind.span()=> clap::ArgAction::IncOccurrence},
- ),
- }
- }
-}
-
-#[derive(Debug, PartialEq, Clone, Copy)]
-pub enum ParserKind {
- FromStr,
- TryFromStr,
- FromOsStr,
- TryFromOsStr,
- FromOccurrences,
- FromFlag,
-}
-
-/// Defines the casing for the attributes long representation.
-#[derive(Copy, Clone, Debug, PartialEq)]
-pub enum CasingStyle {
- /// Indicate word boundaries with uppercase letter, excluding the first word.
- Camel,
- /// Keep all letters lowercase and indicate word boundaries with hyphens.
- Kebab,
- /// Indicate word boundaries with uppercase letter, including the first word.
- Pascal,
- /// Keep all letters uppercase and indicate word boundaries with underscores.
- ScreamingSnake,
- /// Keep all letters lowercase and indicate word boundaries with underscores.
- Snake,
- /// Keep all letters lowercase and remove word boundaries.
- Lower,
- /// Keep all letters uppercase and remove word boundaries.
- Upper,
- /// Use the original attribute name defined in the code.
- Verbatim,
-}
-
-impl CasingStyle {
- fn from_lit(name: LitStr) -> Sp<Self> {
- use self::CasingStyle::*;
-
- let normalized = name.value().to_upper_camel_case().to_lowercase();
- let cs = |kind| Sp::new(kind, name.span());
-
- match normalized.as_ref() {
- "camel" | "camelcase" => cs(Camel),
- "kebab" | "kebabcase" => cs(Kebab),
- "pascal" | "pascalcase" => cs(Pascal),
- "screamingsnake" | "screamingsnakecase" => cs(ScreamingSnake),
- "snake" | "snakecase" => cs(Snake),
- "lower" | "lowercase" => cs(Lower),
- "upper" | "uppercase" => cs(Upper),
- "verbatim" | "verbatimcase" => cs(Verbatim),
- s => abort!(name, "unsupported casing: `{}`", s),
- }
- }
-}
-
-#[derive(Clone)]
-pub enum Name {
- Derived(Ident),
- Assigned(TokenStream),
-}
-
-impl Name {
- #[cfg(feature = "unstable-v4")]
- pub fn raw(self) -> TokenStream {
- match self {
- Name::Assigned(tokens) => tokens,
- Name::Derived(ident) => {
- let s = ident.unraw().to_string();
- quote_spanned!(ident.span()=> #s)
- }
- }
- }
-
- pub fn translate(self, style: CasingStyle) -> TokenStream {
- use CasingStyle::*;
-
- match self {
- Name::Assigned(tokens) => tokens,
- Name::Derived(ident) => {
- let s = ident.unraw().to_string();
- let s = match style {
- Pascal => s.to_upper_camel_case(),
- Kebab => s.to_kebab_case(),
- Camel => s.to_lower_camel_case(),
- ScreamingSnake => s.to_shouty_snake_case(),
- Snake => s.to_snake_case(),
- Lower => s.to_snake_case().replace('_', ""),
- Upper => s.to_shouty_snake_case().replace('_', ""),
- Verbatim => s,
- };
- quote_spanned!(ident.span()=> #s)
- }
- }
- }
-
- pub fn translate_char(self, style: CasingStyle) -> TokenStream {
- use CasingStyle::*;
-
- match self {
- Name::Assigned(tokens) => quote!( (#tokens).chars().next().unwrap() ),
- Name::Derived(ident) => {
- let s = ident.unraw().to_string();
- let s = match style {
- Pascal => s.to_upper_camel_case(),
- Kebab => s.to_kebab_case(),
- Camel => s.to_lower_camel_case(),
- ScreamingSnake => s.to_shouty_snake_case(),
- Snake => s.to_snake_case(),
- Lower => s.to_snake_case(),
- Upper => s.to_shouty_snake_case(),
- Verbatim => s,
- };
-
- let s = s.chars().next().unwrap();
- quote_spanned!(ident.span()=> #s)
- }
- }
- }
-}
diff --git a/src/derives/args.rs b/src/derives/args.rs
index 4195a08..ad2a646 100644
--- a/src/derives/args.rs
+++ b/src/derives/args.rs
@@ -12,20 +12,19 @@
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
-use crate::{
- attrs::{Attrs, Kind, Name, ParserKind, DEFAULT_CASING, DEFAULT_ENV_CASING},
- dummies,
- utils::{inner_type, is_simple_ty, sub_type, Sp, Ty},
-};
-
use proc_macro2::{Ident, Span, TokenStream};
use proc_macro_error::{abort, abort_call_site};
use quote::{format_ident, quote, quote_spanned};
+use syn::ext::IdentExt;
use syn::{
- punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, DataStruct,
- DeriveInput, Field, Fields, Generics, Type,
+ punctuated::Punctuated, spanned::Spanned, token::Comma, Data, DataStruct, DeriveInput, Field,
+ Fields, Generics,
};
+use crate::dummies;
+use crate::item::{Item, Kind, Name};
+use crate::utils::{inner_type, sub_type, Sp, Ty};
+
pub fn derive_args(input: &DeriveInput) -> TokenStream {
let ident = &input.ident;
@@ -35,87 +34,68 @@ pub fn derive_args(input: &DeriveInput) -> TokenStream {
Data::Struct(DataStruct {
fields: Fields::Named(ref fields),
..
- }) => gen_for_struct(ident, &input.generics, &fields.named, &input.attrs),
+ }) => {
+ let name = Name::Derived(ident.clone());
+ let item = Item::from_args_struct(input, name);
+ let fields = fields
+ .named
+ .iter()
+ .map(|field| {
+ let item = Item::from_args_field(field, item.casing(), item.env_casing());
+ (field, item)
+ })
+ .collect::<Vec<_>>();
+ gen_for_struct(&item, ident, &input.generics, &fields)
+ }
Data::Struct(DataStruct {
fields: Fields::Unit,
..
- }) => gen_for_struct(
- ident,
- &input.generics,
- &Punctuated::<Field, Comma>::new(),
- &input.attrs,
- ),
+ }) => {
+ let name = Name::Derived(ident.clone());
+ let item = Item::from_args_struct(input, name);
+ let fields = Punctuated::<Field, Comma>::new();
+ let fields = fields
+ .iter()
+ .map(|field| {
+ let item = Item::from_args_field(field, item.casing(), item.env_casing());
+ (field, item)
+ })
+ .collect::<Vec<_>>();
+ gen_for_struct(&item, ident, &input.generics, &fields)
+ }
_ => abort_call_site!("`#[derive(Args)]` only supports non-tuple structs"),
}
}
pub fn gen_for_struct(
- struct_name: &Ident,
+ item: &Item,
+ item_name: &Ident,
generics: &Generics,
- fields: &Punctuated<Field, Comma>,
- attrs: &[Attribute],
+ fields: &[(&Field, Item)],
) -> TokenStream {
- let from_arg_matches = gen_from_arg_matches_for_struct(struct_name, generics, fields, attrs);
-
- let attrs = Attrs::from_struct(
- Span::call_site(),
- attrs,
- Name::Derived(struct_name.clone()),
- Sp::call_site(DEFAULT_CASING),
- Sp::call_site(DEFAULT_ENV_CASING),
- );
- let app_var = Ident::new("__clap_app", Span::call_site());
- let augmentation = gen_augment(fields, &app_var, &attrs, false);
- let augmentation_update = gen_augment(fields, &app_var, &attrs, true);
-
- let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
-
- quote! {
- #from_arg_matches
-
- #[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
- #[allow(
- clippy::style,
- clippy::complexity,
- clippy::pedantic,
- clippy::restriction,
- clippy::perf,
- clippy::deprecated,
- clippy::nursery,
- clippy::cargo,
- clippy::suspicious_else_formatting,
- )]
- #[deny(clippy::correctness)]
- impl #impl_generics clap::Args for #struct_name #ty_generics #where_clause {
- fn augment_args<'b>(#app_var: clap::Command<'b>) -> clap::Command<'b> {
- #augmentation
- }
- fn augment_args_for_update<'b>(#app_var: clap::Command<'b>) -> clap::Command<'b> {
- #augmentation_update
- }
+ if !matches!(&*item.kind(), Kind::Command(_)) {
+ abort! { item.kind().span(),
+ "`{}` cannot be used with `command`",
+ item.kind().name(),
}
}
-}
-pub fn gen_from_arg_matches_for_struct(
- struct_name: &Ident,
- generics: &Generics,
- fields: &Punctuated<Field, Comma>,
- attrs: &[Attribute],
-) -> TokenStream {
- let attrs = Attrs::from_struct(
- Span::call_site(),
- attrs,
- Name::Derived(struct_name.clone()),
- Sp::call_site(DEFAULT_CASING),
- Sp::call_site(DEFAULT_ENV_CASING),
- );
-
- let constructor = gen_constructor(fields, &attrs);
- let updater = gen_updater(fields, &attrs, true);
+ let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+
+ let constructor = gen_constructor(fields);
+ let updater = gen_updater(fields, true);
let raw_deprecated = raw_deprecated();
- let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+ let app_var = Ident::new("__clap_app", Span::call_site());
+ let augmentation = gen_augment(fields, &app_var, item, false);
+ let augmentation_update = gen_augment(fields, &app_var, item, true);
+
+ let group_id = if item.skip_group() {
+ quote!(None)
+ } else {
+ let group_id = item.ident().unraw().to_string();
+ quote!(Some(clap::Id::from(#group_id)))
+ };
quote! {
#[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
@@ -131,14 +111,14 @@ pub fn gen_from_arg_matches_for_struct(
clippy::suspicious_else_formatting,
)]
#[deny(clippy::correctness)]
- impl #impl_generics clap::FromArgMatches for #struct_name #ty_generics #where_clause {
+ impl #impl_generics clap::FromArgMatches for #item_name #ty_generics #where_clause {
fn from_arg_matches(__clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> {
Self::from_arg_matches_mut(&mut __clap_arg_matches.clone())
}
fn from_arg_matches_mut(__clap_arg_matches: &mut clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> {
#raw_deprecated
- let v = #struct_name #constructor;
+ let v = #item_name #constructor;
::std::result::Result::Ok(v)
}
@@ -152,229 +132,139 @@ pub fn gen_from_arg_matches_for_struct(
::std::result::Result::Ok(())
}
}
+
+ #[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
+ #[allow(
+ clippy::style,
+ clippy::complexity,
+ clippy::pedantic,
+ clippy::restriction,
+ clippy::perf,
+ clippy::deprecated,
+ clippy::nursery,
+ clippy::cargo,
+ clippy::suspicious_else_formatting,
+ )]
+ #[deny(clippy::correctness)]
+ impl #impl_generics clap::Args for #item_name #ty_generics #where_clause {
+ fn group_id() -> Option<clap::Id> {
+ #group_id
+ }
+ fn augment_args<'b>(#app_var: clap::Command) -> clap::Command {
+ #augmentation
+ }
+ fn augment_args_for_update<'b>(#app_var: clap::Command) -> clap::Command {
+ #augmentation_update
+ }
+ }
}
}
/// Generate a block of code to add arguments/subcommands corresponding to
/// the `fields` to an cmd.
pub fn gen_augment(
- fields: &Punctuated<Field, Comma>,
+ fields: &[(&Field, Item)],
app_var: &Ident,
- parent_attribute: &Attrs,
+ parent_item: &Item,
override_required: bool,
) -> TokenStream {
- let mut subcmds = fields.iter().filter_map(|field| {
- let attrs = Attrs::from_field(
- field,
- parent_attribute.casing(),
- parent_attribute.env_casing(),
- );
- let kind = attrs.kind();
- if let Kind::Subcommand(ty) = &*kind {
- let subcmd_type = match (**ty, sub_type(&field.ty)) {
- (Ty::Option, Some(sub_type)) => sub_type,
- _ => &field.ty,
- };
- let required = if **ty == Ty::Option {
- quote!()
- } else {
- quote_spanned! { kind.span()=>
- #[allow(deprecated)]
- let #app_var = #app_var.setting(
- clap::AppSettings::SubcommandRequiredElseHelp
- );
+ let mut subcommand_specified = false;
+ let args = fields.iter().filter_map(|(field, item)| {
+ let kind = item.kind();
+ match &*kind {
+ Kind::Command(_)
+ | Kind::Value
+ | Kind::Skip(_, _)
+ | Kind::FromGlobal(_)
+ | Kind::ExternalSubcommand => None,
+ Kind::Subcommand(ty) => {
+ if subcommand_specified {
+ abort!(field.span(), "`#[command(subcommand)]` can only be used once per container");
}
- };
+ subcommand_specified = true;
- let span = field.span();
- let ts = if override_required {
- quote! {
- let #app_var = <#subcmd_type as clap::Subcommand>::augment_subcommands_for_update( #app_var );
- }
- } else{
- quote! {
+ let subcmd_type = match (**ty, sub_type(&field.ty)) {
+ (Ty::Option, Some(sub_type)) => sub_type,
+ _ => &field.ty,
+ };
+ let implicit_methods = if **ty == Ty::Option || override_required {
+ quote!()
+ } else {
+ quote_spanned! { kind.span()=>
+ .subcommand_required(true)
+ .arg_required_else_help(true)
+ }
+ };
+
+ Some(quote! {
let #app_var = <#subcmd_type as clap::Subcommand>::augment_subcommands( #app_var );
- #required
- }
- };
- Some((span, ts))
- } else {
- None
- }
- });
- let subcmd = subcmds.next().map(|(_, ts)| ts);
- if let Some((span, _)) = subcmds.next() {
- abort!(
- span,
- "multiple subcommand sets are not allowed, that's the second"
- );
- }
+ let #app_var = #app_var
+ #implicit_methods;
+ })
+ }
+ Kind::Flatten(ty) => {
+ let inner_type = match (**ty, sub_type(&field.ty)) {
+ (Ty::Option, Some(sub_type)) => sub_type,
+ _ => &field.ty,
+ };
- let args = fields.iter().filter_map(|field| {
- let attrs = Attrs::from_field(
- field,
- parent_attribute.casing(),
- parent_attribute.env_casing(),
- );
- let kind = attrs.kind();
- match &*kind {
- Kind::Subcommand(_)
- | Kind::Skip(_)
- | Kind::FromGlobal(_)
- | Kind::ExternalSubcommand => None,
- Kind::Flatten => {
- let ty = &field.ty;
- let old_heading_var = format_ident!("__clap_old_heading");
- let next_help_heading = attrs.next_help_heading();
- let next_display_order = attrs.next_display_order();
+ let next_help_heading = item.next_help_heading();
+ let next_display_order = item.next_display_order();
if override_required {
Some(quote_spanned! { kind.span()=>
- let #old_heading_var = #app_var.get_next_help_heading();
- let #app_var = #app_var #next_help_heading #next_display_order;
- let #app_var = <#ty as clap::Args>::augment_args_for_update(#app_var);
- let #app_var = #app_var.next_help_heading(#old_heading_var);
+ let #app_var = #app_var
+ #next_help_heading
+ #next_display_order;
+ let #app_var = <#inner_type as clap::Args>::augment_args_for_update(#app_var);
})
} else {
Some(quote_spanned! { kind.span()=>
- let #old_heading_var = #app_var.get_next_help_heading();
- let #app_var = #app_var #next_help_heading #next_display_order;
- let #app_var = <#ty as clap::Args>::augment_args(#app_var);
- let #app_var = #app_var.next_help_heading(#old_heading_var);
+ let #app_var = #app_var
+ #next_help_heading
+ #next_display_order;
+ let #app_var = <#inner_type as clap::Args>::augment_args(#app_var);
})
}
}
Kind::Arg(ty) => {
- let convert_type = inner_type(&field.ty);
-
- let parser = attrs.parser(&field.ty);
-
- let value_parser = attrs.value_parser(&field.ty);
- let action = attrs.action(&field.ty);
- let func = &parser.func;
-
- let mut occurrences = false;
- let mut flag = false;
- let validator = match *parser.kind {
- _ if attrs.ignore_parser() || attrs.is_enum() => quote!(),
- ParserKind::TryFromStr => quote_spanned! { func.span()=>
- .validator(|s| {
- #func(s)
- .map(|_: #convert_type| ())
- })
- },
- ParserKind::TryFromOsStr => quote_spanned! { func.span()=>
- .validator_os(|s| #func(s).map(|_: #convert_type| ()))
- },
- ParserKind::FromStr | ParserKind::FromOsStr => quote!(),
- ParserKind::FromFlag => {
- flag = true;
- quote!()
- }
- ParserKind::FromOccurrences => {
- occurrences = true;
- quote!()
- }
- };
- let parse_deprecation = match *parser.kind {
- _ if !attrs.explicit_parser() || cfg!(not(feature = "deprecated")) => quote!(),
- ParserKind::FromStr => quote_spanned! { func.span()=>
- #[deprecated(since = "3.2.0", note = "Replaced with `#[clap(value_parser = ...)]`")]
- fn parse_from_str() {
- }
- parse_from_str();
- },
- ParserKind::TryFromStr => quote_spanned! { func.span()=>
- #[deprecated(since = "3.2.0", note = "Replaced with `#[clap(value_parser = ...)]`")]
- fn parse_try_from_str() {
- }
- parse_try_from_str();
- },
- ParserKind::FromOsStr => quote_spanned! { func.span()=>
- #[deprecated(since = "3.2.0", note = "Replaced with `#[clap(value_parser)]` for `PathBuf` or `#[clap(value_parser = ...)]` with a custom `TypedValueParser`")]
- fn parse_from_os_str() {
- }
- parse_from_os_str();
- },
- ParserKind::TryFromOsStr => quote_spanned! { func.span()=>
- #[deprecated(since = "3.2.0", note = "Replaced with `#[clap(value_parser = ...)]` with a custom `TypedValueParser`")]
- fn parse_try_from_os_str() {
- }
- parse_try_from_os_str();
- },
- ParserKind::FromFlag => quote_spanned! { func.span()=>
- #[deprecated(since = "3.2.0", note = "Replaced with `#[clap(action = ArgAction::SetTrue)]`")]
- fn parse_from_flag() {
- }
- parse_from_flag();
- },
- ParserKind::FromOccurrences => quote_spanned! { func.span()=>
- #[deprecated(since = "3.2.0", note = "Replaced with `#[clap(action = ArgAction::Count)]` with a field type of `u8`")]
- fn parse_from_occurrences() {
- }
- parse_from_occurrences();
- },
- };
-
- let value_name = attrs.value_name();
- let possible_values = if attrs.is_enum() && !attrs.ignore_parser() {
- gen_value_enum_possible_values(convert_type)
- } else {
- quote!()
- };
+ let value_parser = item.value_parser(&field.ty);
+ let action = item.action(&field.ty);
+ let value_name = item.value_name();
let implicit_methods = match **ty {
+ Ty::Unit => {
+ // Leaving out `value_parser` as it will always fail
+ quote_spanned! { ty.span()=>
+ .value_name(#value_name)
+ #action
+ }
+ }
Ty::Option => {
quote_spanned! { ty.span()=>
- .takes_value(true)
.value_name(#value_name)
- #possible_values
- #validator
#value_parser
#action
}
}
Ty::OptionOption => quote_spanned! { ty.span()=>
- .takes_value(true)
.value_name(#value_name)
- .min_values(0)
- .max_values(1)
- .multiple_values(false)
- #possible_values
- #validator
+ .num_args(0..=1)
#value_parser
#action
},
Ty::OptionVec => {
- if attrs.ignore_parser() {
- if attrs.is_positional() {
- quote_spanned! { ty.span()=>
- .takes_value(true)
- .value_name(#value_name)
- .multiple_values(true) // action won't be sufficient for getting multiple
- #possible_values
- #validator
- #value_parser
- #action
- }
- } else {
- quote_spanned! { ty.span()=>
- .takes_value(true)
- .value_name(#value_name)
- #possible_values
- #validator
- #value_parser
- #action
- }
+ if item.is_positional() {
+ quote_spanned! { ty.span()=>
+ .value_name(#value_name)
+ .num_args(1..) // action won't be sufficient for getting multiple
+ #value_parser
+ #action
}
} else {
quote_spanned! { ty.span()=>
- .takes_value(true)
.value_name(#value_name)
- .multiple_occurrences(true)
- #possible_values
- #validator
#value_parser
#action
}
@@ -382,72 +272,48 @@ pub fn gen_augment(
}
Ty::Vec => {
- if attrs.ignore_parser() {
- if attrs.is_positional() {
- quote_spanned! { ty.span()=>
- .takes_value(true)
- .value_name(#value_name)
- .multiple_values(true) // action won't be sufficient for getting multiple
- #possible_values
- #validator
- #value_parser
- #action
- }
- } else {
- quote_spanned! { ty.span()=>
- .takes_value(true)
- .value_name(#value_name)
- #possible_values
- #validator
- #value_parser
- #action
- }
+ if item.is_positional() {
+ quote_spanned! { ty.span()=>
+ .value_name(#value_name)
+ .num_args(1..) // action won't be sufficient for getting multiple
+ #value_parser
+ #action
}
} else {
quote_spanned! { ty.span()=>
- .takes_value(true)
.value_name(#value_name)
- .multiple_occurrences(true)
- #possible_values
- #validator
#value_parser
#action
}
}
}
- Ty::Other if occurrences => quote_spanned! { ty.span()=>
- .multiple_occurrences(true)
- },
-
- Ty::Other if flag => quote_spanned! { ty.span()=>
- .takes_value(false)
- },
-
Ty::Other => {
- let required = attrs.find_default_method().is_none() && !override_required;
+ let required = item.find_default_method().is_none() && !override_required;
// `ArgAction::takes_values` is assuming `ArgAction::default_value` will be
// set though that won't always be true but this should be good enough,
// otherwise we'll report an "arg required" error when unwrapping.
let action_value = action.args();
quote_spanned! { ty.span()=>
- .takes_value(true)
.value_name(#value_name)
.required(#required && #action_value.takes_values())
- #possible_values
- #validator
#value_parser
#action
}
}
};
- let id = attrs.id();
- let explicit_methods = attrs.field_methods(true);
+ let id = item.id();
+ let explicit_methods = item.field_methods();
+ let deprecations = if !override_required {
+ item.deprecations()
+ } else {
+ quote!()
+ };
Some(quote_spanned! { field.span()=>
let #app_var = #app_var.arg({
- #parse_deprecation
+ #deprecations
#[allow(deprecated)]
let arg = clap::Arg::new(#id)
@@ -462,36 +328,80 @@ pub fn gen_augment(
}
});
- let initial_app_methods = parent_attribute.initial_top_level_methods();
- let final_app_methods = parent_attribute.final_top_level_methods();
+ let deprecations = if !override_required {
+ parent_item.deprecations()
+ } else {
+ quote!()
+ };
+ let initial_app_methods = parent_item.initial_top_level_methods();
+ let final_app_methods = parent_item.final_top_level_methods();
+ let group_app_methods = if parent_item.skip_group() {
+ quote!()
+ } else {
+ let group_id = parent_item.ident().unraw().to_string();
+ let literal_group_members = fields
+ .iter()
+ .filter_map(|(_field, item)| {
+ let kind = item.kind();
+ if matches!(*kind, Kind::Arg(_)) {
+ Some(item.id())
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>();
+ let literal_group_members_len = literal_group_members.len();
+ let mut literal_group_members = quote! {{
+ let members: [clap::Id; #literal_group_members_len] = [#( clap::Id::from(#literal_group_members) ),* ];
+ members
+ }};
+ // HACK: Validation isn't ready yet for nested arg groups, so just don't populate the group in
+ // that situation
+ let possible_group_members_len = fields
+ .iter()
+ .filter(|(_field, item)| {
+ let kind = item.kind();
+ matches!(*kind, Kind::Flatten(_))
+ })
+ .count();
+ if 0 < possible_group_members_len {
+ literal_group_members = quote! {{
+ let members: [clap::Id; 0] = [];
+ members
+ }};
+ }
+
+ quote!(
+ .group(
+ clap::ArgGroup::new(#group_id)
+ .multiple(true)
+ .args(#literal_group_members)
+ )
+ )
+ };
quote! {{
- let #app_var = #app_var #initial_app_methods;
+ #deprecations
+ let #app_var = #app_var
+ #initial_app_methods
+ #group_app_methods
+ ;
#( #args )*
- #subcmd
#app_var #final_app_methods
}}
}
-fn gen_value_enum_possible_values(ty: &Type) -> TokenStream {
- quote_spanned! { ty.span()=>
- .possible_values(<#ty as clap::ValueEnum>::value_variants().iter().filter_map(clap::ValueEnum::to_possible_value))
- }
-}
-
-pub fn gen_constructor(fields: &Punctuated<Field, Comma>, parent_attribute: &Attrs) -> TokenStream {
- let fields = fields.iter().map(|field| {
- let attrs = Attrs::from_field(
- field,
- parent_attribute.casing(),
- parent_attribute.env_casing(),
- );
+pub fn gen_constructor(fields: &[(&Field, Item)]) -> TokenStream {
+ let fields = fields.iter().map(|(field, item)| {
let field_name = field.ident.as_ref().unwrap();
- let kind = attrs.kind();
+ let kind = item.kind();
let arg_matches = format_ident!("__clap_arg_matches");
match &*kind {
- Kind::ExternalSubcommand => {
+ Kind::Command(_)
+ | Kind::Value
+ | Kind::ExternalSubcommand => {
abort! { kind.span(),
- "`external_subcommand` can be used only on enum variants"
+ "`{}` cannot be used with `arg`",
+ kind.name(),
}
}
Kind::Subcommand(ty) => {
@@ -511,27 +421,72 @@ pub fn gen_constructor(fields: &Punctuated<Field, Comma>, parent_attribute: &Att
}
}
},
- _ => {
+ Ty::Other => {
quote_spanned! { kind.span()=>
#field_name: {
<#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?
}
}
},
+ Ty::Unit |
+ Ty::Vec |
+ Ty::OptionOption |
+ Ty::OptionVec => {
+ abort!(
+ ty.span(),
+ "{} types are not supported for subcommand",
+ ty.as_str()
+ );
+ }
}
}
- Kind::Flatten => quote_spanned! { kind.span()=>
- #field_name: clap::FromArgMatches::from_arg_matches_mut(#arg_matches)?
+ Kind::Flatten(ty) => {
+ let inner_type = match (**ty, sub_type(&field.ty)) {
+ (Ty::Option, Some(sub_type)) => sub_type,
+ _ => &field.ty,
+ };
+ match **ty {
+ Ty::Other => {
+ quote_spanned! { kind.span()=>
+ #field_name: <#inner_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?
+ }
+ },
+ Ty::Option => {
+ quote_spanned! { kind.span()=>
+ #field_name: {
+ let group_id = <#inner_type as clap::Args>::group_id()
+ .expect("`#[arg(flatten)]`ed field type implements `Args::group_id`");
+ if #arg_matches.contains_id(group_id.as_str()) {
+ Some(
+ <#inner_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?
+ )
+ } else {
+ None
+ }
+ }
+ }
+ },
+ Ty::Unit |
+ Ty::Vec |
+ Ty::OptionOption |
+ Ty::OptionVec => {
+ abort!(
+ ty.span(),
+ "{} types are not supported for flatten",
+ ty.as_str()
+ );
+ }
+ }
},
- Kind::Skip(val) => match val {
+ Kind::Skip(val, _) => match val {
None => quote_spanned!(kind.span()=> #field_name: Default::default()),
Some(val) => quote_spanned!(kind.span()=> #field_name: (#val).into()),
},
Kind::Arg(ty) | Kind::FromGlobal(ty) => {
- gen_parsers(&attrs, ty, field_name, field, None)
+ gen_parsers(item, ty, field_name, field, None)
}
}
});
@@ -541,19 +496,10 @@ pub fn gen_constructor(fields: &Punctuated<Field, Comma>, parent_attribute: &Att
}}
}
-pub fn gen_updater(
- fields: &Punctuated<Field, Comma>,
- parent_attribute: &Attrs,
- use_self: bool,
-) -> TokenStream {
- let fields = fields.iter().map(|field| {
- let attrs = Attrs::from_field(
- field,
- parent_attribute.casing(),
- parent_attribute.env_casing(),
- );
+pub fn gen_updater(fields: &[(&Field, Item)], use_self: bool) -> TokenStream {
+ let fields = fields.iter().map(|(field, item)| {
let field_name = field.ident.as_ref().unwrap();
- let kind = attrs.kind();
+ let kind = item.kind();
let access = if use_self {
quote! {
@@ -566,9 +512,12 @@ pub fn gen_updater(
let arg_matches = format_ident!("__clap_arg_matches");
match &*kind {
- Kind::ExternalSubcommand => {
+ Kind::Command(_)
+ | Kind::Value
+ | Kind::ExternalSubcommand => {
abort! { kind.span(),
- "`external_subcommand` can be used only on enum variants"
+ "`{}` cannot be used with `arg`",
+ kind.name(),
}
}
Kind::Subcommand(ty) => {
@@ -604,15 +553,42 @@ pub fn gen_updater(
}
}
- Kind::Flatten => quote_spanned! { kind.span()=> {
- #access
- clap::FromArgMatches::update_from_arg_matches_mut(#field_name, #arg_matches)?;
+ Kind::Flatten(ty) => {
+ let inner_type = match (**ty, sub_type(&field.ty)) {
+ (Ty::Option, Some(sub_type)) => sub_type,
+ _ => &field.ty,
+ };
+
+ let updater = quote_spanned! { ty.span()=>
+ <#inner_type as clap::FromArgMatches>::update_from_arg_matches_mut(#field_name, #arg_matches)?;
+ };
+
+ let updater = match **ty {
+ Ty::Option => quote_spanned! { kind.span()=>
+ if let Some(#field_name) = #field_name.as_mut() {
+ #updater
+ } else {
+ *#field_name = Some(<#inner_type as clap::FromArgMatches>::from_arg_matches_mut(
+ #arg_matches
+ )?);
+ }
+ },
+ _ => quote_spanned! { kind.span()=>
+ #updater
+ },
+ };
+
+ quote_spanned! { kind.span()=>
+ {
+ #access
+ #updater
+ }
}
},
- Kind::Skip(_) => quote!(),
+ Kind::Skip(_, _) => quote!(),
- Kind::Arg(ty) | Kind::FromGlobal(ty) => gen_parsers(&attrs, ty, field_name, field, Some(&access)),
+ Kind::Arg(ty) | Kind::FromGlobal(ty) => gen_parsers(item, ty, field_name, field, Some(&access)),
}
});
@@ -622,78 +598,19 @@ pub fn gen_updater(
}
fn gen_parsers(
- attrs: &Attrs,
+ item: &Item,
ty: &Sp<Ty>,
field_name: &Ident,
field: &Field,
update: Option<&TokenStream>,
) -> TokenStream {
- use self::ParserKind::*;
-
- let parser = attrs.parser(&field.ty);
- let func = &parser.func;
- let span = parser.kind.span();
+ let span = ty.span();
let convert_type = inner_type(&field.ty);
- let id = attrs.id();
- let mut flag = false;
- let mut occurrences = false;
- let (get_one, get_many, deref, mut parse) = match *parser.kind {
- _ if attrs.ignore_parser() => (
- quote_spanned!(span=> remove_one::<#convert_type>),
- quote_spanned!(span=> remove_many::<#convert_type>),
- quote!(|s| s),
- quote_spanned!(func.span()=> |s| ::std::result::Result::Ok::<_, clap::Error>(s)),
- ),
- FromOccurrences => {
- occurrences = true;
- (
- quote_spanned!(span=> occurrences_of),
- quote!(),
- quote!(|s| ::std::ops::Deref::deref(s)),
- func.clone(),
- )
- }
- FromFlag => {
- flag = true;
- (
- quote!(),
- quote!(),
- quote!(|s| ::std::ops::Deref::deref(s)),
- func.clone(),
- )
- }
- FromStr => (
- quote_spanned!(span=> get_one::<String>),
- quote_spanned!(span=> get_many::<String>),
- quote!(|s| ::std::ops::Deref::deref(s)),
- quote_spanned!(func.span()=> |s| ::std::result::Result::Ok::<_, clap::Error>(#func(s))),
- ),
- TryFromStr => (
- quote_spanned!(span=> get_one::<String>),
- quote_spanned!(span=> get_many::<String>),
- quote!(|s| ::std::ops::Deref::deref(s)),
- quote_spanned!(func.span()=> |s| #func(s).map_err(|err| clap::Error::raw(clap::ErrorKind::ValueValidation, format!("Invalid value for {}: {}", #id, err)))),
- ),
- FromOsStr => (
- quote_spanned!(span=> get_one::<::std::ffi::OsString>),
- quote_spanned!(span=> get_many::<::std::ffi::OsString>),
- quote!(|s| ::std::ops::Deref::deref(s)),
- quote_spanned!(func.span()=> |s| ::std::result::Result::Ok::<_, clap::Error>(#func(s))),
- ),
- TryFromOsStr => (
- quote_spanned!(span=> get_one::<::std::ffi::OsString>),
- quote_spanned!(span=> get_many::<::std::ffi::OsString>),
- quote!(|s| ::std::ops::Deref::deref(s)),
- quote_spanned!(func.span()=> |s| #func(s).map_err(|err| clap::Error::raw(clap::ErrorKind::ValueValidation, format!("Invalid value for {}: {}", #id, err)))),
- ),
- };
- if attrs.is_enum() && !attrs.ignore_parser() {
- let ci = attrs.ignore_case();
-
- parse = quote_spanned! { convert_type.span()=>
- |s| <#convert_type as clap::ValueEnum>::from_str(s, #ci).map_err(|err| clap::Error::raw(clap::ErrorKind::ValueValidation, format!("Invalid value for {}: {}", #id, err)))
- }
- }
+ let id = item.id();
+ let get_one = quote_spanned!(span=> remove_one::<#convert_type>);
+ let get_many = quote_spanned!(span=> remove_many::<#convert_type>);
+ let deref = quote!(|s| s);
+ let parse = quote_spanned!(span=> |s| ::std::result::Result::Ok::<_, clap::Error>(s));
// Give this identifier the same hygiene
// as the `arg_matches` parameter definition. This
@@ -701,6 +618,12 @@ fn gen_parsers(
let arg_matches = format_ident!("__clap_arg_matches");
let field_value = match **ty {
+ Ty::Unit => {
+ quote_spanned! { ty.span()=>
+ ()
+ }
+ }
+
Ty::Option => {
quote_spanned! { ty.span()=>
#arg_matches.#get_one(#id)
@@ -742,29 +665,11 @@ fn gen_parsers(
}
}
- Ty::Other if occurrences => quote_spanned! { ty.span()=>
- #parse(
- #arg_matches.#get_one(#id)
- )
- },
-
- Ty::Other if flag => {
- if update.is_some() && is_simple_ty(&field.ty, "bool") {
- quote_spanned! { ty.span()=>
- *#field_name || #arg_matches.is_present(#id)
- }
- } else {
- quote_spanned! { ty.span()=>
- #parse(#arg_matches.is_present(#id))
- }
- }
- }
-
Ty::Other => {
quote_spanned! { ty.span()=>
#arg_matches.#get_one(#id)
.map(#deref)
- .ok_or_else(|| clap::Error::raw(clap::ErrorKind::MissingRequiredArgument, format!("The following required argument was not provided: {}", #id)))
+ .ok_or_else(|| clap::Error::raw(clap::error::ErrorKind::MissingRequiredArgument, format!("The following required argument was not provided: {}", #id)))
.and_then(#parse)?
}
}
diff --git a/src/derives/into_app.rs b/src/derives/into_app.rs
index 3197357..eabdfa3 100644
--- a/src/derives/into_app.rs
+++ b/src/derives/into_app.rs
@@ -12,36 +12,18 @@
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
-use std::env;
-
use proc_macro2::{Span, TokenStream};
use quote::quote;
-use syn::{Attribute, Generics, Ident};
+use syn::{Generics, Ident};
-use crate::{
- attrs::{Attrs, Name, DEFAULT_CASING, DEFAULT_ENV_CASING},
- utils::Sp,
-};
+use crate::item::Item;
-pub fn gen_for_struct(
- struct_name: &Ident,
- generics: &Generics,
- attrs: &[Attribute],
-) -> TokenStream {
- let app_name = env::var("CARGO_PKG_NAME").ok().unwrap_or_default();
+pub fn gen_for_struct(item: &Item, item_name: &Ident, generics: &Generics) -> TokenStream {
+ let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
- let attrs = Attrs::from_struct(
- Span::call_site(),
- attrs,
- Name::Assigned(quote!(#app_name)),
- Sp::call_site(DEFAULT_CASING),
- Sp::call_site(DEFAULT_ENV_CASING),
- );
- let name = attrs.cased_name();
+ let name = item.cased_name();
let app_var = Ident::new("__clap_app", Span::call_site());
- let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
-
let tokens = quote! {
#[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
#[allow(
@@ -56,14 +38,13 @@ pub fn gen_for_struct(
clippy::suspicious_else_formatting,
)]
#[deny(clippy::correctness)]
- #[allow(deprecated)]
- impl #impl_generics clap::CommandFactory for #struct_name #ty_generics #where_clause {
- fn into_app<'b>() -> clap::Command<'b> {
+ impl #impl_generics clap::CommandFactory for #item_name #ty_generics #where_clause {
+ fn command<'b>() -> clap::Command {
let #app_var = clap::Command::new(#name);
<Self as clap::Args>::augment_args(#app_var)
}
- fn into_app_for_update<'b>() -> clap::Command<'b> {
+ fn command_for_update<'b>() -> clap::Command {
let #app_var = clap::Command::new(#name);
<Self as clap::Args>::augment_args_for_update(#app_var)
}
@@ -73,21 +54,12 @@ pub fn gen_for_struct(
tokens
}
-pub fn gen_for_enum(enum_name: &Ident, generics: &Generics, attrs: &[Attribute]) -> TokenStream {
- let app_name = env::var("CARGO_PKG_NAME").ok().unwrap_or_default();
+pub fn gen_for_enum(item: &Item, item_name: &Ident, generics: &Generics) -> TokenStream {
+ let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
- let attrs = Attrs::from_struct(
- Span::call_site(),
- attrs,
- Name::Assigned(quote!(#app_name)),
- Sp::call_site(DEFAULT_CASING),
- Sp::call_site(DEFAULT_ENV_CASING),
- );
- let name = attrs.cased_name();
+ let name = item.cased_name();
let app_var = Ident::new("__clap_app", Span::call_site());
- let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
-
quote! {
#[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
#[allow(
@@ -102,15 +74,15 @@ pub fn gen_for_enum(enum_name: &Ident, generics: &Generics, attrs: &[Attribute])
clippy::suspicious_else_formatting,
)]
#[deny(clippy::correctness)]
- impl #impl_generics clap::CommandFactory for #enum_name #ty_generics #where_clause {
- fn into_app<'b>() -> clap::Command<'b> {
- #[allow(deprecated)]
+ impl #impl_generics clap::CommandFactory for #item_name #ty_generics #where_clause {
+ fn command<'b>() -> clap::Command {
let #app_var = clap::Command::new(#name)
- .setting(clap::AppSettings::SubcommandRequiredElseHelp);
+ .subcommand_required(true)
+ .arg_required_else_help(true);
<Self as clap::Subcommand>::augment_subcommands(#app_var)
}
- fn into_app_for_update<'b>() -> clap::Command<'b> {
+ fn command_for_update<'b>() -> clap::Command {
let #app_var = clap::Command::new(#name);
<Self as clap::Subcommand>::augment_subcommands_for_update(#app_var)
}
diff --git a/src/derives/parser.rs b/src/derives/parser.rs
index 621430f..617857c 100644
--- a/src/derives/parser.rs
+++ b/src/derives/parser.rs
@@ -12,21 +12,24 @@
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
-use crate::{
- derives::{args, into_app, subcommand},
- dummies,
-};
-
use proc_macro2::TokenStream;
use proc_macro_error::abort_call_site;
use quote::quote;
+use syn::Ident;
+use syn::Variant;
use syn::{
- self, punctuated::Punctuated, token::Comma, Attribute, Data, DataEnum, DataStruct, DeriveInput,
- Field, Fields, Generics, Ident,
+ self, punctuated::Punctuated, token::Comma, Data, DataStruct, DeriveInput, Field, Fields,
+ Generics,
};
+use crate::derives::{args, into_app, subcommand};
+use crate::dummies;
+use crate::item::Item;
+use crate::item::Name;
+
pub fn derive_parser(input: &DeriveInput) -> TokenStream {
let ident = &input.ident;
+ let pkg_name = std::env::var("CARGO_PKG_NAME").ok().unwrap_or_default();
match input.data {
Data::Struct(DataStruct {
@@ -34,41 +37,70 @@ pub fn derive_parser(input: &DeriveInput) -> TokenStream {
..
}) => {
dummies::parser_struct(ident);
- gen_for_struct(ident, &input.generics, &fields.named, &input.attrs)
+
+ let name = Name::Assigned(quote!(#pkg_name));
+ let item = Item::from_args_struct(input, name);
+ let fields = fields
+ .named
+ .iter()
+ .map(|field| {
+ let item = Item::from_args_field(field, item.casing(), item.env_casing());
+ (field, item)
+ })
+ .collect::<Vec<_>>();
+ gen_for_struct(&item, ident, &input.generics, &fields)
}
Data::Struct(DataStruct {
fields: Fields::Unit,
..
}) => {
dummies::parser_struct(ident);
- gen_for_struct(
- ident,
- &input.generics,
- &Punctuated::<Field, Comma>::new(),
- &input.attrs,
- )
+
+ let name = Name::Assigned(quote!(#pkg_name));
+ let item = Item::from_args_struct(input, name);
+ let fields = Punctuated::<Field, Comma>::new();
+ let fields = fields
+ .iter()
+ .map(|field| {
+ let item = Item::from_args_field(field, item.casing(), item.env_casing());
+ (field, item)
+ })
+ .collect::<Vec<_>>();
+ gen_for_struct(&item, ident, &input.generics, &fields)
}
Data::Enum(ref e) => {
dummies::parser_enum(ident);
- gen_for_enum(ident, &input.generics, &input.attrs, e)
+
+ let name = Name::Assigned(quote!(#pkg_name));
+ let item = Item::from_subcommand_enum(input, name);
+ let variants = e
+ .variants
+ .iter()
+ .map(|variant| {
+ let item =
+ Item::from_subcommand_variant(variant, item.casing(), item.env_casing());
+ (variant, item)
+ })
+ .collect::<Vec<_>>();
+ gen_for_enum(&item, ident, &input.generics, &variants)
}
_ => abort_call_site!("`#[derive(Parser)]` only supports non-tuple structs and enums"),
}
}
fn gen_for_struct(
- name: &Ident,
+ item: &Item,
+ item_name: &Ident,
generics: &Generics,
- fields: &Punctuated<Field, Comma>,
- attrs: &[Attribute],
+ fields: &[(&Field, Item)],
) -> TokenStream {
- let into_app = into_app::gen_for_struct(name, generics, attrs);
- let args = args::gen_for_struct(name, generics, fields, attrs);
-
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+ let into_app = into_app::gen_for_struct(item, item_name, generics);
+ let args = args::gen_for_struct(item, item_name, generics, fields);
+
quote! {
- impl #impl_generics clap::Parser for #name #ty_generics #where_clause {}
+ impl #impl_generics clap::Parser for #item_name #ty_generics #where_clause {}
#into_app
#args
@@ -76,18 +108,18 @@ fn gen_for_struct(
}
fn gen_for_enum(
- name: &Ident,
+ item: &Item,
+ item_name: &Ident,
generics: &Generics,
- attrs: &[Attribute],
- e: &DataEnum,
+ variants: &[(&Variant, Item)],
) -> TokenStream {
- let into_app = into_app::gen_for_enum(name, generics, attrs);
- let subcommand = subcommand::gen_for_enum(name, generics, attrs, e);
-
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+ let into_app = into_app::gen_for_enum(item, item_name, generics);
+ let subcommand = subcommand::gen_for_enum(item, item_name, generics, variants);
+
quote! {
- impl #impl_generics clap::Parser for #name #ty_generics #where_clause {}
+ impl #impl_generics clap::Parser for #item_name #ty_generics #where_clause {}
#into_app
#subcommand
diff --git a/src/derives/subcommand.rs b/src/derives/subcommand.rs
index 07bdce5..009684b 100644
--- a/src/derives/subcommand.rs
+++ b/src/derives/subcommand.rs
@@ -11,20 +11,16 @@
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
-use crate::{
- attrs::{Attrs, Kind, Name, DEFAULT_CASING, DEFAULT_ENV_CASING},
- derives::args,
- dummies,
- utils::{is_simple_ty, subty_if_name, Sp},
-};
use proc_macro2::{Ident, Span, TokenStream};
use proc_macro_error::{abort, abort_call_site};
use quote::{format_ident, quote, quote_spanned};
-use syn::{
- punctuated::Punctuated, spanned::Spanned, Attribute, Data, DataEnum, DeriveInput,
- FieldsUnnamed, Generics, Token, Variant,
-};
+use syn::{spanned::Spanned, Data, DeriveInput, FieldsUnnamed, Generics, Variant};
+
+use crate::derives::args;
+use crate::dummies;
+use crate::item::{Item, Kind, Name};
+use crate::utils::{is_simple_ty, subty_if_name};
pub fn derive_subcommand(input: &DeriveInput) -> TokenStream {
let ident = &input.ident;
@@ -32,35 +28,47 @@ pub fn derive_subcommand(input: &DeriveInput) -> TokenStream {
dummies::subcommand(ident);
match input.data {
- Data::Enum(ref e) => gen_for_enum(ident, &input.generics, &input.attrs, e),
+ Data::Enum(ref e) => {
+ let name = Name::Derived(ident.clone());
+ let item = Item::from_subcommand_enum(input, name);
+ let variants = e
+ .variants
+ .iter()
+ .map(|variant| {
+ let item =
+ Item::from_subcommand_variant(variant, item.casing(), item.env_casing());
+ (variant, item)
+ })
+ .collect::<Vec<_>>();
+ gen_for_enum(&item, ident, &input.generics, &variants)
+ }
_ => abort_call_site!("`#[derive(Subcommand)]` only supports enums"),
}
}
pub fn gen_for_enum(
- enum_name: &Ident,
+ item: &Item,
+ item_name: &Ident,
generics: &Generics,
- attrs: &[Attribute],
- e: &DataEnum,
+ variants: &[(&Variant, Item)],
) -> TokenStream {
- let from_arg_matches = gen_from_arg_matches_for_enum(enum_name, generics, attrs, e);
-
- let attrs = Attrs::from_struct(
- Span::call_site(),
- attrs,
- Name::Derived(enum_name.clone()),
- Sp::call_site(DEFAULT_CASING),
- Sp::call_site(DEFAULT_ENV_CASING),
- );
- let augmentation = gen_augment(&e.variants, &attrs, false);
- let augmentation_update = gen_augment(&e.variants, &attrs, true);
- let has_subcommand = gen_has_subcommand(&e.variants, &attrs);
+ if !matches!(&*item.kind(), Kind::Command(_)) {
+ abort! { item.kind().span(),
+ "`{}` cannot be used with `command`",
+ item.kind().name(),
+ }
+ }
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
- quote! {
- #from_arg_matches
+ let from_arg_matches = gen_from_arg_matches(variants);
+ let update_from_arg_matches = gen_update_from_arg_matches(variants);
+
+ let augmentation = gen_augment(variants, item, false);
+ let augmentation_update = gen_augment(variants, item, true);
+ let has_subcommand = gen_has_subcommand(variants);
+ quote! {
#[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
#[allow(
clippy::style,
@@ -74,40 +82,19 @@ pub fn gen_for_enum(
clippy::suspicious_else_formatting,
)]
#[deny(clippy::correctness)]
- impl #impl_generics clap::Subcommand for #enum_name #ty_generics #where_clause {
- fn augment_subcommands <'b>(__clap_app: clap::Command<'b>) -> clap::Command<'b> {
- #augmentation
- }
- fn augment_subcommands_for_update <'b>(__clap_app: clap::Command<'b>) -> clap::Command<'b> {
- #augmentation_update
- }
- fn has_subcommand(__clap_name: &str) -> bool {
- #has_subcommand
+ impl #impl_generics clap::FromArgMatches for #item_name #ty_generics #where_clause {
+ fn from_arg_matches(__clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> {
+ Self::from_arg_matches_mut(&mut __clap_arg_matches.clone())
}
- }
- }
-}
-
-fn gen_from_arg_matches_for_enum(
- name: &Ident,
- generics: &Generics,
- attrs: &[Attribute],
- e: &DataEnum,
-) -> TokenStream {
- let attrs = Attrs::from_struct(
- Span::call_site(),
- attrs,
- Name::Derived(name.clone()),
- Sp::call_site(DEFAULT_CASING),
- Sp::call_site(DEFAULT_ENV_CASING),
- );
- let from_arg_matches = gen_from_arg_matches(name, &e.variants, &attrs);
- let update_from_arg_matches = gen_update_from_arg_matches(name, &e.variants, &attrs);
+ #from_arg_matches
- let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+ fn update_from_arg_matches(&mut self, __clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<(), clap::Error> {
+ self.update_from_arg_matches_mut(&mut __clap_arg_matches.clone())
+ }
+ #update_from_arg_matches
+ }
- quote! {
#[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
#[allow(
clippy::style,
@@ -121,24 +108,23 @@ fn gen_from_arg_matches_for_enum(
clippy::suspicious_else_formatting,
)]
#[deny(clippy::correctness)]
- impl #impl_generics clap::FromArgMatches for #name #ty_generics #where_clause {
- fn from_arg_matches(__clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> {
- Self::from_arg_matches_mut(&mut __clap_arg_matches.clone())
+ impl #impl_generics clap::Subcommand for #item_name #ty_generics #where_clause {
+ fn augment_subcommands <'b>(__clap_app: clap::Command) -> clap::Command {
+ #augmentation
}
-
- #from_arg_matches
-
- fn update_from_arg_matches(&mut self, __clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<(), clap::Error> {
- self.update_from_arg_matches_mut(&mut __clap_arg_matches.clone())
+ fn augment_subcommands_for_update <'b>(__clap_app: clap::Command) -> clap::Command {
+ #augmentation_update
+ }
+ fn has_subcommand(__clap_name: &str) -> bool {
+ #has_subcommand
}
- #update_from_arg_matches
}
}
}
fn gen_augment(
- variants: &Punctuated<Variant, Token![,]>,
- parent_attribute: &Attrs,
+ variants: &[(&Variant, Item)],
+ parent_item: &Item,
override_required: bool,
) -> TokenStream {
use syn::Fields::*;
@@ -147,16 +133,14 @@ fn gen_augment(
let subcommands: Vec<_> = variants
.iter()
- .filter_map(|variant| {
- let attrs = Attrs::from_variant(
- variant,
- parent_attribute.casing(),
- parent_attribute.env_casing(),
- );
- let kind = attrs.kind();
+ .filter_map(|(variant, item)| {
+ let kind = item.kind();
match &*kind {
- Kind::Skip(_) => None,
+ Kind::Skip(_, _) |
+ Kind::Arg(_) |
+ Kind::FromGlobal(_) |
+ Kind::Value => None,
Kind::ExternalSubcommand => {
let ty = match variant.fields {
@@ -169,47 +153,51 @@ fn gen_augment(
or `Vec<OsString>`."
),
};
- let subcommand = match subty_if_name(ty, "Vec") {
- Some(subty) => {
- if is_simple_ty(subty, "OsString") {
- quote_spanned! { kind.span()=>
- let #app_var = #app_var.allow_external_subcommands(true).allow_invalid_utf8_for_external_subcommands(true);
- }
- } else {
- quote_spanned! { kind.span()=>
- let #app_var = #app_var.allow_external_subcommands(true);
- }
- }
- }
-
- None => abort!(
+ let deprecations = if !override_required {
+ item.deprecations()
+ } else {
+ quote!()
+ };
+ let subty = subty_if_name(ty, "Vec").unwrap_or_else(|| {
+ abort!(
ty.span(),
"The type must be `Vec<_>` \
to be used with `external_subcommand`."
- ),
+ )
+ });
+ let subcommand = quote_spanned! { kind.span()=>
+ #deprecations
+ let #app_var = #app_var
+ .external_subcommand_value_parser(clap::value_parser!(#subty));
};
Some(subcommand)
}
- Kind::Flatten => match variant.fields {
+ Kind::Flatten(_) => match variant.fields {
Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
let ty = &unnamed[0];
- let old_heading_var = format_ident!("__clap_old_heading");
- let next_help_heading = attrs.next_help_heading();
- let next_display_order = attrs.next_display_order();
+ let deprecations = if !override_required {
+ item.deprecations()
+ } else {
+ quote!()
+ };
+ let next_help_heading = item.next_help_heading();
+ let next_display_order = item.next_display_order();
let subcommand = if override_required {
quote! {
- let #old_heading_var = #app_var.get_next_help_heading();
- let #app_var = #app_var #next_help_heading #next_display_order;
+ #deprecations
+ let #app_var = #app_var
+ #next_help_heading
+ #next_display_order;
let #app_var = <#ty as clap::Subcommand>::augment_subcommands_for_update(#app_var);
- let #app_var = #app_var.next_help_heading(#old_heading_var);
}
} else {
quote! {
- let #old_heading_var = #app_var.get_next_help_heading();
- let #app_var = #app_var #next_help_heading #next_display_order;
+ #deprecations
+ let #app_var = #app_var
+ #next_help_heading
+ #next_display_order;
let #app_var = <#ty as clap::Subcommand>::augment_subcommands(#app_var);
- let #app_var = #app_var.next_help_heading(#old_heading_var);
}
};
Some(subcommand)
@@ -248,33 +236,48 @@ fn gen_augment(
}
};
- let name = attrs.cased_name();
- let initial_app_methods = attrs.initial_top_level_methods();
- let final_from_attrs = attrs.final_top_level_methods();
+ let name = item.cased_name();
+ let deprecations = if !override_required {
+ item.deprecations()
+ } else {
+ quote!()
+ };
+ let initial_app_methods = item.initial_top_level_methods();
+ let final_from_attrs = item.final_top_level_methods();
let subcommand = quote! {
let #app_var = #app_var.subcommand({
+ #deprecations;
let #subcommand_var = clap::Command::new(#name);
let #subcommand_var = #subcommand_var #initial_app_methods;
let #subcommand_var = #arg_block;
- #[allow(deprecated)]
- let #subcommand_var = #subcommand_var.setting(clap::AppSettings::SubcommandRequiredElseHelp);
+ let #subcommand_var = #subcommand_var
+ .subcommand_required(true)
+ .arg_required_else_help(true);
#subcommand_var #final_from_attrs
});
};
Some(subcommand)
}
- _ => {
+ Kind::Command(_) => {
let subcommand_var = Ident::new("__clap_subcommand", Span::call_site());
let sub_augment = match variant.fields {
Named(ref fields) => {
// Defer to `gen_augment` for adding cmd methods
- args::gen_augment(&fields.named, &subcommand_var, &attrs, override_required)
+ let fields = fields
+ .named
+ .iter()
+ .map(|field| {
+ let item = Item::from_args_field(field, item.casing(), item.env_casing());
+ (field, item)
+ })
+ .collect::<Vec<_>>();
+ args::gen_augment(&fields, &subcommand_var, item, override_required)
}
Unit => {
let arg_block = quote!( #subcommand_var );
- let initial_app_methods = attrs.initial_top_level_methods();
- let final_from_attrs = attrs.final_top_level_methods();
+ let initial_app_methods = item.initial_top_level_methods();
+ let final_from_attrs = item.final_top_level_methods();
quote! {
let #subcommand_var = #subcommand_var #initial_app_methods;
let #subcommand_var = #arg_block;
@@ -296,8 +299,8 @@ fn gen_augment(
}
}
};
- let initial_app_methods = attrs.initial_top_level_methods();
- let final_from_attrs = attrs.final_top_level_methods();
+ let initial_app_methods = item.initial_top_level_methods();
+ let final_from_attrs = item.final_top_level_methods();
quote! {
let #subcommand_var = #subcommand_var #initial_app_methods;
let #subcommand_var = #arg_block;
@@ -309,9 +312,15 @@ fn gen_augment(
}
};
- let name = attrs.cased_name();
+ let deprecations = if !override_required {
+ item.deprecations()
+ } else {
+ quote!()
+ };
+ let name = item.cased_name();
let subcommand = quote! {
let #app_var = #app_var.subcommand({
+ #deprecations
let #subcommand_var = clap::Command::new(#name);
#sub_augment
});
@@ -322,46 +331,47 @@ fn gen_augment(
})
.collect();
- let initial_app_methods = parent_attribute.initial_top_level_methods();
- let final_app_methods = parent_attribute.final_top_level_methods();
+ let deprecations = if !override_required {
+ parent_item.deprecations()
+ } else {
+ quote!()
+ };
+ let initial_app_methods = parent_item.initial_top_level_methods();
+ let final_app_methods = parent_item.final_top_level_methods();
quote! {
- let #app_var = #app_var #initial_app_methods;
- #( #subcommands )*;
- #app_var #final_app_methods
+ #deprecations;
+ let #app_var = #app_var #initial_app_methods;
+ #( #subcommands )*;
+ #app_var #final_app_methods
}
}
-fn gen_has_subcommand(
- variants: &Punctuated<Variant, Token![,]>,
- parent_attribute: &Attrs,
-) -> TokenStream {
+fn gen_has_subcommand(variants: &[(&Variant, Item)]) -> TokenStream {
use syn::Fields::*;
let mut ext_subcmd = false;
let (flatten_variants, variants): (Vec<_>, Vec<_>) = variants
.iter()
- .filter_map(|variant| {
- let attrs = Attrs::from_variant(
- variant,
- parent_attribute.casing(),
- parent_attribute.env_casing(),
- );
+ .filter_map(|(variant, item)| {
+ let kind = item.kind();
+ match &*kind {
+ Kind::Skip(_, _) | Kind::Arg(_) | Kind::FromGlobal(_) | Kind::Value => None,
- if let Kind::ExternalSubcommand = &*attrs.kind() {
- ext_subcmd = true;
- None
- } else {
- Some((variant, attrs))
+ Kind::ExternalSubcommand => {
+ ext_subcmd = true;
+ None
+ }
+ Kind::Flatten(_) | Kind::Subcommand(_) | Kind::Command(_) => Some((variant, item)),
}
})
- .partition(|(_, attrs)| {
- let kind = attrs.kind();
- matches!(&*kind, Kind::Flatten)
+ .partition(|(_, item)| {
+ let kind = item.kind();
+ matches!(&*kind, Kind::Flatten(_))
});
- let subcommands = variants.iter().map(|(_variant, attrs)| {
- let sub_name = attrs.cased_name();
+ let subcommands = variants.iter().map(|(_variant, item)| {
+ let sub_name = item.cased_name();
quote! {
if #sub_name == __clap_name {
return true
@@ -398,11 +408,7 @@ fn gen_has_subcommand(
}
}
-fn gen_from_arg_matches(
- name: &Ident,
- variants: &Punctuated<Variant, Token![,]>,
- parent_attribute: &Attrs,
-) -> TokenStream {
+fn gen_from_arg_matches(variants: &[(&Variant, Item)]) -> TokenStream {
use syn::Fields::*;
let mut ext_subcmd = None;
@@ -411,71 +417,79 @@ fn gen_from_arg_matches(
let sub_arg_matches_var = format_ident!("__clap_arg_matches");
let (flatten_variants, variants): (Vec<_>, Vec<_>) = variants
.iter()
- .filter_map(|variant| {
- let attrs = Attrs::from_variant(
- variant,
- parent_attribute.casing(),
- parent_attribute.env_casing(),
- );
-
- if let Kind::ExternalSubcommand = &*attrs.kind() {
- if ext_subcmd.is_some() {
- abort!(
- attrs.kind().span(),
- "Only one variant can be marked with `external_subcommand`, \
+ .filter_map(|(variant, item)| {
+ let kind = item.kind();
+ match &*kind {
+ Kind::Skip(_, _) | Kind::Arg(_) | Kind::FromGlobal(_) | Kind::Value => None,
+
+ Kind::ExternalSubcommand => {
+ if ext_subcmd.is_some() {
+ abort!(
+ item.kind().span(),
+ "Only one variant can be marked with `external_subcommand`, \
this is the second"
- );
- }
+ );
+ }
- let ty = match variant.fields {
- Unnamed(ref fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty,
+ let ty = match variant.fields {
+ Unnamed(ref fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty,
- _ => abort!(
- variant,
- "The enum variant marked with `external_subcommand` must be \
+ _ => abort!(
+ variant,
+ "The enum variant marked with `external_subcommand` must be \
a single-typed tuple, and the type must be either `Vec<String>` \
or `Vec<OsString>`."
- ),
- };
-
- let (span, str_ty) = match subty_if_name(ty, "Vec") {
- Some(subty) => {
- if is_simple_ty(subty, "String") {
- (subty.span(), quote!(::std::string::String))
- } else if is_simple_ty(subty, "OsString") {
- (subty.span(), quote!(::std::ffi::OsString))
- } else {
- abort!(
- ty.span(),
- "The type must be either `Vec<String>` or `Vec<OsString>` \
+ ),
+ };
+
+ let (span, str_ty) = match subty_if_name(ty, "Vec") {
+ Some(subty) => {
+ if is_simple_ty(subty, "String") {
+ (subty.span(), quote!(::std::string::String))
+ } else if is_simple_ty(subty, "OsString") {
+ (subty.span(), quote!(::std::ffi::OsString))
+ } else {
+ abort!(
+ ty.span(),
+ "The type must be either `Vec<String>` or `Vec<OsString>` \
to be used with `external_subcommand`."
- );
+ );
+ }
}
- }
- None => abort!(
- ty.span(),
- "The type must be either `Vec<String>` or `Vec<OsString>` \
+ None => abort!(
+ ty.span(),
+ "The type must be either `Vec<String>` or `Vec<OsString>` \
to be used with `external_subcommand`."
- ),
- };
+ ),
+ };
- ext_subcmd = Some((span, &variant.ident, str_ty));
- None
- } else {
- Some((variant, attrs))
+ ext_subcmd = Some((span, &variant.ident, str_ty));
+ None
+ }
+ Kind::Flatten(_) | Kind::Subcommand(_) | Kind::Command(_) => Some((variant, item)),
}
})
- .partition(|(_, attrs)| {
- let kind = attrs.kind();
- matches!(&*kind, Kind::Flatten)
+ .partition(|(_, item)| {
+ let kind = item.kind();
+ matches!(&*kind, Kind::Flatten(_))
});
- let subcommands = variants.iter().map(|(variant, attrs)| {
- let sub_name = attrs.cased_name();
+ let subcommands = variants.iter().map(|(variant, item)| {
+ let sub_name = item.cased_name();
let variant_name = &variant.ident;
let constructor_block = match variant.fields {
- Named(ref fields) => args::gen_constructor(&fields.named, attrs),
+ Named(ref fields) => {
+ let fields = fields
+ .named
+ .iter()
+ .map(|field| {
+ let item = Item::from_args_field(field, item.casing(), item.env_casing());
+ (field, item)
+ })
+ .collect::<Vec<_>>();
+ args::gen_constructor(&fields)
+ },
Unit => quote!(),
Unnamed(ref fields) if fields.unnamed.len() == 1 => {
let ty = &fields.unnamed[0];
@@ -484,17 +498,9 @@ fn gen_from_arg_matches(
Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident),
};
- if cfg!(feature = "unstable-v4") {
- quote! {
- if #sub_name == #subcommand_name_var && !#sub_arg_matches_var.contains_id("") {
- return ::std::result::Result::Ok(#name :: #variant_name #constructor_block)
- }
- }
- } else {
- quote! {
- if #sub_name == #subcommand_name_var {
- return ::std::result::Result::Ok(#name :: #variant_name #constructor_block)
- }
+ quote! {
+ if #subcommand_name_var == #sub_name && !#sub_arg_matches_var.contains_id("") {
+ return ::std::result::Result::Ok(Self :: #variant_name #constructor_block)
}
}
});
@@ -510,7 +516,7 @@ fn gen_from_arg_matches(
.unwrap_or_default()
{
let __clap_res = <#ty as clap::FromArgMatches>::from_arg_matches_mut(__clap_arg_matches)?;
- return ::std::result::Result::Ok(#name :: #variant_name (__clap_res));
+ return ::std::result::Result::Ok(Self :: #variant_name (__clap_res));
}
}
}
@@ -523,12 +529,12 @@ fn gen_from_arg_matches(
let wildcard = match ext_subcmd {
Some((span, var_name, str_ty)) => quote_spanned! { span=>
- ::std::result::Result::Ok(#name::#var_name(
+ ::std::result::Result::Ok(Self::#var_name(
::std::iter::once(#str_ty::from(#subcommand_name_var))
.chain(
#sub_arg_matches_var
.remove_many::<#str_ty>("")
- .into_iter().flatten() // `""` isn't present, bug in `unstable-v4`
+ .unwrap()
.map(#str_ty::from)
)
.collect::<::std::vec::Vec<_>>()
@@ -536,7 +542,7 @@ fn gen_from_arg_matches(
},
None => quote! {
- ::std::result::Result::Err(clap::Error::raw(clap::ErrorKind::UnrecognizedSubcommand, format!("The subcommand '{}' wasn't recognized", #subcommand_name_var)))
+ ::std::result::Result::Err(clap::Error::raw(clap::error::ErrorKind::InvalidSubcommand, format!("The subcommand '{}' wasn't recognized", #subcommand_name_var)))
},
};
@@ -553,61 +559,52 @@ fn gen_from_arg_matches(
#wildcard
} else {
- ::std::result::Result::Err(clap::Error::raw(clap::ErrorKind::MissingSubcommand, "A subcommand is required but one was not provided."))
+ ::std::result::Result::Err(clap::Error::raw(clap::error::ErrorKind::MissingSubcommand, "A subcommand is required but one was not provided."))
}
}
}
}
-fn gen_update_from_arg_matches(
- name: &Ident,
- variants: &Punctuated<Variant, Token![,]>,
- parent_attribute: &Attrs,
-) -> TokenStream {
+fn gen_update_from_arg_matches(variants: &[(&Variant, Item)]) -> TokenStream {
use syn::Fields::*;
let (flatten, variants): (Vec<_>, Vec<_>) = variants
.iter()
- .filter_map(|variant| {
- let attrs = Attrs::from_variant(
- variant,
- parent_attribute.casing(),
- parent_attribute.env_casing(),
- );
-
- match &*attrs.kind() {
+ .filter_map(|(variant, item)| {
+ let kind = item.kind();
+ match &*kind {
// Fallback to `from_arg_matches_mut`
- Kind::ExternalSubcommand => None,
- _ => Some((variant, attrs)),
+ Kind::Skip(_, _)
+ | Kind::Arg(_)
+ | Kind::FromGlobal(_)
+ | Kind::Value
+ | Kind::ExternalSubcommand => None,
+ Kind::Flatten(_) | Kind::Subcommand(_) | Kind::Command(_) => Some((variant, item)),
}
})
- .partition(|(_, attrs)| {
- let kind = attrs.kind();
- matches!(&*kind, Kind::Flatten)
+ .partition(|(_, item)| {
+ let kind = item.kind();
+ matches!(&*kind, Kind::Flatten(_))
});
- let subcommands = variants.iter().map(|(variant, attrs)| {
- let sub_name = attrs.cased_name();
+ let subcommands = variants.iter().map(|(variant, item)| {
+ let sub_name = item.cased_name();
let variant_name = &variant.ident;
let (pattern, updater) = match variant.fields {
Named(ref fields) => {
- let (fields, update): (Vec<_>, Vec<_>) = fields
+ let field_names = fields.named.iter().map(|field| {
+ field.ident.as_ref().unwrap()
+ }).collect::<Vec<_>>();
+ let fields = fields
.named
.iter()
.map(|field| {
- let attrs = Attrs::from_field(
- field,
- parent_attribute.casing(),
- parent_attribute.env_casing(),
- );
- let field_name = field.ident.as_ref().unwrap();
- (
- quote!( ref mut #field_name ),
- args::gen_updater(&fields.named, &attrs, false),
- )
+ let item = Item::from_args_field(field, item.casing(), item.env_casing());
+ (field, item)
})
- .unzip();
- (quote!( { #( #fields, )* }), quote!( { #( #update )* } ))
+ .collect::<Vec<_>>();
+ let update = args::gen_updater(&fields, false);
+ (quote!( { #( #field_names, )* }), quote!( { #update } ))
}
Unit => (quote!(), quote!({})),
Unnamed(ref fields) => {
@@ -626,7 +623,7 @@ fn gen_update_from_arg_matches(
};
quote! {
- #name :: #variant_name #pattern if #sub_name == __clap_name => {
+ Self :: #variant_name #pattern if #sub_name == __clap_name => {
let (_, mut __clap_arg_sub_matches) = __clap_arg_matches.remove_subcommand().unwrap();
let __clap_arg_matches = &mut __clap_arg_sub_matches;
#updater
@@ -641,7 +638,7 @@ fn gen_update_from_arg_matches(
let ty = &fields.unnamed[0];
quote! {
if <#ty as clap::Subcommand>::has_subcommand(__clap_name) {
- if let #name :: #variant_name (child) = s {
+ if let Self :: #variant_name (child) = s {
<#ty as clap::FromArgMatches>::update_from_arg_matches_mut(child, __clap_arg_matches)?;
return ::std::result::Result::Ok(());
}
diff --git a/src/derives/value_enum.rs b/src/derives/value_enum.rs
index 06d514f..a1411d0 100644
--- a/src/derives/value_enum.rs
+++ b/src/derives/value_enum.rs
@@ -8,20 +8,14 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
-use crate::{
- attrs::{Attrs, Kind, Name, DEFAULT_CASING, DEFAULT_ENV_CASING},
- dummies,
- utils::Sp,
-};
-
-use proc_macro2::{Span, TokenStream};
+use proc_macro2::TokenStream;
use proc_macro_error::{abort, abort_call_site};
use quote::quote;
use quote::quote_spanned;
-use syn::{
- punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, DataEnum, DeriveInput,
- Fields, Ident, Variant,
-};
+use syn::{spanned::Spanned, Data, DeriveInput, Fields, Ident, Variant};
+
+use crate::dummies;
+use crate::item::{Item, Kind, Name};
pub fn derive_value_enum(input: &DeriveInput) -> TokenStream {
let ident = &input.ident;
@@ -29,23 +23,35 @@ pub fn derive_value_enum(input: &DeriveInput) -> TokenStream {
dummies::value_enum(ident);
match input.data {
- Data::Enum(ref e) => gen_for_enum(ident, &input.attrs, e),
+ Data::Enum(ref e) => {
+ let name = Name::Derived(ident.clone());
+ let item = Item::from_value_enum(input, name);
+ let variants = e
+ .variants
+ .iter()
+ .map(|variant| {
+ let item =
+ Item::from_value_enum_variant(variant, item.casing(), item.env_casing());
+ (variant, item)
+ })
+ .collect::<Vec<_>>();
+ gen_for_enum(&item, ident, &variants)
+ }
_ => abort_call_site!("`#[derive(ValueEnum)]` only supports enums"),
}
}
-pub fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStream {
- let attrs = Attrs::from_struct(
- Span::call_site(),
- attrs,
- Name::Derived(name.clone()),
- Sp::call_site(DEFAULT_CASING),
- Sp::call_site(DEFAULT_ENV_CASING),
- );
+pub fn gen_for_enum(item: &Item, item_name: &Ident, variants: &[(&Variant, Item)]) -> TokenStream {
+ if !matches!(&*item.kind(), Kind::Value) {
+ abort! { item.kind().span(),
+ "`{}` cannot be used with `value`",
+ item.kind().name(),
+ }
+ }
- let lits = lits(&e.variants, &attrs);
+ let lits = lits(variants);
let value_variants = gen_value_variants(&lits);
- let to_possible_value = gen_to_possible_value(&lits);
+ let to_possible_value = gen_to_possible_value(item, &lits);
quote! {
#[allow(dead_code, unreachable_code, unused_variables, unused_braces)]
@@ -61,38 +67,32 @@ pub fn gen_for_enum(name: &Ident, attrs: &[Attribute], e: &DataEnum) -> TokenStr
clippy::suspicious_else_formatting,
)]
#[deny(clippy::correctness)]
- impl clap::ValueEnum for #name {
+ impl clap::ValueEnum for #item_name {
#value_variants
#to_possible_value
}
}
}
-fn lits(
- variants: &Punctuated<Variant, Comma>,
- parent_attribute: &Attrs,
-) -> Vec<(TokenStream, Ident)> {
+fn lits(variants: &[(&Variant, Item)]) -> Vec<(TokenStream, Ident)> {
variants
.iter()
- .filter_map(|variant| {
- let attrs = Attrs::from_value_enum_variant(
- variant,
- parent_attribute.casing(),
- parent_attribute.env_casing(),
- );
- if let Kind::Skip(_) = &*attrs.kind() {
+ .filter_map(|(variant, item)| {
+ if let Kind::Skip(_, _) = &*item.kind() {
None
} else {
if !matches!(variant.fields, Fields::Unit) {
abort!(variant.span(), "`#[derive(ValueEnum)]` only supports unit variants. Non-unit variants must be skipped");
}
- let fields = attrs.field_methods(false);
- let name = attrs.cased_name();
+ let fields = item.field_methods();
+ let deprecations = item.deprecations();
+ let name = item.cased_name();
Some((
- quote_spanned! { variant.span()=>
- clap::PossibleValue::new(#name)
+ quote_spanned! { variant.span()=> {
+ #deprecations
+ clap::builder::PossibleValue::new(#name)
#fields
- },
+ }},
variant.ident.clone(),
))
}
@@ -110,11 +110,14 @@ fn gen_value_variants(lits: &[(TokenStream, Ident)]) -> TokenStream {
}
}
-fn gen_to_possible_value(lits: &[(TokenStream, Ident)]) -> TokenStream {
+fn gen_to_possible_value(item: &Item, lits: &[(TokenStream, Ident)]) -> TokenStream {
let (lit, variant): (Vec<TokenStream>, Vec<Ident>) = lits.iter().cloned().unzip();
+ let deprecations = item.deprecations();
+
quote! {
- fn to_possible_value<'a>(&self) -> ::std::option::Option<clap::PossibleValue<'a>> {
+ fn to_possible_value<'a>(&self) -> ::std::option::Option<clap::builder::PossibleValue> {
+ #deprecations
match self {
#(Self::#variant => Some(#lit),)*
_ => None
diff --git a/src/dummies.rs b/src/dummies.rs
index 74b7def..1f54e1d 100644
--- a/src/dummies.rs
+++ b/src/dummies.rs
@@ -18,12 +18,11 @@ pub fn parser_enum(name: &Ident) {
pub fn into_app(name: &Ident) {
append_dummy(quote! {
- #[allow(deprecated)]
impl clap::CommandFactory for #name {
- fn into_app<'b>() -> clap::Command<'b> {
+ fn command<'b>() -> clap::Command {
unimplemented!()
}
- fn into_app_for_update<'b>() -> clap::Command<'b> {
+ fn command_for_update<'b>() -> clap::Command {
unimplemented!()
}
}
@@ -47,10 +46,10 @@ pub fn subcommand(name: &Ident) {
from_arg_matches(name);
append_dummy(quote! {
impl clap::Subcommand for #name {
- fn augment_subcommands(_cmd: clap::Command<'_>) -> clap::Command<'_> {
+ fn augment_subcommands(_cmd: clap::Command) -> clap::Command {
unimplemented!()
}
- fn augment_subcommands_for_update(_cmd: clap::Command<'_>) -> clap::Command<'_> {
+ fn augment_subcommands_for_update(_cmd: clap::Command) -> clap::Command {
unimplemented!()
}
fn has_subcommand(name: &str) -> bool {
@@ -64,10 +63,10 @@ pub fn args(name: &Ident) {
from_arg_matches(name);
append_dummy(quote! {
impl clap::Args for #name {
- fn augment_args(_cmd: clap::Command<'_>) -> clap::Command<'_> {
+ fn augment_args(_cmd: clap::Command) -> clap::Command {
unimplemented!()
}
- fn augment_args_for_update(_cmd: clap::Command<'_>) -> clap::Command<'_> {
+ fn augment_args_for_update(_cmd: clap::Command) -> clap::Command {
unimplemented!()
}
}
@@ -83,7 +82,7 @@ pub fn value_enum(name: &Ident) {
fn from_str(_input: &str, _ignore_case: bool) -> ::std::result::Result<Self, String> {
unimplemented!()
}
- fn to_possible_value<'a>(&self) -> ::std::option::Option<clap::PossibleValue<'a>>{
+ fn to_possible_value<'a>(&self) -> ::std::option::Option<clap::builder::PossibleValue>{
unimplemented!()
}
}
diff --git a/src/item.rs b/src/item.rs
new file mode 100644
index 0000000..a068e36
--- /dev/null
+++ b/src/item.rs
@@ -0,0 +1,1449 @@
+// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
+// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
+// Ana Hobden (@hoverbear) <operator@hoverbear.org>
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+//
+// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
+// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
+// MIT/Apache 2.0 license.
+
+use std::env;
+
+use heck::{ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
+use proc_macro2::{self, Span, TokenStream};
+use proc_macro_error::abort;
+use quote::{format_ident, quote, quote_spanned, ToTokens};
+use syn::DeriveInput;
+use syn::{self, ext::IdentExt, spanned::Spanned, Attribute, Field, Ident, LitStr, Type, Variant};
+
+use crate::attr::*;
+use crate::utils::{extract_doc_comment, format_doc_comment, inner_type, is_simple_ty, Sp, Ty};
+
+/// Default casing style for generated arguments.
+pub const DEFAULT_CASING: CasingStyle = CasingStyle::Kebab;
+
+/// Default casing style for environment variables
+pub const DEFAULT_ENV_CASING: CasingStyle = CasingStyle::ScreamingSnake;
+
+#[derive(Clone)]
+pub struct Item {
+ name: Name,
+ ident: Ident,
+ casing: Sp<CasingStyle>,
+ env_casing: Sp<CasingStyle>,
+ ty: Option<Type>,
+ doc_comment: Vec<Method>,
+ methods: Vec<Method>,
+ deprecations: Vec<Deprecation>,
+ value_parser: Option<ValueParser>,
+ action: Option<Action>,
+ verbatim_doc_comment: bool,
+ force_long_help: bool,
+ next_display_order: Option<Method>,
+ next_help_heading: Option<Method>,
+ is_enum: bool,
+ is_positional: bool,
+ skip_group: bool,
+ kind: Sp<Kind>,
+}
+
+impl Item {
+ pub fn from_args_struct(input: &DeriveInput, name: Name) -> Self {
+ let ident = input.ident.clone();
+ let span = input.ident.span();
+ let attrs = &input.attrs;
+ let argument_casing = Sp::new(DEFAULT_CASING, span);
+ let env_casing = Sp::new(DEFAULT_ENV_CASING, span);
+ let kind = Sp::new(Kind::Command(Sp::new(Ty::Other, span)), span);
+
+ let mut res = Self::new(name, ident, None, argument_casing, env_casing, kind);
+ let parsed_attrs = ClapAttr::parse_all(attrs);
+ res.infer_kind(&parsed_attrs);
+ res.push_attrs(&parsed_attrs);
+ res.push_doc_comment(attrs, "about", Some("long_about"));
+
+ res
+ }
+
+ pub fn from_subcommand_enum(input: &DeriveInput, name: Name) -> Self {
+ let ident = input.ident.clone();
+ let span = input.ident.span();
+ let attrs = &input.attrs;
+ let argument_casing = Sp::new(DEFAULT_CASING, span);
+ let env_casing = Sp::new(DEFAULT_ENV_CASING, span);
+ let kind = Sp::new(Kind::Command(Sp::new(Ty::Other, span)), span);
+
+ let mut res = Self::new(name, ident, None, argument_casing, env_casing, kind);
+ let parsed_attrs = ClapAttr::parse_all(attrs);
+ res.infer_kind(&parsed_attrs);
+ res.push_attrs(&parsed_attrs);
+ res.push_doc_comment(attrs, "about", Some("long_about"));
+
+ res
+ }
+
+ pub fn from_value_enum(input: &DeriveInput, name: Name) -> Self {
+ let ident = input.ident.clone();
+ let span = input.ident.span();
+ let attrs = &input.attrs;
+ let argument_casing = Sp::new(DEFAULT_CASING, span);
+ let env_casing = Sp::new(DEFAULT_ENV_CASING, span);
+ let kind = Sp::new(Kind::Value, span);
+
+ let mut res = Self::new(name, ident, None, argument_casing, env_casing, kind);
+ let parsed_attrs = ClapAttr::parse_all(attrs);
+ res.infer_kind(&parsed_attrs);
+ res.push_attrs(&parsed_attrs);
+ // Ignoring `push_doc_comment` as there is no top-level clap builder to add documentation
+ // to
+
+ if res.has_explicit_methods() {
+ abort!(
+ res.methods[0].name.span(),
+ "{} doesn't exist for `ValueEnum` enums",
+ res.methods[0].name
+ );
+ }
+
+ res
+ }
+
+ pub fn from_subcommand_variant(
+ variant: &Variant,
+ struct_casing: Sp<CasingStyle>,
+ env_casing: Sp<CasingStyle>,
+ ) -> Self {
+ let name = variant.ident.clone();
+ let ident = variant.ident.clone();
+ let span = variant.span();
+ let ty = match variant.fields {
+ syn::Fields::Unnamed(syn::FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
+ Ty::from_syn_ty(&unnamed[0].ty)
+ }
+ syn::Fields::Named(_) | syn::Fields::Unnamed(..) | syn::Fields::Unit => {
+ Sp::new(Ty::Other, span)
+ }
+ };
+ let kind = Sp::new(Kind::Command(ty), span);
+ let mut res = Self::new(
+ Name::Derived(name),
+ ident,
+ None,
+ struct_casing,
+ env_casing,
+ kind,
+ );
+ let parsed_attrs = ClapAttr::parse_all(&variant.attrs);
+ res.infer_kind(&parsed_attrs);
+ res.push_attrs(&parsed_attrs);
+ if matches!(&*res.kind, Kind::Command(_) | Kind::Subcommand(_)) {
+ res.push_doc_comment(&variant.attrs, "about", Some("long_about"));
+ }
+
+ match &*res.kind {
+ Kind::Flatten(_) => {
+ if res.has_explicit_methods() {
+ abort!(
+ res.kind.span(),
+ "methods are not allowed for flattened entry"
+ );
+ }
+ }
+
+ Kind::Subcommand(_)
+ | Kind::ExternalSubcommand
+ | Kind::FromGlobal(_)
+ | Kind::Skip(_, _)
+ | Kind::Command(_)
+ | Kind::Value
+ | Kind::Arg(_) => (),
+ }
+
+ res
+ }
+
+ pub fn from_value_enum_variant(
+ variant: &Variant,
+ argument_casing: Sp<CasingStyle>,
+ env_casing: Sp<CasingStyle>,
+ ) -> Self {
+ let ident = variant.ident.clone();
+ let span = variant.span();
+ let kind = Sp::new(Kind::Value, span);
+ let mut res = Self::new(
+ Name::Derived(variant.ident.clone()),
+ ident,
+ None,
+ argument_casing,
+ env_casing,
+ kind,
+ );
+ let parsed_attrs = ClapAttr::parse_all(&variant.attrs);
+ res.infer_kind(&parsed_attrs);
+ res.push_attrs(&parsed_attrs);
+ if matches!(&*res.kind, Kind::Value) {
+ res.push_doc_comment(&variant.attrs, "help", None);
+ }
+
+ res
+ }
+
+ pub fn from_args_field(
+ field: &Field,
+ struct_casing: Sp<CasingStyle>,
+ env_casing: Sp<CasingStyle>,
+ ) -> Self {
+ let name = field.ident.clone().unwrap();
+ let ident = field.ident.clone().unwrap();
+ let span = field.span();
+ let ty = Ty::from_syn_ty(&field.ty);
+ let kind = Sp::new(Kind::Arg(ty), span);
+ let mut res = Self::new(
+ Name::Derived(name),
+ ident,
+ Some(field.ty.clone()),
+ struct_casing,
+ env_casing,
+ kind,
+ );
+ let parsed_attrs = ClapAttr::parse_all(&field.attrs);
+ res.infer_kind(&parsed_attrs);
+ res.push_attrs(&parsed_attrs);
+ if matches!(&*res.kind, Kind::Arg(_)) {
+ res.push_doc_comment(&field.attrs, "help", Some("long_help"));
+ }
+
+ match &*res.kind {
+ Kind::Flatten(_) => {
+ if res.has_explicit_methods() {
+ abort!(
+ res.kind.span(),
+ "methods are not allowed for flattened entry"
+ );
+ }
+ }
+
+ Kind::Subcommand(_) => {
+ if res.has_explicit_methods() {
+ abort!(
+ res.kind.span(),
+ "methods in attributes are not allowed for subcommand"
+ );
+ }
+ }
+ Kind::Skip(_, _)
+ | Kind::FromGlobal(_)
+ | Kind::Arg(_)
+ | Kind::Command(_)
+ | Kind::Value
+ | Kind::ExternalSubcommand => {}
+ }
+
+ res
+ }
+
+ fn new(
+ name: Name,
+ ident: Ident,
+ ty: Option<Type>,
+ casing: Sp<CasingStyle>,
+ env_casing: Sp<CasingStyle>,
+ kind: Sp<Kind>,
+ ) -> Self {
+ Self {
+ name,
+ ident,
+ ty,
+ casing,
+ env_casing,
+ doc_comment: vec![],
+ methods: vec![],
+ deprecations: vec![],
+ value_parser: None,
+ action: None,
+ verbatim_doc_comment: false,
+ force_long_help: false,
+ next_display_order: None,
+ next_help_heading: None,
+ is_enum: false,
+ is_positional: true,
+ skip_group: false,
+ kind,
+ }
+ }
+
+ fn push_method(&mut self, kind: AttrKind, name: Ident, arg: impl ToTokens) {
+ if name == "id" {
+ match kind {
+ AttrKind::Command | AttrKind::Value => {
+ self.deprecations.push(Deprecation {
+ span: name.span(),
+ id: "id_is_only_for_arg",
+ version: "4.0.0",
+ description: format!(
+ "`#[{}(id)] was allowed by mistake, instead use `#[{}(name)]`",
+ kind.as_str(),
+ kind.as_str()
+ ),
+ });
+ }
+ AttrKind::Group | AttrKind::Arg | AttrKind::Clap | AttrKind::StructOpt => {}
+ }
+ self.name = Name::Assigned(quote!(#arg));
+ } else if name == "name" {
+ match kind {
+ AttrKind::Arg => {
+ self.deprecations.push(Deprecation {
+ span: name.span(),
+ id: "id_is_only_for_arg",
+ version: "4.0.0",
+ description: format!(
+ "`#[{}(name)] was allowed by mistake, instead use `#[{}(id)]` or `#[{}(value_name)]`",
+ kind.as_str(),
+ kind.as_str(),
+ kind.as_str()
+ ),
+ });
+ }
+ AttrKind::Group
+ | AttrKind::Command
+ | AttrKind::Value
+ | AttrKind::Clap
+ | AttrKind::StructOpt => {}
+ }
+ self.name = Name::Assigned(quote!(#arg));
+ } else if name == "value_parser" {
+ self.value_parser = Some(ValueParser::Explicit(Method::new(name, quote!(#arg))));
+ } else if name == "action" {
+ self.action = Some(Action::Explicit(Method::new(name, quote!(#arg))));
+ } else {
+ if name == "short" || name == "long" {
+ self.is_positional = false;
+ }
+ self.methods.push(Method::new(name, quote!(#arg)));
+ }
+ }
+
+ fn infer_kind(&mut self, attrs: &[ClapAttr]) {
+ for attr in attrs {
+ if let Some(AttrValue::Call(_)) = &attr.value {
+ continue;
+ }
+
+ let actual_attr_kind = *attr.kind.get();
+ let kind = match &attr.magic {
+ Some(MagicAttrName::FromGlobal) => {
+ if attr.value.is_some() {
+ let expr = attr.value_or_abort();
+ abort!(expr, "attribute `{}` does not accept a value", attr.name);
+ }
+ let ty = self
+ .kind()
+ .ty()
+ .cloned()
+ .unwrap_or_else(|| Sp::new(Ty::Other, self.kind.span()));
+ let kind = Sp::new(Kind::FromGlobal(ty), attr.name.clone().span());
+ Some(kind)
+ }
+ Some(MagicAttrName::Subcommand) if attr.value.is_none() => {
+ if attr.value.is_some() {
+ let expr = attr.value_or_abort();
+ abort!(expr, "attribute `{}` does not accept a value", attr.name);
+ }
+ let ty = self
+ .kind()
+ .ty()
+ .cloned()
+ .unwrap_or_else(|| Sp::new(Ty::Other, self.kind.span()));
+ let kind = Sp::new(Kind::Subcommand(ty), attr.name.clone().span());
+ Some(kind)
+ }
+ Some(MagicAttrName::ExternalSubcommand) if attr.value.is_none() => {
+ if attr.value.is_some() {
+ let expr = attr.value_or_abort();
+ abort!(expr, "attribute `{}` does not accept a value", attr.name);
+ }
+ let kind = Sp::new(Kind::ExternalSubcommand, attr.name.clone().span());
+ Some(kind)
+ }
+ Some(MagicAttrName::Flatten) if attr.value.is_none() => {
+ if attr.value.is_some() {
+ let expr = attr.value_or_abort();
+ abort!(expr, "attribute `{}` does not accept a value", attr.name);
+ }
+ let ty = self
+ .kind()
+ .ty()
+ .cloned()
+ .unwrap_or_else(|| Sp::new(Ty::Other, self.kind.span()));
+ let kind = Sp::new(Kind::Flatten(ty), attr.name.clone().span());
+ Some(kind)
+ }
+ Some(MagicAttrName::Skip) if actual_attr_kind != AttrKind::Group => {
+ let expr = attr.value.clone();
+ let kind = Sp::new(
+ Kind::Skip(expr, self.kind.attr_kind()),
+ attr.name.clone().span(),
+ );
+ Some(kind)
+ }
+ _ => None,
+ };
+
+ if let Some(kind) = kind {
+ self.set_kind(kind);
+ }
+ }
+ }
+
+ fn push_attrs(&mut self, attrs: &[ClapAttr]) {
+ for attr in attrs {
+ let actual_attr_kind = *attr.kind.get();
+ let expected_attr_kind = self.kind.attr_kind();
+ match (actual_attr_kind, expected_attr_kind) {
+ (AttrKind::Clap, _) | (AttrKind::StructOpt, _) => {
+ self.deprecations.push(Deprecation::attribute(
+ "4.0.0",
+ actual_attr_kind,
+ expected_attr_kind,
+ attr.kind.span(),
+ ));
+ }
+
+ (AttrKind::Group, AttrKind::Command) => {}
+
+ _ if attr.kind != expected_attr_kind => {
+ abort!(
+ attr.kind.span(),
+ "Expected `{}` attribute instead of `{}`",
+ expected_attr_kind.as_str(),
+ actual_attr_kind.as_str()
+ );
+ }
+
+ _ => {}
+ }
+
+ if let Some(AttrValue::Call(tokens)) = &attr.value {
+ // Force raw mode with method call syntax
+ self.push_method(*attr.kind.get(), attr.name.clone(), quote!(#(#tokens),*));
+ continue;
+ }
+
+ match &attr.magic {
+ Some(MagicAttrName::Short) if attr.value.is_none() => {
+ assert_attr_kind(attr, &[AttrKind::Arg]);
+
+ self.push_method(
+ *attr.kind.get(),
+ attr.name.clone(),
+ self.name.clone().translate_char(*self.casing),
+ );
+ }
+
+ Some(MagicAttrName::Long) if attr.value.is_none() => {
+ assert_attr_kind(attr, &[AttrKind::Arg]);
+
+ self.push_method(*attr.kind.get(), attr.name.clone(), self.name.clone().translate(*self.casing));
+ }
+
+ Some(MagicAttrName::ValueParser) if attr.value.is_none() => {
+ assert_attr_kind(attr, &[AttrKind::Arg]);
+
+ self.deprecations.push(Deprecation {
+ span: attr.name.span(),
+ id: "bare_value_parser",
+ version: "4.0.0",
+ description: "`#[arg(value_parser)]` is now the default and is no longer needed`".to_owned(),
+ });
+ self.value_parser = Some(ValueParser::Implicit(attr.name.clone()));
+ }
+
+ Some(MagicAttrName::Action) if attr.value.is_none() => {
+ assert_attr_kind(attr, &[AttrKind::Arg]);
+
+ self.deprecations.push(Deprecation {
+ span: attr.name.span(),
+ id: "bare_action",
+ version: "4.0.0",
+ description: "`#[arg(action)]` is now the default and is no longer needed`".to_owned(),
+ });
+ self.action = Some(Action::Implicit(attr.name.clone()));
+ }
+
+ Some(MagicAttrName::Env) if attr.value.is_none() => {
+ assert_attr_kind(attr, &[AttrKind::Arg]);
+
+ self.push_method(
+ *attr.kind.get(),
+ attr.name.clone(),
+ self.name.clone().translate(*self.env_casing),
+ );
+ }
+
+ Some(MagicAttrName::ValueEnum) if attr.value.is_none() => {
+ assert_attr_kind(attr, &[AttrKind::Arg]);
+
+ self.is_enum = true
+ }
+
+ Some(MagicAttrName::VerbatimDocComment) if attr.value.is_none() => {
+ self.verbatim_doc_comment = true
+ }
+
+ Some(MagicAttrName::About) if attr.value.is_none() => {
+ assert_attr_kind(attr, &[AttrKind::Command]);
+
+ if let Some(method) =
+ Method::from_env(attr.name.clone(), "CARGO_PKG_DESCRIPTION")
+ {
+ self.methods.push(method);
+ }
+ }
+
+ Some(MagicAttrName::LongAbout) if attr.value.is_none() => {
+ assert_attr_kind(attr, &[AttrKind::Command]);
+
+ self.force_long_help = true;
+ }
+
+ Some(MagicAttrName::LongHelp) if attr.value.is_none() => {
+ assert_attr_kind(attr, &[AttrKind::Arg]);
+
+ self.force_long_help = true;
+ }
+
+ Some(MagicAttrName::Author) if attr.value.is_none() => {
+ assert_attr_kind(attr, &[AttrKind::Command]);
+
+ if let Some(method) = Method::from_env(attr.name.clone(), "CARGO_PKG_AUTHORS") {
+ self.methods.push(method);
+ }
+ }
+
+ Some(MagicAttrName::Version) if attr.value.is_none() => {
+ assert_attr_kind(attr, &[AttrKind::Command]);
+
+ if let Some(method) = Method::from_env(attr.name.clone(), "CARGO_PKG_VERSION") {
+ self.methods.push(method);
+ }
+ }
+
+ Some(MagicAttrName::DefaultValueT) => {
+ assert_attr_kind(attr, &[AttrKind::Arg]);
+
+ let ty = if let Some(ty) = self.ty.as_ref() {
+ ty
+ } else {
+ abort!(
+ attr.name.clone(),
+ "#[arg(default_value_t)] (without an argument) can be used \
+ only on field level";
+
+ note = "see \
+ https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
+ };
+
+ let val = if let Some(expr) = &attr.value {
+ quote!(#expr)
+ } else {
+ quote!(<#ty as ::std::default::Default>::default())
+ };
+
+ let val = if attrs
+ .iter()
+ .any(|a| a.magic == Some(MagicAttrName::ValueEnum))
+ {
+ quote_spanned!(attr.name.clone().span()=> {
+ static DEFAULT_VALUE: clap::__macro_refs::once_cell::sync::Lazy<String> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+ let val: #ty = #val;
+ clap::ValueEnum::to_possible_value(&val).unwrap().get_name().to_owned()
+ });
+ let s: &'static str = &*DEFAULT_VALUE;
+ s
+ })
+ } else {
+ quote_spanned!(attr.name.clone().span()=> {
+ static DEFAULT_VALUE: clap::__macro_refs::once_cell::sync::Lazy<String> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+ let val: #ty = #val;
+ ::std::string::ToString::to_string(&val)
+ });
+ let s: &'static str = &*DEFAULT_VALUE;
+ s
+ })
+ };
+
+ let raw_ident = Ident::new("default_value", attr.name.clone().span());
+ self.methods.push(Method::new(raw_ident, val));
+ }
+
+ Some(MagicAttrName::DefaultValuesT) => {
+ assert_attr_kind(attr, &[AttrKind::Arg]);
+
+ let ty = if let Some(ty) = self.ty.as_ref() {
+ ty
+ } else {
+ abort!(
+ attr.name.clone(),
+ "#[arg(default_values_t)] (without an argument) can be used \
+ only on field level";
+
+ note = "see \
+ https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
+ };
+ let expr = attr.value_or_abort();
+
+ let container_type = Ty::from_syn_ty(ty);
+ if *container_type != Ty::Vec {
+ abort!(
+ attr.name.clone(),
+ "#[arg(default_values_t)] can be used only on Vec types";
+
+ note = "see \
+ https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
+ }
+ let inner_type = inner_type(ty);
+
+ // Use `Borrow<#inner_type>` so we accept `&Vec<#inner_type>` and
+ // `Vec<#inner_type>`.
+ let val = if attrs
+ .iter()
+ .any(|a| a.magic == Some(MagicAttrName::ValueEnum))
+ {
+ quote_spanned!(attr.name.clone().span()=> {
+ {
+ fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> impl Iterator<Item=String>
+ where
+ T: ::std::borrow::Borrow<#inner_type>
+ {
+ iterable
+ .into_iter()
+ .map(|val| {
+ clap::ValueEnum::to_possible_value(val.borrow()).unwrap().get_name().to_owned()
+ })
+ }
+
+ static DEFAULT_STRINGS: clap::__macro_refs::once_cell::sync::Lazy<Vec<::std::string::String>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+ iter_to_vals(#expr).collect()
+ });
+
+ static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy<Vec<&str>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+ DEFAULT_STRINGS.iter().map(::std::string::String::as_str).collect()
+ });
+ DEFAULT_VALUES.iter().copied()
+ }
+ })
+ } else {
+ quote_spanned!(attr.name.clone().span()=> {
+ {
+ fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> impl Iterator<Item=String>
+ where
+ T: ::std::borrow::Borrow<#inner_type>
+ {
+ iterable.into_iter().map(|val| val.borrow().to_string())
+ }
+
+ static DEFAULT_STRINGS: clap::__macro_refs::once_cell::sync::Lazy<Vec<::std::string::String>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+ iter_to_vals(#expr).collect()
+ });
+
+ static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy<Vec<&str>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+ DEFAULT_STRINGS.iter().map(::std::string::String::as_str).collect()
+ });
+ DEFAULT_VALUES.iter().copied()
+ }
+ })
+ };
+
+ self.methods.push(Method::new(
+ Ident::new("default_values", attr.name.clone().span()),
+ val,
+ ));
+ }
+
+ Some(MagicAttrName::DefaultValueOsT) => {
+ assert_attr_kind(attr, &[AttrKind::Arg]);
+
+ let ty = if let Some(ty) = self.ty.as_ref() {
+ ty
+ } else {
+ abort!(
+ attr.name.clone(),
+ "#[arg(default_value_os_t)] (without an argument) can be used \
+ only on field level";
+
+ note = "see \
+ https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
+ };
+
+ let val = if let Some(expr) = &attr.value {
+ quote!(#expr)
+ } else {
+ quote!(<#ty as ::std::default::Default>::default())
+ };
+
+ let val = if attrs
+ .iter()
+ .any(|a| a.magic == Some(MagicAttrName::ValueEnum))
+ {
+ quote_spanned!(attr.name.clone().span()=> {
+ static DEFAULT_VALUE: clap::__macro_refs::once_cell::sync::Lazy<::std::ffi::OsString> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+ let val: #ty = #val;
+ clap::ValueEnum::to_possible_value(&val).unwrap().get_name().to_owned()
+ });
+ let s: &'static ::std::ffi::OsStr = &*DEFAULT_VALUE;
+ s
+ })
+ } else {
+ quote_spanned!(attr.name.clone().span()=> {
+ static DEFAULT_VALUE: clap::__macro_refs::once_cell::sync::Lazy<::std::ffi::OsString> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+ let val: #ty = #val;
+ ::std::ffi::OsString::from(val)
+ });
+ let s: &'static ::std::ffi::OsStr = &*DEFAULT_VALUE;
+ s
+ })
+ };
+
+ let raw_ident = Ident::new("default_value", attr.name.clone().span());
+ self.methods.push(Method::new(raw_ident, val));
+ }
+
+ Some(MagicAttrName::DefaultValuesOsT) => {
+ assert_attr_kind(attr, &[AttrKind::Arg]);
+
+ let ty = if let Some(ty) = self.ty.as_ref() {
+ ty
+ } else {
+ abort!(
+ attr.name.clone(),
+ "#[arg(default_values_os_t)] (without an argument) can be used \
+ only on field level";
+
+ note = "see \
+ https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
+ };
+ let expr = attr.value_or_abort();
+
+ let container_type = Ty::from_syn_ty(ty);
+ if *container_type != Ty::Vec {
+ abort!(
+ attr.name.clone(),
+ "#[arg(default_values_os_t)] can be used only on Vec types";
+
+ note = "see \
+ https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#magic-attributes")
+ }
+ let inner_type = inner_type(ty);
+
+ // Use `Borrow<#inner_type>` so we accept `&Vec<#inner_type>` and
+ // `Vec<#inner_type>`.
+ let val = if attrs
+ .iter()
+ .any(|a| a.magic == Some(MagicAttrName::ValueEnum))
+ {
+ quote_spanned!(attr.name.clone().span()=> {
+ {
+ fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> impl Iterator<Item=::std::ffi::OsString>
+ where
+ T: ::std::borrow::Borrow<#inner_type>
+ {
+ iterable
+ .into_iter()
+ .map(|val| {
+ clap::ValueEnum::to_possible_value(val.borrow()).unwrap().get_name().to_owned().into()
+ })
+ }
+
+ static DEFAULT_OS_STRINGS: clap::__macro_refs::once_cell::sync::Lazy<Vec<::std::ffi::OsString>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+ iter_to_vals(#expr).collect()
+ });
+
+ static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy<Vec<&::std::ffi::OsStr>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+ DEFAULT_OS_STRINGS.iter().map(::std::ffi::OsString::as_os_str).collect()
+ });
+ DEFAULT_VALUES.iter().copied()
+ }
+ })
+ } else {
+ quote_spanned!(attr.name.clone().span()=> {
+ {
+ fn iter_to_vals<T>(iterable: impl IntoIterator<Item = T>) -> impl Iterator<Item=::std::ffi::OsString>
+ where
+ T: ::std::borrow::Borrow<#inner_type>
+ {
+ iterable.into_iter().map(|val| val.borrow().into())
+ }
+
+ static DEFAULT_OS_STRINGS: clap::__macro_refs::once_cell::sync::Lazy<Vec<::std::ffi::OsString>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+ iter_to_vals(#expr).collect()
+ });
+
+ static DEFAULT_VALUES: clap::__macro_refs::once_cell::sync::Lazy<Vec<&::std::ffi::OsStr>> = clap::__macro_refs::once_cell::sync::Lazy::new(|| {
+ DEFAULT_OS_STRINGS.iter().map(::std::ffi::OsString::as_os_str).collect()
+ });
+ DEFAULT_VALUES.iter().copied()
+ }
+ })
+ };
+
+ self.methods.push(Method::new(
+ Ident::new("default_values", attr.name.clone().span()),
+ val,
+ ));
+ }
+
+ Some(MagicAttrName::NextDisplayOrder) => {
+ assert_attr_kind(attr, &[AttrKind::Command]);
+
+ let expr = attr.value_or_abort();
+ self.next_display_order = Some(Method::new(attr.name.clone(), quote!(#expr)));
+ }
+
+ Some(MagicAttrName::NextHelpHeading) => {
+ assert_attr_kind(attr, &[AttrKind::Command]);
+
+ let expr = attr.value_or_abort();
+ self.next_help_heading = Some(Method::new(attr.name.clone(), quote!(#expr)));
+ }
+
+ Some(MagicAttrName::RenameAll) => {
+ let lit = attr.lit_str_or_abort();
+ self.casing = CasingStyle::from_lit(lit);
+ }
+
+ Some(MagicAttrName::RenameAllEnv) => {
+ assert_attr_kind(attr, &[AttrKind::Command, AttrKind::Arg]);
+
+ let lit = attr.lit_str_or_abort();
+ self.env_casing = CasingStyle::from_lit(lit);
+ }
+
+ Some(MagicAttrName::Skip) if actual_attr_kind == AttrKind::Group => {
+ self.skip_group = true;
+ }
+
+ None
+ // Magic only for the default, otherwise just forward to the builder
+ | Some(MagicAttrName::Short)
+ | Some(MagicAttrName::Long)
+ | Some(MagicAttrName::Env)
+ | Some(MagicAttrName::About)
+ | Some(MagicAttrName::LongAbout)
+ | Some(MagicAttrName::LongHelp)
+ | Some(MagicAttrName::Author)
+ | Some(MagicAttrName::Version)
+ => {
+ let expr = attr.value_or_abort();
+ self.push_method(*attr.kind.get(), attr.name.clone(), expr);
+ }
+
+ // Magic only for the default, otherwise just forward to the builder
+ Some(MagicAttrName::ValueParser) | Some(MagicAttrName::Action) => {
+ let expr = attr.value_or_abort();
+ self.push_method(*attr.kind.get(), attr.name.clone(), expr);
+ }
+
+ // Directives that never receive a value
+ Some(MagicAttrName::ValueEnum)
+ | Some(MagicAttrName::VerbatimDocComment) => {
+ let expr = attr.value_or_abort();
+ abort!(expr, "attribute `{}` does not accept a value", attr.name);
+ }
+
+ // Kinds
+ Some(MagicAttrName::FromGlobal)
+ | Some(MagicAttrName::Subcommand)
+ | Some(MagicAttrName::ExternalSubcommand)
+ | Some(MagicAttrName::Flatten)
+ | Some(MagicAttrName::Skip) => {
+ }
+ }
+ }
+
+ if self.has_explicit_methods() {
+ if let Kind::Skip(_, attr) = &*self.kind {
+ abort!(
+ self.methods[0].name.span(),
+ "`{}` cannot be used with `#[{}(skip)]",
+ self.methods[0].name,
+ attr.as_str(),
+ );
+ }
+ if let Kind::FromGlobal(_) = &*self.kind {
+ abort!(
+ self.methods[0].name.span(),
+ "`{}` cannot be used with `#[arg(from_global)]",
+ self.methods[0].name,
+ );
+ }
+ }
+ }
+
+ fn push_doc_comment(&mut self, attrs: &[Attribute], short_name: &str, long_name: Option<&str>) {
+ let lines = extract_doc_comment(attrs);
+
+ if !lines.is_empty() {
+ let (short_help, long_help) =
+ format_doc_comment(&lines, !self.verbatim_doc_comment, self.force_long_help);
+ let short_name = format_ident!("{}", short_name);
+ let short = Method::new(
+ short_name,
+ short_help
+ .map(|h| quote!(#h))
+ .unwrap_or_else(|| quote!(None)),
+ );
+ self.doc_comment.push(short);
+ if let Some(long_name) = long_name {
+ let long_name = format_ident!("{}", long_name);
+ let long = Method::new(
+ long_name,
+ long_help
+ .map(|h| quote!(#h))
+ .unwrap_or_else(|| quote!(None)),
+ );
+ self.doc_comment.push(long);
+ }
+ }
+ }
+
+ fn set_kind(&mut self, kind: Sp<Kind>) {
+ match (self.kind.get(), kind.get()) {
+ (Kind::Arg(_), Kind::FromGlobal(_))
+ | (Kind::Arg(_), Kind::Subcommand(_))
+ | (Kind::Arg(_), Kind::Flatten(_))
+ | (Kind::Arg(_), Kind::Skip(_, _))
+ | (Kind::Command(_), Kind::Subcommand(_))
+ | (Kind::Command(_), Kind::Flatten(_))
+ | (Kind::Command(_), Kind::Skip(_, _))
+ | (Kind::Command(_), Kind::ExternalSubcommand)
+ | (Kind::Value, Kind::Skip(_, _)) => {
+ self.kind = kind;
+ }
+
+ (_, _) => {
+ let old = self.kind.name();
+ let new = kind.name();
+ abort!(kind.span(), "`{}` cannot be used with `{}`", new, old);
+ }
+ }
+ }
+
+ pub fn find_default_method(&self) -> Option<&Method> {
+ self.methods
+ .iter()
+ .find(|m| m.name == "default_value" || m.name == "default_value_os")
+ }
+
+ /// generate methods from attributes on top of struct or enum
+ pub fn initial_top_level_methods(&self) -> TokenStream {
+ let next_display_order = self.next_display_order.as_ref().into_iter();
+ let next_help_heading = self.next_help_heading.as_ref().into_iter();
+ quote!(
+ #(#next_display_order)*
+ #(#next_help_heading)*
+ )
+ }
+
+ pub fn final_top_level_methods(&self) -> TokenStream {
+ let methods = &self.methods;
+ let doc_comment = &self.doc_comment;
+
+ quote!( #(#doc_comment)* #(#methods)*)
+ }
+
+ /// generate methods on top of a field
+ pub fn field_methods(&self) -> proc_macro2::TokenStream {
+ let methods = &self.methods;
+ let doc_comment = &self.doc_comment;
+ quote!( #(#doc_comment)* #(#methods)* )
+ }
+
+ pub fn deprecations(&self) -> proc_macro2::TokenStream {
+ let deprecations = &self.deprecations;
+ quote!( #(#deprecations)* )
+ }
+
+ pub fn next_display_order(&self) -> TokenStream {
+ let next_display_order = self.next_display_order.as_ref().into_iter();
+ quote!( #(#next_display_order)* )
+ }
+
+ pub fn next_help_heading(&self) -> TokenStream {
+ let next_help_heading = self.next_help_heading.as_ref().into_iter();
+ quote!( #(#next_help_heading)* )
+ }
+
+ pub fn ident(&self) -> &Ident {
+ &self.ident
+ }
+
+ pub fn id(&self) -> TokenStream {
+ self.name.clone().raw()
+ }
+
+ pub fn cased_name(&self) -> TokenStream {
+ self.name.clone().translate(*self.casing)
+ }
+
+ pub fn value_name(&self) -> TokenStream {
+ self.name.clone().translate(CasingStyle::ScreamingSnake)
+ }
+
+ pub fn value_parser(&self, field_type: &Type) -> Method {
+ self.value_parser
+ .clone()
+ .map(|p| {
+ let inner_type = inner_type(field_type);
+ p.resolve(inner_type)
+ })
+ .unwrap_or_else(|| {
+ let inner_type = inner_type(field_type);
+ if let Some(action) = self.action.as_ref() {
+ let span = action.span();
+ default_value_parser(inner_type, span)
+ } else {
+ let span = self
+ .action
+ .as_ref()
+ .map(|a| a.span())
+ .unwrap_or_else(|| self.kind.span());
+ default_value_parser(inner_type, span)
+ }
+ })
+ }
+
+ pub fn action(&self, field_type: &Type) -> Method {
+ self.action
+ .clone()
+ .map(|p| p.resolve(field_type))
+ .unwrap_or_else(|| {
+ if let Some(value_parser) = self.value_parser.as_ref() {
+ let span = value_parser.span();
+ default_action(field_type, span)
+ } else {
+ let span = self
+ .value_parser
+ .as_ref()
+ .map(|a| a.span())
+ .unwrap_or_else(|| self.kind.span());
+ default_action(field_type, span)
+ }
+ })
+ }
+
+ pub fn kind(&self) -> Sp<Kind> {
+ self.kind.clone()
+ }
+
+ pub fn is_positional(&self) -> bool {
+ self.is_positional
+ }
+
+ pub fn casing(&self) -> Sp<CasingStyle> {
+ self.casing
+ }
+
+ pub fn env_casing(&self) -> Sp<CasingStyle> {
+ self.env_casing
+ }
+
+ pub fn has_explicit_methods(&self) -> bool {
+ self.methods
+ .iter()
+ .any(|m| m.name != "help" && m.name != "long_help")
+ }
+
+ pub fn skip_group(&self) -> bool {
+ self.skip_group
+ }
+}
+
+#[derive(Clone)]
+enum ValueParser {
+ Explicit(Method),
+ Implicit(Ident),
+}
+
+impl ValueParser {
+ fn resolve(self, _inner_type: &Type) -> Method {
+ match self {
+ Self::Explicit(method) => method,
+ Self::Implicit(ident) => default_value_parser(_inner_type, ident.span()),
+ }
+ }
+
+ fn span(&self) -> Span {
+ match self {
+ Self::Explicit(method) => method.name.span(),
+ Self::Implicit(ident) => ident.span(),
+ }
+ }
+}
+
+fn default_value_parser(inner_type: &Type, span: Span) -> Method {
+ let func = Ident::new("value_parser", span);
+ Method::new(
+ func,
+ quote_spanned! { span=>
+ clap::value_parser!(#inner_type)
+ },
+ )
+}
+
+#[derive(Clone)]
+pub enum Action {
+ Explicit(Method),
+ Implicit(Ident),
+}
+
+impl Action {
+ pub fn resolve(self, _field_type: &Type) -> Method {
+ match self {
+ Self::Explicit(method) => method,
+ Self::Implicit(ident) => default_action(_field_type, ident.span()),
+ }
+ }
+
+ pub fn span(&self) -> Span {
+ match self {
+ Self::Explicit(method) => method.name.span(),
+ Self::Implicit(ident) => ident.span(),
+ }
+ }
+}
+
+fn default_action(field_type: &Type, span: Span) -> Method {
+ let ty = Ty::from_syn_ty(field_type);
+ let args = match *ty {
+ Ty::Vec | Ty::OptionVec => {
+ quote_spanned! { span=>
+ clap::ArgAction::Append
+ }
+ }
+ Ty::Option | Ty::OptionOption => {
+ quote_spanned! { span=>
+ clap::ArgAction::Set
+ }
+ }
+ _ => {
+ if is_simple_ty(field_type, "bool") {
+ quote_spanned! { span=>
+ clap::ArgAction::SetTrue
+ }
+ } else {
+ quote_spanned! { span=>
+ clap::ArgAction::Set
+ }
+ }
+ }
+ };
+
+ let func = Ident::new("action", span);
+ Method::new(func, args)
+}
+
+#[allow(clippy::large_enum_variant)]
+#[derive(Clone)]
+pub enum Kind {
+ Arg(Sp<Ty>),
+ Command(Sp<Ty>),
+ Value,
+ FromGlobal(Sp<Ty>),
+ Subcommand(Sp<Ty>),
+ Flatten(Sp<Ty>),
+ Skip(Option<AttrValue>, AttrKind),
+ ExternalSubcommand,
+}
+
+impl Kind {
+ pub fn name(&self) -> &'static str {
+ match self {
+ Self::Arg(_) => "arg",
+ Self::Command(_) => "command",
+ Self::Value => "value",
+ Self::FromGlobal(_) => "from_global",
+ Self::Subcommand(_) => "subcommand",
+ Self::Flatten(_) => "flatten",
+ Self::Skip(_, _) => "skip",
+ Self::ExternalSubcommand => "external_subcommand",
+ }
+ }
+
+ pub fn attr_kind(&self) -> AttrKind {
+ match self {
+ Self::Arg(_) => AttrKind::Arg,
+ Self::Command(_) => AttrKind::Command,
+ Self::Value => AttrKind::Value,
+ Self::FromGlobal(_) => AttrKind::Arg,
+ Self::Subcommand(_) => AttrKind::Command,
+ Self::Flatten(_) => AttrKind::Command,
+ Self::Skip(_, kind) => *kind,
+ Self::ExternalSubcommand => AttrKind::Command,
+ }
+ }
+
+ pub fn ty(&self) -> Option<&Sp<Ty>> {
+ match self {
+ Self::Arg(ty)
+ | Self::Command(ty)
+ | Self::Flatten(ty)
+ | Self::FromGlobal(ty)
+ | Self::Subcommand(ty) => Some(ty),
+ Self::Value | Self::Skip(_, _) | Self::ExternalSubcommand => None,
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct Method {
+ name: Ident,
+ args: TokenStream,
+}
+
+impl Method {
+ pub fn new(name: Ident, args: TokenStream) -> Self {
+ Method { name, args }
+ }
+
+ fn from_env(ident: Ident, env_var: &str) -> Option<Self> {
+ let mut lit = match env::var(env_var) {
+ Ok(val) => {
+ if val.is_empty() {
+ return None;
+ }
+ LitStr::new(&val, ident.span())
+ }
+ Err(_) => {
+ abort!(ident,
+ "cannot derive `{}` from Cargo.toml", ident;
+ note = "`{}` environment variable is not set", env_var;
+ help = "use `{} = \"...\"` to set {} manually", ident, ident;
+ );
+ }
+ };
+
+ if ident == "author" {
+ let edited = process_author_str(&lit.value());
+ lit = LitStr::new(&edited, lit.span());
+ }
+
+ Some(Method::new(ident, quote!(#lit)))
+ }
+
+ pub(crate) fn args(&self) -> &TokenStream {
+ &self.args
+ }
+}
+
+impl ToTokens for Method {
+ fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
+ let Method { ref name, ref args } = self;
+
+ let tokens = quote!( .#name(#args) );
+
+ tokens.to_tokens(ts);
+ }
+}
+
+#[derive(Clone)]
+pub struct Deprecation {
+ pub span: Span,
+ pub id: &'static str,
+ pub version: &'static str,
+ pub description: String,
+}
+
+impl Deprecation {
+ fn attribute(version: &'static str, old: AttrKind, new: AttrKind, span: Span) -> Self {
+ Self {
+ span,
+ id: "old_attribute",
+ version,
+ description: format!(
+ "Attribute `#[{}(...)]` has been deprecated in favor of `#[{}(...)]`",
+ old.as_str(),
+ new.as_str()
+ ),
+ }
+ }
+}
+
+impl ToTokens for Deprecation {
+ fn to_tokens(&self, ts: &mut proc_macro2::TokenStream) {
+ let tokens = if cfg!(feature = "deprecated") {
+ let Deprecation {
+ span,
+ id,
+ version,
+ description,
+ } = self;
+ let span = *span;
+ let id = Ident::new(id, span);
+
+ quote_spanned!(span=> {
+ #[deprecated(since = #version, note = #description)]
+ fn #id() {}
+ #id();
+ })
+ } else {
+ quote!()
+ };
+
+ tokens.to_tokens(ts);
+ }
+}
+
+fn assert_attr_kind(attr: &ClapAttr, possible_kind: &[AttrKind]) {
+ if *attr.kind.get() == AttrKind::Clap || *attr.kind.get() == AttrKind::StructOpt {
+ // deprecated
+ } else if !possible_kind.contains(attr.kind.get()) {
+ let options = possible_kind
+ .iter()
+ .map(|k| format!("`#[{}({})]`", k.as_str(), attr.name))
+ .collect::<Vec<_>>();
+ abort!(
+ attr.name,
+ "Unknown `#[{}({})]` attribute ({} exists)",
+ attr.kind.as_str(),
+ attr.name,
+ options.join(", ")
+ );
+ }
+}
+
+/// replace all `:` with `, ` when not inside the `<>`
+///
+/// `"author1:author2:author3" => "author1, author2, author3"`
+/// `"author1 <http://website1.com>:author2" => "author1 <http://website1.com>, author2"
+fn process_author_str(author: &str) -> String {
+ let mut res = String::with_capacity(author.len());
+ let mut inside_angle_braces = 0usize;
+
+ for ch in author.chars() {
+ if inside_angle_braces > 0 && ch == '>' {
+ inside_angle_braces -= 1;
+ res.push(ch);
+ } else if ch == '<' {
+ inside_angle_braces += 1;
+ res.push(ch);
+ } else if inside_angle_braces == 0 && ch == ':' {
+ res.push_str(", ");
+ } else {
+ res.push(ch);
+ }
+ }
+
+ res
+}
+
+/// Defines the casing for the attributes long representation.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum CasingStyle {
+ /// Indicate word boundaries with uppercase letter, excluding the first word.
+ Camel,
+ /// Keep all letters lowercase and indicate word boundaries with hyphens.
+ Kebab,
+ /// Indicate word boundaries with uppercase letter, including the first word.
+ Pascal,
+ /// Keep all letters uppercase and indicate word boundaries with underscores.
+ ScreamingSnake,
+ /// Keep all letters lowercase and indicate word boundaries with underscores.
+ Snake,
+ /// Keep all letters lowercase and remove word boundaries.
+ Lower,
+ /// Keep all letters uppercase and remove word boundaries.
+ Upper,
+ /// Use the original attribute name defined in the code.
+ Verbatim,
+}
+
+impl CasingStyle {
+ fn from_lit(name: &LitStr) -> Sp<Self> {
+ use self::CasingStyle::*;
+
+ let normalized = name.value().to_upper_camel_case().to_lowercase();
+ let cs = |kind| Sp::new(kind, name.span());
+
+ match normalized.as_ref() {
+ "camel" | "camelcase" => cs(Camel),
+ "kebab" | "kebabcase" => cs(Kebab),
+ "pascal" | "pascalcase" => cs(Pascal),
+ "screamingsnake" | "screamingsnakecase" => cs(ScreamingSnake),
+ "snake" | "snakecase" => cs(Snake),
+ "lower" | "lowercase" => cs(Lower),
+ "upper" | "uppercase" => cs(Upper),
+ "verbatim" | "verbatimcase" => cs(Verbatim),
+ s => abort!(name, "unsupported casing: `{}`", s),
+ }
+ }
+}
+
+#[derive(Clone)]
+pub enum Name {
+ Derived(Ident),
+ Assigned(TokenStream),
+}
+
+impl Name {
+ pub fn raw(self) -> TokenStream {
+ match self {
+ Name::Assigned(tokens) => tokens,
+ Name::Derived(ident) => {
+ let s = ident.unraw().to_string();
+ quote_spanned!(ident.span()=> #s)
+ }
+ }
+ }
+
+ pub fn translate(self, style: CasingStyle) -> TokenStream {
+ use CasingStyle::*;
+
+ match self {
+ Name::Assigned(tokens) => tokens,
+ Name::Derived(ident) => {
+ let s = ident.unraw().to_string();
+ let s = match style {
+ Pascal => s.to_upper_camel_case(),
+ Kebab => s.to_kebab_case(),
+ Camel => s.to_lower_camel_case(),
+ ScreamingSnake => s.to_shouty_snake_case(),
+ Snake => s.to_snake_case(),
+ Lower => s.to_snake_case().replace('_', ""),
+ Upper => s.to_shouty_snake_case().replace('_', ""),
+ Verbatim => s,
+ };
+ quote_spanned!(ident.span()=> #s)
+ }
+ }
+ }
+
+ pub fn translate_char(self, style: CasingStyle) -> TokenStream {
+ use CasingStyle::*;
+
+ match self {
+ Name::Assigned(tokens) => quote!( (#tokens).chars().next().unwrap() ),
+ Name::Derived(ident) => {
+ let s = ident.unraw().to_string();
+ let s = match style {
+ Pascal => s.to_upper_camel_case(),
+ Kebab => s.to_kebab_case(),
+ Camel => s.to_lower_camel_case(),
+ ScreamingSnake => s.to_shouty_snake_case(),
+ Snake => s.to_snake_case(),
+ Lower => s.to_snake_case(),
+ Upper => s.to_shouty_snake_case(),
+ Verbatim => s,
+ };
+
+ let s = s.chars().next().unwrap();
+ quote_spanned!(ident.span()=> #s)
+ }
+ }
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 86b7628..84f2f0d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -22,35 +22,27 @@ use proc_macro::TokenStream;
use proc_macro_error::proc_macro_error;
use syn::{parse_macro_input, DeriveInput};
-mod attrs;
+mod attr;
mod derives;
mod dummies;
-mod parse;
+mod item;
mod utils;
/// Generates the `ValueEnum` impl.
-#[proc_macro_derive(ValueEnum, attributes(clap))]
+#[proc_macro_derive(ValueEnum, attributes(clap, value))]
#[proc_macro_error]
pub fn value_enum(input: TokenStream) -> TokenStream {
let input: DeriveInput = parse_macro_input!(input);
derives::derive_value_enum(&input).into()
}
-/// Generates the `ValueEnum` impl.
-#[proc_macro_derive(ArgEnum, attributes(clap))]
-#[proc_macro_error]
-pub fn arg_enum(input: TokenStream) -> TokenStream {
- let input: DeriveInput = parse_macro_input!(input);
- derives::derive_value_enum(&input).into()
-}
-
/// Generates the `Parser` implementation.
///
/// This is far less verbose than defining the `clap::Command` struct manually,
/// receiving an instance of `clap::ArgMatches` from conducting parsing, and then
/// implementing a conversion code to instantiate an instance of the user
/// context struct.
-#[proc_macro_derive(Parser, attributes(clap, structopt))]
+#[proc_macro_derive(Parser, attributes(clap, structopt, command, arg, group))]
#[proc_macro_error]
pub fn parser(input: TokenStream) -> TokenStream {
let input: DeriveInput = parse_macro_input!(input);
@@ -58,7 +50,7 @@ pub fn parser(input: TokenStream) -> TokenStream {
}
/// Generates the `Subcommand` impl.
-#[proc_macro_derive(Subcommand, attributes(clap))]
+#[proc_macro_derive(Subcommand, attributes(clap, command, arg, group))]
#[proc_macro_error]
pub fn subcommand(input: TokenStream) -> TokenStream {
let input: DeriveInput = parse_macro_input!(input);
@@ -66,7 +58,7 @@ pub fn subcommand(input: TokenStream) -> TokenStream {
}
/// Generates the `Args` impl.
-#[proc_macro_derive(Args, attributes(clap))]
+#[proc_macro_derive(Args, attributes(clap, command, arg, group))]
#[proc_macro_error]
pub fn args(input: TokenStream) -> TokenStream {
let input: DeriveInput = parse_macro_input!(input);
diff --git a/src/parse.rs b/src/parse.rs
deleted file mode 100644
index 7d7e19c..0000000
--- a/src/parse.rs
+++ /dev/null
@@ -1,288 +0,0 @@
-use std::iter::FromIterator;
-
-use proc_macro_error::{abort, ResultExt};
-use quote::ToTokens;
-use syn::{
- self, parenthesized,
- parse::{Parse, ParseBuffer, ParseStream},
- punctuated::Punctuated,
- Attribute, Expr, ExprLit, Ident, Lit, LitBool, LitStr, Token,
-};
-
-pub fn parse_clap_attributes(all_attrs: &[Attribute]) -> Vec<ClapAttr> {
- all_attrs
- .iter()
- .filter(|attr| attr.path.is_ident("clap") || attr.path.is_ident("structopt"))
- .flat_map(|attr| {
- attr.parse_args_with(Punctuated::<ClapAttr, Token![,]>::parse_terminated)
- .unwrap_or_abort()
- })
- .collect()
-}
-
-#[allow(clippy::large_enum_variant)]
-#[derive(Clone)]
-pub enum ClapAttr {
- // single-identifier attributes
- Short(Ident),
- Long(Ident),
- ValueParser(Ident),
- Action(Ident),
- Env(Ident),
- Flatten(Ident),
- ValueEnum(Ident),
- FromGlobal(Ident),
- Subcommand(Ident),
- VerbatimDocComment(Ident),
- ExternalSubcommand(Ident),
- About(Ident),
- Author(Ident),
- Version(Ident),
-
- // ident = "string literal"
- RenameAllEnv(Ident, LitStr),
- RenameAll(Ident, LitStr),
- NameLitStr(Ident, LitStr),
-
- // parse(parser_kind [= parser_func])
- Parse(Ident, ParserSpec),
-
- // ident [= arbitrary_expr]
- Skip(Ident, Option<Expr>),
-
- // ident = arbitrary_expr
- NameExpr(Ident, Expr),
- DefaultValueT(Ident, Option<Expr>),
- DefaultValuesT(Ident, Expr),
- DefaultValueOsT(Ident, Option<Expr>),
- DefaultValuesOsT(Ident, Expr),
- NextDisplayOrder(Ident, Expr),
- NextHelpHeading(Ident, Expr),
- HelpHeading(Ident, Expr),
-
- // ident(arbitrary_expr,*)
- MethodCall(Ident, Vec<Expr>),
-}
-
-impl Parse for ClapAttr {
- fn parse(input: ParseStream) -> syn::Result<Self> {
- use self::ClapAttr::*;
-
- let name: Ident = input.parse()?;
- let name_str = name.to_string();
-
- if input.peek(Token![=]) {
- // `name = value` attributes.
- let assign_token = input.parse::<Token![=]>()?; // skip '='
-
- if input.peek(LitStr) {
- let lit: LitStr = input.parse()?;
-
- match &*name_str {
- "rename_all" => Ok(RenameAll(name, lit)),
- "rename_all_env" => Ok(RenameAllEnv(name, lit)),
-
- "skip" => {
- let expr = ExprLit {
- attrs: vec![],
- lit: Lit::Str(lit),
- };
- let expr = Expr::Lit(expr);
- Ok(Skip(name, Some(expr)))
- }
-
- "next_display_order" => {
- let expr = ExprLit {
- attrs: vec![],
- lit: Lit::Str(lit),
- };
- let expr = Expr::Lit(expr);
- Ok(NextDisplayOrder(name, expr))
- }
-
- "next_help_heading" => {
- let expr = ExprLit {
- attrs: vec![],
- lit: Lit::Str(lit),
- };
- let expr = Expr::Lit(expr);
- Ok(NextHelpHeading(name, expr))
- }
- "help_heading" => {
- let expr = ExprLit {
- attrs: vec![],
- lit: Lit::Str(lit),
- };
- let expr = Expr::Lit(expr);
- Ok(HelpHeading(name, expr))
- }
-
- _ => Ok(NameLitStr(name, lit)),
- }
- } else {
- match input.parse::<Expr>() {
- Ok(expr) => match &*name_str {
- "skip" => Ok(Skip(name, Some(expr))),
- "default_value_t" => Ok(DefaultValueT(name, Some(expr))),
- "default_values_t" => Ok(DefaultValuesT(name, expr)),
- "default_value_os_t" => Ok(DefaultValueOsT(name, Some(expr))),
- "default_values_os_t" => Ok(DefaultValuesOsT(name, expr)),
- "next_display_order" => Ok(NextDisplayOrder(name, expr)),
- "next_help_heading" => Ok(NextHelpHeading(name, expr)),
- "help_heading" => Ok(HelpHeading(name, expr)),
- _ => Ok(NameExpr(name, expr)),
- },
-
- Err(_) => abort! {
- assign_token,
- "expected `string literal` or `expression` after `=`"
- },
- }
- }
- } else if input.peek(syn::token::Paren) {
- // `name(...)` attributes.
- let nested;
- parenthesized!(nested in input);
-
- match name_str.as_ref() {
- "parse" => {
- let parser_specs: Punctuated<ParserSpec, Token![,]> =
- nested.parse_terminated(ParserSpec::parse)?;
-
- if parser_specs.len() == 1 {
- Ok(Parse(name, parser_specs[0].clone()))
- } else {
- abort!(name, "parse must have exactly one argument")
- }
- }
-
- "raw" => match nested.parse::<LitBool>() {
- Ok(bool_token) => {
- let expr = ExprLit {
- attrs: vec![],
- lit: Lit::Bool(bool_token),
- };
- let expr = Expr::Lit(expr);
- Ok(MethodCall(name, vec![expr]))
- }
-
- Err(_) => {
- abort!(name,
- "`#[clap(raw(...))` attributes are removed, \
- they are replaced with raw methods";
- help = "if you meant to call `clap::Arg::raw()` method \
- you should use bool literal, like `raw(true)` or `raw(false)`";
- note = raw_method_suggestion(nested);
- );
- }
- },
-
- _ => {
- let method_args: Punctuated<_, Token![,]> =
- nested.parse_terminated(Expr::parse)?;
- Ok(MethodCall(name, Vec::from_iter(method_args)))
- }
- }
- } else {
- // Attributes represented with a sole identifier.
- match name_str.as_ref() {
- "long" => Ok(Long(name)),
- "short" => Ok(Short(name)),
- "value_parser" => Ok(ValueParser(name)),
- "action" => Ok(Action(name)),
- "env" => Ok(Env(name)),
- "flatten" => Ok(Flatten(name)),
- "arg_enum" => Ok(ValueEnum(name)),
- "value_enum" => Ok(ValueEnum(name)),
- "from_global" => Ok(FromGlobal(name)),
- "subcommand" => Ok(Subcommand(name)),
- "external_subcommand" => Ok(ExternalSubcommand(name)),
- "verbatim_doc_comment" => Ok(VerbatimDocComment(name)),
-
- "default_value" => {
- abort!(name,
- "`#[clap(default_value)` attribute (without a value) has been replaced by `#[clap(default_value_t)]`.";
- help = "Change the attribute to `#[clap(default_value_t)]`";
- )
- }
- "default_value_t" => Ok(DefaultValueT(name, None)),
- "default_value_os_t" => Ok(DefaultValueOsT(name, None)),
- "about" => (Ok(About(name))),
- "author" => (Ok(Author(name))),
- "version" => Ok(Version(name)),
-
- "skip" => Ok(Skip(name, None)),
-
- _ => abort!(name, "unexpected attribute: {}", name_str),
- }
- }
- }
-}
-
-#[derive(Clone)]
-pub struct ParserSpec {
- pub kind: Ident,
- pub eq_token: Option<Token![=]>,
- pub parse_func: Option<Expr>,
-}
-
-impl Parse for ParserSpec {
- fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
- let kind = input
- .parse()
- .map_err(|_| input.error("parser specification must start with identifier"))?;
- let eq_token = input.parse()?;
- let parse_func = match eq_token {
- None => None,
- Some(_) => Some(input.parse()?),
- };
- Ok(ParserSpec {
- kind,
- eq_token,
- parse_func,
- })
- }
-}
-
-fn raw_method_suggestion(ts: ParseBuffer) -> String {
- let do_parse = move || -> Result<(Ident, Punctuated<Expr, Token![,]>), syn::Error> {
- let name = ts.parse()?;
- let _eq: Token![=] = ts.parse()?;
- let val: LitStr = ts.parse()?;
- let exprs = val.parse_with(Punctuated::<Expr, Token![,]>::parse_terminated)?;
- Ok((name, exprs))
- };
-
- fn to_string<T: ToTokens>(val: &T) -> String {
- val.to_token_stream()
- .to_string()
- .replace(' ', "")
- .replace(',', ", ")
- }
-
- if let Ok((name, exprs)) = do_parse() {
- let suggestion = if exprs.len() == 1 {
- let val = to_string(&exprs[0]);
- format!(" = {}", val)
- } else {
- let val = exprs
- .into_iter()
- .map(|expr| to_string(&expr))
- .collect::<Vec<_>>()
- .join(", ");
-
- format!("({})", val)
- };
-
- format!(
- "if you need to call `clap::Arg/Command::{}` method you \
- can do it like this: #[clap({}{})]",
- name, name, suggestion
- )
- } else {
- "if you need to call some method from `clap::Arg/Command` \
- you should use raw method, see \
- https://github.com/clap-rs/clap/blob/master/examples/derive_ref/README.md#raw-attributes"
- .into()
- }
-}
diff --git a/src/utils/doc_comments.rs b/src/utils/doc_comments.rs
index f0a5034..5183b6b 100644
--- a/src/utils/doc_comments.rs
+++ b/src/utils/doc_comments.rs
@@ -3,43 +3,58 @@
//! #[derive(Parser)] works in terms of "paragraphs". Paragraph is a sequence of
//! non-empty adjacent lines, delimited by sequences of blank (whitespace only) lines.
-use crate::attrs::Method;
-
-use quote::{format_ident, quote};
use std::iter;
-pub fn process_doc_comment(lines: Vec<String>, name: &str, preprocess: bool) -> Vec<Method> {
+pub fn extract_doc_comment(attrs: &[syn::Attribute]) -> Vec<String> {
+ use syn::Lit::*;
+ use syn::Meta::*;
+ use syn::MetaNameValue;
+
// multiline comments (`/** ... */`) may have LFs (`\n`) in them,
// we need to split so we could handle the lines correctly
//
// we also need to remove leading and trailing blank lines
- let mut lines: Vec<&str> = lines
+ let mut lines: Vec<_> = attrs
.iter()
+ .filter(|attr| attr.path.is_ident("doc"))
+ .filter_map(|attr| {
+ if let Ok(NameValue(MetaNameValue { lit: Str(s), .. })) = attr.parse_meta() {
+ Some(s.value())
+ } else {
+ // non #[doc = "..."] attributes are not our concern
+ // we leave them for rustc to handle
+ None
+ }
+ })
.skip_while(|s| is_blank(s))
- .flat_map(|s| s.split('\n'))
+ .flat_map(|s| {
+ let lines = s
+ .split('\n')
+ .map(|s| {
+ // remove one leading space no matter what
+ let s = s.strip_prefix(' ').unwrap_or(s);
+ s.to_owned()
+ })
+ .collect::<Vec<_>>();
+ lines
+ })
.collect();
while let Some(true) = lines.last().map(|s| is_blank(s)) {
lines.pop();
}
- // remove one leading space no matter what
- for line in lines.iter_mut() {
- if line.starts_with(' ') {
- *line = &line[1..];
- }
- }
-
- if lines.is_empty() {
- return vec![];
- }
-
- let short_name = format_ident!("{}", name);
- let long_name = format_ident!("long_{}", name);
+ lines
+}
+pub fn format_doc_comment(
+ lines: &[String],
+ preprocess: bool,
+ force_long: bool,
+) -> (Option<String>, Option<String>) {
if let Some(first_blank) = lines.iter().position(|s| is_blank(s)) {
let (short, long) = if preprocess {
- let paragraphs = split_paragraphs(&lines);
+ let paragraphs = split_paragraphs(lines);
let short = paragraphs[0].clone();
let long = paragraphs.join("\n\n");
(remove_period(short), long)
@@ -49,26 +64,24 @@ pub fn process_doc_comment(lines: Vec<String>, name: &str, preprocess: bool) ->
(short, long)
};
- vec![
- Method::new(short_name, quote!(#short)),
- Method::new(long_name, quote!(#long)),
- ]
+ (Some(short), Some(long))
} else {
- let short = if preprocess {
- let s = merge_lines(&lines);
- remove_period(s)
+ let (short, long) = if preprocess {
+ let short = merge_lines(lines);
+ let long = force_long.then(|| short.clone());
+ let short = remove_period(short);
+ (short, long)
} else {
- lines.join("\n")
+ let short = lines.join("\n");
+ let long = force_long.then(|| short.clone());
+ (short, long)
};
- vec![
- Method::new(short_name, quote!(#short)),
- Method::new(long_name, quote!(None)),
- ]
+ (Some(short), long)
}
}
-fn split_paragraphs(lines: &[&str]) -> Vec<String> {
+fn split_paragraphs(lines: &[String]) -> Vec<String> {
let mut last_line = 0;
iter::from_fn(|| {
let slice = &lines[last_line..];
@@ -102,6 +115,10 @@ fn is_blank(s: &str) -> bool {
s.trim().is_empty()
}
-fn merge_lines(lines: &[&str]) -> String {
- lines.iter().map(|s| s.trim()).collect::<Vec<_>>().join(" ")
+fn merge_lines(lines: impl IntoIterator<Item = impl AsRef<str>>) -> String {
+ lines
+ .into_iter()
+ .map(|s| s.as_ref().trim().to_owned())
+ .collect::<Vec<_>>()
+ .join(" ")
}
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index 77a467c..9f8b6f3 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -2,8 +2,10 @@ mod doc_comments;
mod spanned;
mod ty;
+pub use doc_comments::extract_doc_comment;
+pub use doc_comments::format_doc_comment;
+
pub use self::{
- doc_comments::process_doc_comment,
spanned::Sp,
ty::{inner_type, is_simple_ty, sub_type, subty_if_name, Ty},
};
diff --git a/src/utils/spanned.rs b/src/utils/spanned.rs
index 11415f6..339a654 100644
--- a/src/utils/spanned.rs
+++ b/src/utils/spanned.rs
@@ -5,7 +5,7 @@ use syn::LitStr;
use std::ops::{Deref, DerefMut};
/// An entity with a span attached.
-#[derive(Debug, Clone)]
+#[derive(Debug, Copy, Clone)]
pub struct Sp<T> {
val: T,
span: Span,
@@ -16,11 +16,8 @@ impl<T> Sp<T> {
Sp { val, span }
}
- pub fn call_site(val: T) -> Self {
- Sp {
- val,
- span: Span::call_site(),
- }
+ pub fn get(&self) -> &T {
+ &self.val
}
pub fn span(&self) -> Span {
diff --git a/src/utils/ty.rs b/src/utils/ty.rs
index 0bcb59f..1cf0514 100644
--- a/src/utils/ty.rs
+++ b/src/utils/ty.rs
@@ -7,8 +7,9 @@ use syn::{
PathSegment, Type, TypePath,
};
-#[derive(Copy, Clone, PartialEq, Debug)]
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Ty {
+ Unit,
Vec,
Option,
OptionOption,
@@ -21,7 +22,9 @@ impl Ty {
use self::Ty::*;
let t = |kind| Sp::new(kind, ty.span());
- if is_generic_ty(ty, "Vec") {
+ if is_unit_ty(ty) {
+ t(Unit)
+ } else if is_generic_ty(ty, "Vec") {
t(Vec)
} else if let Some(subty) = subty_if_name(ty, "Option") {
if is_generic_ty(subty, "Option") {
@@ -35,6 +38,17 @@ impl Ty {
t(Other)
}
}
+
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Self::Unit => "()",
+ Self::Vec => "Vec<T>",
+ Self::Option => "Option<T>",
+ Self::OptionOption => "Option<Option<T>>",
+ Self::OptionVec => "Option<Vec<T>>",
+ Self::Other => "...other...",
+ }
+ }
}
pub fn inner_type(field_ty: &syn::Type) -> &syn::Type {
@@ -111,6 +125,14 @@ fn is_generic_ty(ty: &syn::Type, name: &str) -> bool {
subty_if_name(ty, name).is_some()
}
+fn is_unit_ty(ty: &syn::Type) -> bool {
+ if let syn::Type::Tuple(tuple) = ty {
+ tuple.elems.is_empty()
+ } else {
+ false
+ }
+}
+
fn only_one<I, T>(mut iter: I) -> Option<T>
where
I: Iterator<Item = T>,