1#![cfg_attr(feature = "nightly", feature(proc_macro_expand))]
2#![doc = include_str!("../README.md")]
3
4use ax_config_gen::{Config, OutputFormat};
5use proc_macro::{LexError, TokenStream};
6use quote::{ToTokens, quote};
7use syn::{
8 Error, Ident, LitStr, Result, Token,
9 parse::{Parse, ParseStream},
10 parse_macro_input,
11};
12
13fn compiler_error<T: ToTokens>(tokens: T, msg: String) -> TokenStream {
14 Error::new_spanned(tokens, msg).to_compile_error().into()
15}
16
17#[proc_macro]
23pub fn parse_configs(config_toml: TokenStream) -> TokenStream {
24 #[cfg(feature = "nightly")]
25 let config_toml = match config_toml.expand_expr() {
26 Ok(s) => s,
27 Err(e) => {
28 return Error::new(proc_macro2::Span::call_site(), e.to_string())
29 .to_compile_error()
30 .into();
31 }
32 };
33
34 let config_toml = parse_macro_input!(config_toml as LitStr).value();
35 let code = Config::from_toml(&config_toml).and_then(|cfg| cfg.dump(OutputFormat::Rust));
36 match code {
37 Ok(code) => code
38 .parse()
39 .unwrap_or_else(|e: LexError| compiler_error(config_toml, e.to_string())),
40 Err(e) => compiler_error(config_toml, e.to_string()),
41 }
42}
43
44#[proc_macro]
59pub fn include_configs(args: TokenStream) -> TokenStream {
60 let args = parse_macro_input!(args as IncludeConfigsArgs);
61 let path = match args {
62 IncludeConfigsArgs::Path(p) => p.value(),
63 IncludeConfigsArgs::PathEnv(env) => {
64 let Ok(path) = std::env::var(env.value()) else {
65 return compiler_error(
66 &env,
67 format!("environment variable `{}` not set", env.value()),
68 );
69 };
70 path
71 }
72 IncludeConfigsArgs::PathEnvFallback(env, fallback) => {
73 std::env::var(env.value()).unwrap_or_else(|_| fallback.value())
74 }
75 };
76
77 let root = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into());
78 let cfg_path = std::path::Path::new(&root).join(&path);
79
80 let Ok(config_toml) = std::fs::read_to_string(&cfg_path) else {
81 return compiler_error(path, format!("failed to read config file: {:?}", cfg_path));
82 };
83
84 quote! {
85 ::ax_config_macros::parse_configs!(#config_toml);
86 }
87 .into()
88}
89
90enum IncludeConfigsArgs {
91 Path(LitStr),
92 PathEnv(LitStr),
93 PathEnvFallback(LitStr, LitStr),
94}
95
96impl Parse for IncludeConfigsArgs {
97 fn parse(input: ParseStream) -> Result<Self> {
98 if input.peek(LitStr) {
99 return Ok(IncludeConfigsArgs::Path(input.parse()?));
100 }
101
102 let mut env = None;
103 let mut fallback = None;
104 while !input.is_empty() {
105 let ident: Ident = input.parse()?;
106 input.parse::<Token![=]>()?;
107 let str: LitStr = input.parse()?;
108
109 match ident.to_string().as_str() {
110 "path_env" => {
111 if env.is_some() {
112 return Err(Error::new(ident.span(), "duplicate parameter `path_env`"));
113 }
114 env = Some(str);
115 }
116 "fallback" => {
117 if fallback.is_some() {
118 return Err(Error::new(ident.span(), "duplicate parameter `fallback`"));
119 }
120 fallback = Some(str);
121 }
122 _ => {
123 return Err(Error::new(
124 ident.span(),
125 format!("unexpected parameter `{}`", ident),
126 ));
127 }
128 }
129
130 if input.peek(Token![,]) {
131 input.parse::<Token![,]>()?;
132 }
133 }
134
135 match (env, fallback) {
136 (Some(env), None) => Ok(IncludeConfigsArgs::PathEnv(env)),
137 (Some(env), Some(fallback)) => Ok(IncludeConfigsArgs::PathEnvFallback(env, fallback)),
138 _ => Err(Error::new(
139 input.span(),
140 "missing required parameter `path_env`",
141 )),
142 }
143 }
144}