Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
on: [push, pull_request]

name: CI

on:
push:
branches:
- main
pull_request:

jobs:
build:
name: Tests
Expand All @@ -11,17 +15,22 @@ jobs:
- name: Checkout
uses: actions/checkout@v2

- name: Test
run: cargo test --workspace
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt

- name: Test with serde
run: cargo test --workspace --features serde
- name: Format check
shell: bash
run: cargo fmt --all --check

- name: Test with features
run: cargo test --workspace --features uuid,chrono,visitor
- name: Clippy (pedantic)
shell: bash
run: cargo clippy -- --no-deps -Dclippy::pedantic -Dwarnings

- name: Test with myers diff algo
run: cargo test --workspace --no-default-features --features vec_diff_myers
- uses: taiki-e/install-action@cargo-hack

- name: Test
run: cargo hack --each-feature test --workspace

- name: Run example
run: cargo run --example visitor --features visitor
30 changes: 15 additions & 15 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 15 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,23 @@ thiserror = "1.0"
uuid = { version = "1.11", optional = true }

[features]
chrono = ["dep:chrono"] # impl Diffable on `chrono::DateTime`
uuid = ["dep:uuid"] # impl Diffable on `uuid::Uuid`
serde = ["dep:serde", "difficient-macros/serde_impl"] # add serde derives to derived structs
visitor = ["serde", "difficient-macros/visitor_impl"] # add AcceptVisitor impl
# impl Diffable on `chrono::DateTime`
chrono = ["dep:chrono"]

# choose your vec diff algo
vec_diff_myers = [] # is generally more efficient
vec_diff_lcs = [] # can result in smaller diffs in certain circumstances
# impl Diffable on `uuid::Uuid`
uuid = ["dep:uuid"]

# add serde derives to derived structs
serde = ["dep:serde", "difficient-macros/serde_impl"]

# add AcceptVisitor impl
visitor = ["serde", "difficient-macros/visitor_impl"]

# choose your vec diff algorithm:
# the default is lcs, which can result in smaller diffs in certain circumstances,
# but you can choose myers instead, which may be more efficient
vec_diff_myers = []

default = ["vec_diff_lcs"]

[dev-dependencies]
pretty_assertions = "1.4.0"
Expand Down
12 changes: 6 additions & 6 deletions difficient-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ version = "0.1.0"
edition = "2024"

[dependencies]
darling = "0.20.10"
darling = "0.21.0"
heck = "0.5.0"
proc-macro2 = "1.0.86"
quote = "1.0.36"
syn = "2.0.72"
proc-macro2 = "1.0.95"
quote = "1.0.40"
syn = "2.0.104"

[features]
serde_impl = []
visitor_impl = ["serde_impl"]

[dev-dependencies]
pretty_assertions = "1.4.0"
prettyplease = "0.2.20"
pretty_assertions = "1.4.1"
prettyplease = "0.2.36"
12 changes: 5 additions & 7 deletions examples/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,13 @@ impl difficient::Visitor for ChangeEmitter {
self.location.pop().unwrap();
}
fn replaced<T: serde::Serialize>(&mut self, val: T) {
use Enter::*;
let mut path = String::new();
for (ix, loc) in self.location.iter().enumerate() {
match loc {
NamedField { name, .. } => path.push_str(name),
PositionalField(p) => path.push_str(&p.to_string()),
Variant { name, .. } => path.push_str(name),
MapKey(key) => path.push_str(&key),
Index(key) => path.push_str(&key.to_string()),
Enter::NamedField { name, .. } | Enter::Variant { name, .. } => path.push_str(name),
Enter::PositionalField(p) => path.push_str(&p.to_string()),
Enter::MapKey(key) => path.push_str(key),
Enter::Index(key) => path.push_str(&key.to_string()),
}
if ix != self.location.len() - 1 {
path.push('.');
Expand Down Expand Up @@ -83,6 +81,6 @@ fn main() {

println!("Changed paths:");
for (path, val) in emitter.changes {
println!("{path}: {val}")
println!("{path}: {val}");
}
}
15 changes: 10 additions & 5 deletions src/serde_visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ tuple_impl!(A 0);

macro_rules! kv_map_impl {
($typ: ident, $bounds: ident) => {
#[allow(
clippy::implicit_hasher,
reason = "Implementation is shared among HashMap and BTreeMap"
)]
impl<'a, K, V, U> AcceptVisitor for $typ<K, KvDiff<'a, V, U>>
where
K: $bounds + ToString + 'a,
Expand Down Expand Up @@ -200,7 +204,7 @@ pub mod tests {
use serde::Serialize;

use super::*;
use crate::{Diffable, Replace, tests::*};
use crate::{tests::*, Diffable, Replace};

impl AcceptVisitor for ParentDiff<'_> {
fn accept<V: Visitor>(&self, visitor: &mut V) {
Expand Down Expand Up @@ -337,7 +341,7 @@ pub mod tests {
let loc = self.location.join(".");
if let Ok(val) = serde_json::to_string(&val) {
self.replaced_locs.push((loc, val));
};
}
}

fn splice<T: serde::Serialize>(&mut self, from_index: usize, replace: usize, values: &[T]) {
Expand All @@ -355,14 +359,15 @@ pub mod tests {
values,
},
));
};
}
}

