argh_derive-0.1.12/.cargo_vcs_info.json0000644000000001510000000000100133710ustar { "git": { "sha1": "e8efc8285f632a4ebedfe4377e0f1d78276f8e19" }, "path_in_vcs": "argh_derive" }argh_derive-0.1.12/Cargo.toml0000644000000020100000000000100113630ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "argh_derive" version = "0.1.12" authors = [ "Taylor Cramer ", "Benjamin Brittain ", "Erick Tryzelaar ", ] description = "Derive-based argument parsing optimized for code size" readme = "README.md" license = "BSD-3-Clause" repository = "https://github.com/google/argh" [lib] proc-macro = true [dependencies.argh_shared] version = "0.1.12" [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0" [dependencies.syn] version = "2.0" argh_derive-0.1.12/Cargo.toml.orig000064400000000000000000000007751046102023000150640ustar 00000000000000[package] name = "argh_derive" version = "0.1.12" authors = ["Taylor Cramer ", "Benjamin Brittain ", "Erick Tryzelaar "] edition = "2018" license = "BSD-3-Clause" description = "Derive-based argument parsing optimized for code size" repository = "https://github.com/google/argh" readme = "README.md" [lib] proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" syn = "2.0" argh_shared = { version = "0.1.12", path = "../argh_shared" } argh_derive-0.1.12/LICENSE000064400000000000000000000027101046102023000131710ustar 00000000000000Copyright 2019 The Fuchsia Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. argh_derive-0.1.12/README.md000064400000000000000000000116311046102023000134450ustar 00000000000000# Argh **Argh is an opinionated Derive-based argument parser optimized for code size** [![crates.io](https://img.shields.io/crates/v/argh.svg)](https://crates.io/crates/argh) [![license](https://img.shields.io/badge/license-BSD3.0-blue.svg)](https://github.com/google/argh/LICENSE) [![docs.rs](https://docs.rs/argh/badge.svg)](https://docs.rs/crate/argh/) ![Argh](https://github.com/google/argh/workflows/Argh/badge.svg) Derive-based argument parsing optimized for code size and conformance to the Fuchsia commandline tools specification The public API of this library consists primarily of the `FromArgs` derive and the `from_env` function, which can be used to produce a top-level `FromArgs` type from the current program's commandline arguments. ## Basic Example ```rust,no_run use argh::FromArgs; #[derive(FromArgs)] /// Reach new heights. struct GoUp { /// whether or not to jump #[argh(switch, short = 'j')] jump: bool, /// how high to go #[argh(option)] height: usize, /// an optional nickname for the pilot #[argh(option)] pilot_nickname: Option, } fn main() { let up: GoUp = argh::from_env(); } ``` `./some_bin --help` will then output the following: ``` Usage: cmdname [-j] --height [--pilot-nickname ] Reach new heights. Options: -j, --jump whether or not to jump --height how high to go --pilot-nickname an optional nickname for the pilot --help display usage information ``` The resulting program can then be used in any of these ways: - `./some_bin --height 5` - `./some_bin -j --height 5` - `./some_bin --jump --height 5 --pilot-nickname Wes` Switches, like `jump`, are optional and will be set to true if provided. Options, like `height` and `pilot_nickname`, can be either required, optional, or repeating, depending on whether they are contained in an `Option` or a `Vec`. Default values can be provided using the `#[argh(default = "")]` attribute, and in this case an option is treated as optional. ```rust use argh::FromArgs; fn default_height() -> usize { 5 } #[derive(FromArgs)] /// Reach new heights. struct GoUp { /// an optional nickname for the pilot #[argh(option)] pilot_nickname: Option, /// an optional height #[argh(option, default = "default_height()")] height: usize, /// an optional direction which is "up" by default #[argh(option, default = "String::from(\"only up\")")] direction: String, } fn main() { let up: GoUp = argh::from_env(); } ``` Custom option types can be deserialized so long as they implement the `FromArgValue` trait (automatically implemented for all `FromStr` types). If more customized parsing is required, you can supply a custom `fn(&str) -> Result` using the `from_str_fn` attribute: ```rust use argh::FromArgs; #[derive(FromArgs)] /// Goofy thing. struct FiveStruct { /// always five #[argh(option, from_str_fn(always_five))] five: usize, } fn always_five(_value: &str) -> Result { Ok(5) } ``` Positional arguments can be declared using `#[argh(positional)]`. These arguments will be parsed in order of their declaration in the structure: ```rust use argh::FromArgs; #[derive(FromArgs, PartialEq, Debug)] /// A command with positional arguments. struct WithPositional { #[argh(positional)] first: String, } ``` The last positional argument may include a default, or be wrapped in `Option` or `Vec` to indicate an optional or repeating positional argument. Subcommands are also supported. To use a subcommand, declare a separate `FromArgs` type for each subcommand as well as an enum that cases over each command: ```rust use argh::FromArgs; #[derive(FromArgs, PartialEq, Debug)] /// Top-level command. struct TopLevel { #[argh(subcommand)] nested: MySubCommandEnum, } #[derive(FromArgs, PartialEq, Debug)] #[argh(subcommand)] enum MySubCommandEnum { One(SubCommandOne), Two(SubCommandTwo), } #[derive(FromArgs, PartialEq, Debug)] /// First subcommand. #[argh(subcommand, name = "one")] struct SubCommandOne { #[argh(option)] /// how many x x: usize, } #[derive(FromArgs, PartialEq, Debug)] /// Second subcommand. #[argh(subcommand, name = "two")] struct SubCommandTwo { #[argh(switch)] /// whether to fooey fooey: bool, } ``` NOTE: This is not an officially supported Google product. ## How to debug the expanded derive macro for `argh` The `argh::FromArgs` derive macro can be debugged with the [cargo-expand](https://crates.io/crates/cargo-expand) crate. ### Expand the derive macro in `examples/simple_example.rs` See [argh/examples/simple_example.rs](./argh/examples/simple_example.rs) for the example struct we wish to expand. First, install `cargo-expand` by running `cargo install cargo-expand`. Note this requires the nightly build of Rust. Once installed, run `cargo expand` with in the `argh` package and you can see the expanded code. argh_derive-0.1.12/src/args_info.rs000064400000000000000000000274361046102023000153040ustar 00000000000000// 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 `()`. This is used to collect // the type of the variant for each subcommand. struct ArgInfoVariant<'a> { ty: &'a syn::Type, } let variants: Vec> = 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, )*], } } } argh_derive-0.1.12/src/errors.rs000064400000000000000000000123331046102023000146370ustar 00000000000000// Copyright (c) 2020 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 { proc_macro2::{Span, TokenStream}, quote::ToTokens, std::cell::RefCell, }; /// A type for collecting procedural macro errors. #[derive(Default)] pub struct Errors { errors: RefCell>, } /// 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, 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, e); None } } )* } } /// Produce functions to expect particular variants of `syn::Meta` macro_rules! expect_meta_fn { ($(($fn_name:ident, $syn_type:ident, $variant:ident, $meta_name:literal),)*) => { $( pub fn $fn_name<'a>(&self, meta: &'a syn::Meta) -> Option<&'a syn::$syn_type> { if let syn::Meta::$variant(inner) = meta { Some(inner) } else { self.unexpected_meta($meta_name, meta); None } } )* } } impl Errors { /// Issue an error like: /// /// Duplicate foo attribute /// First foo attribute here pub fn duplicate_attrs( &self, attr_kind: &str, first: &impl syn::spanned::Spanned, second: &impl syn::spanned::Spanned, ) { self.duplicate_attrs_inner(attr_kind, first.span(), second.span()) } fn duplicate_attrs_inner(&self, attr_kind: &str, first: Span, second: Span) { self.err_span(second, &["Duplicate ", attr_kind, " attribute"].concat()); self.err_span(first, &["First ", attr_kind, " attribute here"].concat()); } expect_lit_fn![ (expect_lit_str, LitStr, Str, "string"), (expect_lit_char, LitChar, Char, "character"), (expect_lit_int, LitInt, Int, "integer"), ]; expect_meta_fn![ (expect_meta_word, Path, Path, "path"), (expect_meta_list, MetaList, List, "list"), (expect_meta_name_value, MetaNameValue, NameValue, "name-value pair"), ]; 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 { Str(_) => "string", ByteStr(_) => "bytestring", Byte(_) => "byte", Char(_) => "character", Int(_) => "integer", Float(_) => "float", Bool(_) => "boolean", Verbatim(_) => "unknown (possibly extra-large integer)", _ => "unknown literal kind", } } 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) { fn meta_kind(meta: &syn::Meta) -> &'static str { use syn::Meta::{List, NameValue, Path}; match meta { Path(_) => "path", List(_) => "list", NameValue(_) => "name-value pair", } } self.err( found, &["Expected ", expected, " attribute, found ", meta_kind(found), " attribute"].concat(), ) } /// Issue an error relating to a particular `Spanned` structure. pub fn err(&self, spanned: &impl syn::spanned::Spanned, msg: &str) { self.err_span(spanned.span(), msg); } /// Issue an error relating to a particular `Span`. pub fn err_span(&self, span: Span, msg: &str) { self.push(syn::Error::new(span, msg)); } /// Issue an error spanning over the given syntax tree node. pub fn err_span_tokens(&self, tokens: T, msg: &str) { self.push(syn::Error::new_spanned(tokens, msg)); } /// Push a `syn::Error` onto the list of errors to issue. 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(&self, r: syn::Result) -> Option { match r { Ok(v) => Some(v), Err(e) => { self.push(e); None } } } } impl ToTokens for Errors { /// Convert the errors into tokens that, when emit, will cause /// the user of the macro to receive compiler errors. fn to_tokens(&self, tokens: &mut TokenStream) { tokens.extend(self.errors.borrow().iter().map(|e| e.to_compile_error())); } } argh_derive-0.1.12/src/help.rs000064400000000000000000000214571046102023000142620ustar 00000000000000// Copyright (c) 2020 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 std::fmt::Write; use { crate::{ errors::Errors, parse_attrs::{Description, FieldKind, TypeAttrs}, Optionality, StructField, }, argh_shared::INDENT, proc_macro2::{Span, TokenStream}, quote::quote, }; const SECTION_SEPARATOR: &str = "\n\n"; /// Returns a `TokenStream` generating a `String` help message. /// /// Note: `fields` entries with `is_subcommand.is_some()` will be ignored /// in favor of the `subcommand` argument. pub(crate) fn help( errors: &Errors, cmd_name_str_array_ident: syn::Ident, ty_attrs: &TypeAttrs, fields: &[StructField<'_>], subcommand: Option<&StructField<'_>>, ) -> TokenStream { let mut format_lit = "Usage: {command_name}".to_string(); 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; format_lit.push(' '); positional_usage(&mut format_lit, arg); } 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() && !f.attrs.hidden_help }); for arg in remain { format_lit.push(' '); positional_usage(&mut format_lit, arg); } if let Some(subcommand) = subcommand { format_lit.push(' '); if !subcommand.optionality.is_required() { format_lit.push('['); } format_lit.push_str(""); if !subcommand.optionality.is_required() { format_lit.push(']'); } format_lit.push_str(" []"); } format_lit.push_str(SECTION_SEPARATOR); let description = require_description(errors, Span::call_site(), &ty_attrs.description, "type"); format_lit.push_str(&description); if has_positional { format_lit.push_str(SECTION_SEPARATOR); format_lit.push_str("Positional Arguments:"); for arg in positional { positional_description(&mut format_lit, arg); } } format_lit.push_str(SECTION_SEPARATOR); format_lit.push_str("Options:"); for option in options { option_description(errors, &mut format_lit, option); } // Also include "help" option_description_format(&mut format_lit, None, "--help", "display usage information"); let subcommand_calculation; let subcommand_format_arg; if let Some(subcommand) = subcommand { format_lit.push_str(SECTION_SEPARATOR); format_lit.push_str("Commands:{subcommands}"); let subcommand_ty = subcommand.ty_without_wrapper; subcommand_format_arg = quote! { subcommands = subcommands }; subcommand_calculation = quote! { let subcommands = argh::print_subcommands( <#subcommand_ty as argh::SubCommands>::COMMANDS .iter() .copied() .chain( <#subcommand_ty as argh::SubCommands>::dynamic_commands() .iter() .copied()) ); }; } else { subcommand_calculation = TokenStream::new(); subcommand_format_arg = TokenStream::new() } lits_section(&mut format_lit, "Examples:", &ty_attrs.examples); lits_section(&mut format_lit, "Notes:", &ty_attrs.notes); if !ty_attrs.error_codes.is_empty() { format_lit.push_str(SECTION_SEPARATOR); format_lit.push_str("Error codes:"); for (code, text) in &ty_attrs.error_codes { format_lit.push('\n'); format_lit.push_str(INDENT); write!(format_lit, "{} {}", code, text.value()).unwrap(); } } format_lit.push('\n'); quote! { { #subcommand_calculation format!(#format_lit, command_name = #cmd_name_str_array_ident.join(" "), #subcommand_format_arg) } } } /// A section composed of exactly just the literals provided to the program. fn lits_section(out: &mut String, heading: &str, lits: &[syn::LitStr]) { if !lits.is_empty() { out.push_str(SECTION_SEPARATOR); out.push_str(heading); for lit in lits { let value = lit.value(); for line in value.split('\n') { out.push('\n'); out.push_str(INDENT); out.push_str(line); } } } } /// Add positional arguments like `[...]` to a help format string. fn positional_usage(out: &mut String, field: &StructField<'_>) { if !field.optionality.is_required() { out.push('['); } if field.attrs.greedy.is_none() { out.push('<'); } let name = field.positional_arg_name(); out.push_str(&name); if field.optionality == Optionality::Repeating { out.push_str("..."); } if field.attrs.greedy.is_none() { out.push('>'); } if !field.optionality.is_required() { out.push(']'); } } /// Add options like `[-f ]` to a help format string. /// This function must only be called on options (things with `long_name.is_some()`) fn option_usage(out: &mut String, field: &StructField<'_>) { // bookend with `[` and `]` if optional if !field.optionality.is_required() { out.push('['); } let long_name = field.long_name.as_ref().expect("missing long name for option"); if let Some(short) = field.attrs.short.as_ref() { out.push('-'); out.push(short.value()); } else { out.push_str(long_name); } match field.kind { FieldKind::SubCommand | FieldKind::Positional => unreachable!(), // don't have long_name FieldKind::Switch => {} FieldKind::Option => { out.push_str(" <"); if let Some(arg_name) = &field.attrs.arg_name { out.push_str(&arg_name.value()); } else { out.push_str(long_name.trim_start_matches("--")); } if field.optionality == Optionality::Repeating { out.push_str("..."); } out.push('>'); } } if !field.optionality.is_required() { out.push(']'); } } // TODO(cramertj) make it so this is only called at least once per object so // as to avoid creating multiple errors. pub fn require_description( errors: &Errors, err_span: Span, desc: &Option, kind: &str, // the thing being described ("type" or "field"), ) -> String { desc.as_ref().map(|d| d.content.value().trim().to_owned()).unwrap_or_else(|| { errors.err_span( err_span, &format!( "#[derive(FromArgs)] {} with no description. Add a doc comment or an `#[argh(description = \"...\")]` attribute.", kind ), ); "".to_string() }) } /// Describes a positional argument like this: /// hello positional argument description fn positional_description(out: &mut String, field: &StructField<'_>) { let field_name = field.positional_arg_name(); let mut description = String::from(""); if let Some(desc) = &field.attrs.description { description = desc.content.value().trim().to_owned(); } positional_description_format(out, &field_name, &description) } fn positional_description_format(out: &mut String, name: &str, description: &str) { let info = argh_shared::CommandInfo { name, description }; argh_shared::write_description(out, &info); } /// Describes an option like this: /// -f, --force force, ignore minor errors. This description /// is so long that it wraps to the next line. fn option_description(errors: &Errors, out: &mut String, field: &StructField<'_>) { let short = field.attrs.short.as_ref().map(|s| s.value()); let long_with_leading_dashes = field.long_name.as_ref().expect("missing long name for option"); let description = require_description(errors, field.name.span(), &field.attrs.description, "field"); option_description_format(out, short, long_with_leading_dashes, &description) } fn option_description_format( out: &mut String, short: Option, long_with_leading_dashes: &str, description: &str, ) { let mut name = String::new(); if let Some(short) = short { name.push('-'); name.push(short); name.push_str(", "); } name.push_str(long_with_leading_dashes); let info = argh_shared::CommandInfo { name: &name, description }; argh_shared::write_description(out, &info); } argh_derive-0.1.12/src/lib.rs000064400000000000000000001227321046102023000140760ustar 00000000000000#![recursion_limit = "256"] // Copyright (c) 2020 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. /// Implementation of the `FromArgs` and `argh(...)` derive attributes. /// /// For more thorough documentation, see the `argh` crate itself. extern crate proc_macro; use { crate::{ errors::Errors, parse_attrs::{check_long_name, FieldAttrs, FieldKind, TypeAttrs}, }, proc_macro2::{Span, TokenStream}, quote::{quote, quote_spanned, ToTokens}, std::{collections::HashMap, str::FromStr}, syn::{spanned::Spanned, GenericArgument, LitStr, PathArguments, Type}, }; mod args_info; mod errors; mod help; mod parse_attrs; /// Entrypoint for `#[derive(FromArgs)]`. #[proc_macro_derive(FromArgs, attributes(argh))] pub fn argh_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = syn::parse_macro_input!(input as syn::DeriveInput); let gen = impl_from_args(&ast); 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 { let errors = &Errors::default(); let type_attrs = &TypeAttrs::parse(errors, input); let mut output_tokens = match &input.data { syn::Data::Struct(ds) => { impl_from_args_struct(errors, &input.ident, type_attrs, &input.generics, ds) } syn::Data::Enum(de) => { impl_from_args_enum(errors, &input.ident, type_attrs, &input.generics, de) } syn::Data::Union(_) => { errors.err(input, "`#[derive(FromArgs)]` cannot be applied to unions"); TokenStream::new() } }; errors.to_tokens(&mut output_tokens); output_tokens } /// The kind of optionality a parameter has. enum Optionality { None, Defaulted(TokenStream), Optional, Repeating, } impl PartialEq for Optionality { fn eq(&self, other: &Optionality) -> bool { use Optionality::*; // NB: (Defaulted, Defaulted) can't contain the same token streams matches!((self, other), (Optional, Optional) | (Repeating, Repeating)) } } impl Optionality { /// Whether or not this is `Optionality::None` fn is_required(&self) -> bool { matches!(self, Optionality::None) } } /// A field of a `#![derive(FromArgs)]` struct with attributes and some other /// notable metadata appended. struct StructField<'a> { /// The original parsed field field: &'a syn::Field, /// The parsed attributes of the field attrs: FieldAttrs, /// The field name. This is contained optionally inside `field`, /// but is duplicated non-optionally here to indicate that all field that /// have reached this point must have a field name, and it no longer /// needs to be unwrapped. name: &'a syn::Ident, /// Similar to `name` above, this is contained optionally inside `FieldAttrs`, /// but here is fully present to indicate that we only have to consider fields /// with a valid `kind` at this point. kind: FieldKind, // If `field.ty` is `Vec` or `Option`, this is `T`, otherwise it's `&field.ty`. // This is used to enable consistent parsing code between optional and non-optional // keyed and subcommand fields. ty_without_wrapper: &'a syn::Type, // Whether the field represents an optional value, such as an `Option` subcommand field // or an `Option` or `Vec` keyed argument, or if it has a `default`. optionality: Optionality, // The `--`-prefixed name of the option, if one exists. long_name: Option, } impl<'a> StructField<'a> { /// Attempts to parse a field of a `#[derive(FromArgs)]` struct, pulling out the /// fields required for code generation. fn new(errors: &Errors, field: &'a syn::Field, attrs: FieldAttrs) -> Option { let name = field.ident.as_ref().expect("missing ident for named field"); // Ensure that one "kind" is present (switch, option, subcommand, positional) let kind = if let Some(field_type) = &attrs.field_type { field_type.kind } else { errors.err( field, concat!( "Missing `argh` field kind attribute.\n", "Expected one of: `switch`, `option`, `remaining`, `subcommand`, `positional`", ), ); return None; }; // Parse out whether a field is optional (`Option` or `Vec`). let optionality; let ty_without_wrapper; match kind { FieldKind::Switch => { if !ty_expect_switch(errors, &field.ty) { return None; } optionality = Optionality::Optional; ty_without_wrapper = &field.ty; } FieldKind::Option | FieldKind::Positional => { if let Some(default) = &attrs.default { let tokens = match TokenStream::from_str(&default.value()) { Ok(tokens) => tokens, Err(_) => { errors.err(&default, "Invalid tokens: unable to lex `default` value"); return None; } }; // Set the span of the generated tokens to the string literal let tokens: TokenStream = tokens .into_iter() .map(|mut tree| { tree.set_span(default.span()); tree }) .collect(); optionality = Optionality::Defaulted(tokens); ty_without_wrapper = &field.ty; } else { let mut inner = None; optionality = if let Some(x) = ty_inner(&["Option"], &field.ty) { inner = Some(x); Optionality::Optional } else if let Some(x) = ty_inner(&["Vec"], &field.ty) { inner = Some(x); Optionality::Repeating } else { Optionality::None }; ty_without_wrapper = inner.unwrap_or(&field.ty); } } FieldKind::SubCommand => { let inner = ty_inner(&["Option"], &field.ty); optionality = if inner.is_some() { Optionality::Optional } else { Optionality::None }; ty_without_wrapper = inner.unwrap_or(&field.ty); } } // Determine the "long" name of options and switches. // 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(|| { 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."); } let long_name = format!("--{}", long_name); Some(long_name) } FieldKind::SubCommand | FieldKind::Positional => None, }; Some(StructField { field, attrs, kind, optionality, ty_without_wrapper, name, long_name }) } 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, name: &syn::Ident, type_attrs: &TypeAttrs, generic_args: &syn::Generics, ds: &syn::DataStruct, ) -> TokenStream { let fields = match &ds.fields { syn::Fields::Named(fields) => fields, syn::Fields::Unnamed(_) => { errors.err( &ds.struct_token, "`#![derive(FromArgs)]` is not currently supported on tuple structs", ); return TokenStream::new(); } syn::Fields::Unit => { errors.err(&ds.struct_token, "#![derive(FromArgs)]` cannot be applied to unit structs"); return TokenStream::new(); } }; let fields: Vec<_> = fields .named .iter() .filter_map(|field| { let attrs = FieldAttrs::parse(errors, field); StructField::new(errors, field, attrs) }) .collect(); ensure_unique_names(errors, &fields); ensure_only_last_positional_is_optional(errors, &fields); let impl_span = Span::call_site(); let from_args_method = impl_from_args_struct_from_args(errors, type_attrs, &fields); let redact_arg_values_method = impl_from_args_struct_redact_arg_values(errors, type_attrs, &fields); let top_or_sub_cmd_impl = top_or_sub_cmd_impl(errors, name, type_attrs, generic_args); let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl(); let trait_impl = quote_spanned! { impl_span => #[automatically_derived] impl #impl_generics argh::FromArgs for #name #ty_generics #where_clause { #from_args_method #redact_arg_values_method } #top_or_sub_cmd_impl }; trait_impl } fn impl_from_args_struct_from_args<'a>( errors: &Errors, type_attrs: &TypeAttrs, fields: &'a [StructField<'a>], ) -> TokenStream { let init_fields = declare_local_storage_for_from_args_fields(fields); let unwrap_fields = unwrap_from_args_fields(fields); let positional_fields: Vec<&StructField<'_>> = fields.iter().filter(|field| field.kind == FieldKind::Positional).collect(); let positional_field_idents = positional_fields.iter().map(|field| &field.field.ident); let positional_field_names = positional_fields.iter().map(|field| field.name.to_string()); let last_positional_is_repeating = positional_fields .last() .map(|field| field.optionality == Optionality::Repeating) .unwrap_or(false); let last_positional_is_greedy = positional_fields .last() .map(|field| field.kind == FieldKind::Positional && field.attrs.greedy.is_some()) .unwrap_or(false); let flag_output_table = fields.iter().filter_map(|field| { let field_name = &field.field.ident; match field.kind { FieldKind::Option => Some(quote! { argh::ParseStructOption::Value(&mut #field_name) }), FieldKind::Switch => Some(quote! { argh::ParseStructOption::Flag(&mut #field_name) }), FieldKind::SubCommand | FieldKind::Positional => None, } }); let flag_str_to_output_table_map = flag_str_to_output_table_map_entries(fields); 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 missing_requirements_ident = syn::Ident::new("__missing_requirements", impl_span); let append_missing_requirements = append_missing_requirements(&missing_requirements_ident, fields); let parse_subcommands = if let Some(subcommand) = subcommand { let name = subcommand.name; let ty = subcommand.ty_without_wrapper; quote_spanned! { impl_span => Some(argh::ParseStructSubCommand { subcommands: <#ty as argh::SubCommands>::COMMANDS, dynamic_subcommands: &<#ty as argh::SubCommands>::dynamic_commands(), parse_func: &mut |__command, __remaining_args| { #name = Some(<#ty as argh::FromArgs>::from_args(__command, __remaining_args)?); Ok(()) }, }) } } else { quote_spanned! { impl_span => None } }; // Identifier referring to a value containing the name of the current command as an `&[&str]`. let cmd_name_str_array_ident = syn::Ident::new("__cmd_name", impl_span); let help = help::help(errors, cmd_name_str_array_ident, type_attrs, fields, subcommand); let method_impl = quote_spanned! { impl_span => fn from_args(__cmd_name: &[&str], __args: &[&str]) -> std::result::Result { #![allow(clippy::unwrap_in_result)] #( #init_fields )* argh::parse_struct_args( __cmd_name, __args, argh::ParseStructOptions { arg_to_slot: &[ #( #flag_str_to_output_table_map ,)* ], slots: &mut [ #( #flag_output_table, )* ], }, argh::ParseStructPositionals { positionals: &mut [ #( argh::ParseStructPositional { name: #positional_field_names, slot: &mut #positional_field_idents as &mut argh::ParseValueSlot, }, )* ], last_is_repeating: #last_positional_is_repeating, last_is_greedy: #last_positional_is_greedy, }, #parse_subcommands, &|| #help, )?; let mut #missing_requirements_ident = argh::MissingRequirements::default(); #( #append_missing_requirements )* #missing_requirements_ident.err_on_any()?; Ok(Self { #( #unwrap_fields, )* }) } }; method_impl } fn impl_from_args_struct_redact_arg_values<'a>( errors: &Errors, type_attrs: &TypeAttrs, fields: &'a [StructField<'a>], ) -> TokenStream { let init_fields = declare_local_storage_for_redacted_fields(fields); let unwrap_fields = unwrap_redacted_fields(fields); let positional_fields: Vec<&StructField<'_>> = fields.iter().filter(|field| field.kind == FieldKind::Positional).collect(); let positional_field_idents = positional_fields.iter().map(|field| &field.field.ident); let positional_field_names = positional_fields.iter().map(|field| field.name.to_string()); let last_positional_is_repeating = positional_fields .last() .map(|field| field.optionality == Optionality::Repeating) .unwrap_or(false); let last_positional_is_greedy = positional_fields .last() .map(|field| field.kind == FieldKind::Positional && field.attrs.greedy.is_some()) .unwrap_or(false); let flag_output_table = fields.iter().filter_map(|field| { let field_name = &field.field.ident; match field.kind { FieldKind::Option => Some(quote! { argh::ParseStructOption::Value(&mut #field_name) }), FieldKind::Switch => Some(quote! { argh::ParseStructOption::Flag(&mut #field_name) }), FieldKind::SubCommand | FieldKind::Positional => None, } }); let flag_str_to_output_table_map = flag_str_to_output_table_map_entries(fields); 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 missing_requirements_ident = syn::Ident::new("__missing_requirements", impl_span); let append_missing_requirements = append_missing_requirements(&missing_requirements_ident, fields); let redact_subcommands = if let Some(subcommand) = subcommand { let name = subcommand.name; let ty = subcommand.ty_without_wrapper; quote_spanned! { impl_span => Some(argh::ParseStructSubCommand { subcommands: <#ty as argh::SubCommands>::COMMANDS, dynamic_subcommands: &<#ty as argh::SubCommands>::dynamic_commands(), parse_func: &mut |__command, __remaining_args| { #name = Some(<#ty as argh::FromArgs>::redact_arg_values(__command, __remaining_args)?); Ok(()) }, }) } } else { quote_spanned! { impl_span => None } }; let unwrap_cmd_name_err_string = if type_attrs.is_subcommand.is_none() { quote! { "no command name" } } else { quote! { "no subcommand name" } }; // Identifier referring to a value containing the name of the current command as an `&[&str]`. let cmd_name_str_array_ident = syn::Ident::new("__cmd_name", impl_span); let help = help::help(errors, cmd_name_str_array_ident, type_attrs, fields, subcommand); let method_impl = quote_spanned! { impl_span => fn redact_arg_values(__cmd_name: &[&str], __args: &[&str]) -> std::result::Result, argh::EarlyExit> { #( #init_fields )* argh::parse_struct_args( __cmd_name, __args, argh::ParseStructOptions { arg_to_slot: &[ #( #flag_str_to_output_table_map ,)* ], slots: &mut [ #( #flag_output_table, )* ], }, argh::ParseStructPositionals { positionals: &mut [ #( argh::ParseStructPositional { name: #positional_field_names, slot: &mut #positional_field_idents as &mut argh::ParseValueSlot, }, )* ], last_is_repeating: #last_positional_is_repeating, last_is_greedy: #last_positional_is_greedy, }, #redact_subcommands, &|| #help, )?; let mut #missing_requirements_ident = argh::MissingRequirements::default(); #( #append_missing_requirements )* #missing_requirements_ident.err_on_any()?; let mut __redacted = vec![ if let Some(cmd_name) = __cmd_name.last() { (*cmd_name).to_owned() } else { return Err(argh::EarlyExit::from(#unwrap_cmd_name_err_string.to_owned())); } ]; #( #unwrap_fields )* Ok(__redacted) } }; method_impl } /// Ensures that only the last positional arg is non-required. fn ensure_only_last_positional_is_optional(errors: &Errors, fields: &[StructField<'_>]) { let mut first_non_required_span = None; for field in fields { if field.kind == FieldKind::Positional { if let Some(first) = first_non_required_span { errors.err_span( first, "Only the last positional argument may be `Option`, `Vec`, or defaulted.", ); errors.err(&field.field, "Later positional argument declared here."); return; } if !field.optionality.is_required() { first_non_required_span = Some(field.field.span()); } } } } /// Ensures that only one short or long name is used. fn ensure_unique_names(errors: &Errors, fields: &[StructField<'_>]) { let mut seen_short_names = HashMap::new(); let mut seen_long_names = HashMap::new(); for field in fields { if let Some(short_name) = &field.attrs.short { let short_name = short_name.value(); if let Some(first_use_field) = seen_short_names.get(&short_name) { errors.err_span_tokens( first_use_field, &format!("The short name of \"-{}\" was already used here.", short_name), ); errors.err_span_tokens(field.field, "Later usage here."); } seen_short_names.insert(short_name, &field.field); } if let Some(long_name) = &field.long_name { if let Some(first_use_field) = seen_long_names.get(&long_name) { errors.err_span_tokens( *first_use_field, &format!("The long name of \"{}\" was already used here.", long_name), ); errors.err_span_tokens(field.field, "Later usage here."); } seen_long_names.insert(long_name, field.field); } } } /// Implement `argh::TopLevelCommand` or `argh::SubCommand` as appropriate. fn top_or_sub_cmd_impl( errors: &Errors, name: &syn::Ident, type_attrs: &TypeAttrs, generic_args: &syn::Generics, ) -> TokenStream { let description = help::require_description(errors, name.span(), &type_attrs.description, "type"); let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl(); if type_attrs.is_subcommand.is_none() { // Not a subcommand quote! { #[automatically_derived] impl #impl_generics argh::TopLevelCommand for #name #ty_generics #where_clause {} } } else { let empty_str = syn::LitStr::new("", Span::call_site()); let subcommand_name = type_attrs.name.as_ref().unwrap_or_else(|| { errors.err(name, "`#[argh(name = \"...\")]` attribute is required for subcommands"); &empty_str }); quote! { #[automatically_derived] impl #impl_generics argh::SubCommand for #name #ty_generics #where_clause { const COMMAND: &'static argh::CommandInfo = &argh::CommandInfo { name: #subcommand_name, description: #description, }; } } } } /// Declare a local slots to store each field in during parsing. /// /// Most fields are stored in `Option` locals. /// `argh(option)` fields are stored in a `ParseValueSlotTy` along with a /// function that knows how to decode the appropriate value. fn declare_local_storage_for_from_args_fields<'a>( fields: &'a [StructField<'a>], ) -> impl Iterator + 'a { fields.iter().map(|field| { let field_name = &field.field.ident; let field_type = &field.ty_without_wrapper; // Wrap field types in `Option` if they aren't already `Option` or `Vec`-wrapped. let field_slot_type = match field.optionality { Optionality::Optional | Optionality::Repeating => (&field.field.ty).into_token_stream(), Optionality::None | Optionality::Defaulted(_) => { quote! { std::option::Option<#field_type> } } }; match field.kind { FieldKind::Option | FieldKind::Positional => { let from_str_fn = match &field.attrs.from_str_fn { Some(from_str_fn) => from_str_fn.into_token_stream(), None => { quote! { <#field_type as argh::FromArgValue>::from_arg_value } } }; quote! { let mut #field_name: argh::ParseValueSlotTy<#field_slot_type, #field_type> = argh::ParseValueSlotTy { slot: std::default::Default::default(), parse_func: |_, value| { #from_str_fn(value) }, }; } } FieldKind::SubCommand => { quote! { let mut #field_name: #field_slot_type = None; } } FieldKind::Switch => { quote! { let mut #field_name: #field_slot_type = argh::Flag::default(); } } } }) } /// Unwrap non-optional fields and take options out of their tuple slots. fn unwrap_from_args_fields<'a>( fields: &'a [StructField<'a>], ) -> impl Iterator + 'a { fields.iter().map(|field| { let field_name = field.name; match field.kind { FieldKind::Option | FieldKind::Positional => match &field.optionality { Optionality::None => quote! { #field_name: #field_name.slot.unwrap() }, Optionality::Optional | Optionality::Repeating => { quote! { #field_name: #field_name.slot } } Optionality::Defaulted(tokens) => { quote! { #field_name: #field_name.slot.unwrap_or_else(|| #tokens) } } }, FieldKind::Switch => field_name.into_token_stream(), FieldKind::SubCommand => match field.optionality { Optionality::None => quote! { #field_name: #field_name.unwrap() }, Optionality::Optional | Optionality::Repeating => field_name.into_token_stream(), Optionality::Defaulted(_) => unreachable!(), }, } }) } /// Declare a local slots to store each field in during parsing. /// /// Most fields are stored in `Option` locals. /// `argh(option)` fields are stored in a `ParseValueSlotTy` along with a /// function that knows how to decode the appropriate value. fn declare_local_storage_for_redacted_fields<'a>( fields: &'a [StructField<'a>], ) -> impl Iterator + 'a { fields.iter().map(|field| { let field_name = &field.field.ident; match field.kind { FieldKind::Switch => { quote! { let mut #field_name = argh::RedactFlag { slot: None, }; } } FieldKind::Option => { let field_slot_type = match field.optionality { Optionality::Repeating => { quote! { std::vec::Vec } } Optionality::None | Optionality::Optional | Optionality::Defaulted(_) => { quote! { std::option::Option } } }; quote! { let mut #field_name: argh::ParseValueSlotTy::<#field_slot_type, String> = argh::ParseValueSlotTy { slot: std::default::Default::default(), parse_func: |arg, _| { Ok(arg.to_owned()) }, }; } } FieldKind::Positional => { let field_slot_type = match field.optionality { Optionality::Repeating => { quote! { std::vec::Vec } } Optionality::None | Optionality::Optional | Optionality::Defaulted(_) => { quote! { std::option::Option } } }; let arg_name = field.positional_arg_name(); quote! { let mut #field_name: argh::ParseValueSlotTy::<#field_slot_type, String> = argh::ParseValueSlotTy { slot: std::default::Default::default(), parse_func: |_, _| { Ok(#arg_name.to_owned()) }, }; } } FieldKind::SubCommand => { quote! { let mut #field_name: std::option::Option> = None; } } } }) } /// Unwrap non-optional fields and take options out of their tuple slots. fn unwrap_redacted_fields<'a>( fields: &'a [StructField<'a>], ) -> impl Iterator + 'a { fields.iter().map(|field| { let field_name = field.name; match field.kind { FieldKind::Switch => { quote! { if let Some(__field_name) = #field_name.slot { __redacted.push(__field_name); } } } FieldKind::Option => match field.optionality { Optionality::Repeating => { quote! { __redacted.extend(#field_name.slot.into_iter()); } } Optionality::None | Optionality::Optional | Optionality::Defaulted(_) => { quote! { if let Some(__field_name) = #field_name.slot { __redacted.push(__field_name); } } } }, FieldKind::Positional => { quote! { __redacted.extend(#field_name.slot.into_iter()); } } FieldKind::SubCommand => { quote! { if let Some(__subcommand_args) = #field_name { __redacted.extend(__subcommand_args.into_iter()); } } } } }) } /// Entries of tokens like `("--some-flag-key", 5)` that map from a flag key string /// to an index in the output table. fn flag_str_to_output_table_map_entries<'a>(fields: &'a [StructField<'a>]) -> Vec { let mut flag_str_to_output_table_map = vec![]; for (i, (field, long_name)) in fields .iter() .filter_map(|field| field.long_name.as_ref().map(|long_name| (field, long_name))) .enumerate() { if let Some(short) = &field.attrs.short { let short = format!("-{}", short.value()); flag_str_to_output_table_map.push(quote! { (#short, #i) }); } flag_str_to_output_table_map.push(quote! { (#long_name, #i) }); } flag_str_to_output_table_map } /// For each non-optional field, add an entry to the `argh::MissingRequirements`. fn append_missing_requirements<'a>( // missing_requirements_ident mri: &syn::Ident, fields: &'a [StructField<'a>], ) -> impl Iterator + 'a { let mri = mri.clone(); fields.iter().filter(|f| f.optionality.is_required()).map(move |field| { let field_name = field.name; match field.kind { FieldKind::Switch => unreachable!("switches are always optional"), FieldKind::Positional => { let name = field.positional_arg_name(); quote! { if #field_name.slot.is_none() { #mri.missing_positional_arg(#name) } } } FieldKind::Option => { let name = field.long_name.as_ref().expect("options always have a long name"); quote! { if #field_name.slot.is_none() { #mri.missing_option(#name) } } } FieldKind::SubCommand => { let ty = field.ty_without_wrapper; quote! { if #field_name.is_none() { #mri.missing_subcommands( <#ty as argh::SubCommands>::COMMANDS .iter() .cloned() .chain( <#ty as argh::SubCommands>::dynamic_commands() .iter() .copied() ), ) } } } } }) } /// Require that a type can be a `switch`. /// Throws an error for all types except booleans and integers fn ty_expect_switch(errors: &Errors, ty: &syn::Type) -> bool { fn ty_can_be_switch(ty: &syn::Type) -> bool { if let syn::Type::Path(path) = ty { if path.qself.is_some() { return false; } if path.path.segments.len() != 1 { return false; } let ident = &path.path.segments[0].ident; // `Option` 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) } else { false } } let res = ty_can_be_switch(ty); if !res { errors.err(ty, "switches must be of type `bool`, `Option`, or integer type"); } res } /// Returns `Some(T)` if a type is `wrapper_name` for any `wrapper_name` in `wrapper_names`. fn ty_inner<'a>(wrapper_names: &[&str], ty: &'a syn::Type) -> Option<&'a syn::Type> { if let syn::Type::Path(path) = ty { if path.qself.is_some() { return None; } // Since we only check the last path segment, it isn't necessarily the case that // we're referring to `std::vec::Vec` or `std::option::Option`, but there isn't // a fool proof way to check these since name resolution happens after macro expansion, // so this is likely "good enough" (so long as people don't have their own types called // `Option` or `Vec` that take one generic parameter they're looking to parse). let last_segment = path.path.segments.last()?; if !wrapper_names.iter().any(|name| last_segment.ident == *name) { return None; } if let syn::PathArguments::AngleBracketed(gen_args) = &last_segment.arguments { let generic_arg = gen_args.args.first()?; if let syn::GenericArgument::Type(ty) = &generic_arg { return Some(ty); } } } None } /// Implements `FromArgs` and `SubCommands` for a `#![derive(FromArgs)]` enum. fn impl_from_args_enum( errors: &Errors, name: &syn::Ident, type_attrs: &TypeAttrs, generic_args: &syn::Generics, de: &syn::DataEnum, ) -> TokenStream { parse_attrs::check_enum_type_attrs(errors, type_attrs, &de.enum_token.span); // An enum variant like `()` struct SubCommandVariant<'a> { name: &'a syn::Ident, ty: &'a syn::Type, } let mut dynamic_type_and_variant = None; let variants: Vec> = de .variants .iter() .filter_map(|variant| { let name = &variant.ident; let ty = enum_only_single_field_unnamed_variants(errors, &variant.fields)?; if parse_attrs::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(SubCommandVariant { name, ty }) } }) .collect(); let name_repeating = std::iter::repeat(name.clone()); let variant_ty = variants.iter().map(|x| x.ty).collect::>(); let variant_names = variants.iter().map(|x| x.name).collect::>(); let dynamic_from_args = dynamic_type_and_variant.as_ref().map(|(dynamic_type, dynamic_variant)| { quote! { if let Some(result) = <#dynamic_type as argh::DynamicSubCommand>::try_from_args( command_name, args) { return result.map(#name::#dynamic_variant); } } }); let dynamic_redact_arg_values = dynamic_type_and_variant.as_ref().map(|(dynamic_type, _)| { quote! { if let Some(result) = <#dynamic_type as argh::DynamicSubCommand>::try_redact_arg_values( command_name, args) { return result; } } }); let dynamic_commands = dynamic_type_and_variant.as_ref().map(|(dynamic_type, _)| { quote! { fn dynamic_commands() -> &'static [&'static argh::CommandInfo] { <#dynamic_type as argh::DynamicSubCommand>::commands() } } }); let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl(); quote! { impl #impl_generics argh::FromArgs for #name #ty_generics #where_clause { fn from_args(command_name: &[&str], args: &[&str]) -> std::result::Result { let subcommand_name = if let Some(subcommand_name) = command_name.last() { *subcommand_name } else { return Err(argh::EarlyExit::from("no subcommand name".to_owned())); }; #( if subcommand_name == <#variant_ty as argh::SubCommand>::COMMAND.name { return Ok(#name_repeating::#variant_names( <#variant_ty as argh::FromArgs>::from_args(command_name, args)? )); } )* #dynamic_from_args Err(argh::EarlyExit::from("no subcommand matched".to_owned())) } fn redact_arg_values(command_name: &[&str], args: &[&str]) -> std::result::Result, argh::EarlyExit> { let subcommand_name = if let Some(subcommand_name) = command_name.last() { *subcommand_name } else { return Err(argh::EarlyExit::from("no subcommand name".to_owned())); }; #( if subcommand_name == <#variant_ty as argh::SubCommand>::COMMAND.name { return <#variant_ty as argh::FromArgs>::redact_arg_values(command_name, args); } )* #dynamic_redact_arg_values Err(argh::EarlyExit::from("no subcommand matched".to_owned())) } } impl #impl_generics argh::SubCommands for #name #ty_generics #where_clause { const COMMANDS: &'static [&'static argh::CommandInfo] = &[#( <#variant_ty as argh::SubCommand>::COMMAND, )*]; #dynamic_commands } } } /// Returns `Some(Bar)` if the field is a single-field unnamed variant like `Foo(Bar)`. /// Otherwise, generates an error. fn enum_only_single_field_unnamed_variants<'a>( errors: &Errors, variant_fields: &'a syn::Fields, ) -> Option<&'a syn::Type> { macro_rules! with_enum_suggestion { ($help_text:literal) => { concat!( $help_text, "\nInstead, use a variant with a single unnamed field for each subcommand:\n", " enum MyCommandEnum {\n", " SubCommandOne(SubCommandOne),\n", " SubCommandTwo(SubCommandTwo),\n", " }", ) }; } match variant_fields { syn::Fields::Named(fields) => { errors.err( fields, with_enum_suggestion!( "`#![derive(FromArgs)]` `enum`s do not support variants with named fields." ), ); None } syn::Fields::Unit => { errors.err( variant_fields, with_enum_suggestion!( "`#![derive(FromArgs)]` does not support `enum`s with no variants." ), ); None } syn::Fields::Unnamed(fields) => { if fields.unnamed.len() != 1 { errors.err( fields, with_enum_suggestion!( "`#![derive(FromArgs)]` `enum` variants must only contain one field." ), ); None } else { // `unwrap` is okay because of the length check above. let first_field = fields.unnamed.first().unwrap(); Some(&first_field.ty) } } } } argh_derive-0.1.12/src/parse_attrs.rs000064400000000000000000000511551046102023000156570ustar 00000000000000// Copyright (c) 2020 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::errors::Errors, proc_macro2::Span, std::collections::hash_map::{Entry, HashMap}, }; /// Attributes applied to a field of a `#![derive(FromArgs)]` struct. #[derive(Default)] pub struct FieldAttrs { pub default: Option, pub description: Option, pub from_str_fn: Option, pub field_type: Option, pub long: Option, pub short: Option, pub arg_name: Option, pub greedy: Option, pub hidden_help: bool, } /// The purpose of a particular field on a `#![derive(FromArgs)]` struct. #[derive(Copy, Clone, Eq, PartialEq)] pub enum FieldKind { /// Switches are booleans that are set to "true" by passing the flag. Switch, /// Options are `--key value`. They may be optional (using `Option`), /// or repeating (using `Vec`), or required (neither `Option` nor `Vec`) Option, /// Subcommand fields (of which there can be at most one) refer to enums /// containing one of several potential subcommands. They may be optional /// (using `Option`) or required (no `Option`). SubCommand, /// Positional arguments are parsed literally if the input /// does not begin with `-` or `--` and is not a subcommand. /// They are parsed in declaration order, and only the last positional /// argument in a type may be an `Option`, `Vec`, or have a default value. Positional, } /// The type of a field on a `#![derive(FromArgs)]` struct. /// /// This is a simple wrapper around `FieldKind` which includes the `syn::Ident` /// of the attribute containing the field kind. pub struct FieldType { pub kind: FieldKind, pub ident: syn::Ident, } /// A description of a `#![derive(FromArgs)]` struct. /// /// Defaults to the docstring if one is present, or `#[argh(description = "...")]` /// if one is provided. pub struct Description { /// Whether the description was an explicit annotation or whether it was a doc string. pub explicit: bool, pub content: syn::LitStr, } impl FieldAttrs { pub fn parse(errors: &Errors, field: &syn::Field) -> Self { let mut this = Self::default(); for attr in &field.attrs { if is_doc_attr(attr) { parse_attr_doc(errors, attr, &mut this.description); continue; } let ml = if let Some(ml) = argh_attr_to_meta_list(errors, attr) { ml } 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) { this.parse_attr_arg_name(errors, m); } } else if name.is_ident("default") { 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) { 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) { this.parse_attr_from_str_fn(errors, m); } } else if name.is_ident("long") { 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); } else if name.is_ident("short") { 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, FieldKind::SubCommand, &mut this.field_type, ); } else if name.is_ident("switch") { parse_attr_field_type(errors, &meta, FieldKind::Switch, &mut this.field_type); } else if name.is_ident("positional") { parse_attr_field_type( errors, &meta, FieldKind::Positional, &mut this.field_type, ); } 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`, `hidden_help`", ), ); } } } if let (Some(default), Some(field_type)) = (&this.default, &this.field_type) { match field_type.kind { FieldKind::Option | FieldKind::Positional => {} FieldKind::SubCommand | FieldKind::Switch => errors.err( default, "`default` may only be specified on `#[argh(option)]` \ or `#[argh(positional)]` fields", ), } } match (&this.greedy, this.field_type.as_ref().map(|f| f.kind)) { (Some(_), Some(FieldKind::Positional)) => {} (Some(greedy), Some(_)) => errors.err( &greedy, "`greedy` may only be specified on `#[argh(positional)]` \ fields", ), _ => {} } if let Some(d) = &this.description { check_option_description(errors, d.content.value().trim(), d.content.span()); } this } fn parse_attr_from_str_fn(&mut self, errors: &Errors, m: &syn::MetaList) { parse_attr_fn_name(errors, m, "from_str_fn", &mut self.from_str_fn) } fn parse_attr_default(&mut self, errors: &Errors, m: &syn::MetaNameValue) { parse_attr_single_string(errors, m, "default", &mut self.default); } fn parse_attr_arg_name(&mut self, errors: &Errors, m: &syn::MetaNameValue) { parse_attr_single_string(errors, m, "arg_name", &mut self.arg_name); } fn parse_attr_long(&mut self, errors: &Errors, m: &syn::MetaNameValue) { parse_attr_single_string(errors, m, "long", &mut self.long); let long = self.long.as_ref().unwrap(); let value = long.value(); check_long_name(errors, long, &value); } 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.value) { self.short = Some(lit_char.clone()); if !lit_char.value().is_ascii() { errors.err(lit_char, "Short names must be ASCII"); } } } } 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, attr_name: &str, slot: &mut Option, ) { if let Some(first) = slot { errors.duplicate_attrs(attr_name, first, m); } *slot = errors.ok(m.parse_args()); } fn parse_attr_field_type( errors: &Errors, meta: &syn::Meta, kind: FieldKind, slot: &mut Option, ) { if let Some(path) = errors.expect_meta_word(meta) { if let Some(first) = slot { errors.duplicate_attrs("field kind", &first.ident, path); } else if let Some(word) = path.get_ident() { *slot = Some(FieldType { kind, ident: word.clone() }); } } } // Whether the attribute is one like `#[ ...]` fn is_matching_attr(name: &str, attr: &syn::Attribute) -> bool { attr.path().segments.len() == 1 && attr.path().segments[0].ident == name } /// Checks for `#[doc ...]`, which is generated by doc comments. fn is_doc_attr(attr: &syn::Attribute) -> bool { is_matching_attr("doc", attr) } /// Checks for `#[argh ...]` fn is_argh_attr(attr: &syn::Attribute) -> bool { is_matching_attr("argh", attr) } /// Filters out non-`#[argh(...)]` attributes and converts to a sequence of `syn::Meta`. fn argh_attr_to_meta_list( errors: &Errors, attr: &syn::Attribute, ) -> Option> { if !is_argh_attr(attr) { return None; } let ml = errors.expect_meta_list(&attr.meta)?; errors.ok(ml.parse_args_with( syn::punctuated::Punctuated::::parse_terminated, )) } /// Represents a `#[derive(FromArgs)]` type's top-level attributes. #[derive(Default)] pub struct TypeAttrs { pub is_subcommand: Option, pub name: Option, pub description: Option, pub examples: Vec, pub notes: Vec, pub error_codes: Vec<(syn::LitInt, syn::LitStr)>, } impl TypeAttrs { /// Parse top-level `#[argh(...)]` attributes pub fn parse(errors: &Errors, derive_input: &syn::DeriveInput) -> Self { let mut this = TypeAttrs::default(); for attr in &derive_input.attrs { if is_doc_attr(attr) { parse_attr_doc(errors, attr, &mut this.description); continue; } let ml = if let Some(ml) = argh_attr_to_meta_list(errors, attr) { ml } 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) { parse_attr_description(errors, m, &mut this.description); } } else if name.is_ident("error_code") { 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) { this.parse_attr_example(errors, m); } } else if name.is_ident("name") { 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) { 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()) { this.parse_attr_subcommand(errors, ident); } } else { errors.err( &meta, concat!( "Invalid type-level `argh` attribute\n", "Expected one of: `description`, `error_code`, `example`, `name`, ", "`note`, `subcommand`", ), ); } } } this.check_error_codes(errors); this } /// Checks that error codes are within range for `i32` and that they are /// never duplicated. fn check_error_codes(&self, errors: &Errors) { // map from error code to index let mut map: HashMap = HashMap::new(); for (index, (lit_int, _lit_str)) in self.error_codes.iter().enumerate() { let value = match lit_int.base10_parse::() { Ok(v) => v, Err(e) => { errors.push(e); continue; } }; if value > (std::i32::MAX as u64) { errors.err(lit_int, "Error code out of range for `i32`"); } match map.entry(value) { Entry::Occupied(previous) => { let previous_index = *previous.get(); let (previous_lit_int, _previous_lit_str) = &self.error_codes[previous_index]; errors.err(lit_int, &format!("Duplicate error code {}", value)); errors.err( previous_lit_int, &format!("Error code {} previously defined here", value), ); } Entry::Vacant(slot) => { slot.insert(index); } } } } fn parse_attr_error_code(&mut self, errors: &Errors, ml: &syn::MetaList) { errors.ok(ml.parse_args_with(|input: syn::parse::ParseStream| { let err_code = input.parse()?; input.parse::()?; 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) { parse_attr_multi_string(errors, m, &mut self.examples) } fn parse_attr_name(&mut self, errors: &Errors, m: &syn::MetaNameValue) { parse_attr_single_string(errors, m, "name", &mut self.name); if let Some(name) = &self.name { if name.value() == "help" { errors.err(name, "Custom `help` commands are not supported."); } } } fn parse_attr_note(&mut self, errors: &Errors, m: &syn::MetaNameValue) { parse_attr_multi_string(errors, m, &mut self.notes) } fn parse_attr_subcommand(&mut self, errors: &Errors, ident: &syn::Ident) { if let Some(first) = &self.is_subcommand { errors.duplicate_attrs("subcommand", first, ident); } else { self.is_subcommand = Some(ident.clone()); } } } /// Represents an enum variant's attributes. #[derive(Default)] pub struct VariantAttrs { pub is_dynamic: Option, } impl VariantAttrs { /// Parse enum variant `#[argh(...)]` attributes pub fn parse(errors: &Errors, variant: &syn::Variant) -> Self { let mut this = VariantAttrs::default(); let fields = match &variant.fields { syn::Fields::Named(fields) => Some(&fields.named), syn::Fields::Unnamed(fields) => Some(&fields.unnamed), syn::Fields::Unit => None, }; for field in fields.into_iter().flatten() { for attr in &field.attrs { if is_argh_attr(attr) { err_unused_enum_attr(errors, attr); } } } for attr in &variant.attrs { let ml = if let Some(ml) = argh_attr_to_meta_list(errors, attr) { ml } 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); } else { this.is_dynamic = errors.expect_meta_word(&meta).cloned(); } } else { errors.err( &meta, "Invalid variant-level `argh` attribute\n\ Variants can only have the #[argh(dynamic)] attribute.", ); } } } this } } fn check_option_description(errors: &Errors, desc: &str, span: Span) { let chars = &mut desc.trim().chars(); match (chars.next(), chars.next()) { (Some(x), _) if x.is_lowercase() => {} // If both the first and second letter are not lowercase, // this is likely an initialism which should be allowed. (Some(x), Some(y)) if !x.is_lowercase() && !y.is_lowercase() => {} _ => { errors.err_span(span, "Descriptions must begin with a lowercase letter"); } } } fn parse_attr_single_string( errors: &Errors, m: &syn::MetaNameValue, name: &str, slot: &mut Option, ) { if let Some(first) = slot { errors.duplicate_attrs(name, first, m); } 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) { 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) { let nv = if let Some(nv) = errors.expect_meta_name_value(&attr.meta) { nv } else { return; }; // Don't replace an existing description. if slot.as_ref().map(|d| d.explicit).unwrap_or(false) { return; } 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(); syn::LitStr::new(&(previous.value() + &*lit_str.value()), previous_span) } else { lit_str.clone() }; *slot = Some(Description { explicit: false, content: lit_str }); } } fn parse_attr_description(errors: &Errors, m: &syn::MetaNameValue, slot: &mut Option) { 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 { if description.explicit { errors.duplicate_attrs("description", &description.content, lit_str); } } *slot = Some(Description { explicit: true, content: lit_str.clone() }); } /// Checks that a `#![derive(FromArgs)]` enum has an `#[argh(subcommand)]` /// attribute and that it does not have any other type-level `#[argh(...)]` attributes. pub fn check_enum_type_attrs(errors: &Errors, type_attrs: &TypeAttrs, type_span: &Span) { let TypeAttrs { is_subcommand, name, description, examples, notes, error_codes } = type_attrs; // Ensure that `#[argh(subcommand)]` is present. if is_subcommand.is_none() { errors.err_span( *type_span, concat!( "`#![derive(FromArgs)]` on `enum`s can only be used to enumerate subcommands.\n", "Consider adding `#[argh(subcommand)]` to the `enum` declaration.", ), ); } // Error on all other type-level attributes. if let Some(name) = name { err_unused_enum_attr(errors, name); } if let Some(description) = description { if description.explicit { err_unused_enum_attr(errors, &description.content); } } if let Some(example) = examples.first() { err_unused_enum_attr(errors, example); } if let Some(note) = notes.first() { err_unused_enum_attr(errors, note); } if let Some(err_code) = error_codes.first() { err_unused_enum_attr(errors, &err_code.0); } } fn err_unused_enum_attr(errors: &Errors, location: &impl syn::spanned::Spanned) { errors.err( location, concat!( "Unused `argh` attribute on `#![derive(FromArgs)]` enum. ", "Such `enum`s can only be used to dispatch to subcommands, ", "and should only contain the #[argh(subcommand)] attribute.", ), ); }