narrative_macros/
lib.rs

1mod error;
2mod extract_types_for_assertion;
3mod item_story;
4mod local_type_for;
5mod no_foreign_type_validation;
6mod output;
7mod step_attr_syntax;
8mod step_usage;
9mod story_attr_syntax;
10
11use item_story::ItemStory;
12use proc_macro2::TokenStream;
13use story_attr_syntax::StoryAttr;
14use syn::parse_macro_input;
15
16#[proc_macro_attribute]
17/// TODO: Add documentation.
18pub fn story(
19    attr: proc_macro::TokenStream,
20    input: proc_macro::TokenStream,
21) -> proc_macro::TokenStream {
22    let attr = parse_macro_input!(attr as StoryAttr);
23    let story = parse_macro_input!(input as ItemStory);
24    process_story(attr, story).into()
25}
26
27#[proc_macro_attribute]
28/// Marks a data type as a local type for a specific story.
29/// This implements both `IndependentType` and `<StoryName>LocalType` for the type.
30pub fn local_type_for(
31    attr: proc_macro::TokenStream,
32    input: proc_macro::TokenStream,
33) -> proc_macro::TokenStream {
34    let story_name = parse_macro_input!(attr as syn::Ident);
35    let input_item = parse_macro_input!(input as syn::Item);
36
37    local_type_for::generate(&story_name, &input_item).into()
38}
39
40// In general, we don't do caching some intermediate results to keep the implementation simple.
41// However, we should avoid to have heavy computation in this crate, to keep the story compilation
42// fast. So, modules have their own functionality which is simple.
43fn process_story(attr: StoryAttr, story: ItemStory) -> TokenStream {
44    output::generate(&attr, &story)
45}
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub(crate) enum Asyncness {
49    Sync,
50    Async,
51}
52
53impl quote::ToTokens for Asyncness {
54    fn to_tokens(&self, tokens: &mut TokenStream) {
55        match self {
56            Asyncness::Sync => quote::quote!().to_tokens(tokens),
57            Asyncness::Async => quote::quote!(async).to_tokens(tokens),
58        }
59    }
60}
61
62pub(crate) fn collect_format_args(lit_str: &syn::LitStr) -> Vec<String> {
63    lit_str
64        .value()
65        // remove escaped braces
66        .split("{{")
67        .flat_map(|part| part.split("}}"))
68        // iter parts that start with '{' by skipping the first split
69        .flat_map(|part| part.split('{').skip(1))
70        // take the part before the first '}'
71        .filter_map(|part| part.split_once('}').map(|(head, _)| head))
72        // remove parts after the first ':'
73        .map(|format| {
74            format
75                .split_once(':')
76                .map(|(head, _)| head)
77                .unwrap_or(format)
78        })
79        .map(ToOwned::to_owned)
80        .collect()
81}
82
83struct MakeStaticWalker;
84
85impl syn::visit_mut::VisitMut for MakeStaticWalker {
86    fn visit_type_reference_mut(&mut self, i: &mut syn::TypeReference) {
87        i.lifetime = Some(syn::Lifetime::new(
88            "'static",
89            proc_macro2::Span::mixed_site(),
90        ));
91        self.visit_type_mut(&mut i.elem);
92    }
93}
94
95pub(crate) fn make_static(ty: &syn::Type) -> syn::Type {
96    use syn::visit_mut::VisitMut;
97    let mut walker = MakeStaticWalker;
98    let mut static_ty = ty.clone();
99    walker.visit_type_mut(&mut static_ty);
100    static_ty
101}
102
103pub(crate) fn pretty_print_expr(expr: &syn::Expr) -> String {
104    prettyplease::unparse(
105        &syn::parse_file(
106            &quote::quote! {
107                const IDENT: String = #expr;
108            }
109            .to_string(),
110        )
111        .unwrap(),
112    )
113    .replace("const IDENT: String = ", "")
114    .replace(";", "")
115    .trim()
116    .to_string()
117}
118
119pub(crate) fn pretty_print_type(ty: &syn::Type) -> String {
120    prettyplease::unparse(
121        &syn::parse_file(
122            &quote::quote! {
123                const IDENT: #ty = 1;
124            }
125            .to_string(),
126        )
127        .unwrap(),
128    )
129    .replace("const IDENT: ", "")
130    .replace(" = 1;", "")
131    .trim()
132    .to_string()
133}