chore(vendor): add anyhow crate

This commit was merged in pull request #68.
This commit is contained in:
2026-09-17 14:13:23 -04:00
parent 7a3a3d6b90
commit 37539682d4
454 changed files with 8906 additions and 1468 deletions

View File

@@ -0,0 +1,238 @@
use std::iter::FromIterator;
use proc_macro2::TokenStream;
use quote::ToTokens;
use quote::quote;
use syn::spanned::Spanned;
use syn::{
Attribute, Expr, Ident, LitStr, Token, parenthesized,
parse::{Parse, ParseStream},
punctuated::Punctuated,
};
use crate::utils::Sp;
#[derive(Clone)]
pub(crate) struct ClapAttr {
pub(crate) kind: Sp<AttrKind>,
pub(crate) name: Ident,
pub(crate) magic: Option<MagicAttrName>,
pub(crate) value: Option<AttrValue>,
}
impl ClapAttr {
pub(crate) fn parse_all(all_attrs: &[Attribute]) -> Result<Vec<Self>, syn::Error> {
let mut parsed = Vec::new();
for attr in all_attrs {
let kind = if attr.path().is_ident("clap") {
Sp::new(AttrKind::Clap, attr.path().span())
} else if attr.path().is_ident("structopt") {
Sp::new(AttrKind::StructOpt, attr.path().span())
} else if attr.path().is_ident("command") {
Sp::new(AttrKind::Command, attr.path().span())
} else if attr.path().is_ident("group") {
Sp::new(AttrKind::Group, attr.path().span())
} else if attr.path().is_ident("arg") {
Sp::new(AttrKind::Arg, attr.path().span())
} else if attr.path().is_ident("value") {
Sp::new(AttrKind::Value, attr.path().span())
} else {
continue;
};
for mut attr in attr.parse_args_with(Punctuated::<Self, Token![,]>::parse_terminated)? {
attr.kind = kind;
parsed.push(attr);
}
}
Ok(parsed)
}
pub(crate) fn value_or_abort(&self) -> Result<&AttrValue, syn::Error> {
self.value
.as_ref()
.ok_or_else(|| format_err!(self.name, "attribute `{}` requires a value", self.name))
}
pub(crate) fn lit_str_or_abort(&self) -> Result<&LitStr, syn::Error> {
let value = self.value_or_abort()?;
match value {
AttrValue::Expr(Expr::Lit(expr)) => match &expr.lit {
syn::Lit::Str(lit) => Ok(lit),
_ => {
abort!(
expr,
"attribute `{}` can only accept string literals",
self.name
)
}
},
AttrValue::Expr(expr) => {
abort!(
expr,
"attribute `{}` can only accept string literals",
self.name
)
}
AttrValue::Call(_) => {
abort!(
self.name,
"attribute `{}` can only accept string literals",
self.name
)
}
}
}
pub(crate) fn lit_bool_or_abort(&self) -> Result<bool, syn::Error> {
let value = self.value_or_abort()?;
match value {
AttrValue::Expr(Expr::Lit(syn::ExprLit {
lit: syn::Lit::Bool(lit),
..
})) => Ok(lit.value()),
_ => abort!(
self.name,
"attribute `{}` can only accept bool literals",
self.name
),
}
}
}
impl Parse for ClapAttr {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let name: Ident = input.parse()?;
let name_str = name.to_string();
let magic = match name_str.as_str() {
"rename_all" => Some(MagicAttrName::RenameAll),
"rename_all_env" => Some(MagicAttrName::RenameAllEnv),
"skip" => Some(MagicAttrName::Skip),
"next_display_order" => Some(MagicAttrName::NextDisplayOrder),
"next_help_heading" => Some(MagicAttrName::NextHelpHeading),
"default_value_t" => Some(MagicAttrName::DefaultValueT),
"default_values_t" => Some(MagicAttrName::DefaultValuesT),
"default_value_os_t" => Some(MagicAttrName::DefaultValueOsT),
"default_values_os_t" => Some(MagicAttrName::DefaultValuesOsT),
"long" => Some(MagicAttrName::Long),
"short" => Some(MagicAttrName::Short),
"value_parser" => Some(MagicAttrName::ValueParser),
"action" => Some(MagicAttrName::Action),
"env" => Some(MagicAttrName::Env),
"flatten" => Some(MagicAttrName::Flatten),
"value_enum" => Some(MagicAttrName::ValueEnum),
"from_global" => Some(MagicAttrName::FromGlobal),
"subcommand" => Some(MagicAttrName::Subcommand),
"external_subcommand" => Some(MagicAttrName::ExternalSubcommand),
"verbatim_doc_comment" => Some(MagicAttrName::VerbatimDocComment),
"about" => Some(MagicAttrName::About),
"long_about" => Some(MagicAttrName::LongAbout),
"long_help" => Some(MagicAttrName::LongHelp),
"author" => Some(MagicAttrName::Author),
"version" => Some(MagicAttrName::Version),
"defer" => Some(MagicAttrName::Defer),
_ => None,
};
let value = if input.peek(Token![=]) {
// `name = value` attributes.
let assign_token = input.parse::<Token![=]>()?; // skip '='
match input.parse::<Expr>() {
Ok(expr) => Some(AttrValue::Expr(expr)),
Err(_) => abort! {
assign_token,
"expected `string literal` or `expression` after `=`"
},
}
} else if input.peek(syn::token::Paren) {
// `name(...)` attributes.
let nested;
parenthesized!(nested in input);
let method_args: Punctuated<_, _> = nested.parse_terminated(Expr::parse, Token![,])?;
Some(AttrValue::Call(Vec::from_iter(method_args)))
} else {
None
};
Ok(Self {
kind: Sp::new(AttrKind::Clap, name.span()),
name,
magic,
value,
})
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub(crate) enum MagicAttrName {
Short,
Long,
ValueParser,
Action,
Env,
Flatten,
ValueEnum,
FromGlobal,
Subcommand,
VerbatimDocComment,
ExternalSubcommand,
About,
LongAbout,
LongHelp,
Author,
Version,
RenameAllEnv,
RenameAll,
Skip,
DefaultValueT,
DefaultValuesT,
DefaultValueOsT,
DefaultValuesOsT,
NextDisplayOrder,
NextHelpHeading,
Defer,
}
#[derive(Clone)]
#[allow(clippy::large_enum_variant)]
pub(crate) enum AttrValue {
Expr(Expr),
Call(Vec<Expr>),
}
impl ToTokens for AttrValue {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Expr(t) => t.to_tokens(tokens),
Self::Call(t) => {
let t = quote!(#(#t),*);
t.to_tokens(tokens);
}
}
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub(crate) enum AttrKind {
Clap,
StructOpt,
Command,
Group,
Arg,
Value,
}
impl AttrKind {
pub(crate) fn as_str(&self) -> &'static str {
match self {
Self::Clap => "clap",
Self::StructOpt => "structopt",
Self::Command => "command",
Self::Group => "group",
Self::Arg => "arg",
Self::Value => "value",
}
}
}

View File

@@ -0,0 +1,792 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote, quote_spanned};
use syn::{
Data, DataStruct, DeriveInput, Field, Fields, FieldsNamed, Generics, punctuated::Punctuated,
spanned::Spanned, token::Comma,
};
use crate::item::{Item, Kind, Name};
use crate::utils::{Sp, Ty, inner_type, sub_type};
pub(crate) fn derive_args(input: &DeriveInput) -> Result<TokenStream, syn::Error> {
let ident = &input.ident;
match input.data {
Data::Struct(DataStruct {
fields: Fields::Named(ref fields),
..
}) => {
let name = Name::Derived(ident.clone());
let item = Item::from_args_struct(input, name)?;
let fields = collect_args_fields(&item, fields)?;
gen_for_struct(&item, ident, &input.generics, &fields)
}
Data::Struct(DataStruct {
fields: Fields::Unit,
..
}) => {
let name = Name::Derived(ident.clone());
let item = Item::from_args_struct(input, name)?;
let fields = Punctuated::<Field, Comma>::new();
let fields = fields
.iter()
.map(|field| {
let item = Item::from_args_field(field, item.casing(), item.env_casing())?;
Ok((field, item))
})
.collect::<Result<Vec<_>, syn::Error>>()?;
gen_for_struct(&item, ident, &input.generics, &fields)
}
_ => abort_call_site!("`#[derive(Args)]` only supports non-tuple structs"),
}
}
pub(crate) fn gen_for_struct(
item: &Item,
item_name: &Ident,
generics: &Generics,
fields: &[(&Field, Item)],
) -> Result<TokenStream, syn::Error> {
if !matches!(&*item.kind(), Kind::Command(_)) {
abort! { item.kind().span(),
"`{}` cannot be used with `#[command]`",
item.kind().name(),
}
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let constructor = gen_constructor(fields)?;
let updater = gen_updater(fields, true)?;
let raw_deprecated = raw_deprecated();
let app_var = Ident::new("__clap_app", Span::call_site());
let augmentation = gen_augment(fields, &app_var, item, false)?;
let augmentation_update = gen_augment(fields, &app_var, item, true)?;
let initial_app_methods = item.initial_top_level_methods();
let final_app_methods = item.final_top_level_methods();
let group_id = if item.skip_group() {
quote!(None)
} else {
let group_id = item.group_id();
quote!(Some(clap::Id::from(#group_id)))
};
Ok(quote! {
#[allow(
dead_code,
unreachable_code,
unused_variables,
unused_braces,
unused_qualifications,
)]
#[allow(
clippy::style,
clippy::complexity,
clippy::pedantic,
clippy::restriction,
clippy::perf,
clippy::deprecated,
clippy::nursery,
clippy::cargo,
clippy::suspicious_else_formatting,
clippy::almost_swapped,
clippy::redundant_locals,
)]
#[automatically_derived]
impl #impl_generics clap::FromArgMatches for #item_name #ty_generics #where_clause {
fn from_arg_matches(__clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> {
Self::from_arg_matches_mut(&mut __clap_arg_matches.clone())
}
fn from_arg_matches_mut(__clap_arg_matches: &mut clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> {
#raw_deprecated
let v = #item_name #constructor;
::std::result::Result::Ok(v)
}
fn update_from_arg_matches(&mut self, __clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<(), clap::Error> {
self.update_from_arg_matches_mut(&mut __clap_arg_matches.clone())
}
fn update_from_arg_matches_mut(&mut self, __clap_arg_matches: &mut clap::ArgMatches) -> ::std::result::Result<(), clap::Error> {
#raw_deprecated
#updater
::std::result::Result::Ok(())
}
}
#[allow(
dead_code,
unreachable_code,
unused_variables,
unused_braces,
unused_qualifications,
)]
#[allow(
clippy::style,
clippy::complexity,
clippy::pedantic,
clippy::restriction,
clippy::perf,
clippy::deprecated,
clippy::nursery,
clippy::cargo,
clippy::suspicious_else_formatting,
clippy::almost_swapped,
clippy::redundant_locals,
)]
#[automatically_derived]
impl #impl_generics clap::Args for #item_name #ty_generics #where_clause {
fn group_id() -> Option<clap::Id> {
#group_id
}
fn augment_args<'b>(#app_var: clap::Command) -> clap::Command {
let #app_var = #app_var #initial_app_methods;
let #app_var = #augmentation;
#app_var #final_app_methods
}
fn augment_args_for_update<'b>(#app_var: clap::Command) -> clap::Command {
let #app_var = #app_var #initial_app_methods;
let #app_var = #augmentation_update;
#app_var #final_app_methods
}
}
})
}
/// Generate a block of code to add arguments/subcommands corresponding to
/// the `fields` to an cmd.
pub(crate) fn gen_augment(
fields: &[(&Field, Item)],
app_var: &Ident,
parent_item: &Item,
override_required: bool,
) -> Result<TokenStream, syn::Error> {
let mut subcommand_specified = false;
let mut args = Vec::new();
for (field, item) in fields {
let kind = item.kind();
let genned = match &*kind {
Kind::Command(_)
| Kind::Value
| Kind::Skip(_, _)
| Kind::FromGlobal(_)
| Kind::ExternalSubcommand => None,
Kind::Subcommand(ty) => {
if subcommand_specified {
abort!(
field.span(),
"`#[command(subcommand)]` can only be used once per container"
);
}
subcommand_specified = true;
let subcmd_type = match (**ty, sub_type(&field.ty)) {
(Ty::Option, Some(sub_type)) => sub_type,
_ => &field.ty,
};
let implicit_methods = if **ty == Ty::Option {
quote!()
} else {
quote_spanned! { kind.span()=>
.subcommand_required(true)
.arg_required_else_help(true)
}
};
let override_methods = if override_required {
quote_spanned! { kind.span()=>
.subcommand_required(false)
.arg_required_else_help(false)
}
} else {
quote!()
};
Some(quote! {
let #app_var = <#subcmd_type as clap::Subcommand>::augment_subcommands( #app_var );
let #app_var = #app_var
#implicit_methods
#override_methods;
})
}
Kind::Flatten(ty) => {
let inner_type = match (**ty, sub_type(&field.ty)) {
(Ty::Option, Some(sub_type)) => sub_type,
_ => &field.ty,
};
let next_help_heading = item.next_help_heading();
let next_display_order = item.next_display_order();
let flatten_group_assert = if matches!(**ty, Ty::Option) {
quote_spanned! { kind.span()=>
<#inner_type as clap::Args>::group_id().expect("cannot `#[flatten]` an `Option<Args>` with `#[group(skip)]`");
}
} else {
quote! {}
};
if override_required {
Some(quote_spanned! { kind.span()=>
#flatten_group_assert
let #app_var = #app_var
#next_help_heading
#next_display_order;
let #app_var = <#inner_type as clap::Args>::augment_args_for_update(#app_var);
})
} else {
Some(quote_spanned! { kind.span()=>
#flatten_group_assert
let #app_var = #app_var
#next_help_heading
#next_display_order;
let #app_var = <#inner_type as clap::Args>::augment_args(#app_var);
})
}
}
Kind::Arg(ty) => {
let value_parser = item.value_parser(&field.ty);
let action = item.action(&field.ty);
let value_name = item.value_name();
let implicit_methods = match **ty {
Ty::Unit => {
// Leaving out `value_parser` as it will always fail
quote_spanned! { ty.span()=>
.value_name(#value_name)
#action
}
}
Ty::Option => {
quote_spanned! { ty.span()=>
.value_name(#value_name)
#value_parser
#action
}
}
Ty::OptionOption => quote_spanned! { ty.span()=>
.value_name(#value_name)
.num_args(0..=1)
#value_parser
#action
},
Ty::OptionVec => {
if item.is_positional() {
quote_spanned! { ty.span()=>
.value_name(#value_name)
.num_args(1..) // action won't be sufficient for getting multiple
#value_parser
#action
}
} else {
quote_spanned! { ty.span()=>
.value_name(#value_name)
#value_parser
#action
}
}
}
Ty::Vec => {
if item.is_positional() {
quote_spanned! { ty.span()=>
.value_name(#value_name)
.num_args(1..) // action won't be sufficient for getting multiple
#value_parser
#action
}
} else {
quote_spanned! { ty.span()=>
.value_name(#value_name)
#value_parser
#action
}
}
}
Ty::VecVec | Ty::OptionVecVec => {
quote_spanned! { ty.span() =>
.value_name(#value_name)
#value_parser
#action
}
}
Ty::Other => {
let required = item.find_default_method().is_none();
// `ArgAction::takes_values` is assuming `ArgAction::default_value` will be
// set though that won't always be true but this should be good enough,
// otherwise we'll report an "arg required" error when unwrapping.
let action_value = action.args();
quote_spanned! { ty.span()=>
.value_name(#value_name)
.required(#required && #action_value.takes_values())
#value_parser
#action
}
}
};
let id = item.id();
let explicit_methods = item.field_methods();
let deprecations = if !override_required {
item.deprecations()
} else {
quote!()
};
let override_methods = if override_required {
quote_spanned! { kind.span()=>
.required(false)
}
} else {
quote!()
};
Some(quote_spanned! { field.span()=>
let #app_var = #app_var.arg({
#deprecations
#[allow(deprecated)]
let arg = clap::Arg::new(#id)
#implicit_methods;
let arg = arg
#explicit_methods;
let arg = arg
#override_methods;
arg
});
})
}
};
args.push(genned);
}
let deprecations = if !override_required {
parent_item.deprecations()
} else {
quote!()
};
let group_app_methods = if parent_item.skip_group() {
quote!()
} else {
let group_id = parent_item.group_id();
let literal_group_members = fields
.iter()
.filter_map(|(_field, item)| {
let kind = item.kind();
if matches!(*kind, Kind::Arg(_)) {
Some(item.id())
} else {
None
}
})
.collect::<Vec<_>>();
let literal_group_members_len = literal_group_members.len();
let mut literal_group_members = quote! {{
let members: [clap::Id; #literal_group_members_len] = [#( clap::Id::from(#literal_group_members) ),* ];
members
}};
// HACK: Validation isn't ready yet for nested arg groups, so just don't populate the group in
// that situation
let possible_group_members_len = fields
.iter()
.filter(|(_field, item)| {
let kind = item.kind();
matches!(*kind, Kind::Flatten(_))
})
.count();
if 0 < possible_group_members_len {
literal_group_members = quote! {{
let members: [clap::Id; 0] = [];
members
}};
}
let group_methods = parent_item.group_methods();
quote!(
.group(
clap::ArgGroup::new(#group_id)
.multiple(true)
#group_methods
.args(#literal_group_members)
)
)
};
Ok(quote! {{
#deprecations
let #app_var = #app_var
#group_app_methods
;
#( #args )*
#app_var
}})
}
pub(crate) fn gen_constructor(fields: &[(&Field, Item)]) -> Result<TokenStream, syn::Error> {
let fields = fields.iter().map(|(field, item)| {
let field_name = field.ident.as_ref().unwrap();
let kind = item.kind();
let arg_matches = format_ident!("__clap_arg_matches");
let genned = match &*kind {
Kind::Command(_)
| Kind::Value
| Kind::ExternalSubcommand => {
abort! { kind.span(),
"`{}` cannot be used with `#[arg]`",
kind.name(),
}
}
Kind::Subcommand(ty) => {
let subcmd_type = match (**ty, sub_type(&field.ty)) {
(Ty::Option, Some(sub_type)) => sub_type,
_ => &field.ty,
};
match **ty {
Ty::Option => {
quote_spanned! { kind.span()=>
#field_name: {
if #arg_matches.subcommand_name().map(<#subcmd_type as clap::Subcommand>::has_subcommand).unwrap_or(false) {
Some(<#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?)
} else {
None
}
}
}
},
Ty::Other => {
quote_spanned! { kind.span()=>
#field_name: {
<#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?
}
}
},
Ty::Unit |
Ty::Vec |
Ty::OptionOption |
Ty::OptionVec |
Ty::VecVec |
Ty::OptionVecVec => {
abort!(
ty.span(),
"`{}` is an invalid `#[command(subcommand)]` type",
ty.as_str()
);
}
}
}
Kind::Flatten(ty) => {
let inner_type = match (**ty, sub_type(&field.ty)) {
(Ty::Option, Some(sub_type)) => sub_type,
_ => &field.ty,
};
match **ty {
Ty::Other => {
quote_spanned! { kind.span()=>
#field_name: <#inner_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?
}
},
Ty::Option => {
quote_spanned! { kind.span()=>
#field_name: {
let group_id = <#inner_type as clap::Args>::group_id()
.expect("asserted during `Arg` creation");
if #arg_matches.contains_id(group_id.as_str()) {
Some(
<#inner_type as clap::FromArgMatches>::from_arg_matches_mut(#arg_matches)?
)
} else {
None
}
}
}
},
Ty::Unit |
Ty::Vec |
Ty::OptionOption |
Ty::OptionVec |
Ty::VecVec |
Ty::OptionVecVec => {
abort!(
ty.span(),
"`{}` is an invalid `#[command(flatten)]` type",
ty.as_str()
);
}
}
},
Kind::Skip(val, _) => match val {
None => quote_spanned!(kind.span()=> #field_name: Default::default()),
Some(val) => quote_spanned!(kind.span()=> #field_name: (#val).into()),
},
Kind::Arg(ty) | Kind::FromGlobal(ty) => {
gen_parsers(item, ty, field_name, field, None)?
}
};
Ok(genned)
}).collect::<Result<Vec<_>, syn::Error>>()?;
Ok(quote! {{
#( #fields ),*
}})
}
pub(crate) fn gen_updater(
fields: &[(&Field, Item)],
use_self: bool,
) -> Result<TokenStream, syn::Error> {
let mut genned_fields = Vec::new();
for (field, item) in fields {
let field_name = field.ident.as_ref().unwrap();
let kind = item.kind();
let access = if use_self {
quote! {
#[allow(non_snake_case)]
let #field_name = &mut self.#field_name;
}
} else {
quote!()
};
let arg_matches = format_ident!("__clap_arg_matches");
let genned = match &*kind {
Kind::Command(_) | Kind::Value | Kind::ExternalSubcommand => {
abort! { kind.span(),
"`{}` cannot be used with `#[arg]`",
kind.name(),
}
}
Kind::Subcommand(ty) => {
let subcmd_type = match (**ty, sub_type(&field.ty)) {
(Ty::Option, Some(sub_type)) => sub_type,
_ => &field.ty,
};
let updater = quote_spanned! { ty.span()=>
<#subcmd_type as clap::FromArgMatches>::update_from_arg_matches_mut(#field_name, #arg_matches)?;
};
let updater = match **ty {
Ty::Option => quote_spanned! { kind.span()=>
if let Some(#field_name) = #field_name.as_mut() {
#updater
} else {
*#field_name = Some(<#subcmd_type as clap::FromArgMatches>::from_arg_matches_mut(
#arg_matches
)?);
}
},
_ => quote_spanned! { kind.span()=>
#updater
},
};
quote_spanned! { kind.span()=>
{
#access
#updater
}
}
}
Kind::Flatten(ty) => {
let inner_type = match (**ty, sub_type(&field.ty)) {
(Ty::Option, Some(sub_type)) => sub_type,
_ => &field.ty,
};
let updater = quote_spanned! { ty.span()=>
<#inner_type as clap::FromArgMatches>::update_from_arg_matches_mut(#field_name, #arg_matches)?;
};
let updater = match **ty {
Ty::Option => quote_spanned! { kind.span()=>
if let Some(#field_name) = #field_name.as_mut() {
#updater
} else {
*#field_name = Some(<#inner_type as clap::FromArgMatches>::from_arg_matches_mut(
#arg_matches
)?);
}
},
_ => quote_spanned! { kind.span()=>
#updater
},
};
quote_spanned! { kind.span()=>
{
#access
#updater
}
}
}
Kind::Skip(_, _) => quote!(),
Kind::Arg(ty) | Kind::FromGlobal(ty) => {
gen_parsers(item, ty, field_name, field, Some(&access))?
}
};
genned_fields.push(genned);
}
Ok(quote! {
#( #genned_fields )*
})
}
fn gen_parsers(
item: &Item,
ty: &Sp<Ty>,
field_name: &Ident,
field: &Field,
update: Option<&TokenStream>,
) -> Result<TokenStream, syn::Error> {
let span = ty.span();
let convert_type = inner_type(&field.ty);
let id = item.id();
let get_one = quote_spanned!(span=> remove_one::<#convert_type>);
let get_many = quote_spanned!(span=> remove_many::<#convert_type>);
let get_occurrences = quote_spanned!(span=> remove_occurrences::<#convert_type>);
// Give this identifier the same hygiene
// as the `arg_matches` parameter definition. This
// allows us to refer to `arg_matches` within a `quote_spanned` block
let arg_matches = format_ident!("__clap_arg_matches");
let field_value = match **ty {
Ty::Unit => {
quote_spanned! { ty.span()=>
()
}
}
Ty::Option => {
quote_spanned! { ty.span()=>
#arg_matches.#get_one(#id)
}
}
Ty::OptionOption => quote_spanned! { ty.span()=>
if #arg_matches.contains_id(#id) {
Some(
#arg_matches.#get_one(#id)
)
} else {
None
}
},
Ty::OptionVec => quote_spanned! { ty.span()=>
if #arg_matches.contains_id(#id) {
Some(#arg_matches.#get_many(#id)
.map(|v| v.collect::<Vec<_>>())
.unwrap_or_else(Vec::new))
} else {
None
}
},
Ty::Vec => {
quote_spanned! { ty.span()=>
#arg_matches.#get_many(#id)
.map(|v| v.collect::<Vec<_>>())
.unwrap_or_else(Vec::new)
}
}
Ty::VecVec => quote_spanned! { ty.span()=>
#arg_matches.#get_occurrences(#id)
.map(|g| g.map(::std::iter::Iterator::collect).collect::<Vec<Vec<_>>>())
.unwrap_or_else(Vec::new)
},
Ty::OptionVecVec => quote_spanned! { ty.span()=>
#arg_matches.#get_occurrences(#id)
.map(|g| g.map(::std::iter::Iterator::collect).collect::<Vec<Vec<_>>>())
},
Ty::Other => {
// Prefer `concat` where possible for reduced code size but fallback to `format!` to
// allow non-literal `id`s
match id {
Name::Assigned(_) => {
quote_spanned! { ty.span()=>
#arg_matches.#get_one(#id)
.ok_or_else(|| clap::Error::raw(clap::error::ErrorKind::MissingRequiredArgument, format!("the following required argument was not provided: {}", #id)))?
}
}
Name::Derived(_) => {
quote_spanned! { ty.span()=>
#arg_matches.#get_one(#id)
.ok_or_else(|| clap::Error::raw(clap::error::ErrorKind::MissingRequiredArgument, concat!("the following required argument was not provided: ", #id)))?
}
}
}
}
};
let genned = if let Some(access) = update {
quote_spanned! { field.span()=>
if #arg_matches.contains_id(#id) {
#access
*#field_name = #field_value
}
}
} else {
quote_spanned!(field.span()=> #field_name: #field_value )
};
Ok(genned)
}
#[cfg(feature = "raw-deprecated")]
pub(crate) fn raw_deprecated() -> TokenStream {
quote! {}
}
#[cfg(not(feature = "raw-deprecated"))]
pub(crate) fn raw_deprecated() -> TokenStream {
quote! {
#![allow(deprecated)] // Assuming any deprecation in here will be related to a deprecation in `Args`
}
}
pub(crate) fn collect_args_fields<'a>(
item: &'a Item,
fields: &'a FieldsNamed,
) -> Result<Vec<(&'a Field, Item)>, syn::Error> {
fields
.named
.iter()
.map(|field| {
let item = Item::from_args_field(field, item.casing(), item.env_casing())?;
Ok((field, item))
})
.collect()
}

View File

@@ -0,0 +1,117 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{Generics, Ident};
use crate::item::Item;
pub(crate) fn gen_for_struct(
item: &Item,
item_name: &Ident,
generics: &Generics,
) -> Result<TokenStream, syn::Error> {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let name = item.cased_name();
let app_var = Ident::new("__clap_app", Span::call_site());
let tokens = quote! {
#[allow(
dead_code,
unreachable_code,
unused_variables,
unused_braces,
unused_qualifications,
)]
#[allow(
clippy::style,
clippy::complexity,
clippy::pedantic,
clippy::restriction,
clippy::perf,
clippy::deprecated,
clippy::nursery,
clippy::cargo,
clippy::suspicious_else_formatting,
clippy::almost_swapped,
clippy::redundant_locals,
)]
#[automatically_derived]
impl #impl_generics clap::CommandFactory for #item_name #ty_generics #where_clause {
fn command<'b>() -> clap::Command {
let #app_var = clap::Command::new(#name);
<Self as clap::Args>::augment_args(#app_var)
}
fn command_for_update<'b>() -> clap::Command {
let #app_var = clap::Command::new(#name);
<Self as clap::Args>::augment_args_for_update(#app_var)
}
}
};
Ok(tokens)
}
pub(crate) fn gen_for_enum(
item: &Item,
item_name: &Ident,
generics: &Generics,
) -> Result<TokenStream, syn::Error> {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let name = item.cased_name();
let app_var = Ident::new("__clap_app", Span::call_site());
Ok(quote! {
#[allow(
dead_code,
unreachable_code,
unused_variables,
unused_braces,
unused_qualifications,
)]
#[allow(
clippy::style,
clippy::complexity,
clippy::pedantic,
clippy::restriction,
clippy::perf,
clippy::deprecated,
clippy::nursery,
clippy::cargo,
clippy::suspicious_else_formatting,
clippy::almost_swapped,
clippy::redundant_locals,
)]
#[automatically_derived]
impl #impl_generics clap::CommandFactory for #item_name #ty_generics #where_clause {
fn command<'b>() -> clap::Command {
let #app_var = clap::Command::new(#name)
.subcommand_required(true)
.arg_required_else_help(true);
<Self as clap::Subcommand>::augment_subcommands(#app_var)
}
fn command_for_update<'b>() -> clap::Command {
let #app_var = clap::Command::new(#name);
<Self as clap::Subcommand>::augment_subcommands_for_update(#app_var)
.subcommand_required(false)
.arg_required_else_help(false)
}
}
})
}

View File

@@ -0,0 +1,23 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
mod args;
mod into_app;
mod parser;
mod subcommand;
mod value_enum;
pub(crate) use self::parser::derive_parser;
pub(crate) use args::derive_args;
pub(crate) use subcommand::derive_subcommand;
pub(crate) use value_enum::derive_value_enum;

View File

@@ -0,0 +1,127 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use proc_macro2::TokenStream;
use quote::quote;
use syn::Ident;
use syn::Variant;
use syn::{
self, Data, DataStruct, DeriveInput, Field, Fields, Generics, punctuated::Punctuated,
token::Comma,
};
use crate::derives::args::collect_args_fields;
use crate::derives::{args, into_app, subcommand};
use crate::item::Item;
use crate::item::Name;
pub(crate) fn derive_parser(input: &DeriveInput) -> Result<TokenStream, syn::Error> {
let ident = &input.ident;
let pkg_name = std::env::var("CARGO_PKG_NAME").ok().unwrap_or_default();
let pkg_name_tokens = if pkg_name.is_empty() {
quote!(#pkg_name)
} else {
quote!({
let _ = ::core::env!("CARGO_PKG_NAME");
#pkg_name })
};
match input.data {
Data::Struct(DataStruct {
fields: Fields::Named(ref fields),
..
}) => {
let name = Name::Assigned(pkg_name_tokens.clone());
let item = Item::from_args_struct(input, name)?;
let fields = collect_args_fields(&item, fields)?;
gen_for_struct(&item, ident, &input.generics, &fields)
}
Data::Struct(DataStruct {
fields: Fields::Unit,
..
}) => {
let name = Name::Assigned(pkg_name_tokens.clone());
let item = Item::from_args_struct(input, name)?;
let fields = Punctuated::<Field, Comma>::new();
let fields = fields
.iter()
.map(|field| {
let item = Item::from_args_field(field, item.casing(), item.env_casing())?;
Ok((field, item))
})
.collect::<Result<Vec<_>, syn::Error>>()?;
gen_for_struct(&item, ident, &input.generics, &fields)
}
Data::Enum(ref e) => {
let name = Name::Assigned(pkg_name_tokens);
let item = Item::from_subcommand_enum(input, name)?;
let variants = e
.variants
.iter()
.map(|variant| {
let item =
Item::from_subcommand_variant(variant, item.casing(), item.env_casing())?;
Ok((variant, item))
})
.collect::<Result<Vec<_>, syn::Error>>()?;
gen_for_enum(&item, ident, &input.generics, &variants)
}
_ => abort_call_site!("`#[derive(Parser)]` only supports non-tuple structs and enums"),
}
}
fn gen_for_struct(
item: &Item,
item_name: &Ident,
generics: &Generics,
fields: &[(&Field, Item)],
) -> Result<TokenStream, syn::Error> {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let into_app = into_app::gen_for_struct(item, item_name, generics)?;
let args = args::gen_for_struct(item, item_name, generics, fields)?;
Ok(quote! {
#[automatically_derived]
#[allow(
unused_qualifications,
clippy::redundant_locals,
)]
impl #impl_generics clap::Parser for #item_name #ty_generics #where_clause {}
#into_app
#args
})
}
fn gen_for_enum(
item: &Item,
item_name: &Ident,
generics: &Generics,
variants: &[(&Variant, Item)],
) -> Result<TokenStream, syn::Error> {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let into_app = into_app::gen_for_enum(item, item_name, generics)?;
let subcommand = subcommand::gen_for_enum(item, item_name, generics, variants)?;
Ok(quote! {
#[automatically_derived]
impl #impl_generics clap::Parser for #item_name #ty_generics #where_clause {}
#into_app
#subcommand
})
}

View File

@@ -0,0 +1,686 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote, quote_spanned};
use syn::{Data, DeriveInput, FieldsUnnamed, Generics, Variant, spanned::Spanned};
use crate::derives::args;
use crate::derives::args::collect_args_fields;
use crate::item::{Item, Kind, Name};
use crate::utils::{is_simple_ty, subty_if_name};
pub(crate) fn derive_subcommand(input: &DeriveInput) -> Result<TokenStream, syn::Error> {
let ident = &input.ident;
match input.data {
Data::Enum(ref e) => {
let name = Name::Derived(ident.clone());
let item = Item::from_subcommand_enum(input, name)?;
let variants = e
.variants
.iter()
.map(|variant| {
let item =
Item::from_subcommand_variant(variant, item.casing(), item.env_casing())?;
Ok((variant, item))
})
.collect::<Result<Vec<_>, syn::Error>>()?;
gen_for_enum(&item, ident, &input.generics, &variants)
}
_ => abort_call_site!("`#[derive(Subcommand)]` only supports enums"),
}
}
pub(crate) fn gen_for_enum(
item: &Item,
item_name: &Ident,
generics: &Generics,
variants: &[(&Variant, Item)],
) -> Result<TokenStream, syn::Error> {
if !matches!(&*item.kind(), Kind::Command(_)) {
abort! { item.kind().span(),
"`{}` cannot be used with `#[command]`",
item.kind().name(),
}
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let from_arg_matches = gen_from_arg_matches(variants)?;
let update_from_arg_matches = gen_update_from_arg_matches(variants)?;
let augmentation = gen_augment(variants, item, false)?;
let augmentation_update = gen_augment(variants, item, true)?;
let has_subcommand = gen_has_subcommand(variants)?;
let initial_app_methods = item.initial_top_level_methods();
let final_app_methods = item.final_top_level_methods();
Ok(quote! {
#[allow(
dead_code,
unreachable_code,
unused_variables,
unused_braces,
unused_qualifications,
)]
#[allow(
clippy::style,
clippy::complexity,
clippy::pedantic,
clippy::restriction,
clippy::perf,
clippy::deprecated,
clippy::nursery,
clippy::cargo,
clippy::suspicious_else_formatting,
clippy::almost_swapped,
clippy::redundant_locals,
)]
#[automatically_derived]
impl #impl_generics clap::FromArgMatches for #item_name #ty_generics #where_clause {
fn from_arg_matches(__clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> {
Self::from_arg_matches_mut(&mut __clap_arg_matches.clone())
}
#from_arg_matches
fn update_from_arg_matches(&mut self, __clap_arg_matches: &clap::ArgMatches) -> ::std::result::Result<(), clap::Error> {
self.update_from_arg_matches_mut(&mut __clap_arg_matches.clone())
}
#update_from_arg_matches
}
#[allow(
dead_code,
unreachable_code,
unused_variables,
unused_braces,
unused_qualifications,
)]
#[allow(
clippy::style,
clippy::complexity,
clippy::pedantic,
clippy::restriction,
clippy::perf,
clippy::deprecated,
clippy::nursery,
clippy::cargo,
clippy::suspicious_else_formatting,
clippy::almost_swapped,
clippy::redundant_locals,
)]
#[automatically_derived]
impl #impl_generics clap::Subcommand for #item_name #ty_generics #where_clause {
fn augment_subcommands <'b>(__clap_app: clap::Command) -> clap::Command {
let __clap_app = __clap_app #initial_app_methods;
let __clap_app = #augmentation;
__clap_app #final_app_methods
}
fn augment_subcommands_for_update <'b>(__clap_app: clap::Command) -> clap::Command {
let __clap_app = __clap_app #initial_app_methods;
let __clap_app = #augmentation_update;
__clap_app #final_app_methods
}
fn has_subcommand(__clap_name: &str) -> bool {
#has_subcommand
}
}
})
}
fn gen_augment(
variants: &[(&Variant, Item)],
parent_item: &Item,
override_required: bool,
) -> Result<TokenStream, syn::Error> {
use syn::Fields::{Named, Unit, Unnamed};
let app_var = Ident::new("__clap_app", Span::call_site());
let mut subcommands = Vec::new();
for (variant, item) in variants {
let kind = item.kind();
let genned = match &*kind {
Kind::Skip(_, _) | Kind::Arg(_) | Kind::FromGlobal(_) | Kind::Value => None,
Kind::ExternalSubcommand => {
let ty = match variant.fields {
Unnamed(ref fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty,
_ => abort!(
variant,
"invalid type for `#[command(external_subcommand)]`, expected a newtype variant with either a `Vec<String>` or `Vec<OsString>`"
),
};
let deprecations = if !override_required {
item.deprecations()
} else {
quote!()
};
let subty = subty_if_name(ty, "Vec").ok_or_else(|| {
format_err!(
ty.span(),
"invalid type for `#[command(external_subcommand)]`, expected a `Vec<_>`"
)
})?;
let subcommand = quote_spanned! { kind.span()=>
#deprecations
let #app_var = #app_var
.external_subcommand_value_parser(clap::value_parser!(#subty));
};
Some(subcommand)
}
Kind::Flatten(_) => match variant.fields {
Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
let ty = &unnamed[0].ty;
let deprecations = if !override_required {
item.deprecations()
} else {
quote!()
};
let next_help_heading = item.next_help_heading();
let next_display_order = item.next_display_order();
let subcommand = if override_required {
quote! {
#deprecations
let #app_var = #app_var
#next_help_heading
#next_display_order;
let #app_var = <#ty as clap::Subcommand>::augment_subcommands_for_update(#app_var);
}
} else {
quote! {
#deprecations
let #app_var = #app_var
#next_help_heading
#next_display_order;
let #app_var = <#ty as clap::Subcommand>::augment_subcommands(#app_var);
}
};
Some(subcommand)
}
_ => abort!(
variant,
"invalid variant for `#[command(flatten)]`, expected a newtype variant"
),
},
Kind::Subcommand(_) => {
let subcommand_var = Ident::new("__clap_subcommand", Span::call_site());
let arg_block = match variant.fields {
Named(_) => {
abort!(
variant,
"invalid variant for `#[command(subcommand)]`, expected a newtype variant"
)
}
Unit => quote!( #subcommand_var ),
Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
let ty = &unnamed[0].ty;
if override_required {
quote_spanned! { ty.span()=>
{
<#ty as clap::Subcommand>::augment_subcommands_for_update(#subcommand_var)
}
}
} else {
quote_spanned! { ty.span()=>
{
<#ty as clap::Subcommand>::augment_subcommands(#subcommand_var)
}
}
}
}
Unnamed(..) => {
abort!(
variant,
"invalid variant for `#[command(subcommand)]`, expected a newtype variant"
)
}
};
let name = item.cased_name();
let deprecations = if !override_required {
item.deprecations()
} else {
quote!()
};
let initial_app_methods = item.initial_top_level_methods();
let final_from_attrs = item.final_top_level_methods();
let override_methods = if override_required {
quote_spanned! { kind.span()=>
.subcommand_required(false)
.arg_required_else_help(false)
}
} else {
quote!()
};
let subcommand = quote! {
let #app_var = #app_var.subcommand({
#deprecations;
let #subcommand_var = clap::Command::new(#name);
let #subcommand_var = #subcommand_var
.subcommand_required(true)
.arg_required_else_help(true);
let #subcommand_var = #subcommand_var #initial_app_methods;
let #subcommand_var = #arg_block;
#subcommand_var #final_from_attrs #override_methods
});
};
Some(subcommand)
}
Kind::Command(_) => {
let subcommand_var = Ident::new("__clap_subcommand", Span::call_site());
let (sub_augment, initial_app_methods, final_from_attrs) = match variant.fields {
Named(ref fields) => {
let fields = collect_args_fields(item, fields)?;
let sub_augment =
args::gen_augment(&fields, &subcommand_var, item, override_required)?;
(
sub_augment,
item.initial_top_level_methods(),
item.final_top_level_methods(),
)
}
Unit => (
quote! { #subcommand_var },
item.initial_top_level_methods(),
item.final_top_level_methods(),
),
Unnamed(FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 => {
let ty = &unnamed[0].ty;
let arg_block = if override_required {
quote_spanned! { ty.span()=>
{
<#ty as clap::Args>::augment_args_for_update(#subcommand_var)
}
}
} else {
quote_spanned! { ty.span()=>
{
<#ty as clap::Args>::augment_args(#subcommand_var)
}
}
};
(
arg_block,
item.initial_top_level_methods(),
item.final_top_level_methods(),
)
}
Unnamed(..) => {
abort!(
variant,
"invalid variant for `#[command(subcommand)]`, expected a newtype variant"
)
}
};
let sub_augment = if parent_item.defer() {
quote! {
#subcommand_var.defer(|#subcommand_var| { #sub_augment })
}
} else {
sub_augment
};
let deprecations = if !override_required {
item.deprecations()
} else {
quote!()
};
let name = item.cased_name();
let subcommand = quote! {
let #app_var = #app_var.subcommand({
#deprecations
let #subcommand_var = clap::Command::new(#name)
#initial_app_methods;
let #subcommand_var = #sub_augment;
#subcommand_var #final_from_attrs
});
};
Some(subcommand)
}
};
subcommands.push(genned);
}
let deprecations = if !override_required {
parent_item.deprecations()
} else {
quote!()
};
Ok(quote! {{
#deprecations;
#( #subcommands )*;
#app_var
}})
}
fn gen_has_subcommand(variants: &[(&Variant, Item)]) -> Result<TokenStream, syn::Error> {
use syn::Fields::Unnamed;
let mut ext_subcmd = false;
let (flatten_variants, variants): (Vec<_>, Vec<_>) = variants
.iter()
.filter_map(|(variant, item)| {
let kind = item.kind();
match &*kind {
Kind::Skip(_, _) | Kind::Arg(_) | Kind::FromGlobal(_) | Kind::Value => None,
Kind::ExternalSubcommand => {
ext_subcmd = true;
None
}
Kind::Flatten(_) | Kind::Subcommand(_) | Kind::Command(_) => Some((variant, item)),
}
})
.partition(|(_, item)| {
let kind = item.kind();
matches!(&*kind, Kind::Flatten(_))
});
let subcommands = variants.iter().map(|(_variant, item)| {
let sub_name = item.cased_name();
quote! {
if #sub_name == __clap_name {
return true
}
}
});
let child_subcommands = flatten_variants
.iter()
.map(|(variant, _attrs)| match variant.fields {
Unnamed(ref fields) if fields.unnamed.len() == 1 => {
let ty = &fields.unnamed[0].ty;
Ok(quote! {
if <#ty as clap::Subcommand>::has_subcommand(__clap_name) {
return true;
}
})
}
_ => abort!(
variant,
"invalid variant for `#[command(flatten)]`, expected newtype variant"
),
})
.collect::<Result<Vec<_>, syn::Error>>()?;
let genned = if ext_subcmd {
quote! { true }
} else {
quote! {
#( #subcommands )*
#( #child_subcommands )else*
false
}
};
Ok(genned)
}
fn gen_from_arg_matches(variants: &[(&Variant, Item)]) -> Result<TokenStream, syn::Error> {
use syn::Fields::{Named, Unit, Unnamed};
let subcommand_name_var = format_ident!("__clap_name");
let sub_arg_matches_var = format_ident!("__clap_arg_matches");
let mut ext_subcmd = None;
let mut flatten_variants = Vec::new();
let mut unflatten_variants = Vec::new();
for (variant, item) in variants {
let kind = item.kind();
match &*kind {
Kind::Skip(_, _) | Kind::Arg(_) | Kind::FromGlobal(_) | Kind::Value => {}
Kind::ExternalSubcommand => {
if ext_subcmd.is_some() {
abort!(
item.kind().span(),
"`#[command(external_subcommand)] can only be specified once and has already been specified"
);
}
let ty = match variant.fields {
Unnamed(ref fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty,
_ => abort!(
variant,
"invalid type for `#[command(external_subcommand)]`, expected a newtype variant with either a `Vec<String>` or `Vec<OsString>`"
),
};
let (span, str_ty) = match subty_if_name(ty, "Vec") {
Some(subty) => {
if is_simple_ty(subty, "String") {
(subty.span(), quote!(::std::string::String))
} else if is_simple_ty(subty, "OsString") {
(subty.span(), quote!(::std::ffi::OsString))
} else {
abort!(
ty.span(),
"invalid type for `#[command(external_subcommand)]`, expected a `Vec<String>` or `Vec<OsString>`"
);
}
}
None => abort!(
ty.span(),
"invalid type for `#[command(external_subcommand)]`, expected a `Vec<String>` or `Vec<OsString>`"
),
};
ext_subcmd = Some((span, &variant.ident, str_ty));
}
Kind::Flatten(_) | Kind::Subcommand(_) | Kind::Command(_) => {
if matches!(&*item.kind(), Kind::Flatten(_)) {
flatten_variants.push((variant, item));
} else {
unflatten_variants.push((variant, item));
}
}
}
}
let subcommands = unflatten_variants.iter().map(|(variant, item)| {
let sub_name = item.cased_name();
let variant_name = &variant.ident;
let constructor_block = match variant.fields {
Named(ref fields) => {
let fields = collect_args_fields(item, fields)?;
args::gen_constructor(&fields)?
},
Unit => quote!(),
Unnamed(ref fields) if fields.unnamed.len() == 1 => {
let ty = &fields.unnamed[0].ty;
quote!( ( <#ty as clap::FromArgMatches>::from_arg_matches_mut(__clap_arg_matches)? ) )
}
Unnamed(..) => abort_call_site!("{}: tuple enums are not supported", variant.ident),
};
Ok(quote! {
if #subcommand_name_var == #sub_name && !#sub_arg_matches_var.contains_id("") {
return ::std::result::Result::Ok(Self :: #variant_name #constructor_block)
}
})
}).collect::<Result<Vec<_>, syn::Error>>()?;
let child_subcommands = flatten_variants.iter().map(|(variant, _attrs)| {
let variant_name = &variant.ident;
match variant.fields {
Unnamed(ref fields) if fields.unnamed.len() == 1 => {
let ty = &fields.unnamed[0].ty;
Ok(quote! {
if __clap_arg_matches
.subcommand_name()
.map(|__clap_name| <#ty as clap::Subcommand>::has_subcommand(__clap_name))
.unwrap_or_default()
{
let __clap_res = <#ty as clap::FromArgMatches>::from_arg_matches_mut(__clap_arg_matches)?;
return ::std::result::Result::Ok(Self :: #variant_name (__clap_res));
}
})
}
_ => abort!(
variant,
"invalid variant for `#[command(flatten)]`, expected newtype variant"
),
}
}).collect::<Result<Vec<_>, syn::Error>>()?;
let wildcard = match ext_subcmd {
Some((span, var_name, str_ty)) => quote_spanned! { span=>
::std::result::Result::Ok(Self::#var_name(
::std::iter::once(#str_ty::from(#subcommand_name_var))
.chain(
#sub_arg_matches_var
.remove_many::<#str_ty>("")
.unwrap()
.map(#str_ty::from)
)
.collect::<::std::vec::Vec<_>>()
))
},
None => quote! {
::std::result::Result::Err(clap::Error::raw(clap::error::ErrorKind::InvalidSubcommand, format!("the subcommand '{}' wasn't recognized", #subcommand_name_var)))
},
};
let raw_deprecated = args::raw_deprecated();
Ok(quote! {
fn from_arg_matches_mut(__clap_arg_matches: &mut clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> {
#raw_deprecated
#( #child_subcommands )else*
if let Some((#subcommand_name_var, mut __clap_arg_sub_matches)) = __clap_arg_matches.remove_subcommand() {
let #sub_arg_matches_var = &mut __clap_arg_sub_matches;
#( #subcommands )*
#wildcard
} else {
::std::result::Result::Err(clap::Error::raw(clap::error::ErrorKind::MissingSubcommand, "a subcommand is required but one was not provided"))
}
}
})
}
fn gen_update_from_arg_matches(variants: &[(&Variant, Item)]) -> Result<TokenStream, syn::Error> {
use syn::Fields::{Named, Unit, Unnamed};
let (flatten, variants): (Vec<_>, Vec<_>) = variants
.iter()
.filter_map(|(variant, item)| {
let kind = item.kind();
match &*kind {
// Fallback to `from_arg_matches_mut`
Kind::Skip(_, _)
| Kind::Arg(_)
| Kind::FromGlobal(_)
| Kind::Value
| Kind::ExternalSubcommand => None,
Kind::Flatten(_) | Kind::Subcommand(_) | Kind::Command(_) => Some((variant, item)),
}
})
.partition(|(_, item)| {
let kind = item.kind();
matches!(&*kind, Kind::Flatten(_))
});
let subcommands = variants.iter().map(|(variant, item)| {
let sub_name = item.cased_name();
let variant_name = &variant.ident;
let (pattern, updater) = match variant.fields {
Named(ref fields) => {
let field_names = fields.named.iter().map(|field| {
field.ident.as_ref().unwrap()
}).collect::<Vec<_>>();
let fields = collect_args_fields(item, fields)?;
let update = args::gen_updater(&fields, false)?;
(quote!( { #( #field_names, )* }), quote!( { #update } ))
}
Unit => (quote!(), quote!({})),
Unnamed(ref fields) => {
if fields.unnamed.len() == 1 {
(
quote!((__clap_arg)),
quote!(clap::FromArgMatches::update_from_arg_matches_mut(
__clap_arg,
__clap_arg_matches
)?),
)
} else {
abort_call_site!("{}: tuple enums are not supported", variant.ident)
}
}
};
Ok(quote! {
Self :: #variant_name #pattern if #sub_name == __clap_name => {
let (_, mut __clap_arg_sub_matches) = __clap_arg_matches.remove_subcommand().unwrap();
let __clap_arg_matches = &mut __clap_arg_sub_matches;
#updater
}
})
}).collect::<Result<Vec<_>, _>>()?;
let child_subcommands = flatten.iter().map(|(variant, _attrs)| {
let variant_name = &variant.ident;
match variant.fields {
Unnamed(ref fields) if fields.unnamed.len() == 1 => {
let ty = &fields.unnamed[0].ty;
Ok(quote! {
if <#ty as clap::Subcommand>::has_subcommand(__clap_name) {
if let Self :: #variant_name (child) = s {
<#ty as clap::FromArgMatches>::update_from_arg_matches_mut(child, __clap_arg_matches)?;
return ::std::result::Result::Ok(());
}
}
})
}
_ => abort!(
variant,
"invalid variant for `#[command(flatten)]`, expected newtype variant"
),
}
}).collect::<Result<Vec<_>, _>>()?;
let raw_deprecated = args::raw_deprecated();
Ok(quote! {
fn update_from_arg_matches_mut<'b>(
&mut self,
__clap_arg_matches: &mut clap::ArgMatches,
) -> ::std::result::Result<(), clap::Error> {
#raw_deprecated
if let Some(__clap_name) = __clap_arg_matches.subcommand_name() {
match self {
#( #subcommands ),*
s => {
#( #child_subcommands )*
*s = <Self as clap::FromArgMatches>::from_arg_matches_mut(__clap_arg_matches)?;
}
}
}
::std::result::Result::Ok(())
}
})
}

View File

@@ -0,0 +1,133 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use proc_macro2::TokenStream;
use quote::quote;
use quote::quote_spanned;
use syn::{Data, DeriveInput, Fields, Ident, Variant, spanned::Spanned};
use crate::item::{Item, Kind, Name};
pub(crate) fn derive_value_enum(input: &DeriveInput) -> Result<TokenStream, syn::Error> {
let ident = &input.ident;
match input.data {
Data::Enum(ref e) => {
let name = Name::Derived(ident.clone());
let item = Item::from_value_enum(input, name)?;
let mut variants = Vec::new();
for variant in &e.variants {
let item =
Item::from_value_enum_variant(variant, item.casing(), item.env_casing())?;
variants.push((variant, item));
}
gen_for_enum(&item, ident, &variants)
}
_ => abort_call_site!("`#[derive(ValueEnum)]` only supports enums"),
}
}
pub(crate) fn gen_for_enum(
item: &Item,
item_name: &Ident,
variants: &[(&Variant, Item)],
) -> Result<TokenStream, syn::Error> {
if !matches!(&*item.kind(), Kind::Value) {
abort! { item.kind().span(),
"`{}` cannot be used with `#[value]`",
item.kind().name(),
}
}
let lits = lits(variants)?;
let value_variants = gen_value_variants(&lits);
let to_possible_value = gen_to_possible_value(item, &lits);
Ok(quote! {
#[allow(
dead_code,
unreachable_code,
unused_variables,
unused_braces,
unused_qualifications,
)]
#[allow(
clippy::style,
clippy::complexity,
clippy::pedantic,
clippy::restriction,
clippy::perf,
clippy::deprecated,
clippy::nursery,
clippy::cargo,
clippy::suspicious_else_formatting,
clippy::almost_swapped,
clippy::redundant_locals,
)]
#[automatically_derived]
impl clap::ValueEnum for #item_name {
#value_variants
#to_possible_value
}
})
}
fn lits(variants: &[(&Variant, Item)]) -> Result<Vec<(TokenStream, Ident)>, syn::Error> {
let mut genned = Vec::new();
for (variant, item) in variants {
if let Kind::Skip(_, _) = &*item.kind() {
continue;
}
if !matches!(variant.fields, Fields::Unit) {
abort!(
variant.span(),
"`#[derive(ValueEnum)]` only supports unit variants. Non-unit variants must be skipped"
);
}
let fields = item.field_methods();
let deprecations = item.deprecations();
let name = item.cased_name();
genned.push((
quote_spanned! { variant.span()=> {
#deprecations
clap::builder::PossibleValue::new(#name)
#fields
}},
variant.ident.clone(),
));
}
Ok(genned)
}
fn gen_value_variants(lits: &[(TokenStream, Ident)]) -> TokenStream {
let lit = lits.iter().map(|l| &l.1).collect::<Vec<_>>();
quote! {
fn value_variants<'a>() -> &'a [Self]{
&[#(Self::#lit),*]
}
}
}
fn gen_to_possible_value(item: &Item, lits: &[(TokenStream, Ident)]) -> TokenStream {
let (lit, variant): (Vec<TokenStream>, Vec<Ident>) = lits.iter().cloned().unzip();
let deprecations = item.deprecations();
quote! {
fn to_possible_value<'a>(&self) -> ::std::option::Option<clap::builder::PossibleValue> {
#deprecations
match self {
#(Self::#variant => Some(#lit),)*
_ => None
}
}
}
}

View File

@@ -0,0 +1,99 @@
//! Dummy implementations that we emit along with an error.
use proc_macro2::Ident;
use quote::quote;
#[must_use]
pub(crate) fn parser(name: &Ident) -> proc_macro2::TokenStream {
let into_app = into_app(name);
quote!(
#[automatically_derived]
impl clap::Parser for #name {}
#into_app
)
}
#[must_use]
pub(crate) fn into_app(name: &Ident) -> proc_macro2::TokenStream {
quote! {
#[automatically_derived]
impl clap::CommandFactory for #name {
fn command<'b>() -> clap::Command {
unimplemented!()
}
fn command_for_update<'b>() -> clap::Command {
unimplemented!()
}
}
}
}
#[must_use]
pub(crate) fn from_arg_matches(name: &Ident) -> proc_macro2::TokenStream {
quote! {
#[automatically_derived]
impl clap::FromArgMatches for #name {
fn from_arg_matches(_m: &clap::ArgMatches) -> ::std::result::Result<Self, clap::Error> {
unimplemented!()
}
fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> ::std::result::Result<(), clap::Error>{
unimplemented!()
}
}
}
}
#[must_use]
pub(crate) fn subcommand(name: &Ident) -> proc_macro2::TokenStream {
let from_arg_matches = from_arg_matches(name);
quote! {
#[automatically_derived]
impl clap::Subcommand for #name {
fn augment_subcommands(_cmd: clap::Command) -> clap::Command {
unimplemented!()
}
fn augment_subcommands_for_update(_cmd: clap::Command) -> clap::Command {
unimplemented!()
}
fn has_subcommand(name: &str) -> bool {
unimplemented!()
}
}
#from_arg_matches
}
}
#[must_use]
pub(crate) fn args(name: &Ident) -> proc_macro2::TokenStream {
let from_arg_matches = from_arg_matches(name);
quote! {
#[automatically_derived]
impl clap::Args for #name {
fn augment_args(_cmd: clap::Command) -> clap::Command {
unimplemented!()
}
fn augment_args_for_update(_cmd: clap::Command) -> clap::Command {
unimplemented!()
}
}
#from_arg_matches
}
}
#[must_use]
pub(crate) fn value_enum(name: &Ident) -> proc_macro2::TokenStream {
quote! {
#[automatically_derived]
impl clap::ValueEnum for #name {
fn value_variants<'a>() -> &'a [Self]{
unimplemented!()
}
fn from_str(_input: &str, _ignore_case: bool) -> ::std::result::Result<Self, String> {
unimplemented!()
}
fn to_possible_value<'a>(&self) -> ::std::option::Option<clap::builder::PossibleValue>{
unimplemented!()
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,118 @@
// Copyright 2018 Guillaume Pinot (@TeXitoi) <texitoi@texitoi.eu>,
// Kevin Knapp (@kbknapp) <kbknapp@gmail.com>, and
// Ana Hobden (@hoverbear) <operator@hoverbear.org>
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// This work was derived from Structopt (https://github.com/TeXitoi/structopt)
// commit#ea76fa1b1b273e65e3b0b1046643715b49bec51f which is licensed under the
// MIT/Apache 2.0 license.
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://raw.githubusercontent.com/clap-rs/clap/master/assets/clap.png")]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![warn(clippy::print_stderr)]
#![warn(clippy::print_stdout)]
use proc_macro::TokenStream;
use syn::{Data, DataStruct, Fields};
use syn::{DeriveInput, parse_macro_input};
#[macro_use]
mod macros;
mod attr;
mod derives;
mod dummies;
mod item;
mod utils;
/// Generates the `ValueEnum` impl.
#[proc_macro_derive(ValueEnum, attributes(clap, value))]
pub fn value_enum(input: TokenStream) -> TokenStream {
let input: DeriveInput = parse_macro_input!(input);
derives::derive_value_enum(&input)
.unwrap_or_else(|err| {
let dummy = dummies::value_enum(&input.ident);
to_compile_error(err, dummy)
})
.into()
}
/// Generates the `Parser` implementation.
///
/// This is far less verbose than defining the `clap::Command` struct manually,
/// receiving an instance of `clap::ArgMatches` from conducting parsing, and then
/// implementing a conversion code to instantiate an instance of the user
/// context struct.
#[proc_macro_derive(Parser, attributes(clap, structopt, command, arg, group))]
pub fn parser(input: TokenStream) -> TokenStream {
let input: DeriveInput = parse_macro_input!(input);
derives::derive_parser(&input)
.unwrap_or_else(|err| {
let specific_dummy = match input.data {
Data::Struct(DataStruct {
fields: Fields::Named(ref _fields),
..
}) => Some(dummies::args(&input.ident)),
Data::Struct(DataStruct {
fields: Fields::Unit,
..
}) => Some(dummies::args(&input.ident)),
Data::Enum(_) => Some(dummies::subcommand(&input.ident)),
_ => None,
};
let dummy = specific_dummy
.map(|specific_dummy| {
let parser_dummy = dummies::parser(&input.ident);
quote::quote! {
#parser_dummy
#specific_dummy
}
})
.unwrap_or_else(|| quote::quote!());
to_compile_error(err, dummy)
})
.into()
}
/// Generates the `Subcommand` impl.
#[proc_macro_derive(Subcommand, attributes(clap, command, arg, group))]
pub fn subcommand(input: TokenStream) -> TokenStream {
let input: DeriveInput = parse_macro_input!(input);
derives::derive_subcommand(&input)
.unwrap_or_else(|err| {
let dummy = dummies::subcommand(&input.ident);
to_compile_error(err, dummy)
})
.into()
}
/// Generates the `Args` impl.
#[proc_macro_derive(Args, attributes(clap, command, arg, group))]
pub fn args(input: TokenStream) -> TokenStream {
let input: DeriveInput = parse_macro_input!(input);
derives::derive_args(&input)
.unwrap_or_else(|err| {
let dummy = dummies::args(&input.ident);
to_compile_error(err, dummy)
})
.into()
}
fn to_compile_error(
error: syn::Error,
dummy: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let compile_errors = error.to_compile_error();
quote::quote!(
#dummy
#compile_errors
)
}

View File

@@ -0,0 +1,21 @@
macro_rules! format_err {
($obj:expr, $($format:tt)+) => {{
#[allow(unused_imports)]
use $crate::utils::error::*;
let msg = format!($($format)+);
$obj.EXPECTED_Span_OR_ToTokens(msg)
}};
}
macro_rules! abort {
($obj:expr, $($format:tt)+) => {{
return Err(format_err!($obj, $($format)+));
}};
}
macro_rules! abort_call_site {
($($format:tt)+) => {{
let span = proc_macro2::Span::call_site();
abort!(span, $($format)+)
}};
}

View File

@@ -0,0 +1,410 @@
//! The preprocessing we apply to doc comments.
//!
//! #[derive(Parser)] works in terms of "paragraphs". Paragraph is a sequence of
//! non-empty adjacent lines, delimited by sequences of blank (whitespace only) lines.
#[cfg(feature = "unstable-markdown")]
use markdown::parse_markdown;
pub(crate) fn extract_doc_comment(attrs: &[syn::Attribute]) -> Vec<String> {
// multiline comments (`/** ... */`) may have LFs (`\n`) in them,
// we need to split so we could handle the lines correctly
//
// we also need to remove leading and trailing blank lines
let mut lines: Vec<_> = attrs
.iter()
.filter(|attr| attr.path().is_ident("doc"))
.filter_map(|attr| {
// non #[doc = "..."] attributes are not our concern
// we leave them for rustc to handle
match &attr.meta {
syn::Meta::NameValue(syn::MetaNameValue {
value:
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(s),
..
}),
..
}) => Some(s.value()),
_ => None,
}
})
.skip_while(|s| is_blank(s))
.flat_map(|s| {
let lines = s
.split('\n')
.map(|s| {
// remove one leading space no matter what
let s = s.strip_prefix(' ').unwrap_or(s);
s.to_owned()
})
.collect::<Vec<_>>();
lines
})
.collect();
while let Some(true) = lines.last().map(|s| is_blank(s)) {
lines.pop();
}
lines
}
pub(crate) fn format_doc_comment(
lines: &[String],
preprocess: bool,
force_long: bool,
) -> (Option<String>, Option<String>) {
if preprocess {
let (short, long) = parse_markdown(lines);
let long = long.or_else(|| force_long.then(|| short.clone()));
(Some(remove_period(short)), long)
} else if let Some(first_blank) = lines.iter().position(|s| is_blank(s)) {
let short = lines[..first_blank].join("\n");
let long = lines.join("\n");
(Some(short), Some(long))
} else {
let short = lines.join("\n");
let long = force_long.then(|| short.clone());
(Some(short), long)
}
}
#[cfg(not(feature = "unstable-markdown"))]
fn split_paragraphs(lines: &[String]) -> Vec<String> {
use std::iter;
let mut last_line = 0;
iter::from_fn(|| {
let slice = &lines[last_line..];
let start = slice.iter().position(|s| !is_blank(s)).unwrap_or(0);
let slice = &slice[start..];
let len = slice
.iter()
.position(|s| is_blank(s))
.unwrap_or(slice.len());
last_line += start + len;
if len != 0 {
Some(merge_lines(&slice[..len]))
} else {
None
}
})
.collect()
}
fn remove_period(mut s: String) -> String {
if s.ends_with('.') && !s.ends_with("..") {
s.pop();
}
s
}
fn is_blank(s: &str) -> bool {
s.trim().is_empty()
}
#[cfg(not(feature = "unstable-markdown"))]
fn merge_lines(lines: impl IntoIterator<Item = impl AsRef<str>>) -> String {
lines
.into_iter()
.map(|s| s.as_ref().trim().to_owned())
.collect::<Vec<_>>()
.join(" ")
}
#[cfg(not(feature = "unstable-markdown"))]
fn parse_markdown(lines: &[String]) -> (String, Option<String>) {
if lines.iter().any(|s| is_blank(s)) {
let paragraphs = split_paragraphs(lines);
let short = paragraphs[0].clone();
let long = paragraphs.join("\n\n");
(short, Some(long))
} else {
let short = merge_lines(lines);
(short, None)
}
}
#[cfg(feature = "unstable-markdown")]
mod markdown {
use anstyle::{Reset, Style};
use pulldown_cmark::{Event, Options, Parser, Tag, TagEnd};
use std::fmt;
use std::fmt::Write;
use std::ops::AddAssign;
#[derive(Default)]
struct MarkdownWriter {
output: String,
/// Prefix inserted for each line.
prefix: String,
/// Should an empty line be inserted before the next anything.
hanging_paragraph: bool,
/// Are we in an empty line
dirty_line: bool,
styles: Vec<Style>,
}
impl MarkdownWriter {
fn newline(&mut self) {
self.reset();
self.output.push('\n');
self.dirty_line = false;
}
fn endline(&mut self) {
if self.dirty_line {
self.newline();
}
}
fn new_paragraph(&mut self) {
self.endline();
self.hanging_paragraph = true;
}
fn write_fmt(&mut self, arguments: fmt::Arguments<'_>) {
if self.hanging_paragraph {
self.hanging_paragraph = false;
self.newline();
}
if !self.dirty_line {
self.output.push_str(&self.prefix);
self.apply_styles();
self.dirty_line = true;
}
self.output.write_fmt(arguments).unwrap();
}
fn start_link(&mut self, dest_url: pulldown_cmark::CowStr<'_>) {
write!(self, "\x1B]8;;{dest_url}\x1B\\");
}
fn end_link(&mut self) {
write!(self, "\x1B]8;;\x1B\\");
}
fn start_style(&mut self, style: Style) {
self.styles.push(style);
write!(self, "{style}");
}
fn end_style(&mut self, style: Style) {
let last_style = self.styles.pop();
debug_assert_eq!(last_style.unwrap(), style);
write!(self, "{Reset}");
self.apply_styles();
}
fn reset(&mut self) {
write!(self, "{Reset}");
}
fn apply_styles(&mut self) {
// Reapplying all, because anstyle doesn't support merging styles
// (probably because the ambiguity around colors)
// TODO If we decide not to support any colors, we can replace this with
// anstyle::Effects and remove the need for applying them all individually.
for style in &self.styles {
write!(self.output, "{style}").unwrap();
}
}
fn remove_prefix(&mut self, quote_prefix: &str) {
debug_assert!(self.prefix.ends_with(quote_prefix));
let new_len = self.prefix.len() - quote_prefix.len();
self.prefix.truncate(new_len);
}
fn add_prefix(&mut self, quote_prefix: &str) {
if self.hanging_paragraph {
self.hanging_paragraph = false;
self.newline();
}
self.prefix += quote_prefix;
}
}
pub(super) fn parse_markdown(input: &[String]) -> (String, Option<String>) {
// Markdown Configuration
let parsing_options = Options::ENABLE_STRIKETHROUGH;
// Minimal Styling for now, because we cannot configure it
let style_heading = Style::new().bold().underline();
let style_emphasis = Style::new().italic();
let style_strong = Style::new().bold();
let style_strike_through = Style::new().strikethrough();
let style_link = Style::new().underline();
let style_code = Style::new().bold();
let list_symbol = '-';
let quote_prefix = "| ";
let indentation = " ";
let input = input.join("\n");
let input = Parser::new_ext(&input, parsing_options);
let mut short = None;
let mut has_details = false;
let mut writer = MarkdownWriter::default();
let mut list_indices = Vec::new();
for event in input {
if short.is_some() {
has_details = true;
}
match event {
Event::Start(Tag::Paragraph) => { /* nothing to do */ }
Event::End(TagEnd::Paragraph) => {
if short.is_none() {
short = Some(writer.output.trim().to_owned());
}
writer.new_paragraph();
}
Event::Start(Tag::Heading { .. }) => writer.start_style(style_heading),
Event::End(TagEnd::Heading(..)) => {
writer.end_style(style_heading);
writer.new_paragraph();
}
Event::Start(Tag::Image { .. } | Tag::HtmlBlock) => { /* IGNORED */ }
Event::End(TagEnd::Image) => { /* IGNORED */ }
Event::End(TagEnd::HtmlBlock) => writer.new_paragraph(),
Event::Start(Tag::BlockQuote(_)) => writer.add_prefix(quote_prefix),
Event::End(TagEnd::BlockQuote(_)) => {
writer.remove_prefix(quote_prefix);
writer.new_paragraph();
}
Event::Start(Tag::CodeBlock(_)) => {
writer.add_prefix(indentation);
writer.start_style(style_code);
}
Event::End(TagEnd::CodeBlock) => {
writer.remove_prefix(indentation);
writer.end_style(style_code);
writer.dirty_line = false;
writer.hanging_paragraph = true;
}
Event::Start(Tag::List(list_start)) => {
list_indices.push(list_start);
writer.endline();
}
Event::End(TagEnd::List(_)) => {
let list = list_indices.pop();
debug_assert!(list.is_some());
if list_indices.is_empty() {
writer.new_paragraph();
}
}
Event::Start(Tag::Item) => {
if let Some(Some(index)) = list_indices.last_mut() {
write!(writer, "{index}. ");
index.add_assign(1);
} else {
write!(writer, "{list_symbol} ");
}
writer.add_prefix(indentation);
}
Event::End(TagEnd::Item) => {
writer.remove_prefix(indentation);
writer.endline();
}
Event::Start(Tag::Emphasis) => writer.start_style(style_emphasis),
Event::End(TagEnd::Emphasis) => writer.end_style(style_emphasis),
Event::Start(Tag::Strong) => writer.start_style(style_strong),
Event::End(TagEnd::Strong) => writer.end_style(style_strong),
Event::Start(Tag::Strikethrough) => writer.start_style(style_strike_through),
Event::End(TagEnd::Strikethrough) => writer.end_style(style_strike_through),
Event::Start(Tag::Link { dest_url, .. }) => {
writer.start_link(dest_url);
writer.start_style(style_link);
}
Event::End(TagEnd::Link) => {
writer.end_link();
writer.end_style(style_link);
}
Event::Text(segment) => {
// split into lines to support code blocks
let mut lines = segment.lines();
// `.lines()` always returns at least one
write!(writer, "{}", lines.next().unwrap());
for line in lines {
writer.endline();
write!(writer, "{line}");
}
if segment.ends_with('\n') {
writer.endline();
}
}
Event::Code(code) => {
writer.start_style(style_code);
write!(writer, "{code}");
writer.end_style(style_code);
}
// There is not really anything useful to do with block level html.
Event::Html(html) => write!(writer, "{html}"),
// At some point we could support custom tags like `<red>`
Event::InlineHtml(html) => write!(writer, "{html}"),
Event::SoftBreak => write!(writer, " "),
Event::HardBreak => writer.endline(),
Event::Rule => {
writer.new_paragraph();
write!(writer, "---");
writer.new_paragraph();
}
// Markdown features currently not supported
Event::Start(
Tag::FootnoteDefinition(_)
| Tag::DefinitionList
| Tag::DefinitionListTitle
| Tag::DefinitionListDefinition
| Tag::Table(_)
| Tag::TableHead
| Tag::TableRow
| Tag::TableCell
| Tag::MetadataBlock(_)
| Tag::Superscript
| Tag::Subscript,
)
| Event::End(
TagEnd::FootnoteDefinition
| TagEnd::DefinitionList
| TagEnd::DefinitionListTitle
| TagEnd::DefinitionListDefinition
| TagEnd::Table
| TagEnd::TableHead
| TagEnd::TableRow
| TagEnd::TableCell
| TagEnd::MetadataBlock(_)
| TagEnd::Superscript
| TagEnd::Subscript,
)
| Event::InlineMath(_)
| Event::DisplayMath(_)
| Event::FootnoteReference(_)
| Event::TaskListMarker(_) => {
unimplemented!("feature not enabled {event:?}")
}
}
}
let short = short.unwrap_or_else(|| writer.output.trim_end().to_owned());
let long = writer.output.trim_end();
let long = has_details.then(|| long.to_owned());
(short, long)
}
}

