easy_macros_context_internal/
lib.rs

1use proc_macro::TokenStream;
2use syn::{Expr, Token, punctuated::Punctuated, token::Comma};
3
4///Same input as format! macro
5struct ContextInternalInput {
6    str: syn::LitStr,
7    _comma: Option<Token![,]>,
8    args: syn::punctuated::Punctuated<syn::Expr, Token![,]>,
9}
10
11enum ContextInternalMaybeInput {
12    Yes(ContextInternalInput),
13    No,
14}
15
16impl syn::parse::Parse for ContextInternalInput {
17    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
18        //Handle no input
19        if input.is_empty() {
20            return Ok(ContextInternalInput {
21                str: syn::LitStr::new("", proc_macro2::Span::call_site()),
22                _comma: None,
23                args: syn::punctuated::Punctuated::new(),
24            });
25        }
26        let str = input.parse()?;
27        if !input.is_empty() {
28            let _comma = input.parse()?;
29            let args = input.parse_terminated(syn::Expr::parse, Token![,])?;
30            Ok(ContextInternalInput { str, _comma, args })
31        } else {
32            Ok(ContextInternalInput {
33                str,
34                _comma: None,
35                args: syn::punctuated::Punctuated::new(),
36            })
37        }
38    }
39}
40
41impl syn::parse::Parse for ContextInternalMaybeInput {
42    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
43        if input.is_empty() {
44            return Ok(ContextInternalMaybeInput::No);
45        }
46        Ok(ContextInternalMaybeInput::Yes(input.parse()?))
47    }
48}
49
50struct ContextInternalInput2 {
51    line: syn::Expr,
52    deeper: Option<ContextInternalInput>,
53}
54
55impl syn::parse::Parse for ContextInternalInput2 {
56    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
57        let line = input.parse()?;
58        if !input.is_empty() {
59            input.parse::<Token![,]>()?;
60            let deeper = input.parse()?;
61            Ok(ContextInternalInput2 {
62                line,
63                deeper: Some(deeper),
64            })
65        } else {
66            Ok(ContextInternalInput2 { line, deeper: None })
67        }
68    }
69}
70
71fn context_base(
72    mut passed_in_str: String,
73    mut passed_in_args: Punctuated<Expr, Comma>,
74    line: Expr,
75    closure: bool,
76) -> TokenStream {
77    if passed_in_str.is_empty() {
78        passed_in_str = "{}:{}".to_owned();
79    } else {
80        passed_in_str = format!("{{}}:{{}}\r\n{}", passed_in_str);
81    }
82    passed_in_args.insert(
83        0,
84        syn::parse_quote! {
85            file!()
86        },
87    );
88
89    passed_in_args.insert(1, line);
90
91    let result = if closure {
92        quote::quote! {
93            ||{format!(#passed_in_str, #passed_in_args)}
94        }
95    } else {
96        quote::quote! {
97            format!(#passed_in_str, #passed_in_args)
98        }
99    };
100
101    // panic!("{}", result.to_string());
102
103    result.into()
104}
105
106#[proc_macro]
107/// Macro used by `context!` macro in easy_macros_helpers crate
108///
109/// Use context! macro from helpers crate instead
110pub fn context_internal(item: TokenStream) -> TokenStream {
111    let parsed = syn::parse_macro_input!(item as ContextInternalMaybeInput);
112
113    let (passed_in_str, passed_in_args) = match parsed {
114        ContextInternalMaybeInput::Yes(context_internal_input) => (
115            context_internal_input.str.value(),
116            context_internal_input.args,
117        ),
118        ContextInternalMaybeInput::No => (String::new(), syn::punctuated::Punctuated::new()),
119    };
120
121    context_base(
122        passed_in_str,
123        passed_in_args,
124        syn::parse_quote! {
125            line!()
126        },
127        false,
128    )
129}
130
131/// Macro used by `always_context` attribute macro
132///
133/// Since it needs to provide the current line by itself
134#[proc_macro]
135pub fn context_internal2(item: TokenStream) -> TokenStream {
136    let parsed = syn::parse_macro_input!(item as ContextInternalInput2);
137
138    let (passed_in_str, passed_in_args) = match parsed.deeper {
139        Some(context_internal_input) => (
140            context_internal_input.str.value(),
141            context_internal_input.args,
142        ),
143        None => (String::new(), syn::punctuated::Punctuated::new()),
144    };
145
146    context_base(passed_in_str, passed_in_args, parsed.line, true)
147}
148
149#[test]
150fn format_compiler_test() {
151    let test_str = "Str";
152    let _ = format!("file: {}:{} | {test_str} | ", file!(), line!());
153    let _ = format!("{} | file: {}:{}", test_str, file!(), line!());
154}