fn enter(&mut self, val: Enter) {
match val {
Enter::NamedField { name, .. } => self.location.push(name.into()),
Enter::PositionalField(p) => self.location.push(p.to_string()),
Enter::Variant { name, .. } => self.location.push(name.into()),
Enter::NamedField { name, .. } | Enter::Variant { name, .. } => {
self.location.push(name.into());
}
Enter::MapKey(k) => self.location.push(k),
Enter::Index(ix) => self.location.push(ix.to_string()),
}
Expand Down
27 changes: 18 additions & 9 deletions src/vec.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
#[cfg(feature = "vec_diff_lcs")]
use similar::algorithms::lcs as diff_algo;

#[cfg(feature = "vec_diff_myers")]
use similar::algorithms::lcs as diff_algo;

use similar::algorithms::{Capture, Compact, Replace as SimilarReplace};

use crate::{Apply, ApplyError, Diffable, Replace};
Expand Down Expand Up @@ -75,7 +69,7 @@ mod visitor_impls {
Self::Unchanged => {}
Self::Changed { changes } => {
for chg in changes {
chg.accept(visitor)
chg.accept(visitor);
}
}
Self::Replaced(r) => visitor.replaced(r),
Expand All @@ -90,7 +84,7 @@ mod visitor_impls {
fn accept<V: crate::Visitor>(&self, visitor: &mut V) {
match self {
VecChange::Remove { at_index, count } => {
visitor.splice::<T>(*at_index, *count, &[])
visitor.splice::<T>(*at_index, *count, &[]);
}
VecChange::Insert { at_index, values } => visitor.splice(*at_index, 0, values),
VecChange::Splice {
Expand Down Expand Up @@ -188,6 +182,7 @@ where
{
type Diff = VecDiff<'a, T, T::Diff>;

#[allow(clippy::too_many_lines)]
fn diff(&self, other: &'a Self) -> Self::Diff {
if self.is_empty() && other.is_empty() {
// short circuit
Expand All @@ -205,7 +200,21 @@ where
)]
let rkeys: Vec<_> = other.iter().map(|v| v.key()).collect();
let mut d = Compact::new(SimilarReplace::new(Capture::new()), &lkeys, &rkeys);
diff_algo::diff(&mut d, &lkeys, 0..lkeys.len(), &rkeys, 0..rkeys.len()).unwrap();

if cfg!(feature = "vec_diff_myers") {
similar::algorithms::myers::diff(
&mut d,
&lkeys,
0..lkeys.len(),
&rkeys,
0..rkeys.len(),
)
.unwrap();
} else {
similar::algorithms::lcs::diff(&mut d, &lkeys, 0..lkeys.len(), &rkeys, 0..rkeys.len())
.unwrap();
}

let ops = d.into_inner().into_inner().into_ops();
let n_ops = ops.len();
let mut offset = 0isize;
Expand Down