Skip to main content

ppx_macros/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![cfg_attr(feature = "nightly", feature(proc_macro_tracked_path))]
3
4use std::path::PathBuf;
5
6use proc_macro::Span;
7use syn::parse::Parse;
8use syn::{Expr, ExprArray, LitStr, Token};
9use quote::ToTokens;
10
11use ppx_impl as ppx;
12
13struct Args {
14    file_path: String,
15    base_path: String,
16    params: Vec<String>,
17}
18
19impl Parse for Args {
20    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
21        let file_path: LitStr = input.parse()?;
22        input.parse::<Token![,]>()?;
23
24        let mut params: Option<ExprArray> = None;
25        let base_path: LitStr = input.parse()?;
26        if input.peek(Token![,]) {
27            input.parse::<Token![,]>()?;
28            if !input.is_empty() {
29                params = Some(input.parse()?);
30            }
31        }
32
33        let params = params
34            .map(|params| {
35                params.elems
36                    .into_iter()
37                    .map(|elem| match elem {
38                        Expr::Lit(lit) => match &lit.lit {
39                            syn::Lit::Str(s) => s.value(),
40                            _ => panic!("Expected string literal in params"),
41                        },
42                        _ => panic!("Expected string literal in params")
43                    }).collect::<Vec<_>>()
44            })
45            .unwrap_or(Vec::new());
46
47        if !input.is_empty() {
48            if input.peek(Token![,]) {
49                _ = input.parse::<Token![,]>()?;
50                if !input.is_empty() {
51                    panic!("Unexpected token(s) in input {:?}", input);
52                }
53            } else {
54                panic!("Unexpected token(s) in input {:?}", input);
55            }
56        }
57
58        Ok(Args {
59            file_path: file_path.value(),
60            base_path: base_path.value(),
61            params: params
62        })
63    }
64}
65
66/// Parse a macro at compile time from a file.
67///
68/// # Example
69///
70/// ```ignore
71/// include_ppx_string!("path/to/file", "./templates", ["param1", "param2"])
72/// ```
73#[cfg(feature = "nightly")]
74#[proc_macro]
75pub fn include_ppx(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
76    let args = syn::parse_macro_input!(input as Args);
77
78    let source_path = PathBuf::from(Span::call_site().file());
79    let base_path = source_path.parent().unwrap();
80
81    let file_path = base_path.join(args.file_path);
82    proc_macro::tracked::path(file_path.to_str().expect("File path was not UTF-8 encoded"));
83    let base_path = base_path.join(args.base_path);
84    let mut tracked_paths = Vec::new();
85    list_files_recursive(&base_path, &mut tracked_paths);
86    for path in tracked_paths.iter() {
87        proc_macro::tracked::path(path.to_str().expect("File path was not UTF-8 encoded"));
88    }
89
90    let output = ppx::parse(file_path, base_path, args.params.iter().map(|s| s.as_str())).unwrap();
91    let output = LitStr::new(&output, Span::call_site().into());
92
93    return output.to_token_stream().into();
94}
95
96#[cfg(feature = "nightly")]
97fn list_files_recursive(dir: impl AsRef<std::path::Path>, out: &mut Vec<PathBuf>) {
98    for path in std::fs::read_dir(dir.as_ref()).unwrap() {
99        let path = path.unwrap().path();
100        if std::fs::metadata(&path).unwrap().is_dir() {
101            list_files_recursive(&path, out);
102        } else {
103            out.push(path);
104        }
105    }
106}
107
108/// Parse a macro at compile time from a string.
109///
110/// # Example
111///
112/// ```rust
113/// # use ppx_macros::include_ppx_string;
114/// assert_eq!(
115///     include_ppx_string!("#define A Hello\nA", ".", []),
116///     "Hello"
117/// );
118/// ```
119#[proc_macro]
120pub fn include_ppx_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
121    let args = syn::parse_macro_input!(input as Args);
122
123    let source_path = PathBuf::from(Span::call_site().file());
124    let base_path = source_path.parent().unwrap();
125
126    let contents = args.file_path;
127    let base_path = base_path.join(args.base_path);
128
129    let output = ppx::parse_string(&contents, base_path, args.params.iter().map(|s| s.as_str())).unwrap();
130    let output = LitStr::new(&output, Span::call_site().into());
131
132    return output.to_token_stream().into();
133}