mod error;
mod item_story;
mod no_foreign_type_validation;
mod output;
mod step_attr_syntax;
mod step_usage;
mod story_attr_syntax;
use item_story::ItemStory;
use proc_macro2::TokenStream;
use story_attr_syntax::StoryAttr;
use syn::parse_macro_input;
#[proc_macro_attribute]
pub fn story(
attr: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let attr = parse_macro_input!(attr as StoryAttr);
let story = parse_macro_input!(input as ItemStory);
process_story(attr, story).into()
}
#[proc_macro_attribute]
pub fn local_type_for(
attr: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let story_name = parse_macro_input!(attr as syn::Ident);
let input_item = parse_macro_input!(input as syn::Item);
let (type_name, generics) = match &input_item {
syn::Item::Struct(item) => (&item.ident, &item.generics),
syn::Item::Enum(item) => (&item.ident, &item.generics),
_ => {
return syn::Error::new_spanned(
input_item,
"local_type_for can only be applied to structs or enums",
)
.to_compile_error()
.into();
}
};
let local_type_trait = syn::Ident::new(&format!("{}LocalType", story_name), story_name.span());
let mut impl_generics = generics.clone();
impl_generics.type_params_mut().for_each(|param| {
param
.bounds
.push(syn::parse_quote!(narrative::StoryOwnedType));
param.bounds.push(syn::parse_quote!(#local_type_trait));
});
let mut type_generics = generics.clone();
type_generics.type_params_mut().for_each(|param| {
param.bounds.clear();
});
let output = quote::quote! {
#input_item
impl #impl_generics narrative::StoryOwnedType for #type_name #type_generics {}
impl #impl_generics #local_type_trait for #type_name #type_generics {}
};
output.into()
}
fn process_story(attr: StoryAttr, story: ItemStory) -> TokenStream {
output::generate(&attr, &story)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Asyncness {
Sync,
Async,
}
impl quote::ToTokens for Asyncness {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Asyncness::Sync => quote::quote!().to_tokens(tokens),
Asyncness::Async => quote::quote!(async).to_tokens(tokens),
}
}
}
pub(crate) fn collect_format_args(lit_str: &syn::LitStr) -> Vec<String> {
lit_str
.value()
.split("{{")
.flat_map(|part| part.split("}}"))
.flat_map(|part| part.split('{').skip(1))
.filter_map(|part| part.split_once('}').map(|(head, _)| head))
.map(|format| {
format
.split_once(':')
.map(|(head, _)| head)
.unwrap_or(format)
})
.map(ToOwned::to_owned)
.collect()
}
struct MakeStaticWalker;
impl syn::visit_mut::VisitMut for MakeStaticWalker {
fn visit_type_reference_mut(&mut self, i: &mut syn::TypeReference) {
i.lifetime = Some(syn::Lifetime::new(
"'static",
proc_macro2::Span::mixed_site(),
));
self.visit_type_mut(&mut i.elem);
}
}
pub(crate) fn make_static(ty: &syn::Type) -> syn::Type {
use syn::visit_mut::VisitMut;
let mut walker = MakeStaticWalker;
let mut static_ty = ty.clone();
walker.visit_type_mut(&mut static_ty);
static_ty
}
pub(crate) fn pretty_print_expr(expr: &syn::Expr) -> String {
prettyplease::unparse(
&syn::parse_file(
"e::quote! {
const IDENT: String = #expr;
}
.to_string(),
)
.unwrap(),
)
.replace("const IDENT: String = ", "")
.replace(";", "")
.trim()
.to_string()
}
pub(crate) fn pretty_print_type(ty: &syn::Type) -> String {
prettyplease::unparse(
&syn::parse_file(
"e::quote! {
const IDENT: #ty = 1;
}
.to_string(),
)
.unwrap(),
)
.replace("const IDENT: ", "")
.replace(" = 1;", "")
.trim()
.to_string()
}