use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{
parse_quote, spanned::Spanned, Attribute, Field, GenericArgument, Generics, Ident,
ImplGenerics, Lifetime, LifetimeParam, Lit, PathArguments, Type, TypeGenerics, TypeParam,
WhereClause,
};
use crate::{
config::{self, Config, Configs},
error,
value::Value,
};
pub const CONFIG_SUB: &str = "sub";
pub const CONFIG_ARG: &str = "arg";
pub const CONFIG_CMD: &str = "cmd";
pub const CONFIG_POS: &str = "pos";
pub const CONFIG_DOC: &str = "doc";
pub const POLICY_SEQ: &str = "seq";
pub const POLICY_FWD: &str = "fwd";
pub const POLICY_DELAY: &str = "delay";
pub const HELP_OPTION: &str = "--help;-h=b: Display help message";
#[derive(Debug, Clone, Copy)]
pub enum AttrKind {
Sub,
Arg,
Cmd,
Pos,
Main,
}
impl AttrKind {
pub fn is_sub(&self) -> bool {
matches!(self, AttrKind::Sub)
}
pub fn is_cmd(&self) -> bool {
matches!(self, AttrKind::Cmd)
}
pub fn is_pos(&self) -> bool {
matches!(self, AttrKind::Pos)
}
pub fn is_main(&self) -> bool {
matches!(self, AttrKind::Main)
}
pub fn name(&self) -> &'static str {
match self {
AttrKind::Sub => CONFIG_SUB,
AttrKind::Arg => CONFIG_ARG,
AttrKind::Cmd => CONFIG_CMD,
AttrKind::Pos => CONFIG_POS,
AttrKind::Main => unreachable!("Main don't need this"),
}
}
pub fn gen_infer(&self, cfg_ident: &Ident, field_ty: &Type) -> syn::Result<TokenStream> {
match self {
AttrKind::Cmd => Ok(quote! {
cote::prelude::ConfigValue::set_type::<#field_ty>(&mut #cfg_ident);
<cote::prelude::Cmd as cote::prelude::InferOverride>::infer_fill_info(&mut #cfg_ident)?;
<cote::prelude::Cmd as cote::prelude::Infer>::infer_fill_info(&mut #cfg_ident)?;
}),
AttrKind::Pos => {
Ok(quote! {
<cote::prelude::Pos<#field_ty> as cote::prelude::InferOverride>::infer_fill_info(&mut #cfg_ident)?;
<cote::prelude::Pos<#field_ty> as cote::prelude::Infer>::infer_fill_info(&mut #cfg_ident)?;
})
}
AttrKind::Arg => Ok(quote! {
<#field_ty as cote::prelude::InferOverride>::infer_fill_info(&mut #cfg_ident)?;
<#field_ty as cote::prelude::Infer>::infer_fill_info(&mut #cfg_ident)?;
}),
_ => {
unreachable!("In AttrKind, can not get here ...")
}
}
}
}
#[derive(Debug)]
pub struct FieldCfg<'a, T> {
id: u64,
ty: &'a Type,
kind: AttrKind,
ident: &'a Ident,
docs: Vec<Lit>,
configs: Configs<T>,
}
impl<'a, T: config::Kind + PartialEq> FieldCfg<'a, T> {
pub fn new(id: u64, field: &'a Field, kind: AttrKind) -> syn::Result<Self> {
let ty = &field.ty;
let ident = field.ident.as_ref();
let ident = ident.ok_or_else(|| error(field.span(), "Not support unnamed field"))?;
let configs = Configs::<T>::parse_attrs(kind.name(), &field.attrs);
let docs = Self::filter_comment_doc(&field.attrs);
Ok(Self {
id,
ty,
kind,
ident,
configs,
docs,
})
}
pub fn filter_comment_doc(attrs: &[Attribute]) -> Vec<Lit> {
let attrs = attrs.iter().filter(|v| v.path().is_ident(CONFIG_DOC));
let mut ret = vec![];
for attr in attrs {
if let syn::Meta::NameValue(meta) = &attr.meta {
if let syn::Expr::Lit(syn::ExprLit { lit, .. }) = &meta.value {
ret.push(lit.clone());
}
}
}
ret
}
pub fn id(&self) -> u64 {
self.id
}
pub fn kind(&self) -> AttrKind {
self.kind
}
pub fn docs(&self) -> &[Lit] {
&self.docs
}
pub fn configs(&self) -> &Configs<T> {
&self.configs
}
pub fn ty(&self) -> &'a Type {
self.ty
}
pub fn ident(&self) -> &'a Ident {
self.ident
}
pub fn has_cfg(&self, kind: T) -> bool {
self.configs.has_cfg(kind)
}
pub fn find_cfg(&self, kind: T) -> Option<&Config<T>> {
self.configs.find_cfg(kind)
}
pub fn find_value(&self, kind: T) -> Option<&Value> {
self.configs.find_cfg(kind).map(|v| v.value())
}
pub fn collect_help_msgs(&self) -> Option<TokenStream> {
if self.docs().is_empty() {
None
} else {
let docs = self.docs.iter();
Some(quote! {
[ #(#docs),* ].into_iter().map(|v|v.trim()).collect::<Vec<_>>().join(" ")
})
}
}
}
#[derive(Debug)]
pub struct Utils;
impl Utils {
pub fn ident2opt_name(ident: &str) -> String {
if ident.chars().count() > 1 {
format!("--{}", ident.replace('_', "-"))
} else {
format!("-{}", ident)
}
}
pub fn id2opt_ident(id: u64, span: Span) -> Ident {
Ident::new(&format!("option_{}", id), span)
}
pub fn id2opt_uid_ident(id: u64, span: Span) -> Ident {
Ident::new(&format!("option_{}_uid", id), span)
}
pub fn id2uid_literal(id: u64) -> syn::Lit {
syn::Lit::Verbatim(proc_macro2::Literal::u64_suffixed(id))
}
pub fn gen_opt_create(
ident: &Ident,
cfg_modifer: Option<TokenStream>,
) -> syn::Result<TokenStream> {
Ok(quote! {
let #ident = {
let cfg = {
let mut cfg = cote::prelude::SetCfg::<Set>::default();
#cfg_modifer
cfg
};
cote::prelude::Ctor::new_with(cote::prelude::SetExt::ctor_mut(set, &ctor_name)?, cfg).map_err(Into::into)?
};
})
}
pub fn gen_opt_insert(
ident: &Ident,
uid_ident: &Ident,
uid_literal: &syn::Lit,
) -> syn::Result<TokenStream> {
Ok(quote! {
let #uid_ident = set.insert(#ident);
assert_eq!(#uid_ident, #uid_literal, "Oops! Uid must be equal here");
})
}
pub fn gen_opt_handler<T>(
uid_ident: &Ident,
on: Option<&Config<T>>,
fallback: Option<&Config<T>>,
then: Option<&Config<T>>,
) -> syn::Result<Option<TokenStream>> {
if on.is_some() && fallback.is_some() {
Err(error(
uid_ident.span(),
"Can not set both `on` and `fallback` attribute at same time",
))
} else {
Ok(on
.map(|handler| {
if let Some(then) = then {
quote! {
parser.entry(#uid_ident)?.on(#handler).then(#then);
}
} else {
quote! {
parser.entry(#uid_ident)?.on(#handler);
}
}
})
.or_else(|| {
fallback.map(|handler| {
if let Some(then) = then {
quote! {
parser.entry(#uid_ident)?.fallback(#handler).then(#then);
}
} else {
quote! {
parser.entry(#uid_ident)?.fallback(#handler);
}
}
})
}))
}
}
pub fn check_in_ty(ty: &Type, ty_name: &str) -> syn::Result<bool> {
if let Type::Path(path) = ty {
if let Some(segment) = path.path.segments.last() {
let ident = segment.ident.to_string();
if ident == ty_name {
return Ok(true);
} else if let PathArguments::AngleBracketed(ab) = &segment.arguments {
for arg in ab.args.iter() {
if let GenericArgument::Type(next_ty) = arg {
return Self::check_in_ty(next_ty, ty_name);
}
}
}
}
Ok(false)
} else {
Err(error(ty, "Cote not support reference type"))
}
}
pub fn gen_policy_ty(policy_name: &str) -> Option<TokenStream> {
match policy_name {
POLICY_FWD => Some(quote! {
cote::prelude::FwdPolicy<'inv, Set>
}),
POLICY_DELAY => Some(quote! {
cote::prelude::DelayPolicy<'inv, Set>
}),
POLICY_SEQ => Some(quote! {
cote::prelude::SeqPolicy<'inv, Set>
}),
_ => None,
}
}
pub fn gen_policy_default_ty(policy_name: &str) -> Option<TokenStream> {
match policy_name {
POLICY_FWD => Some(quote! {
cote::prelude::FwdPolicy<'inv, cote::prelude::CoteSet>
}),
POLICY_DELAY => Some(quote! {
cote::prelude::DelayPolicy<'inv, cote::prelude::CoteSet>
}),
POLICY_SEQ => Some(quote! {
cote::prelude::SeqPolicy<'inv, cote::prelude::CoteSet>
}),
_ => None,
}
}
pub fn gen_sync_ret(
has_sub: bool,
enable_abort: bool,
enable_normal: bool,
help_uid: Option<u64>,
) -> syn::Result<TokenStream> {
let abort_help = enable_abort.then(|| {
Some(quote! {
if error_or_failure {
rctx.set_display_help(true);
rctx.set_exit(false);
}
})
});
let normal_help = enable_normal.then(|| {
let uid_literal = Utils::id2uid_literal(help_uid.unwrap());
Some(quote! {
if cote::prelude::OptValueExt::val::<bool>(cote::prelude::SetExt::opt(set, #uid_literal)?).ok() == Some(&true) {
rctx.set_display_help(true);
rctx.set_exit(!error_or_failure);
if #has_sub && !sub_parser && !rctx.sub_parser() {
}
}
})
});
Ok(quote! {
let error_or_failure = ret.is_err() ||
!ret.as_ref().map(cote::prelude::Status::status).unwrap_or(true);
#abort_help
#normal_help
})
}
}
pub struct GenericsModifier(Generics);
impl GenericsModifier {
pub fn new(generics: Generics) -> Self {
Self(generics)
}
pub fn insert_lifetime(&mut self, lifetime: &str) -> &mut Self {
self.0.params.insert(
0,
syn::GenericParam::from(LifetimeParam::new(Lifetime::new(lifetime, self.0.span()))),
);
self
}
pub fn append_type(&mut self, ty: &str) -> &mut Self {
self.0
.params
.push(syn::GenericParam::from(TypeParam::from(Ident::new(
ty,
self.0.span(),
))));
self
}
pub fn mod_for_ipd(&mut self, used: &[&Ident]) -> &mut Self {
let orig_where = self.0.where_clause.as_ref().map(|v| &v.predicates);
let infer_override = Self::gen_inferoverride_for_ty(used);
let fetch = Self::gen_fetch_for_ty(used, quote!(Set));
let new_where: WhereClause = parse_quote! {
where
Set: cote::prelude::Set + cote::prelude::OptParser<Output: cote::prelude::Information> +
cote::prelude::OptValidator + cote::prelude::SetValueFindExt + Default + 'inv,
cote::prelude::SetCfg<Set>: cote::prelude::ConfigValue + Default,
#(#used: cote::prelude::Infer + cote::prelude::ErasedTy,)*
#(<#used as cote::prelude::Infer>::Val: cote::prelude::RawValParser,)*
#infer_override
#fetch
#orig_where
};
self.0.where_clause = Some(new_where);
self.insert_lifetime("'inv");
self.append_type("Set");
self
}
pub fn split_for_impl_ipd(
&mut self,
used: &[&Ident],
) -> (ImplGenerics<'_>, TypeGenerics<'_>, Option<&WhereClause>) {
self.mod_for_ipd(used);
self.0.split_for_impl()
}
pub fn mod_for_esd(&mut self, used: &[&Ident]) -> &mut Self {
let orig_where = self.0.where_clause.as_ref().map(|v| &v.predicates);
let fetch = Self::gen_fetch_for_ty(used, quote!(Set));
let new_where: WhereClause = parse_quote! {
where
Set: cote::prelude::SetValueFindExt,
cote::prelude::SetCfg<Set>: cote::prelude::ConfigValue + Default,
#fetch
#orig_where
};
self.0.where_clause = Some(new_where);
self.insert_lifetime("'set");
self.append_type("Set");
self
}
pub fn split_for_impl_esd(
&mut self,
used: &[&Ident],
) -> (ImplGenerics<'_>, TypeGenerics<'_>, Option<&WhereClause>) {
self.mod_for_esd(used);
self.0.split_for_impl()
}
pub fn mod_for_pi(&mut self, used: &[&Ident]) -> &mut Self {
let orig_where = self.0.where_clause.as_ref().map(|v| &v.predicates);
let new_where: WhereClause = parse_quote! {
where
#(#used: cote::prelude::Infer + cote::prelude::ErasedTy,)*
#(<#used as cote::prelude::Infer>::Val: cote::prelude::RawValParser,)*
#orig_where
};
self.0.where_clause = Some(new_where);
self
}
pub fn split_for_impl_pi(
&mut self,
used: &[&Ident],
) -> (ImplGenerics<'_>, TypeGenerics<'_>, Option<&WhereClause>) {
self.mod_for_pi(used);
self.0.split_for_impl()
}
pub fn mod_for_fetch(&mut self, used: &[&Ident]) -> &mut Self {
let orig_where = self.0.where_clause.as_ref().map(|v| &v.predicates);
let fetch = Self::gen_fetch_for_ty(used, quote!(Set));
let new_where: WhereClause = parse_quote! {
where
Set: cote::prelude::SetValueFindExt,
cote::prelude::SetCfg<Set>: cote::prelude::ConfigValue + Default,
Self: cote::prelude::ErasedTy + Sized,
#fetch
#orig_where
};
self.0.where_clause = Some(new_where);
self.append_type("Set");
self
}
pub fn split_for_impl_fetch(
&mut self,
used: &[&Ident],
) -> (ImplGenerics<'_>, TypeGenerics<'_>, Option<&WhereClause>) {
self.mod_for_fetch(used);
self.0.split_for_impl()
}
pub fn gen_fetch_for_ty(used: &[&Ident], set: TokenStream) -> TokenStream {
quote! {
#(#used: cote::prelude::Fetch<#set>,)*
}
}
pub fn gen_inferoverride_for_ty(used: &[&Ident]) -> TokenStream {
quote! {
#(#used: cote::prelude::InferOverride)*
}
}
}
impl ToTokens for GenericsModifier {
fn to_tokens(&self, tokens: &mut TokenStream) {
ToTokens::to_tokens(&self.0, tokens)
}
}
#[derive(Debug, Default)]
pub struct OptUpdate {
pub c: Option<TokenStream>,
pub i: Option<TokenStream>,
pub h: Option<TokenStream>,
}
impl OptUpdate {
pub fn with_create(mut self, value: TokenStream) -> Self {
self.c = Some(value);
self
}
pub fn with_insert(mut self, value: TokenStream) -> Self {
self.i = Some(value);
self
}
pub fn with_handler(mut self, value: TokenStream) -> Self {
self.h = Some(value);
self
}
}