aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fondant_derive/Cargo.toml1
-rw-r--r--fondant_derive/src/lib.rs114
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]
8proc-macro2 = "1.0.9" 8proc-macro2 = "1.0.9"
9quote = "1.0" 9quote = "1.0"
10directories = "2.0"
10 11
11[dependencies.syn] 12[dependencies.syn]
12version = "1.0" 13version = "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 @@
1extern crate proc_macro; 1extern crate proc_macro;
2 2
3use ::std::ffi::{OsStr, OsString};
4use ::std::path::{Path, PathBuf};
5use directories::ProjectDirs;
3use proc_macro::TokenStream; 6use proc_macro::TokenStream;
4use proc_macro2::Span; 7use proc_macro2::Span;
5use quote::quote; 8use quote::quote;
6use syn::{parse_macro_input, DeriveInput, Error, Ident, Lit, Meta, MetaNameValue, Result}; 9use 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)]
12struct ConfigPath {
13 parent: Option<PathBuf>,
14 filename: Option<OsString>,
15 extension: Option<OsString>,
16}
17
18#[proc_macro_derive(Configure, attributes(config_file))]
9pub fn config_attribute(item: TokenStream) -> TokenStream { 19pub 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
20fn extract_attributes(ast: &DeriveInput) -> (Option<String>, Option<String>) { 26fn 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
46fn map_ser(ext: &str) -> (Ident, Ident) { 54fn 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
64fn gen_impl(ast: &DeriveInput, filename: String, filetype: String) -> TokenStream { 78fn 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)?;