Skip to content

Commit

Permalink
Fix PR issues
Browse files Browse the repository at this point in the history
Round 1.
Once reviewed, these changes should be merged into the appropriate
commits to produce a clean history.
  • Loading branch information
thorio committed Aug 31, 2024
1 parent 83678de commit 6ccfe06
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 121 deletions.
206 changes: 132 additions & 74 deletions src/coalesce.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::Profile;
use crate::value::{Map, Value};
use crate::Profile;

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Order {
Expand Down Expand Up @@ -27,11 +27,11 @@ impl Coalescible for Profile {

impl Coalescible for Value {
fn coalesce(self, other: Self, o: Order) -> Self {
use {Value::Dict as D, Value::Array as A, Order::*};
use {Order::*, Value::Array as A, Value::Dict as D};
match (self, other, o) {
(D(t, a), D(_, b), Join | Adjoin | Zipjoin) | (D(_, a), D(t, b), Merge | Admerge | Zipmerge) => D(t, a.coalesce(b, o)),
(A(t, mut a), A(_, b), Adjoin | Admerge) => A(t, { a.extend(b); a }),
(A(t, a), A(_, b), Zipjoin | Zipmerge) => A(t, a.coalesce(b, o)),
(D(t, a), D(_, b), Join | Adjoin | Zipjoin) => D(t, a.coalesce(b, o)),
(D(_, a), D(t, b), Merge | Admerge | Zipmerge) => D(t, a.coalesce(b, o)),
(A(t, a), A(_, b), _) => A(t, a.coalesce(b, o)),
(v, _, Join | Adjoin | Zipjoin) | (_, v, Merge | Admerge | Zipmerge) => v,
}
}
Expand All @@ -54,93 +54,130 @@ impl<K: Eq + std::hash::Hash + Ord, V: Coalescible> Coalescible for Map<K, V> {
}

impl Coalescible for Vec<Value> {
fn coalesce(self, other: Self, order: Order) -> Self {
let mut zipped = Vec::new();
let mut other = other.into_iter();

// Coalesces self[0] with other[0], self[1] with other[1] and so on.
for a_val in self.into_iter() {
match other.next() {
// Special cases: either a or b has an empty value, in which
// case we always choose the non-empty one regardless of order.
// If both are empty we just push either of the empties.
Some(b_val) if a_val.is_none() => zipped.push(b_val),
Some(b_val) if b_val.is_none() => zipped.push(a_val),

Some(b_val) => zipped.push(a_val.coalesce(b_val, order)),
None => zipped.push(a_val),
};
fn coalesce(mut self, other: Self, order: Order) -> Self {
match order {
Order::Join => self,
Order::Merge => other,
Order::Adjoin | Order::Admerge => { self.extend(other); self }
Order::Zipjoin | Order::Zipmerge => zip_vec(self, other, order),
}
}
}

// `b` contains more items than `a`; append them all.
zipped.extend(other);
zipped
fn zip_vec(this: Vec<Value>, other: Vec<Value>, order: Order) -> Vec<Value> {
let mut zipped = Vec::new();
let mut other = other.into_iter();

// Coalesces self[0] with other[0], self[1] with other[1] and so on.
for a_val in this.into_iter() {
match other.next() {
// Special cases: either a or b has an empty value, in which
// case we always choose the non-empty one regardless of order.
// If both are empty we just push either of the empties.
Some(b_val) if a_val.is_none() => zipped.push(b_val),
Some(b_val) if b_val.is_none() => zipped.push(a_val),

Some(b_val) => zipped.push(a_val.coalesce(b_val, order)),
None => zipped.push(a_val),
};
}

// `b` contains more items than `a`; append them all.
zipped.extend(other);
zipped
}

#[cfg(test)]
mod tests {
use crate::coalesce::{Coalescible, Order};
use crate::value::Empty;
use crate::{map, value::Value};
use crate::coalesce::{Coalescible, Order};

#[test]
pub fn coalesce_values() {
fn a() -> Value { Value::from("a") }
fn b() -> Value { Value::from("b") }

fn expect(order: Order, result: Value) { assert_eq!(a().coalesce(b(), order), result) }

expect(Order::Merge, b());
expect(Order::Admerge, b());
expect(Order::Zipmerge, b());
expect(Order::Join, a());
expect(Order::Adjoin, a());
expect(Order::Zipjoin, a());
assert_eq!(a().coalesce(b(), Order::Merge), b());
assert_eq!(a().coalesce(b(), Order::Admerge), b());
assert_eq!(a().coalesce(b(), Order::Zipmerge), b());
assert_eq!(a().coalesce(b(), Order::Join), a());
assert_eq!(a().coalesce(b(), Order::Adjoin), a());
assert_eq!(a().coalesce(b(), Order::Zipjoin), a());
}

#[test]
pub fn coalesce_dicts() {
fn a() -> Value { Value::from(map!(
"a" => map!("one" => 1, "two" => 2),
"b" => map!("ten" => 10, "twenty" => 20),
)) }
fn b() -> Value { Value::from(map!(
"a" => map!("one" => 2, "three" => 3),
"b" => map!("ten" => 20, "thirty" => 30),
)) }
fn result_join() -> Value { Value::from(map!(
"a" => map!("one" => 1, "two" => 2, "three" => 3),
"b" => map!("ten" => 10, "twenty" => 20, "thirty" => 30),
)) }
fn result_merge() -> Value { Value::from(map!(
"a" => map!("one" => 2, "two" => 2, "three" => 3),
"b" => map!("ten" => 20, "twenty" => 20, "thirty" => 30),
)) }

fn expect(order: Order, result: Value) { assert_eq!(a().coalesce(b(), order), result) }

expect(Order::Merge, result_merge());
expect(Order::Admerge, result_merge());
expect(Order::Zipmerge, result_merge());
expect(Order::Join, result_join());
expect(Order::Adjoin, result_join());
expect(Order::Zipjoin, result_join());
fn a() -> Value {
Value::from(map!(
"a" => map!("one" => 1, "two" => 2),
"b" => map!("ten" => 10, "twenty" => 20),
))
}

fn b() -> Value {
Value::from(map!(
"a" => map!("one" => 2, "three" => 3),
"b" => map!("ten" => 20, "thirty" => 30),
))
}

fn joined() -> Value {
Value::from(map!(
"a" => map!("one" => 1, "two" => 2, "three" => 3),
"b" => map!("ten" => 10, "twenty" => 20, "thirty" => 30),
))
}

fn merged() -> Value {
Value::from(map!(
"a" => map!("one" => 2, "two" => 2, "three" => 3),
"b" => map!("ten" => 20, "twenty" => 20, "thirty" => 30),
))
}

assert_eq!(a().coalesce(b(), Order::Merge), merged());
assert_eq!(a().coalesce(b(), Order::Admerge), merged());
assert_eq!(a().coalesce(b(), Order::Zipmerge), merged());
assert_eq!(a().coalesce(b(), Order::Join), joined());
assert_eq!(a().coalesce(b(), Order::Adjoin), joined());
assert_eq!(a().coalesce(b(), Order::Zipjoin), joined());
}

#[test]
pub fn coalesce_arrays() {
fn a() -> Value { Value::from(vec![1, 2]) }
fn b() -> Value { Value::from(vec![2, 3, 4]) }

fn expect(order: Order, result: Value) { assert_eq!(a().coalesce(b(), order), result) }

expect(Order::Merge, Value::from(vec![2, 3, 4]));
expect(Order::Admerge, Value::from(vec![1, 2, 2, 3, 4]));
expect(Order::Zipmerge, Value::from(vec![2, 3, 4]));
expect(Order::Join, Value::from(vec![1, 2]));
expect(Order::Adjoin, Value::from(vec![1, 2, 2, 3, 4]));
expect(Order::Zipjoin, Value::from(vec![1, 2, 4]));
assert_eq!(
a().coalesce(b(), Order::Merge),
Value::from(vec![2, 3, 4])
);

assert_eq!(
a().coalesce(b(), Order::Admerge),
Value::from(vec![1, 2, 2, 3, 4])
);

assert_eq!(
a().coalesce(b(), Order::Zipmerge),
Value::from(vec![2, 3, 4])
);

assert_eq!(
a().coalesce(b(), Order::Join),
Value::from(vec![1, 2])
);

assert_eq!(
a().coalesce(b(), Order::Adjoin),
Value::from(vec![1, 2, 2, 3, 4])
);

assert_eq!(
a().coalesce(b(), Order::Zipjoin),
Value::from(vec![1, 2, 4])
)
}

#[test]
Expand All @@ -150,13 +187,34 @@ mod tests {
fn a() -> Value { Value::from(vec![v(50), e(), v(4)]) }
fn b() -> Value { Value::from(vec![e(), v(2), v(6), e(), v(20)]) }

fn expect(order: Order, result: Value) { assert_eq!(a().coalesce(b(), order), result) }

expect(Order::Merge, Value::from(vec![e(), v(2), v(6), e(), v(20)]));
expect(Order::Admerge, Value::from(vec![v(50), e(), v(4), e(), v(2), v(6), e(), v(20)]));
expect(Order::Zipmerge, Value::from(vec![v(50), v(2), v(6), e(), v(20)]));
expect(Order::Join, Value::from(vec![v(50), e(), v(4)]));
expect(Order::Adjoin, Value::from(vec![v(50), e(), v(4), e(), v(2), v(6), e(), v(20)]));
expect(Order::Zipjoin, Value::from(vec![v(50), v(2), v(4), e(), v(20)]));
assert_eq!(
a().coalesce(b(), Order::Merge),
Value::from(vec![e(), v(2), v(6), e(), v(20)])
);

assert_eq!(
a().coalesce(b(), Order::Admerge),
Value::from(vec![v(50), e(), v(4), e(), v(2), v(6), e(), v(20)]),
);

assert_eq!(
a().coalesce(b(), Order::Zipmerge),
Value::from(vec![v(50), v(2), v(6), e(), v(20)]),
);

assert_eq!(
a().coalesce(b(), Order::Join),
Value::from(vec![v(50), e(), v(4)]),
);

assert_eq!(
a().coalesce(b(), Order::Adjoin),
Value::from(vec![v(50), e(), v(4), e(), v(2), v(6), e(), v(20)]),
);

assert_eq!(
a().coalesce(b(), Order::Zipjoin),
Value::from(vec![v(50), v(2), v(4), e(), v(20)]),
);
}
}
9 changes: 5 additions & 4 deletions src/figment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ use crate::coalesce::{Coalescible, Order};
/// resolved via one of six strategies: [`join`], [`adjoin`], [`zipjoin`],
/// [`merge`], [`admerge`], and [`zipmerge`]. In general, the `-join` strategies
/// prefer existing values while the `-merge` strategies prefer later values.
/// The `ad-` strategies additionally concatenate arrays, the `zip-` strategies
/// combine both of the first items, both of the second items and so on, whereas
/// the unprefixed strategies treat arrays as non-composite values.
/// The `ad-` strategies additionally concatenate arrays; the `zip-` strategies
/// treat array indices as keys resulting in index-wise joining (for zipjoin)
/// or merging (for zipmerge) arrays while preserving all excess elements.
///
/// The table below summarizes these strategies and their behavior, with the
/// column label referring to the type of the value pointed to by the
Expand All @@ -53,7 +53,8 @@ use crate::coalesce::{Coalescible, Order};
/// * `join` uses the existing value
/// * `merge` uses the incoming value
/// * `adjoin` and `admerge` concatenate the arrays
/// * `zipjoin` and `zipmerge` combine array items with equal index
/// * `zipjoin` index-wise joins the arrays and keeps excess values
/// * `zipmerge` index-wise merges the arrays and keeps excess values
///
/// If both keys point to a **non-composite** (`String`, `Num`, etc.) or values
/// of different kinds (i.e, **array** and **num**):
Expand Down
6 changes: 4 additions & 2 deletions src/providers/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ crate::util::cloneable_fn_trait!(
/// environment variable `Value`. For example, the environment variable
/// `a.b.c=3` creates the mapping `a -> b -> c -> 3` in the emitted data.
///
/// The created dictionaries are then zipmerged, which allows multiple variables
/// like `array.2=24` to form one array, as well as overwriting single values in
/// existing arrays.
///
/// Environment variable names cannot typically contain the `.` character, but
/// another character can be used in its place by replacing that character in
/// the name with `.` with [`Env::map()`]. The [`Env::split()`] method is a
Expand Down Expand Up @@ -411,8 +415,6 @@ impl Env {
/// array: vec![1, 2, 3],
/// });
///
/// jail.clear_env();
///
/// // With splitting.
/// jail.set_env("APP_FOO_KEY", 20);
/// jail.set_env("APP_MAP_ONE", "1.0");
Expand Down
Loading

0 comments on commit 6ccfe06

Please sign in to comment.