aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAkshay <[email protected]>2020-03-15 16:24:52 +0000
committerAkshay <[email protected]>2020-03-15 16:24:52 +0000
commita4e98bf35f1d1e55c6471aba51851f122e018370 (patch)
tree482e5c6a29fb0221ffa9b9eb7b7a65f17ce09f25
parente37ff7725a37854be50c06693cc0f71e72847f19 (diff)
add derive macros
-rw-r--r--confondant_derive/.gitignore10
-rw-r--r--confondant_derive/Cargo.toml16
-rw-r--r--confondant_derive/src/lib.rs125
3 files changed, 151 insertions, 0 deletions
diff --git a/confondant_derive/.gitignore b/confondant_derive/.gitignore
new file mode 100644
index 0000000..088ba6b
--- /dev/null
+++ b/confondant_derive/.gitignore
@@ -0,0 +1,10 @@
1# Generated by Cargo
2# will have compiled files and executables
3/target/
4
5# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
6# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
7Cargo.lock
8
9# These are backup files generated by rustfmt
10**/*.rs.bk
diff --git a/confondant_derive/Cargo.toml b/confondant_derive/Cargo.toml
new file mode 100644
index 0000000..4b94b57
--- /dev/null
+++ b/confondant_derive/Cargo.toml
@@ -0,0 +1,16 @@
1[package]
2name = "confondant_derive"
3version = "0.1.0"
4authors = ["Akshay <[email protected]>"]
5edition = "2018"
6
7[dependencies]
8proc-macro2 = "1.0.9"
9quote = "1.0"
10
11[dependencies.syn]
12version = "1.0"
13features = ["full"]
14
15[lib]
16proc-macro = true
diff --git a/confondant_derive/src/lib.rs b/confondant_derive/src/lib.rs
new file mode 100644
index 0000000..076a66e
--- /dev/null
+++ b/confondant_derive/src/lib.rs
@@ -0,0 +1,125 @@
1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use proc_macro2::Span;
5use quote::quote;
6use syn::{parse_macro_input, DeriveInput, Error, Ident, Lit, Meta, MetaNameValue, Result};
7
8#[proc_macro_derive(Config, attributes(filename, extension))]
9pub fn config_attribute(item: TokenStream) -> TokenStream {
10 let ast: DeriveInput = parse_macro_input!(item as DeriveInput);
11 let (filename, filetype) = extract_attributes(&ast);
12
13 gen_impl(
14 &ast,
15 filename.unwrap_or("config".into()),
16 filetype.unwrap_or("toml".into()),
17 )
18}
19
20fn extract_attributes(ast: &DeriveInput) -> (Option<String>, Option<String>) {
21 let mut filename: Option<String> = None;
22 let mut filetype: Option<String> = None;
23 for option in ast.attrs.iter() {
24 let option = option.parse_meta().unwrap();
25 match option {
26 Meta::NameValue(MetaNameValue {
27 ref path, ref lit, ..
28 }) if path.is_ident("filename") => {
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 {
37 filetype = Some(f.value());
38 }
39 }
40 _ => {}
41 }
42 }
43 return (filename, filetype);
44}
45
46fn map_ser(ext: &str) -> (Ident, Ident) {
47 match ext.as_ref() {
48 "toml" => (
49 Ident::new("toml", Span::call_site()),
50 Ident::new("to_string_pretty", Span::call_site()),
51 ),
52 "yaml" => (
53 Ident::new("serde_yaml", Span::call_site()),
54 Ident::new("to_string", Span::call_site()),
55 ),
56 "json" => (
57 Ident::new("serde_json", Span::call_site()),
58 Ident::new("to_string_pretty", Span::call_site()),
59 ),
60 _ => panic!("Invalid extension!"),
61 }
62}
63
64fn gen_impl(ast: &DeriveInput, filename: String, filetype: String) -> TokenStream {
65 let struct_name = &ast.ident.to_string();
66 let struct_ident = &ast.ident;
67 let (ser, ser_fn) = map_ser(&filetype);
68
69 let gen = quote! {
70 use ::confondant::{ ProjectDirs, toml, serde_json, serde_yaml, FondantError };
71 use ::std::path::{ Path, PathBuf };
72 use ::std::option::Option;
73 use ::std::fs::{self, File, OpenOptions};
74 use ::std::io::prelude::*;
75 use ::std::io::{ ErrorKind::NotFound, Write };
76
77 impl Config for #struct_ident {
78 fn load() -> Result<#struct_ident, FondantError> {
79 let project = ProjectDirs::from("rs", "", #struct_name).ok_or(FondantError::InvalidHomeDir)?;
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) => {
87 // the file exists, parse the toml and return the struct
88 let mut cfg_data = String::new();
89 cfg.read_to_string(&mut cfg_data);
90
91 let config: #struct_ident = #ser::from_str(&cfg_data[..])
92 .map_err(|_| FondantError::ConfigParseError)?;
93 return Ok(config);
94 },
95 Err(ref e) if e.kind() == NotFound => {
96 fs::create_dir_all(project.config_dir());
97 let default_impl = #struct_ident::default();
98 Config::store(&default_impl)?;
99 return Ok(default_impl);
100 },
101 Err(e) => return Err(FondantError::LoadError),
102 };
103 }
104 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()
112 .write(true)
113 .create(true)
114 .truncate(true)
115 .open(config_file)
116 .map_err(|_| FondantError::FileOpenError)?;
117
118 let s = #ser::#ser_fn(self).map_err(|_| FondantError::ConfigParseError)?;
119 f.write_all(s.as_bytes()).map_err(|_| FondantError::FileWriteError);
120 Ok(())
121 }
122 }
123 };
124 gen.into()
125}