diff options
author | Akshay <[email protected]> | 2020-03-15 16:24:52 +0000 |
---|---|---|
committer | Akshay <[email protected]> | 2020-03-15 16:24:52 +0000 |
commit | a4e98bf35f1d1e55c6471aba51851f122e018370 (patch) | |
tree | 482e5c6a29fb0221ffa9b9eb7b7a65f17ce09f25 /confondant_derive | |
parent | e37ff7725a37854be50c06693cc0f71e72847f19 (diff) |
add derive macros
Diffstat (limited to 'confondant_derive')
-rw-r--r-- | confondant_derive/.gitignore | 10 | ||||
-rw-r--r-- | confondant_derive/Cargo.toml | 16 | ||||
-rw-r--r-- | confondant_derive/src/lib.rs | 125 |
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 | ||
7 | Cargo.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] | ||
2 | name = "confondant_derive" | ||
3 | version = "0.1.0" | ||
4 | authors = ["Akshay <[email protected]>"] | ||
5 | edition = "2018" | ||
6 | |||
7 | [dependencies] | ||
8 | proc-macro2 = "1.0.9" | ||
9 | quote = "1.0" | ||
10 | |||
11 | [dependencies.syn] | ||
12 | version = "1.0" | ||
13 | features = ["full"] | ||
14 | |||
15 | [lib] | ||
16 | proc-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 @@ | |||
1 | extern crate proc_macro; | ||
2 | |||
3 | use proc_macro::TokenStream; | ||
4 | use proc_macro2::Span; | ||
5 | use quote::quote; | ||
6 | use syn::{parse_macro_input, DeriveInput, Error, Ident, Lit, Meta, MetaNameValue, Result}; | ||
7 | |||
8 | #[proc_macro_derive(Config, attributes(filename, extension))] | ||
9 | pub 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 | |||
20 | fn 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 | |||
46 | fn 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 | |||
64 | fn 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 | } | ||