From bcd6cc3d46b87288435dd4c51a006a6c34f7aa9d Mon Sep 17 00:00:00 2001 From: Ivan Nikulin Date: Sat, 20 Jan 2024 00:34:36 +0000 Subject: [PATCH] Add support for settings collections, add more basic impls --- foundations/src/settings/basic_impls.rs | 133 +++++++++++++++++++----- foundations/src/settings/mod.rs | 25 +++-- foundations/tests/data/with_vec.yaml | 15 +++ foundations/tests/settings.rs | 15 +++ 4 files changed, 157 insertions(+), 31 deletions(-) create mode 100644 foundations/tests/data/with_vec.yaml diff --git a/foundations/src/settings/basic_impls.rs b/foundations/src/settings/basic_impls.rs index 0c27572..e1e0fd1 100644 --- a/foundations/src/settings/basic_impls.rs +++ b/foundations/src/settings/basic_impls.rs @@ -1,45 +1,128 @@ use super::Settings; +use indexmap::IndexSet; +use serde::de::DeserializeOwned; +use serde::Serialize; +use std::cmp::Reverse; +use std::collections::{BinaryHeap, LinkedList, VecDeque}; +use std::ffi::{CString, OsString}; +use std::fmt::Debug; +use std::hash::Hash; +use std::marker::PhantomData; +use std::num::Wrapping; +use std::ops::Range; +use std::path::PathBuf; +use std::rc::Rc; +use std::sync::Arc; +use std::time::Duration; -macro_rules! impl_for_basic_types { - ($($ty:ty),*) => { - $(impl Settings for $ty {})* +macro_rules! impl_noop { + ( $( $impl_desc:tt )* ) => { + impl $( $impl_desc )* { + #[inline] + fn add_docs( + &self, + _parent_key: &[String], + _docs: &mut std::collections::HashMap, &'static [&'static str]>, + ) { + } + } + }; +} + +impl_noop!( Settings for PhantomData where T: 'static); +impl_noop!( Settings for [T; 0] where T: Debug + Clone + 'static); +impl_noop!( Settings for Range where Idx: Debug + Serialize + DeserializeOwned + Clone + Default + 'static); +impl_noop!( Settings for Reverse where T: Settings); +impl_noop!( Settings for Wrapping where T: Settings); + +macro_rules! impl_for_non_generic { + ( $( $Ty:ty ),* ) => { + $( impl_noop!(Settings for $Ty); )* }; } -impl_for_basic_types![ +impl_for_non_generic! { bool, - isize, - i8, + char, + f32, + f64, + i128, i16, i32, i64, - usize, - u8, + i8, + isize, + u128, u16, u32, u64, - f32, - f64, - char, + u8, + usize, String, - std::path::PathBuf, - () -]; + (), + CString, + OsString, + Duration, + PathBuf +} -impl Settings for Vec { - fn add_docs( - &self, - parent_key: &[String], - docs: &mut std::collections::HashMap, &'static [&'static str]>, - ) { - for (k, v) in self.iter().enumerate() { - let mut key = parent_key.to_vec(); +macro_rules! impl_for_ref { + ( $( $impl_desc:tt )* ) => { + impl $( $impl_desc )* { + #[inline] + fn add_docs( + &self, + parent_key: &[String], + docs: &mut std::collections::HashMap, &'static [&'static str]>, + ) { + (**self).add_docs(parent_key, docs); + } + } + }; +} + +impl_for_ref!( Settings for Box where T: Settings); +impl_for_ref!( Settings for Rc where T: Settings, Rc: Serialize + DeserializeOwned); +impl_for_ref!( Settings for Arc where T: Settings, Arc: Serialize + DeserializeOwned); - key.push(k.to_string()); +macro_rules! impl_for_seq { + ( $( $impl_desc:tt )* ) => { + impl $( $impl_desc )* { + fn add_docs( + &self, + parent_key: &[String], + docs: &mut std::collections::HashMap, &'static [&'static str]>, + ) { + let mut key = parent_key.to_vec(); - v.add_docs(&key, docs); + for (k, v) in self.iter().enumerate() { + + key.push(k.to_string()); + v.add_docs(&key, docs); + key.pop(); + } + } } - } + }; +} + +impl_for_seq!( Settings for Vec where T: Settings); +impl_for_seq!( Settings for VecDeque where T: Settings); +impl_for_seq!( Settings for BinaryHeap where T: Settings + Ord); +impl_for_seq!( Settings for IndexSet where T: Settings + Eq + Hash); +impl_for_seq!( Settings for LinkedList where T: Settings); + +macro_rules! impl_for_array { + ( $( $len:tt )* ) => { + $( impl_for_seq!( Settings for [T; $len] where T: Settings); )* + }; +} + +impl_for_array! { + 1 2 3 4 5 6 7 8 9 10 + 11 12 13 14 15 16 17 18 19 20 + 21 22 23 24 25 26 27 28 29 30 + 31 32 } impl Settings for Option { diff --git a/foundations/src/settings/mod.rs b/foundations/src/settings/mod.rs index b69221c..b8ad830 100644 --- a/foundations/src/settings/mod.rs +++ b/foundations/src/settings/mod.rs @@ -319,9 +319,7 @@ pub use foundations_macros::settings; /// [`settings`] macro. /// /// [`settings`]: crate::settings::settings -pub trait Settings: - Default + Send + Sync + Clone + Serialize + DeserializeOwned + Debug + 'static -{ +pub trait Settings: Default + Clone + Serialize + DeserializeOwned + Debug + 'static { /// Add Rust doc comments for the settings fields. /// /// Docs for each field need to be added to the provided hashmap with the key consisting of the @@ -403,16 +401,19 @@ pub trait Settings: /// Serialize documented settings as a YAML string. pub fn to_yaml_string(settings: &impl Settings) -> BootstrapResult { + const LIST_ITEM_PREFIX: &str = "- "; + let mut doc_comments = Default::default(); let yaml = serde_yaml::to_string(settings)?; let mut yaml_with_docs = String::new(); let mut key_stack = vec![]; + let mut list_index = 0; settings.add_docs(&[], &mut doc_comments); // We read each line of the uncommented YAML, and push each key we find to `key_stack`. for line in yaml.lines() { - let spaces = line.find(|c: char| !c.is_whitespace()).unwrap_or(0); + let mut spaces = line.find(|c: char| !c.is_whitespace()).unwrap_or(0); // This is where we remove the keys we have just handled, by truncating the length // of the key stack based on how much indentation the current line got. serde_yaml @@ -421,9 +422,21 @@ pub fn to_yaml_string(settings: &impl Settings) -> BootstrapResult { key_stack.truncate(spaces / 2); if let Some(colon_idx) = line.find(':') { - let field_name = &line[spaces..colon_idx].trim(); + let mut field_name = line[spaces..colon_idx].trim().to_string(); + let is_list_item = field_name.starts_with(LIST_ITEM_PREFIX); + + // NOTE: if we have a list item, then append the index of the item to the key stack. + if is_list_item { + key_stack.push(list_index.to_string()); + + field_name = field_name[LIST_ITEM_PREFIX.len()..].trim().to_string(); + spaces += LIST_ITEM_PREFIX.len(); + list_index += 1; + } else { + list_index = 0; + } - key_stack.push(field_name.to_string()); + key_stack.push(field_name); // The field described by the current line has some documentation, so // we print it before the current line. diff --git a/foundations/tests/data/with_vec.yaml b/foundations/tests/data/with_vec.yaml new file mode 100644 index 0000000..30a94f0 --- /dev/null +++ b/foundations/tests/data/with_vec.yaml @@ -0,0 +1,15 @@ +--- +# Items +items: + # A field, which is named the same as another field. + - a: 0 + # multi-line + # doc comment + b: 11 + c: 0 + # A field, which is named the same as another field. + - a: 0 + # multi-line + # doc comment + b: 11 + c: 0 \ No newline at end of file diff --git a/foundations/tests/settings.rs b/foundations/tests/settings.rs index e54c18a..0ded87c 100644 --- a/foundations/tests/settings.rs +++ b/foundations/tests/settings.rs @@ -134,6 +134,12 @@ impl Default for NoDefaultEnum { } } +#[settings] +struct WithVec { + /// Items + items: Vec, +} + mod foundations_reexport { pub(crate) mod nested { pub(crate) use foundations::*; @@ -241,3 +247,12 @@ fn option() { assert_ser_eq!(s, "data/with_option_none.yaml"); } + +#[test] +fn vec() { + let s = WithVec { + items: vec![Default::default(), Default::default()], + }; + + assert_ser_eq!(s, "data/with_vec.yaml"); +}