diff options
author | Jeff Vander Stoep <jeffv@google.com> | 2023-02-01 14:21:29 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2023-02-01 14:21:29 +0000 |
commit | 0b51dba309b5f9130df8cb98b5e70b067cffb14a (patch) | |
tree | 97df10f43d1dddccfaff512372427b7fa2a41bd2 | |
parent | 999908da63624c3a844445dab95e931d41941f21 (diff) | |
parent | 5428a147eefd352c61715bdb2f903774903cb977 (diff) | |
download | argh_derive-0b51dba309b5f9130df8cb98b5e70b067cffb14a.tar.gz |
Upgrade argh_derive to 0.1.10 am: 5428a147ee
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/argh_derive/+/2411608
Change-Id: I4ad72e57483d5ad004288004adce977578d4ecab
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r-- | .cargo_vcs_info.json | 2 | ||||
-rw-r--r-- | Android.bp | 3 | ||||
-rw-r--r-- | Cargo.toml | 7 | ||||
-rw-r--r-- | Cargo.toml.orig | 5 | ||||
-rw-r--r-- | METADATA | 14 | ||||
-rw-r--r-- | src/help.rs | 17 | ||||
-rw-r--r-- | src/lib.rs | 71 | ||||
-rw-r--r-- | src/parse_attrs.rs | 21 |
8 files changed, 94 insertions, 46 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index 1b6d0b1..9595563 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,6 +1,6 @@ { "git": { - "sha1": "f1f85d2d89cbe09314dc1b59e581b8a43531cf3e" + "sha1": "3f3c29726a21c4b541bb2b9aa2c592461897ded0" }, "path_in_vcs": "argh_derive" }
\ No newline at end of file @@ -22,12 +22,11 @@ rust_proc_macro { name: "libargh_derive", crate_name: "argh_derive", cargo_env_compat: true, - cargo_pkg_version: "0.1.9", + cargo_pkg_version: "0.1.10", srcs: ["src/lib.rs"], edition: "2018", rustlibs: [ "libargh_shared", - "libheck", "libproc_macro2", "libquote", "libsyn", @@ -12,7 +12,7 @@ [package] edition = "2018" name = "argh_derive" -version = "0.1.9" +version = "0.1.10" authors = [ "Taylor Cramer <cramertj@google.com>", "Benjamin Brittain <bwb@google.com>", @@ -27,10 +27,7 @@ repository = "https://github.com/google/argh" proc-macro = true [dependencies.argh_shared] -version = "0.1.9" - -[dependencies.heck] -version = "0.4" +version = "0.1.10" [dependencies.proc-macro2] version = "1.0" diff --git a/Cargo.toml.orig b/Cargo.toml.orig index f6614e9..750b494 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -1,6 +1,6 @@ [package] name = "argh_derive" -version = "0.1.9" +version = "0.1.10" authors = ["Taylor Cramer <cramertj@google.com>", "Benjamin Brittain <bwb@google.com>", "Erick Tryzelaar <etryzelaar@google.com>"] edition = "2018" license = "BSD-3-Clause" @@ -12,8 +12,7 @@ readme = "README.md" proc-macro = true [dependencies] -heck = "0.4" proc-macro2 = "1.0" quote = "1.0" syn = "1.0" -argh_shared = { version = "0.1.9", path = "../argh_shared" } +argh_shared = { version = "0.1.10", path = "../argh_shared" } @@ -1,3 +1,7 @@ +# This project was upgraded with external_updater. +# Usage: tools/external_updater/updater.sh update rust/crates/argh_derive +# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md + name: "argh_derive" description: "Derive-based argument parsing optimized for code size" third_party { @@ -7,13 +11,13 @@ third_party { } url { type: ARCHIVE - value: "https://static.crates.io/crates/argh_derive/argh_derive-0.1.9.crate" + value: "https://static.crates.io/crates/argh_derive/argh_derive-0.1.10.crate" } - version: "0.1.9" + version: "0.1.10" license_type: NOTICE last_upgrade_date { - year: 2022 - month: 12 - day: 5 + year: 2023 + month: 2 + day: 1 } } diff --git a/src/help.rs b/src/help.rs index c9daad5..bd20622 100644 --- a/src/help.rs +++ b/src/help.rs @@ -27,11 +27,11 @@ pub(crate) fn help( fields: &[StructField<'_>], subcommand: Option<&StructField<'_>>, ) -> TokenStream { - #![allow(clippy::format_push_string)] let mut format_lit = "Usage: {command_name}".to_string(); - let positional = - fields.iter().filter(|f| f.kind == FieldKind::Positional && f.attrs.greedy.is_none()); + let positional = fields.iter().filter(|f| { + f.kind == FieldKind::Positional && f.attrs.greedy.is_none() && !f.attrs.hidden_help + }); let mut has_positional = false; for arg in positional.clone() { has_positional = true; @@ -39,14 +39,15 @@ pub(crate) fn help( positional_usage(&mut format_lit, arg); } - let options = fields.iter().filter(|f| f.long_name.is_some()); + let options = fields.iter().filter(|f| f.long_name.is_some() && !f.attrs.hidden_help); for option in options.clone() { format_lit.push(' '); option_usage(&mut format_lit, option); } - let remain = - fields.iter().filter(|f| f.kind == FieldKind::Positional && f.attrs.greedy.is_some()); + let remain = fields.iter().filter(|f| { + f.kind == FieldKind::Positional && f.attrs.greedy.is_some() && !f.attrs.hidden_help + }); for arg in remain { format_lit.push(' '); positional_usage(&mut format_lit, arg); @@ -154,7 +155,7 @@ fn positional_usage(out: &mut String, field: &StructField<'_>) { if field.attrs.greedy.is_none() { out.push('<'); } - let name = field.arg_name(); + let name = field.positional_arg_name(); out.push_str(&name); if field.optionality == Optionality::Repeating { out.push_str("..."); @@ -229,7 +230,7 @@ Add a doc comment or an `#[argh(description = \"...\")]` attribute.", /// Describes a positional argument like this: /// hello positional argument description fn positional_description(out: &mut String, field: &StructField<'_>) { - let field_name = field.arg_name(); + let field_name = field.positional_arg_name(); let mut description = String::from(""); if let Some(desc) = &field.attrs.description { @@ -11,13 +11,12 @@ extern crate proc_macro; use { crate::{ errors::Errors, - parse_attrs::{FieldAttrs, FieldKind, TypeAttrs}, + parse_attrs::{check_long_name, FieldAttrs, FieldKind, TypeAttrs}, }, - heck::ToKebabCase, proc_macro2::{Span, TokenStream}, quote::{quote, quote_spanned, ToTokens}, std::{collections::HashMap, str::FromStr}, - syn::{spanned::Spanned, LitStr}, + syn::{spanned::Spanned, GenericArgument, LitStr, PathArguments, Type}, }; mod errors; @@ -179,11 +178,11 @@ impl<'a> StructField<'a> { // Defaults to the kebab-case'd field name if `#[argh(long = "...")]` is omitted. let long_name = match kind { FieldKind::Switch | FieldKind::Option => { - let long_name = attrs - .long - .as_ref() - .map(syn::LitStr::value) - .unwrap_or_else(|| name.to_string().to_kebab_case()); + let long_name = attrs.long.as_ref().map(syn::LitStr::value).unwrap_or_else(|| { + let kebab_name = to_kebab_case(&name.to_string()); + check_long_name(errors, name, &kebab_name); + kebab_name + }); if long_name == "help" { errors.err(field, "Custom `--help` flags are not supported."); } @@ -196,11 +195,43 @@ impl<'a> StructField<'a> { Some(StructField { field, attrs, kind, optionality, ty_without_wrapper, name, long_name }) } - pub(crate) fn arg_name(&self) -> String { - self.attrs.arg_name.as_ref().map(LitStr::value).unwrap_or_else(|| self.name.to_string()) + pub(crate) fn positional_arg_name(&self) -> String { + self.attrs + .arg_name + .as_ref() + .map(LitStr::value) + .unwrap_or_else(|| self.name.to_string().trim_matches('_').to_owned()) } } +fn to_kebab_case(s: &str) -> String { + let words = s.split('_').filter(|word| !word.is_empty()); + let mut res = String::with_capacity(s.len()); + for word in words { + if !res.is_empty() { + res.push('-') + } + res.push_str(word) + } + res +} + +#[test] +fn test_kebabs() { + #[track_caller] + fn check(s: &str, want: &str) { + let got = to_kebab_case(s); + assert_eq!(got.as_str(), want) + } + check("", ""); + check("_", ""); + check("foo", "foo"); + check("__foo_", "foo"); + check("foo_bar", "foo-bar"); + check("foo__Bar", "foo-Bar"); + check("foo_bar__baz_", "foo-bar-baz"); +} + /// Implements `FromArgs` and `TopLevelCommand` or `SubCommand` for a `#[derive(FromArgs)]` struct. fn impl_from_args_struct( errors: &Errors, @@ -530,7 +561,7 @@ fn ensure_unique_names(errors: &Errors, fields: &[StructField<'_>]) { first_use_field, &format!("The short name of \"-{}\" was already used here.", short_name), ); - errors.err_span_tokens(&field.field, "Later usage here."); + errors.err_span_tokens(field.field, "Later usage here."); } seen_short_names.insert(short_name, &field.field); @@ -542,7 +573,7 @@ fn ensure_unique_names(errors: &Errors, fields: &[StructField<'_>]) { *first_use_field, &format!("The long name of \"{}\" was already used here.", long_name), ); - errors.err_span_tokens(&field.field, "Later usage here."); + errors.err_span_tokens(field.field, "Later usage here."); } seen_long_names.insert(long_name, field.field); @@ -710,7 +741,7 @@ fn declare_local_storage_for_redacted_fields<'a>( } }; - let arg_name = field.arg_name(); + let arg_name = field.positional_arg_name(); quote! { let mut #field_name: argh::ParseValueSlotTy::<#field_slot_type, String> = argh::ParseValueSlotTy { @@ -802,7 +833,7 @@ fn append_missing_requirements<'a>( match field.kind { FieldKind::Switch => unreachable!("switches are always optional"), FieldKind::Positional => { - let name = field.arg_name(); + let name = field.positional_arg_name(); quote! { if #field_name.slot.is_none() { #mri.missing_positional_arg(#name) @@ -850,6 +881,16 @@ fn ty_expect_switch(errors: &Errors, ty: &syn::Type) -> bool { return false; } let ident = &path.path.segments[0].ident; + // `Option<bool>` can be used as a `switch`. + if ident == "Option" { + if let PathArguments::AngleBracketed(args) = &path.path.segments[0].arguments { + if let GenericArgument::Type(Type::Path(p)) = &args.args[0] { + if p.path.segments[0].ident == "bool" { + return true; + } + } + } + } ["bool", "u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128"] .iter() .any(|path| ident == path) @@ -860,7 +901,7 @@ fn ty_expect_switch(errors: &Errors, ty: &syn::Type) -> bool { let res = ty_can_be_switch(ty); if !res { - errors.err(ty, "switches must be of type `bool` or integer type"); + errors.err(ty, "switches must be of type `bool`, `Option<bool>`, or integer type"); } res } diff --git a/src/parse_attrs.rs b/src/parse_attrs.rs index f5fa841..04dcbdd 100644 --- a/src/parse_attrs.rs +++ b/src/parse_attrs.rs @@ -19,6 +19,7 @@ pub struct FieldAttrs { pub short: Option<syn::LitChar>, pub arg_name: Option<syn::LitStr>, pub greedy: Option<syn::Path>, + pub hidden_help: bool, } /// The purpose of a particular field on a `#![derive(FromArgs)]` struct. @@ -123,13 +124,15 @@ impl FieldAttrs { ); } else if name.is_ident("greedy") { this.greedy = Some(name.clone()); + } else if name.is_ident("hidden_help") { + this.hidden_help = true; } else { errors.err( &meta, concat!( "Invalid field-level `argh` attribute\n", "Expected one of: `arg_name`, `default`, `description`, `from_str_fn`, `greedy`, ", - "`long`, `option`, `short`, `subcommand`, `switch`", + "`long`, `option`, `short`, `subcommand`, `switch`, `hidden_help`", ), ); } @@ -180,12 +183,7 @@ impl FieldAttrs { parse_attr_single_string(errors, m, "long", &mut self.long); let long = self.long.as_ref().unwrap(); let value = long.value(); - if !value.is_ascii() { - errors.err(long, "Long names must be ASCII"); - } - if !value.chars().all(|c| c.is_lowercase() || c == '-' || c.is_ascii_digit()) { - errors.err(long, "Long names must be lowercase"); - } + check_long_name(errors, long, &value); } fn parse_attr_short(&mut self, errors: &Errors, m: &syn::MetaNameValue) { @@ -200,6 +198,15 @@ impl FieldAttrs { } } +pub(crate) fn check_long_name(errors: &Errors, spanned: &impl syn::spanned::Spanned, value: &str) { + if !value.is_ascii() { + errors.err(spanned, "Long names must be ASCII"); + } + if !value.chars().all(|c| c.is_lowercase() || c == '-' || c.is_ascii_digit()) { + errors.err(spanned, "Long names must be lowercase"); + } +} + fn parse_attr_fn_name( errors: &Errors, m: &syn::MetaList, |