use std::fmt;
use std::marker::PhantomData;
use std::str::FromStr;
use educe::Educe;
use itertools::Itertools;
use serde::{Deserialize, Deserializer, Serialize};
use thiserror::Error;
pub use crate::define_list_builder_accessors;
pub use crate::define_list_builder_helper;
#[macro_export]
macro_rules! define_list_builder_helper {
{
$(#[ $docs_and_attrs:meta ])*
$vis:vis
struct $ListBuilder:ident $( [ $($generics:tt)* ] )?
$( where [ $($where_clauses:tt)* ] )?
{
$field_vis:vis $things:ident : [$EntryBuilder:ty] $(,)?
}
built: $Built:ty = $built:expr;
default = $default:expr;
$( item_build: $item_build:expr; )?
$(#[ serde $serde_attrs:tt ] )+
} => {
#[derive($crate::educe::Educe, Clone, Debug)]
#[derive($crate::serde::Serialize, $crate::serde::Deserialize)]
#[educe(Default)]
$(#[ serde $serde_attrs ])+
$(#[ $docs_and_attrs ])*
$vis struct $ListBuilder $( < $($generics)* > )?
$( where $($where_clauses)* )?
{
$field_vis $things: Option<Vec<$EntryBuilder>>,
}
impl $( < $($generics)* > )? $ListBuilder $( < $($generics)* > )?
$( where $($where_clauses)* )?
{
$vis fn build(&self) -> Result<$Built, $crate::ConfigBuildError> {
let default_buffer;
let $things = match &self.$things {
Some($things) => $things,
None => {
default_buffer = Self::default_list();
&default_buffer
}
};
let $things = $things
.iter()
.map(
$crate::macro_first_nonempty!{
[ $( $item_build )? ],
[ |item| item.build() ],
}
)
.collect::<Result<_, $crate::ConfigBuildError>>()?;
Ok($built)
}
fn default_list() -> Vec<$EntryBuilder> {
$default
}
$vis fn access(&mut self) -> &mut Vec<$EntryBuilder> {
self.$things.get_or_insert_with(Self::default_list)
}
$vis fn access_opt(&self) -> &Option<Vec<$EntryBuilder>> {
&self.$things
}
$vis fn access_opt_mut(&mut self) -> &mut Option<Vec<$EntryBuilder>> {
&mut self.$things
}
}
};
{
$(#[ $docs_and_attrs:meta ])*
$vis:vis
struct $ListBuilder:ident $( [ $($generics:tt)* ] )?
$( where [ $($where_clauses:tt)* ] )?
{
$field_vis:vis $things:ident : [$EntryBuilder:ty] $(,)?
}
built: $Built:ty = $built:expr;
default = $default:expr;
$( item_build: $item_build:expr; )?
} => {
define_list_builder_helper! {
$(#[ $docs_and_attrs ])*
$vis
struct $ListBuilder $( [ $($generics)* ] )?
$( where [ $($where_clauses)* ] )?
{
$field_vis $things : [$EntryBuilder],
}
built: $Built = $built;
default = $default;
$( item_build: $item_build; )?
#[serde(transparent)]
}
};
}
#[macro_export]
macro_rules! define_list_builder_accessors {
{
struct $OuterBuilder:ty {
$(
$vis:vis $things:ident: [$EntryBuilder:ty],
)*
}
} => {
impl $OuterBuilder { $( $crate::paste!{
$vis fn $things(&mut self) -> &mut Vec<$EntryBuilder> {
#[allow(unused_imports)]
use $crate::list_builder::DirectDefaultEmptyListBuilderAccessors as _;
self.$things.access()
}
$vis fn [<set_ $things>](&mut self, list: Vec<$EntryBuilder>) {
#[allow(unused_imports)]
use $crate::list_builder::DirectDefaultEmptyListBuilderAccessors as _;
*self.$things.access_opt_mut() = Some(list)
}
$vis fn [<opt_ $things>](&self) -> &Option<Vec<$EntryBuilder>> {
#[allow(unused_imports)]
use $crate::list_builder::DirectDefaultEmptyListBuilderAccessors as _;
self.$things.access_opt()
}
$vis fn [<opt_ $things _mut>](&mut self) -> &mut Option<Vec<$EntryBuilder>> {
#[allow(unused_imports)]
use $crate::list_builder::DirectDefaultEmptyListBuilderAccessors as _;
self.$things.access_opt_mut()
}
} )* }
}
}
pub trait DirectDefaultEmptyListBuilderAccessors {
type T;
fn access(&mut self) -> &mut Vec<Self::T>;
fn access_opt(&self) -> &Option<Vec<Self::T>>;
fn access_opt_mut(&mut self) -> &mut Option<Vec<Self::T>>;
}
impl<T> DirectDefaultEmptyListBuilderAccessors for Option<Vec<T>> {
type T = T;
fn access(&mut self) -> &mut Vec<T> {
self.get_or_insert_with(Vec::new)
}
fn access_opt(&self) -> &Option<Vec<T>> {
self
}
fn access_opt_mut(&mut self) -> &mut Option<Vec<T>> {
self
}
}
define_list_builder_helper! {
pub struct VecBuilder[T] where [T: Clone] {
values: [T],
}
built: Vec<T> = values;
default = vec![];
item_build: |item| Ok(item.clone());
}
#[derive(Clone, Debug, Educe, Serialize)]
#[serde(untagged)]
#[educe(Default)]
#[non_exhaustive]
pub enum MultilineListBuilder<EB> {
#[educe(Default)]
Unspecified,
String(String),
List(Vec<EB>),
}
#[derive(Error, Debug, Clone)]
#[error("multi-line string, line/item {item_number}: could not parse {line:?}: {error}")]
#[non_exhaustive]
pub struct MultilineListBuilderError<E: std::error::Error + Clone + Send + Sync> {
item_number: usize,
line: String,
error: E,
}
impl<'de, EB: Deserialize<'de>> Deserialize<'de> for MultilineListBuilder<EB> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_any(MllbVisitor::default())
}
}
#[derive(Educe)]
#[educe(Default)]
struct MllbVisitor<EB> {
ret: PhantomData<fn() -> EB>,
}
impl<'de, EB: Deserialize<'de>> serde::de::Visitor<'de> for MllbVisitor<EB> {
type Value = MultilineListBuilder<EB>;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "list of items, or multi-line string")
}
fn visit_seq<A: serde::de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
let mut v = vec![];
while let Some(e) = seq.next_element()? {
v.push(e);
}
Ok(MultilineListBuilder::List(v))
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
self.visit_string(v.to_owned())
}
fn visit_string<E: serde::de::Error>(self, v: String) -> Result<Self::Value, E> {
Ok(MultilineListBuilder::String(v))
}
fn visit_none<E: serde::de::Error>(self) -> Result<Self::Value, E> {
Ok(MultilineListBuilder::Unspecified)
}
}
impl<EB> From<Option<Vec<EB>>> for MultilineListBuilder<EB> {
fn from(list: Option<Vec<EB>>) -> Self {
use MultilineListBuilder as MlLB;
match list {
None => MlLB::Unspecified,
Some(list) => MlLB::List(list),
}
}
}
impl<EB> TryInto<Option<Vec<EB>>> for MultilineListBuilder<EB>
where
EB: FromStr,
EB::Err: std::error::Error + Clone + Send + Sync,
{
type Error = MultilineListBuilderError<EB::Err>;
fn try_into(self) -> Result<Option<Vec<EB>>, Self::Error> {
use MultilineListBuilder as MlLB;
fn parse_collect<'s, I>(
iter: impl Iterator<Item = (usize, &'s str)>,
) -> Result<Option<Vec<I>>, MultilineListBuilderError<I::Err>>
where
I: FromStr,
I::Err: std::error::Error + Clone + Send + Sync,
{
Ok(Some(
iter.map(|(i, l)| {
l.parse().map_err(|error| MultilineListBuilderError {
item_number: i + 1,
line: l.to_owned(),
error,
})
})
.try_collect()?,
))
}
Ok(match self {
MlLB::Unspecified => None,
MlLB::List(list) => Some(list),
MlLB::String(s) => parse_collect(
s.lines()
.enumerate()
.map(|(i, l)| (i, l.trim()))
.filter(|(_, l)| !(l.starts_with('#') || l.is_empty())),
)?,
})
}
}
#[macro_export]
macro_rules! convert_helper_via_multi_line_list_builder { {
struct $ListBuilder:ident { $things:ident: [$EntryBuilder:ty] $(,)? }
} => {
impl std::convert::TryFrom<$crate::MultilineListBuilder<$EntryBuilder>> for $ListBuilder {
type Error = $crate::MultilineListBuilderError<<$EntryBuilder as std::str::FromStr>::Err>;
fn try_from(mllb: $crate::MultilineListBuilder<$EntryBuilder>)
-> std::result::Result<$ListBuilder, Self::Error> {
Ok($ListBuilder { $things: mllb.try_into()? })
}
}
impl From<$ListBuilder> for MultilineListBuilder<$EntryBuilder> {
fn from(lb: $ListBuilder) -> MultilineListBuilder<$EntryBuilder> {
lb.$things.into()
}
}
} }
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
use super::*;
use derive_builder::Builder;
#[derive(Eq, PartialEq, Builder)]
#[builder(derive(Deserialize))]
struct Outer {
#[builder(sub_builder, setter(custom))]
list: List,
}
define_list_builder_accessors! {
struct OuterBuilder {
list: [char],
}
}
type List = Vec<char>;
define_list_builder_helper! {
struct ListBuilder {
list: [char],
}
built: List = list;
default = vec!['a'];
item_build: |&c| Ok(c);
}
#[test]
fn nonempty_default() {
let mut b = OuterBuilder::default();
assert!(b.opt_list().is_none());
assert_eq! { b.build().expect("build failed").list, ['a'] };
b.list().push('b');
assert!(b.opt_list().is_some());
assert_eq! { b.build().expect("build failed").list, ['a', 'b'] };
for mut b in [b.clone(), OuterBuilder::default()] {
b.set_list(vec!['x', 'y']);
assert!(b.opt_list().is_some());
assert_eq! { b.build().expect("build failed").list, ['x', 'y'] };
}
*b.opt_list_mut() = None;
assert_eq! { b.build().expect("build failed").list, ['a'] };
}
#[test]
fn vecbuilder() {
let mut b = VecBuilder::<u32>::default();
b.access().push(1);
b.access().push(2);
b.access().push(3);
assert_eq!(b.build().unwrap(), vec![1, 2, 3]);
}
#[test]
fn deser() {
let o: OuterBuilder = toml::from_str("list = ['x','y']").unwrap();
let o = o.build().unwrap();
assert_eq!(o.list, ['x', 'y']);
#[derive(Deserialize, Debug)]
struct OuterWithMllb {
#[serde(default)]
list: MultilineListBuilder<u32>,
}
let parse_ok = |s: &str| {
let o: OuterWithMllb = toml::from_str(s).unwrap();
let l: Option<Vec<_>> = o.list.try_into().unwrap();
l
};
let l = parse_ok("");
assert!(l.is_none());
let l = parse_ok("list = []");
assert!(l.unwrap().is_empty());
let l = parse_ok("list = [12,42]");
assert_eq!(l.unwrap(), [12, 42]);
let l = parse_ok(r#"list = """#);
assert!(l.unwrap().is_empty());
let l = parse_ok("list = \"\"\"\n12\n42\n\"\"\"\n");
assert_eq!(l.unwrap(), [12, 42]);
let e = toml::from_str::<OuterWithMllb>("list = [\"fail\"]")
.unwrap_err()
.to_string();
assert!(dbg!(e).contains(r#"invalid type: string "fail", expected u32"#));
let o = toml::from_str::<OuterWithMllb>("list = \"\"\"\nfail\n\"\"\"").unwrap();
let l: Result<Option<Vec<_>>, _> = o.list.try_into();
let e = l.unwrap_err().to_string();
assert_eq!(e, "multi-line string, line/item 1: could not parse \"fail\": invalid digit found in string");
}
}