Skip to content

Commit

Permalink
Add support for settings collections, add more basic impls
Browse files Browse the repository at this point in the history
  • Loading branch information
inikulin committed Jan 20, 2024
1 parent d73de5f commit bcd6cc3
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 31 deletions.
133 changes: 108 additions & 25 deletions foundations/src/settings/basic_impls.rs
Original file line number Diff line number Diff line change
@@ -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<Vec<String>, &'static [&'static str]>,
) {
}
}
};
}

impl_noop!(<T> Settings for PhantomData<T> where T: 'static);
impl_noop!(<T> Settings for [T; 0] where T: Debug + Clone + 'static);
impl_noop!(<Idx> Settings for Range<Idx> where Idx: Debug + Serialize + DeserializeOwned + Clone + Default + 'static);
impl_noop!(<T> Settings for Reverse<T> where T: Settings);
impl_noop!(<T> Settings for Wrapping<T> 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<T: Settings> Settings for Vec<T> {
fn add_docs(
&self,
parent_key: &[String],
docs: &mut std::collections::HashMap<Vec<String>, &'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<Vec<String>, &'static [&'static str]>,
) {
(**self).add_docs(parent_key, docs);
}
}
};
}

impl_for_ref!(<T> Settings for Box<T> where T: Settings);
impl_for_ref!(<T> Settings for Rc<T> where T: Settings, Rc<T>: Serialize + DeserializeOwned);
impl_for_ref!(<T> Settings for Arc<T> where T: Settings, Arc<T>: 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<Vec<String>, &'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!(<T> Settings for Vec<T> where T: Settings);
impl_for_seq!(<T> Settings for VecDeque<T> where T: Settings);
impl_for_seq!(<T> Settings for BinaryHeap<T> where T: Settings + Ord);
impl_for_seq!(<T> Settings for IndexSet<T> where T: Settings + Eq + Hash);
impl_for_seq!(<T> Settings for LinkedList<T> where T: Settings);

macro_rules! impl_for_array {
( $( $len:tt )* ) => {
$( impl_for_seq!(<T> 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<T: Settings> Settings for Option<T> {
Expand Down
25 changes: 19 additions & 6 deletions foundations/src/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -403,16 +401,19 @@ pub trait Settings:

/// Serialize documented settings as a YAML string.
pub fn to_yaml_string(settings: &impl Settings) -> BootstrapResult<String> {
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
Expand All @@ -421,9 +422,21 @@ pub fn to_yaml_string(settings: &impl Settings) -> BootstrapResult<String> {
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.
Expand Down
15 changes: 15 additions & 0 deletions foundations/tests/data/with_vec.yaml
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions foundations/tests/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ impl Default for NoDefaultEnum {
}
}

#[settings]
struct WithVec {
/// Items
items: Vec<NestedStruct>,
}

mod foundations_reexport {
pub(crate) mod nested {
pub(crate) use foundations::*;
Expand Down Expand Up @@ -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");
}

0 comments on commit bcd6cc3

Please sign in to comment.