View File

@@ -0,0 +1,22 @@
pub(crate) trait SpanError {
#[allow(non_snake_case)]
fn EXPECTED_Span_OR_ToTokens<D: std::fmt::Display>(&self, msg: D) -> syn::Error;
}
pub(crate) trait ToTokensError {
#[allow(non_snake_case)]
fn EXPECTED_Span_OR_ToTokens<D: std::fmt::Display>(&self, msg: D) -> syn::Error;
}
impl<T: quote::ToTokens> ToTokensError for T {
fn EXPECTED_Span_OR_ToTokens<D: std::fmt::Display>(&self, msg: D) -> syn::Error {
// Curb monomorphization from generating too many identical `new_spanned`.
syn::Error::new_spanned(self.to_token_stream(), msg)
}
}
impl SpanError for proc_macro2::Span {
fn EXPECTED_Span_OR_ToTokens<D: std::fmt::Display>(&self, msg: D) -> syn::Error {
syn::Error::new(*self, msg)
}
}

View File

@@ -0,0 +1,13 @@
pub(crate) mod error;
mod doc_comments;
mod spanned;
mod ty;
pub(crate) use doc_comments::extract_doc_comment;
pub(crate) use doc_comments::format_doc_comment;
pub(crate) use self::{
spanned::Sp,
ty::{Ty, inner_type, is_simple_ty, sub_type, subty_if_name},
};

