diff options
-rw-r--r-- | .cargo_vcs_info.json | 2 | ||||
-rw-r--r-- | Android.bp | 2 | ||||
-rw-r--r-- | Cargo.toml | 6 | ||||
-rw-r--r-- | Cargo.toml.orig | 6 | ||||
-rw-r--r-- | METADATA | 10 | ||||
-rw-r--r-- | patches/syn-2.patch | 383 | ||||
-rw-r--r-- | src/args_info.rs | 356 | ||||
-rw-r--r-- | src/lib.rs | 9 | ||||
-rw-r--r-- | src/parse_attrs.rs | 4 |
9 files changed, 380 insertions, 398 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json index 9595563..e06a0db 100644 --- a/.cargo_vcs_info.json +++ b/.cargo_vcs_info.json @@ -1,6 +1,6 @@ { "git": { - "sha1": "3f3c29726a21c4b541bb2b9aa2c592461897ded0" + "sha1": "e8efc8285f632a4ebedfe4377e0f1d78276f8e19" }, "path_in_vcs": "argh_derive" }
\ No newline at end of file @@ -22,7 +22,7 @@ rust_proc_macro { name: "libargh_derive", crate_name: "argh_derive", cargo_env_compat: true, - cargo_pkg_version: "0.1.10", + cargo_pkg_version: "0.1.12", srcs: ["src/lib.rs"], edition: "2018", rustlibs: [ @@ -12,7 +12,7 @@ [package] edition = "2018" name = "argh_derive" -version = "0.1.10" +version = "0.1.12" authors = [ "Taylor Cramer <cramertj@google.com>", "Benjamin Brittain <bwb@google.com>", @@ -27,7 +27,7 @@ repository = "https://github.com/google/argh" proc-macro = true [dependencies.argh_shared] -version = "0.1.10" +version = "0.1.12" [dependencies.proc-macro2] version = "1.0" @@ -36,4 +36,4 @@ version = "1.0" version = "1.0" [dependencies.syn] -version = "2.0.1" +version = "2.0" diff --git a/Cargo.toml.orig b/Cargo.toml.orig index 750b494..dcc965f 100644 --- a/Cargo.toml.orig +++ b/Cargo.toml.orig @@ -1,6 +1,6 @@ [package] name = "argh_derive" -version = "0.1.10" +version = "0.1.12" authors = ["Taylor Cramer <cramertj@google.com>", "Benjamin Brittain <bwb@google.com>", "Erick Tryzelaar <etryzelaar@google.com>"] edition = "2018" license = "BSD-3-Clause" @@ -14,5 +14,5 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" -syn = "1.0" -argh_shared = { version = "0.1.10", path = "../argh_shared" } +syn = "2.0" +argh_shared = { version = "0.1.12", path = "../argh_shared" } @@ -1,6 +1,6 @@ # 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 +# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md name: "argh_derive" description: "Derive-based argument parsing optimized for code size" @@ -11,13 +11,13 @@ third_party { } url { type: ARCHIVE - value: "https://static.crates.io/crates/argh_derive/argh_derive-0.1.10.crate" + value: "https://static.crates.io/crates/argh_derive/argh_derive-0.1.12.crate" } - version: "0.1.10" + version: "0.1.12" license_type: NOTICE last_upgrade_date { year: 2023 - month: 2 - day: 1 + month: 10 + day: 11 } } diff --git a/patches/syn-2.patch b/patches/syn-2.patch deleted file mode 100644 index ffe0488..0000000 --- a/patches/syn-2.patch +++ /dev/null @@ -1,383 +0,0 @@ -diff --git a/src/errors.rs b/src/errors.rs -index cfad383..fc5b584 100644 ---- a/src/errors.rs -+++ b/src/errors.rs -@@ -14,15 +14,15 @@ pub struct Errors { - errors: RefCell<Vec<syn::Error>>, - } - --/// Produce functions to expect particular variants of `syn::Lit` -+/// Produce functions to expect particular literals in `syn::Expr` - macro_rules! expect_lit_fn { - ($(($fn_name:ident, $syn_type:ident, $variant:ident, $lit_name:literal),)*) => { - $( -- pub fn $fn_name<'a>(&self, lit: &'a syn::Lit) -> Option<&'a syn::$syn_type> { -- if let syn::Lit::$variant(inner) = lit { -+ pub fn $fn_name<'a>(&self, e: &'a syn::Expr) -> Option<&'a syn::$syn_type> { -+ if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::$variant(inner), .. }) = e { - Some(inner) - } else { -- self.unexpected_lit($lit_name, lit); -+ self.unexpected_lit($lit_name, e); - None - } - } -@@ -65,28 +65,6 @@ impl Errors { - self.err_span(first, &["First ", attr_kind, " attribute here"].concat()); - } - -- /// Error on literals, expecting attribute syntax. -- pub fn expect_nested_meta<'a>(&self, nm: &'a syn::NestedMeta) -> Option<&'a syn::Meta> { -- match nm { -- syn::NestedMeta::Lit(l) => { -- self.err(l, "Unexpected literal"); -- None -- } -- syn::NestedMeta::Meta(m) => Some(m), -- } -- } -- -- /// Error on attribute syntax, expecting literals -- pub fn expect_nested_lit<'a>(&self, nm: &'a syn::NestedMeta) -> Option<&'a syn::Lit> { -- match nm { -- syn::NestedMeta::Meta(m) => { -- self.err(m, "Expected literal"); -- None -- } -- syn::NestedMeta::Lit(l) => Some(l), -- } -- } -- - expect_lit_fn![ - (expect_lit_str, LitStr, Str, "string"), - (expect_lit_char, LitChar, Char, "character"), -@@ -99,7 +77,7 @@ impl Errors { - (expect_meta_name_value, MetaNameValue, NameValue, "name-value pair"), - ]; - -- fn unexpected_lit(&self, expected: &str, found: &syn::Lit) { -+ fn unexpected_lit(&self, expected: &str, found: &syn::Expr) { - fn lit_kind(lit: &syn::Lit) -> &'static str { - use syn::Lit::{Bool, Byte, ByteStr, Char, Float, Int, Str, Verbatim}; - match lit { -@@ -111,13 +89,21 @@ impl Errors { - Float(_) => "float", - Bool(_) => "boolean", - Verbatim(_) => "unknown (possibly extra-large integer)", -+ _ => "unknown literal kind", - } - } - -- self.err( -- found, -- &["Expected ", expected, " literal, found ", lit_kind(found), " literal"].concat(), -- ) -+ if let syn::Expr::Lit(syn::ExprLit { lit, .. }) = found { -+ self.err( -+ found, -+ &["Expected ", expected, " literal, found ", lit_kind(lit), " literal"].concat(), -+ ) -+ } else { -+ self.err( -+ found, -+ &["Expected ", expected, " literal, found non-literal expression."].concat(), -+ ) -+ } - } - - fn unexpected_meta(&self, expected: &str, found: &syn::Meta) { -@@ -155,6 +141,17 @@ impl Errors { - pub fn push(&self, err: syn::Error) { - self.errors.borrow_mut().push(err); - } -+ -+ /// Convert a `syn::Result` to an `Option`, logging the error if present. -+ pub fn ok<T>(&self, r: syn::Result<T>) -> Option<T> { -+ match r { -+ Ok(v) => Some(v), -+ Err(e) => { -+ self.push(e); -+ None -+ } -+ } -+ } - } - - impl ToTokens for Errors { -diff --git a/src/parse_attrs.rs b/src/parse_attrs.rs -index 04dcbdd..10ebcb5 100644 ---- a/src/parse_attrs.rs -+++ b/src/parse_attrs.rs -@@ -76,49 +76,47 @@ impl FieldAttrs { - continue; - }; - -- for meta in &ml.nested { -- let meta = if let Some(m) = errors.expect_nested_meta(meta) { m } else { continue }; -- -+ for meta in ml { - let name = meta.path(); - if name.is_ident("arg_name") { -- if let Some(m) = errors.expect_meta_name_value(meta) { -+ if let Some(m) = errors.expect_meta_name_value(&meta) { - this.parse_attr_arg_name(errors, m); - } - } else if name.is_ident("default") { -- if let Some(m) = errors.expect_meta_name_value(meta) { -+ if let Some(m) = errors.expect_meta_name_value(&meta) { - this.parse_attr_default(errors, m); - } - } else if name.is_ident("description") { -- if let Some(m) = errors.expect_meta_name_value(meta) { -+ if let Some(m) = errors.expect_meta_name_value(&meta) { - parse_attr_description(errors, m, &mut this.description); - } - } else if name.is_ident("from_str_fn") { -- if let Some(m) = errors.expect_meta_list(meta) { -+ if let Some(m) = errors.expect_meta_list(&meta) { - this.parse_attr_from_str_fn(errors, m); - } - } else if name.is_ident("long") { -- if let Some(m) = errors.expect_meta_name_value(meta) { -+ if let Some(m) = errors.expect_meta_name_value(&meta) { - this.parse_attr_long(errors, m); - } - } else if name.is_ident("option") { -- parse_attr_field_type(errors, meta, FieldKind::Option, &mut this.field_type); -+ parse_attr_field_type(errors, &meta, FieldKind::Option, &mut this.field_type); - } else if name.is_ident("short") { -- if let Some(m) = errors.expect_meta_name_value(meta) { -+ if let Some(m) = errors.expect_meta_name_value(&meta) { - this.parse_attr_short(errors, m); - } - } else if name.is_ident("subcommand") { - parse_attr_field_type( - errors, -- meta, -+ &meta, - FieldKind::SubCommand, - &mut this.field_type, - ); - } else if name.is_ident("switch") { -- parse_attr_field_type(errors, meta, FieldKind::Switch, &mut this.field_type); -+ parse_attr_field_type(errors, &meta, FieldKind::Switch, &mut this.field_type); - } else if name.is_ident("positional") { - parse_attr_field_type( - errors, -- meta, -+ &meta, - FieldKind::Positional, - &mut this.field_type, - ); -@@ -189,7 +187,7 @@ impl FieldAttrs { - fn parse_attr_short(&mut self, errors: &Errors, m: &syn::MetaNameValue) { - if let Some(first) = &self.short { - errors.duplicate_attrs("short", first, m); -- } else if let Some(lit_char) = errors.expect_lit_char(&m.lit) { -+ } else if let Some(lit_char) = errors.expect_lit_char(&m.value) { - self.short = Some(lit_char.clone()); - if !lit_char.value().is_ascii() { - errors.err(lit_char, "Short names must be ASCII"); -@@ -217,16 +215,7 @@ fn parse_attr_fn_name( - errors.duplicate_attrs(attr_name, first, m); - } - -- if m.nested.len() != 1 { -- errors.err(&m.nested, "Expected a single argument containing the function name"); -- return; -- } -- -- // `unwrap` will not fail because of the call above -- let nested = m.nested.first().unwrap(); -- if let Some(path) = errors.expect_nested_meta(nested).and_then(|m| errors.expect_meta_word(m)) { -- *slot = path.get_ident().cloned(); -- } -+ *slot = errors.ok(m.parse_args()); - } - - fn parse_attr_field_type( -@@ -246,7 +235,7 @@ fn parse_attr_field_type( - - // Whether the attribute is one like `#[<name> ...]` - fn is_matching_attr(name: &str, attr: &syn::Attribute) -> bool { -- attr.path.segments.len() == 1 && attr.path.segments[0].ident == name -+ attr.path().segments.len() == 1 && attr.path().segments[0].ident == name - } - - /// Checks for `#[doc ...]`, which is generated by doc comments. -@@ -259,34 +248,18 @@ fn is_argh_attr(attr: &syn::Attribute) -> bool { - is_matching_attr("argh", attr) - } - --fn attr_to_meta_subtype<R: Clone>( -+/// Filters out non-`#[argh(...)]` attributes and converts to a sequence of `syn::Meta`. -+fn argh_attr_to_meta_list( - errors: &Errors, - attr: &syn::Attribute, -- f: impl FnOnce(&syn::Meta) -> Option<&R>, --) -> Option<R> { -- match attr.parse_meta() { -- Ok(meta) => f(&meta).cloned(), -- Err(e) => { -- errors.push(e); -- None -- } -- } --} -- --fn attr_to_meta_list(errors: &Errors, attr: &syn::Attribute) -> Option<syn::MetaList> { -- attr_to_meta_subtype(errors, attr, |m| errors.expect_meta_list(m)) --} -- --fn attr_to_meta_name_value(errors: &Errors, attr: &syn::Attribute) -> Option<syn::MetaNameValue> { -- attr_to_meta_subtype(errors, attr, |m| errors.expect_meta_name_value(m)) --} -- --/// Filters out non-`#[argh(...)]` attributes and converts to `syn::MetaList`. --fn argh_attr_to_meta_list(errors: &Errors, attr: &syn::Attribute) -> Option<syn::MetaList> { -+) -> Option<impl IntoIterator<Item = syn::Meta>> { - if !is_argh_attr(attr) { - return None; - } -- attr_to_meta_list(errors, attr) -+ let ml = errors.expect_meta_list(&attr.meta)?; -+ errors.ok(ml.parse_args_with( -+ syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated, -+ )) - } - - /// Represents a `#[derive(FromArgs)]` type's top-level attributes. -@@ -317,32 +290,31 @@ impl TypeAttrs { - continue; - }; - -- for meta in &ml.nested { -- let meta = if let Some(m) = errors.expect_nested_meta(meta) { m } else { continue }; -- -+ for meta in ml { - let name = meta.path(); - if name.is_ident("description") { -- if let Some(m) = errors.expect_meta_name_value(meta) { -+ if let Some(m) = errors.expect_meta_name_value(&meta) { - parse_attr_description(errors, m, &mut this.description); - } - } else if name.is_ident("error_code") { -- if let Some(m) = errors.expect_meta_list(meta) { -+ if let Some(m) = errors.expect_meta_list(&meta) { - this.parse_attr_error_code(errors, m); - } - } else if name.is_ident("example") { -- if let Some(m) = errors.expect_meta_name_value(meta) { -+ if let Some(m) = errors.expect_meta_name_value(&meta) { - this.parse_attr_example(errors, m); - } - } else if name.is_ident("name") { -- if let Some(m) = errors.expect_meta_name_value(meta) { -+ if let Some(m) = errors.expect_meta_name_value(&meta) { - this.parse_attr_name(errors, m); - } - } else if name.is_ident("note") { -- if let Some(m) = errors.expect_meta_name_value(meta) { -+ if let Some(m) = errors.expect_meta_name_value(&meta) { - this.parse_attr_note(errors, m); - } - } else if name.is_ident("subcommand") { -- if let Some(ident) = errors.expect_meta_word(meta).and_then(|p| p.get_ident()) { -+ if let Some(ident) = errors.expect_meta_word(&meta).and_then(|p| p.get_ident()) -+ { - this.parse_attr_subcommand(errors, ident); - } - } else { -@@ -396,20 +368,17 @@ impl TypeAttrs { - } - - fn parse_attr_error_code(&mut self, errors: &Errors, ml: &syn::MetaList) { -- if ml.nested.len() != 2 { -- errors.err(&ml, "Expected two arguments, an error number and a string description"); -- return; -- } -- -- let err_code = &ml.nested[0]; -- let err_msg = &ml.nested[1]; -- -- let err_code = errors.expect_nested_lit(err_code).and_then(|l| errors.expect_lit_int(l)); -- let err_msg = errors.expect_nested_lit(err_msg).and_then(|l| errors.expect_lit_str(l)); -- -- if let (Some(err_code), Some(err_msg)) = (err_code, err_msg) { -- self.error_codes.push((err_code.clone(), err_msg.clone())); -- } -+ errors.ok(ml.parse_args_with(|input: syn::parse::ParseStream| { -+ let err_code = input.parse()?; -+ input.parse::<syn::Token![,]>()?; -+ let err_msg = input.parse()?; -+ if let (Some(err_code), Some(err_msg)) = -+ (errors.expect_lit_int(&err_code), errors.expect_lit_str(&err_msg)) -+ { -+ self.error_codes.push((err_code.clone(), err_msg.clone())); -+ } -+ Ok(()) -+ })); - } - - fn parse_attr_example(&mut self, errors: &Errors, m: &syn::MetaNameValue) { -@@ -470,15 +439,13 @@ impl VariantAttrs { - continue; - }; - -- for meta in &ml.nested { -- let meta = if let Some(m) = errors.expect_nested_meta(meta) { m } else { continue }; -- -+ for meta in ml { - let name = meta.path(); - if name.is_ident("dynamic") { - if let Some(prev) = this.is_dynamic.as_ref() { -- errors.duplicate_attrs("dynamic", prev, meta); -+ errors.duplicate_attrs("dynamic", prev, &meta); - } else { -- this.is_dynamic = errors.expect_meta_word(meta).cloned(); -+ this.is_dynamic = errors.expect_meta_word(&meta).cloned(); - } - } else { - errors.err( -@@ -515,19 +482,19 @@ fn parse_attr_single_string( - ) { - if let Some(first) = slot { - errors.duplicate_attrs(name, first, m); -- } else if let Some(lit_str) = errors.expect_lit_str(&m.lit) { -+ } else if let Some(lit_str) = errors.expect_lit_str(&m.value) { - *slot = Some(lit_str.clone()); - } - } - - fn parse_attr_multi_string(errors: &Errors, m: &syn::MetaNameValue, list: &mut Vec<syn::LitStr>) { -- if let Some(lit_str) = errors.expect_lit_str(&m.lit) { -+ if let Some(lit_str) = errors.expect_lit_str(&m.value) { - list.push(lit_str.clone()); - } - } - - fn parse_attr_doc(errors: &Errors, attr: &syn::Attribute, slot: &mut Option<Description>) { -- let nv = if let Some(nv) = attr_to_meta_name_value(errors, attr) { -+ let nv = if let Some(nv) = errors.expect_meta_name_value(&attr.meta) { - nv - } else { - return; -@@ -538,7 +505,7 @@ fn parse_attr_doc(errors: &Errors, attr: &syn::Attribute, slot: &mut Option<Desc - return; - } - -- if let Some(lit_str) = errors.expect_lit_str(&nv.lit) { -+ if let Some(lit_str) = errors.expect_lit_str(&nv.value) { - let lit_str = if let Some(previous) = slot { - let previous = &previous.content; - let previous_span = previous.span(); -@@ -551,7 +518,8 @@ fn parse_attr_doc(errors: &Errors, attr: &syn::Attribute, slot: &mut Option<Desc - } - - fn parse_attr_description(errors: &Errors, m: &syn::MetaNameValue, slot: &mut Option<Description>) { -- let lit_str = if let Some(lit_str) = errors.expect_lit_str(&m.lit) { lit_str } else { return }; -+ let lit_str = -+ if let Some(lit_str) = errors.expect_lit_str(&m.value) { lit_str } else { return }; - - // Don't allow multiple explicit (non doc-comment) descriptions - if let Some(description) = slot { diff --git a/src/args_info.rs b/src/args_info.rs new file mode 100644 index 0000000..ae640a7 --- /dev/null +++ b/src/args_info.rs @@ -0,0 +1,356 @@ +// Copyright (c) 2023 Google LLC All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +use crate::{ + enum_only_single_field_unnamed_variants, + errors::Errors, + help::require_description, + parse_attrs::{check_enum_type_attrs, FieldAttrs, FieldKind, TypeAttrs, VariantAttrs}, + Optionality, StructField, +}; +use proc_macro2::{Span, TokenStream}; +use quote::{quote, quote_spanned, ToTokens}; +use syn::LitStr; + +/// Implement the derive macro for ArgsInfo. +pub(crate) fn impl_args_info(input: &syn::DeriveInput) -> TokenStream { + let errors = &Errors::default(); + + // parse the types + let type_attrs = &TypeAttrs::parse(errors, input); + + // Based on the type generate the appropriate code. + let mut output_tokens = match &input.data { + syn::Data::Struct(ds) => { + impl_arg_info_struct(errors, &input.ident, type_attrs, &input.generics, ds) + } + syn::Data::Enum(de) => { + impl_arg_info_enum(errors, &input.ident, type_attrs, &input.generics, de) + } + syn::Data::Union(_) => { + errors.err(input, "`#[derive(ArgsInfo)]` cannot be applied to unions"); + TokenStream::new() + } + }; + errors.to_tokens(&mut output_tokens); + output_tokens +} + +/// Implement the ArgsInfo trait for a struct annotated with argh attributes. +fn impl_arg_info_struct( + errors: &Errors, + name: &syn::Ident, + type_attrs: &TypeAttrs, + generic_args: &syn::Generics, + ds: &syn::DataStruct, +) -> TokenStream { + // Collect the fields, skipping fields that are not supported. + let fields = match &ds.fields { + syn::Fields::Named(fields) => fields, + syn::Fields::Unnamed(_) => { + errors.err( + &ds.struct_token, + "`#![derive(ArgsInfo)]` is not currently supported on tuple structs", + ); + return TokenStream::new(); + } + syn::Fields::Unit => { + errors.err(&ds.struct_token, "#![derive(ArgsInfo)]` cannot be applied to unit structs"); + return TokenStream::new(); + } + }; + + // Map the fields into StructField objects. + let fields: Vec<_> = fields + .named + .iter() + .filter_map(|field| { + let attrs = FieldAttrs::parse(errors, field); + StructField::new(errors, field, attrs) + }) + .collect(); + + let impl_span = Span::call_site(); + + // Generate the implementation of `get_args_info()` for this struct. + let args_info = impl_args_info_data(name, errors, type_attrs, &fields); + + // Split out the generics info for the impl declaration. + let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl(); + + quote_spanned! { impl_span => + #[automatically_derived] + impl #impl_generics argh::ArgsInfo for #name #ty_generics #where_clause { + fn get_args_info() -> argh::CommandInfoWithArgs { + #args_info + } + } + } +} + +/// Implement ArgsInfo for an enum. The enum is a collection of subcommands. +fn impl_arg_info_enum( + errors: &Errors, + name: &syn::Ident, + type_attrs: &TypeAttrs, + generic_args: &syn::Generics, + de: &syn::DataEnum, +) -> TokenStream { + // Validate the enum is OK for argh. + check_enum_type_attrs(errors, type_attrs, &de.enum_token.span); + + // Ensure that `#[argh(subcommand)]` is present. + if type_attrs.is_subcommand.is_none() { + errors.err_span( + de.enum_token.span, + concat!( + "`#![derive(ArgsInfo)]` on `enum`s can only be used to enumerate subcommands.\n", + "Consider adding `#[argh(subcommand)]` to the `enum` declaration.", + ), + ); + } + + // One of the variants can be annotated as providing dynamic subcommands. + // We treat this differently since we need to call a function at runtime + // to determine the subcommands provided. + let mut dynamic_type_and_variant = None; + + // An enum variant like `<name>(<ty>)`. This is used to collect + // the type of the variant for each subcommand. + struct ArgInfoVariant<'a> { + ty: &'a syn::Type, + } + + let variants: Vec<ArgInfoVariant<'_>> = de + .variants + .iter() + .filter_map(|variant| { + let name = &variant.ident; + let ty = enum_only_single_field_unnamed_variants(errors, &variant.fields)?; + if VariantAttrs::parse(errors, variant).is_dynamic.is_some() { + if dynamic_type_and_variant.is_some() { + errors.err(variant, "Only one variant can have the `dynamic` attribute"); + } + dynamic_type_and_variant = Some((ty, name)); + None + } else { + Some(ArgInfoVariant { ty }) + } + }) + .collect(); + + let dynamic_subcommands = if let Some((dynamic_type, _)) = dynamic_type_and_variant { + quote! { + <#dynamic_type as argh::DynamicSubCommand>::commands().iter() + .map(|s| + SubCommandInfo { + name: s.name, + command: CommandInfoWithArgs { + name: s.name, + description: s.description, + ..Default::default() + } + }).collect() + } + } else { + quote! { vec![]} + }; + + let variant_ty_info = variants.iter().map(|t| { + let ty = t.ty; + quote!( + argh::SubCommandInfo { + name: #ty::get_args_info().name, + command: #ty::get_args_info() + } + ) + }); + + let cmd_name = if let Some(id) = &type_attrs.name { + id.clone() + } else { + LitStr::new("", Span::call_site()) + }; + + let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl(); + + quote! { + #[automatically_derived] + impl #impl_generics argh::ArgsInfo for #name #ty_generics #where_clause { + fn get_args_info() -> argh::CommandInfoWithArgs { + + let mut the_subcommands = vec![#(#variant_ty_info),*]; + let mut dynamic_commands = #dynamic_subcommands; + + the_subcommands.append(&mut dynamic_commands); + + + argh::CommandInfoWithArgs { + name: #cmd_name, + /// A short description of the command's functionality. + description: " enum of subcommands", + commands: the_subcommands, + ..Default::default() + } + } // end of get_args_ifo + } // end of impl ArgsInfo + } +} + +fn impl_args_info_data<'a>( + name: &proc_macro2::Ident, + errors: &Errors, + type_attrs: &TypeAttrs, + fields: &'a [StructField<'a>], +) -> TokenStream { + let mut subcommands_iter = + fields.iter().filter(|field| field.kind == FieldKind::SubCommand).fuse(); + + let subcommand: Option<&StructField<'_>> = subcommands_iter.next(); + for dup_subcommand in subcommands_iter { + errors.duplicate_attrs("subcommand", subcommand.unwrap().field, dup_subcommand.field); + } + + let impl_span = Span::call_site(); + + let mut positionals = vec![]; + let mut flags = vec![]; + + // Add the implicit --help flag + flags.push(quote! { + argh::FlagInfo { + short: None, + long: "--help", + description: "display usage information", + optionality: argh::Optionality::Optional, + kind: argh::FlagInfoKind::Switch, + hidden: false + } + }); + + for field in fields { + let optionality = match field.optionality { + Optionality::None => quote! { argh::Optionality::Required }, + Optionality::Defaulted(_) => quote! { argh::Optionality::Optional }, + Optionality::Optional => quote! { argh::Optionality::Optional }, + Optionality::Repeating if field.attrs.greedy.is_some() => { + quote! { argh::Optionality::Greedy } + } + Optionality::Repeating => quote! { argh::Optionality::Repeating }, + }; + + match field.kind { + FieldKind::Positional => { + let name = field.positional_arg_name(); + + let description = if let Some(desc) = &field.attrs.description { + desc.content.value().trim().to_owned() + } else { + String::new() + }; + let hidden = field.attrs.hidden_help; + + positionals.push(quote! { + argh::PositionalInfo { + name: #name, + description: #description, + optionality: #optionality, + hidden: #hidden, + } + }); + } + FieldKind::Switch | FieldKind::Option => { + let short = if let Some(short) = &field.attrs.short { + quote! { Some(#short) } + } else { + quote! { None } + }; + + let long = field.long_name.as_ref().expect("missing long name for option"); + + let description = require_description( + errors, + field.name.span(), + &field.attrs.description, + "field", + ); + + let kind = if field.kind == FieldKind::Switch { + quote! { + argh::FlagInfoKind::Switch + } + } else { + let arg_name = if let Some(arg_name) = &field.attrs.arg_name { + quote! { #arg_name } + } else { + let arg_name = long.trim_start_matches("--"); + quote! { #arg_name } + }; + + quote! { + argh::FlagInfoKind::Option { + arg_name: #arg_name, + } + } + }; + + let hidden = field.attrs.hidden_help; + + flags.push(quote! { + argh::FlagInfo { + short: #short, + long: #long, + description: #description, + optionality: #optionality, + kind: #kind, + hidden: #hidden, + } + }); + } + FieldKind::SubCommand => {} + } + } + + let empty_str = syn::LitStr::new("", Span::call_site()); + let type_name = LitStr::new(&name.to_string(), Span::call_site()); + let subcommand_name = if type_attrs.is_subcommand.is_some() { + type_attrs.name.as_ref().unwrap_or_else(|| { + errors.err(name, "`#[argh(name = \"...\")]` attribute is required for subcommands"); + &empty_str + }) + } else { + &type_name + }; + + let subcommand = if let Some(subcommand) = subcommand { + let subcommand_ty = subcommand.ty_without_wrapper; + quote! { + #subcommand_ty::get_subcommands() + } + } else { + quote! {vec![]} + }; + + let description = + require_description(errors, Span::call_site(), &type_attrs.description, "type"); + let examples = type_attrs.examples.iter().map(|e| quote! { #e }); + let notes = type_attrs.notes.iter().map(|e| quote! { #e }); + + let error_codes = type_attrs.error_codes.iter().map(|(code, text)| { + quote! { argh::ErrorCodeInfo{code:#code, description: #text} } + }); + + quote_spanned! { impl_span => + argh::CommandInfoWithArgs { + name: #subcommand_name, + description: #description, + examples: &[#( #examples, )*], + notes: &[#( #notes, )*], + positionals: &[#( #positionals, )*], + flags: &[#( #flags, )*], + commands: #subcommand, + error_codes: &[#( #error_codes, )*], + } + } +} @@ -19,6 +19,7 @@ use { syn::{spanned::Spanned, GenericArgument, LitStr, PathArguments, Type}, }; +mod args_info; mod errors; mod help; mod parse_attrs; @@ -31,6 +32,14 @@ pub fn argh_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { gen.into() } +/// Entrypoint for `#[derive(ArgsInfo)]`. +#[proc_macro_derive(ArgsInfo, attributes(argh))] +pub fn args_info_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ast = syn::parse_macro_input!(input as syn::DeriveInput); + let gen = args_info::impl_args_info(&ast); + gen.into() +} + /// Transform the input into a token stream containing any generated implementations, /// as well as all errors that occurred. fn impl_from_args(input: &syn::DeriveInput) -> TokenStream { diff --git a/src/parse_attrs.rs b/src/parse_attrs.rs index 10ebcb5..f968ca7 100644 --- a/src/parse_attrs.rs +++ b/src/parse_attrs.rs @@ -13,7 +13,7 @@ use { pub struct FieldAttrs { pub default: Option<syn::LitStr>, pub description: Option<Description>, - pub from_str_fn: Option<syn::Ident>, + pub from_str_fn: Option<syn::ExprPath>, pub field_type: Option<FieldType>, pub long: Option<syn::LitStr>, pub short: Option<syn::LitChar>, @@ -209,7 +209,7 @@ fn parse_attr_fn_name( errors: &Errors, m: &syn::MetaList, attr_name: &str, - slot: &mut Option<syn::Ident>, + slot: &mut Option<syn::ExprPath>, ) { if let Some(first) = slot { errors.duplicate_attrs(attr_name, first, m); |