use proc_macro2::{Literal, TokenStream as TokenStream2};
use quote::{format_ident, quote};
use syn::{Ident, Lifetime, Token, Variant, punctuated::Punctuated};
use crate::{
common::{
ParsedFieldInfo,
enumeration::subcommand::{
Fallback, ParsedSubcommandInfo, ParsedSubcommandVariant, SubcommandVariantMode,
SubcommandVariantNormalizedMode,
},
},
from_args::common::{
complete_long_arg_body, complete_long_body, complete_short_body, final_field_initializers,
get_subcommand_field_visitor_calls, struct_state_block, struct_state_block_from_fields,
struct_state_init_block_from_field_count, visit_positional_arms_for_fields,
},
generics::AngleBracedLifetime,
};
fn make_variant_arm<const N: usize>(
variant: &ParsedSubcommandVariant<'_>,
state_method: &Ident,
fields_ident: &Ident,
argument_idents: [&Ident; N],
bind: TokenStream2,
make_body: impl FnOnce(&[ParsedFieldInfo<'_>]) -> TokenStream2,
) -> TokenStream2 {
let variant_ident = variant.ident.raw();
let body = match variant.mode.normalized() {
SubcommandVariantNormalizedMode::Fields(fields) => make_body(fields),
SubcommandVariantNormalizedMode::Newtype(_) => quote! {
::debate::state::State::#state_method(&mut #fields_ident.0, #(#argument_idents,)*)
},
};
quote! {
Self :: #variant_ident { #bind } => #body
}
}
pub fn derive_args_enum_subcommand(
name: &Ident,
variants: &Punctuated<Variant, Token![,]>,
lifetime: &Lifetime,
type_lifetime: Option<&AngleBracedLifetime>,
) -> syn::Result<TokenStream2> {
let arg_ident = format_ident!("arg");
let add_arg_ident = format_ident!("add_arg");
let fields_ident = format_ident!("fields");
let argument = format_ident!("argument");
let option = format_ident!("option");
let visitor = format_ident!("visitor");
let add_positional_ident = format_ident!("add_positional");
let add_long_argument_ident = format_ident!("add_long_argument");
let add_long_ident = format_ident!("add_long");
let add_short_ident = format_ident!("add_short");
let parsed_variants = ParsedSubcommandInfo::from_variants(variants)?;
let fallback_ident = parsed_variants.fallback.ident();
let state_ident = format_ident!("__{name}State");
let state_variants = parsed_variants.variants.iter().map(|variant| {
let variant_ident = variant.ident.raw();
let variant_state_body = match variant.mode {
SubcommandVariantMode::Unit => struct_state_block(quote! {()}, lifetime, []),
SubcommandVariantMode::Newtype { ty } => struct_state_block(
quote! {()},
lifetime,
[quote! { <#ty as ::debate::build::BuildFromArgs<#lifetime>>::State }],
),
SubcommandVariantMode::Struct { ref fields } => {
struct_state_block_from_fields(fields, lifetime)
}
};
quote! {
#variant_ident #variant_state_body
}
});
let all_commands = parsed_variants
.variants
.iter()
.map(|variant| variant.command.as_str());
let all_commands_slice = quote! {
&[#(#all_commands,)*]
};
let select_subcommand_arms = parsed_variants.variants.iter().map(|variant| {
let scrutinee = variant.command.as_bytes();
let scrutinee = Literal::byte_string(scrutinee);
let variant_ident = variant.ident.raw();
let variant_init_block = struct_state_init_block_from_field_count(match variant.mode {
SubcommandVariantMode::Unit => 0,
SubcommandVariantMode::Newtype { .. } => 1,
SubcommandVariantMode::Struct { ref fields } => fields.len(),
});
quote! {
#scrutinee => Self :: #variant_ident #variant_init_block
}
});
let visit_positional_arms = parsed_variants.variants.iter().map(|variant| {
make_variant_arm(
variant,
&add_positional_ident,
&fields_ident,
[&argument],
quote! { ref mut #fields_ident, ref mut position, .. },
|fields| {
let local_positional_arms = visit_positional_arms_for_fields(
&fields_ident,
&argument,
&arg_ident,
&add_arg_ident,
fields,
);
quote! {{
#(#local_positional_arms)*
::core::result::Result::Err(
::debate::state::Error::unrecognized(())
)
}}
},
)
});
let long_option_arms = parsed_variants.variants.iter().map(|variant| {
make_variant_arm(
variant,
&add_long_argument_ident,
&fields_ident,
[&option, &argument],
quote! { ref mut #fields_ident, .. },
|fields| complete_long_arg_body(&fields_ident, &argument, &option, fields, None),
)
});
let long_arms = parsed_variants.variants.iter().map(|variant| {
make_variant_arm(
variant,
&add_long_ident,
&fields_ident,
[&option, &argument],
quote! { ref mut #fields_ident, .. },
|fields| complete_long_body(&fields_ident, &argument, &option, fields, None),
)
});
let short_arms = parsed_variants.variants.iter().map(|variant| {
make_variant_arm(
variant,
&add_short_ident,
&fields_ident,
[&option, &argument],
quote! {ref mut #fields_ident, ..},
|fields| complete_short_body(&fields_ident, &argument, &option, fields, None),
)
});
let get_subcommand_arms = parsed_variants.variants.iter().map(|variant| {
let variant_ident = variant.ident.raw();
let command_name = variant.command.as_str();
let body = match variant.mode.normalized() {
SubcommandVariantNormalizedMode::Fields(fields) => {
let field_visitor_calls =
get_subcommand_field_visitor_calls(&fields_ident, &visitor, fields);
quote! { #(#field_visitor_calls)* }
}
SubcommandVariantNormalizedMode::Newtype(_) => quote! {
let #visitor = match ::debate::state::State::get_subcommand_path(
&#fields_ident.0, #visitor
) {
::core::result::Result::Ok(out) => return ::core::result::Result::Ok(out),
::core::result::Result::Err(visitor) => visitor,
};
},
};
quote! {
Self :: #variant_ident { ref #fields_ident, .. } => {
let #visitor = ::debate::state::SubcommandPathVisitorWithItem::new(
::debate::state::SubcommandPathItem::Command(#command_name),
#visitor,
);
#body
let _ = #fields_ident;
::core::result::Result::Ok(#visitor.call())
}
}
});
let build_body_fallback = match parsed_variants.fallback {
Fallback::Explicit(ident) => quote! { Self :: #ident },
Fallback::Internal(_) => quote! {
return ::core::result::Result::Err(
::debate::build::Error::required_subcommand(
#all_commands_slice,
)
)
},
};
let build_body_variant_arms = parsed_variants.variants.iter().map(|variant| {
let variant_ident = variant.ident.raw();
let variant_body = match variant.mode {
SubcommandVariantMode::Unit => quote! {},
SubcommandVariantMode::Newtype { .. } => quote! {(
match ::debate::build::BuildFromArgs::build(#fields_ident.0) {
::core::result::Result::Ok(value) => value,
::core::result::Result::Err(err) => return (
::core::result::Result::Err(err)
),
}
)},
SubcommandVariantMode::Struct { ref fields } => {
let field_initializers = final_field_initializers(&fields_ident, fields);
quote! { { #(#field_initializers,)* } }
}
};
quote! {
Self :: State :: #variant_ident { #fields_ident, ..} => (
Self :: #variant_ident #variant_body
)
}
});
Ok({
quote! {
#[doc(hidden)]
#[derive(::core::default::Default)]
enum #state_ident <#lifetime> {
#[default]
#fallback_ident,
#(#state_variants,)*
}
impl<#lifetime> ::debate::state::State<#lifetime> for #state_ident <#lifetime> {
fn add_positional<E>(
&mut self,
#argument: & #lifetime ::debate::Arg
) -> ::core::result::Result<(), E>
where
E: ::debate::state::Error<#lifetime, ()>
{
match *self {
Self :: #fallback_ident => {
*self = match #argument.bytes() {
#(#select_subcommand_arms,)*
_ => return ::core::result::Result::Err(
::debate::state::Error::unknown_subcommand(
#all_commands_slice,
)
),
};
::core::result::Result::Ok(())
}
#(
#visit_positional_arms,
)*
}
}
fn add_long_argument<E>(
&mut self,
option: & #lifetime ::debate::Arg,
argument: & #lifetime ::debate::Arg
) -> ::core::result::Result<(), E>
where
E: ::debate::state::Error<#lifetime, ()>
{
match *self {
Self :: #fallback_ident => ::core::result::Result::Err(
::debate::state::Error::unrecognized(())
),
#(#long_option_arms,)*
}
}
fn add_long<A, E>(
&mut self,
option: & #lifetime ::debate::Arg,
argument: A
) -> ::core::result::Result<(), E>
where
A: ::debate::parameter::ArgAccess<#lifetime>,
E: ::debate::state::Error<#lifetime, A>
{
match *self {
Self :: #fallback_ident => ::core::result::Result::Err(
::debate::state::Error::unrecognized(#argument)
),
#(#long_arms,)*
}
}
fn add_short<A, E>(
&mut self,
option: u8,
argument: A
) -> ::core::result::Result<(), E>
where
A: ::debate::parameter::ArgAccess<#lifetime>,
E: ::debate::state::Error<#lifetime, A>
{
match *self {
Self :: #fallback_ident => ::core::result::Result::Err(
::debate::state::Error::unrecognized(#argument)
),
#(#short_arms,)*
}
}
fn get_subcommand_path<V: ::debate::state::SubcommandVisitor>(
&self,
#visitor: V,
) -> ::core::result::Result<V::Output, V> {
match *self {
Self :: #fallback_ident => ::core::result::Result::Err(#visitor),
#(#get_subcommand_arms)*
}
}
}
impl<#lifetime> ::debate::build::BuildFromArgs<#lifetime> for #name #type_lifetime {
type State = #state_ident <#lifetime>;
fn build<E>(state: Self::State) -> Result<Self,E>
where
E: ::debate::build::Error
{
::core::result::Result::Ok(
match state {
Self :: State :: #fallback_ident => #build_body_fallback,
#(#build_body_variant_arms,)*
}
)
}
}
}
})
}