View File

@@ -0,0 +1,89 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::ToTokens;
use syn::LitStr;
use std::ops::{Deref, DerefMut};
/// An entity with a span attached.
#[derive(Debug, Copy, Clone)]
pub(crate) struct Sp<T> {
val: T,
span: Span,
}
impl<T> Sp<T> {
pub(crate) fn new(val: T, span: Span) -> Self {
Self { val, span }
}
pub(crate) fn get(&self) -> &T {
&self.val
}
pub(crate) fn span(&self) -> Span {
self.span
}
}
impl<T> Deref for Sp<T> {
type Target = T;
fn deref(&self) -> &T {
&self.val
}
}
impl<T> DerefMut for Sp<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.val
}
}
impl From<Ident> for Sp<String> {
fn from(ident: Ident) -> Self {
Self {
val: ident.to_string(),
span: ident.span(),
}
}
}
impl From<LitStr> for Sp<String> {
fn from(lit: LitStr) -> Self {
Self {
val: lit.value(),
span: lit.span(),
}
}
}
impl<'a> From<Sp<&'a str>> for Sp<String> {
fn from(sp: Sp<&'a str>) -> Self {
Self::new(sp.val.into(), sp.span)
}
}
impl<U, T: PartialEq<U>> PartialEq<U> for Sp<T> {
fn eq(&self, other: &U) -> bool {
self.val == *other
}
}
impl<T: AsRef<str>> AsRef<str> for Sp<T> {
fn as_ref(&self) -> &str {
self.val.as_ref()
}
}
impl<T: ToTokens> ToTokens for Sp<T> {
fn to_tokens(&self, stream: &mut TokenStream) {
// this is the simplest way out of correct ones to change span on
// arbitrary token tree I could come up with
let tt = self.val.to_token_stream().into_iter().map(|mut tt| {
tt.set_span(self.span);
tt
});
stream.extend(tt);
}
}

