#![cfg_attr(feature = "relative-to-macro", feature(proc_macro_span))]
mod code_generation;
mod parse;
mod types;
#[cfg(feature = "relative-to-macro")]
use proc_macro::Span;
#[cfg(feature = "relative-to-macro")]
use std::path::PathBuf;
use proc_macro::TokenStream;
use saphyr::Yaml;
use std::process::Command;
use syn::{parse_macro_input, DeriveInput};
use types::Ast;
#[proc_macro_attribute]
#[cfg_attr(
feature = "relative-to-macro",
doc = r##"
```rust
use config_to_rs::config_to_rs;
// tests/test.yaml
// parsing: working
// age: 22
// enabled:
// array_of_arrays:
// - ["a", "b", "c"]
// - ["d", "e", "f"]
// array_of_objects:
// - name: "a"
// age: 1
// - name: "b"
// age: 2
// - name: "c"
// age: 3
#[config_to_rs(yaml, ../test.yaml)]
pub struct Config;
assert_eq!(CONFIG.parsing, "working");
assert_eq!(CONFIG.age, 22i64);
assert_eq!(CONFIG.enabled, true);
assert_eq!(CONFIG.array_of_arrays, [["a", "b", "c"], ["d", "e", "f"]]);
for (i, obj) in CONFIG.array_of_objects.iter().enumerate() {
assert_eq!(obj.name, ['a', 'b', 'c'][i].to_string());
assert_eq!(obj.age, (i + 1) as i64);
}
```"##
)]
#[cfg_attr(
not(feature = "relative-to-macro"),
doc = r##"
```rust
use config_to_rs::config_to_rs;
// tests/test.yaml
// parsing: working
// age: 22
// enabled:
// array_of_arrays:
// - ["a", "b", "c"]
// - ["d", "e", "f"]
// array_of_objects:
// - name: "a"
// age: 1
// - name: "b"
// age: 2
// - name: "c"
// age: 3
#[config_to_rs(yaml, test.yaml)]
pub struct Config;
assert_eq!(CONFIG.parsing, "working");
assert_eq!(CONFIG.age, 22i64);
assert_eq!(CONFIG.enabled, true);
assert_eq!(CONFIG.array_of_arrays, [["a", "b", "c"], ["d", "e", "f"]]);
for (i, obj) in CONFIG.array_of_objects.iter().enumerate() {
assert_eq!(obj.name, ['a', 'b', 'c'][i].to_string());
assert_eq!(obj.age, (i + 1) as i64);
}
```"##
)]
pub fn config_to_rs(args: TokenStream, input: TokenStream) -> TokenStream {
let mut ast: DeriveInput = parse_macro_input!(input as DeriveInput);
let args = args
.to_string()
.split(",")
.map(|x| x.to_string())
.collect::<Vec<String>>();
if args.is_empty() {
panic!("`config_to_rs` must have arguments in the form `config_to_rs(file_type)` or `config_to_rs(file_type, config_path)`");
}
let file_type = args[0].replace("\"", "").replace(" ", "");
if file_type != "yaml" {
panic!("`config_to_rs` currently only supports yaml files");
}
let config_path;
#[cfg(feature = "relative-to-macro")]
{
config_path = if args.len() == 1 {
std::env::var("CONFIG_PATH").unwrap()
} else {
let span = Span::call_site();
let mut mut_config_path = PathBuf::from(span.file());
mut_config_path.pop();
mut_config_path.push(args[1].replace("\"", "").replace(" ", ""));
mut_config_path.as_os_str().to_str().unwrap().to_string()
};
}
#[cfg(not(feature = "relative-to-macro"))]
{
config_path = if args.len() == 1 {
std::env::var("CONFIG_PATH").unwrap()
} else {
args[1].replace("\"", "").replace(" ", "")
};
}
let debug = std::env::var("DEBUG").is_ok();
if debug {
println!("{}", config_path);
}
if let syn::Data::Struct(_) = &mut ast.data {
let struct_name = ast.ident.to_string();
let ast = do_the_yaml(config_path, struct_name);
if debug {
let out_path = std::env::current_dir().unwrap().join("out.rs");
std::fs::write(out_path.clone(), ast.to_string()).unwrap();
Command::new("rustfmt")
.arg(out_path)
.spawn()
.unwrap()
.wait()
.unwrap();
}
ast
} else {
panic!("`config_to_rs` must only be used on structs");
}
}
fn do_the_yaml(yaml_path: String, base_name: String) -> TokenStream {
let file = std::fs::read_to_string(yaml_path).unwrap();
let config = Yaml::load_from_str(&file).unwrap()[0].clone();
let parse_tree = Ast::from_yaml(base_name, config);
let token_stream = parse_tree.to_rust();
token_stream.into()
}