1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Error, Ident, Lit, Meta, MetaNameValue, Result};
#[proc_macro_derive(Config, attributes(filename, extension))]
pub fn config_attribute(item: TokenStream) -> TokenStream {
let ast: DeriveInput = parse_macro_input!(item as DeriveInput);
let (filename, filetype) = extract_attributes(&ast);
gen_impl(
&ast,
filename.unwrap_or("config".into()),
filetype.unwrap_or("toml".into()),
)
}
fn extract_attributes(ast: &DeriveInput) -> (Option<String>, Option<String>) {
let mut filename: Option<String> = None;
let mut filetype: Option<String> = None;
for option in ast.attrs.iter() {
let option = option.parse_meta().unwrap();
match option {
Meta::NameValue(MetaNameValue {
ref path, ref lit, ..
}) if path.is_ident("filename") => {
if let Lit::Str(f) = lit {
filename = Some(f.value());
}
}
Meta::NameValue(MetaNameValue {
ref path, ref lit, ..
}) if path.is_ident("extension") => {
if let Lit::Str(f) = lit {
filetype = Some(f.value());
}
}
_ => {}
}
}
return (filename, filetype);
}
fn map_ser(ext: &str) -> (Ident, Ident) {
match ext.as_ref() {
"toml" => (
Ident::new("toml", Span::call_site()),
Ident::new("to_string_pretty", Span::call_site()),
),
"yaml" => (
Ident::new("serde_yaml", Span::call_site()),
Ident::new("to_string", Span::call_site()),
),
"json" => (
Ident::new("serde_json", Span::call_site()),
Ident::new("to_string_pretty", Span::call_site()),
),
_ => panic!("Invalid extension!"),
}
}
fn gen_impl(ast: &DeriveInput, filename: String, filetype: String) -> TokenStream {
let struct_name = &ast.ident.to_string();
let struct_ident = &ast.ident;
let (ser, ser_fn) = map_ser(&filetype);
let gen = quote! {
use ::fondant::{ ProjectDirs, toml, serde_json, serde_yaml, FondantError };
use ::std::path::{ Path, PathBuf };
use ::std::option::Option;
use ::std::fs::{self, File, OpenOptions};
use ::std::io::prelude::*;
use ::std::io::{ ErrorKind::NotFound, Write };
impl Config for #struct_ident {
fn load() -> Result<#struct_ident, FondantError> {
let project = ProjectDirs::from("rs", "", #struct_name).ok_or(FondantError::InvalidHomeDir)?;
let config_dir = project.config_dir();
let tip = (Path::new(#filename)).with_extension(&#filetype);
let config_file: PathBuf = [config_dir, &tip].iter().collect();
match File::open(&config_file) {
Ok(mut cfg) => {
// the file exists, parse the toml and return the struct
let mut cfg_data = String::new();
cfg.read_to_string(&mut cfg_data);
let config: #struct_ident = #ser::from_str(&cfg_data[..])
.map_err(|_| FondantError::ConfigParseError)?;
return Ok(config);
},
Err(ref e) if e.kind() == NotFound => {
fs::create_dir_all(project.config_dir());
let default_impl = #struct_ident::default();
Config::store(&default_impl)?;
return Ok(default_impl);
},
Err(e) => return Err(FondantError::LoadError),
};
}
fn store(&self) -> Result<(), FondantError> {
let project = ProjectDirs::from("rs", "", #struct_name).ok_or(FondantError::InvalidHomeDir)?;
let config_dir = project.config_dir();
let tip = (Path::new(#filename)).with_extension(&#filetype);
let config_file: PathBuf = [config_dir, &tip].iter().collect();
let mut f = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(config_file)
.map_err(|_| FondantError::FileOpenError)?;
let s = #ser::#ser_fn(self).map_err(|_| FondantError::ConfigParseError)?;
f.write_all(s.as_bytes()).map_err(|_| FondantError::FileWriteError);
Ok(())
}
}
};
gen.into()
}
|