View File

@@ -0,0 +1,166 @@
//! Special types handling
use super::spanned::Sp;
use syn::{
GenericArgument, Path, PathArguments, PathArguments::AngleBracketed, PathSegment, Type,
TypePath, spanned::Spanned,
};
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub(crate) enum Ty {
Unit,
Vec,
VecVec,
Option,
OptionOption,
OptionVec,
OptionVecVec,
Other,
}
impl Ty {
pub(crate) fn from_syn_ty(ty: &Type) -> Sp<Self> {
use self::Ty::{Option, OptionOption, OptionVec, OptionVecVec, Other, Unit, Vec, VecVec};
let t = |kind| Sp::new(kind, ty.span());
if is_unit_ty(ty) {
t(Unit)
} else if let Some(vt) = get_vec_ty(ty, Vec, VecVec) {
t(vt)
} else if let Some(subty) = subty_if_name(ty, "Option") {
if is_generic_ty(subty, "Option") {
t(OptionOption)
} else if let Some(vt) = get_vec_ty(subty, OptionVec, OptionVecVec) {
t(vt)
} else {
t(Option)
}
} else {
t(Other)
}
}
pub(crate) fn as_str(&self) -> &'static str {
match self {
Self::Unit => "()",
Self::Vec => "Vec<T>",
Self::Option => "Option<T>",
Self::OptionOption => "Option<Option<T>>",
Self::OptionVec => "Option<Vec<T>>",
Self::VecVec => "Vec<Vec<T>>",
Self::OptionVecVec => "Option<Vec<Vec<T>>>",
Self::Other => "...other...",
}
}
}
pub(crate) fn inner_type(field_ty: &Type) -> &Type {
let ty = Ty::from_syn_ty(field_ty);
match *ty {
Ty::Vec | Ty::Option => sub_type(field_ty).unwrap_or(field_ty),
Ty::OptionOption | Ty::OptionVec | Ty::VecVec => {
sub_type(field_ty).and_then(sub_type).unwrap_or(field_ty)
}
Ty::OptionVecVec => sub_type(field_ty)
.and_then(sub_type)
.and_then(sub_type)
.unwrap_or(field_ty),
_ => field_ty,
}
}
pub(crate) fn sub_type(ty: &Type) -> Option<&Type> {
subty_if(ty, |_| true)
}
fn only_last_segment(mut ty: &Type) -> Option<&PathSegment> {
while let Type::Group(syn::TypeGroup { elem, .. }) = ty {
ty = elem;
}
match ty {
Type::Path(TypePath {
qself: None,
path:
Path {
leading_colon: None,
segments,
},
attrs: _,
}) => only_one(segments.iter()),
_ => None,
}
}
fn subty_if<F>(ty: &Type, f: F) -> Option<&Type>
where
F: FnOnce(&PathSegment) -> bool,
{
only_last_segment(ty)
.filter(|segment| f(segment))
.and_then(|segment| {
if let AngleBracketed(args) = &segment.arguments {
only_one(args.args.iter()).and_then(|genneric| {
if let GenericArgument::Type(ty) = genneric {
Some(ty)
} else {
None
}
})
} else {
None
}
})
}
pub(crate) fn subty_if_name<'a>(ty: &'a Type, name: &str) -> Option<&'a Type> {
subty_if(ty, |seg| seg.ident == name)
}
pub(crate) fn is_simple_ty(ty: &Type, name: &str) -> bool {
only_last_segment(ty)
.map(|segment| {
if let PathArguments::None = segment.arguments {
segment.ident == name
} else {
false
}
})
.unwrap_or(false)
}
fn is_generic_ty(ty: &Type, name: &str) -> bool {
subty_if_name(ty, name).is_some()
}
fn is_unit_ty(ty: &Type) -> bool {
if let Type::Tuple(tuple) = ty {
tuple.elems.is_empty()
} else {
false
}
}
fn only_one<I, T>(mut iter: I) -> Option<T>
where
I: Iterator<Item = T>,
{
iter.next().filter(|_| iter.next().is_none())
}
#[cfg(feature = "unstable-v5")]
fn get_vec_ty(ty: &Type, vec_ty: Ty, vecvec_ty: Ty) -> Option<Ty> {
subty_if_name(ty, "Vec").map(|subty| {
if is_generic_ty(subty, "Vec") {
vecvec_ty
} else {
vec_ty
}
})
}
#[cfg(not(feature = "unstable-v5"))]
fn get_vec_ty(ty: &Type, vec_ty: Ty, _vecvec_ty: Ty) -> Option<Ty> {
is_generic_ty(ty, "Vec").then_some(vec_ty)
}