From 897705611c2004f4446119512a277cd9d520f4b1 Mon Sep 17 00:00:00 2001 From: Akshay Date: Mon, 16 Mar 2020 14:14:58 +0530 Subject: add simplified meta syntax, path spec features --- fondant_derive/Cargo.toml | 1 + 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" [dependencies] proc-macro2 = "1.0.9" quote = "1.0" +directories = "2.0" [dependencies.syn] 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 @@ extern crate proc_macro; +use ::std::ffi::{OsStr, OsString}; +use ::std::path::{Path, PathBuf}; +use directories::ProjectDirs; 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))] +#[derive(Debug, Default)] +struct ConfigPath { + parent: Option, + filename: Option, + extension: Option, +} + +#[proc_macro_derive(Configure, attributes(config_file))] pub fn config_attribute(item: TokenStream) -> TokenStream { let ast: DeriveInput = parse_macro_input!(item as DeriveInput); - let (filename, filetype) = extract_attributes(&ast); + let cfg_path = extract_attributes(&ast); - gen_impl( - &ast, - filename.unwrap_or("config".into()), - filetype.unwrap_or("toml".into()), - ) + gen_impl(&ast, cfg_path) } -fn extract_attributes(ast: &DeriveInput) -> (Option, Option) { - let mut filename: Option = None; - let mut filetype: Option = None; +fn extract_attributes(ast: &DeriveInput) -> ConfigPath { 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 path.is_ident("config_file") => { if let Lit::Str(f) = lit { - filetype = Some(f.value()); + let f = f.value(); + let fp = Path::new(&f); + let blank = Path::new(""); + let parent = match fp.parent() { + Some(blank) => None, + other => other, + }; + return ConfigPath { + parent: parent.map(Path::to_path_buf), + filename: fp.file_stem().map(OsStr::to_os_string), + extension: fp.extension().map(OsStr::to_os_string), + }; } } _ => {} } } - return (filename, filetype); + return Default::default(); } -fn map_ser(ext: &str) -> (Ident, Ident) { +fn pick_serializer(ext: &str) -> (Ident, Ident) { + /* returns serializer and a corresponding function to + * stringify with based on file extension + * toml::to_string_pretty + * serde_yaml::to_string + * serde_json::to_string_pretty + */ match ext.as_ref() { "toml" => ( Ident::new("toml", Span::call_site()), @@ -61,30 +75,48 @@ fn map_ser(ext: &str) -> (Ident, Ident) { } } -fn gen_impl(ast: &DeriveInput, filename: String, filetype: String) -> TokenStream { - let struct_name = &ast.ident.to_string(); +fn gen_impl(ast: &DeriveInput, cfg_path: ConfigPath) -> TokenStream { let struct_ident = &ast.ident; - let (ser, ser_fn) = map_ser(&filetype); + + let filename = cfg_path + .filename + .unwrap_or(OsStr::new("config").to_os_string()); + let filetype = cfg_path + .extension + .unwrap_or(OsStr::new("toml").to_os_string()); + + let filename = filename.into_string().unwrap(); + let filetype = filetype.into_string().unwrap(); + + let pkg_name = env!("CARGO_PKG_NAME"); + let project = ProjectDirs::from("rs", "", pkg_name).unwrap(); + let default_dir = project.config_dir(); + let config_dir = cfg_path.parent.as_deref().unwrap_or(default_dir); + + let tip = Path::new(&filename).with_extension(&filetype); + let config_file = [config_dir, tip.as_ref()] + .iter() + .collect::() + .into_os_string() + .into_string() + .unwrap(); + let parent: String = config_dir.to_str().unwrap().into(); + + let (ser, ser_fn) = pick_serializer(&filetype); let gen = quote! { - use ::fondant::{ ProjectDirs, toml, serde_json, serde_yaml, FondantError }; - use ::std::path::{ Path, PathBuf }; + use ::fondant::{Configure, ProjectDirs, toml, serde_json, serde_yaml, FondantError}; use ::std::option::Option; use ::std::fs::{self, File, OpenOptions}; use ::std::io::prelude::*; use ::std::io::{ ErrorKind::NotFound, Write }; + use ::std::ffi::{OsStr, OsString}; + use ::std::path::{Path, PathBuf}; - impl Config for #struct_ident { + impl Configure 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) { + 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); @@ -93,26 +125,20 @@ fn gen_impl(ast: &DeriveInput, filename: String, filetype: String) -> TokenStrea return Ok(config); }, Err(ref e) if e.kind() == NotFound => { - fs::create_dir_all(project.config_dir()); + fs::create_dir_all(#parent).map_err(FondantError::DirCreateErr)?; let default_impl = #struct_ident::default(); - Config::store(&default_impl)?; + Configure::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) + .open(#config_file) .map_err(|_| FondantError::FileOpenError)?; let s = #ser::#ser_fn(self).map_err(|_| FondantError::ConfigParseError)?; -- cgit v1.2.3