diff options
-rw-r--r-- | fondant_derive/Cargo.toml | 1 | ||||
-rw-r--r-- | fondant_derive/src/lib.rs | 114 |
2 files changed, 71 insertions, 44 deletions
diff --git a/fondant_derive/Cargo.toml b/fondant_derive/Cargo.toml index 73a3a19..b93b8a0 100644 --- a/fondant_derive/Cargo.toml +++ b/fondant_derive/Cargo.toml | |||
@@ -7,6 +7,7 @@ edition = "2018" | |||
7 | [dependencies] | 7 | [dependencies] |
8 | proc-macro2 = "1.0.9" | 8 | proc-macro2 = "1.0.9" |
9 | quote = "1.0" | 9 | quote = "1.0" |
10 | directories = "2.0" | ||
10 | 11 | ||
11 | [dependencies.syn] | 12 | [dependencies.syn] |
12 | version = "1.0" | 13 | version = "1.0" |
diff --git a/fondant_derive/src/lib.rs b/fondant_derive/src/lib.rs index 5584d84..946c130 100644 --- a/fondant_derive/src/lib.rs +++ b/fondant_derive/src/lib.rs | |||
@@ -1,49 +1,63 @@ | |||
1 | extern crate proc_macro; | 1 | extern crate proc_macro; |
2 | 2 | ||
3 | use ::std::ffi::{OsStr, OsString}; | ||
4 | use ::std::path::{Path, PathBuf}; | ||
5 | use directories::ProjectDirs; | ||
3 | use proc_macro::TokenStream; | 6 | use proc_macro::TokenStream; |
4 | use proc_macro2::Span; | 7 | use proc_macro2::Span; |
5 | use quote::quote; | 8 | use quote::quote; |
6 | use syn::{parse_macro_input, DeriveInput, Error, Ident, Lit, Meta, MetaNameValue, Result}; | 9 | use syn::{parse_macro_input, DeriveInput, Error, Ident, Lit, Meta, MetaNameValue, Result}; |
7 | 10 | ||
8 | #[proc_macro_derive(Config, attributes(filename, extension))] | 11 | #[derive(Debug, Default)] |
12 | struct ConfigPath { | ||
13 | parent: Option<PathBuf>, | ||
14 | filename: Option<OsString>, | ||
15 | extension: Option<OsString>, | ||
16 | } | ||
17 | |||
18 | #[proc_macro_derive(Configure, attributes(config_file))] | ||
9 | pub fn config_attribute(item: TokenStream) -> TokenStream { | 19 | pub fn config_attribute(item: TokenStream) -> TokenStream { |
10 | let ast: DeriveInput = parse_macro_input!(item as DeriveInput); | 20 | let ast: DeriveInput = parse_macro_input!(item as DeriveInput); |
11 | let (filename, filetype) = extract_attributes(&ast); | 21 | let cfg_path = extract_attributes(&ast); |
12 | 22 | ||
13 | gen_impl( | 23 | gen_impl(&ast, cfg_path) |
14 | &ast, | ||
15 | filename.unwrap_or("config".into()), | ||
16 | filetype.unwrap_or("toml".into()), | ||
17 | ) | ||
18 | } | 24 | } |
19 | 25 | ||
20 | fn extract_attributes(ast: &DeriveInput) -> (Option<String>, Option<String>) { | 26 | fn extract_attributes(ast: &DeriveInput) -> ConfigPath { |
21 | let mut filename: Option<String> = None; | ||
22 | let mut filetype: Option<String> = None; | ||
23 | for option in ast.attrs.iter() { | 27 | for option in ast.attrs.iter() { |
24 | let option = option.parse_meta().unwrap(); | 28 | let option = option.parse_meta().unwrap(); |
25 | match option { | 29 | match option { |
26 | Meta::NameValue(MetaNameValue { | 30 | Meta::NameValue(MetaNameValue { |
27 | ref path, ref lit, .. | 31 | ref path, ref lit, .. |
28 | }) if path.is_ident("filename") => { | 32 | }) if path.is_ident("config_file") => { |
29 | if let Lit::Str(f) = lit { | ||
30 | filename = Some(f.value()); | ||
31 | } | ||
32 | } | ||
33 | Meta::NameValue(MetaNameValue { | ||
34 | ref path, ref lit, .. | ||
35 | }) if path.is_ident("extension") => { | ||
36 | if let Lit::Str(f) = lit { | 33 | if let Lit::Str(f) = lit { |
37 | filetype = Some(f.value()); | 34 | let f = f.value(); |
35 | let fp = Path::new(&f); | ||
36 | let blank = Path::new(""); | ||
37 | let parent = match fp.parent() { | ||
38 | Some(blank) => None, | ||
39 | other => other, | ||
40 | }; | ||
41 | return ConfigPath { | ||
42 | parent: parent.map(Path::to_path_buf), | ||
43 | filename: fp.file_stem().map(OsStr::to_os_string), | ||
44 | extension: fp.extension().map(OsStr::to_os_string), | ||
45 | }; | ||
38 | } | 46 | } |
39 | } | 47 | } |
40 | _ => {} | 48 | _ => {} |
41 | } | 49 | } |
42 | } | 50 | } |
43 | return (filename, filetype); | 51 | return Default::default(); |
44 | } | 52 | } |
45 | 53 | ||
46 | fn map_ser(ext: &str) -> (Ident, Ident) { | 54 | fn pick_serializer(ext: &str) -> (Ident, Ident) { |
55 | /* returns serializer and a corresponding function to | ||
56 | * stringify with based on file extension | ||
57 | * toml::to_string_pretty | ||
58 | * serde_yaml::to_string | ||
59 | * serde_json::to_string_pretty | ||
60 | */ | ||
47 | match ext.as_ref() { | 61 | match ext.as_ref() { |
48 | "toml" => ( | 62 | "toml" => ( |
49 | Ident::new("toml", Span::call_site()), | 63 | Ident::new("toml", Span::call_site()), |
@@ -61,30 +75,48 @@ fn map_ser(ext: &str) -> (Ident, Ident) { | |||
61 | } | 75 | } |
62 | } | 76 | } |
63 | 77 | ||
64 | fn gen_impl(ast: &DeriveInput, filename: String, filetype: String) -> TokenStream { | 78 | fn gen_impl(ast: &DeriveInput, cfg_path: ConfigPath) -> TokenStream { |
65 | let struct_name = &ast.ident.to_string(); | ||
66 | let struct_ident = &ast.ident; | 79 | let struct_ident = &ast.ident; |
67 | let (ser, ser_fn) = map_ser(&filetype); | 80 | |
81 | let filename = cfg_path | ||
82 | .filename | ||
83 | .unwrap_or(OsStr::new("config").to_os_string()); | ||
84 | let filetype = cfg_path | ||
85 | .extension | ||
86 | .unwrap_or(OsStr::new("toml").to_os_string()); | ||
87 | |||
88 | let filename = filename.into_string().unwrap(); | ||
89 | let filetype = filetype.into_string().unwrap(); | ||
90 | |||
91 | let pkg_name = env!("CARGO_PKG_NAME"); | ||
92 | let project = ProjectDirs::from("rs", "", pkg_name).unwrap(); | ||
93 | let default_dir = project.config_dir(); | ||
94 | let config_dir = cfg_path.parent.as_deref().unwrap_or(default_dir); | ||
95 | |||
96 | let tip = Path::new(&filename).with_extension(&filetype); | ||
97 | let config_file = [config_dir, tip.as_ref()] | ||
98 | .iter() | ||
99 | .collect::<PathBuf>() | ||
100 | .into_os_string() | ||
101 | .into_string() | ||
102 | .unwrap(); | ||
103 | let parent: String = config_dir.to_str().unwrap().into(); | ||
104 | |||
105 | let (ser, ser_fn) = pick_serializer(&filetype); | ||
68 | 106 | ||
69 | let gen = quote! { | 107 | let gen = quote! { |
70 | use ::fondant::{ ProjectDirs, toml, serde_json, serde_yaml, FondantError }; | 108 | use ::fondant::{Configure, ProjectDirs, toml, serde_json, serde_yaml, FondantError}; |
71 | use ::std::path::{ Path, PathBuf }; | ||
72 | use ::std::option::Option; | 109 | use ::std::option::Option; |
73 | use ::std::fs::{self, File, OpenOptions}; | 110 | use ::std::fs::{self, File, OpenOptions}; |
74 | use ::std::io::prelude::*; | 111 | use ::std::io::prelude::*; |
75 | use ::std::io::{ ErrorKind::NotFound, Write }; | 112 | use ::std::io::{ ErrorKind::NotFound, Write }; |
113 | use ::std::ffi::{OsStr, OsString}; | ||
114 | use ::std::path::{Path, PathBuf}; | ||
76 | 115 | ||
77 | impl Config for #struct_ident { | 116 | impl Configure for #struct_ident { |
78 | fn load() -> Result<#struct_ident, FondantError> { | 117 | fn load() -> Result<#struct_ident, FondantError> { |
79 | let project = ProjectDirs::from("rs", "", #struct_name).ok_or(FondantError::InvalidHomeDir)?; | 118 | match File::open(&#config_file) { |
80 | let config_dir = project.config_dir(); | ||
81 | |||
82 | let tip = (Path::new(#filename)).with_extension(&#filetype); | ||
83 | let config_file: PathBuf = [config_dir, &tip].iter().collect(); | ||
84 | |||
85 | match File::open(&config_file) { | ||
86 | Ok(mut cfg) => { | 119 | Ok(mut cfg) => { |
87 | // the file exists, parse the toml and return the struct | ||
88 | let mut cfg_data = String::new(); | 120 | let mut cfg_data = String::new(); |
89 | cfg.read_to_string(&mut cfg_data); | 121 | cfg.read_to_string(&mut cfg_data); |
90 | 122 | ||
@@ -93,26 +125,20 @@ fn gen_impl(ast: &DeriveInput, filename: String, filetype: String) -> TokenStrea | |||
93 | return Ok(config); | 125 | return Ok(config); |
94 | }, | 126 | }, |
95 | Err(ref e) if e.kind() == NotFound => { | 127 | Err(ref e) if e.kind() == NotFound => { |
96 | fs::create_dir_all(project.config_dir()); | 128 | fs::create_dir_all(#parent).map_err(FondantError::DirCreateErr)?; |
97 | let default_impl = #struct_ident::default(); | 129 | let default_impl = #struct_ident::default(); |
98 | Config::store(&default_impl)?; | 130 | Configure::store(&default_impl)?; |
99 | return Ok(default_impl); | 131 | return Ok(default_impl); |
100 | }, | 132 | }, |
101 | Err(e) => return Err(FondantError::LoadError), | 133 | Err(e) => return Err(FondantError::LoadError), |
102 | }; | 134 | }; |
103 | } | 135 | } |
104 | fn store(&self) -> Result<(), FondantError> { | 136 | fn store(&self) -> Result<(), FondantError> { |
105 | let project = ProjectDirs::from("rs", "", #struct_name).ok_or(FondantError::InvalidHomeDir)?; | ||
106 | let config_dir = project.config_dir(); | ||
107 | |||
108 | let tip = (Path::new(#filename)).with_extension(&#filetype); | ||
109 | let config_file: PathBuf = [config_dir, &tip].iter().collect(); | ||
110 | |||
111 | let mut f = OpenOptions::new() | 137 | let mut f = OpenOptions::new() |
112 | .write(true) | 138 | .write(true) |
113 | .create(true) | 139 | .create(true) |
114 | .truncate(true) | 140 | .truncate(true) |
115 | .open(config_file) | 141 | .open(#config_file) |
116 | .map_err(|_| FondantError::FileOpenError)?; | 142 | .map_err(|_| FondantError::FileOpenError)?; |
117 | 143 | ||
118 | let s = #ser::#ser_fn(self).map_err(|_| FondantError::ConfigParseError)?; | 144 | let s = #ser::#ser_fn(self).map_err(|_| FondantError::ConfigParseError)?; |