1extern crate proc_macro;
4
5use ::std::ffi::{OsStr, OsString};
6use ::std::path::Path;
7use proc_macro::TokenStream;
8use proc_macro2::Span;
9use quote::quote;
10use syn::{parse_macro_input, DeriveInput, Ident, Lit, Meta, MetaNameValue};
11
12#[derive(Debug, Default)]
13struct ConfigPath {
14 parent: String,
15 filename: Option<OsString>,
16 extension: Option<OsString>,
17}
18
19#[proc_macro_derive(Configure, attributes(config_file))]
20pub fn config_attribute(item: TokenStream) -> TokenStream {
21 let ast: DeriveInput = parse_macro_input!(item as DeriveInput);
22 let cfg_path = extract_attributes(&ast);
23
24 gen_impl(&ast, cfg_path)
25}
26
27fn extract_attributes(ast: &DeriveInput) -> ConfigPath {
28 for option in ast.attrs.iter() {
29 let option = option.parse_meta().unwrap();
30 match option {
31 Meta::NameValue(MetaNameValue {
32 ref path, ref lit, ..
33 }) if path.is_ident("config_file") => {
34 if let Lit::Str(f) = lit {
35 let f = f.value();
36 let fp = Path::new(&f);
37 let parent = fp.parent().unwrap_or(Path::new(""));
38 return ConfigPath {
39 parent: parent.to_str().unwrap().into(),
40 filename: fp.file_stem().map(OsStr::to_os_string),
41 extension: fp.extension().map(OsStr::to_os_string),
42 };
43 }
44 }
45 _ => {}
46 }
47 }
48 return Default::default();
49}
50
51fn pick_serializer(ext: &str) -> (Ident, Ident) {
52 match ext.as_ref() {
59 "toml" => (
60 Ident::new("toml", Span::call_site()),
61 Ident::new("to_string_pretty", Span::call_site()),
62 ),
63 "yaml" => (
64 Ident::new("serde_yaml", Span::call_site()),
65 Ident::new("to_string", Span::call_site()),
66 ),
67 "json" => (
68 Ident::new("serde_json", Span::call_site()),
69 Ident::new("to_string_pretty", Span::call_site()),
70 ),
71 _ => panic!("Invalid extension!"),
72 }
73}
74
75fn gen_impl(ast: &DeriveInput, cfg_path: ConfigPath) -> TokenStream {
76 let struct_ident = &ast.ident;
77
78 let filename = cfg_path
79 .filename
80 .unwrap_or(OsStr::new("config").to_os_string())
81 .into_string()
82 .unwrap();
83
84 let filetype = cfg_path
85 .extension
86 .unwrap_or(OsStr::new("toml").to_os_string())
87 .into_string()
88 .unwrap();
89
90 let parent = cfg_path.parent;
91
92 let (ser, ser_fn) = pick_serializer(&filetype);
93
94 let includes = quote! {
95 use ::fondant::fondant_exports::*;
96 use ::fondant::FondantError ;
97 use ::std::option::Option;
98 use ::std::fs::{self, File, OpenOptions};
99 use ::std::io::prelude::*;
100 use ::std::io::{ ErrorKind::NotFound, Write };
101 use ::std::ffi::{OsStr, OsString};
102 use ::std::path::{Path, PathBuf};
103 };
104
105 let load_paths = quote! {
106 let pkg_name = env!("CARGO_PKG_NAME");
107 let project = ProjectDirs::from("rs", "", pkg_name).unwrap();
108 let default_dir: String = project.config_dir().to_str().unwrap().into();
109
110 let d = if #parent != "" { #parent.into() } else { default_dir };
111 let config_dir: String = expand_tilde(d)
112 .as_path()
113 .to_str()
114 .unwrap()
115 .into();
116
117 let tip = Path::new(&#filename).with_extension(&#filetype);
118 let mut config_file = PathBuf::from(&config_dir);
119 config_file.push(tip);
120 };
121
122 let gen = quote! {
123 #includes
124 impl Configure for #struct_ident {
125 fn load() -> Result<#struct_ident, FondantError> {
126 #load_paths
127 match File::open(&config_file) {
128 Ok(mut cfg) => {
129 let mut cfg_data = String::new();
130 cfg.read_to_string(&mut cfg_data).unwrap();
131
132 let config: #struct_ident = #ser::from_str(&cfg_data[..])
133 .map_err(|_| FondantError::ConfigParseError)?;
134 return Ok(config);
135 },
136 Err(ref e) if e.kind() == NotFound => {
137 if !Path::new(&config_dir).is_dir() {
138 fs::create_dir_all(config_dir).map_err(FondantError::DirCreateErr)?;
139 }
140 let default_impl = #struct_ident::default();
141 Configure::store(&default_impl)?;
142 return Ok(default_impl);
143 },
144 Err(e) => return Err(FondantError::LoadError),
145 };
146 }
147 fn store(&self) -> Result<(), FondantError> {
148 #load_paths
149 let mut f = OpenOptions::new()
150 .write(true)
151 .create(true)
152 .truncate(true)
153 .open(config_file)
154 .map_err(|_| FondantError::FileOpenError)?;
155
156 let s = #ser::#ser_fn(self).map_err(|_| FondantError::ConfigParseError)?;
157 f.write_all(s.as_bytes()).map_err(|_| FondantError::FileWriteError)?;
158 Ok(())
159 }
160 }
161 };
162 gen.into()
163}