diff --git a/macon-config.schema.json b/macon-config.schema.json index 048d383..f3cfbad 100644 --- a/macon-config.schema.json +++ b/macon-config.schema.json @@ -55,6 +55,41 @@ } } } + }, + "extend_types": { + "description": "Override pathes that should implement Extend", + "type": "object", + "properties": { + "includes": { + "description": "Pathes that should implement Extend", + "type": "array", + "items": { + "oneOf": [ + { + "description": "Path that should implement Extend", + "type": "string" + }, + { + "description": "Path and associated wrapped type", + "type": "object", + "properties": { + "path": { + "description": "Path that should implement Extend", + "type": "string" + }, + "wrapped": { + "description": "Contained type", + "type": "string" + }, + "required": [ + "path" + ] + } + } + ] + } + } + } } } } diff --git a/macon_api/src/lib.rs b/macon_api/src/lib.rs index d225329..48208bb 100644 --- a/macon_api/src/lib.rs +++ b/macon_api/src/lib.rs @@ -3,7 +3,10 @@ //! See it for all details. //! -use std::fmt::Debug; +use std::{ + fmt::Debug, + vec, +}; /// Builder field type when building struct implementing [`Default`]. #[derive(Default,)] @@ -25,6 +28,14 @@ pub enum Defaulting { Set(T), } +/// Builder field type when target implment [`Extend`]. +pub struct Extending { + /// Collecting items + items: Vec, + /// Building value + value: C, +} + /// Builder field type for `Panic` or `Result` mode. #[derive(Default,)] pub enum Building { @@ -82,6 +93,41 @@ impl Defaulting { } } +impl Default for Extending where C: Default { + fn default() -> Self { + Self { + items: Default::default(), + value: Default::default(), + } + } +} + +impl Extend for Extending { + /// Store `iter` values into `items` (until container is created) + fn extend>(&mut self, iter: T) { + self.items.extend(iter) + } +} + +impl IntoIterator for Extending { + type Item = I; + type IntoIter = vec::IntoIter; + fn into_iter(self) -> Self::IntoIter { + self.items.into_iter() + } +} + +impl Extending { + pub fn value_mut(&mut self) -> &mut C { + &mut self.value + } + + /// Consume to return `value` and collected `items`. + pub fn unwrap(self) -> (C, Vec) { + (self.value, self.items) + } +} + impl Debug for Building { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/macon_derive/src/config.rs b/macon_derive/src/config.rs index c400a22..742b36b 100644 --- a/macon_derive/src/config.rs +++ b/macon_derive/src/config.rs @@ -44,7 +44,8 @@ pub fn get() -> &'static anyhow::Result { #[derive(Debug,)] pub struct Configuration { default_types: TypeSet, - option_types: TypeSet, + option_types: TypeSet, + extend_types: TypeSet, } pub trait TypeSetItem { @@ -133,7 +134,12 @@ impl Default for Configuration { fn default() -> Self { let default_types = Configuration::default_default_types(); let option_types = Configuration::default_option_types(); - Self { default_types, option_types, } + let extend_types = Configuration::default_extend_types(); + Self { + default_types, + option_types, + extend_types, + } } } @@ -157,10 +163,15 @@ impl Configuration { crate_config.option_types.map_includes(|i| i.into()), Self::default_option_types, ); + let extend_types = TypeSet::create( + crate_config.extend_types.map_includes(|i| i.into()), + Self::default_extend_types, + ); let this = Self { default_types, option_types, + extend_types, }; #[cfg(feature = "debug")] eprintln!("Merge configuration\n{:#?}", this); @@ -197,18 +208,26 @@ impl Configuration { .add_path("std::collections::hash_set::HashSet") } - pub fn default_option_types() -> TypeSet { + pub fn default_option_types() -> TypeSet { TypeSet::default() .add_path("std::option::Option") .add_path("core::option::Option") } + pub fn default_extend_types() -> TypeSet { + TypeSet::default() + .add_path("std::vec::Vec") + } + pub fn default_types(&self) -> &TypeSet { &self.default_types } - pub fn option_types(&self) -> &TypeSet { + pub fn option_types(&self) -> &TypeSet { &self.option_types } + pub fn extend_types(&self) -> &TypeSet { + &self.extend_types + } } #[derive(Debug,Deserialize,)] @@ -218,31 +237,33 @@ struct CrateConfiguration { pub version: String, #[serde(default)] pub default_types: TypeSetConfiguration, - #[serde(default = "default_typesetconfiguration", deserialize_with="deserialize_crateconfiguration_option_types")] - pub option_types: TypeSetConfiguration, + #[serde(default = "default_typesetconfiguration", deserialize_with="deserialize_crateconfiguration_container_types")] + pub option_types: TypeSetConfiguration, + #[serde(default = "default_typesetconfiguration", deserialize_with="deserialize_crateconfiguration_container_types")] + pub extend_types: TypeSetConfiguration, } -fn deserialize_crateconfiguration_option_types<'de, D>(deserializer: D) -> std::result::Result, D::Error> +fn deserialize_crateconfiguration_container_types<'de, D>(deserializer: D) -> std::result::Result, D::Error> where D: Deserializer<'de>, { let decoded: TypeSetConfiguration = Deserialize::deserialize(deserializer)?; - Ok(decoded.map_includes(OptionTypeSetItem::from)) + Ok(decoded.map_includes(ContainerTypeSetItem::from)) } #[derive(Debug,Default,Deserialize,PartialEq,)] -pub struct OptionTypeSetItem { +pub struct ContainerTypeSetItem { pub path: String, pub wrapped: Option, } -impl TypeSetItem for OptionTypeSetItem { +impl TypeSetItem for ContainerTypeSetItem { fn path(&self) -> String { self.path.clone() } } -impl From<&str> for OptionTypeSetItem { +impl From<&str> for ContainerTypeSetItem { fn from(path: &str) -> Self { Self { path: path.to_owned(), @@ -255,7 +276,7 @@ impl From<&str> for OptionTypeSetItem { #[serde(untagged)] pub enum OptionTypeSetItemConfiguration { String(String), - Item(OptionTypeSetItem), + Item(ContainerTypeSetItem), } impl Default for OptionTypeSetItemConfiguration { @@ -264,7 +285,7 @@ impl Default for OptionTypeSetItemConfiguration { } } -impl From for OptionTypeSetItem { +impl From for ContainerTypeSetItem { fn from(value: OptionTypeSetItemConfiguration) -> Self { match value { OptionTypeSetItemConfiguration::String(string) => string.as_str().into(), @@ -402,7 +423,7 @@ mod tests { let mut option_types_includes = option_types.includes.iter(); assert_eq!( option_types_includes.next(), - Some(&OptionTypeSetItem { + Some(&ContainerTypeSetItem { path: "AsString".to_owned(), wrapped: None, }), @@ -411,19 +432,19 @@ mod tests { assert_eq!( option_types_includes.next(), - Some(&OptionTypeSetItem { path: "AsItemWithoutWrapped".to_owned(), wrapped: None, }), + Some(&ContainerTypeSetItem { path: "AsItemWithoutWrapped".to_owned(), wrapped: None, }), "option_types.includes[1]\n{:#?}", config ); assert_eq!( option_types_includes.next(), - Some(&OptionTypeSetItem { path: "AsItemWithShortWrapped".to_owned(), wrapped: Some("ShortWrapped".to_string()), }), + Some(&ContainerTypeSetItem { path: "AsItemWithShortWrapped".to_owned(), wrapped: Some("ShortWrapped".to_string()), }), "option_types.includes[2]\n{:#?}", config ); assert_eq!( option_types_includes.next(), - Some(&OptionTypeSetItem { path: "AsItemWithFullWrapped".to_owned(), wrapped: Some("::full::path::FullWrapped".to_string()), }), + Some(&ContainerTypeSetItem { path: "AsItemWithFullWrapped".to_owned(), wrapped: Some("::full::path::FullWrapped".to_string()), }), "option_types.includes[3]\n{:#?}", config ); @@ -431,5 +452,42 @@ mod tests { let mut option_types_excludes = option_types.excludes.iter(); assert_eq!(option_types_excludes.next(), None, "option_types.excludes[0]\n{:#?}", config); + + + let extend_types = &config.extend_types; + assert_eq!(extend_types.defaults, true, "extend_types.defaults"); + + let mut extend_types_includes = extend_types.includes.iter(); + assert_eq!( + extend_types_includes.next(), + Some(&ContainerTypeSetItem { + path: "AsString".to_owned(), + wrapped: None, + }), + "extend_types.includes[0]\n{:#?}", config + ); + + assert_eq!( + extend_types_includes.next(), + Some(&ContainerTypeSetItem { path: "AsItemWithoutWrapped".to_owned(), wrapped: None, }), + "extend_types.includes[1]\n{:#?}", config + ); + + assert_eq!( + extend_types_includes.next(), + Some(&ContainerTypeSetItem { path: "AsItemWithShortWrapped".to_owned(), wrapped: Some("ShortWrapped".to_string()), }), + "extend_types.includes[2]\n{:#?}", config + ); + + assert_eq!( + extend_types_includes.next(), + Some(&ContainerTypeSetItem { path: "AsItemWithFullWrapped".to_owned(), wrapped: Some("::full::path::FullWrapped".to_string()), }), + "extend_types.includes[3]\n{:#?}", config + ); + + assert!(extend_types_includes.next().is_none(), "extend_types.includes[4]\n{:#?}", config); + + let mut extend_types_excludes = extend_types.excludes.iter(); + assert_eq!(extend_types_excludes.next(), None, "extend_types.excludes[0]\n{:#?}", config); } } diff --git a/macon_derive/src/model.rs b/macon_derive/src/model.rs index e9f25de..6e9ea5a 100644 --- a/macon_derive/src/model.rs +++ b/macon_derive/src/model.rs @@ -7,7 +7,8 @@ use proc_macro2::{ }; use quote::{ format_ident, - quote, ToTokens, + quote, + ToTokens, }; use syn::{ Attribute, @@ -135,6 +136,7 @@ impl From for Setting { pub struct PropertySettings { pub option: Setting, pub default: Setting<()>, + pub extend: Setting, } #[derive(Debug,Default,)] @@ -432,6 +434,13 @@ impl Property { settings.option = Setting::enable(ty); } } + if settings.extend.is_undefined() { + if let Some(ty) = Self::get_extend_arg(&field.ty)? { + #[cfg(feature = "debug")] + eprintln!("{}.{}: Extend<{}>", builder.ident, name, ty.to_token_stream()); + settings.extend = Setting::enable(ty); + } + } if settings.default.is_undefined() { let default_types = match crate::config::get() { Ok(config) => config.default_types(), @@ -489,6 +498,19 @@ impl Property { }) } + pub fn get_extend_arg(ty: &Type) -> Result> { + let config = match crate::config::get() { + Ok(config) => config, + Err(error) => return Err(Error::new_spanned(ty, error)), + }; + + Ok(if let Some(_item) = config.extend_types().match_type(ty) { + None + } else { + None + }) + } + pub fn id(&self) -> TokenStream { if self.is_tuple { let literal = Literal::usize_unsuffixed(self.ordinal); @@ -536,6 +558,7 @@ impl Property { } } + /// Not option, field default or struct default. pub fn is_required(&self) -> bool { ! self.option.is_enabled() && ! self.default.is_enabled() && diff --git a/macon_derive/tests/config/macon-config-parse.yaml b/macon_derive/tests/config/macon-config-parse.yaml index d2b1ede..41fb1c6 100644 --- a/macon_derive/tests/config/macon-config-parse.yaml +++ b/macon_derive/tests/config/macon-config-parse.yaml @@ -13,3 +13,12 @@ option_types: wrapped: ShortWrapped - path: AsItemWithFullWrapped wrapped: ::full::path::FullWrapped + +extend_types: + includes: + - AsString + - path: AsItemWithoutWrapped + - path: AsItemWithShortWrapped + wrapped: ShortWrapped + - path: AsItemWithFullWrapped + wrapped: ::full::path::FullWrapped diff --git a/tests/blueprint_panic_extend_vec.rs b/tests/blueprint_panic_extend_vec.rs new file mode 100644 index 0000000..967571c --- /dev/null +++ b/tests/blueprint_panic_extend_vec.rs @@ -0,0 +1,167 @@ +// ############################################################################# +// ################################### INPUT ################################### +// ############################################################################# +use std::path::PathBuf; + +#[derive(PartialEq,Debug)] +struct DefaultStructNamed { + list: Vec, +} + +impl Default for DefaultStructNamed { + fn default() -> Self { + Self { + list: vec![ + "a", + "b", + "c", + ] + .into_iter() + .map(PathBuf::from) + .collect(), + } + } +} + +// ############################################################################# +// ############################## IMPLEMENTATION ############################### +// ############################################################################# + +// impl_target +impl DefaultStructNamed { + pub fn builder() -> DefaultStructNamedBuilder { + ::default() + } +} + +// struct_builder +#[derive(Default,)] +struct DefaultStructNamedBuilder { + list: ::macon::Extending<::macon::Keeping<::macon::Defaulting>>, PathBuf>, +} + +// impl_builder +impl DefaultStructNamedBuilder { + // impl_builder / impl_builder_setters + pub fn list>>(mut self, list: LIST) -> DefaultStructNamedBuilder { + *self.list.value_mut() = ::macon::Keeping::Set(::macon::Defaulting::Set(list.into())); + self + } + pub fn list_keep(mut self) -> DefaultStructNamedBuilder { + *self.list.value_mut() = ::macon::Keeping::Keep; + self + } + pub fn list_default(mut self) -> DefaultStructNamedBuilder { + *self.list.value_mut() = ::macon::Keeping::Set(::macon::Defaulting::Default); + self + } + + pub fn list_extend, LIST: ::std::iter::IntoIterator>(mut self, list: LIST) -> DefaultStructNamedBuilder { + self.list.extend(list.into_iter().map(Into::into)); + self + } + + // impl_builder / impl_builder_build + pub fn build(self) -> DefaultStructNamed { + let mut built = ::default(); + + let (list, listitems) = self.list.unwrap(); + if list.is_set() { + built.list = list.unwrap().unwrap(); + } + built.list.extend(listitems); + + built + } +} + +// impl_builder / impl_builder_from +impl ::core::convert::From for DefaultStructNamed { + fn from(builder: DefaultStructNamedBuilder) -> Self { + builder.build() + } +} + +// ############################################################################# +// ################################### TESTS ################################### +// ############################################################################# + +#[test] +fn defaultstructnamed_builder_struct_default() { + let built = DefaultStructNamed::builder() + .build(); + + assert_eq!( + DefaultStructNamed { + list: vec![ + PathBuf::from("a"), + PathBuf::from("b"), + PathBuf::from("c"), + ], + }, + built, + ); +} + +#[test] +fn defaultstructnamed_builder_keep_extend() { + let built = DefaultStructNamed::builder() + .list_keep() + .list_extend(&["d", "e", "f",]) + .build(); + + assert_eq!( + DefaultStructNamed { + list: vec![ + PathBuf::from("a"), + PathBuf::from("b"), + PathBuf::from("c"), + PathBuf::from("d"), + PathBuf::from("e"), + PathBuf::from("f"), + ], + }, + built, + ); +} + +#[test] +fn defaultstructnamed_builder_default_extend() { + let built = DefaultStructNamed::builder() + .list_default() + .list_extend(&["d", "e", "f",]) + .build(); + + assert_eq!( + DefaultStructNamed { + list: vec![ + PathBuf::from("d"), + PathBuf::from("e"), + PathBuf::from("f"), + ], + }, + built, + ); +} + +#[test] +fn defaultstructnamed_builder_set_extend() { + let built = DefaultStructNamed::builder() + .list(vec!["g", "h", "i",].into_iter().map(PathBuf::from).collect::>()) + .list_extend(&["d", "e", "f",]) + .build(); + + assert_eq!( + DefaultStructNamed { + list: vec![ + PathBuf::from("g"), + PathBuf::from("h"), + PathBuf::from("i"), + PathBuf::from("d"), + PathBuf::from("e"), + PathBuf::from("f"), + ], + }, + built, + ); +}