From 90bdd73f5f57215f0b6a887c6074aaf60b38155f Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Sun, 22 Nov 2020 23:16:20 +0100 Subject: [PATCH 001/112] Setting version to 0.3.31-SNAPSHOT --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 5666f5e9..28056724 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.3.30" +version in ThisBuild := "0.3.31-SNAPSHOT" From 3073988c41f834409a28ea9e25111140a31023b3 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Mon, 23 Nov 2020 05:18:15 +0100 Subject: [PATCH 002/112] Update sbt to 1.4.4 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 49742172..786fd0d6 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.3 \ No newline at end of file +sbt.version=1.4.4 \ No newline at end of file From 2ab9a247c04c071b117d4ae32c1756f67b9a2e20 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Wed, 25 Nov 2020 19:37:12 +0100 Subject: [PATCH 003/112] Update mdoc, sbt-mdoc to 2.2.13 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index cdf8d551..ead3024f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,4 +3,4 @@ addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-extra" % "1.9.14") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.3.1") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.12") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.13") From 86cce8d2d7ab49c1bbc7223fb7a8f92f6fd31f1c Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Thu, 26 Nov 2020 09:54:14 +0100 Subject: [PATCH 004/112] Update cats-core to 2.3.0 --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 26bae4d6..5281700c 100644 --- a/build.sbt +++ b/build.sbt @@ -112,7 +112,7 @@ lazy val cats = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-cats", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % "2.2.0", + "org.typelevel" %% "cats-core" % "2.3.0", "org.scalatest" %% "scalatest-freespec" % scalatestVersion % Test, "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test ) @@ -145,7 +145,7 @@ lazy val docs = project publishArtifact := false, name := "docs", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % "2.2.0", + "org.typelevel" %% "cats-core" % "2.3.0", "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion ) ) From 2784d472de876a9310bb0ffb67b41d9c08a9b73a Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Mon, 30 Nov 2020 22:54:10 +0100 Subject: [PATCH 005/112] Update sbt-softwaremill-common, ... to 1.9.15 --- project/plugins.sbt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index ead3024f..28790ff1 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ -addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-common" % "1.9.14") -addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % "1.9.14") -addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-extra" % "1.9.14") +addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-common" % "1.9.15") +addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % "1.9.15") +addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-extra" % "1.9.15") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.3.1") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.13") From 8e1b7694631665bae31c4a56bcd7b9d000d582a9 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Wed, 2 Dec 2020 17:42:36 +0100 Subject: [PATCH 006/112] Update refined to 0.9.19 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 5281700c..f7039569 100644 --- a/build.sbt +++ b/build.sbt @@ -128,7 +128,7 @@ lazy val refined = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-refined", libraryDependencies ++= Seq( - "eu.timepit" %% "refined" % "0.9.18", + "eu.timepit" %% "refined" % "0.9.19", "org.scalatest" %% "scalatest-flatspec" % scalatestVersion % Test, "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test ) From c67441a3b1a3034b053bc46e3d3a463dc5bbeaf4 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Mon, 14 Dec 2020 10:48:49 +0100 Subject: [PATCH 007/112] Update sbt to 1.4.5 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 786fd0d6..13932582 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.4 \ No newline at end of file +sbt.version=1.4.5 \ No newline at end of file From db1e6632f0476943d22ed186834fbe181062c357 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Thu, 17 Dec 2020 17:24:51 +0100 Subject: [PATCH 008/112] Update mdoc, sbt-mdoc to 2.2.14 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 28790ff1..871e99b6 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,4 +3,4 @@ addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-extra" % "1.9.15") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.3.1") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.13") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.14") From 06f6cc80aead188e198f091288e1b035c92ecec9 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Fri, 18 Dec 2020 20:54:31 +0100 Subject: [PATCH 009/112] Update cats-core to 2.3.1 --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index f7039569..fe9aef29 100644 --- a/build.sbt +++ b/build.sbt @@ -112,7 +112,7 @@ lazy val cats = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-cats", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % "2.3.0", + "org.typelevel" %% "cats-core" % "2.3.1", "org.scalatest" %% "scalatest-freespec" % scalatestVersion % Test, "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test ) @@ -145,7 +145,7 @@ lazy val docs = project publishArtifact := false, name := "docs", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % "2.3.0", + "org.typelevel" %% "cats-core" % "2.3.1", "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion ) ) From 43fcca16b3331d310abf3cca116b1ae3c8caf256 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Thu, 24 Dec 2020 12:43:54 +0100 Subject: [PATCH 010/112] Update sbt to 1.4.6 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 13932582..d404814a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.5 \ No newline at end of file +sbt.version=1.4.6 \ No newline at end of file From 481af082fcb6338500257f07ce352682f50c99ca Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Mon, 28 Dec 2020 21:52:41 +0100 Subject: [PATCH 011/112] Implement semi-auto derivation --- .../scala/com/softwaremill/diffx/Diff.scala | 32 +++++- .../softwaremill/diffx/DiffInstances.scala | 108 ++++++++---------- .../diffx/DiffMagnoliaDerivation.scala | 58 ---------- .../generic/DiffMagnoliaDerivation.scala | 51 +++++++++ .../diffx/generic/MagnoliaDerivedMacro.scala | 14 +++ .../diffx/generic/auto/DiffDerivation.scala | 9 ++ .../diffx/{ => test}/DiffIgnoreIntTest.scala | 10 +- .../diffx/{ => test}/DiffResultTest.scala | 3 +- .../diffx/test/DiffSemiautoTest.scala | 38 ++++++ .../diffx/{ => test}/DiffTest.scala | 39 +++---- .../diffx/{ => test}/IgnoreMacroTest.scala | 3 +- 11 files changed, 217 insertions(+), 148 deletions(-) delete mode 100644 core/src/main/scala/com/softwaremill/diffx/DiffMagnoliaDerivation.scala create mode 100644 core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala create mode 100644 core/src/main/scala/com/softwaremill/diffx/generic/MagnoliaDerivedMacro.scala create mode 100644 core/src/main/scala/com/softwaremill/diffx/generic/auto/DiffDerivation.scala rename core/src/test/scala/com/softwaremill/diffx/{ => test}/DiffIgnoreIntTest.scala (85%) rename core/src/test/scala/com/softwaremill/diffx/{ => test}/DiffResultTest.scala (97%) create mode 100644 core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala rename core/src/test/scala/com/softwaremill/diffx/{ => test}/DiffTest.scala (92%) rename core/src/test/scala/com/softwaremill/diffx/{ => test}/IgnoreMacroTest.scala (93%) diff --git a/core/src/main/scala/com/softwaremill/diffx/Diff.scala b/core/src/main/scala/com/softwaremill/diffx/Diff.scala index b0d1d393..ab54b730 100644 --- a/core/src/main/scala/com/softwaremill/diffx/Diff.scala +++ b/core/src/main/scala/com/softwaremill/diffx/Diff.scala @@ -1,5 +1,6 @@ package com.softwaremill.diffx import acyclic.skipped +import magnolia.Magnolia trait Diff[-T] { outer => def apply(left: T, right: T): DiffResult = apply(left, right, Nil) @@ -19,7 +20,7 @@ trait Diff[-T] { outer => } } -object Diff extends DiffInstances { +object Diff extends MiddlePriorityDiff { def apply[T: Diff]: Diff[T] = implicitly[Diff[T]] def identical[T]: Diff[T] = (left: T, _: T, _: List[FieldPath]) => Identical(left) @@ -27,10 +28,35 @@ object Diff extends DiffInstances { def compare[T: Diff](left: T, right: T): DiffResult = apply[T].apply(left, right) /** Create a Diff instance using [[Object#equals]] */ - def useEquals[T]: Derived[Diff[T]] = Derived(Diff.fallback[T]) + def useEquals[T]: Diff[T] = Diff.fallback[T] + def derived[T]: Diff[T] = macro Magnolia.gen[T] + + implicit val diffForString: Diff[String] = new DiffForString + implicit val diffForRange: Diff[Range] = Diff.fallback[Range] + implicit def diffForNumeric[T: Numeric]: Diff[T] = numeric + implicit def diffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]](implicit + ddot: Diff[Option[V]], + ddk: Diff[K], + matcher: ObjectMatcher[K] + ): Diff[C[K, V]] = new DiffForMap[K, V, C](matcher, ddk, ddot) + implicit def diffForOptional[T](implicit ddt: Diff[T]): Diff[Option[T]] = optional + implicit def diffForSet[T, C[W] <: scala.collection.Set[W]](implicit + ddt: Diff[T], + matcher: ObjectMatcher[T] + ): Diff[C[T]] = set[T, C] +} + +trait MiddlePriorityDiff extends DiffInstances with LowPriorityDiff { + + implicit def diffForIterable[T, C[W] <: Iterable[W]](implicit + ddot: Diff[Option[T]] + ): Diff[C[T]] = iterable +} + +trait LowPriorityDiff { // Implicit instance of Diff[T] created from implicit Derived[Diff[T]] - implicit def anyDiff[T](implicit dd: Derived[Diff[T]]): Diff[T] = dd.value + implicit def derivedDiff[T](implicit dd: Derived[Diff[T]]): Diff[T] = dd.value // Implicit conversion implicit def unwrapDerivedDiff[T](dd: Derived[Diff[T]]): Diff[T] = dd.value diff --git a/core/src/main/scala/com/softwaremill/diffx/DiffInstances.scala b/core/src/main/scala/com/softwaremill/diffx/DiffInstances.scala index 8271947c..a18ee747 100644 --- a/core/src/main/scala/com/softwaremill/diffx/DiffInstances.scala +++ b/core/src/main/scala/com/softwaremill/diffx/DiffInstances.scala @@ -1,47 +1,46 @@ package com.softwaremill.diffx import acyclic.skipped import com.softwaremill.diffx.Matching._ +import com.softwaremill.diffx.generic.DiffMagnoliaDerivation import scala.collection.immutable.ListMap trait DiffInstances extends DiffMagnoliaDerivation { - implicit def diffForNumeric[T: Numeric]: Derived[Diff[T]] = - Derived((left: T, right: T, _: List[FieldPath]) => { - val numeric = implicitly[Numeric[T]] - if (!numeric.equiv(left, right)) { - DiffResultValue(left, right) - } else { - Identical(left) - } - }) + def numeric[T: Numeric]: Diff[T] = { (left: T, right: T, _: List[FieldPath]) => + val numeric = implicitly[Numeric[T]] + if (!numeric.equiv(left, right)) { + DiffResultValue(left, right) + } else { + Identical(left) + } + } - implicit def diffForOption[T](implicit ddt: Diff[T]): Derived[Diff[Option[T]]] = - Derived((left: Option[T], right: Option[T], toIgnore: List[FieldPath]) => { + def optional[T](implicit ddt: Diff[T]): Diff[Option[T]] = { + (left: Option[T], right: Option[T], toIgnore: List[FieldPath]) => (left, right) match { case (Some(l), Some(r)) => ddt.apply(l, r, toIgnore) case (None, None) => Identical(None) case (l, r) => DiffResultValue(l, r) } - }) + } - implicit def diffForSet[T: ObjectMatcher, C[W] <: scala.collection.Set[W]](implicit + def set[T, C[W] <: scala.collection.Set[W]](implicit ddt: Diff[T], matcher: ObjectMatcher[T] - ): Derived[Diff[C[T]]] = - Derived((left: C[T], right: C[T], toIgnore: List[FieldPath]) => { - val MatchingResults(unMatchedLeftInstances, unMatchedRightInstances, matchedInstances) = - matching[T](left.toSet, right.toSet, matcher, ddt, toIgnore) - val leftDiffs = unMatchedLeftInstances - .diff(unMatchedRightInstances) - .map(DiffResultAdditional(_)) - .toList - val rightDiffs = unMatchedRightInstances - .diff(unMatchedLeftInstances) - .map(DiffResultMissing(_)) - .toList - val matchedDiffs = matchedInstances.map { case (l, r) => ddt(l, r, toIgnore) }.toList - diffResultSet(left, leftDiffs, rightDiffs, matchedDiffs) - }) + ): Diff[C[T]] = { (left: C[T], right: C[T], toIgnore: List[FieldPath]) => + val MatchingResults(unMatchedLeftInstances, unMatchedRightInstances, matchedInstances) = + matching[T](left.toSet, right.toSet, matcher, ddt, toIgnore) + val leftDiffs = unMatchedLeftInstances + .diff(unMatchedRightInstances) + .map(DiffResultAdditional(_)) + .toList + val rightDiffs = unMatchedRightInstances + .diff(unMatchedLeftInstances) + .map(DiffResultMissing(_)) + .toList + val matchedDiffs = matchedInstances.map { case (l, r) => ddt(l, r, toIgnore) }.toList + diffResultSet(left, leftDiffs, rightDiffs, matchedDiffs) + } private def diffResultSet[T]( left: T, @@ -57,38 +56,27 @@ trait DiffInstances extends DiffMagnoliaDerivation { } } - implicit def diffForIterable[T, C[W] <: Iterable[W]](implicit + def iterable[T, C[W] <: Iterable[W]](implicit ddot: Diff[Option[T]] - ): Derived[Diff[C[T]]] = - Derived((left: C[T], right: C[T], toIgnore: List[FieldPath]) => { - val indexes = Range(0, Math.max(left.size, right.size)) - val leftAsMap = left.toList.lift - val rightAsMap = right.toList.lift - val differences = ListMap(indexes.map { index => - index.toString -> (ddot.apply(leftAsMap(index), rightAsMap(index), toIgnore) match { - case DiffResultValue(Some(v), None) => DiffResultAdditional(v) - case DiffResultValue(None, Some(v)) => DiffResultMissing(v) - case d => d - }) - }: _*) + ): Diff[C[T]] = { (left: C[T], right: C[T], toIgnore: List[FieldPath]) => + val indexes = Range(0, Math.max(left.size, right.size)) + val leftAsMap = left.toList.lift + val rightAsMap = right.toList.lift + val differences = ListMap(indexes.map { index => + index.toString -> (ddot.apply(leftAsMap(index), rightAsMap(index), toIgnore) match { + case DiffResultValue(Some(v), None) => DiffResultAdditional(v) + case DiffResultValue(None, Some(v)) => DiffResultMissing(v) + case d => d + }) + }: _*) - if (differences.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject( - "List", - differences - ) - } - }) - - implicit def diffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]](implicit - ddot: Diff[Option[V]], - ddk: Diff[K], - matcher: ObjectMatcher[K] - ): Derived[Diff[C[K, V]]] = Derived(new DiffForMap[K, V, C](matcher, ddk, ddot)) - - implicit val diffForString: Derived[Diff[String]] = Derived(new DiffForString) - - implicit val diffForRange: Derived[Diff[Range]] = Derived(Diff.fallback[Range]) + if (differences.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject( + "List", + differences + ) + } + } } diff --git a/core/src/main/scala/com/softwaremill/diffx/DiffMagnoliaDerivation.scala b/core/src/main/scala/com/softwaremill/diffx/DiffMagnoliaDerivation.scala deleted file mode 100644 index 5ca339fc..00000000 --- a/core/src/main/scala/com/softwaremill/diffx/DiffMagnoliaDerivation.scala +++ /dev/null @@ -1,58 +0,0 @@ -package com.softwaremill.diffx -import acyclic.skipped -import magnolia._ - -import scala.collection.immutable.ListMap -import scala.language.experimental.macros - -trait DiffMagnoliaDerivation extends LowPriority { - type Typeclass[T] = Derived[Diff[T]] - - def combine[T](ctx: ReadOnlyCaseClass[Typeclass, T]): Derived[Diff[T]] = - Derived(new Diff[T] { - override def apply(left: T, right: T, toIgnore: List[FieldPath]): DiffResult = { - val map = ListMap(ctx.parameters.map { p => - val lType = p.dereference(left) - val pType = p.dereference(right) - if (toIgnore.contains(List(p.label))) { - p.label -> Identical(lType) - } else { - val nestedIgnore = - if (toIgnore.exists(_.headOption.exists(h => h == p.label))) toIgnore.map(_.drop(1)) else Nil - p.label -> p.typeclass.value(lType, pType, nestedIgnore) - } - }: _*) - if (map.values.forall(p => p.isIdentical)) { - Identical(left) - } else { - DiffResultObject(ctx.typeName.short, map) - } - } - }) - - def dispatch[T](ctx: SealedTrait[Typeclass, T]): Derived[Diff[T]] = - Derived({ (left: T, right: T, toIgnore: List[FieldPath]) => - { - val lType = ctx.dispatch(left)(a => a) - val rType = ctx.dispatch(right)(a => a) - if (lType == rType) { - lType.typeclass.value(lType.cast(left), lType.cast(right), toIgnore) - } else { - DiffResultValue(lType.typeName.full, rType.typeName.full) - } - } - }) - - implicit def gen[T]: Derived[Diff[T]] = macro Magnolia.gen[T] -} - -trait LowPriority { - def fallback[T]: Derived[Diff[T]] = - Derived((left: T, right: T, toIgnore: List[FieldPath]) => { - if (left != right) { - DiffResultValue(left, right) - } else { - Identical(left) - } - }) -} diff --git a/core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala b/core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala new file mode 100644 index 00000000..8c0f1047 --- /dev/null +++ b/core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala @@ -0,0 +1,51 @@ +package com.softwaremill.diffx.generic + +import com.softwaremill.diffx.{Diff, DiffResultObject, DiffResultValue, FieldPath, Identical} +import magnolia._ +import acyclic.skipped + +import scala.collection.immutable.ListMap + +trait DiffMagnoliaDerivation extends LowPriority { + type Typeclass[T] = Diff[T] + + def combine[T](ctx: ReadOnlyCaseClass[Typeclass, T]): Diff[T] = { (left: T, right: T, toIgnore: List[FieldPath]) => + val map = ListMap(ctx.parameters.map { p => + val lType = p.dereference(left) + val pType = p.dereference(right) + if (toIgnore.contains(List(p.label))) { + p.label -> Identical(lType) + } else { + val nestedIgnore = + if (toIgnore.exists(_.headOption.exists(h => h == p.label))) toIgnore.map(_.drop(1)) else Nil + p.label -> p.typeclass(lType, pType, nestedIgnore) + } + }: _*) + if (map.values.forall(p => p.isIdentical)) { + Identical(left) + } else { + DiffResultObject(ctx.typeName.short, map) + } + } + + def dispatch[T](ctx: SealedTrait[Typeclass, T]): Diff[T] = { (left: T, right: T, toIgnore: List[FieldPath]) => + val lType = ctx.dispatch(left)(a => a) + val rType = ctx.dispatch(right)(a => a) + if (lType == rType) { + lType.typeclass(lType.cast(left), lType.cast(right), toIgnore) + } else { + DiffResultValue(lType.typeName.full, rType.typeName.full) + } + } +} + +trait LowPriority { + def fallback[T]: Diff[T] = + (left: T, right: T, toIgnore: List[FieldPath]) => { + if (left != right) { + DiffResultValue(left, right) + } else { + Identical(left) + } + } +} diff --git a/core/src/main/scala/com/softwaremill/diffx/generic/MagnoliaDerivedMacro.scala b/core/src/main/scala/com/softwaremill/diffx/generic/MagnoliaDerivedMacro.scala new file mode 100644 index 00000000..db45b547 --- /dev/null +++ b/core/src/main/scala/com/softwaremill/diffx/generic/MagnoliaDerivedMacro.scala @@ -0,0 +1,14 @@ +package com.softwaremill.diffx.generic + +import com.softwaremill.diffx.Derived +import magnolia.Magnolia +import scala.language.experimental.macros + +object MagnoliaDerivedMacro { + import scala.reflect.macros.whitebox + + def derivedGen[T: c.WeakTypeTag](c: whitebox.Context): c.Expr[Derived[T]] = { + import c.universe._ + c.Expr[Derived[T]](q"com.softwaremill.diffx.Derived(${Magnolia.gen[T](c)(implicitly[c.WeakTypeTag[T]])})") + } +} diff --git a/core/src/main/scala/com/softwaremill/diffx/generic/auto/DiffDerivation.scala b/core/src/main/scala/com/softwaremill/diffx/generic/auto/DiffDerivation.scala new file mode 100644 index 00000000..35808379 --- /dev/null +++ b/core/src/main/scala/com/softwaremill/diffx/generic/auto/DiffDerivation.scala @@ -0,0 +1,9 @@ +package com.softwaremill.diffx.generic + +import com.softwaremill.diffx.{Derived, Diff} + +package object auto extends DiffDerivation + +trait DiffDerivation extends DiffMagnoliaDerivation { + implicit def diffForCaseClass[T]: Derived[Diff[T]] = macro MagnoliaDerivedMacro.derivedGen[T] +} diff --git a/core/src/test/scala/com/softwaremill/diffx/DiffIgnoreIntTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffIgnoreIntTest.scala similarity index 85% rename from core/src/test/scala/com/softwaremill/diffx/DiffIgnoreIntTest.scala rename to core/src/test/scala/com/softwaremill/diffx/test/DiffIgnoreIntTest.scala index 4e44c42c..46b00a9f 100644 --- a/core/src/test/scala/com/softwaremill/diffx/DiffIgnoreIntTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffIgnoreIntTest.scala @@ -1,9 +1,11 @@ -package com.softwaremill.diffx - -import java.time.Instant +package com.softwaremill.diffx.test +import com.softwaremill.diffx._ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import com.softwaremill.diffx.generic.auto._ + +import java.time.Instant class DiffIgnoreIntTest extends AnyFlatSpec with Matchers { val instant: Instant = Instant.now() @@ -11,7 +13,7 @@ class DiffIgnoreIntTest extends AnyFlatSpec with Matchers { val p2 = Person("p2", 11, instant) it should "allow importing and exporting implicits" in { - implicit val d: Diff[Person] = Derived[Diff[Person]].value.ignore(_.name) + implicit val d: Diff[Person] = Derived[Diff[Person]].ignore(_.name) compare(p1, p2) shouldBe DiffResultObject( "Person", Map("name" -> Identical("p1"), "age" -> DiffResultValue(22, 11), "in" -> Identical(instant)) diff --git a/core/src/test/scala/com/softwaremill/diffx/DiffResultTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffResultTest.scala similarity index 97% rename from core/src/test/scala/com/softwaremill/diffx/DiffResultTest.scala rename to core/src/test/scala/com/softwaremill/diffx/test/DiffResultTest.scala index d4e608cd..1f4236d2 100644 --- a/core/src/test/scala/com/softwaremill/diffx/DiffResultTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffResultTest.scala @@ -1,5 +1,6 @@ -package com.softwaremill.diffx +package com.softwaremill.diffx.test +import com.softwaremill.diffx._ import org.scalatest.freespec.AnyFreeSpec import org.scalatest.matchers.should.Matchers diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala new file mode 100644 index 00000000..41c071a1 --- /dev/null +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala @@ -0,0 +1,38 @@ +package com.softwaremill.diffx.test + +import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.matchers.should.Matchers + +class DiffSemiautoTest extends AnyFreeSpec with Matchers { + "should compile if all required instances are defined" in { + assertCompiles(""" + |import com.softwaremill.diffx._ + |final case class P1(f1: String) + |final case class P2(f1: P1) + | + |implicit val p1 = Diff.derived[P1] + |implicit val p2 = Diff.derived[P2] + |""".stripMargin) + } + + "should not allow to compile if an instance is missing" in { + assertDoesNotCompile(""" + |import com.softwaremill.diffx._ + |final case class P1(f1: String) + |final case class P2(f1: P1) + | + |implicit val p2 = Diff.derived[P2] + |""".stripMargin) + } + + "should compile with generic.auto._" in { + assertCompiles(""" + |import com.softwaremill.diffx._ + |import com.softwaremill.diffx.generic.auto._ + |final case class P1(f1: String) + |final case class P2(f1: P1) + | + |val p2 = Diff[P2] + |""".stripMargin) + } +} diff --git a/core/src/test/scala/com/softwaremill/diffx/DiffTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala similarity index 92% rename from core/src/test/scala/com/softwaremill/diffx/DiffTest.scala rename to core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala index fb8e93d8..565bc453 100644 --- a/core/src/test/scala/com/softwaremill/diffx/DiffTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala @@ -1,11 +1,12 @@ -package com.softwaremill.diffx - -import java.time.Instant -import java.util.UUID +package com.softwaremill.diffx.test +import com.softwaremill.diffx.generic.auto._ +import com.softwaremill.diffx._ import org.scalatest.freespec.AnyFreeSpec import org.scalatest.matchers.should.Matchers +import java.time.Instant +import java.util.UUID import scala.collection.immutable.ListMap class DiffTest extends AnyFreeSpec with Matchers { @@ -165,8 +166,8 @@ class DiffTest extends AnyFreeSpec with Matchers { } "diff" in { compare[TsDirection](TsDirection.Outgoing, TsDirection.Incoming) shouldBe DiffResultValue( - "com.softwaremill.diffx.TsDirection.Outgoing", - "com.softwaremill.diffx.TsDirection.Incoming" + "com.softwaremill.diffx.test.TsDirection.Outgoing", + "com.softwaremill.diffx.test.TsDirection.Incoming" ) } } @@ -181,7 +182,7 @@ class DiffTest extends AnyFreeSpec with Matchers { Map( "bar" -> DiffResultObject("Bar", Map("s" -> Identical("asdf"), "i" -> DiffResultValue(66, 5))), "b" -> DiffResultObject("List", Map("0" -> DiffResultValue(1234, 123), "1" -> DiffResultMissing(1234))), - "parent" -> DiffResultValue("com.softwaremill.diffx.Foo", "com.softwaremill.diffx.Bar") + "parent" -> DiffResultValue("com.softwaremill.diffx.test.Foo", "com.softwaremill.diffx.test.Bar") ) ) } @@ -245,7 +246,7 @@ class DiffTest extends AnyFreeSpec with Matchers { val o1 = Organization(List(p1, p2)) val o2 = Organization(List(p2, p1)) implicit val om: ObjectMatcher[Person] = (left: Person, right: Person) => left.name == right.name - implicit val dd: Derived[Diff[List[Person]]] = new Derived(Diff[Set[Person]].contramap(_.toSet)) + implicit val dd: Diff[List[Person]] = Diff[Set[Person]].contramap(_.toSet) compare(o1, o2) shouldBe Identical(Organization(List(p1, p2))) } @@ -284,8 +285,8 @@ class DiffTest extends AnyFreeSpec with Matchers { val p2m = p2.copy(age = 33, in = Instant.now()) val d = Diff[Person].ignoreUnsafe("age") implicit val im: ObjectMatcher[Person] = (left: Person, right: Person) => left.name == right.name - val ds: Derived[Diff[Set[Person]]] = Diff.diffForSet(im, Derived(d), implicitly[ObjectMatcher[Person]]) - compare(Set(p1, p2), Set(p1, p2m))(ds.value) shouldBe DiffResultSet( + val ds: Diff[Set[Person]] = Diff.diffForSet(d, im) + compare(Set(p1, p2), Set(p1, p2m))(ds) shouldBe DiffResultSet( List( Identical(p1), DiffResultObject( @@ -315,16 +316,16 @@ class DiffTest extends AnyFreeSpec with Matchers { "identical when products are identical using ignored" in { val p2m = p2.copy(age = 33, in = Instant.now()) val d = Diff[Person].ignoreUnsafe("age").ignoreUnsafe("in") - val ds: Derived[Diff[Set[Person]]] = - Diff.diffForSet(implicitly[ObjectMatcher[Person]], Derived(d), implicitly[ObjectMatcher[Person]]) - compare(Set(p1, p2), Set(p1, p2m))(ds.value) shouldBe Identical(Set(p1, p2)) + val ds: Diff[Set[Person]] = + Diff.diffForSet(d, implicitly[ObjectMatcher[Person]]) + compare(Set(p1, p2), Set(p1, p2m))(ds) shouldBe Identical(Set(p1, p2)) } "propagate ignore fields to elements" in { val p2m = p2.copy(in = Instant.now()) implicit val im: ObjectMatcher[Person] = (left: Person, right: Person) => left.name == right.name val ds: Diff[Set[Person]] = - Diff.diffForSet(im, Derived[Diff[Person]], implicitly[ObjectMatcher[Person]]).value.ignoreUnsafe("age") + Diff.diffForSet(Diff[Person], im).ignoreUnsafe("age") compare(Set(p1, p2), Set(p1, p2m))(ds) shouldBe DiffResultSet( List( Identical(p1), @@ -400,10 +401,8 @@ class DiffTest extends AnyFreeSpec with Matchers { } "maps by values" in { - type DD[T] = Derived[Diff[T]] - - implicit def mapWithoutKeys[T, R: DD]: Derived[Diff[Map[T, R]]] = - new Derived(Diff[List[R]].contramap(_.values.toList)) + implicit def mapWithoutKeys[T, R: Diff]: Diff[Map[T, R]] = + Diff[List[R]].contramap(_.values.toList) val person = Person("123", 11, Instant.now()) compare( @@ -421,9 +420,7 @@ class DiffTest extends AnyFreeSpec with Matchers { } "match values using object mapper" in { - implicit val om: ObjectMatcher[KeyModel] = new ObjectMatcher[KeyModel] { - override def isSameObject(left: KeyModel, right: KeyModel): Boolean = left.name == right.name - } + implicit val om: ObjectMatcher[KeyModel] = (left: KeyModel, right: KeyModel) => left.name == right.name val uuid1 = UUID.randomUUID() val uuid2 = UUID.randomUUID() val a1 = MyLookup(Map(KeyModel(uuid1, "k1") -> "val1")) diff --git a/core/src/test/scala/com/softwaremill/diffx/IgnoreMacroTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/IgnoreMacroTest.scala similarity index 93% rename from core/src/test/scala/com/softwaremill/diffx/IgnoreMacroTest.scala rename to core/src/test/scala/com/softwaremill/diffx/test/IgnoreMacroTest.scala index 0ad20e42..a6a5ab48 100644 --- a/core/src/test/scala/com/softwaremill/diffx/IgnoreMacroTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/IgnoreMacroTest.scala @@ -1,5 +1,6 @@ -package com.softwaremill.diffx +package com.softwaremill.diffx.test +import com.softwaremill.diffx.IgnoreMacro import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers From 9e96254ef8766164085beb5894b80c0a7d0e4aa3 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Mon, 28 Dec 2020 22:12:18 +0100 Subject: [PATCH 012/112] Extract instances to separate package & drop acyclic --- build.sbt | 2 +- .../com.softwaremill.diffx/package.scala | 1 - .../com.softwaremill.diffx/package.scala | 1 - .../scala/com/softwaremill/diffx/Diff.scala | 13 +-- .../softwaremill/diffx/DiffInstances.scala | 82 ------------------- .../com/softwaremill/diffx/DiffResult.scala | 1 - .../com/softwaremill/diffx/DiffxSupport.scala | 1 - .../com/softwaremill/diffx/Matching.scala | 1 - .../generic/DiffMagnoliaDerivation.scala | 1 - .../diffx/instances/DiffForIterable.scala | 28 +++++++ .../diffx/{ => instances}/DiffForMap.scala | 4 +- .../diffx/instances/DiffForNumeric.scala | 14 ++++ .../diffx/instances/DiffForOption.scala | 13 +++ .../diffx/instances/DiffForSet.scala | 36 ++++++++ .../diffx/{ => instances}/DiffForString.scala | 7 +- 15 files changed, 105 insertions(+), 100 deletions(-) delete mode 100644 core/src/main/scala/com/softwaremill/diffx/DiffInstances.scala create mode 100644 core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala rename core/src/main/scala/com/softwaremill/diffx/{ => instances}/DiffForMap.scala (96%) create mode 100644 core/src/main/scala/com/softwaremill/diffx/instances/DiffForNumeric.scala create mode 100644 core/src/main/scala/com/softwaremill/diffx/instances/DiffForOption.scala create mode 100644 core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala rename core/src/main/scala/com/softwaremill/diffx/{ => instances}/DiffForString.scala (87%) diff --git a/build.sbt b/build.sbt index fe9aef29..1ae1b950 100644 --- a/build.sbt +++ b/build.sbt @@ -8,7 +8,7 @@ val scalatestVersion = "3.2.3" val specs2Version = "4.10.5" val smlTaggingVersion = "2.2.1" -lazy val commonSettings = commonSmlBuildSettings ++ ossPublishSettings ++ acyclicSettings ++ Seq( +lazy val commonSettings = commonSmlBuildSettings ++ ossPublishSettings ++ Seq( organization := "com.softwaremill.diffx", scalaVersion := v2_12, scalafmtOnCompile := true, diff --git a/core/src/main/scala-2.13+/com.softwaremill.diffx/package.scala b/core/src/main/scala-2.13+/com.softwaremill.diffx/package.scala index e1d662b3..0058ba33 100644 --- a/core/src/main/scala-2.13+/com.softwaremill.diffx/package.scala +++ b/core/src/main/scala-2.13+/com.softwaremill.diffx/package.scala @@ -1,5 +1,4 @@ package com.softwaremill -import acyclic.skipped import scala.annotation.compileTimeOnly import scala.collection.Factory diff --git a/core/src/main/scala-2.13-/com.softwaremill.diffx/package.scala b/core/src/main/scala-2.13-/com.softwaremill.diffx/package.scala index b4081154..1d3cd45f 100644 --- a/core/src/main/scala-2.13-/com.softwaremill.diffx/package.scala +++ b/core/src/main/scala-2.13-/com.softwaremill.diffx/package.scala @@ -1,5 +1,4 @@ package com.softwaremill -import acyclic.skipped import scala.annotation.compileTimeOnly import scala.collection.TraversableLike diff --git a/core/src/main/scala/com/softwaremill/diffx/Diff.scala b/core/src/main/scala/com/softwaremill/diffx/Diff.scala index ab54b730..661784b6 100644 --- a/core/src/main/scala/com/softwaremill/diffx/Diff.scala +++ b/core/src/main/scala/com/softwaremill/diffx/Diff.scala @@ -1,5 +1,6 @@ package com.softwaremill.diffx -import acyclic.skipped +import com.softwaremill.diffx.generic.DiffMagnoliaDerivation +import com.softwaremill.diffx.instances._ import magnolia.Magnolia trait Diff[-T] { outer => @@ -34,24 +35,24 @@ object Diff extends MiddlePriorityDiff { implicit val diffForString: Diff[String] = new DiffForString implicit val diffForRange: Diff[Range] = Diff.fallback[Range] - implicit def diffForNumeric[T: Numeric]: Diff[T] = numeric + implicit def diffForNumeric[T: Numeric]: Diff[T] = new DiffForNumeric[T] implicit def diffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]](implicit ddot: Diff[Option[V]], ddk: Diff[K], matcher: ObjectMatcher[K] ): Diff[C[K, V]] = new DiffForMap[K, V, C](matcher, ddk, ddot) - implicit def diffForOptional[T](implicit ddt: Diff[T]): Diff[Option[T]] = optional + implicit def diffForOptional[T](implicit ddt: Diff[T]): Diff[Option[T]] = new DiffForOption[T](ddt) implicit def diffForSet[T, C[W] <: scala.collection.Set[W]](implicit ddt: Diff[T], matcher: ObjectMatcher[T] - ): Diff[C[T]] = set[T, C] + ): Diff[C[T]] = new DiffForSet[T, C](ddt, matcher) } -trait MiddlePriorityDiff extends DiffInstances with LowPriorityDiff { +trait MiddlePriorityDiff extends DiffMagnoliaDerivation with LowPriorityDiff { implicit def diffForIterable[T, C[W] <: Iterable[W]](implicit ddot: Diff[Option[T]] - ): Diff[C[T]] = iterable + ): Diff[C[T]] = new DiffForIterable[T, C](ddot) } trait LowPriorityDiff { diff --git a/core/src/main/scala/com/softwaremill/diffx/DiffInstances.scala b/core/src/main/scala/com/softwaremill/diffx/DiffInstances.scala deleted file mode 100644 index a18ee747..00000000 --- a/core/src/main/scala/com/softwaremill/diffx/DiffInstances.scala +++ /dev/null @@ -1,82 +0,0 @@ -package com.softwaremill.diffx -import acyclic.skipped -import com.softwaremill.diffx.Matching._ -import com.softwaremill.diffx.generic.DiffMagnoliaDerivation - -import scala.collection.immutable.ListMap - -trait DiffInstances extends DiffMagnoliaDerivation { - def numeric[T: Numeric]: Diff[T] = { (left: T, right: T, _: List[FieldPath]) => - val numeric = implicitly[Numeric[T]] - if (!numeric.equiv(left, right)) { - DiffResultValue(left, right) - } else { - Identical(left) - } - } - - def optional[T](implicit ddt: Diff[T]): Diff[Option[T]] = { - (left: Option[T], right: Option[T], toIgnore: List[FieldPath]) => - (left, right) match { - case (Some(l), Some(r)) => ddt.apply(l, r, toIgnore) - case (None, None) => Identical(None) - case (l, r) => DiffResultValue(l, r) - } - } - - def set[T, C[W] <: scala.collection.Set[W]](implicit - ddt: Diff[T], - matcher: ObjectMatcher[T] - ): Diff[C[T]] = { (left: C[T], right: C[T], toIgnore: List[FieldPath]) => - val MatchingResults(unMatchedLeftInstances, unMatchedRightInstances, matchedInstances) = - matching[T](left.toSet, right.toSet, matcher, ddt, toIgnore) - val leftDiffs = unMatchedLeftInstances - .diff(unMatchedRightInstances) - .map(DiffResultAdditional(_)) - .toList - val rightDiffs = unMatchedRightInstances - .diff(unMatchedLeftInstances) - .map(DiffResultMissing(_)) - .toList - val matchedDiffs = matchedInstances.map { case (l, r) => ddt(l, r, toIgnore) }.toList - diffResultSet(left, leftDiffs, rightDiffs, matchedDiffs) - } - - private def diffResultSet[T]( - left: T, - leftDiffs: List[DiffResult], - rightDiffs: List[DiffResult], - matchedDiffs: List[DiffResult] - ): DiffResult = { - val diffs = leftDiffs ++ rightDiffs ++ matchedDiffs - if (diffs.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultSet(diffs) - } - } - - def iterable[T, C[W] <: Iterable[W]](implicit - ddot: Diff[Option[T]] - ): Diff[C[T]] = { (left: C[T], right: C[T], toIgnore: List[FieldPath]) => - val indexes = Range(0, Math.max(left.size, right.size)) - val leftAsMap = left.toList.lift - val rightAsMap = right.toList.lift - val differences = ListMap(indexes.map { index => - index.toString -> (ddot.apply(leftAsMap(index), rightAsMap(index), toIgnore) match { - case DiffResultValue(Some(v), None) => DiffResultAdditional(v) - case DiffResultValue(None, Some(v)) => DiffResultMissing(v) - case d => d - }) - }: _*) - - if (differences.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject( - "List", - differences - ) - } - } -} diff --git a/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala b/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala index b701b2f9..d956419a 100644 --- a/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala +++ b/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala @@ -1,5 +1,4 @@ package com.softwaremill.diffx -import acyclic.skipped import DiffResult._ trait DiffResult extends Product with Serializable { diff --git a/core/src/main/scala/com/softwaremill/diffx/DiffxSupport.scala b/core/src/main/scala/com/softwaremill/diffx/DiffxSupport.scala index 671da61c..5bfa23aa 100644 --- a/core/src/main/scala/com/softwaremill/diffx/DiffxSupport.scala +++ b/core/src/main/scala/com/softwaremill/diffx/DiffxSupport.scala @@ -1,5 +1,4 @@ package com.softwaremill.diffx -import acyclic.skipped import scala.annotation.compileTimeOnly import com.softwaremill.diffx.DiffxSupport._ diff --git a/core/src/main/scala/com/softwaremill/diffx/Matching.scala b/core/src/main/scala/com/softwaremill/diffx/Matching.scala index 8b92bcaa..8d986c41 100644 --- a/core/src/main/scala/com/softwaremill/diffx/Matching.scala +++ b/core/src/main/scala/com/softwaremill/diffx/Matching.scala @@ -1,5 +1,4 @@ package com.softwaremill.diffx -import acyclic.skipped private[diffx] object Matching { private[diffx] def matching[T]( diff --git a/core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala b/core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala index 8c0f1047..386d5b68 100644 --- a/core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala +++ b/core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala @@ -2,7 +2,6 @@ package com.softwaremill.diffx.generic import com.softwaremill.diffx.{Diff, DiffResultObject, DiffResultValue, FieldPath, Identical} import magnolia._ -import acyclic.skipped import scala.collection.immutable.ListMap diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala new file mode 100644 index 00000000..100551f5 --- /dev/null +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala @@ -0,0 +1,28 @@ +package com.softwaremill.diffx.instances + +import com.softwaremill.diffx._ +import scala.collection.immutable.ListMap + +private[diffx] class DiffForIterable[T, C[W] <: Iterable[W]](dot: Diff[Option[T]]) extends Diff[C[T]] { + override def apply(left: C[T], right: C[T], toIgnore: List[FieldPath]): DiffResult = { + val indexes = Range(0, Math.max(left.size, right.size)) + val leftAsMap = left.toList.lift + val rightAsMap = right.toList.lift + val differences = ListMap(indexes.map { index => + index.toString -> (dot.apply(leftAsMap(index), rightAsMap(index), toIgnore) match { + case DiffResultValue(Some(v), None) => DiffResultAdditional(v) + case DiffResultValue(None, Some(v)) => DiffResultMissing(v) + case d => d + }) + }: _*) + + if (differences.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject( + "List", + differences + ) + } + } +} diff --git a/core/src/main/scala/com/softwaremill/diffx/DiffForMap.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala similarity index 96% rename from core/src/main/scala/com/softwaremill/diffx/DiffForMap.scala rename to core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala index c7ab6396..08057e4b 100644 --- a/core/src/main/scala/com/softwaremill/diffx/DiffForMap.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala @@ -1,7 +1,7 @@ -package com.softwaremill.diffx -import acyclic.skipped +package com.softwaremill.diffx.instances import com.softwaremill.diffx.Matching._ +import com.softwaremill.diffx._ private[diffx] class DiffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]]( matcher: ObjectMatcher[K], diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForNumeric.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForNumeric.scala new file mode 100644 index 00000000..157c5e05 --- /dev/null +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForNumeric.scala @@ -0,0 +1,14 @@ +package com.softwaremill.diffx.instances + +import com.softwaremill.diffx._ + +private[diffx] class DiffForNumeric[T: Numeric] extends Diff[T] { + override def apply(left: T, right: T, toIgnore: List[FieldPath]): DiffResult = { + val numeric = implicitly[Numeric[T]] + if (!numeric.equiv(left, right)) { + DiffResultValue(left, right) + } else { + Identical(left) + } + } +} diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForOption.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForOption.scala new file mode 100644 index 00000000..0542269d --- /dev/null +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForOption.scala @@ -0,0 +1,13 @@ +package com.softwaremill.diffx.instances + +import com.softwaremill.diffx._ + +private[diffx] class DiffForOption[T](dt: Diff[T]) extends Diff[Option[T]] { + override def apply(left: Option[T], right: Option[T], toIgnore: List[FieldPath]): DiffResult = { + (left, right) match { + case (Some(l), Some(r)) => dt.apply(l, r, toIgnore) + case (None, None) => Identical(None) + case (l, r) => DiffResultValue(l, r) + } + } +} diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala new file mode 100644 index 00000000..0dbadf1a --- /dev/null +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala @@ -0,0 +1,36 @@ +package com.softwaremill.diffx.instances + +import com.softwaremill.diffx.Matching.{MatchingResults, matching} +import com.softwaremill.diffx._ + +private[diffx] class DiffForSet[T, C[W] <: scala.collection.Set[W]](dt: Diff[T], matcher: ObjectMatcher[T]) + extends Diff[C[T]] { + override def apply(left: C[T], right: C[T], toIgnore: List[FieldPath]): DiffResult = { + val MatchingResults(unMatchedLeftInstances, unMatchedRightInstances, matchedInstances) = + matching[T](left.toSet, right.toSet, matcher, dt, toIgnore) + val leftDiffs = unMatchedLeftInstances + .diff(unMatchedRightInstances) + .map(DiffResultAdditional(_)) + .toList + val rightDiffs = unMatchedRightInstances + .diff(unMatchedLeftInstances) + .map(DiffResultMissing(_)) + .toList + val matchedDiffs = matchedInstances.map { case (l, r) => dt(l, r, toIgnore) }.toList + diffResultSet(left, leftDiffs, rightDiffs, matchedDiffs) + } + + private def diffResultSet( + left: C[T], + leftDiffs: List[DiffResult], + rightDiffs: List[DiffResult], + matchedDiffs: List[DiffResult] + ): DiffResult = { + val diffs = leftDiffs ++ rightDiffs ++ matchedDiffs + if (diffs.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultSet(diffs) + } + } +} diff --git a/core/src/main/scala/com/softwaremill/diffx/DiffForString.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForString.scala similarity index 87% rename from core/src/main/scala/com/softwaremill/diffx/DiffForString.scala rename to core/src/main/scala/com/softwaremill/diffx/instances/DiffForString.scala index 28167026..213e8d1d 100644 --- a/core/src/main/scala/com/softwaremill/diffx/DiffForString.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForString.scala @@ -1,7 +1,8 @@ -package com.softwaremill.diffx -import acyclic.skipped +package com.softwaremill.diffx.instances -class DiffForString extends Diff[String] { +import com.softwaremill.diffx._ + +private[diffx] class DiffForString extends Diff[String] { override def apply(left: String, right: String, toIgnore: List[FieldPath]): DiffResult = { val leftLines = left.split("\n").toList val rightLines = right.split("\n").toList From e58aaa2d1e145d1cb708002dd2f34f1222850d0a Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Tue, 29 Dec 2020 16:04:47 +0100 Subject: [PATCH 013/112] Fix 3rd party integrations --- .../diffx/cats/DiffCatsInstances.scala | 18 +++++++++--------- docs-sources/README.md | 4 ++-- .../diffx/refined/RefinedSupport.scala | 4 ++-- .../diffx/refined/RefinedSupportTest.scala | 1 + .../diffx/scalatest/DiffMatcherTest.scala | 1 + .../diffx/specs2/DiffMatcherTest.scala | 1 + .../diffx/tagging/DiffTaggingSupport.scala | 4 ++-- .../tagging/test/DiffTaggingSupportTest.scala | 1 + .../diffx/utest/UtestAssertTest.scala | 1 + 9 files changed, 20 insertions(+), 15 deletions(-) diff --git a/cats/shared/src/main/scala/com/softwaremill/diffx/cats/DiffCatsInstances.scala b/cats/shared/src/main/scala/com/softwaremill/diffx/cats/DiffCatsInstances.scala index 6db53a02..ca77a945 100644 --- a/cats/shared/src/main/scala/com/softwaremill/diffx/cats/DiffCatsInstances.scala +++ b/cats/shared/src/main/scala/com/softwaremill/diffx/cats/DiffCatsInstances.scala @@ -1,19 +1,19 @@ package com.softwaremill.diffx.cats import cats.data.{NonEmptyChain, NonEmptyList, NonEmptySet, NonEmptyVector} -import com.softwaremill.diffx.{Derived, Diff} +import com.softwaremill.diffx.Diff import com.softwaremill.diffx.Diff._ trait DiffCatsInstances { - implicit def diffNel[T: Diff]: Derived[Diff[NonEmptyList[T]]] = - Derived(Diff[List[T]].contramap[NonEmptyList[T]](_.toList)) + implicit def diffNel[T: Diff]: Diff[NonEmptyList[T]] = + Diff[List[T]].contramap[NonEmptyList[T]](_.toList) - implicit def diffNec[T: Diff]: Derived[Diff[NonEmptyChain[T]]] = - Derived(Diff[List[T]].contramap[NonEmptyChain[T]](_.toChain.toList)) + implicit def diffNec[T: Diff]: Diff[NonEmptyChain[T]] = + Diff[List[T]].contramap[NonEmptyChain[T]](_.toChain.toList) - implicit def diffNes[T: Diff]: Derived[Diff[NonEmptySet[T]]] = - Derived(Diff[Set[T]].contramap[NonEmptySet[T]](_.toSortedSet)) + implicit def diffNes[T: Diff]: Diff[NonEmptySet[T]] = + Diff[Set[T]].contramap[NonEmptySet[T]](_.toSortedSet) - implicit def diffNev[T: Diff]: Derived[Diff[NonEmptyVector[T]]] = - Derived(Diff[List[T]].contramap[NonEmptyVector[T]](_.toVector.toList)) + implicit def diffNev[T: Diff]: Diff[NonEmptyVector[T]] = + Diff[List[T]].contramap[NonEmptyVector[T]](_.toVector.toList) } diff --git a/docs-sources/README.md b/docs-sources/README.md index 67bd7ac4..a65c57b1 100644 --- a/docs-sources/README.md +++ b/docs-sources/README.md @@ -57,7 +57,7 @@ val left: Foo = Foo( Some(right) ) - +import com.softwaremill.diffx.generic.auto._ import com.softwaremill.diffx._ compare(left, right) ``` @@ -175,7 +175,7 @@ sealed trait ABParent case class A(id: String, name: String) extends ABParent case class B(id: String, name: String) extends ABParent -implicit val diffA: Derived[Diff[A]] = Derived(Diff.gen[A].value.ignore[A, String](_.id)) +implicit val diffA: Diff[A] = Derived[Diff[A]].ignore[A, String](_.id) ``` ```scala mdoc val a1: ABParent = A("1", "X") diff --git a/refined/shared/src/main/scala/com/softwaremill/diffx/refined/RefinedSupport.scala b/refined/shared/src/main/scala/com/softwaremill/diffx/refined/RefinedSupport.scala index bf8eb31f..bb96a725 100644 --- a/refined/shared/src/main/scala/com/softwaremill/diffx/refined/RefinedSupport.scala +++ b/refined/shared/src/main/scala/com/softwaremill/diffx/refined/RefinedSupport.scala @@ -1,8 +1,8 @@ package com.softwaremill.diffx.refined -import com.softwaremill.diffx.{Derived, Diff} +import com.softwaremill.diffx.Diff import eu.timepit.refined.api.Refined trait RefinedSupport { - implicit def refinedDiff[T: Diff, P]: Derived[Diff[T Refined P]] = Derived(Diff[T].contramap[T Refined P](_.value)) + implicit def refinedDiff[T: Diff, P]: Diff[T Refined P] = Diff[T].contramap[T Refined P](_.value) } diff --git a/refined/shared/src/test/scala/com/softwaremill/diffx/refined/RefinedSupportTest.scala b/refined/shared/src/test/scala/com/softwaremill/diffx/refined/RefinedSupportTest.scala index 32f89509..7137cac1 100644 --- a/refined/shared/src/test/scala/com/softwaremill/diffx/refined/RefinedSupportTest.scala +++ b/refined/shared/src/test/scala/com/softwaremill/diffx/refined/RefinedSupportTest.scala @@ -6,6 +6,7 @@ import eu.timepit.refined.auto._ import eu.timepit.refined.types.string.NonEmptyString import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import com.softwaremill.diffx.generic.auto._ class RefinedSupportTest extends AnyFlatSpec with Matchers { it should "work for refined types" in { diff --git a/scalatest/shared/src/test/scala/com/softwaremill/diffx/scalatest/DiffMatcherTest.scala b/scalatest/shared/src/test/scala/com/softwaremill/diffx/scalatest/DiffMatcherTest.scala index e6a7188c..3f32ef29 100644 --- a/scalatest/shared/src/test/scala/com/softwaremill/diffx/scalatest/DiffMatcherTest.scala +++ b/scalatest/shared/src/test/scala/com/softwaremill/diffx/scalatest/DiffMatcherTest.scala @@ -2,6 +2,7 @@ package com.softwaremill.diffx.scalatest import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import com.softwaremill.diffx.generic.auto._ class DiffMatcherTest extends AnyFlatSpec with Matchers with DiffMatcher { val right: Foo = Foo( diff --git a/specs2/shared/src/test/scala/com/softwaremill/diffx/specs2/DiffMatcherTest.scala b/specs2/shared/src/test/scala/com/softwaremill/diffx/specs2/DiffMatcherTest.scala index 6e4016ff..f2c27fcb 100644 --- a/specs2/shared/src/test/scala/com/softwaremill/diffx/specs2/DiffMatcherTest.scala +++ b/specs2/shared/src/test/scala/com/softwaremill/diffx/specs2/DiffMatcherTest.scala @@ -1,6 +1,7 @@ package com.softwaremill.diffx.specs2 import org.specs2.Specification +import com.softwaremill.diffx.generic.auto._ class DiffMatcherTest extends Specification with DiffMatcher { override def is = s2"""This is an empty specification""" diff --git a/tagging/shared/src/main/scala/com/softwaremill/diffx/tagging/DiffTaggingSupport.scala b/tagging/shared/src/main/scala/com/softwaremill/diffx/tagging/DiffTaggingSupport.scala index 3bc2295b..144e0e15 100644 --- a/tagging/shared/src/main/scala/com/softwaremill/diffx/tagging/DiffTaggingSupport.scala +++ b/tagging/shared/src/main/scala/com/softwaremill/diffx/tagging/DiffTaggingSupport.scala @@ -1,8 +1,8 @@ package com.softwaremill.diffx.tagging -import com.softwaremill.diffx.{Derived, Diff} +import com.softwaremill.diffx.Diff import com.softwaremill.tagging.@@ trait DiffTaggingSupport { - implicit def taggedDiff[T: Diff, U]: Derived[Diff[T @@ U]] = Derived(Diff[T].contramap[T @@ U](identity)) + implicit def taggedDiff[T: Diff, U]: Diff[T @@ U] = Diff[T].contramap[T @@ U](identity) } diff --git a/tagging/shared/src/test/scala/com/softwaremill/diffx/tagging/test/DiffTaggingSupportTest.scala b/tagging/shared/src/test/scala/com/softwaremill/diffx/tagging/test/DiffTaggingSupportTest.scala index a04f1648..73689af0 100644 --- a/tagging/shared/src/test/scala/com/softwaremill/diffx/tagging/test/DiffTaggingSupportTest.scala +++ b/tagging/shared/src/test/scala/com/softwaremill/diffx/tagging/test/DiffTaggingSupportTest.scala @@ -6,6 +6,7 @@ import com.softwaremill.tagging._ import com.softwaremill.tagging.@@ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import com.softwaremill.diffx.generic.auto._ class DiffTaggingSupportTest extends AnyFlatSpec with Matchers { it should "work for tagged types" in { diff --git a/utest/shared/src/test/scala/com/softwaremill/diffx/utest/UtestAssertTest.scala b/utest/shared/src/test/scala/com/softwaremill/diffx/utest/UtestAssertTest.scala index 638e7e01..28e9cff2 100644 --- a/utest/shared/src/test/scala/com/softwaremill/diffx/utest/UtestAssertTest.scala +++ b/utest/shared/src/test/scala/com/softwaremill/diffx/utest/UtestAssertTest.scala @@ -1,6 +1,7 @@ package com.softwaremill.diffx.utest import utest._ +import com.softwaremill.diffx.generic.auto._ object UtestAssertTest extends TestSuite with DiffxAssertions { val tests = Tests { From fe393c31fdf5b5c6dffab2072e951b212c9e6e17 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Tue, 29 Dec 2020 16:49:51 +0100 Subject: [PATCH 014/112] Update readme --- docs-sources/README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs-sources/README.md b/docs-sources/README.md index a65c57b1..c423661d 100644 --- a/docs-sources/README.md +++ b/docs-sources/README.md @@ -149,13 +149,11 @@ implicit val modifiedDiff: Diff[Person] = Derived[Diff[Person]].ignore[Person,St ## Customization -If you'd like to implement custom matching logic for the given type, create an implicit `Diff` instance for that +If you'd like to implement custom matching logic for the given type, create an implicit `Diff` instance for that type, and make sure it's in scope when any `Diff` instances depending on that type are created. -If there is already a typeclass for a particular type, but you would like to use another one, you wil have to override existing instance. Because of the "exporting" mechanism the top level typeclass is `Derived[Diff]` rather then `Diff` and that's the typeclass you need to override. - -To understand it better, consider following example with `NonEmptyList` from cats. -`NonEmptyList` is implemented as case class so diffx will create a `Derived[Diff[NonEmptyList]]` typeclass instance using magnolia derivation. +Consider following example with `NonEmptyList` from cats. `NonEmptyList` is implemented as case class so diffx +will create a `Diff[NonEmptyList]` typeclass instance using magnolia derivation. Obviously that's not what we usually want. In most of the cases we would like `NonEmptyList` to be compared as a list. Diffx already has an instance of a typeclass for a list. One more thing to do is to use that typeclass by converting `NonEmptyList` to list which can be done using `contramap` method. @@ -164,11 +162,11 @@ The final code looks as follows: ```scala mdoc:nest import cats.data.NonEmptyList -implicit def nelDiff[T: Diff]: Derived[Diff[NonEmptyList[T]]] = - Derived(Diff[List[T]].contramap[NonEmptyList[T]](_.toList)) +implicit def nelDiff[T: Diff]: Diff[NonEmptyList[T]] = + Diff[List[T]].contramap[NonEmptyList[T]](_.toList) ``` -And here's an example customizing the `Diff` instance for a child class of a sealed trait +And here's an example of customizing the `Diff` instance for a child class of a sealed trait ```scala mdoc:silent sealed trait ABParent @@ -184,6 +182,9 @@ val a2: ABParent = A("2", "X") compare(a1, a2) ``` +As you can see instead of summoning bare instance of `Diff` for given `A` we summoned `Derived[Diff[A]]`. +This is required in order to workaround self reference error. + You may need to add `-Wmacros:after` Scala compiler option to make sure to check for unused implicits after macro expansion. If you get warnings from Magnolia which looks like `magnolia: using fallback derivation for TYPE`, From 65fb75ab9ac0a596f28f29155a8d54aee0e23ad9 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Tue, 29 Dec 2020 17:00:28 +0100 Subject: [PATCH 015/112] Readme: add note on semiauto --- docs-sources/README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs-sources/README.md b/docs-sources/README.md index c423661d..594ea56e 100644 --- a/docs-sources/README.md +++ b/docs-sources/README.md @@ -13,6 +13,7 @@ The library is published for Scala 2.12 and 2.13. ## Table of contents - [goals of the project](#goals-of-the-project) - [teaser](#teaser) +- [derivation](#derivation) - [colors](#colors) - integrations - [scalatest](#scalatest-integration) @@ -67,6 +68,29 @@ Will result in: ![example](https://github.com/softwaremill/diff-x/blob/master/example.png?raw=true) +## Derivation + +Diffx supports auto and semi-auto derivation. +To use auto derivation add following import + +`import com.softwaremill.diffx.generic.auto._` + +or + +extend trait + +`com.softwaremill.diffx.generic.DiffDerivation` + + +For semi-auto derivation you don't need any additional import, just define your instances using: +```scala mdoc:compile-only +case class Product(name: String) +case class Basket(products: List[Product]) + +val productDiff = Diff.derived[Product] +val basketDiff = Diff.derived[Basket] +``` + ## Colors When running tests through sbt, default diffx's colors work well on both dark and light backgrounds. From a666122eaa5ed792b65f1bd88e9458a2ec361ec7 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Wed, 30 Dec 2020 09:13:58 +0100 Subject: [PATCH 016/112] Setting version to 0.4.0 --- README.md | 61 +++++++++++++++++++++++++++++++++++++---------------- version.sbt | 2 +- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 03803204..e1b5e62f 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ The library is published for Scala 2.12 and 2.13. ## Table of contents - [goals of the project](#goals-of-the-project) - [teaser](#teaser) +- [derivation](#derivation) - [colors](#colors) - integrations - [scalatest](#scalatest-integration) @@ -37,7 +38,7 @@ The library is published for Scala 2.12 and 2.13. Add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-core" % "0.3.30" +"com.softwaremill.diffx" %% "diffx-core" % "0.4.0" ``` ```scala @@ -63,7 +64,7 @@ val left: Foo = Foo( // Some(Foo(Bar("asdf", 5), List(123, 1234), Some(Bar("asdf", 5)))) // ) - +import com.softwaremill.diffx.generic.auto._ import com.softwaremill.diffx._ compare(left, right) // res0: DiffResult = DiffResultObject( @@ -87,6 +88,29 @@ Will result in: ![example](https://github.com/softwaremill/diff-x/blob/master/example.png?raw=true) +## Derivation + +Diffx supports auto and semi-auto derivation. +To use auto derivation add following import + +`import com.softwaremill.diffx.generic.auto._` + +or + +extend trait + +`com.softwaremill.diffx.generic.DiffDerivation` + + +For semi-auto derivation you don't need any additional import, just define your instances using: +```scala +case class Product(name: String) +case class Basket(products: List[Product]) + +val productDiff = Diff.derived[Product] +val basketDiff = Diff.derived[Basket] +``` + ## Colors When running tests through sbt, default diffx's colors work well on both dark and light backgrounds. @@ -104,7 +128,7 @@ If anyone has an idea how this could be improved, I am open for suggestions. To use with scalatest, add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-scalatest" % "0.3.30" % Test +"com.softwaremill.diffx" %% "diffx-scalatest" % "0.4.0" % Test ``` Then, extend the `com.softwaremill.diffx.scalatest.DiffMatcher` trait or `import com.softwaremill.diffx.scalatest.DiffMatcher._`. @@ -124,7 +148,7 @@ Giving you nice error messages: To use with specs2, add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-specs2" % "0.3.30" % Test +"com.softwaremill.diffx" %% "diffx-specs2" % "0.4.0" % Test ``` Then, extend the `com.softwaremill.diffx.specs2.DiffMatcher` trait or `import com.softwaremill.diffx.specs2.DiffMatcher._`. @@ -142,7 +166,7 @@ left must matchTo(right) To use with utest, add following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-utest" % "0.3.30" % Test +"com.softwaremill.diffx" %% "diffx-utest" % "0.4.0" % Test ``` Then, mixin `DiffxAssertions` trait or add `import com.softwaremill.diffx.utest.DiffxAssertions._` to your test code. @@ -169,13 +193,11 @@ implicit val modifiedDiff: Diff[Person] = Derived[Diff[Person]].ignore[Person,St ## Customization -If you'd like to implement custom matching logic for the given type, create an implicit `Diff` instance for that +If you'd like to implement custom matching logic for the given type, create an implicit `Diff` instance for that type, and make sure it's in scope when any `Diff` instances depending on that type are created. -If there is already a typeclass for a particular type, but you would like to use another one, you wil have to override existing instance. Because of the "exporting" mechanism the top level typeclass is `Derived[Diff]` rather then `Diff` and that's the typeclass you need to override. - -To understand it better, consider following example with `NonEmptyList` from cats. -`NonEmptyList` is implemented as case class so diffx will create a `Derived[Diff[NonEmptyList]]` typeclass instance using magnolia derivation. +Consider following example with `NonEmptyList` from cats. `NonEmptyList` is implemented as case class so diffx +will create a `Diff[NonEmptyList]` typeclass instance using magnolia derivation. Obviously that's not what we usually want. In most of the cases we would like `NonEmptyList` to be compared as a list. Diffx already has an instance of a typeclass for a list. One more thing to do is to use that typeclass by converting `NonEmptyList` to list which can be done using `contramap` method. @@ -184,18 +206,18 @@ The final code looks as follows: ```scala import cats.data.NonEmptyList -implicit def nelDiff[T: Diff]: Derived[Diff[NonEmptyList[T]]] = - Derived(Diff[List[T]].contramap[NonEmptyList[T]](_.toList)) +implicit def nelDiff[T: Diff]: Diff[NonEmptyList[T]] = + Diff[List[T]].contramap[NonEmptyList[T]](_.toList) ``` -And here's an example customizing the `Diff` instance for a child class of a sealed trait +And here's an example of customizing the `Diff` instance for a child class of a sealed trait ```scala sealed trait ABParent case class A(id: String, name: String) extends ABParent case class B(id: String, name: String) extends ABParent -implicit val diffA: Derived[Diff[A]] = Derived(Diff.gen[A].value.ignore[A, String](_.id)) +implicit val diffA: Diff[A] = Derived[Diff[A]].ignore[A, String](_.id) ``` ```scala val a1: ABParent = A("1", "X") @@ -204,9 +226,12 @@ val a2: ABParent = A("2", "X") // a2: ABParent = A("2", "X") compare(a1, a2) -// res5: DiffResult = Identical(A("1", "X")) +// res6: DiffResult = Identical(A("1", "X")) ``` +As you can see instead of summoning bare instance of `Diff` for given `A` we summoned `Derived[Diff[A]]`. +This is required in order to workaround self reference error. + You may need to add `-Wmacros:after` Scala compiler option to make sure to check for unused implicits after macro expansion. If you get warnings from Magnolia which looks like `magnolia: using fallback derivation for TYPE`, @@ -217,17 +242,17 @@ with the compiler option `"-P:silencer:globalFilters=^magnolia: using fallback d - [com.softwaremill.common.tagging](https://github.com/softwaremill/scala-common) ```scala - "com.softwaremill.diffx" %% "diffx-tagging" % "0.3.30" + "com.softwaremill.diffx" %% "diffx-tagging" % "0.4.0" ``` `com.softwaremill.diffx.tagging.DiffTaggingSupport` - [eu.timepit.refined](https://github.com/fthomas/refined) ```scala - "com.softwaremill.diffx" %% "diffx-refined" % "0.3.30" + "com.softwaremill.diffx" %% "diffx-refined" % "0.4.0" ``` `com.softwaremill.diffx.refined.RefinedSupport` - [org.typelevel.cats](https://github.com/typelevel/cats) ```scala - "com.softwaremill.diffx" %% "diffx-cats" % "0.3.30" + "com.softwaremill.diffx" %% "diffx-cats" % "0.4.0" ``` `com.softwaremill.diffx.cats.DiffCatsInstances` diff --git a/version.sbt b/version.sbt index 28056724..9f0e5a53 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.3.31-SNAPSHOT" +version in ThisBuild := "0.4.0" From e2441ef1daff985f2205226aec935235c1f05024 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Wed, 30 Dec 2020 09:14:00 +0100 Subject: [PATCH 017/112] Setting version to 0.4.1-SNAPSHOT --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 9f0e5a53..5492cbd5 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.4.0" +version in ThisBuild := "0.4.1-SNAPSHOT" From 8ed0a55ba33baca0b1fe95d4b7af13fc87f62ffc Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Wed, 30 Dec 2020 17:22:41 +0100 Subject: [PATCH 018/112] Harden the test suite - perform real implicit lookup for Diffx instances --- .../diffx/test/DiffSemiautoTest.scala | 8 +- .../softwaremill/diffx/test/DiffTest.scala | 81 ++++++++++--------- 2 files changed, 46 insertions(+), 43 deletions(-) diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala index 41c071a1..53499d43 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala @@ -10,8 +10,8 @@ class DiffSemiautoTest extends AnyFreeSpec with Matchers { |final case class P1(f1: String) |final case class P2(f1: P1) | - |implicit val p1 = Diff.derived[P1] - |implicit val p2 = Diff.derived[P2] + |implicit val p1: Diff[P1] = Diff.derived[P1] + |implicit val p2: Diff[P2] = Diff.derived[P2] |""".stripMargin) } @@ -21,7 +21,7 @@ class DiffSemiautoTest extends AnyFreeSpec with Matchers { |final case class P1(f1: String) |final case class P2(f1: P1) | - |implicit val p2 = Diff.derived[P2] + |implicit val p2: Diff[P2] = Diff.derived[P2] |""".stripMargin) } @@ -32,7 +32,7 @@ class DiffSemiautoTest extends AnyFreeSpec with Matchers { |final case class P1(f1: String) |final case class P2(f1: P1) | - |val p2 = Diff[P2] + |val p2: Diff[P2] = Diff[P2] |""".stripMargin) } } diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala index 565bc453..9341937d 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala @@ -43,9 +43,9 @@ class DiffTest extends AnyFreeSpec with Matchers { } "ignoring given fields" in { - val d = Diff[Person].ignoreUnsafe("name").ignoreUnsafe("age") + implicit val d: Diff[Person] = Derived[Diff[Person]].ignoreUnsafe("name").ignoreUnsafe("age") val p3 = p2.copy(in = Instant.now()) - compare(p1, p3)(d) shouldBe DiffResultObject( + compare(p1, p3) shouldBe DiffResultObject( "Person", Map( "name" -> Identical(p1.name), @@ -77,8 +77,8 @@ class DiffTest extends AnyFreeSpec with Matchers { "nested products ignoring nested fields" in { val f1 = Family(p1, p2) val f2 = Family(p1, p1) - val d = Diff[Family].ignoreUnsafe("second", "name") - compare(f1, f2)(d) shouldBe DiffResultObject( + implicit val d: Diff[Family] = Derived[Diff[Family]].ignoreUnsafe("second", "name") + compare(f1, f2) shouldBe DiffResultObject( "Family", Map( "first" -> Identical(p1), @@ -98,8 +98,8 @@ class DiffTest extends AnyFreeSpec with Matchers { val p1p = p1.copy(name = "other") val f1 = Family(p1, p2) val f2 = Family(p1p, p2.copy(name = "other")) - val d = Diff[Family].ignoreUnsafe("second", "name") - compare(f1, f2)(d) shouldBe DiffResultObject( + implicit val d: Diff[Family] = Derived[Diff[Family]].ignoreUnsafe("second", "name") + compare(f1, f2) shouldBe DiffResultObject( "Family", Map( "first" -> DiffResultObject( @@ -118,8 +118,8 @@ class DiffTest extends AnyFreeSpec with Matchers { "nested products ignoring nested products" in { val f1 = Family(p1, p2) val f2 = Family(p1, p1) - val d = Diff[Family].ignoreUnsafe("second") - compare(f1, f2)(d) shouldBe Identical(f1) + implicit val d: Diff[Family] = Derived[Diff[Family]].ignoreUnsafe("second") + compare(f1, f2) shouldBe Identical(f1) } "list of products" in { @@ -186,21 +186,21 @@ class DiffTest extends AnyFreeSpec with Matchers { ) ) } - - "coproduct types with ignored fields" in { - sealed trait Base { - def id: Int - def name: String - } - - final case class SubtypeOne(id: Int, name: String) extends Base - final case class SubtypeTwo(id: Int, name: String) extends Base - - val left: Base = SubtypeOne(2, "one") - val right: Base = SubtypeOne(1, "one") - val diff = Derived[Diff[Base]].ignoreUnsafe("id") - compare(left, right)(diff) shouldBe an[Identical[Base]] - } +// TODO: uncomment once https://github.com/propensive/magnolia/issues/277 is resolved +// +// "coproduct types with ignored fields" in { +// sealed trait Base { +// def id: Int +// def name: String +// } +// +// final case class SubtypeOne(id: Int, name: String) extends Base +// final case class SubtypeTwo(id: Int, name: String) extends Base +// val left: Base = SubtypeOne(2, "one") +// val right: Base = SubtypeOne(1, "one") +// implicit val diff: Diff[Base] = Derived[Diff[Base]].ignoreUnsafe("id") +// compare(left, right) shouldBe an[Identical[Base]] +// } } "collections" - { @@ -219,8 +219,8 @@ class DiffTest extends AnyFreeSpec with Matchers { "use ignored fields from elements" in { val o1 = Organization(List(p1, p2)) val o2 = Organization(List(p1, p1, p1)) - val d = Diff[Organization].ignoreUnsafe("people", "name") - compare(o1, o2)(d) shouldBe DiffResultObject( + implicit val d: Diff[Organization] = Derived[Diff[Organization]].ignoreUnsafe("people", "name") + compare(o1, o2) shouldBe DiffResultObject( "Organization", Map( "people" -> DiffResultObject( @@ -283,10 +283,9 @@ class DiffTest extends AnyFreeSpec with Matchers { } "ignored fields from elements" in { val p2m = p2.copy(age = 33, in = Instant.now()) - val d = Diff[Person].ignoreUnsafe("age") + implicit val d: Diff[Person] = Derived[Diff[Person]].ignoreUnsafe("age") implicit val im: ObjectMatcher[Person] = (left: Person, right: Person) => left.name == right.name - val ds: Diff[Set[Person]] = Diff.diffForSet(d, im) - compare(Set(p1, p2), Set(p1, p2m))(ds) shouldBe DiffResultSet( + compare(Set(p1, p2), Set(p1, p2m)) shouldBe DiffResultSet( List( Identical(p1), DiffResultObject( @@ -315,18 +314,15 @@ class DiffTest extends AnyFreeSpec with Matchers { "identical when products are identical using ignored" in { val p2m = p2.copy(age = 33, in = Instant.now()) - val d = Diff[Person].ignoreUnsafe("age").ignoreUnsafe("in") - val ds: Diff[Set[Person]] = - Diff.diffForSet(d, implicitly[ObjectMatcher[Person]]) - compare(Set(p1, p2), Set(p1, p2m))(ds) shouldBe Identical(Set(p1, p2)) + implicit val d: Diff[Person] = Derived[Diff[Person]].ignoreUnsafe("age").ignoreUnsafe("in") + compare(Set(p1, p2), Set(p1, p2m)) shouldBe Identical(Set(p1, p2)) } "propagate ignore fields to elements" in { val p2m = p2.copy(in = Instant.now()) implicit val im: ObjectMatcher[Person] = (left: Person, right: Person) => left.name == right.name - val ds: Diff[Set[Person]] = - Diff.diffForSet(Diff[Person], im).ignoreUnsafe("age") - compare(Set(p1, p2), Set(p1, p2m))(ds) shouldBe DiffResultSet( + implicit val ds: Diff[Person] = Derived[Diff[Person]].ignoreUnsafe("age") + compare(Set(p1, p2), Set(p1, p2m)) shouldBe DiffResultSet( List( Identical(p1), DiffResultObject( @@ -346,6 +342,12 @@ class DiffTest extends AnyFreeSpec with Matchers { List(DiffResultAdditional(p2), DiffResultMissing(p2m), Identical(p1)) ) } + "override set instance" in { + val p2m = p2.copy(age = 33) + implicit def setDiff[T, C[W] <: scala.collection.Set[W]]: Diff[C[T]] = + (left: C[T], _: C[T], _: List[Any]) => Identical(left) + compare(Set(p1, p2), Set(p1, p2m)) shouldBe an[Identical[_]] + } "set of products using instance matcher" in { val p2m = p2.copy(age = 33) @@ -380,8 +382,8 @@ class DiffTest extends AnyFreeSpec with Matchers { } "propagate ignored fields to elements" in { - val dm = Diff[Map[String, Person]].ignoreUnsafe("age") - compare(Map("first" -> p1), Map("first" -> p2))(dm) shouldBe DiffResultMap( + implicit val dm: Diff[Person] = Derived[Diff[Person]].ignoreUnsafe("age") + compare(Map("first" -> p1), Map("first" -> p2)) shouldBe DiffResultMap( Map( Identical("first") -> DiffResultObject( "Person", @@ -396,8 +398,9 @@ class DiffTest extends AnyFreeSpec with Matchers { } "identical when products are identical using ignore" in { - val dm = Diff[Map[String, Person]].ignoreUnsafe("age").ignoreUnsafe("name") - compare(Map("first" -> p1), Map("first" -> p2))(dm) shouldBe Identical(Map("first" -> p1)) + implicit val dm: Diff[Person] = + Derived[Diff[Person]].ignoreUnsafe("age").ignoreUnsafe("name") + compare(Map("first" -> p1), Map("first" -> p2)) shouldBe Identical(Map("first" -> p1)) } "maps by values" in { From c89a2c88fcd4532c4f8a7631fa8aa235d02dcc43 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Tue, 5 Jan 2021 21:12:42 +0100 Subject: [PATCH 019/112] Update refined to 0.9.20 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 1ae1b950..6a68d87c 100644 --- a/build.sbt +++ b/build.sbt @@ -128,7 +128,7 @@ lazy val refined = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-refined", libraryDependencies ++= Seq( - "eu.timepit" %% "refined" % "0.9.19", + "eu.timepit" %% "refined" % "0.9.20", "org.scalatest" %% "scalatest-flatspec" % scalatestVersion % Test, "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test ) From 2df66ddf6397d3df53de2847d4a1878f96c1aaed Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Mon, 11 Jan 2021 23:07:31 +0100 Subject: [PATCH 020/112] Update sbt-scalajs, scalajs-library, ... to 1.4.0 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 871e99b6..a3d1f2b7 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-common" % "1.9.15") addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % "1.9.15") addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-extra" % "1.9.15") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.3.1") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.4.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.14") From 5c7167eec23ae43212362014fbe2b166b766834d Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Tue, 12 Jan 2021 12:43:01 +0100 Subject: [PATCH 021/112] Update scalajs-compiler to 1.4.0 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 871e99b6..a3d1f2b7 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-common" % "1.9.15") addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % "1.9.15") addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-extra" % "1.9.15") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.3.1") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.4.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.14") From 759dc9ebdc07ac7afe1aa223160ad74bf4c3b17c Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Mon, 18 Jan 2021 21:44:31 +0100 Subject: [PATCH 022/112] Update specs2-core to 4.10.6 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 6a68d87c..93ecd3f4 100644 --- a/build.sbt +++ b/build.sbt @@ -5,7 +5,7 @@ val v2_12 = "2.12.8" val v2_13 = "2.13.1" val scalatestVersion = "3.2.3" -val specs2Version = "4.10.5" +val specs2Version = "4.10.6" val smlTaggingVersion = "2.2.1" lazy val commonSettings = commonSmlBuildSettings ++ ossPublishSettings ++ Seq( From 2215d4cae9ee5326738f5ebf5aa941de74efdab8 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Thu, 21 Jan 2021 06:45:03 +0100 Subject: [PATCH 023/112] Update utest to 0.7.6 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 93ecd3f4..cf9e9d20 100644 --- a/build.sbt +++ b/build.sbt @@ -81,7 +81,7 @@ lazy val utest = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-utests", libraryDependencies ++= Seq( - "com.lihaoyi" %% "utest" % "0.7.5" + "com.lihaoyi" %% "utest" % "0.7.6" ), testFrameworks += new TestFramework("utest.runner.Framework") ) From 349da7d9c668b57335ccb40ca39c07cb500852ce Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Sat, 23 Jan 2021 12:39:43 +0100 Subject: [PATCH 024/112] Update utest to 0.7.7 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index cf9e9d20..1063322e 100644 --- a/build.sbt +++ b/build.sbt @@ -81,7 +81,7 @@ lazy val utest = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-utests", libraryDependencies ++= Seq( - "com.lihaoyi" %% "utest" % "0.7.6" + "com.lihaoyi" %% "utest" % "0.7.7" ), testFrameworks += new TestFramework("utest.runner.Framework") ) From dbb6c18d64225cf5fb1c4632237c771a78a36078 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Sun, 24 Jan 2021 23:05:38 +0100 Subject: [PATCH 025/112] Fix NPE when comparing string to null #195 --- .../diffx/instances/DiffForString.scala | 48 +++++++++++-------- .../softwaremill/diffx/test/DiffTest.scala | 15 ++++++ 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForString.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForString.scala index 213e8d1d..3077ce8b 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForString.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForString.scala @@ -4,28 +4,34 @@ import com.softwaremill.diffx._ private[diffx] class DiffForString extends Diff[String] { override def apply(left: String, right: String, toIgnore: List[FieldPath]): DiffResult = { - val leftLines = left.split("\n").toList - val rightLines = right.split("\n").toList - val leftAsMap = leftLines.lift - val rightAsMap = rightLines.lift - val maxSize = Math.max(leftLines.length, rightLines.length) - val partialResults = (0 until maxSize).map { i => - (leftAsMap(i), rightAsMap(i)) match { - case (Some(lv), Some(rv)) => - if (lv == rv) { - Identical(lv) - } else { - DiffResultValue(lv, rv) - } - case (Some(lv), None) => DiffResultAdditional(lv) - case (None, Some(rv)) => DiffResultMissing(rv) - case (None, None) => throw new IllegalStateException("That should never happen") - } - }.toList - if (partialResults.forall(_.isIdentical)) { - Identical(left) + if ((left == null && right != null) || (left != null && right == null)) { + DiffResultValue(left, right) + } else if (right == null && left == null) { + Identical(null) } else { - DiffResultString(partialResults) + val leftLines = left.split("\n").toList + val rightLines = right.split("\n").toList + val leftAsMap = leftLines.lift + val rightAsMap = rightLines.lift + val maxSize = Math.max(leftLines.length, rightLines.length) + val partialResults = (0 until maxSize).map { i => + (leftAsMap(i), rightAsMap(i)) match { + case (Some(lv), Some(rv)) => + if (lv == rv) { + Identical(lv) + } else { + DiffResultValue(lv, rv) + } + case (Some(lv), None) => DiffResultAdditional(lv) + case (None, Some(rv)) => DiffResultMissing(rv) + case (None, None) => throw new IllegalStateException("That should never happen") + } + }.toList + if (partialResults.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultString(partialResults) + } } } } diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala index 9341937d..1b0df6fb 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala @@ -42,6 +42,21 @@ class DiffTest extends AnyFreeSpec with Matchers { ) } + "difference between null and value" in { + compare(p1.copy(name = null), p2) shouldBe DiffResultObject( + "Person", + Map( + "name" -> DiffResultValue(null, p2.name), + "age" -> DiffResultValue(p1.age, p2.age), + "in" -> Identical(instant) + ) + ) + } + + "two nulls should be equal" in { + compare(p1.copy(name = null), p1.copy(name = null)) shouldBe an[Identical[_]] + } + "ignoring given fields" in { implicit val d: Diff[Person] = Derived[Diff[Person]].ignoreUnsafe("name").ignoreUnsafe("age") val p3 = p2.copy(in = Instant.now()) From 5a13c515aaf1731d474b8d3eb6302be585f63c98 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Sun, 24 Jan 2021 23:10:25 +0100 Subject: [PATCH 026/112] Setting version to 0.4.1 --- README.md | 14 +++++++------- version.sbt | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e1b5e62f..bd95eca1 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ The library is published for Scala 2.12 and 2.13. Add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-core" % "0.4.0" +"com.softwaremill.diffx" %% "diffx-core" % "0.4.1" ``` ```scala @@ -128,7 +128,7 @@ If anyone has an idea how this could be improved, I am open for suggestions. To use with scalatest, add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-scalatest" % "0.4.0" % Test +"com.softwaremill.diffx" %% "diffx-scalatest" % "0.4.1" % Test ``` Then, extend the `com.softwaremill.diffx.scalatest.DiffMatcher` trait or `import com.softwaremill.diffx.scalatest.DiffMatcher._`. @@ -148,7 +148,7 @@ Giving you nice error messages: To use with specs2, add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-specs2" % "0.4.0" % Test +"com.softwaremill.diffx" %% "diffx-specs2" % "0.4.1" % Test ``` Then, extend the `com.softwaremill.diffx.specs2.DiffMatcher` trait or `import com.softwaremill.diffx.specs2.DiffMatcher._`. @@ -166,7 +166,7 @@ left must matchTo(right) To use with utest, add following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-utest" % "0.4.0" % Test +"com.softwaremill.diffx" %% "diffx-utest" % "0.4.1" % Test ``` Then, mixin `DiffxAssertions` trait or add `import com.softwaremill.diffx.utest.DiffxAssertions._` to your test code. @@ -242,17 +242,17 @@ with the compiler option `"-P:silencer:globalFilters=^magnolia: using fallback d - [com.softwaremill.common.tagging](https://github.com/softwaremill/scala-common) ```scala - "com.softwaremill.diffx" %% "diffx-tagging" % "0.4.0" + "com.softwaremill.diffx" %% "diffx-tagging" % "0.4.1" ``` `com.softwaremill.diffx.tagging.DiffTaggingSupport` - [eu.timepit.refined](https://github.com/fthomas/refined) ```scala - "com.softwaremill.diffx" %% "diffx-refined" % "0.4.0" + "com.softwaremill.diffx" %% "diffx-refined" % "0.4.1" ``` `com.softwaremill.diffx.refined.RefinedSupport` - [org.typelevel.cats](https://github.com/typelevel/cats) ```scala - "com.softwaremill.diffx" %% "diffx-cats" % "0.4.0" + "com.softwaremill.diffx" %% "diffx-cats" % "0.4.1" ``` `com.softwaremill.diffx.cats.DiffCatsInstances` diff --git a/version.sbt b/version.sbt index 5492cbd5..9e6291c5 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.4.1-SNAPSHOT" +version in ThisBuild := "0.4.1" From 0a9b72696ab206bf327a25687e20ac2af29a372d Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Sun, 24 Jan 2021 23:10:27 +0100 Subject: [PATCH 027/112] Setting version to 0.4.2-SNAPSHOT --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 9e6291c5..11c590d8 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.4.1" +version in ThisBuild := "0.4.2-SNAPSHOT" From 561cd0852711b711ec40640054ff73b5af95196e Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Wed, 10 Feb 2021 02:16:51 +0100 Subject: [PATCH 028/112] Update cats-core to 2.4.0 --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 1063322e..c8365671 100644 --- a/build.sbt +++ b/build.sbt @@ -112,7 +112,7 @@ lazy val cats = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-cats", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % "2.3.1", + "org.typelevel" %% "cats-core" % "2.4.0", "org.scalatest" %% "scalatest-freespec" % scalatestVersion % Test, "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test ) @@ -145,7 +145,7 @@ lazy val docs = project publishArtifact := false, name := "docs", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % "2.3.1", + "org.typelevel" %% "cats-core" % "2.4.0", "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion ) ) From fac42e4298a5ccf95c13eb1cfd776a8a7171c23d Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Wed, 10 Feb 2021 19:03:53 +0100 Subject: [PATCH 029/112] Update cats-core to 2.4.1 --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index c8365671..fcc793a4 100644 --- a/build.sbt +++ b/build.sbt @@ -112,7 +112,7 @@ lazy val cats = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-cats", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % "2.4.0", + "org.typelevel" %% "cats-core" % "2.4.1", "org.scalatest" %% "scalatest-freespec" % scalatestVersion % Test, "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test ) @@ -145,7 +145,7 @@ lazy val docs = project publishArtifact := false, name := "docs", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % "2.4.0", + "org.typelevel" %% "cats-core" % "2.4.1", "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion ) ) From a73e70a21db66285e0116605efe6ab9fe5fcc87d Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Fri, 12 Feb 2021 13:11:46 +0100 Subject: [PATCH 030/112] Update sbt-scalajs, scalajs-compiler, ... to 1.5.0 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index a3d1f2b7..fd510c15 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,6 @@ addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-common" % "1.9.15") addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % "1.9.15") addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-extra" % "1.9.15") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.4.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.14") From d4d0717591129e356135089875ae5054823f0688 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Wed, 17 Feb 2021 10:24:04 +0100 Subject: [PATCH 031/112] Update cats-core to 2.4.2 --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index fcc793a4..d1b6d8ea 100644 --- a/build.sbt +++ b/build.sbt @@ -112,7 +112,7 @@ lazy val cats = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-cats", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % "2.4.1", + "org.typelevel" %% "cats-core" % "2.4.2", "org.scalatest" %% "scalatest-freespec" % scalatestVersion % Test, "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test ) @@ -145,7 +145,7 @@ lazy val docs = project publishArtifact := false, name := "docs", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % "2.4.1", + "org.typelevel" %% "cats-core" % "2.4.2", "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion ) ) From eb3893c4412a78f8b85112c68e6ffaf20dd0e05d Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Thu, 18 Feb 2021 09:38:23 +0100 Subject: [PATCH 032/112] Update scalatest-flatspec, ... to 3.2.4 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index d1b6d8ea..e2df2efc 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ import sbtcrossproject.CrossPlugin.autoImport.{CrossType, crossProject} val v2_12 = "2.12.8" val v2_13 = "2.13.1" -val scalatestVersion = "3.2.3" +val scalatestVersion = "3.2.4" val specs2Version = "4.10.6" val smlTaggingVersion = "2.2.1" From 09159c7f8d698c9fbbf81520cbcc9fb8928e0b6e Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Thu, 18 Feb 2021 14:36:01 +0100 Subject: [PATCH 033/112] Update refined to 0.9.21 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index e2df2efc..caa43927 100644 --- a/build.sbt +++ b/build.sbt @@ -128,7 +128,7 @@ lazy val refined = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-refined", libraryDependencies ++= Seq( - "eu.timepit" %% "refined" % "0.9.20", + "eu.timepit" %% "refined" % "0.9.21", "org.scalatest" %% "scalatest-flatspec" % scalatestVersion % Test, "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test ) From 18f5ced4744ec85196dc20313674df618c74a909 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Sat, 27 Feb 2021 16:11:58 +0100 Subject: [PATCH 034/112] #203 Add option on the results to show only differences --- .../com/softwaremill/diffx/DiffResult.scala | 86 +++++++++++++++---- .../diffx/test/DiffResultTest.scala | 39 ++++++--- 2 files changed, 96 insertions(+), 29 deletions(-) diff --git a/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala b/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala index d956419a..b28fb827 100644 --- a/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala +++ b/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala @@ -1,12 +1,14 @@ package com.softwaremill.diffx + import DiffResult._ trait DiffResult extends Product with Serializable { def isIdentical: Boolean - def show(implicit c: ConsoleColorConfig): String = showIndented(indentLevel) + def show(renderIdentical: Boolean)(implicit c: ConsoleColorConfig): String = + showIndented(indentLevel, renderIdentical) - private[diffx] def showIndented(indent: Int)(implicit c: ConsoleColorConfig): String + private[diffx] def showIndented(indent: Int, renderIdentical: Boolean)(implicit c: ConsoleColorConfig): String } object DiffResult { @@ -14,34 +16,78 @@ object DiffResult { } case class DiffResultObject(name: String, fields: Map[String, DiffResult]) extends DiffResultDifferent { - override private[diffx] def showIndented(indent: Int)(implicit c: ConsoleColorConfig): String = { - val showFields = - fields.map(f => s"${i(indent)}${defaultColor(s"${f._1}:")} ${f._2.showIndented(indent + indentLevel)}") + override private[diffx] def showIndented(indent: Int, renderIdentical: Boolean)(implicit + c: ConsoleColorConfig + ): String = { + val showFields = fields + .filter { case (_, v) => + renderIdentical || !v.isIdentical + } + .map { case (field, value) => + renderField(indent, field) + renderValue(indent, renderIdentical, value) + } defaultColor(s"$name(") + s"\n${showFields.mkString(defaultColor(",") + "\n")}" + defaultColor(")") } + + private def renderValue(indent: Int, renderIdentical: Boolean, value: DiffResult)(implicit + c: ConsoleColorConfig + ) = { + s"${value.showIndented(indent + indentLevel, renderIdentical)}" + } + + private def renderField(indent: Int, field: String)(implicit + c: ConsoleColorConfig + ) = { + s"${i(indent)}${defaultColor(s"$field: ")}" + } } case class DiffResultMap(fields: Map[DiffResult, DiffResult]) extends DiffResultDifferent { - override private[diffx] def showIndented(indent: Int)(implicit c: ConsoleColorConfig): String = { - val showFields = - fields.map(f => - s"${i(indent)}${defaultColor(s"${f._1.showIndented(indent + indentLevel)}")}" + defaultColor(": ") + s"${f._2 - .showIndented(indent + indentLevel)}" - ) + override private[diffx] def showIndented(indent: Int, renderIdentical: Boolean)(implicit + c: ConsoleColorConfig + ): String = { + val showFields = fields + .filter { case (k, v) => + renderIdentical || !v.isIdentical || !k.isIdentical + } + .map { case (k, v) => + val key = renderKey(indent, renderIdentical, k) + val separator = defaultColor(": ") + val value = renderValue(indent, renderIdentical, v) + key + separator + value + } defaultColor("Map(") + s"\n${showFields.mkString(defaultColor(",") + "\n")}" + defaultColor(")") } + + private def renderValue(indent: Int, renderIdentical: Boolean, value: DiffResult)(implicit + c: ConsoleColorConfig + ) = { + value.showIndented(indent + indentLevel, renderIdentical) + } + + private def renderKey(indent: Int, renderIdentical: Boolean, key: DiffResult)(implicit + c: ConsoleColorConfig + ) = { + s"${i(indent)}${defaultColor(s"${key.showIndented(indent + indentLevel, renderIdentical)}")}" + } } case class DiffResultSet(diffs: List[DiffResult]) extends DiffResultDifferent { - override private[diffx] def showIndented(indent: Int)(implicit c: ConsoleColorConfig): String = { - val showFields = diffs.map(f => s"${i(indent)}${f.showIndented(indent + indentLevel)}") + override private[diffx] def showIndented(indent: Int, renderIdentical: Boolean)(implicit + c: ConsoleColorConfig + ): String = { + val showFields = diffs + .filter(df => renderIdentical || !df.isIdentical) + .map(f => s"${i(indent)}${f.showIndented(indent + indentLevel, renderIdentical)}") showFields.mkString(defaultColor("Set(\n"), ",\n", defaultColor(")")) } } case class DiffResultString(diffs: List[DiffResult]) extends DiffResultDifferent { - override private[diffx] def showIndented(indent: Int)(implicit c: ConsoleColorConfig): String = { - s"${diffs.map(_.showIndented(indent)).mkString("\n")}" + override private[diffx] def showIndented(indent: Int, renderIdentical: Boolean)(implicit + c: ConsoleColorConfig + ): String = { + s"${diffs.map(_.showIndented(indent, renderIdentical)).mkString("\n")}" } } @@ -52,23 +98,25 @@ trait DiffResultDifferent extends DiffResult { } case class DiffResultValue[T](left: T, right: T) extends DiffResultDifferent { - override def showIndented(indent: Int)(implicit c: ConsoleColorConfig): String = showChange(s"$left", s"$right") + override def showIndented(indent: Int, renderIdentical: Boolean)(implicit c: ConsoleColorConfig): String = + showChange(s"$left", s"$right") } case class Identical[T](value: T) extends DiffResult { override def isIdentical: Boolean = true - override def showIndented(indent: Int)(implicit c: ConsoleColorConfig): String = defaultColor(s"$value") + override def showIndented(indent: Int, renderIdentical: Boolean)(implicit c: ConsoleColorConfig): String = + defaultColor(s"$value") } case class DiffResultMissing[T](value: T) extends DiffResultDifferent { - override def showIndented(indent: Int)(implicit c: ConsoleColorConfig): String = { + override def showIndented(indent: Int, renderIdentical: Boolean)(implicit c: ConsoleColorConfig): String = { rightColor(s"$value") } } case class DiffResultAdditional[T](value: T) extends DiffResultDifferent { - override def showIndented(indent: Int)(implicit c: ConsoleColorConfig): String = { + override def showIndented(indent: Int, renderIdentical: Boolean)(implicit c: ConsoleColorConfig): String = { leftColor(s"$value") } } diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffResultTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffResultTest.scala index 1f4236d2..784c46ba 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffResultTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffResultTest.scala @@ -10,7 +10,7 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport "diff set output" - { "it should show a simple difference" in { - val output = DiffResultSet(List(Identical("a"), DiffResultValue("1", "2"))).show + val output = DiffResultSet(List(Identical("a"), DiffResultValue("1", "2"))).show(renderIdentical = true) output shouldBe s"""Set( | a, @@ -18,7 +18,8 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport } "it should show an indented difference" in { - val output = DiffResultSet(List(Identical("a"), DiffResultValue("1", "2"))).showIndented(5) + val output = + DiffResultSet(List(Identical("a"), DiffResultValue("1", "2"))).show(renderIdentical = true) output shouldBe s"""Set( | a, @@ -26,7 +27,7 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport } "it should show a nested list difference" in { - val output = DiffResultSet(List(Identical("a"), DiffResultSet(List(Identical("b"))))).show + val output = DiffResultSet(List(Identical("a"), DiffResultSet(List(Identical("b"))))).show(renderIdentical = true) output shouldBe s"""Set( | a, @@ -35,7 +36,7 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport } "it should show null" in { - val output = DiffResultSet(List(Identical(null), DiffResultValue(null, null))).show + val output = DiffResultSet(List(Identical(null), DiffResultValue(null, null))).show(renderIdentical = true) output shouldBe s"""Set( | null, @@ -46,7 +47,8 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport "diff map output" - { "it should show a simple diff" in { val output = - DiffResultMap(Map(Identical("a") -> DiffResultValue(1, 2), DiffResultMissing("b") -> DiffResultMissing(3))).show + DiffResultMap(Map(Identical("a") -> DiffResultValue(1, 2), DiffResultMissing("b") -> DiffResultMissing(3))) + .show(renderIdentical = true) output shouldBe s"""Map( | a: 1 -> 2, @@ -56,7 +58,7 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport "it should show an indented diff" in { val output = DiffResultMap(Map(Identical("a") -> DiffResultValue(1, 2), DiffResultMissing("b") -> DiffResultMissing(3))) - .showIndented(5) + .show(renderIdentical = true) output shouldBe s"""Map( | a: 1 -> 2, @@ -65,7 +67,8 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport "it should show a nested diff" in { val output = - DiffResultMap(Map(Identical("a") -> DiffResultMap(Map(Identical("b") -> DiffResultValue(1, 2))))).show + DiffResultMap(Map(Identical("a") -> DiffResultMap(Map(Identical("b") -> DiffResultValue(1, 2))))) + .show(renderIdentical = true) output shouldBe s"""Map( | a: Map( @@ -78,12 +81,28 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport val colorConfigWithPlusMinus: ConsoleColorConfig = ConsoleColorConfig(default = identity, arrow = identity, right = s => "+" + s, left = s => "-" + s) - val output = DiffResultObject("List", Map("0" -> DiffResultValue(1234, 123), "1" -> DiffResultMissing(1234))) - .showIndented(5)(colorConfigWithPlusMinus) + val output = DiffResultObject( + "List", + Map("0" -> DiffResultValue(1234, 123), "1" -> DiffResultMissing(1234), "2" -> Identical(1234)) + ) + .show(renderIdentical = true)(colorConfigWithPlusMinus) output shouldBe s"""List( | 0: -1234 -> +123, - | 1: +1234)""".stripMargin + | 1: +1234, + | 2: 1234)""".stripMargin + } + + "it should not render identical fields" in { + val output = DiffResultObject( + "List", + Map("0" -> DiffResultValue(1234, 123), "1" -> DiffResultMissing(1234), "2" -> Identical(1234)) + ) + .show(renderIdentical = false) + output shouldBe + s"""List( + | 0: 1234 -> 123, + | 1: 1234)""".stripMargin } } } From e43b99cbe91707f95588b40800b86554938b990e Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Sat, 27 Feb 2021 16:19:39 +0100 Subject: [PATCH 035/112] [#203] Add more tests and fix call-site --- .../com/softwaremill/diffx/DiffResult.scala | 2 +- .../diffx/test/DiffResultTest.scala | 38 +++++++++++++++---- .../diffx/scalatest/DiffMatcher.scala | 2 +- .../diffx/specs2/DiffMatcher.scala | 2 +- .../diffx/utest/DiffxAssertions.scala | 2 +- 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala b/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala index b28fb827..e83ad9d9 100644 --- a/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala +++ b/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala @@ -5,7 +5,7 @@ import DiffResult._ trait DiffResult extends Product with Serializable { def isIdentical: Boolean - def show(renderIdentical: Boolean)(implicit c: ConsoleColorConfig): String = + def show(renderIdentical: Boolean = true)(implicit c: ConsoleColorConfig): String = showIndented(indentLevel, renderIdentical) private[diffx] def showIndented(indent: Int, renderIdentical: Boolean)(implicit c: ConsoleColorConfig): String diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffResultTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffResultTest.scala index 784c46ba..5707c62c 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffResultTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffResultTest.scala @@ -10,7 +10,7 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport "diff set output" - { "it should show a simple difference" in { - val output = DiffResultSet(List(Identical("a"), DiffResultValue("1", "2"))).show(renderIdentical = true) + val output = DiffResultSet(List(Identical("a"), DiffResultValue("1", "2"))).show() output shouldBe s"""Set( | a, @@ -19,7 +19,7 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport "it should show an indented difference" in { val output = - DiffResultSet(List(Identical("a"), DiffResultValue("1", "2"))).show(renderIdentical = true) + DiffResultSet(List(Identical("a"), DiffResultValue("1", "2"))).show() output shouldBe s"""Set( | a, @@ -27,7 +27,7 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport } "it should show a nested list difference" in { - val output = DiffResultSet(List(Identical("a"), DiffResultSet(List(Identical("b"))))).show(renderIdentical = true) + val output = DiffResultSet(List(Identical("a"), DiffResultSet(List(Identical("b"))))).show() output shouldBe s"""Set( | a, @@ -36,19 +36,25 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport } "it should show null" in { - val output = DiffResultSet(List(Identical(null), DiffResultValue(null, null))).show(renderIdentical = true) + val output = DiffResultSet(List(Identical(null), DiffResultValue(null, null))).show() output shouldBe s"""Set( | null, | null -> null)""".stripMargin } + "it shouldn't render identical elements" in { + val output = DiffResultSet(List(Identical("a"), DiffResultValue("1", "2"))).show(renderIdentical = false) + output shouldBe + s"""Set( + | 1 -> 2)""".stripMargin + } } "diff map output" - { "it should show a simple diff" in { val output = DiffResultMap(Map(Identical("a") -> DiffResultValue(1, 2), DiffResultMissing("b") -> DiffResultMissing(3))) - .show(renderIdentical = true) + .show() output shouldBe s"""Map( | a: 1 -> 2, @@ -58,7 +64,7 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport "it should show an indented diff" in { val output = DiffResultMap(Map(Identical("a") -> DiffResultValue(1, 2), DiffResultMissing("b") -> DiffResultMissing(3))) - .show(renderIdentical = true) + .show() output shouldBe s"""Map( | a: 1 -> 2, @@ -68,12 +74,28 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport "it should show a nested diff" in { val output = DiffResultMap(Map(Identical("a") -> DiffResultMap(Map(Identical("b") -> DiffResultValue(1, 2))))) - .show(renderIdentical = true) + .show() output shouldBe s"""Map( | a: Map( | b: 1 -> 2))""".stripMargin } + + "shouldn't render identical entries" in { + val output = + DiffResultMap( + Map( + Identical("a") -> DiffResultValue(1, 2), + DiffResultValue("b", "c") -> Identical(3), + Identical("d") -> Identical(4) + ) + ) + .show(renderIdentical = false) + output shouldBe + s"""Map( + | a: 1 -> 2, + | b -> c: 3)""".stripMargin + } } "diff object output" - { @@ -85,7 +107,7 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport "List", Map("0" -> DiffResultValue(1234, 123), "1" -> DiffResultMissing(1234), "2" -> Identical(1234)) ) - .show(renderIdentical = true)(colorConfigWithPlusMinus) + .show()(colorConfigWithPlusMinus) output shouldBe s"""List( | 0: -1234 -> +123, diff --git a/scalatest/shared/src/main/scala/com/softwaremill/diffx/scalatest/DiffMatcher.scala b/scalatest/shared/src/main/scala/com/softwaremill/diffx/scalatest/DiffMatcher.scala index 1a457877..f2722e88 100644 --- a/scalatest/shared/src/main/scala/com/softwaremill/diffx/scalatest/DiffMatcher.scala +++ b/scalatest/shared/src/main/scala/com/softwaremill/diffx/scalatest/DiffMatcher.scala @@ -7,7 +7,7 @@ trait DiffMatcher { def matchTo[A: Diff](right: A)(implicit c: ConsoleColorConfig): Matcher[A] = { left => Diff[A].apply(left, right) match { case c: DiffResultDifferent => - val diff = c.show.split('\n').mkString(Console.RESET, s"${Console.RESET}\n${Console.RESET}", Console.RESET) + val diff = c.show().split('\n').mkString(Console.RESET, s"${Console.RESET}\n${Console.RESET}", Console.RESET) MatchResult(matches = false, s"Matching error:\n$diff", "") case _ => MatchResult(matches = true, "", "") } diff --git a/specs2/shared/src/main/scala/com/softwaremill/diffx/specs2/DiffMatcher.scala b/specs2/shared/src/main/scala/com/softwaremill/diffx/specs2/DiffMatcher.scala index 0d965c62..81a603cc 100644 --- a/specs2/shared/src/main/scala/com/softwaremill/diffx/specs2/DiffMatcher.scala +++ b/specs2/shared/src/main/scala/com/softwaremill/diffx/specs2/DiffMatcher.scala @@ -16,7 +16,7 @@ trait DiffMatcher { okMessage = "", koMessage = diff.apply(left.value, right) match { case c: DiffResultDifferent => - c.show + c.show() case _ => "" }, diff --git a/utest/shared/src/main/scala/com/softwaremill/diffx/utest/DiffxAssertions.scala b/utest/shared/src/main/scala/com/softwaremill/diffx/utest/DiffxAssertions.scala index 6def8186..7888caaa 100644 --- a/utest/shared/src/main/scala/com/softwaremill/diffx/utest/DiffxAssertions.scala +++ b/utest/shared/src/main/scala/com/softwaremill/diffx/utest/DiffxAssertions.scala @@ -8,7 +8,7 @@ trait DiffxAssertions { def assertEqual[T: Diff](t1: T, t2: T): Unit = { val result = Diff.compare(t1, t2) result match { - case different: DiffResultDifferent => throw AssertionError(different.show, Seq.empty, null) + case different: DiffResultDifferent => throw AssertionError(different.show(), Seq.empty, null) case _ => // do nothing } } From cc9fcc641e8bb798a81ff49a7bf846f3119dc59b Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Sat, 27 Feb 2021 16:42:07 +0100 Subject: [PATCH 036/112] Setting version to 0.4.2 --- README.md | 14 +++++++------- version.sbt | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index bd95eca1..0b5dfd65 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ The library is published for Scala 2.12 and 2.13. Add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-core" % "0.4.1" +"com.softwaremill.diffx" %% "diffx-core" % "0.4.2" ``` ```scala @@ -128,7 +128,7 @@ If anyone has an idea how this could be improved, I am open for suggestions. To use with scalatest, add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-scalatest" % "0.4.1" % Test +"com.softwaremill.diffx" %% "diffx-scalatest" % "0.4.2" % Test ``` Then, extend the `com.softwaremill.diffx.scalatest.DiffMatcher` trait or `import com.softwaremill.diffx.scalatest.DiffMatcher._`. @@ -148,7 +148,7 @@ Giving you nice error messages: To use with specs2, add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-specs2" % "0.4.1" % Test +"com.softwaremill.diffx" %% "diffx-specs2" % "0.4.2" % Test ``` Then, extend the `com.softwaremill.diffx.specs2.DiffMatcher` trait or `import com.softwaremill.diffx.specs2.DiffMatcher._`. @@ -166,7 +166,7 @@ left must matchTo(right) To use with utest, add following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-utest" % "0.4.1" % Test +"com.softwaremill.diffx" %% "diffx-utest" % "0.4.2" % Test ``` Then, mixin `DiffxAssertions` trait or add `import com.softwaremill.diffx.utest.DiffxAssertions._` to your test code. @@ -242,17 +242,17 @@ with the compiler option `"-P:silencer:globalFilters=^magnolia: using fallback d - [com.softwaremill.common.tagging](https://github.com/softwaremill/scala-common) ```scala - "com.softwaremill.diffx" %% "diffx-tagging" % "0.4.1" + "com.softwaremill.diffx" %% "diffx-tagging" % "0.4.2" ``` `com.softwaremill.diffx.tagging.DiffTaggingSupport` - [eu.timepit.refined](https://github.com/fthomas/refined) ```scala - "com.softwaremill.diffx" %% "diffx-refined" % "0.4.1" + "com.softwaremill.diffx" %% "diffx-refined" % "0.4.2" ``` `com.softwaremill.diffx.refined.RefinedSupport` - [org.typelevel.cats](https://github.com/typelevel/cats) ```scala - "com.softwaremill.diffx" %% "diffx-cats" % "0.4.1" + "com.softwaremill.diffx" %% "diffx-cats" % "0.4.2" ``` `com.softwaremill.diffx.cats.DiffCatsInstances` diff --git a/version.sbt b/version.sbt index 11c590d8..f78cd177 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.4.2-SNAPSHOT" +version in ThisBuild := "0.4.2" From 2db133f5765928c19d85fc15dfd0ce5de26b37eb Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Sat, 27 Feb 2021 16:42:08 +0100 Subject: [PATCH 037/112] Setting version to 0.4.3-SNAPSHOT --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index f78cd177..0731f9c3 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.4.2" +version in ThisBuild := "0.4.3-SNAPSHOT" From 9c94f9c734351708a0e7b5b741ec5339eac25705 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Mon, 1 Mar 2021 00:05:00 +0100 Subject: [PATCH 038/112] Fix artifact name for utest and pass ConsoleColorConfig through matcher --- build.sbt | 2 +- .../scala/com/softwaremill/diffx/utest/DiffxAssertions.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index caa43927..4105e973 100644 --- a/build.sbt +++ b/build.sbt @@ -79,7 +79,7 @@ lazy val utest = crossProject(JVMPlatform, JSPlatform) .in(file("utest")) .settings(commonSettings: _*) .settings( - name := "diffx-utests", + name := "diffx-utest", libraryDependencies ++= Seq( "com.lihaoyi" %% "utest" % "0.7.7" ), diff --git a/utest/shared/src/main/scala/com/softwaremill/diffx/utest/DiffxAssertions.scala b/utest/shared/src/main/scala/com/softwaremill/diffx/utest/DiffxAssertions.scala index 7888caaa..7387ecfd 100644 --- a/utest/shared/src/main/scala/com/softwaremill/diffx/utest/DiffxAssertions.scala +++ b/utest/shared/src/main/scala/com/softwaremill/diffx/utest/DiffxAssertions.scala @@ -1,11 +1,11 @@ package com.softwaremill.diffx.utest -import com.softwaremill.diffx.{Diff, DiffResultDifferent} +import com.softwaremill.diffx.{ConsoleColorConfig, Diff, DiffResultDifferent} import utest.AssertionError trait DiffxAssertions { - def assertEqual[T: Diff](t1: T, t2: T): Unit = { + def assertEqual[T: Diff](t1: T, t2: T)(implicit c: ConsoleColorConfig): Unit = { val result = Diff.compare(t1, t2) result match { case different: DiffResultDifferent => throw AssertionError(different.show(), Seq.empty, null) From eaad539140c5382bbb4c2b183935af2b4d02de3d Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Mon, 1 Mar 2021 00:07:58 +0100 Subject: [PATCH 039/112] Setting version to 0.4.3 --- README.md | 14 +++++++------- version.sbt | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0b5dfd65..07f45f67 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ The library is published for Scala 2.12 and 2.13. Add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-core" % "0.4.2" +"com.softwaremill.diffx" %% "diffx-core" % "0.4.3" ``` ```scala @@ -128,7 +128,7 @@ If anyone has an idea how this could be improved, I am open for suggestions. To use with scalatest, add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-scalatest" % "0.4.2" % Test +"com.softwaremill.diffx" %% "diffx-scalatest" % "0.4.3" % Test ``` Then, extend the `com.softwaremill.diffx.scalatest.DiffMatcher` trait or `import com.softwaremill.diffx.scalatest.DiffMatcher._`. @@ -148,7 +148,7 @@ Giving you nice error messages: To use with specs2, add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-specs2" % "0.4.2" % Test +"com.softwaremill.diffx" %% "diffx-specs2" % "0.4.3" % Test ``` Then, extend the `com.softwaremill.diffx.specs2.DiffMatcher` trait or `import com.softwaremill.diffx.specs2.DiffMatcher._`. @@ -166,7 +166,7 @@ left must matchTo(right) To use with utest, add following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-utest" % "0.4.2" % Test +"com.softwaremill.diffx" %% "diffx-utest" % "0.4.3" % Test ``` Then, mixin `DiffxAssertions` trait or add `import com.softwaremill.diffx.utest.DiffxAssertions._` to your test code. @@ -242,17 +242,17 @@ with the compiler option `"-P:silencer:globalFilters=^magnolia: using fallback d - [com.softwaremill.common.tagging](https://github.com/softwaremill/scala-common) ```scala - "com.softwaremill.diffx" %% "diffx-tagging" % "0.4.2" + "com.softwaremill.diffx" %% "diffx-tagging" % "0.4.3" ``` `com.softwaremill.diffx.tagging.DiffTaggingSupport` - [eu.timepit.refined](https://github.com/fthomas/refined) ```scala - "com.softwaremill.diffx" %% "diffx-refined" % "0.4.2" + "com.softwaremill.diffx" %% "diffx-refined" % "0.4.3" ``` `com.softwaremill.diffx.refined.RefinedSupport` - [org.typelevel.cats](https://github.com/typelevel/cats) ```scala - "com.softwaremill.diffx" %% "diffx-cats" % "0.4.2" + "com.softwaremill.diffx" %% "diffx-cats" % "0.4.3" ``` `com.softwaremill.diffx.cats.DiffCatsInstances` diff --git a/version.sbt b/version.sbt index 0731f9c3..272112db 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.4.3-SNAPSHOT" +version in ThisBuild := "0.4.3" From 9ff4502164c72d224f4e3432df4ce2e85c8f6ff9 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Mon, 1 Mar 2021 00:07:59 +0100 Subject: [PATCH 040/112] Setting version to 0.4.4-SNAPSHOT --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 272112db..b9ea942f 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.4.3" +version in ThisBuild := "0.4.4-SNAPSHOT" From dff6bc2fd7065c5b8ecd8779af8341068df365af Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Thu, 4 Mar 2021 17:43:47 +0100 Subject: [PATCH 041/112] Fix semi-auto derivation for coproducts Related to #218 --- .../scala/com/softwaremill/diffx/Diff.scala | 11 ++++--- .../com/softwaremill/diffx/IgnoreMacro.scala | 14 +++++++++ .../diffx/generic/auto/DiffDerivation.scala | 3 ++ .../diffx/test/DiffSemiautoTest.scala | 30 +++++++++++++++++-- 4 files changed, 51 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala/com/softwaremill/diffx/Diff.scala b/core/src/main/scala/com/softwaremill/diffx/Diff.scala index 661784b6..5fdcea1a 100644 --- a/core/src/main/scala/com/softwaremill/diffx/Diff.scala +++ b/core/src/main/scala/com/softwaremill/diffx/Diff.scala @@ -1,5 +1,5 @@ package com.softwaremill.diffx -import com.softwaremill.diffx.generic.DiffMagnoliaDerivation +import com.softwaremill.diffx.generic.{DiffMagnoliaDerivation, MagnoliaDerivedMacro} import com.softwaremill.diffx.instances._ import magnolia.Magnolia @@ -31,7 +31,7 @@ object Diff extends MiddlePriorityDiff { /** Create a Diff instance using [[Object#equals]] */ def useEquals[T]: Diff[T] = Diff.fallback[T] - def derived[T]: Diff[T] = macro Magnolia.gen[T] + def derived[T]: Derived[Diff[T]] = macro MagnoliaDerivedMacro.derivedGen[T] implicit val diffForString: Diff[String] = new DiffForString implicit val diffForRange: Diff[Range] = Diff.fallback[Range] @@ -59,8 +59,11 @@ trait LowPriorityDiff { // Implicit instance of Diff[T] created from implicit Derived[Diff[T]] implicit def derivedDiff[T](implicit dd: Derived[Diff[T]]): Diff[T] = dd.value - // Implicit conversion - implicit def unwrapDerivedDiff[T](dd: Derived[Diff[T]]): Diff[T] = dd.value + implicit class RichDerivedDiff[T](val dd: Derived[Diff[T]]) { + def contramap[R](f: R => T): Derived[Diff[R]] = Derived(dd.value.contramap(f)) + + def ignore[S <: T, U](path: S => U): Derived[Diff[S]] = macro IgnoreMacro.derivedIgnoreMacro[S, U] + } } case class Derived[T](value: T) diff --git a/core/src/main/scala/com/softwaremill/diffx/IgnoreMacro.scala b/core/src/main/scala/com/softwaremill/diffx/IgnoreMacro.scala index 98625139..d7152976 100644 --- a/core/src/main/scala/com/softwaremill/diffx/IgnoreMacro.scala +++ b/core/src/main/scala/com/softwaremill/diffx/IgnoreMacro.scala @@ -2,10 +2,24 @@ package com.softwaremill.diffx import scala.annotation.tailrec import scala.reflect.macros.blackbox +import scala.reflect.macros.whitebox object IgnoreMacro { private val ShapeInfo = "Path must have shape: _.field1.field2.each.field3.(...)" + def derivedIgnoreMacro[T: c.WeakTypeTag, U: c.WeakTypeTag]( + c: whitebox.Context + )(path: c.Expr[T => U]): c.Tree = applyDerivedIgnored[T, U](c)(ignoredFromPathMacro(c)(path)) + + private def applyDerivedIgnored[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)( + path: c.Expr[List[String]] + ): c.Tree = { + import c.universe._ + q"""{ + com.softwaremill.diffx.Derived(${c.prefix}.dd.value.ignoreUnsafe($path:_*)) + }""" + } + def ignoreMacro[T: c.WeakTypeTag, U: c.WeakTypeTag]( c: blackbox.Context )(path: c.Expr[T => U]): c.Tree = applyIgnored[T, U](c)(ignoredFromPathMacro(c)(path)) diff --git a/core/src/main/scala/com/softwaremill/diffx/generic/auto/DiffDerivation.scala b/core/src/main/scala/com/softwaremill/diffx/generic/auto/DiffDerivation.scala index 35808379..db32e629 100644 --- a/core/src/main/scala/com/softwaremill/diffx/generic/auto/DiffDerivation.scala +++ b/core/src/main/scala/com/softwaremill/diffx/generic/auto/DiffDerivation.scala @@ -6,4 +6,7 @@ package object auto extends DiffDerivation trait DiffDerivation extends DiffMagnoliaDerivation { implicit def diffForCaseClass[T]: Derived[Diff[T]] = macro MagnoliaDerivedMacro.derivedGen[T] + + // Implicit conversion + implicit def unwrapDerivedDiff[T](dd: Derived[Diff[T]]): Diff[T] = dd.value } diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala index 53499d43..7131bc89 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala @@ -1,5 +1,7 @@ package com.softwaremill.diffx.test +import com.softwaremill.diffx.test.ACoproduct.ProductA +import com.softwaremill.diffx.{Derived, Diff, Identical} import org.scalatest.freespec.AnyFreeSpec import org.scalatest.matchers.should.Matchers @@ -10,8 +12,8 @@ class DiffSemiautoTest extends AnyFreeSpec with Matchers { |final case class P1(f1: String) |final case class P2(f1: P1) | - |implicit val p1: Diff[P1] = Diff.derived[P1] - |implicit val p2: Diff[P2] = Diff.derived[P2] + |implicit val p1: Derived[Diff[P1]] = Diff.derived[P1] + |implicit val p2: Derived[Diff[P2]] = Diff.derived[P2] |""".stripMargin) } @@ -21,7 +23,7 @@ class DiffSemiautoTest extends AnyFreeSpec with Matchers { |final case class P1(f1: String) |final case class P2(f1: P1) | - |implicit val p2: Diff[P2] = Diff.derived[P2] + |implicit val p2: Derived[Diff[P2]] = Diff.derived[P2] |""".stripMargin) } @@ -35,4 +37,26 @@ class DiffSemiautoTest extends AnyFreeSpec with Matchers { |val p2: Diff[P2] = Diff[P2] |""".stripMargin) } + + "should work for coproducts" in { + implicit val dACoproduct: Derived[Diff[ACoproduct]] = Diff.derived[ACoproduct] + + Diff.compare[ACoproduct](ProductA("1"), ProductA("1")) shouldBe Identical( + ProductA("1") + ) + } + + "should allow ignoring on derived diffs" in { + implicit val dACoproduct: Derived[Diff[ProductA]] = Diff.derived[ProductA].ignore(_.id) + + Diff.compare[ProductA](ProductA("1"), ProductA("2")) shouldBe Identical( + ProductA("1") + ) + } +} + +sealed trait ACoproduct +object ACoproduct { + case class ProductA(id: String) extends ACoproduct + case class ProductB(id: String) extends ACoproduct } From 52692cc6b85e61214af7f793b0c478857af84ba8 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Thu, 4 Mar 2021 22:44:02 +0100 Subject: [PATCH 042/112] Add instance for Either --- .../scala/com/softwaremill/diffx/Diff.scala | 7 ++++++- .../diffx/instances/DiffForEither.scala | 17 +++++++++++++++++ .../com/softwaremill/diffx/test/DiffTest.scala | 16 ++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 core/src/main/scala/com/softwaremill/diffx/instances/DiffForEither.scala diff --git a/core/src/main/scala/com/softwaremill/diffx/Diff.scala b/core/src/main/scala/com/softwaremill/diffx/Diff.scala index 5fdcea1a..0f4851fe 100644 --- a/core/src/main/scala/com/softwaremill/diffx/Diff.scala +++ b/core/src/main/scala/com/softwaremill/diffx/Diff.scala @@ -34,7 +34,10 @@ object Diff extends MiddlePriorityDiff { def derived[T]: Derived[Diff[T]] = macro MagnoliaDerivedMacro.derivedGen[T] implicit val diffForString: Diff[String] = new DiffForString - implicit val diffForRange: Diff[Range] = Diff.fallback[Range] + implicit val diffForRange: Diff[Range] = Diff.useEquals[Range] + implicit val diffForChar: Diff[Char] = Diff.useEquals[Char] + implicit val diffForBoolean: Diff[Boolean] = Diff.useEquals[Boolean] + implicit def diffForNumeric[T: Numeric]: Diff[T] = new DiffForNumeric[T] implicit def diffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]](implicit ddot: Diff[Option[V]], @@ -46,6 +49,8 @@ object Diff extends MiddlePriorityDiff { ddt: Diff[T], matcher: ObjectMatcher[T] ): Diff[C[T]] = new DiffForSet[T, C](ddt, matcher) + implicit def diffForEither[L, R](implicit ld: Diff[L], rd: Diff[R]): Diff[Either[L, R]] = + new DiffForEither[L, R](ld, rd) } trait MiddlePriorityDiff extends DiffMagnoliaDerivation with LowPriorityDiff { diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForEither.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForEither.scala new file mode 100644 index 00000000..3c2f07ff --- /dev/null +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForEither.scala @@ -0,0 +1,17 @@ +package com.softwaremill.diffx.instances + +import com.softwaremill.diffx.{Diff, DiffResult, DiffResultValue, FieldPath} + +private[diffx] class DiffForEither[L, R](ld: Diff[L], rd: Diff[R]) extends Diff[Either[L, R]] { + override def apply( + left: Either[L, R], + right: Either[L, R], + toIgnore: List[FieldPath] + ): DiffResult = { + (left, right) match { + case (Left(v1), Left(v2)) => ld.apply(v1, v2, toIgnore) + case (Right(v1), Right(v2)) => rd.apply(v1, v2, toIgnore) + case (v1, v2) => DiffResultValue(v1, v2) + } + } +} diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala index 1b0df6fb..b392b710 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala @@ -528,6 +528,22 @@ class DiffTest extends AnyFreeSpec with Matchers { ) } } + "either" - { + "equal rights should be identical" in { + val e1: Either[String, String] = Right("a") + compare(e1, e1) shouldBe Identical("a") + + } + "equal lefts should be identical" in { + val e1: Either[String, String] = Left("a") + compare(e1, e1) shouldBe Identical("a") + } + "left and right should be different" in { + val e1: Either[String, String] = Left("a") + val e2: Either[String, String] = Right("a") + compare(e1, e2) shouldBe DiffResultValue(e1, e2) + } + } } case class Person(name: String, age: Int, in: Instant) From 3a8ebffa23c4bd6f1587944df5f654ea208283f7 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Fri, 5 Mar 2021 10:17:58 +0100 Subject: [PATCH 043/112] Add generated instances for Tuples to speed up compilation --- build.sbt | 4 +- .../diffx/TupleInstances.scala.template | 21 ++++++++++ .../scala/com/softwaremill/diffx/Diff.scala | 2 +- .../softwaremill/diffx/test/DiffTest.scala | 42 +++++++++++++++++++ project/plugins.sbt | 1 + 5 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 core/src/main/boilerplate/com/softwaremill/diffx/TupleInstances.scala.template diff --git a/build.sbt b/build.sbt index 4105e973..b030f6d3 100644 --- a/build.sbt +++ b/build.sbt @@ -39,8 +39,10 @@ lazy val core = crossProject(JVMPlatform, JSPlatform) case Some((2, n)) if n >= 13 => sourceDir / "scala-2.13+" case _ => sourceDir / "scala-2.13-" } - } + }, + boilerplateSource in Compile := baseDirectory.value.getParentFile / "src" / "main" / "boilerplate" ) + .enablePlugins(spray.boilerplate.BoilerplatePlugin) lazy val coreJVM = core.jvm lazy val coreJS = core.js diff --git a/core/src/main/boilerplate/com/softwaremill/diffx/TupleInstances.scala.template b/core/src/main/boilerplate/com/softwaremill/diffx/TupleInstances.scala.template new file mode 100644 index 00000000..f67cf79e --- /dev/null +++ b/core/src/main/boilerplate/com/softwaremill/diffx/TupleInstances.scala.template @@ -0,0 +1,21 @@ +package com.softwaremill.diffx + +trait TupleInstances { + + [2..#def dTuple1[[#T1#]](implicit [#d1: Diff[T1]#]): Diff[Tuple1[[#T1#]]] = new Diff[Tuple1[[#T1#]]] { + override def apply( + left: ([#T1#]), + right: ([#T1#]), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List([#"_1" -> d1.apply(left._1, right._1)#]).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple1", results) + } + } + } + # + ] +} \ No newline at end of file diff --git a/core/src/main/scala/com/softwaremill/diffx/Diff.scala b/core/src/main/scala/com/softwaremill/diffx/Diff.scala index 0f4851fe..9114d2d5 100644 --- a/core/src/main/scala/com/softwaremill/diffx/Diff.scala +++ b/core/src/main/scala/com/softwaremill/diffx/Diff.scala @@ -21,7 +21,7 @@ trait Diff[-T] { outer => } } -object Diff extends MiddlePriorityDiff { +object Diff extends MiddlePriorityDiff with TupleInstances { def apply[T: Diff]: Diff[T] = implicitly[Diff[T]] def identical[T]: Diff[T] = (left: T, _: T, _: List[FieldPath]) => Identical(left) diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala index b392b710..8e2eb908 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala @@ -544,6 +544,48 @@ class DiffTest extends AnyFreeSpec with Matchers { compare(e1, e2) shouldBe DiffResultValue(e1, e2) } } + "tuples" - { + "tuple2" - { + "equal tuples should be identical" in { + compare((1, 2), (1, 2)) shouldBe Identical((1, 2)) + } + "different first element should make them different" in { + compare((1, 2), (3, 2)) shouldBe DiffResultObject( + "Tuple2", + Map("_1" -> DiffResultValue(1, 3), "_2" -> Identical(2)) + ) + } + "different second element should make them different" in { + compare((1, 3), (1, 2)) shouldBe DiffResultObject( + "Tuple2", + Map("_1" -> Identical(1), "_2" -> DiffResultValue(3, 2)) + ) + } + } + "tuple3" - { + "equal tuples should be identical" in { + compare((1, 2, 3), (1, 2, 3)) shouldBe Identical((1, 2, 3)) + } + "different first element should make them different" in { + compare((1, 2, 3), (4, 2, 3)) shouldBe DiffResultObject( + "Tuple3", + Map("_1" -> DiffResultValue(1, 4), "_2" -> Identical(2), "_3" -> Identical(3)) + ) + } + "different second element should make them different" in { + compare((1, 2, 3), (1, 4, 3)) shouldBe DiffResultObject( + "Tuple3", + Map("_1" -> Identical(1), "_2" -> DiffResultValue(2, 4), "_3" -> Identical(3)) + ) + } + "different third element should make them different" in { + compare((1, 2, 3), (1, 2, 4)) shouldBe DiffResultObject( + "Tuple3", + Map("_1" -> Identical(1), "_2" -> Identical(2), "_3" -> DiffResultValue(3, 4)) + ) + } + } + } } case class Person(name: String, age: Int, in: Instant) diff --git a/project/plugins.sbt b/project/plugins.sbt index fd510c15..f7a9b5d4 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -4,3 +4,4 @@ addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-extra" % "1 addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.14") +addSbtPlugin("io.spray" % "sbt-boilerplate" % "0.6.1") From 26fb027047d80f2a15b4fde219f20dabd2f40391 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Fri, 5 Mar 2021 10:34:00 +0100 Subject: [PATCH 044/112] Update readme to recommend use of semi-auto derivation --- docs-sources/README.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/docs-sources/README.md b/docs-sources/README.md index 594ea56e..50d051f9 100644 --- a/docs-sources/README.md +++ b/docs-sources/README.md @@ -71,25 +71,28 @@ Will result in: ## Derivation Diffx supports auto and semi-auto derivation. + +For semi-auto derivation you don't need any additional import, just define your instances using: +```scala mdoc:compile-only +case class Product(name: String) +case class Basket(products: List[Product]) + +val productDiff = Diff.derived[Product] +val basketDiff = Diff.derived[Basket] +``` + To use auto derivation add following import `import com.softwaremill.diffx.generic.auto._` -or +or extend trait `com.softwaremill.diffx.generic.DiffDerivation` +**Auto derivation will have a huge impact on compilation times**, because of that it is recommended to use `semi-auto` derivation. -For semi-auto derivation you don't need any additional import, just define your instances using: -```scala mdoc:compile-only -case class Product(name: String) -case class Basket(products: List[Product]) - -val productDiff = Diff.derived[Product] -val basketDiff = Diff.derived[Basket] -``` ## Colors From 30f71016185e4248596f6452f3dbae18fa825474 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Fri, 5 Mar 2021 10:44:02 +0100 Subject: [PATCH 045/112] Mark tuple instances as implicit --- .../com/softwaremill/diffx/TupleInstances.scala.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/boilerplate/com/softwaremill/diffx/TupleInstances.scala.template b/core/src/main/boilerplate/com/softwaremill/diffx/TupleInstances.scala.template index f67cf79e..090dd612 100644 --- a/core/src/main/boilerplate/com/softwaremill/diffx/TupleInstances.scala.template +++ b/core/src/main/boilerplate/com/softwaremill/diffx/TupleInstances.scala.template @@ -2,7 +2,7 @@ package com.softwaremill.diffx trait TupleInstances { - [2..#def dTuple1[[#T1#]](implicit [#d1: Diff[T1]#]): Diff[Tuple1[[#T1#]]] = new Diff[Tuple1[[#T1#]]] { + [2..#implicit def dTuple1[[#T1#]](implicit [#d1: Diff[T1]#]): Diff[Tuple1[[#T1#]]] = new Diff[Tuple1[[#T1#]]] { override def apply( left: ([#T1#]), right: ([#T1#]), From 75dde301d61832da2b3ce52419b55259db0d4f95 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Fri, 5 Mar 2021 13:29:43 +0100 Subject: [PATCH 046/112] Setting version to 0.4.4 --- README.md | 35 +++++++++++++++++++---------------- version.sbt | 2 +- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 07f45f67..bec8b16e 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ The library is published for Scala 2.12 and 2.13. Add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-core" % "0.4.3" +"com.softwaremill.diffx" %% "diffx-core" % "0.4.4" ``` ```scala @@ -91,25 +91,28 @@ Will result in: ## Derivation Diffx supports auto and semi-auto derivation. + +For semi-auto derivation you don't need any additional import, just define your instances using: +```scala +case class Product(name: String) +case class Basket(products: List[Product]) + +val productDiff = Diff.derived[Product] +val basketDiff = Diff.derived[Basket] +``` + To use auto derivation add following import `import com.softwaremill.diffx.generic.auto._` -or +or extend trait `com.softwaremill.diffx.generic.DiffDerivation` +**Auto derivation will have a huge impact on compilation times**, because of that it is recommended to use `semi-auto` derivation. -For semi-auto derivation you don't need any additional import, just define your instances using: -```scala -case class Product(name: String) -case class Basket(products: List[Product]) - -val productDiff = Diff.derived[Product] -val basketDiff = Diff.derived[Basket] -``` ## Colors @@ -128,7 +131,7 @@ If anyone has an idea how this could be improved, I am open for suggestions. To use with scalatest, add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-scalatest" % "0.4.3" % Test +"com.softwaremill.diffx" %% "diffx-scalatest" % "0.4.4" % Test ``` Then, extend the `com.softwaremill.diffx.scalatest.DiffMatcher` trait or `import com.softwaremill.diffx.scalatest.DiffMatcher._`. @@ -148,7 +151,7 @@ Giving you nice error messages: To use with specs2, add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-specs2" % "0.4.3" % Test +"com.softwaremill.diffx" %% "diffx-specs2" % "0.4.4" % Test ``` Then, extend the `com.softwaremill.diffx.specs2.DiffMatcher` trait or `import com.softwaremill.diffx.specs2.DiffMatcher._`. @@ -166,7 +169,7 @@ left must matchTo(right) To use with utest, add following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-utest" % "0.4.3" % Test +"com.softwaremill.diffx" %% "diffx-utest" % "0.4.4" % Test ``` Then, mixin `DiffxAssertions` trait or add `import com.softwaremill.diffx.utest.DiffxAssertions._` to your test code. @@ -242,17 +245,17 @@ with the compiler option `"-P:silencer:globalFilters=^magnolia: using fallback d - [com.softwaremill.common.tagging](https://github.com/softwaremill/scala-common) ```scala - "com.softwaremill.diffx" %% "diffx-tagging" % "0.4.3" + "com.softwaremill.diffx" %% "diffx-tagging" % "0.4.4" ``` `com.softwaremill.diffx.tagging.DiffTaggingSupport` - [eu.timepit.refined](https://github.com/fthomas/refined) ```scala - "com.softwaremill.diffx" %% "diffx-refined" % "0.4.3" + "com.softwaremill.diffx" %% "diffx-refined" % "0.4.4" ``` `com.softwaremill.diffx.refined.RefinedSupport` - [org.typelevel.cats](https://github.com/typelevel/cats) ```scala - "com.softwaremill.diffx" %% "diffx-cats" % "0.4.3" + "com.softwaremill.diffx" %% "diffx-cats" % "0.4.4" ``` `com.softwaremill.diffx.cats.DiffCatsInstances` diff --git a/version.sbt b/version.sbt index b9ea942f..d3a524bb 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.4.4-SNAPSHOT" +version in ThisBuild := "0.4.4" From a40fb14745f8ae7ffd34d1786fb0470662257a56 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Fri, 5 Mar 2021 13:29:45 +0100 Subject: [PATCH 047/112] Setting version to 0.4.5-SNAPSHOT --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index d3a524bb..6f55c7d8 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.4.4" +version in ThisBuild := "0.4.5-SNAPSHOT" From 253788550119078d27266fe698e866f4525a7c7c Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Mon, 8 Mar 2021 09:00:09 +0100 Subject: [PATCH 048/112] Update scalatest-flatspec, ... to 3.2.6 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index b030f6d3..54475a15 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ import sbtcrossproject.CrossPlugin.autoImport.{CrossType, crossProject} val v2_12 = "2.12.8" val v2_13 = "2.13.1" -val scalatestVersion = "3.2.4" +val scalatestVersion = "3.2.6" val specs2Version = "4.10.6" val smlTaggingVersion = "2.2.1" From 908ed5334e218e2d78b86f210ad7b224734cb9aa Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Tue, 9 Mar 2021 21:28:47 +0100 Subject: [PATCH 049/112] Fix example in readme --- README.md | 4 ++-- docs-sources/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bec8b16e..3d5af5f7 100644 --- a/README.md +++ b/README.md @@ -97,8 +97,8 @@ For semi-auto derivation you don't need any additional import, just define your case class Product(name: String) case class Basket(products: List[Product]) -val productDiff = Diff.derived[Product] -val basketDiff = Diff.derived[Basket] +implicit val productDiff = Diff.derived[Product] +implicit val basketDiff = Diff.derived[Basket] ``` To use auto derivation add following import diff --git a/docs-sources/README.md b/docs-sources/README.md index 50d051f9..00e723ef 100644 --- a/docs-sources/README.md +++ b/docs-sources/README.md @@ -77,8 +77,8 @@ For semi-auto derivation you don't need any additional import, just define your case class Product(name: String) case class Basket(products: List[Product]) -val productDiff = Diff.derived[Product] -val basketDiff = Diff.derived[Basket] +implicit val productDiff = Diff.derived[Product] +implicit val basketDiff = Diff.derived[Basket] ``` To use auto derivation add following import From 443439f20aeb64e05df59cea4568489e585e8081 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Tue, 9 Mar 2021 21:30:23 +0100 Subject: [PATCH 050/112] Switch to blackbox macro --- core/src/main/scala/com/softwaremill/diffx/IgnoreMacro.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/scala/com/softwaremill/diffx/IgnoreMacro.scala b/core/src/main/scala/com/softwaremill/diffx/IgnoreMacro.scala index d7152976..c32cb44e 100644 --- a/core/src/main/scala/com/softwaremill/diffx/IgnoreMacro.scala +++ b/core/src/main/scala/com/softwaremill/diffx/IgnoreMacro.scala @@ -2,13 +2,12 @@ package com.softwaremill.diffx import scala.annotation.tailrec import scala.reflect.macros.blackbox -import scala.reflect.macros.whitebox object IgnoreMacro { private val ShapeInfo = "Path must have shape: _.field1.field2.each.field3.(...)" def derivedIgnoreMacro[T: c.WeakTypeTag, U: c.WeakTypeTag]( - c: whitebox.Context + c: blackbox.Context )(path: c.Expr[T => U]): c.Tree = applyDerivedIgnored[T, U](c)(ignoredFromPathMacro(c)(path)) private def applyDerivedIgnored[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)( From 54aa7310102f2a4a1f5ff7249e71d5b4dc5306be Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Fri, 19 Mar 2021 16:53:43 +0100 Subject: [PATCH 051/112] Update tagging to 2.3.0 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 54475a15..e5550381 100644 --- a/build.sbt +++ b/build.sbt @@ -6,7 +6,7 @@ val v2_13 = "2.13.1" val scalatestVersion = "3.2.6" val specs2Version = "4.10.6" -val smlTaggingVersion = "2.2.1" +val smlTaggingVersion = "2.3.0" lazy val commonSettings = commonSmlBuildSettings ++ ossPublishSettings ++ Seq( organization := "com.softwaremill.diffx", From 529ac697917ff2e03a48f395da9f818a6f08585a Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Mon, 29 Mar 2021 20:48:59 +0200 Subject: [PATCH 052/112] Update mdoc, sbt-mdoc to 2.2.19 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index f7a9b5d4..d2f0e186 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,5 +3,5 @@ addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-extra" % "1.9.15") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.14") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.19") addSbtPlugin("io.spray" % "sbt-boilerplate" % "0.6.1") From 249a54698ac9d0330700afee05da8661e070c689 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Tue, 30 Mar 2021 06:11:44 +0200 Subject: [PATCH 053/112] Update refined to 0.9.22 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index e5550381..b277fbf7 100644 --- a/build.sbt +++ b/build.sbt @@ -130,7 +130,7 @@ lazy val refined = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-refined", libraryDependencies ++= Seq( - "eu.timepit" %% "refined" % "0.9.21", + "eu.timepit" %% "refined" % "0.9.22", "org.scalatest" %% "scalatest-flatspec" % scalatestVersion % Test, "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test ) From c7ac9fd9cb3f6998c37d9126b73f3478cecc2e0f Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Tue, 30 Mar 2021 06:11:49 +0200 Subject: [PATCH 054/112] Update cats-core to 2.5.0 --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index e5550381..18503b77 100644 --- a/build.sbt +++ b/build.sbt @@ -114,7 +114,7 @@ lazy val cats = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-cats", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % "2.4.2", + "org.typelevel" %% "cats-core" % "2.5.0", "org.scalatest" %% "scalatest-freespec" % scalatestVersion % Test, "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test ) @@ -147,7 +147,7 @@ lazy val docs = project publishArtifact := false, name := "docs", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % "2.4.2", + "org.typelevel" %% "cats-core" % "2.5.0", "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion ) ) From 96b51c30b13be6cc170d50bd0924f201ef2a397a Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Wed, 31 Mar 2021 16:53:29 +0200 Subject: [PATCH 055/112] Add checks against nulls --- .../com/softwaremill/diffx/DiffxSupport.scala | 10 +++++ .../generic/DiffMagnoliaDerivation.scala | 44 ++++++++++--------- .../diffx/instances/DiffForIterable.scala | 39 ++++++++-------- .../diffx/instances/DiffForMap.scala | 2 +- .../diffx/instances/DiffForSet.scala | 27 ++++++------ .../diffx/instances/DiffForString.scala | 9 +--- .../softwaremill/diffx/test/DiffTest.scala | 26 +++++++++++ 7 files changed, 97 insertions(+), 60 deletions(-) diff --git a/core/src/main/scala/com/softwaremill/diffx/DiffxSupport.scala b/core/src/main/scala/com/softwaremill/diffx/DiffxSupport.scala index 5bfa23aa..67b8276e 100644 --- a/core/src/main/scala/com/softwaremill/diffx/DiffxSupport.scala +++ b/core/src/main/scala/com/softwaremill/diffx/DiffxSupport.scala @@ -7,6 +7,16 @@ trait DiffxSupport extends DiffxEitherSupport with DiffxConsoleSupport with Diff type FieldPath = List[String] def compare[T](left: T, right: T)(implicit d: Diff[T]): DiffResult = d.apply(left, right) + + private[diffx] def nullGuard[T](left: T, right: T)(compareNotNull: (T, T) => DiffResult): DiffResult = { + if ((left == null && right != null) || (left != null && right == null)) { + DiffResultValue(left, right) + } else if (left == null && right == null) { + Identical(null) + } else { + compareNotNull(left, right) + } + } } object DiffxSupport { diff --git a/core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala b/core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala index 386d5b68..ff9f2d4b 100644 --- a/core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala +++ b/core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala @@ -1,6 +1,6 @@ package com.softwaremill.diffx.generic -import com.softwaremill.diffx.{Diff, DiffResultObject, DiffResultValue, FieldPath, Identical} +import com.softwaremill.diffx.{Diff, DiffResultObject, DiffResultValue, FieldPath, Identical, nullGuard} import magnolia._ import scala.collection.immutable.ListMap @@ -9,31 +9,35 @@ trait DiffMagnoliaDerivation extends LowPriority { type Typeclass[T] = Diff[T] def combine[T](ctx: ReadOnlyCaseClass[Typeclass, T]): Diff[T] = { (left: T, right: T, toIgnore: List[FieldPath]) => - val map = ListMap(ctx.parameters.map { p => - val lType = p.dereference(left) - val pType = p.dereference(right) - if (toIgnore.contains(List(p.label))) { - p.label -> Identical(lType) + nullGuard(left, right) { (left, right) => + val map = ListMap(ctx.parameters.map { p => + val lType = p.dereference(left) + val pType = p.dereference(right) + if (toIgnore.contains(List(p.label))) { + p.label -> Identical(lType) + } else { + val nestedIgnore = + if (toIgnore.exists(_.headOption.exists(h => h == p.label))) toIgnore.map(_.drop(1)) else Nil + p.label -> p.typeclass(lType, pType, nestedIgnore) + } + }: _*) + if (map.values.forall(p => p.isIdentical)) { + Identical(left) } else { - val nestedIgnore = - if (toIgnore.exists(_.headOption.exists(h => h == p.label))) toIgnore.map(_.drop(1)) else Nil - p.label -> p.typeclass(lType, pType, nestedIgnore) + DiffResultObject(ctx.typeName.short, map) } - }: _*) - if (map.values.forall(p => p.isIdentical)) { - Identical(left) - } else { - DiffResultObject(ctx.typeName.short, map) } } def dispatch[T](ctx: SealedTrait[Typeclass, T]): Diff[T] = { (left: T, right: T, toIgnore: List[FieldPath]) => - val lType = ctx.dispatch(left)(a => a) - val rType = ctx.dispatch(right)(a => a) - if (lType == rType) { - lType.typeclass(lType.cast(left), lType.cast(right), toIgnore) - } else { - DiffResultValue(lType.typeName.full, rType.typeName.full) + nullGuard(left, right) { (left, right) => + val lType = ctx.dispatch(left)(a => a) + val rType = ctx.dispatch(right)(a => a) + if (lType == rType) { + lType.typeclass(lType.cast(left), lType.cast(right), toIgnore) + } else { + DiffResultValue(lType.typeName.full, rType.typeName.full) + } } } } diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala index 100551f5..b1a40d15 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala @@ -4,25 +4,26 @@ import com.softwaremill.diffx._ import scala.collection.immutable.ListMap private[diffx] class DiffForIterable[T, C[W] <: Iterable[W]](dot: Diff[Option[T]]) extends Diff[C[T]] { - override def apply(left: C[T], right: C[T], toIgnore: List[FieldPath]): DiffResult = { - val indexes = Range(0, Math.max(left.size, right.size)) - val leftAsMap = left.toList.lift - val rightAsMap = right.toList.lift - val differences = ListMap(indexes.map { index => - index.toString -> (dot.apply(leftAsMap(index), rightAsMap(index), toIgnore) match { - case DiffResultValue(Some(v), None) => DiffResultAdditional(v) - case DiffResultValue(None, Some(v)) => DiffResultMissing(v) - case d => d - }) - }: _*) + override def apply(left: C[T], right: C[T], toIgnore: List[FieldPath]): DiffResult = nullGuard(left, right) { + (left, right) => + val indexes = Range(0, Math.max(left.size, right.size)) + val leftAsMap = left.toList.lift + val rightAsMap = right.toList.lift + val differences = ListMap(indexes.map { index => + index.toString -> (dot.apply(leftAsMap(index), rightAsMap(index), toIgnore) match { + case DiffResultValue(Some(v), None) => DiffResultAdditional(v) + case DiffResultValue(None, Some(v)) => DiffResultMissing(v) + case d => d + }) + }: _*) - if (differences.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject( - "List", - differences - ) - } + if (differences.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject( + "List", + differences + ) + } } } diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala index 08057e4b..538950a8 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala @@ -12,7 +12,7 @@ private[diffx] class DiffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]] left: C[K, V], right: C[K, V], toIgnore: List[FieldPath] - ): DiffResult = { + ): DiffResult = nullGuard(left, right) { (left, right) => val MatchingResults(unMatchedLeftKeys, unMatchedRightKeys, matchedKeys) = matching[K](left.keySet, right.keySet, matcher, diffKey, toIgnore) val leftDiffs = this.leftDiffs(left, unMatchedLeftKeys, unMatchedRightKeys) diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala index 0dbadf1a..64eac2b8 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala @@ -5,19 +5,20 @@ import com.softwaremill.diffx._ private[diffx] class DiffForSet[T, C[W] <: scala.collection.Set[W]](dt: Diff[T], matcher: ObjectMatcher[T]) extends Diff[C[T]] { - override def apply(left: C[T], right: C[T], toIgnore: List[FieldPath]): DiffResult = { - val MatchingResults(unMatchedLeftInstances, unMatchedRightInstances, matchedInstances) = - matching[T](left.toSet, right.toSet, matcher, dt, toIgnore) - val leftDiffs = unMatchedLeftInstances - .diff(unMatchedRightInstances) - .map(DiffResultAdditional(_)) - .toList - val rightDiffs = unMatchedRightInstances - .diff(unMatchedLeftInstances) - .map(DiffResultMissing(_)) - .toList - val matchedDiffs = matchedInstances.map { case (l, r) => dt(l, r, toIgnore) }.toList - diffResultSet(left, leftDiffs, rightDiffs, matchedDiffs) + override def apply(left: C[T], right: C[T], toIgnore: List[FieldPath]): DiffResult = nullGuard(left, right) { + (left, right) => + val MatchingResults(unMatchedLeftInstances, unMatchedRightInstances, matchedInstances) = + matching[T](left.toSet, right.toSet, matcher, dt, toIgnore) + val leftDiffs = unMatchedLeftInstances + .diff(unMatchedRightInstances) + .map(DiffResultAdditional(_)) + .toList + val rightDiffs = unMatchedRightInstances + .diff(unMatchedLeftInstances) + .map(DiffResultMissing(_)) + .toList + val matchedDiffs = matchedInstances.map { case (l, r) => dt(l, r, toIgnore) }.toList + diffResultSet(left, leftDiffs, rightDiffs, matchedDiffs) } private def diffResultSet( diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForString.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForString.scala index 3077ce8b..0eccd8b2 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForString.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForString.scala @@ -3,12 +3,8 @@ package com.softwaremill.diffx.instances import com.softwaremill.diffx._ private[diffx] class DiffForString extends Diff[String] { - override def apply(left: String, right: String, toIgnore: List[FieldPath]): DiffResult = { - if ((left == null && right != null) || (left != null && right == null)) { - DiffResultValue(left, right) - } else if (right == null && left == null) { - Identical(null) - } else { + override def apply(left: String, right: String, toIgnore: List[FieldPath]): DiffResult = nullGuard(left, right) { + (left, right) => val leftLines = left.split("\n").toList val rightLines = right.split("\n").toList val leftAsMap = leftLines.lift @@ -32,6 +28,5 @@ private[diffx] class DiffForString extends Diff[String] { } else { DiffResultString(partialResults) } - } } } diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala index 8e2eb908..08d7ff5e 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala @@ -26,11 +26,21 @@ class DiffTest extends AnyFreeSpec with Matchers { } } + "options" - { + "nullable" in { + compare(Option.empty[Int], null: Option[Int]) shouldBe DiffResultValue(Option.empty[Int], null) + } + } + "products" - { "identity" in { compare(p1, p1) shouldBe Identical(p1) } + "nullable" in { + compare(p1, null) shouldBe DiffResultValue(p1, null) + } + "diff" in { compare(p1, p2) shouldBe DiffResultObject( "Person", @@ -191,6 +201,10 @@ class DiffTest extends AnyFreeSpec with Matchers { compare(left, left) shouldBe an[Identical[_]] } + "nullable" in { + compare[TsDirection](TsDirection.Outgoing, null: TsDirection) shouldBe DiffResultValue(TsDirection.Outgoing, null) + } + "diff" in { compare(left, right) shouldBe DiffResultObject( "Foo", @@ -224,6 +238,10 @@ class DiffTest extends AnyFreeSpec with Matchers { compare(List("a"), List("a")) shouldBe Identical(List("a")) } + "nullable" in { + compare(List.empty[Int], null: List[Int]) shouldBe DiffResultValue(List.empty, null) + } + "diff" in { compare(List("a"), List("B")) shouldBe DiffResultObject( "List", @@ -286,6 +304,10 @@ class DiffTest extends AnyFreeSpec with Matchers { compare(Set(1), Set(1)) shouldBe an[Identical[_]] } + "nullable" in { + compare(Set.empty[Int], null: Set[Int]) shouldBe DiffResultValue(Set.empty[Int], null) + } + "diff" in { val diffResult = compare(Set(1, 2, 3, 4, 5), Set(1, 2, 3, 4)).asInstanceOf[DiffResultSet] diffResult.diffs should contain theSameElementsAs List( @@ -384,6 +406,10 @@ class DiffTest extends AnyFreeSpec with Matchers { compare(m1, m1) shouldBe an[Identical[_]] } + "nullable" in { + compare(Map.empty[Int, Int], null: Map[Int, Int]) shouldBe DiffResultValue(Map.empty[Int, Int], null) + } + "simple diff" in { val m1 = Map("a" -> 1) val m2 = Map("a" -> 2) From ba0d0be19f43dfbb535f3c905a25f628bfa28eed Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Wed, 31 Mar 2021 17:01:23 +0200 Subject: [PATCH 056/112] Downgrade mdoc to 2.2.14 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index d2f0e186..f7a9b5d4 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,5 +3,5 @@ addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-extra" % "1.9.15") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.19") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.14") addSbtPlugin("io.spray" % "sbt-boilerplate" % "0.6.1") From 8381beb2af9e62074974d7d347f55d15283cbe79 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Wed, 31 Mar 2021 17:03:20 +0200 Subject: [PATCH 057/112] Setting version to 0.4.5 --- README.md | 14 +++++++------- version.sbt | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 3d5af5f7..bcd0c531 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ The library is published for Scala 2.12 and 2.13. Add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-core" % "0.4.4" +"com.softwaremill.diffx" %% "diffx-core" % "0.4.5" ``` ```scala @@ -131,7 +131,7 @@ If anyone has an idea how this could be improved, I am open for suggestions. To use with scalatest, add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-scalatest" % "0.4.4" % Test +"com.softwaremill.diffx" %% "diffx-scalatest" % "0.4.5" % Test ``` Then, extend the `com.softwaremill.diffx.scalatest.DiffMatcher` trait or `import com.softwaremill.diffx.scalatest.DiffMatcher._`. @@ -151,7 +151,7 @@ Giving you nice error messages: To use with specs2, add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-specs2" % "0.4.4" % Test +"com.softwaremill.diffx" %% "diffx-specs2" % "0.4.5" % Test ``` Then, extend the `com.softwaremill.diffx.specs2.DiffMatcher` trait or `import com.softwaremill.diffx.specs2.DiffMatcher._`. @@ -169,7 +169,7 @@ left must matchTo(right) To use with utest, add following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-utest" % "0.4.4" % Test +"com.softwaremill.diffx" %% "diffx-utest" % "0.4.5" % Test ``` Then, mixin `DiffxAssertions` trait or add `import com.softwaremill.diffx.utest.DiffxAssertions._` to your test code. @@ -245,17 +245,17 @@ with the compiler option `"-P:silencer:globalFilters=^magnolia: using fallback d - [com.softwaremill.common.tagging](https://github.com/softwaremill/scala-common) ```scala - "com.softwaremill.diffx" %% "diffx-tagging" % "0.4.4" + "com.softwaremill.diffx" %% "diffx-tagging" % "0.4.5" ``` `com.softwaremill.diffx.tagging.DiffTaggingSupport` - [eu.timepit.refined](https://github.com/fthomas/refined) ```scala - "com.softwaremill.diffx" %% "diffx-refined" % "0.4.4" + "com.softwaremill.diffx" %% "diffx-refined" % "0.4.5" ``` `com.softwaremill.diffx.refined.RefinedSupport` - [org.typelevel.cats](https://github.com/typelevel/cats) ```scala - "com.softwaremill.diffx" %% "diffx-cats" % "0.4.4" + "com.softwaremill.diffx" %% "diffx-cats" % "0.4.5" ``` `com.softwaremill.diffx.cats.DiffCatsInstances` diff --git a/version.sbt b/version.sbt index 6f55c7d8..b47474be 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.4.5-SNAPSHOT" +version in ThisBuild := "0.4.5" From 4624d5498c7c6ef819e46d204ae42e5948b89c89 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Wed, 31 Mar 2021 17:03:21 +0200 Subject: [PATCH 058/112] Setting version to 0.4.6-SNAPSHOT --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index b47474be..e614bb44 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.4.5" +version in ThisBuild := "0.4.6-SNAPSHOT" From 4a8d06d4d750353e784bf254fe271012db7bb0e4 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Wed, 31 Mar 2021 19:24:15 +0200 Subject: [PATCH 059/112] Update sbt-scalajs, scalajs-compiler, ... to 1.5.1 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index f7a9b5d4..ca2548c5 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,7 @@ addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-common" % "1.9.15") addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % "1.9.15") addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-extra" % "1.9.15") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.1") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.14") addSbtPlugin("io.spray" % "sbt-boilerplate" % "0.6.1") From ba748232d83ce57a543a8c2849bb3d03164f61a5 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Thu, 1 Apr 2021 09:20:45 +0200 Subject: [PATCH 060/112] Update scalatest-flatspec, ... to 3.2.7 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index c9c2ca9c..10d17a57 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ import sbtcrossproject.CrossPlugin.autoImport.{CrossType, crossProject} val v2_12 = "2.12.8" val v2_13 = "2.13.1" -val scalatestVersion = "3.2.6" +val scalatestVersion = "3.2.7" val specs2Version = "4.10.6" val smlTaggingVersion = "2.3.0" From 4a3e74d0ce4a879270f3446dd8eb0f79828d4f51 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Thu, 1 Apr 2021 16:05:44 +0200 Subject: [PATCH 061/112] Update utest to 0.7.8 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 10d17a57..10dcd9a3 100644 --- a/build.sbt +++ b/build.sbt @@ -83,7 +83,7 @@ lazy val utest = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-utest", libraryDependencies ++= Seq( - "com.lihaoyi" %% "utest" % "0.7.7" + "com.lihaoyi" %% "utest" % "0.7.8" ), testFrameworks += new TestFramework("utest.runner.Framework") ) From 651ece22b9a7d975ab80b0822dac84794419f6b2 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Sun, 4 Apr 2021 12:42:16 +0200 Subject: [PATCH 062/112] Update refined to 0.9.23 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 10dcd9a3..cafebffd 100644 --- a/build.sbt +++ b/build.sbt @@ -130,7 +130,7 @@ lazy val refined = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-refined", libraryDependencies ++= Seq( - "eu.timepit" %% "refined" % "0.9.22", + "eu.timepit" %% "refined" % "0.9.23", "org.scalatest" %% "scalatest-flatspec" % scalatestVersion % Test, "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test ) From 9add1d430ba209a96e41c0621d88d6154f0e44a5 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Tue, 20 Apr 2021 03:40:26 +0200 Subject: [PATCH 063/112] Update cats-core to 2.6.0 --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index cafebffd..fd58a3e4 100644 --- a/build.sbt +++ b/build.sbt @@ -114,7 +114,7 @@ lazy val cats = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-cats", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % "2.5.0", + "org.typelevel" %% "cats-core" % "2.6.0", "org.scalatest" %% "scalatest-freespec" % scalatestVersion % Test, "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test ) @@ -147,7 +147,7 @@ lazy val docs = project publishArtifact := false, name := "docs", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % "2.5.0", + "org.typelevel" %% "cats-core" % "2.6.0", "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion ) ) From b4e5eb94e309c607bc6521fe16aba1626bd6d9ca Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Tue, 20 Apr 2021 05:34:28 +0200 Subject: [PATCH 064/112] Update utest to 0.7.9 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index fd58a3e4..12887627 100644 --- a/build.sbt +++ b/build.sbt @@ -83,7 +83,7 @@ lazy val utest = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-utest", libraryDependencies ++= Seq( - "com.lihaoyi" %% "utest" % "0.7.8" + "com.lihaoyi" %% "utest" % "0.7.9" ), testFrameworks += new TestFramework("utest.runner.Framework") ) From e43856955bf6078c8e943237189ca6178a961e01 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Tue, 20 Apr 2021 14:31:33 +0200 Subject: [PATCH 065/112] Update mdoc, sbt-mdoc to 2.2.20 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index ca2548c5..087ac24f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,5 +3,5 @@ addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-extra" % "1.9.15") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.1") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.14") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.20") addSbtPlugin("io.spray" % "sbt-boilerplate" % "0.6.1") From c517d59951337aef28a0236e0d3250f5acfca1c7 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Tue, 20 Apr 2021 23:06:52 +0200 Subject: [PATCH 066/112] Update scalatest-flatspec, ... to 3.2.8 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 12887627..2f9796b7 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ import sbtcrossproject.CrossPlugin.autoImport.{CrossType, crossProject} val v2_12 = "2.12.8" val v2_13 = "2.13.1" -val scalatestVersion = "3.2.7" +val scalatestVersion = "3.2.8" val specs2Version = "4.10.6" val smlTaggingVersion = "2.3.0" From a0e556561c7de49b22868be4e7565198f3b0a8f0 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Wed, 21 Apr 2021 16:28:40 +0200 Subject: [PATCH 067/112] Update refined to 0.9.24 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 2f9796b7..08396d29 100644 --- a/build.sbt +++ b/build.sbt @@ -130,7 +130,7 @@ lazy val refined = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-refined", libraryDependencies ++= Seq( - "eu.timepit" %% "refined" % "0.9.23", + "eu.timepit" %% "refined" % "0.9.24", "org.scalatest" %% "scalatest-flatspec" % scalatestVersion % Test, "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test ) From 77bd6e7f04385c66d4609555b351f9d8686a550e Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Fri, 23 Apr 2021 21:45:37 +0200 Subject: [PATCH 068/112] Update specs2-core to 4.11.0 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 08396d29..15d93d96 100644 --- a/build.sbt +++ b/build.sbt @@ -5,7 +5,7 @@ val v2_12 = "2.12.8" val v2_13 = "2.13.1" val scalatestVersion = "3.2.8" -val specs2Version = "4.10.6" +val specs2Version = "4.11.0" val smlTaggingVersion = "2.3.0" lazy val commonSettings = commonSmlBuildSettings ++ ossPublishSettings ++ Seq( From a270ffd713adf15af5f6b6981697e8180fdeb316 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Thu, 13 May 2021 23:13:30 +0200 Subject: [PATCH 069/112] Update cats-core to 2.6.1 --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 15d93d96..75ad40b0 100644 --- a/build.sbt +++ b/build.sbt @@ -114,7 +114,7 @@ lazy val cats = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-cats", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % "2.6.0", + "org.typelevel" %% "cats-core" % "2.6.1", "org.scalatest" %% "scalatest-freespec" % scalatestVersion % Test, "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test ) @@ -147,7 +147,7 @@ lazy val docs = project publishArtifact := false, name := "docs", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % "2.6.0", + "org.typelevel" %% "cats-core" % "2.6.1", "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion ) ) From a695fec45275a48c591fec7d492c59a320590668 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Fri, 14 May 2021 03:15:59 +0200 Subject: [PATCH 070/112] Update utest to 0.7.10 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 75ad40b0..28dfa2b2 100644 --- a/build.sbt +++ b/build.sbt @@ -83,7 +83,7 @@ lazy val utest = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-utest", libraryDependencies ++= Seq( - "com.lihaoyi" %% "utest" % "0.7.9" + "com.lihaoyi" %% "utest" % "0.7.10" ), testFrameworks += new TestFramework("utest.runner.Framework") ) From 45d22107d36e633876ff02aae71a68fd50d15c1a Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Fri, 14 May 2021 11:22:32 +0200 Subject: [PATCH 071/112] Update refined to 0.9.25 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 28dfa2b2..1be31ed0 100644 --- a/build.sbt +++ b/build.sbt @@ -130,7 +130,7 @@ lazy val refined = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-refined", libraryDependencies ++= Seq( - "eu.timepit" %% "refined" % "0.9.24", + "eu.timepit" %% "refined" % "0.9.25", "org.scalatest" %% "scalatest-flatspec" % scalatestVersion % Test, "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test ) From 2a10c4f6317e7536527806735536ddb8e9cec0d8 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Fri, 14 May 2021 13:17:03 +0200 Subject: [PATCH 072/112] Update mdoc, sbt-mdoc to 2.2.21 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 087ac24f..45ce9ff2 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,5 +3,5 @@ addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-extra" % "1.9.15") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.1") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.20") +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.21") addSbtPlugin("io.spray" % "sbt-boilerplate" % "0.6.1") From b536759ec18b49ba5b3e9e9a8bc3d68f3103dcd5 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Fri, 14 May 2021 20:29:49 +0200 Subject: [PATCH 073/112] Update scalatest-flatspec, ... to 3.2.9 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 1be31ed0..75a4f1f8 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ import sbtcrossproject.CrossPlugin.autoImport.{CrossType, crossProject} val v2_12 = "2.12.8" val v2_13 = "2.13.1" -val scalatestVersion = "3.2.8" +val scalatestVersion = "3.2.9" val specs2Version = "4.11.0" val smlTaggingVersion = "2.3.0" From 2f00173780bd407bd04e9f072340323762523402 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Sat, 22 May 2021 12:57:25 +0200 Subject: [PATCH 074/112] Update specs2-core to 4.12.0 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 75a4f1f8..e3599826 100644 --- a/build.sbt +++ b/build.sbt @@ -5,7 +5,7 @@ val v2_12 = "2.12.8" val v2_13 = "2.13.1" val scalatestVersion = "3.2.9" -val specs2Version = "4.11.0" +val specs2Version = "4.12.0" val smlTaggingVersion = "2.3.0" lazy val commonSettings = commonSmlBuildSettings ++ ossPublishSettings ++ Seq( From 027d1782ad1a99d60022db1d6a37abca8d78f546 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Wed, 26 May 2021 22:00:25 +0200 Subject: [PATCH 075/112] Update refined to 0.9.26 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index e3599826..da7676e6 100644 --- a/build.sbt +++ b/build.sbt @@ -130,7 +130,7 @@ lazy val refined = crossProject(JVMPlatform, JSPlatform) .settings( name := "diffx-refined", libraryDependencies ++= Seq( - "eu.timepit" %% "refined" % "0.9.25", + "eu.timepit" %% "refined" % "0.9.26", "org.scalatest" %% "scalatest-flatspec" % scalatestVersion % Test, "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test ) From 68719bc1318da58bb5c1f2d45ab1956d7e03b960 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Fri, 28 May 2021 14:48:11 +0200 Subject: [PATCH 076/112] Update scala-library, scala-reflect to 2.12.14 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 720e8605..9644ebdf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,4 +27,4 @@ deploy: skip_cleanup: true on: all_branches: true - condition: $TRAVIS_SCALA_VERSION = "2.12.8" && $TRAVIS_TAG =~ ^v[0-9]+\.[0-9]+(\.[0-9]+)? + condition: $TRAVIS_SCALA_VERSION = "2.12.14" && $TRAVIS_TAG =~ ^v[0-9]+\.[0-9]+(\.[0-9]+)? From d5758a46efc808a96baecf91d2e61649df1d0f22 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Tue, 8 Jun 2021 19:22:23 +0200 Subject: [PATCH 077/112] Update sbt-scalajs, scalajs-compiler, ... to 1.6.0 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 45ce9ff2..4ee6bba6 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,7 @@ addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-common" % "1.9.15") addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % "1.9.15") addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-extra" % "1.9.15") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.1") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.6.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.21") addSbtPlugin("io.spray" % "sbt-boilerplate" % "0.6.1") From 939b56452a777b07367d51b2db51c1317985d5b3 Mon Sep 17 00:00:00 2001 From: Kasper Kondzielski Date: Fri, 11 Jun 2021 10:35:56 +0200 Subject: [PATCH 078/112] Gha (#251) * Commit tuple instances * Migrate to new sbt-softwaremill * Skip js projects * Fix linking errors --- .github/workflows/main.yaml | 42 + .travis.yml | 30 - build.sbt | 254 ++--- .../diffx/cats/DiffCatsInstances.scala | 0 .../com/softwaremill/diffx/cats/package.scala | 0 .../diffx/cats/DiffxCatsTest.scala | 0 .../softwaremill/diffx/TupleInstances.scala | 898 ++++++++++++++++++ project/plugins.sbt | 14 +- .../diffx/refined/RefinedSupport.scala | 0 .../softwaremill/diffx/refined/package.scala | 0 .../diffx/refined/RefinedSupportTest.scala | 0 .../diffx/scalatest/DiffMatcher.scala | 0 .../diffx/scalatest/DiffMatcherTest.scala | 0 scripts/decrypt_files_if_not_pr.sh | 6 - secrets.tar.enc | Bin 20496 -> 0 bytes .../diffx/specs2/DiffMatcher.scala | 0 .../diffx/specs2/DiffMatcherTest.scala | 0 .../diffx/tagging/DiffTaggingSupport.scala | 0 .../softwaremill/diffx/tagging/package.scala | 0 .../tagging/test/DiffTaggingSupportTest.scala | 0 .../diffx/utest/DiffxAssertions.scala | 0 .../diffx/utest/UtestAssertTest.scala | 0 version.sbt | 1 - 23 files changed, 1089 insertions(+), 156 deletions(-) create mode 100644 .github/workflows/main.yaml delete mode 100644 .travis.yml rename cats/{shared => }/src/main/scala/com/softwaremill/diffx/cats/DiffCatsInstances.scala (100%) rename cats/{shared => }/src/main/scala/com/softwaremill/diffx/cats/package.scala (100%) rename cats/{shared => }/src/test/scala/com/softwaremill/diffx/cats/DiffxCatsTest.scala (100%) create mode 100644 core/src/main/scala/com/softwaremill/diffx/TupleInstances.scala rename refined/{shared => }/src/main/scala/com/softwaremill/diffx/refined/RefinedSupport.scala (100%) rename refined/{shared => }/src/main/scala/com/softwaremill/diffx/refined/package.scala (100%) rename refined/{shared => }/src/test/scala/com/softwaremill/diffx/refined/RefinedSupportTest.scala (100%) rename scalatest/{shared => }/src/main/scala/com/softwaremill/diffx/scalatest/DiffMatcher.scala (100%) rename scalatest/{shared => }/src/test/scala/com/softwaremill/diffx/scalatest/DiffMatcherTest.scala (100%) delete mode 100644 scripts/decrypt_files_if_not_pr.sh delete mode 100644 secrets.tar.enc rename specs2/{shared => }/src/main/scala/com/softwaremill/diffx/specs2/DiffMatcher.scala (100%) rename specs2/{shared => }/src/test/scala/com/softwaremill/diffx/specs2/DiffMatcherTest.scala (100%) rename tagging/{shared => }/src/main/scala/com/softwaremill/diffx/tagging/DiffTaggingSupport.scala (100%) rename tagging/{shared => }/src/main/scala/com/softwaremill/diffx/tagging/package.scala (100%) rename tagging/{shared => }/src/test/scala/com/softwaremill/diffx/tagging/test/DiffTaggingSupportTest.scala (100%) rename utest/{shared => }/src/main/scala/com/softwaremill/diffx/utest/DiffxAssertions.scala (100%) rename utest/{shared => }/src/test/scala/com/softwaremill/diffx/utest/UtestAssertTest.scala (100%) delete mode 100644 version.sbt diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 00000000..f2f534cd --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,42 @@ +name: CI + +on: + push: + branches: [ "**" ] + tags: [ v* ] + pull_request: + branches: [ "**" ] + +jobs: + build: + runs-on: ubuntu-20.04 + env: + JAVA_OPTS: -Xmx4G + steps: + - uses: actions/checkout@v2 + - uses: coursier/cache-action@v5 + - uses: olafurpg/setup-scala@v10 + with: + java-version: adopt@1.11 + - name: Run tests with sbt + run: sbt test + + publish: + name: Publish release + if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v')) + needs: [build] + runs-on: ubuntu-20.04 + env: + JAVA_OPTS: -Xmx4G + steps: + - uses: actions/checkout@v2.3.4 + with: + fetch-depth: 0 + - uses: coursier/cache-action@v5 + - uses: olafurpg/setup-scala@v10 + - run: sbt ci-release + env: + PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} + PGP_SECRET: ${{ secrets.PGP_SECRET }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 720e8605..00000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -language: scala -scala: - - 2.12.8 - - 2.13.1 -before_install: - - bash scripts/decrypt_files_if_not_pr.sh -before_cache: - - du -h -d 1 $HOME/.ivy2/ - - du -h -d 2 $HOME/.sbt/ - - du -h -d 4 $HOME/.coursier/ - - find $HOME/.sbt -name "*.lock" -type f -delete - - find $HOME/.ivy2/cache -name "ivydata-*.properties" -type f -delete - - find $HOME/.coursier/cache -name "*.lock" -type f -delete -cache: - directories: - - "$HOME/.sbt/1.0" - - "$HOME/.sbt/boot/scala*" - - "$HOME/.sbt/cache" - - "$HOME/.sbt/launchers" - - "$HOME/.ivy2/cache" - - "$HOME/.coursier" -script: - - sbt ++$TRAVIS_SCALA_VERSION test -deploy: - - provider: script - script: sbt publishRelease - skip_cleanup: true - on: - all_branches: true - condition: $TRAVIS_SCALA_VERSION = "2.12.8" && $TRAVIS_TAG =~ ^v[0-9]+\.[0-9]+(\.[0-9]+)? diff --git a/build.sbt b/build.sbt index da7676e6..72268a4c 100644 --- a/build.sbt +++ b/build.sbt @@ -1,147 +1,180 @@ -import com.softwaremill.PublishTravis.publishTravisSettings -import sbtcrossproject.CrossPlugin.autoImport.{CrossType, crossProject} +import com.softwaremill.UpdateVersionInDocs +import sbt.Def +import sbt.Reference.display -val v2_12 = "2.12.8" -val v2_13 = "2.13.1" +val scala212 = "2.12.13" +val scala213 = "2.13.6" + +val scalaIdeaVersion = scala212 // the version for which to import sources into intellij val scalatestVersion = "3.2.9" val specs2Version = "4.12.0" val smlTaggingVersion = "2.3.0" -lazy val commonSettings = commonSmlBuildSettings ++ ossPublishSettings ++ Seq( +lazy val commonSettings: Seq[Def.Setting[_]] = commonSmlBuildSettings ++ ossPublishSettings ++ Seq( organization := "com.softwaremill.diffx", - scalaVersion := v2_12, - scalafmtOnCompile := true, - crossScalaVersions := Seq(v2_12, v2_13), - libraryDependencies ++= Seq(compilerPlugin("com.softwaremill.neme" %% "neme-plugin" % "0.0.5")), scmInfo := Some(ScmInfo(url("https://github.com/softwaremill/diffx"), "git@github.com:softwaremill/diffx.git")), - // sbt-release - releaseCrossBuild := true + ideSkipProject := (scalaVersion.value != scalaIdeaVersion) || thisProjectRef.value.project.contains("JS"), + updateDocs := Def.taskDyn { + val files1 = + UpdateVersionInDocs(sLog.value, organization.value, version.value, List(file("docs-sources") / "README.md")) + Def.task { + (docs.jvm(scala213) / mdoc).toTask("").value + files1 ++ Seq(file("generated-docs"), file("README.md")) + } + }.value ) -lazy val core = crossProject(JVMPlatform, JSPlatform) - .crossType(CrossType.Pure) - .in(file("core")) - .settings(commonSettings: _*) +val compileDocs: TaskKey[Unit] = taskKey[Unit]("Compiles docs module throwing away its output") +compileDocs := { + (docs.jvm(scala213) / mdoc).toTask(" --out target/sttp-docs").value +} + +val versionSpecificScalaSources = { + Compile / unmanagedSourceDirectories := { + val current = (Compile / unmanagedSourceDirectories).value + val sv = (Compile / scalaVersion).value + val baseDirectory = (Compile / scalaSource).value + val suffixes = CrossVersion.partialVersion(sv) match { + case Some((2, 13)) => List("2", "2.13+") + case Some((2, _)) => List("2", "2.13-") + case Some((3, _)) => List("3") + case _ => Nil + } + val versionSpecificSources = suffixes.map(s => new File(baseDirectory.getAbsolutePath + "-" + s)) + versionSpecificSources ++ current + } +} + +lazy val core = (projectMatrix in file("core")) + .settings(commonSettings) .settings( name := "diffx-core", libraryDependencies ++= Seq( - "com.propensive" %% "magnolia" % "0.17.0", + "com.propensive" %%% "magnolia" % "0.17.0", "org.scala-lang" % "scala-reflect" % scalaVersion.value, - "org.scalatest" %% "scalatest-flatspec" % scalatestVersion % Test, - "org.scalatest" %% "scalatest-freespec" % scalatestVersion % Test, - "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test + "org.scalatest" %%% "scalatest-flatspec" % scalatestVersion % Test, + "org.scalatest" %%% "scalatest-freespec" % scalatestVersion % Test, + "org.scalatest" %%% "scalatest-shouldmatchers" % scalatestVersion % Test, + "io.github.cquiroz" %%% "scala-java-time" % "2.2.2" % Test ), - unmanagedSourceDirectories in Compile += { - // sourceDirectory returns a platform-scoped directory, e.g. /.jvm - val sourceDir = (baseDirectory in Compile).value / ".." / "src" / "main" - CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, n)) if n >= 13 => sourceDir / "scala-2.13+" - case _ => sourceDir / "scala-2.13-" - } - }, - boilerplateSource in Compile := baseDirectory.value.getParentFile / "src" / "main" / "boilerplate" - ) - .enablePlugins(spray.boilerplate.BoilerplatePlugin) - -lazy val coreJVM = core.jvm -lazy val coreJS = core.js - -lazy val scalatest = crossProject(JVMPlatform, JSPlatform) - .in(file("scalatest")) - .settings(commonSettings: _*) + versionSpecificScalaSources + ) + .jvmPlatform( + scalaVersions = List(scala212, scala213) + ) + .jsPlatform( + scalaVersions = List(scala212, scala213) + ) + +lazy val scalatest = (projectMatrix in file("scalatest")) + .settings(commonSettings) .settings( name := "diffx-scalatest", libraryDependencies ++= Seq( - "org.scalatest" %% "scalatest-matchers-core" % scalatestVersion, - "org.scalatest" %% "scalatest-flatspec" % scalatestVersion % Test, - "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test + "org.scalatest" %%% "scalatest-matchers-core" % scalatestVersion, + "org.scalatest" %%% "scalatest-flatspec" % scalatestVersion % Test, + "org.scalatest" %%% "scalatest-shouldmatchers" % scalatestVersion % Test ) ) .dependsOn(core) + .jvmPlatform( + scalaVersions = List(scala212, scala213) + ) + .jsPlatform( + scalaVersions = List(scala212, scala213) + ) -lazy val scalatestJVM = scalatest.jvm -lazy val scalatestJS = scalatest.js - -lazy val specs2 = crossProject(JVMPlatform, JSPlatform) - .in(file("specs2")) - .settings(commonSettings: _*) +lazy val specs2 = (projectMatrix in file("specs2")) + .settings(commonSettings) .settings( name := "diffx-specs2", libraryDependencies ++= Seq( - "org.specs2" %% "specs2-core" % specs2Version + "org.specs2" %%% "specs2-core" % specs2Version ) ) .dependsOn(core) + .jvmPlatform( + scalaVersions = List(scala212, scala213) + ) + .jsPlatform( + scalaVersions = List(scala212, scala213) + ) -lazy val specs2JVM = specs2.jvm -lazy val specs2JS = specs2.js - -lazy val utest = crossProject(JVMPlatform, JSPlatform) - .in(file("utest")) - .settings(commonSettings: _*) +lazy val utest = (projectMatrix in file("utest")) + .settings(commonSettings) .settings( name := "diffx-utest", libraryDependencies ++= Seq( - "com.lihaoyi" %% "utest" % "0.7.10" + "com.lihaoyi" %%% "utest" % "0.7.10" ), testFrameworks += new TestFramework("utest.runner.Framework") ) .dependsOn(core) + .jvmPlatform( + scalaVersions = List(scala212, scala213) + ) + .jsPlatform( + scalaVersions = List(scala212, scala213) + ) -lazy val utestJVM = utest.jvm -lazy val utestJS = utest.js - -lazy val tagging = crossProject(JVMPlatform, JSPlatform) - .in(file("tagging")) - .settings(commonSettings: _*) +lazy val tagging = (projectMatrix in file("tagging")) + .settings(commonSettings) .settings( name := "diffx-tagging", libraryDependencies ++= Seq( - "com.softwaremill.common" %% "tagging" % smlTaggingVersion, - "org.scalatest" %% "scalatest-flatspec" % scalatestVersion % Test, - "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test + "com.softwaremill.common" %%% "tagging" % smlTaggingVersion, + "org.scalatest" %%% "scalatest-flatspec" % scalatestVersion % Test, + "org.scalatest" %%% "scalatest-shouldmatchers" % scalatestVersion % Test ) ) .dependsOn(core) + .jvmPlatform( + scalaVersions = List(scala212, scala213) + ) + .jsPlatform( + scalaVersions = List(scala212, scala213) + ) -lazy val taggingJVM = tagging.jvm -lazy val taggingJS = tagging.js - -lazy val cats = crossProject(JVMPlatform, JSPlatform) - .in(file("cats")) - .settings(commonSettings: _*) +lazy val cats = (projectMatrix in file("cats")) + .settings(commonSettings) .settings( name := "diffx-cats", libraryDependencies ++= Seq( - "org.typelevel" %% "cats-core" % "2.6.1", - "org.scalatest" %% "scalatest-freespec" % scalatestVersion % Test, - "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test + "org.typelevel" %%% "cats-core" % "2.6.1", + "org.scalatest" %%% "scalatest-freespec" % scalatestVersion % Test, + "org.scalatest" %%% "scalatest-shouldmatchers" % scalatestVersion % Test ) ) .dependsOn(core) + .jvmPlatform( + scalaVersions = List(scala212, scala213) + ) + .jsPlatform( + scalaVersions = List(scala212, scala213) + ) -lazy val catsJVM = cats.jvm -lazy val catsJS = cats.js - -lazy val refined = crossProject(JVMPlatform, JSPlatform) - .in(file("refined")) - .settings(commonSettings: _*) +lazy val refined = (projectMatrix in file("refined")) + .settings(commonSettings) .settings( name := "diffx-refined", libraryDependencies ++= Seq( - "eu.timepit" %% "refined" % "0.9.26", - "org.scalatest" %% "scalatest-flatspec" % scalatestVersion % Test, - "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion % Test + "eu.timepit" %%% "refined" % "0.9.26", + "org.scalatest" %%% "scalatest-flatspec" % scalatestVersion % Test, + "org.scalatest" %%% "scalatest-shouldmatchers" % scalatestVersion % Test ) ) .dependsOn(core) + .jvmPlatform( + scalaVersions = List(scala212, scala213) + ) + .jsPlatform( + scalaVersions = List(scala212, scala213) + ) +// -lazy val refinedJVM = refined.jvm -lazy val refinedJS = refined.js - -lazy val docs = project - .in(file("generated-docs")) // important: it must not be docs/ +lazy val docs = (projectMatrix in file("generated-docs")) // important: it must not be docs/ + .enablePlugins(MdocPlugin) .settings(commonSettings) .settings( publishArtifact := false, @@ -149,11 +182,7 @@ lazy val docs = project libraryDependencies ++= Seq( "org.typelevel" %% "cats-core" % "2.6.1", "org.scalatest" %% "scalatest-shouldmatchers" % scalatestVersion - ) - ) - .dependsOn(coreJVM, scalatestJVM, specs2JVM, utestJVM, refinedJVM, taggingJVM) - .enablePlugins(MdocPlugin) - .settings( + ), mdocIn := file("docs-sources"), moduleName := "diffx-docs", mdocVariables := Map( @@ -161,29 +190,28 @@ lazy val docs = project ), mdocOut := file(".") ) + .dependsOn(core, scalatest, specs2, utest, refined, tagging) + .jvmPlatform(scalaVersions = List(scala213)) + +val testJVM = taskKey[Unit]("Test JVM projects") +val testJS = taskKey[Unit]("Test JS projects") + +val allAggregates = + core.projectRefs ++ scalatest.projectRefs ++ + specs2.projectRefs ++ utest.projectRefs ++ cats.projectRefs ++ + refined.projectRefs ++ tagging.projectRefs ++ docs.projectRefs + +def filterProject(p: String => Boolean) = + ScopeFilter(inProjects(allAggregates.filter(pr => p(display(pr.project))): _*)) lazy val rootProject = project .in(file(".")) - .settings(commonSettings: _*) - .settings(publishArtifact := false, name := "diffx") - .settings(publishTravisSettings) - .settings(beforeCommitSteps := { - Seq(releaseStepInputTask(docs / mdoc), Release.stageChanges("README.md")) - }) - .aggregate( - coreJVM, - coreJS, - scalatestJVM, - scalatestJS, - specs2JVM, - specs2JS, - utestJVM, - utestJS, - refinedJVM, - refinedJS, - taggingJVM, - taggingJS, - catsJVM, - catsJS, - docs + .settings(commonSettings) + .settings( + publishArtifact := false, + name := "diffx", + scalaVersion := scalaIdeaVersion, + testJVM := (Test / test).all(filterProject(p => !p.contains("JS") && !p.contains("Native"))).value, + testJS := (Test / test).all(filterProject(_.contains("JS"))).value ) + .aggregate(allAggregates: _*) diff --git a/cats/shared/src/main/scala/com/softwaremill/diffx/cats/DiffCatsInstances.scala b/cats/src/main/scala/com/softwaremill/diffx/cats/DiffCatsInstances.scala similarity index 100% rename from cats/shared/src/main/scala/com/softwaremill/diffx/cats/DiffCatsInstances.scala rename to cats/src/main/scala/com/softwaremill/diffx/cats/DiffCatsInstances.scala diff --git a/cats/shared/src/main/scala/com/softwaremill/diffx/cats/package.scala b/cats/src/main/scala/com/softwaremill/diffx/cats/package.scala similarity index 100% rename from cats/shared/src/main/scala/com/softwaremill/diffx/cats/package.scala rename to cats/src/main/scala/com/softwaremill/diffx/cats/package.scala diff --git a/cats/shared/src/test/scala/com/softwaremill/diffx/cats/DiffxCatsTest.scala b/cats/src/test/scala/com/softwaremill/diffx/cats/DiffxCatsTest.scala similarity index 100% rename from cats/shared/src/test/scala/com/softwaremill/diffx/cats/DiffxCatsTest.scala rename to cats/src/test/scala/com/softwaremill/diffx/cats/DiffxCatsTest.scala diff --git a/core/src/main/scala/com/softwaremill/diffx/TupleInstances.scala b/core/src/main/scala/com/softwaremill/diffx/TupleInstances.scala new file mode 100644 index 00000000..d0a23159 --- /dev/null +++ b/core/src/main/scala/com/softwaremill/diffx/TupleInstances.scala @@ -0,0 +1,898 @@ +package com.softwaremill.diffx + +trait TupleInstances { + + implicit def dTuple2[T1, T2](implicit d1: Diff[T1], d2: Diff[T2]): Diff[Tuple2[T1, T2]] = new Diff[Tuple2[T1, T2]] { + override def apply( + left: (T1, T2), + right: (T1, T2), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List("_1" -> d1.apply(left._1, right._1), "_2" -> d2.apply(left._2, right._2)).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple2", results) + } + } + } + + implicit def dTuple3[T1, T2, T3](implicit d1: Diff[T1], d2: Diff[T2], d3: Diff[T3]): Diff[Tuple3[T1, T2, T3]] = + new Diff[Tuple3[T1, T2, T3]] { + override def apply( + left: (T1, T2, T3), + right: (T1, T2, T3), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple3", results) + } + } + } + + implicit def dTuple4[T1, T2, T3, T4](implicit + d1: Diff[T1], + d2: Diff[T2], + d3: Diff[T3], + d4: Diff[T4] + ): Diff[Tuple4[T1, T2, T3, T4]] = new Diff[Tuple4[T1, T2, T3, T4]] { + override def apply( + left: (T1, T2, T3, T4), + right: (T1, T2, T3, T4), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3), + "_4" -> d4.apply(left._4, right._4) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple4", results) + } + } + } + + implicit def dTuple5[T1, T2, T3, T4, T5](implicit + d1: Diff[T1], + d2: Diff[T2], + d3: Diff[T3], + d4: Diff[T4], + d5: Diff[T5] + ): Diff[Tuple5[T1, T2, T3, T4, T5]] = new Diff[Tuple5[T1, T2, T3, T4, T5]] { + override def apply( + left: (T1, T2, T3, T4, T5), + right: (T1, T2, T3, T4, T5), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3), + "_4" -> d4.apply(left._4, right._4), + "_5" -> d5.apply(left._5, right._5) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple5", results) + } + } + } + + implicit def dTuple6[T1, T2, T3, T4, T5, T6](implicit + d1: Diff[T1], + d2: Diff[T2], + d3: Diff[T3], + d4: Diff[T4], + d5: Diff[T5], + d6: Diff[T6] + ): Diff[Tuple6[T1, T2, T3, T4, T5, T6]] = new Diff[Tuple6[T1, T2, T3, T4, T5, T6]] { + override def apply( + left: (T1, T2, T3, T4, T5, T6), + right: (T1, T2, T3, T4, T5, T6), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3), + "_4" -> d4.apply(left._4, right._4), + "_5" -> d5.apply(left._5, right._5), + "_6" -> d6.apply(left._6, right._6) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple6", results) + } + } + } + + implicit def dTuple7[T1, T2, T3, T4, T5, T6, T7](implicit + d1: Diff[T1], + d2: Diff[T2], + d3: Diff[T3], + d4: Diff[T4], + d5: Diff[T5], + d6: Diff[T6], + d7: Diff[T7] + ): Diff[Tuple7[T1, T2, T3, T4, T5, T6, T7]] = new Diff[Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + override def apply( + left: (T1, T2, T3, T4, T5, T6, T7), + right: (T1, T2, T3, T4, T5, T6, T7), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3), + "_4" -> d4.apply(left._4, right._4), + "_5" -> d5.apply(left._5, right._5), + "_6" -> d6.apply(left._6, right._6), + "_7" -> d7.apply(left._7, right._7) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple7", results) + } + } + } + + implicit def dTuple8[T1, T2, T3, T4, T5, T6, T7, T8](implicit + d1: Diff[T1], + d2: Diff[T2], + d3: Diff[T3], + d4: Diff[T4], + d5: Diff[T5], + d6: Diff[T6], + d7: Diff[T7], + d8: Diff[T8] + ): Diff[Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] = new Diff[Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + override def apply( + left: (T1, T2, T3, T4, T5, T6, T7, T8), + right: (T1, T2, T3, T4, T5, T6, T7, T8), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3), + "_4" -> d4.apply(left._4, right._4), + "_5" -> d5.apply(left._5, right._5), + "_6" -> d6.apply(left._6, right._6), + "_7" -> d7.apply(left._7, right._7), + "_8" -> d8.apply(left._8, right._8) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple8", results) + } + } + } + + implicit def dTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9](implicit + d1: Diff[T1], + d2: Diff[T2], + d3: Diff[T3], + d4: Diff[T4], + d5: Diff[T5], + d6: Diff[T6], + d7: Diff[T7], + d8: Diff[T8], + d9: Diff[T9] + ): Diff[Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] = new Diff[Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + override def apply( + left: (T1, T2, T3, T4, T5, T6, T7, T8, T9), + right: (T1, T2, T3, T4, T5, T6, T7, T8, T9), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3), + "_4" -> d4.apply(left._4, right._4), + "_5" -> d5.apply(left._5, right._5), + "_6" -> d6.apply(left._6, right._6), + "_7" -> d7.apply(left._7, right._7), + "_8" -> d8.apply(left._8, right._8), + "_9" -> d9.apply(left._9, right._9) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple9", results) + } + } + } + + implicit def dTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10](implicit + d1: Diff[T1], + d2: Diff[T2], + d3: Diff[T3], + d4: Diff[T4], + d5: Diff[T5], + d6: Diff[T6], + d7: Diff[T7], + d8: Diff[T8], + d9: Diff[T9], + d10: Diff[T10] + ): Diff[Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] = + new Diff[Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + override def apply( + left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10), + right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3), + "_4" -> d4.apply(left._4, right._4), + "_5" -> d5.apply(left._5, right._5), + "_6" -> d6.apply(left._6, right._6), + "_7" -> d7.apply(left._7, right._7), + "_8" -> d8.apply(left._8, right._8), + "_9" -> d9.apply(left._9, right._9), + "_10" -> d10.apply(left._10, right._10) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple10", results) + } + } + } + + implicit def dTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11](implicit + d1: Diff[T1], + d2: Diff[T2], + d3: Diff[T3], + d4: Diff[T4], + d5: Diff[T5], + d6: Diff[T6], + d7: Diff[T7], + d8: Diff[T8], + d9: Diff[T9], + d10: Diff[T10], + d11: Diff[T11] + ): Diff[Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] = + new Diff[Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] { + override def apply( + left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11), + right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3), + "_4" -> d4.apply(left._4, right._4), + "_5" -> d5.apply(left._5, right._5), + "_6" -> d6.apply(left._6, right._6), + "_7" -> d7.apply(left._7, right._7), + "_8" -> d8.apply(left._8, right._8), + "_9" -> d9.apply(left._9, right._9), + "_10" -> d10.apply(left._10, right._10), + "_11" -> d11.apply(left._11, right._11) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple11", results) + } + } + } + + implicit def dTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12](implicit + d1: Diff[T1], + d2: Diff[T2], + d3: Diff[T3], + d4: Diff[T4], + d5: Diff[T5], + d6: Diff[T6], + d7: Diff[T7], + d8: Diff[T8], + d9: Diff[T9], + d10: Diff[T10], + d11: Diff[T11], + d12: Diff[T12] + ): Diff[Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] = + new Diff[Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] { + override def apply( + left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12), + right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3), + "_4" -> d4.apply(left._4, right._4), + "_5" -> d5.apply(left._5, right._5), + "_6" -> d6.apply(left._6, right._6), + "_7" -> d7.apply(left._7, right._7), + "_8" -> d8.apply(left._8, right._8), + "_9" -> d9.apply(left._9, right._9), + "_10" -> d10.apply(left._10, right._10), + "_11" -> d11.apply(left._11, right._11), + "_12" -> d12.apply(left._12, right._12) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple12", results) + } + } + } + + implicit def dTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13](implicit + d1: Diff[T1], + d2: Diff[T2], + d3: Diff[T3], + d4: Diff[T4], + d5: Diff[T5], + d6: Diff[T6], + d7: Diff[T7], + d8: Diff[T8], + d9: Diff[T9], + d10: Diff[T10], + d11: Diff[T11], + d12: Diff[T12], + d13: Diff[T13] + ): Diff[Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] = + new Diff[Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] { + override def apply( + left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13), + right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3), + "_4" -> d4.apply(left._4, right._4), + "_5" -> d5.apply(left._5, right._5), + "_6" -> d6.apply(left._6, right._6), + "_7" -> d7.apply(left._7, right._7), + "_8" -> d8.apply(left._8, right._8), + "_9" -> d9.apply(left._9, right._9), + "_10" -> d10.apply(left._10, right._10), + "_11" -> d11.apply(left._11, right._11), + "_12" -> d12.apply(left._12, right._12), + "_13" -> d13.apply(left._13, right._13) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple13", results) + } + } + } + + implicit def dTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14](implicit + d1: Diff[T1], + d2: Diff[T2], + d3: Diff[T3], + d4: Diff[T4], + d5: Diff[T5], + d6: Diff[T6], + d7: Diff[T7], + d8: Diff[T8], + d9: Diff[T9], + d10: Diff[T10], + d11: Diff[T11], + d12: Diff[T12], + d13: Diff[T13], + d14: Diff[T14] + ): Diff[Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] = + new Diff[Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] { + override def apply( + left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14), + right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3), + "_4" -> d4.apply(left._4, right._4), + "_5" -> d5.apply(left._5, right._5), + "_6" -> d6.apply(left._6, right._6), + "_7" -> d7.apply(left._7, right._7), + "_8" -> d8.apply(left._8, right._8), + "_9" -> d9.apply(left._9, right._9), + "_10" -> d10.apply(left._10, right._10), + "_11" -> d11.apply(left._11, right._11), + "_12" -> d12.apply(left._12, right._12), + "_13" -> d13.apply(left._13, right._13), + "_14" -> d14.apply(left._14, right._14) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple14", results) + } + } + } + + implicit def dTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15](implicit + d1: Diff[T1], + d2: Diff[T2], + d3: Diff[T3], + d4: Diff[T4], + d5: Diff[T5], + d6: Diff[T6], + d7: Diff[T7], + d8: Diff[T8], + d9: Diff[T9], + d10: Diff[T10], + d11: Diff[T11], + d12: Diff[T12], + d13: Diff[T13], + d14: Diff[T14], + d15: Diff[T15] + ): Diff[Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] = + new Diff[Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] { + override def apply( + left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15), + right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3), + "_4" -> d4.apply(left._4, right._4), + "_5" -> d5.apply(left._5, right._5), + "_6" -> d6.apply(left._6, right._6), + "_7" -> d7.apply(left._7, right._7), + "_8" -> d8.apply(left._8, right._8), + "_9" -> d9.apply(left._9, right._9), + "_10" -> d10.apply(left._10, right._10), + "_11" -> d11.apply(left._11, right._11), + "_12" -> d12.apply(left._12, right._12), + "_13" -> d13.apply(left._13, right._13), + "_14" -> d14.apply(left._14, right._14), + "_15" -> d15.apply(left._15, right._15) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple15", results) + } + } + } + + implicit def dTuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16](implicit + d1: Diff[T1], + d2: Diff[T2], + d3: Diff[T3], + d4: Diff[T4], + d5: Diff[T5], + d6: Diff[T6], + d7: Diff[T7], + d8: Diff[T8], + d9: Diff[T9], + d10: Diff[T10], + d11: Diff[T11], + d12: Diff[T12], + d13: Diff[T13], + d14: Diff[T14], + d15: Diff[T15], + d16: Diff[T16] + ): Diff[Tuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16]] = + new Diff[Tuple16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16]] { + override def apply( + left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16), + right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3), + "_4" -> d4.apply(left._4, right._4), + "_5" -> d5.apply(left._5, right._5), + "_6" -> d6.apply(left._6, right._6), + "_7" -> d7.apply(left._7, right._7), + "_8" -> d8.apply(left._8, right._8), + "_9" -> d9.apply(left._9, right._9), + "_10" -> d10.apply(left._10, right._10), + "_11" -> d11.apply(left._11, right._11), + "_12" -> d12.apply(left._12, right._12), + "_13" -> d13.apply(left._13, right._13), + "_14" -> d14.apply(left._14, right._14), + "_15" -> d15.apply(left._15, right._15), + "_16" -> d16.apply(left._16, right._16) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple16", results) + } + } + } + + implicit def dTuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17](implicit + d1: Diff[T1], + d2: Diff[T2], + d3: Diff[T3], + d4: Diff[T4], + d5: Diff[T5], + d6: Diff[T6], + d7: Diff[T7], + d8: Diff[T8], + d9: Diff[T9], + d10: Diff[T10], + d11: Diff[T11], + d12: Diff[T12], + d13: Diff[T13], + d14: Diff[T14], + d15: Diff[T15], + d16: Diff[T16], + d17: Diff[T17] + ): Diff[Tuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17]] = + new Diff[Tuple17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17]] { + override def apply( + left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17), + right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3), + "_4" -> d4.apply(left._4, right._4), + "_5" -> d5.apply(left._5, right._5), + "_6" -> d6.apply(left._6, right._6), + "_7" -> d7.apply(left._7, right._7), + "_8" -> d8.apply(left._8, right._8), + "_9" -> d9.apply(left._9, right._9), + "_10" -> d10.apply(left._10, right._10), + "_11" -> d11.apply(left._11, right._11), + "_12" -> d12.apply(left._12, right._12), + "_13" -> d13.apply(left._13, right._13), + "_14" -> d14.apply(left._14, right._14), + "_15" -> d15.apply(left._15, right._15), + "_16" -> d16.apply(left._16, right._16), + "_17" -> d17.apply(left._17, right._17) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple17", results) + } + } + } + + implicit def dTuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18](implicit + d1: Diff[T1], + d2: Diff[T2], + d3: Diff[T3], + d4: Diff[T4], + d5: Diff[T5], + d6: Diff[T6], + d7: Diff[T7], + d8: Diff[T8], + d9: Diff[T9], + d10: Diff[T10], + d11: Diff[T11], + d12: Diff[T12], + d13: Diff[T13], + d14: Diff[T14], + d15: Diff[T15], + d16: Diff[T16], + d17: Diff[T17], + d18: Diff[T18] + ): Diff[Tuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18]] = + new Diff[Tuple18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18]] { + override def apply( + left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18), + right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3), + "_4" -> d4.apply(left._4, right._4), + "_5" -> d5.apply(left._5, right._5), + "_6" -> d6.apply(left._6, right._6), + "_7" -> d7.apply(left._7, right._7), + "_8" -> d8.apply(left._8, right._8), + "_9" -> d9.apply(left._9, right._9), + "_10" -> d10.apply(left._10, right._10), + "_11" -> d11.apply(left._11, right._11), + "_12" -> d12.apply(left._12, right._12), + "_13" -> d13.apply(left._13, right._13), + "_14" -> d14.apply(left._14, right._14), + "_15" -> d15.apply(left._15, right._15), + "_16" -> d16.apply(left._16, right._16), + "_17" -> d17.apply(left._17, right._17), + "_18" -> d18.apply(left._18, right._18) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple18", results) + } + } + } + + implicit def dTuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19](implicit + d1: Diff[T1], + d2: Diff[T2], + d3: Diff[T3], + d4: Diff[T4], + d5: Diff[T5], + d6: Diff[T6], + d7: Diff[T7], + d8: Diff[T8], + d9: Diff[T9], + d10: Diff[T10], + d11: Diff[T11], + d12: Diff[T12], + d13: Diff[T13], + d14: Diff[T14], + d15: Diff[T15], + d16: Diff[T16], + d17: Diff[T17], + d18: Diff[T18], + d19: Diff[T19] + ): Diff[Tuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19]] = + new Diff[Tuple19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19]] { + override def apply( + left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19), + right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3), + "_4" -> d4.apply(left._4, right._4), + "_5" -> d5.apply(left._5, right._5), + "_6" -> d6.apply(left._6, right._6), + "_7" -> d7.apply(left._7, right._7), + "_8" -> d8.apply(left._8, right._8), + "_9" -> d9.apply(left._9, right._9), + "_10" -> d10.apply(left._10, right._10), + "_11" -> d11.apply(left._11, right._11), + "_12" -> d12.apply(left._12, right._12), + "_13" -> d13.apply(left._13, right._13), + "_14" -> d14.apply(left._14, right._14), + "_15" -> d15.apply(left._15, right._15), + "_16" -> d16.apply(left._16, right._16), + "_17" -> d17.apply(left._17, right._17), + "_18" -> d18.apply(left._18, right._18), + "_19" -> d19.apply(left._19, right._19) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple19", results) + } + } + } + + implicit def dTuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20]( + implicit + d1: Diff[T1], + d2: Diff[T2], + d3: Diff[T3], + d4: Diff[T4], + d5: Diff[T5], + d6: Diff[T6], + d7: Diff[T7], + d8: Diff[T8], + d9: Diff[T9], + d10: Diff[T10], + d11: Diff[T11], + d12: Diff[T12], + d13: Diff[T13], + d14: Diff[T14], + d15: Diff[T15], + d16: Diff[T16], + d17: Diff[T17], + d18: Diff[T18], + d19: Diff[T19], + d20: Diff[T20] + ): Diff[Tuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20]] = + new Diff[Tuple20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20]] { + override def apply( + left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20), + right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3), + "_4" -> d4.apply(left._4, right._4), + "_5" -> d5.apply(left._5, right._5), + "_6" -> d6.apply(left._6, right._6), + "_7" -> d7.apply(left._7, right._7), + "_8" -> d8.apply(left._8, right._8), + "_9" -> d9.apply(left._9, right._9), + "_10" -> d10.apply(left._10, right._10), + "_11" -> d11.apply(left._11, right._11), + "_12" -> d12.apply(left._12, right._12), + "_13" -> d13.apply(left._13, right._13), + "_14" -> d14.apply(left._14, right._14), + "_15" -> d15.apply(left._15, right._15), + "_16" -> d16.apply(left._16, right._16), + "_17" -> d17.apply(left._17, right._17), + "_18" -> d18.apply(left._18, right._18), + "_19" -> d19.apply(left._19, right._19), + "_20" -> d20.apply(left._20, right._20) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple20", results) + } + } + } + + implicit def dTuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21]( + implicit + d1: Diff[T1], + d2: Diff[T2], + d3: Diff[T3], + d4: Diff[T4], + d5: Diff[T5], + d6: Diff[T6], + d7: Diff[T7], + d8: Diff[T8], + d9: Diff[T9], + d10: Diff[T10], + d11: Diff[T11], + d12: Diff[T12], + d13: Diff[T13], + d14: Diff[T14], + d15: Diff[T15], + d16: Diff[T16], + d17: Diff[T17], + d18: Diff[T18], + d19: Diff[T19], + d20: Diff[T20], + d21: Diff[T21] + ): Diff[Tuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21]] = + new Diff[Tuple21[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21]] { + override def apply( + left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21), + right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3), + "_4" -> d4.apply(left._4, right._4), + "_5" -> d5.apply(left._5, right._5), + "_6" -> d6.apply(left._6, right._6), + "_7" -> d7.apply(left._7, right._7), + "_8" -> d8.apply(left._8, right._8), + "_9" -> d9.apply(left._9, right._9), + "_10" -> d10.apply(left._10, right._10), + "_11" -> d11.apply(left._11, right._11), + "_12" -> d12.apply(left._12, right._12), + "_13" -> d13.apply(left._13, right._13), + "_14" -> d14.apply(left._14, right._14), + "_15" -> d15.apply(left._15, right._15), + "_16" -> d16.apply(left._16, right._16), + "_17" -> d17.apply(left._17, right._17), + "_18" -> d18.apply(left._18, right._18), + "_19" -> d19.apply(left._19, right._19), + "_20" -> d20.apply(left._20, right._20), + "_21" -> d21.apply(left._21, right._21) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple21", results) + } + } + } + + implicit def dTuple22[ + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + T9, + T10, + T11, + T12, + T13, + T14, + T15, + T16, + T17, + T18, + T19, + T20, + T21, + T22 + ](implicit + d1: Diff[T1], + d2: Diff[T2], + d3: Diff[T3], + d4: Diff[T4], + d5: Diff[T5], + d6: Diff[T6], + d7: Diff[T7], + d8: Diff[T8], + d9: Diff[T9], + d10: Diff[T10], + d11: Diff[T11], + d12: Diff[T12], + d13: Diff[T13], + d14: Diff[T14], + d15: Diff[T15], + d16: Diff[T16], + d17: Diff[T17], + d18: Diff[T18], + d19: Diff[T19], + d20: Diff[T20], + d21: Diff[T21], + d22: Diff[T22] + ): Diff[ + Tuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22] + ] = new Diff[ + Tuple22[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22] + ] { + override def apply( + left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22), + right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22), + toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + ): DiffResult = { + val results = List( + "_1" -> d1.apply(left._1, right._1), + "_2" -> d2.apply(left._2, right._2), + "_3" -> d3.apply(left._3, right._3), + "_4" -> d4.apply(left._4, right._4), + "_5" -> d5.apply(left._5, right._5), + "_6" -> d6.apply(left._6, right._6), + "_7" -> d7.apply(left._7, right._7), + "_8" -> d8.apply(left._8, right._8), + "_9" -> d9.apply(left._9, right._9), + "_10" -> d10.apply(left._10, right._10), + "_11" -> d11.apply(left._11, right._11), + "_12" -> d12.apply(left._12, right._12), + "_13" -> d13.apply(left._13, right._13), + "_14" -> d14.apply(left._14, right._14), + "_15" -> d15.apply(left._15, right._15), + "_16" -> d16.apply(left._16, right._16), + "_17" -> d17.apply(left._17, right._17), + "_18" -> d18.apply(left._18, right._18), + "_19" -> d19.apply(left._19, right._19), + "_20" -> d20.apply(left._20, right._20), + "_21" -> d21.apply(left._21, right._21), + "_22" -> d22.apply(left._22, right._22) + ).toMap + if (results.values.forall(_.isIdentical)) { + Identical(left) + } else { + DiffResultObject("Tuple22", results) + } + } + } + +} diff --git a/project/plugins.sbt b/project/plugins.sbt index 4ee6bba6..9b59c86c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,9 @@ -addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-common" % "1.9.15") -addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % "1.9.15") -addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-extra" % "1.9.15") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.6.0") -addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") +addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-common" % "2.0.5") +addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % "2.0.5") +addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-extra" % "2.0.5") + +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.1") +addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.8.0") addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.21") -addSbtPlugin("io.spray" % "sbt-boilerplate" % "0.6.1") + +addSbtPlugin("org.jetbrains.scala" % "sbt-ide-settings" % "1.1.1") diff --git a/refined/shared/src/main/scala/com/softwaremill/diffx/refined/RefinedSupport.scala b/refined/src/main/scala/com/softwaremill/diffx/refined/RefinedSupport.scala similarity index 100% rename from refined/shared/src/main/scala/com/softwaremill/diffx/refined/RefinedSupport.scala rename to refined/src/main/scala/com/softwaremill/diffx/refined/RefinedSupport.scala diff --git a/refined/shared/src/main/scala/com/softwaremill/diffx/refined/package.scala b/refined/src/main/scala/com/softwaremill/diffx/refined/package.scala similarity index 100% rename from refined/shared/src/main/scala/com/softwaremill/diffx/refined/package.scala rename to refined/src/main/scala/com/softwaremill/diffx/refined/package.scala diff --git a/refined/shared/src/test/scala/com/softwaremill/diffx/refined/RefinedSupportTest.scala b/refined/src/test/scala/com/softwaremill/diffx/refined/RefinedSupportTest.scala similarity index 100% rename from refined/shared/src/test/scala/com/softwaremill/diffx/refined/RefinedSupportTest.scala rename to refined/src/test/scala/com/softwaremill/diffx/refined/RefinedSupportTest.scala diff --git a/scalatest/shared/src/main/scala/com/softwaremill/diffx/scalatest/DiffMatcher.scala b/scalatest/src/main/scala/com/softwaremill/diffx/scalatest/DiffMatcher.scala similarity index 100% rename from scalatest/shared/src/main/scala/com/softwaremill/diffx/scalatest/DiffMatcher.scala rename to scalatest/src/main/scala/com/softwaremill/diffx/scalatest/DiffMatcher.scala diff --git a/scalatest/shared/src/test/scala/com/softwaremill/diffx/scalatest/DiffMatcherTest.scala b/scalatest/src/test/scala/com/softwaremill/diffx/scalatest/DiffMatcherTest.scala similarity index 100% rename from scalatest/shared/src/test/scala/com/softwaremill/diffx/scalatest/DiffMatcherTest.scala rename to scalatest/src/test/scala/com/softwaremill/diffx/scalatest/DiffMatcherTest.scala diff --git a/scripts/decrypt_files_if_not_pr.sh b/scripts/decrypt_files_if_not_pr.sh deleted file mode 100644 index b3c4456c..00000000 --- a/scripts/decrypt_files_if_not_pr.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -if [[ "$TRAVIS_PULL_REQUEST" == "false" ]]; then - openssl aes-256-cbc -K $encrypted_5331bd37a5e5_key -iv $encrypted_5331bd37a5e5_iv -in secrets.tar.enc -out secrets.tar -d - tar xvf secrets.tar -fi diff --git a/secrets.tar.enc b/secrets.tar.enc deleted file mode 100644 index 25ae3194003c6695aba378097cd463cc899e8bf7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20496 zcmV(qK<~eZ74Xwm67k&M=q7{(t9gJU@Tl05&7y9X2-vc$GQ2bfmw)R)Y;S&1R+Fu| zpGj~R6u5)6^nyRke!pT=34zy7;EA~b2G0s)2dC-Fq{JW~F2 zy<6}5r!sYjD!&nytx z9vMZ`IAaqPxA4g02KP2%e-ae75L zYywlf-mYSJE4LywR8STo^^LxdBul;1gv2dez<=w+GdS*Tb|4sHd*WXqJ*SY_xG11s z1bV*NIuNFJ)at_}zS_=C86&^4QUNz~1V7AUr(tK(N`7)JjvwA+G^jDk!!XQzhfiwc zolo0O81y2<;E?@GJ8St`2e^5y=b(g|OhrA#N)tD^`eWaW86ve|$E#z@&NjVDDd!)H zY;pt;9Q*De^1?&M1dZO=RIua`JLP5=v}^+cleZ~^2(nRG-QqESw^=P~s5DP+XeCIU zOnV(annSuHdln*;SdZTxdIlcR6r%hnZnrhA^%A$j|H%{AZ9_h&mnizppvxf)b;4p+ zAqN}TMX}dAi9kvl3azNLwP(pIr!nl-C?g#hg&_{8TeI$bLNQ)TYZ3$kqL^&52U7fa z&7j~t=F-r@OtB9N)-?f9*JKCd!i-abqCcYns7kMj z-BsMJWSdvtNtJp2$$${gn7bk={22kMBueX3jxs|US~ zO5FuuSw`#fL~ydMOcm}?&B!1t5)!xSn1A=BAfLsO2`&`VR=GT!87im~h<8NSOSTmF!K47hjW$*rDgRc zJpbh#ox)7~@u)XTwWE*Qa&RphGV!julSC;dr7Y})`^w@S?k`iY_K-Q@g5``&4Gv!P zY_nV9WZc_^H~b8ZYS0~NO;`Lr;1N8Z(P>ce;k${WU&i2|qPGT|mfa_vlpJ1dGKrAm z^=UZG8)Bv-{9+x$u_7;u$d{8*!L4PGw03jc0Flis?xNjPci^g&a`E|Dh&l1_;|XY!a8r7!+lF4q3e={a0i}9P zN2_d{`M&Ib@Q|?dO{TP40{EkmNpQD$QlM1AHd5D=Yt~iEaf4)fQcn7XO7Qwd6zn>? zL`gmj|EIVdQ|*FFaG6$32VjJF)YrgYA44CjhX{`R3>E%Z&F_1nPHZU>i1;NkpCg8G z!(5$54Q>DD;AsbGUaT&LjrlqYlGZ;NO^ISu-dL6ap{Y$zLdqGrfkPX@MncHojQ)d1 zfA4_d2F4?(WIp^Bx@euZDh%w1;_!;f`H2Rz}f)@TCE8wK3*`Xh$W|Os#S>qN+@;G|4UkNwOcdWaV_>F|x zlV_Nja~|JG&INtB);lH_eINxtziPMr%#8@|9`wr|8+g?5lL2FXZwg3fpvMIfIm|?V zz{(|Qw1KmIIL-S7B1yCjVO(a)3f+@tgL9iE6~BP)>68680DaN!tyXbhTx`8LJO?P2**&H4RHRZYeWj9G9ABIH+%i*Q2Ej!gCPu>cU{v;(!bdjw0jSMe#fSRQV zD1BBY=*$P{Y8){teXOMT;$!sea2F&nArYh8`cA?dt!KlDHJxfUj$kG=1NThBy#@_D z(1Z8s@9huwFms<#8)Tf{FK79R3(?h1^|?A40__%3A@(1iyb+Jz8ArOgbHNjOzO#(eIJZe4%TsXK3_&6~hrGo|360vEF?F6U z5$+g!@gIxn2OP7&!>k$n7I1THKgsZ-rL9F!BZsXKTBx`|>s>W-|5*Y;K&6(1V@i|U zVP~rU&!TK%;>*K8=Aq0AqH5d_+5hdw5kX+-uy%WOV>!XR`1LCYK}xB^8{sEtz9tfI zonZW==B_{;Zuw@9n*Jar${4MfDi>Z2DXLVT+i2k&;V!aQFgsK)_MIVBIY-@;-GKX> zXb+bZ&+atSe#;~rqJ)>01CsX~djYayIk$&tipJJz5l7^z)t#F)oIQ$F`}X|cAxa{^*S^Cx+!pW zopnl7Y0MmHwO&Z=xdts~bJsnoEe3sK75*4$Z6GkOwB6KH&u}@{pCytDu;U5l)@>}m zhZ+S(YsffN)tK9vQD)E95^;MBNSMgdhMgpRi~y}lw&V|KF(kg@!F894`t3Ex-`YuS`j|E50UPgy?W$vs(l|Rh4;vc5a)JFW8mOHi2 zOt7e_i{#?E2cYm(uM&Y@Xs|xoZG2Iz$Cig;cglA{Mfv`J2ZS}wvD5J@Evzpp*)`?b zR*mMYl|s`7=9myLex^&RESudvq+fb(9N+ypo$z!PNd2nLwUvUN<4@a}Ore3w>`AR9 zyb!rTRpm+Jd7tm2VpPF)!^?IBp~QpiZfxPL90*+`#!+I`&fL1IXTLKyWR9eZTLGAw z4fZGMdGnJ4JIu8ABOTSL!&I|=ZAh+hF>5$Dn}!gWbbtxuJ>MHyw^eFV|7H$ci3zf$ z6BgmE1Dhd4odA*L_q%Ne+SqJtn&9$|$H3DDte_^m&z>`_M6f3Hfic$Z%wL+fBNtru zD;@=4TLG;tREd3ybR|b9mf%A74_Q%I7?sCm4xS!b39P=yd+kqEXsK$j!D}s0GgPOJ z>UTNOpf~Mb0LzNf+$uZ_g z480CuOxrlR0$ktt!r7MhU0mTaC5$5Y6yTd;ag7{E$|%j4?Nlgg$pngVE`ylPd8NQPhbJPw&~wf0R?v+r=6c3f4vUXL6Nv684)5 zE%tawXi$oye_yd`a=B68GE9H0d$E4DYsnPS`$F`p(_4WW?0S?W8yOtYrcK<+`xqL( zvLqMt8s5uKEs{4ekAE*55^3;3*t2#0fv4wr?NWK|oSg()SYkiF!+RTR$Vt!&k<;>$ z12P(vJ5tT&IO3slVi2RGfLCtRss)DtnhuzlOfeO6fMdyk%7Nyie<%C?a+=Lcc7i?D zhjiF)vf*MVi`gxN7?_s$D ziD)Dh&mS*1s&@?;v&*Ym>p*S^>z4!7MIC?Jp3YkFlTTCW7#yvg<{!~U9jO4?D8QArXOA*J8KqVA#Yg?4c#pZi=Z~{w8q% zLHxSkNbJhzAzf|dYl_pHu)66ZG0ABYjGnw~X_Zblw*DRSB=}3b_aN6of+cJWSoDN} zvOq>7IleE>*aQp&@A3248PJM#Mzz&?LRus)L%}A%(sx+4l>qoZ#6nmxD`cB`Z)6D6 zKYlDyX%<_T(XhWml6ZU_+?2#ud**oTAsIO2ow{;qIxAC31ueVHZF%F&!!wJ6CZ(ma z6CJLY2ufE`z#;GeYnnSvVUw0RSzsCp3FpnaN{*ToLGLd|!)=mFc%KLWF#KKo>32zf zZqj!a5*u6&DQDb1g_UnA2~P1iRrX!XPqNG^)=Vgn#ceb=7<>h$q^P#9A5M)|FpYf7 z_%_IQw$nVivi^kbRNJ5u-Fb!P{L&ce)j+`2#1D~$%Z9-+mZoO%P6*>NDjoDYwI^|z@C+{Rmh|B-^=zp54UIU!g z2?bOLShooN5?bTq1eKr!-bk^}`1Tg|ZKaK1D7v*XPS%KpKDa~bhls8T&*bUKj>Z!D z6mHzmrtz?XV#4f%k4S*=aof!n#7R0|`rdaeU&D5DQ%S z!;#kqfN=z>)b;9D;}H-Np)qq86})0}jS)j8m)dwO!a~*laq?C%V0E_~zz<8Ke>Q+! z)V}!9E-<{daeVK*>5H~$4FwRi;G+~Jr4zY)V4lOue{{u7!>8wR8dr?qM9Ugu*4iCA zVW$wdooqD!#d67FJ(kmi$y*VemxK$j`Ny--G)L7m{3LBZ1dGf(o6^nknIlq}2(mD^ z1+S4Q-!eW`;JMOZC}3F2HWV}%&n*jfY2V|Hd5C^u2@n%${;@_B+s{1Qw@`7I({%(P zUk=#nWgW@p_VkXK+u4yX#SFiOFcZln!Nr1R|F=KL&oFEUE_B?A%5pEp(6Ea4zTrA*8fzBK)sqqk{%&h*k+8si}?l@{EECgN(}1X8Hk8 zD!%;Dcez|BA$J`2KPJAP|X> zJQu>G)oR5G)xNOH0%AnO${yQ|`Da@NM-Yu=?5Hs|emz=B3d1-1EV{oj0JS(pD)nB_ z@USt9y-eHEIIo$eTXCs6l$%@s)N?Vf5Phvbh_dZ*s@rwR;g-#O`;wM2Q6{7M!Is&x$RIn8(`bT7gU_AEPU<~qhfeK{JT zu!^8FQeNg0`1aHeCc8`REwd=cY~=MnfBX5rB^-q+@L?Fa6)+({&{uK6UppEvWJ5Dr z^&pm$C#4p;6FLZ0E?DE_>FP&QxchVC`$R!R#y(EeOFK;k@Bt2~GtuOo*LPIQvW?B( z__+3aa;DB;Bv_4*%dnh;6Gsj>P~Q6$mA$GVwOK+}zV#klzgo%2{|PWHH2!e#H#<-H zO|N??iV?Qjc>e$tCr2)rK{e|{nh-pSPm4+-EFO4DgmKVihIj5v@2gw-%e-qs;(Cx` zO}$}K$>x~4{M=91)|xeR z{%M-JM;C#V#&wR4V(+A=;`*#F9R@0>C_j?uc*WLwmTFCOaV6v31LQFjBv*&$aC&Lh zELUL@CFDqe=1Q`Fq%=dXuq_aHES{Nb(MZX_ztYl41T7|8F|8*TnX zd9gG_3+1qAw=g8-&^w(pWqnf^91(aGOIq5uya{v~Er$$QE6q&BS@6^RLJmdVMI4Co!Rk#!XPQRMPyG|>OiXxK0az0l26%AZx@b2{MK?| zh*D0w3r`qKcN)nf2Q&4^2tU+aio}1y=AM5E=8$D1_8i-6M0mM6MKM4=OC5=BdNpPj zAuM856!G>ZAT53SqQ*|OIF;cf$FX|3S7nxWX9o;H1}+=p$h>9q`;b8nf$_{}DPs`H zB!Vb}Ld%6)QVnS?RG2uU(CVry@7MbMpOTEF4Bvi54V6gh9y!)^zjG*8Kpder}&PYFBSLyE2{DIV(xkD5D38d|! zprN5Yf)<<|LvdvEvW{KlE0`?O(^WG|_xjoPH{*hKZp@pg+=Cc=!B)dUYiF*{2|p(* z!8uF%yje(?|B_*)>FNF#uzTT?DFO9vQbDAp1!=KyeR5p5)bW0RJigYhw!rT{CN*-&bzrb^5L9rQSA)Ba&Y%8u9!PWeDTI?GP| z>`7MeC+FsKr*Q0=T^(DI-cJ5QY+5Yw@Q4$mu4GH|N6h>ti)t<`qPFekdg5zdAq}lb z$4k$vIb);4IU3hfILbs79-4oA$}7Iq{BK2^(YByHcj|RV4&j+vB}T*W%XUC8rtV6o zo1ryp`+(vzaN*STr#({6`!p5P%xmHYu$C!tFTFq;I()R3oV8pVno!!$@rhc0QhFz3 zrq*rYutx6YffZAelKqzzpb|a>k|05opMCNtrsXTrVCTt#a{*}>Lz8Q_tZ^J5eW>Tr zLpr$Cd5eBUJE4x5yYi)IASZW_c(jWkG!?dd+n?urxGz=E!KZ;%G?0;cAMHft$k`A zy3&&Gn8%#^>`w5_)$1G4)rAegrl(ID`N<(qP;?CKD%SH2&(k5uSB&aAE=nW04p?4) zI@MUJ!`spX9}yEXAAio7WhVpsR+;QFp>1U|24c8H&9A&QbH^*fn_$fA4`in@V>|0y zq=|}p+!U;eC|gWl?Um){&=Yot_wf*K^q3GaZarw6I`_|vl$I4y8JeQe z!fj$~mOxtkmD^^?Rt!i1H)ot?X`vEBSGh^-yRl7{d?fYk zF_7}qf#W{8jp*I&wXX%^#`*+-Vp>oIlJ=jbMlFzAGFN95hVG!+Lh{r`>dib5#R(mA zw;lo;nj_@(k3Uz<9h;u2npm2_<~#*qG@M7O)SAPSFwCZLjCM3$?YDs)Epx%D5pEQ+ zR*&h?nM`o7CjdL(1&8MxZX5K=u9kH2undmb6VT@`l@Kk7UOaHpXI%%%gdb|pQ8@w6 z>dbk8V7xdsc--gl^_2CN6UeW@YU-xRO8Pfo`HyVJ z_WVpGO^Kb|vjtDwWgeGzFdlIoThLqBRDw{YbOa3zKx=2{GOt!LwSW5kuhl3p>^?3! zn)xv9&c}? z^90)EtE1>L?SX0-c1q~4#^U|8pba)}k4>Bwn3wh57Dqwh+O85|*ldpY46xL4vSlBU4qbrprE1SY*G8>%@$iEs2*PBWUqI_MaK8#URKEJ~UVpa~ z)6%QFj`~*?F!W1c4n+)6F+b2X{j8%p5-gUK(ItPjb^IoKxnz%T*y@LPmQ& zDqkE3rC^E`HaaAD12qIe763@p3FY}RkK9ozBMZ}e3fuwKP9knmOI{$Tq}DmC6B2#D zAJPFqjyD`}(_X;qk3Ide{K-P$h#KN0rrm?RASOLJ{8|>+Ucp`jG;bL#AV;Rb6{+5z*JfpJaRB@-F6Qta(qCSMYK#)RY%I$3PSUNqnYB%T{H7|qx!cI%G zYI89}`LR5Z+oApr>(p3WjVlxo7wDp6L#iEZ-@T0avThV4$<#dIQS;ZMgzS3ZuJ>Zr z2VQ1(LFM`A<^0>APJ)!eqT)V*eZIvfdTPl#adjXXu2#;mz-x;DKPKzVVSD06_p;~M z`E1-m&;}q|-5gt;j~xmi3E^i+$^Ua`GHh2X5K{AJA~AOT9knDhmb&nuYdPs9IgQJ4 zoYZ*h8=|akGdQ+xuIVI5KcA%=Smdbk!uhd~ju4MHxYXBr@5P%%Z);K6vj)%=pZP8% zzq4&69a$<|UdjB7`4d){7ba~(BwGkxaTSYl?zc3C4DM=5It z+xUD&Zq$ENdJ4*1CKY+pp0^~&4Piv*zKQuIdKbTYzr#*gu6yTL&2%%O@IL=h%3Ac=b-eoX6O2c9iEanl+(tw_=n{x>tI!jy zH>aZTG=#+5>b2;G=*GbvKBUe1VNtTGJzG|`3DihnOTH}cet%MK;-Co$55nvG{r22r zNODRdhSvw8kbwPmTco})d<}5Vn2}xLstgoT1e;Zhpj?O84LWCrr@|?To(?F^oQt6@ z&a+q;)lJ4QILni-F)SiGDLi445T|cGsmHK<=U#QG=Q>AiCz!q`XvW!|&G+Z|aWw5( z9D!v7i!FrXXbiQ-IbDiEv_tgu`|wFeHScNv6eo7^S_Bn}s*oP+Q&jB8;$N2unZIq=T#J@a(o^D*rbOEM%|> z*4!Z-Sj!!(3w6*Ntkr$EKn`g@0APX6y$a7=2v`f0E>5)2L})mLceci8kbtlh3vP#4 zE01*}zd$NbeOw;HvB)`4E+yE^e)Oq=KMXGXY<*LfBe=2a<2&qm@iZP$Pm~&VZ}-KR z_Yb!1iG+(Rlug6#wz!87#z(m>djWN}mnYi&EeGlX+i7;O0bSZEQEB4iLbonRi@K0? zK4IOi{X5V}ysx|=aDT^F%5zv=NpV%?;~B>0V9({`%d6 zXG~>o8HQdvpKE_7%|U`LjpKRpA5zQrJhEzM-gP!TR4MFd0QJ#J;$0+%fRyiMMB7XB zm?AKG_jGBsSG@>rU9jb|Y*@!0qxPOX(Op-t1D)fgioQ~p6j+>dnYME~3RH2(&Im3` z!(=_E@O96en;Wv!3btJTC!>`3DbPiuNg#Gz9ra$+$yDi_5Sm z9MB>kO24pd$&l4*;8`0>rReVB_C}4(QC{Fni+1Z>sZkF{8WC>Bfk~7VLvEVP@mMUP z!5)+Ykqn!~d-4hCE`faG-CjJ(-gpsyM8t6OmZ^iEhd=H7PibIW)SqY>KqNe_AfrO> z)*ckckE(WSMaPl$yZ>FPy=VAF*4F zcZ~UpOOQ7-&_o&R=QLH{BNXucx%fRz)Pw*==$lApPzcMoGsW9^IXPNg`CP@UID?VA z_VSGozE2|q-YofMp5_%gza|e@G33)ZwHQrR_fnT6t$+~END`ggbe1n8Q@A)5+rfv~ zZW8C`<(7C-?;}8a%RnnVQ!Smla#j`-d1p;#VuCstZZ))QjIT85Q&`ta>$Mps*W)xN z$bf$&R5$>7U%JrfJZEbJIo;kT+$9$RTFvXvmw9B$5G?vKN;~k&p@!fvU$kpG5WpNl z#0^rgaEmQKVk_$fYM2Ik$80TEQ3o&!gk*KCS^A{=! zhkaawAnF&r%nvM`g7_RUU;4USM33EFvjSU);pQb@W>9M>xet;}JE`4PWEYZ7y^Ukr zBsk{sqVj*^8f>8!1FPW%U1-SYn^cxrMOp;L{RN zy?u8H?AoYqb(JvXozn+vr?g7R>9A)pnSeJ6IS{482VV9FX1$7mvcOvGeMKi{IFtPf zkgNCf)M4GfmZ2el$Dq+s_+X8pMUbiH+oOjB0r!ThsqCP@!e51l0b3LbvPt-KnPz%d z(pYeyawoINZSIM|ox$87sj`TtWYf6Q(sEZ=_LImi!LB)B3)ja#Q(*keTd6WPQVW*g z!+Kh}lt2zjp!M;HG1AAzPYiOoHX5yL$5(W15B!NxdV~&Js8lVSh%L zcK|tMHJV3OdzcMtMcl5bHZt!sV!r(OLRn3nYnMjL7yrjMcD~G5&a4S@Z*9-RH{W0w_Xku`@xB{GI(z(&_k4)@UHKXR#cuBhZ7xk3%S6g5$NjWlrH$@ zn^U)dKWOl7ts?^311o7xfymWTkUUf&ZidxLuVBEW0yFUw3d}KrNRpD)m~HuVxUT)A z;9R@W)$Qk)bCwGfM21Q#2d~MNJ0q9CuEjxnvuB9 zOvEdbv3?8yPlqc%&C0R?$KKgOU{^Nar%1SYa=jUiYEKJb16PeLdYk$l&qIAyF)qaC z%nZgtHzld$ooi%qAWCm@^elCGdSLi-od@D-=05q3u%J5d6$xE9=h+0r8Hmqa59}k& zDLB7kT&GNmxlNda>4**x%#79<&qPmBco`J0(TKg=sxVjBXm`I=8=8K1261_VTRsMH z*f*7Hz0Xn$r3f`^a?5s~)Rnbss{bnyKY!$$#7k!KwX<$|(3iXhG0#gs10?y2g(}5g zIIsI!O>MBL%^8-VXespf-c%^2%qDLwZOY;;v@f|2G zW6^hWUiLGs6*w6 zZAN9dCjmwR05hr0I8s4;F^pEGG;^7B4iKT#dg)nWts4{DcW}CDmEl2M=-=1Yf5QTE zxM32k5`Hngzg!NV_@f*)_6|-AkH}!M*Z0SI#%`JN*|;f%WLf~RGH)+FTJmE?q}@K! z);k1@HmZDL!2l*~(p`hCm0f*cyt3~6Bsmb=n2<<-MJ{_fzm@&xgJ?0$szxZFF5{jX zVW8M)f5pA#-Mo z&xJ_WOx+Lf43^tYUXf4>AP~1gtElG+ksgm(hKP`GdA+jcHcXFg`gBP-a-{Wdfl=Yo zFT2Rh^!39r8>@#L+6S6XtF1NsOJ8{r=nk-#;6YSjPkZ6U;=a0v?rPGmF$K>>$ghg4)dfmAZdD||!-=&=SVvK5bju2v#) zTQ@T@RO)o7pG}8!#IG(TZ0_u?PbhW$R_67cA_sP5Ozq~iRh*e`W!<_y4#uV6{2ra@ ze~|}N3wZOIR`;D0d96QoG$m8VhpLf8arvhZN7Q^$e!PDZ#iAONNu5;Sk7dvkv3G-; zFw)zvS>QlMN+fIvidRVC@v&B$;Dt7eWq4_;O;}`~lMxDBvkwa^C)Sz@UeWxT2))`&u* z1naxn-V_sO8dbn>U=iA0O~hUBxh}dvbhI?z^Y5SQ|4g^PU@Gk!5kmFdR?5kIR|@%l zxwSs~HYpD=Z8HtBZfSA$!tk*}d&-T;j$hfiS6h<^_;RHoJ9otr7HLq;?t+aRd6r6qeyq{_~EWSZ^Q z`qr%ra?`^`0;o5iP^v6oFW-4xK<9yyo9OHTp z(SAa$% zaYi~i{f*~2iZnG<-!X2F%=2)Yr3Y12Qj!5a1|#*4ne}qG3lmnq)^-mnOR_QBHIN=8 zlfL03HM@*z)$zOCdfay)j+{PS+2k zhDXPw9eKy~M&+)qCyBs1F*moDzY+z+YpX9Ey_>SZuj4$fL zUm{o!uy7^LmxmG);@V7+&@0Qp`Z6!yJsJYsK!W@D$|7aVA2%uE$T=Chh=(CB9&JNr zy06phEud~19q~=4tN?nyS&gxAAI08VDqt=g38_9oy9q*tp&MbOh4aP0cpGMtQ-CbY z5MWM;NL_dI$*&oStO#~O3RCR2V|jDNX>fBV$;TQyfLf3(?A_F0{-&>g!}U-Wl;V;3 z?Ea9;?BDEGq5(S`vk4V6k6p#v-vxe%o-^*!@kZJw}Y=Vxwrv8aQ|3Lj$y_mn6u!- z16X!@!6<4>8MBcsU>0oF9`^SNHzLY8Ekq`ZZUH*&?(MV7?wD3}+sqD7CeZ>o+@)R= zW>utLT+@>D{#Kr8E|lxPj!epkHg~}5R7(qJmU4}2BH;7j6^HJY>B9+c`PHYZ1wLGmAgmEm zH$5R^UDb8OqvAoS3jV3dekyNxZoz)+)y2z6JQ7hsp}<$Z_Iv_P-r%s&eP z8!2!5XbgRkt~Qm_Hv;r^e?pZw9wJGUEA-VzXzn^*W6P`6sTuYGp%yMym{kFg?{a45 zCH1NydIvE@M|*_l6#f==EH7c+v~fl^K1t1b!;dd0=UGwL{dB*$9z&LoEJOfic3;}`%&+J(LLEdG_5^dF~b0@xm2HN4^|j!6+HOcTMpUAC8fWjhSOaKxNTEe z^^Bkm`M=liTnwoV5dnZwN3iGCO2KVivrRY3)dK-WAU}}ksN5=gn`AdGD&8Us64Ghd zzAr-iib)`*BXAw#-4s&H@15wa0aZ;l5UMxn_??qeV^wZ#v?{)lI9W2S_FBD;JcZ>l z*AC*$em_-N;7e`!%$US#_PZ)#x$5T$_f6(OfI2;+i@K>p1MtTHLsMsLuj=3NX2Hjw zC_MyR|F`DRK$nk`-qpjOoO-`7XSuv__~Mj@S%c?vl-_nJ&xQNoWSfvg8=ILw1EH2 z^Nws+SS))eX{m@q?DX_J1YDSG4A+y^%g~8#rwS8oe9aHyB2x1dq_bXX^wkE&_P9pd zB0$m90xlxUH$nqV>McOX9&Pf9WR`GOgi`inBC&vf#3FKf;eQyyff01d9Nd>pl*h86 zV{H40HE`p3;$^A5cY6)4jddUN1=yq_L45zK6X|*GT3zAc=7#EkD{e_2$K-RSRYa1E zGv?Y;RDf5z&j7?XMxUHJ+!ZOWxScuWu_5{Kv1KPu0~AUglrR>OAaXQrW?1icl=6u*icC}R`V^5#HU1ds5f00#TI$eL>um$?j$VGc{ly=j+hx-|&oY*AC zC6g9!l7cFiIS&&yyW;RCEm^VKt=E%%)YWWIqyC=iDoX`hw9Anc?1c-K%vewt?%;)3 zx5k?AJ1A#iIL}$~$J!`ua&3)TuIWeIH-_OuskW;mSXBI4KbGT^%41}vG7k%s!QEab zKui4|mfNczuPeEnbFGzyw|Rm35Bi%*1e9rVdNb|_q%rqHivmBmo$%b*+;ap_I)8c~ z=HvV?!?icYfS#&ne1p&(@lP7Ua)+!%(y1g87McyWApVD2`9jGCgPhK?jRIY~avPai z0q9}S|5QsyFJ%yuG;Ov6pQz&k1piHqa)c+5xx&gn1LS}&elielB=}LT@oz-mBr-j3 z!@{});7=mA*y40Kps6)lA&NgaGSF2)dh{1WcRqS8XQ2eWZuT|_Ur!xKzyh?llNP2< z#X;p_H4D4S&Cq|w1g8~sTM?>mE`z<4M8h%LSZ8}<$zM5tG0RwmVm-hal%xWRMz0k) zXzv2`_nJ!;jhNll;2G?e5G?~%{9hVeZ&iZ;6}1yF{e91tCa7q3d{*S-yTAqpd!X0A zaq*#v_cdq=0nrV{`Hw9(CC``iL!4>*;?m&H7F}V!g?WQ5G{mP#U^^&bA46)9oLv@m zq_Z4HgjV8=GK3JK=BstU5?gOfxSrKd%_hI1G+(Im9B{AN3y%OA2x61Way@k6-V*S0 zRnV-pE**fnFDJT9e9<%sg>boXN9&V8zyFotfek9Impi$#e9wWw$WFqRCcQ00o1LAkquTmqI$?db0xp!+0hCt7dtf7d_FGRoR&q7%$2YIR&{ zH)~%FCI%|}J=SG+dhAjF6#l|Uia?_2xllR0M#F<@&pb$U12to^jdlw}BNqlHlV=wI-K+KZ?$4iZR=~+QoK8!gM5=#L z5(Xf83OcBeIN&|* z^GBHT`YJpzF?#N^VI%F(T_fyFjWX&Y#`Pc}4Ti%AY#7aW;SA>)w|p}wj{8+aIb}CB zZo9t*z93^L`*jg9B|NqM{7MH4_{9?6j=!UeDXBQ5vD#^Fe6q#dNOeA7?O`HW?m>&f zNll=bkt`UNkpr(N+<4kzkb=r$6RCx;F%qsPGhlW|t2$}uCk&qRmgA#@hSj^38CC0f zaX5v7ydX@5i+t*I-Y_O2=K{r>6=vq@vl)4aP{5~#A6Zj8IF}`hZqj;G7tY#=L*}2niqG6agglBU2e5yT-3Ba?`GZO_ifGV9|WsH z1ioXo9{Hr~`EOxjwq|`J`}F-{7>3f#G$i5qFE--ZjuUdyHxWkf83($zmYS>KIa4oM z!pgq=CbIK}<4pC-B&U|TQW!{>a{KF4cZ)3YZS`_en;l^K8%8ec^!Y&~GmmU;ritXs z6e=h?8T%W!NxB5Oxh~?fxQbV6Bj~6JZj9Cki+ExqNv&C9|v;h$^kv8M2Qqt}EUNR;!P_SV7Vf7mcy(9^86IK!s z?-U?(^syCYo$+B&OC9<%3yW|M)S7~mk@u8q&BfY^P*yJfO|i9CM_J5!JBhyk6<%xc zev>3hoJ{niRyf)%qr56{LM5cy247U{VvhCrM9bJOSZm;JD*$n$1PR|iQCgS5m-LqM z$s1lxxj3eEV!kI{H$Wo+KwYKC=&9QFPS-{6c)z*yp6W|_pC#O>jz&=hl($SQPY z3HDX)j%Q9y3hjAK&`>Eeo2z5qCqYd@0qCZYoy;Bv?g0lm-3mqUM`eH#w*ON=f2(hj zv;r|R`}^xFBjc`I^ZA{E;LPj9*nmzo#p^J!jIpwr4kR=Oh7~;``T~+4HN;Ei0aaoz zS+5ta+46qB@h9ck^9WrqLnZvJLN$DoZ@2Df*LEr)JqQzlzR3QuHEmSe>}M%s1lIs2 zeBk=6-Ry$fEW^W|QkCieXrpYY-=o!UH>>F-PK_qR^`D!2$oscU&-pUQjkd|X0+$q~ zokjq!-+t-H0#L`L6Lx6kdT25t)1G0$38On3yL~oFDZlukv{^$P)g>5gTW(nAZ0R)T zPscUD=@~emiu^O^vvSKf+;E;jZ_(3#YebiIVCHx1D}kinpel7t=(9^^E2KjRwQ!0+ z=^Ocg>-5yJ{2)$lWe&loh5Y-sA*{)hps&lq6D&kciLyPCN^$LT_fHW471@v&pk{Ru z!kx(kkqi?$aP3`IeQ>KQF?&kxS9^(F9Dj1YD23?*7GZH)Mk;P`y3`e?syvB8y%K-|lEuL=Sq5B%de@icxs)Ka<@l42S@uh#ds26m>6b1Ndwc*(?NY$~@Yt12 z>Ava=zZ1h98^Yw&h{+y4m{B=v;LA}s{?QG&uQMp2NAj_inVkvzr=1t3qX_w=m5P1IlfuM^tTqGG?$B`J;7 zebde9wPFVivldzairCINBsF~N2H5?KZBO2!Ak(R~56m4AH^|a^g_W`QGReQKPj}C* zj&I8}Q-|TSu_2ob?nIbZ#Z}+{wgB?UxB1xM8O5Lv>+W=(T->EVpt!bBc1A4q4_u`M zPTEM_7dZVJIiNv(G7LRjCdYL5@h&E7VNS+4cOp%VdSNaUOUea`5WLmtK2VXLO~Vj0 zAI!xZbNn{=%Lbx%d`#&ImmW;>bbQ?BGkPYqsqafp5^w}9tJ!P`KnFnu8CVUcdgTZv z!!s)Bx!yUfT(;!kz9WHnh83OCByXsOaI%B5_TL7n2Z);CBZR*9-(3A7vWxvq3{H9+ zzmMhTYL^?dcF~xtYIdC9v#Dn8lF6-3#n9PJmnklIw36gm0&CQs<#SU+k-0GnCNpUc zO%r2rG}kPD*eUcJ_D5HrF4394=rG|TGMq3001*H02O$$**KehIL#yKq;nq9nN6sAH zI1WU=EWwG~%6Btz(%$g)$v&FVVzNdAc)rB_8>2uT;^QM-H&`Q}Y<%}Qo!T>YT|!Mm zK8FMg58XXCh}*{0@;-pDiIL-0X+E3-Tlv1gt2(}8;hXj?FL{KRq*ML~4|~gPYd-#d z6f3w0Nds#caQ4ba%GrbaO! zGAlvzXtfan)*z|P&)mj>k-xb$(G|PJOjYsFMXw82kJ&2mhJ?kiSWxw9wD_#yCp=tg zIO+yxA$&Vu8}(kX5Rfky6_(Gh>xSfALpy<*aeS=d)P@_;B23gRHPdMo3RY$w6sByr z;=*UAzWA0Vl2{2>@jU;}{XMb(ko+4bWx|m9d>2GbR1q8Q)_HGw1V;eXT|?BnC3CX6 zp)@%gVPPkors}POZP7Py9{@7*WR*!Qn<`qREi{1_Ify+I(N6KrzXJfE9%Wd@>b>Re zwxzBt)Xc?y1&!Tx7(XaU7KGH>EQ#e|&hgJXjD}`(v@F+W@H@^D0S`U*p{+lM*Qhf~ z*cz`e4RTt^$_$mR@d!IR}DtwZ(_7^rJ1_@3va%V}#jRr)s<;azh_9%m%h{4Toi{kM#W z>+DpJrT#=U#r%J#G2N$^GKueVQL&Wo(@$jYPIa2OUrx^>`Kxuys}#qMrL2F5@M{Srjfb#;54=t!Pdmp$i|u*w+rW+i z1Qv^SO0lGkd}5M5hCCcdGh3;eoHw+|!57B65lPf}aIiozgN)*oJZ;;+p&_`7C&@_K`agFRM6;$8G?Ej|ZLko0F&96j(j4OWdg;(K(c&;$E z3!9e+6cD%cv4=&WSwH&N?#!j!+d|sX_Dz3_JsuX?a0NigeP;4s3#qhr+W5r!bcxxm z4*KCD-N%@&$kjyGp*e?Fir-cA53A&}?YL{lbnG&9`ma0~L=C{G_u5y$>bu(O-xEg( z@hJZ1q2E9oQ_of7k4YFG&X zNDTQc+7Eugi0QC|p~`m&RzA~3^UEOCDePgUS9GHgWdKtgq%w89hlK1Az3P1 zcg<9;HU@pui4EC@ax$bU}3-F=CzflxbRMWc9df?rp$;3Rd&ylWHpU$~EQKzoP zN1gYJ^vJ9UD;Ev+EtY-(|Dn$mR5Snn>qMCzX^L!unsstTX}{8NQ_R1@x373gBy&`h zMZ9of8`>`(+6Cu<9w0Ty;AMP>KLNlP6P;Lrh*TYj?8~6n_l`b?S#K9?0zCPX5j-hh zahER<_n!7({d*hISb(Vh3T`Frw0RzkTqKwT4pT+ZULH#KTUmM;wf~hR%Y{E@?926wQF#+Gv)->9P$lz))BU@pV5lamQ$Sr2b5QgIUwT6q!JiP=T`I!Cr& zUim$?^KMjrHQsu|7`1Vha^f=Ndf)nTJD<^j8Hi7L^p_;@uQvxQp_2kbdb_NhB6}>@ zNWC61t=eHg2musYT8VbH^vL5g>^4SE6l~AGQUjImfWn!EFn1(P6;oQ)mS6$l!)CBo z%Qd@_xW%#I1pO1WKH7tblWsvP9~o_0;aIE~wzNl_coh+V zD+nQ|KUDm z&4wqVRGbZx8zF?SlBqk9d|}SP6?QiX73@^tuOP7qXraF)he`pW@G<)rMYoOnW1n-~ zVeNxZfQTh34dI-h*ioC*d~PP$n1r4j=)cVU2zw}^aokxs=sE57tzWk*+<0`F=k0-2epIz=I?Eov4hHx9hC3|Z3`1S{bUhV_3mt+hxyiYz zPCnk-Bki{0?O7atUr@|(zdNL(#?~+GCM0uBT3Ge$fnhPR5mXHi9 zd`MXmr*qrn5xndEWdS68LhE26CA%ui9A<)5M}Nd%*xHWgb|z2Nt=bbMcFzG`v@&HA zr)0}`L_JpC4>##vVBZhNYxmV$)_DiizBq6BijZkXcHREqVP`ZD5Jym%e)z97GxA>E z8CqxgK{sL$d8MhPen~{3qrC`bZngl$9^#&_%EcsFA*_}O&o;R0liwDyG9uZhDUdE$ zdRLKrofy+!97*m(Ik5{OnJio%oKKjIbE4@|{x|3N)5>fNp>(dDagm zb-po?QB6Kf>BRh13PxExgX&MB&_-*|)hG?I*q);o*AE$J?U`w@LQ2EI2;HH4^0#li zdeZH#hUbeZk9d+)HcRrvk0Fz8)%nHtzswASBE%RlW&~^~F=(Twwl%ux`_OpI`G(5K z#960NGZFkIz^Vvd#jtkz)J5aa&n;6*a%TS8nZOA*AGsPtb<OXc-Z9DUU_O7{q7t$#cGdQ23Tl_=)Wb+gU;x>B!Bi zQlT)#W`#O=p5iBBK3i*)a?^fm4{e7We3JGWrmiS!Y!t#JJPZyfFSiwt2<;*cia<{E zyE6B6=)*MQ^-S!K5AKNVX+JGoGv!m4^lW=Kft9u#KoiOSjGnHdLu0fKy$qq>_?SQW z8nFA=&LPZ8z+ojU%48L~ru8azmrM#K0&#<{im>Z)dOO^mZih>asEp#>-aUy@H=y&wm+~6)i`&ndr{^%~Q(>jDRmphOM zB5XQER{rZd^vX3bCGcCf#h~Jt~8QIR-w(7uu+=Iud@A%6Ur9avesc4)v1Y!TvBrzJ|~cB zc0rits7b87siFsmMas~EK6!%N0GP6`0Uh%T%E7O~v_o+A=BhUSH=CSpYn>cV`Q^t2 zZJdPm+yorhrr}O>6|w?vVsTRD-RJ9%TyDj;BK`@p6*z#+It`RTvVLlJmb&B~!#{`x zr}42m=fOnEE>GvI!i&S_GM(@-!&MKam*TKJYkbBYbX6guuah=VIXCQ8(*7vaB))$A zW>3HwAZ~Vv02};{3e%cUnCE?J5lt`L`gCrHYz(FZgITZKu}!lv1>vN%YG6jeU1;4b zR#A^>0l*nBBe|cK8e7yq!FyXI=*Y4jb{*+4bP)GnwQ;ET1go6pZJMenDEW#7r-SgL z?baz;GfJZJh?*BiAxH2Y)7>k$DCQElrQaSDNwdt3BR^5o|Wygvsk6W`u6JP zXSu*;SXPm3Y~i~85J`Zc=WW{AsOrK;Qj%r?I}^&^_-E=voIqE1@@{{rPBcnR7{|u# z@a-rCat>&icAAx4X5_j(K>$0#x)p%liJRA#Sz;@>c$6S=zvjWt+}|lrk)|OAR{ID& z)?q8ue$JG?%TyCyV=?!@`OM|`MzS%d_!A=t#JxC|h$oSTrV%JS_BTcld*;{m^MvSP zm8!}XE45~nsVH4RAVtmLXr_x{(P|mJoM0pK#{OMU>*{0KZQ5f=*@>vyUg>q>2?lE= z*aIi80ZM*QC_z9djx-DH>}t&QfHu>z$X?%%CxLa!KzA4vv3oCLvRa0GF56tdM!oCh zo(v)$(KDO)W(u*}rT#Kd@2w%e!1(8|=T(xky7FSK590))corn_(Jjeg#jj)Tl|&%F z4v2{u z2Zl|CLSH|{(CD@m?Qt;W8ot*N-a6Byg;UhQ`Ot>Ez!Y+!^BGamN68oaoXA1ckfSOo zx7I1Y^*13%cOq=N?3vg=ct_AUx}i#lvz4jlAk^|`T(90wPJ1~q=Nmod6Z0uLl*PA> zDi0Z|%FH<``GG70vBY&KbE=m9S59_K5+_qM(cpcjv*YJ^n=OKkX_k@eVhYMc6ru>I zs^x_!W^2UtLKEaVnAvk0JE~|7%fdHg=y4M_s&ni#QlCvcI*Nqzjv;n&h!i!*MfUY~ zYDHvm;NIZ)U=_^E6*Zko-Che3ByQGw7tsxD+AlqG%D$iD^T5>y{qf>(uh87fvpt4) z$@q2-1fOOV_N4k?9%=FLarwH9AMhm?lQCkH#T655V)`?t0e82iKguX`ylpEa%A_PA zaG)FNT&{-9Z7N8KWKAhkXnO86xTA%F(HJU9pQKAP2|z|JH)i$v35>rsqKgGBM-B&StnIh*n!8B7R;y|C)`b--G_-22heo z?VbI{Q!c6Plh?jqV9-dUd;aIOES;=z|B^&lKkYkp*Ng8pO7{4T-+Uf_R737kkz4n? zBA6JQm40u~Ka8_w)wo|IKWe4yf0tON*tm_lm^5i+TO>$c9Kc~<%dcnWCnoK{6^E|n X0>fq0N59z|J}Dvcu~?zrJy-p`Rtwt4 diff --git a/specs2/shared/src/main/scala/com/softwaremill/diffx/specs2/DiffMatcher.scala b/specs2/src/main/scala/com/softwaremill/diffx/specs2/DiffMatcher.scala similarity index 100% rename from specs2/shared/src/main/scala/com/softwaremill/diffx/specs2/DiffMatcher.scala rename to specs2/src/main/scala/com/softwaremill/diffx/specs2/DiffMatcher.scala diff --git a/specs2/shared/src/test/scala/com/softwaremill/diffx/specs2/DiffMatcherTest.scala b/specs2/src/test/scala/com/softwaremill/diffx/specs2/DiffMatcherTest.scala similarity index 100% rename from specs2/shared/src/test/scala/com/softwaremill/diffx/specs2/DiffMatcherTest.scala rename to specs2/src/test/scala/com/softwaremill/diffx/specs2/DiffMatcherTest.scala diff --git a/tagging/shared/src/main/scala/com/softwaremill/diffx/tagging/DiffTaggingSupport.scala b/tagging/src/main/scala/com/softwaremill/diffx/tagging/DiffTaggingSupport.scala similarity index 100% rename from tagging/shared/src/main/scala/com/softwaremill/diffx/tagging/DiffTaggingSupport.scala rename to tagging/src/main/scala/com/softwaremill/diffx/tagging/DiffTaggingSupport.scala diff --git a/tagging/shared/src/main/scala/com/softwaremill/diffx/tagging/package.scala b/tagging/src/main/scala/com/softwaremill/diffx/tagging/package.scala similarity index 100% rename from tagging/shared/src/main/scala/com/softwaremill/diffx/tagging/package.scala rename to tagging/src/main/scala/com/softwaremill/diffx/tagging/package.scala diff --git a/tagging/shared/src/test/scala/com/softwaremill/diffx/tagging/test/DiffTaggingSupportTest.scala b/tagging/src/test/scala/com/softwaremill/diffx/tagging/test/DiffTaggingSupportTest.scala similarity index 100% rename from tagging/shared/src/test/scala/com/softwaremill/diffx/tagging/test/DiffTaggingSupportTest.scala rename to tagging/src/test/scala/com/softwaremill/diffx/tagging/test/DiffTaggingSupportTest.scala diff --git a/utest/shared/src/main/scala/com/softwaremill/diffx/utest/DiffxAssertions.scala b/utest/src/main/scala/com/softwaremill/diffx/utest/DiffxAssertions.scala similarity index 100% rename from utest/shared/src/main/scala/com/softwaremill/diffx/utest/DiffxAssertions.scala rename to utest/src/main/scala/com/softwaremill/diffx/utest/DiffxAssertions.scala diff --git a/utest/shared/src/test/scala/com/softwaremill/diffx/utest/UtestAssertTest.scala b/utest/src/test/scala/com/softwaremill/diffx/utest/UtestAssertTest.scala similarity index 100% rename from utest/shared/src/test/scala/com/softwaremill/diffx/utest/UtestAssertTest.scala rename to utest/src/test/scala/com/softwaremill/diffx/utest/UtestAssertTest.scala diff --git a/version.sbt b/version.sbt deleted file mode 100644 index e614bb44..00000000 --- a/version.sbt +++ /dev/null @@ -1 +0,0 @@ -version in ThisBuild := "0.4.6-SNAPSHOT" From 72d0ffcd941baeb4aa9f4d026619e7ed4cdeb754 Mon Sep 17 00:00:00 2001 From: Kasper Kondzielski Date: Fri, 11 Jun 2021 10:46:02 +0200 Subject: [PATCH 079/112] Update build status badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bcd0c531..1a8270cb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![diffx](https://github.com/softwaremill/diffx/raw/master/banner.png) -[![Build Status](https://travis-ci.org/softwaremill/diffx.svg?branch=master)](https://travis-ci.org/softwaremill/diffx) +[![CI](https://github.com/softwaremill/diffx/workflows/CI/badge.svg)](https://github.com/softwaremill/diffx/actions?query=workflow%3A%22CI%22) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.diffx/diffx-core_2.13/badge.svg)](https://search.maven.org/search?q=g:com.softwaremill.diffx) [![Gitter](https://badges.gitter.im/softwaremill/diffx.svg)](https://gitter.im/softwaremill/diffx?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Mergify Status](https://img.shields.io/endpoint.svg?url=https://gh.mergify.io/badges/softwaremill/diffx&style=flat)](https://mergify.io) From 74e55fc487ee01b390e36d863cbd229aca7633d4 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Fri, 11 Jun 2021 12:39:37 +0200 Subject: [PATCH 080/112] Update scala-java-time to 2.3.0 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 72268a4c..dcf85828 100644 --- a/build.sbt +++ b/build.sbt @@ -56,7 +56,7 @@ lazy val core = (projectMatrix in file("core")) "org.scalatest" %%% "scalatest-flatspec" % scalatestVersion % Test, "org.scalatest" %%% "scalatest-freespec" % scalatestVersion % Test, "org.scalatest" %%% "scalatest-shouldmatchers" % scalatestVersion % Test, - "io.github.cquiroz" %%% "scala-java-time" % "2.2.2" % Test + "io.github.cquiroz" %%% "scala-java-time" % "2.3.0" % Test ), versionSpecificScalaSources ) From ce51f5aea5d534102e1f34bae3bc14ee9597440c Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Fri, 11 Jun 2021 12:39:53 +0200 Subject: [PATCH 081/112] Update scala-library, scala-reflect to 2.12.14 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 72268a4c..da240686 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ import com.softwaremill.UpdateVersionInDocs import sbt.Def import sbt.Reference.display -val scala212 = "2.12.13" +val scala212 = "2.12.14" val scala213 = "2.13.6" val scalaIdeaVersion = scala212 // the version for which to import sources into intellij From d5698aa7a607d9961ce9b02274872af5995d060c Mon Sep 17 00:00:00 2001 From: Kasper Kondzielski Date: Fri, 11 Jun 2021 13:06:05 +0200 Subject: [PATCH 082/112] Update mergify to wait for gha --- .mergify.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index 54e87096..2e542012 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -6,7 +6,7 @@ pull_request_rules: - name: automatic merge for scala-steward pull requests affecting build.sbt conditions: - author=scala-steward - - status-success=continuous-integration/travis-ci/pr + - check-success=build - "#files=1" - files=build.sbt actions: @@ -15,7 +15,7 @@ pull_request_rules: - name: automatic merge for scala-steward pull requests affecting project plugins.sbt conditions: - author=scala-steward - - status-success=continuous-integration/travis-ci/pr + - check-success=build - "#files=1" - files=project/plugins.sbt actions: @@ -24,7 +24,7 @@ pull_request_rules: - name: semi-automatic merge for scala-steward pull requests conditions: - author=scala-steward - - status-success=continuous-integration/travis-ci/pr + - check-success=build - "#approved-reviews-by>=1" actions: merge: @@ -32,7 +32,7 @@ pull_request_rules: - name: automatic merge for scala-steward pull requests affecting project build.properties conditions: - author=scala-steward - - status-success=continuous-integration/travis-ci/pr + - check-success=build - "#files=1" - files=project/build.properties actions: @@ -41,7 +41,7 @@ pull_request_rules: - name: automatic merge for scala-steward pull requests affecting .scalafmt.conf conditions: - author=scala-steward - - status-success=continuous-integration/travis-ci/pr + - check-success=build - "#files=1" - files=.scalafmt.conf actions: From 68096fe58f5aec97bbc7131af7b6f63b79e82586 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Fri, 11 Jun 2021 21:25:32 +0200 Subject: [PATCH 083/112] Add dependabot.yaml --- .github/dependabot.yaml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/dependabot.yaml diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 00000000..583decfd --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,7 @@ +version: 2 +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" \ No newline at end of file From 12970b2583c713cfa2bf53ad774cc9a83f8a50b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Jun 2021 19:25:56 +0000 Subject: [PATCH 084/112] Bump olafurpg/setup-scala from 10 to 12 Bumps [olafurpg/setup-scala](https://github.com/olafurpg/setup-scala) from 10 to 12. - [Release notes](https://github.com/olafurpg/setup-scala/releases) - [Commits](https://github.com/olafurpg/setup-scala/compare/v10...v12) --- updated-dependencies: - dependency-name: olafurpg/setup-scala dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/main.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index f2f534cd..302c9988 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: coursier/cache-action@v5 - - uses: olafurpg/setup-scala@v10 + - uses: olafurpg/setup-scala@v12 with: java-version: adopt@1.11 - name: Run tests with sbt @@ -33,7 +33,7 @@ jobs: with: fetch-depth: 0 - uses: coursier/cache-action@v5 - - uses: olafurpg/setup-scala@v10 + - uses: olafurpg/setup-scala@v12 - run: sbt ci-release env: PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} From aca1dbac8efc085ed978d18c0ff7d99f895f3022 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Fri, 11 Jun 2021 21:33:04 +0200 Subject: [PATCH 085/112] Add convenient method for creation of object matchers This fixes #253 --- .../scala/com/softwaremill/diffx/ObjectMatcher.scala | 5 +++++ .../scala/com/softwaremill/diffx/test/DiffTest.scala | 10 +++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala b/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala index 57da2980..d020f605 100644 --- a/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala +++ b/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala @@ -8,5 +8,10 @@ trait ObjectMatcher[T] { } object ObjectMatcher { + def apply[T: ObjectMatcher]: ObjectMatcher[T] = implicitly[ObjectMatcher[T]] + implicit def default[T]: ObjectMatcher[T] = (l: T, r: T) => l == r + + def by[T, U: ObjectMatcher](f: T => U): ObjectMatcher[T] = (left: T, right: T) => + ObjectMatcher[U].isSameObject(f(left), f(right)) } diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala index 08d7ff5e..a90732e5 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala @@ -278,7 +278,7 @@ class DiffTest extends AnyFreeSpec with Matchers { "compare lists using set-like comparator" in { val o1 = Organization(List(p1, p2)) val o2 = Organization(List(p2, p1)) - implicit val om: ObjectMatcher[Person] = (left: Person, right: Person) => left.name == right.name + implicit val om: ObjectMatcher[Person] = ObjectMatcher.by(_.name) implicit val dd: Diff[List[Person]] = Diff[Set[Person]].contramap(_.toSet) compare(o1, o2) shouldBe Identical(Organization(List(p1, p2))) } @@ -321,7 +321,7 @@ class DiffTest extends AnyFreeSpec with Matchers { "ignored fields from elements" in { val p2m = p2.copy(age = 33, in = Instant.now()) implicit val d: Diff[Person] = Derived[Diff[Person]].ignoreUnsafe("age") - implicit val im: ObjectMatcher[Person] = (left: Person, right: Person) => left.name == right.name + implicit val im: ObjectMatcher[Person] = ObjectMatcher.by(_.name) compare(Set(p1, p2), Set(p1, p2m)) shouldBe DiffResultSet( List( Identical(p1), @@ -357,7 +357,7 @@ class DiffTest extends AnyFreeSpec with Matchers { "propagate ignore fields to elements" in { val p2m = p2.copy(in = Instant.now()) - implicit val im: ObjectMatcher[Person] = (left: Person, right: Person) => left.name == right.name + implicit val im: ObjectMatcher[Person] = ObjectMatcher.by(_.name) implicit val ds: Diff[Person] = Derived[Diff[Person]].ignoreUnsafe("age") compare(Set(p1, p2), Set(p1, p2m)) shouldBe DiffResultSet( List( @@ -388,7 +388,7 @@ class DiffTest extends AnyFreeSpec with Matchers { "set of products using instance matcher" in { val p2m = p2.copy(age = 33) - implicit val im: ObjectMatcher[Person] = (left: Person, right: Person) => left.name == right.name + implicit val im: ObjectMatcher[Person] = ObjectMatcher.by(_.name) compare(Set(p1, p2), Set(p1, p2m)) shouldBe DiffResultSet( List( Identical(p1), @@ -464,7 +464,7 @@ class DiffTest extends AnyFreeSpec with Matchers { } "match values using object mapper" in { - implicit val om: ObjectMatcher[KeyModel] = (left: KeyModel, right: KeyModel) => left.name == right.name + implicit val om: ObjectMatcher[KeyModel] = ObjectMatcher.by(_.name) val uuid1 = UUID.randomUUID() val uuid2 = UUID.randomUUID() val a1 = MyLookup(Map(KeyModel(uuid1, "k1") -> "val1")) From 794ffda524bb0fb976010017afd0057b1a3cccd9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Jun 2021 20:03:16 +0000 Subject: [PATCH 086/112] Bump coursier/cache-action from 5 to 6.1 Bumps [coursier/cache-action](https://github.com/coursier/cache-action) from 5 to 6.1. - [Release notes](https://github.com/coursier/cache-action/releases) - [Commits](https://github.com/coursier/cache-action/compare/v5...v6.1) --- updated-dependencies: - dependency-name: coursier/cache-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/main.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 302c9988..cec8a40b 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -14,7 +14,7 @@ jobs: JAVA_OPTS: -Xmx4G steps: - uses: actions/checkout@v2 - - uses: coursier/cache-action@v5 + - uses: coursier/cache-action@v6.1 - uses: olafurpg/setup-scala@v12 with: java-version: adopt@1.11 @@ -32,7 +32,7 @@ jobs: - uses: actions/checkout@v2.3.4 with: fetch-depth: 0 - - uses: coursier/cache-action@v5 + - uses: coursier/cache-action@v6.1 - uses: olafurpg/setup-scala@v12 - run: sbt ci-release env: From 8fb169ec5a3410a21cd6659cd666691011e8190d Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Sun, 13 Jun 2021 10:42:04 +0200 Subject: [PATCH 087/112] Update specs2-core to 4.12.1 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index e12f4c2f..fc14f474 100644 --- a/build.sbt +++ b/build.sbt @@ -8,7 +8,7 @@ val scala213 = "2.13.6" val scalaIdeaVersion = scala212 // the version for which to import sources into intellij val scalatestVersion = "3.2.9" -val specs2Version = "4.12.0" +val specs2Version = "4.12.1" val smlTaggingVersion = "2.3.0" lazy val commonSettings: Seq[Def.Setting[_]] = commonSmlBuildSettings ++ ossPublishSettings ++ Seq( From 4bbd0f4eba80bbc4e6a59a3b0842fa07ef1dbe8f Mon Sep 17 00:00:00 2001 From: Kasper Kondzielski Date: Thu, 17 Jun 2021 09:00:04 +0200 Subject: [PATCH 088/112] Allow for arbitrary modification of derived diffs (#250) * Allow for arbitrary modification of derived diffs * Renames * Remove println * Improve quality * Fix after rebase * Introduce quicklens like syntax * Restore previous way of ignoring * Allow numeric types comparison with some approximation --- .../diffx/TupleInstances.scala.template | 2 +- .../scala/com/softwaremill/diffx/Diff.scala | 40 ++++++++---- .../com/softwaremill/diffx/DiffContext.scala | 63 +++++++++++++++++++ .../com/softwaremill/diffx/DiffResult.scala | 1 + .../com/softwaremill/diffx/Matching.scala | 4 +- .../{IgnoreMacro.scala => ModifyMacro.scala} | 59 ++++++++++++----- .../softwaremill/diffx/TupleInstances.scala | 42 ++++++------- .../generic/DiffMagnoliaDerivation.scala | 19 +++--- .../instances/ApproximateDiffForNumeric.scala | 14 +++++ .../diffx/instances/DiffForEither.scala | 8 +-- .../diffx/instances/DiffForIterable.scala | 5 +- .../diffx/instances/DiffForMap.scala | 10 +-- .../diffx/instances/DiffForNumeric.scala | 2 +- .../diffx/instances/DiffForOption.scala | 4 +- .../diffx/instances/DiffForSet.scala | 6 +- .../diffx/instances/DiffForString.scala | 2 +- .../diffx/test/DiffIgnoreIntTest.scala | 8 ++- .../softwaremill/diffx/test/DiffTest.scala | 31 +++++---- .../diffx/test/IgnoreMacroTest.scala | 28 --------- .../diffx/test/ModifyMacroTest.scala | 28 +++++++++ 20 files changed, 251 insertions(+), 125 deletions(-) create mode 100644 core/src/main/scala/com/softwaremill/diffx/DiffContext.scala rename core/src/main/scala/com/softwaremill/diffx/{IgnoreMacro.scala => ModifyMacro.scala} (57%) create mode 100644 core/src/main/scala/com/softwaremill/diffx/instances/ApproximateDiffForNumeric.scala delete mode 100644 core/src/test/scala/com/softwaremill/diffx/test/IgnoreMacroTest.scala create mode 100644 core/src/test/scala/com/softwaremill/diffx/test/ModifyMacroTest.scala diff --git a/core/src/main/boilerplate/com/softwaremill/diffx/TupleInstances.scala.template b/core/src/main/boilerplate/com/softwaremill/diffx/TupleInstances.scala.template index 090dd612..91a5ae65 100644 --- a/core/src/main/boilerplate/com/softwaremill/diffx/TupleInstances.scala.template +++ b/core/src/main/boilerplate/com/softwaremill/diffx/TupleInstances.scala.template @@ -6,7 +6,7 @@ trait TupleInstances { override def apply( left: ([#T1#]), right: ([#T1#]), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List([#"_1" -> d1.apply(left._1, right._1)#]).toMap if (results.values.forall(_.isIdentical)) { diff --git a/core/src/main/scala/com/softwaremill/diffx/Diff.scala b/core/src/main/scala/com/softwaremill/diffx/Diff.scala index 9114d2d5..e3e10b2a 100644 --- a/core/src/main/scala/com/softwaremill/diffx/Diff.scala +++ b/core/src/main/scala/com/softwaremill/diffx/Diff.scala @@ -1,36 +1,40 @@ package com.softwaremill.diffx import com.softwaremill.diffx.generic.{DiffMagnoliaDerivation, MagnoliaDerivedMacro} import com.softwaremill.diffx.instances._ -import magnolia.Magnolia trait Diff[-T] { outer => - def apply(left: T, right: T): DiffResult = apply(left, right, Nil) - def apply(left: T, right: T, toIgnore: List[FieldPath]): DiffResult + def apply(left: T, right: T): DiffResult = apply(left, right, DiffContext.Empty) + def apply(left: T, right: T, context: DiffContext): DiffResult def contramap[R](f: R => T): Diff[R] = - (left: R, right: R, toIgnore: List[FieldPath]) => { - outer(f(left), f(right), toIgnore) + (left: R, right: R, context: DiffContext) => { + outer(f(left), f(right), context) } - def ignore[S <: T, U](path: S => U): Diff[S] = macro IgnoreMacro.ignoreMacro[S, U] + def modify[S <: T, U](path: S => U): DiffLens[S, U] = macro ModifyMacro.modifyMacro[S, U] + def ignore[S <: T, U](path: S => U): Diff[S] = macro ModifyMacro.ignoreMacro[S, U] - def ignoreUnsafe(fields: String*): Diff[T] = + def modifyUnsafe(path: String*)(diff: Diff[_]): Diff[T] = new Diff[T] { - override def apply(left: T, right: T, toIgnore: List[FieldPath]): DiffResult = - outer.apply(left, right, toIgnore ++ List(fields.toList)) + override def apply(left: T, right: T, context: DiffContext): DiffResult = + outer.apply(left, right, context.merge(DiffContext(Tree.fromList(path.toList, diff), List.empty))) } } object Diff extends MiddlePriorityDiff with TupleInstances { def apply[T: Diff]: Diff[T] = implicitly[Diff[T]] - def identical[T]: Diff[T] = (left: T, _: T, _: List[FieldPath]) => Identical(left) + def identical[T]: Diff[T] = (left: T, _: T, _: DiffContext) => Identical(left) + def ignored[T]: Diff[T] = (_: T, _: T, _: DiffContext) => DiffResult.Ignored def compare[T: Diff](left: T, right: T): DiffResult = apply[T].apply(left, right) /** Create a Diff instance using [[Object#equals]] */ def useEquals[T]: Diff[T] = Diff.fallback[T] + def approximateNumericDiff[T: Numeric](epsilon: T): Diff[T] = + new ApproximateDiffForNumeric[T](epsilon) + def derived[T]: Derived[Diff[T]] = macro MagnoliaDerivedMacro.derivedGen[T] implicit val diffForString: Diff[String] = new DiffForString @@ -67,7 +71,8 @@ trait LowPriorityDiff { implicit class RichDerivedDiff[T](val dd: Derived[Diff[T]]) { def contramap[R](f: R => T): Derived[Diff[R]] = Derived(dd.value.contramap(f)) - def ignore[S <: T, U](path: S => U): Derived[Diff[S]] = macro IgnoreMacro.derivedIgnoreMacro[S, U] + def modify[S <: T, U](path: S => U): DerivedDiffLens[S, U] = macro ModifyMacro.derivedModifyMacro[S, U] + def ignore[S <: T, U](path: S => U): Derived[Diff[S]] = macro ModifyMacro.derivedIgnoreMacro[S, U] } } @@ -76,3 +81,16 @@ case class Derived[T](value: T) object Derived { def apply[T: Derived]: Derived[T] = implicitly[Derived[T]] } + +case class DiffLens[T, U](outer: Diff[T], path: List[String]) { + def setTo(d: Diff[U]): Diff[T] = { + outer.modifyUnsafe(path: _*)(d) + } + def ignore(): Diff[T] = outer.modifyUnsafe(path: _*)(Diff.ignored) +} +case class DerivedDiffLens[T, U](outer: Diff[T], path: List[String]) { + def setTo(d: Diff[U]): Derived[Diff[T]] = { + Derived(outer.modifyUnsafe(path: _*)(d)) + } + def ignore(): Derived[Diff[T]] = Derived(outer.modifyUnsafe(path: _*)(Diff.ignored)) +} diff --git a/core/src/main/scala/com/softwaremill/diffx/DiffContext.scala b/core/src/main/scala/com/softwaremill/diffx/DiffContext.scala new file mode 100644 index 00000000..f2b54a7e --- /dev/null +++ b/core/src/main/scala/com/softwaremill/diffx/DiffContext.scala @@ -0,0 +1,63 @@ +package com.softwaremill.diffx + +case class DiffContext(overrides: Tree, path: FieldPath) { + def merge(other: DiffContext): DiffContext = { + DiffContext(overrides.merge(other.overrides), List.empty) + } + + def getOverride(label: String): Option[Diff[_]] = { + overrides match { + case Tree.Leaf(_) => throw new IllegalStateException(s"Expected node, got leaf at $path") + case Tree.Node(tries) => + tries.get(label) match { + case Some(Tree.Leaf(v)) => Some(v) + case _ => None + } + } + } + def getNextStep(label: String): DiffContext = { + overrides match { + case Tree.Leaf(_) => throw new IllegalStateException(s"Expected node, got leaf at $path") + case Tree.Node(tries) => + val currentPath = path :+ label + tries.get(label) match { + case Some(value) => DiffContext(value, currentPath) + case None => DiffContext.Empty + } + } + } +} + +object DiffContext { + val Empty: DiffContext = DiffContext(Tree.Node(Map.empty), List.empty) + def atPath(path: FieldPath, diff: Diff[_]): DiffContext = DiffContext(Tree.fromList(path, diff), List.empty) +} + +sealed trait Tree { + def merge(tree: Tree): Tree +} +object Tree { + case class Leaf(v: Diff[_]) extends Tree { + override def merge(tree: Tree): Tree = tree + } + case class Node(tries: Map[String, Tree]) extends Tree { + override def merge(tree: Tree): Tree = { + tree match { + case Leaf(v) => Leaf(v) + case Node(otherTries) => + val keys = tries.keySet ++ otherTries.keySet + Node(keys.map { k => + k -> ((tries.get(k), otherTries.get(k)) match { + case (Some(t1), Some(t2)) => t1.merge(t2) + case (Some(t1), None) => t1 + case (None, Some(t2)) => t2 + case (None, None) => throw new IllegalStateException("cannot happen") + }) + }.toMap) + } + } + } + def fromList(path: FieldPath, diff: Diff[_]): Tree = { + path.reverse.foldLeft(Leaf(diff): Tree)((acc, item) => Node(Map(item -> acc))) + } +} diff --git a/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala b/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala index e83ad9d9..a6d2bb04 100644 --- a/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala +++ b/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala @@ -13,6 +13,7 @@ trait DiffResult extends Product with Serializable { object DiffResult { private[diffx] final val indentLevel = 5 + val Ignored = Identical("") } case class DiffResultObject(name: String, fields: Map[String, DiffResult]) extends DiffResultDifferent { diff --git a/core/src/main/scala/com/softwaremill/diffx/Matching.scala b/core/src/main/scala/com/softwaremill/diffx/Matching.scala index 8d986c41..666293b8 100644 --- a/core/src/main/scala/com/softwaremill/diffx/Matching.scala +++ b/core/src/main/scala/com/softwaremill/diffx/Matching.scala @@ -6,11 +6,11 @@ private[diffx] object Matching { right: scala.collection.Set[T], matcher: ObjectMatcher[T], diff: Diff[T], - toIgnore: List[FieldPath] + context: DiffContext ): MatchingResults[T] = { val matchedKeys = left.flatMap(l => right.collectFirst { - case r if matcher.isSameObject(l, r) || diff(l, r, toIgnore).isIdentical => l -> r + case r if matcher.isSameObject(l, r) || diff(l, r, context).isIdentical => l -> r } ) MatchingResults(left.diff(matchedKeys.map(_._1)), right.diff(matchedKeys.map(_._2)), matchedKeys) diff --git a/core/src/main/scala/com/softwaremill/diffx/IgnoreMacro.scala b/core/src/main/scala/com/softwaremill/diffx/ModifyMacro.scala similarity index 57% rename from core/src/main/scala/com/softwaremill/diffx/IgnoreMacro.scala rename to core/src/main/scala/com/softwaremill/diffx/ModifyMacro.scala index c32cb44e..c75c83fa 100644 --- a/core/src/main/scala/com/softwaremill/diffx/IgnoreMacro.scala +++ b/core/src/main/scala/com/softwaremill/diffx/ModifyMacro.scala @@ -3,38 +3,62 @@ package com.softwaremill.diffx import scala.annotation.tailrec import scala.reflect.macros.blackbox -object IgnoreMacro { +object ModifyMacro { private val ShapeInfo = "Path must have shape: _.field1.field2.each.field3.(...)" - def derivedIgnoreMacro[T: c.WeakTypeTag, U: c.WeakTypeTag]( + def derivedModifyMacro[T: c.WeakTypeTag, U: c.WeakTypeTag]( c: blackbox.Context - )(path: c.Expr[T => U]): c.Tree = applyDerivedIgnored[T, U](c)(ignoredFromPathMacro(c)(path)) + )(path: c.Expr[T => U]): c.Tree = + applyDerivedModified[T, U](c)(modifiedFromPathMacro(c)(path)) - private def applyDerivedIgnored[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)( + private def applyDerivedModified[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)( path: c.Expr[List[String]] ): c.Tree = { import c.universe._ - q"""{ - com.softwaremill.diffx.Derived(${c.prefix}.dd.value.ignoreUnsafe($path:_*)) - }""" + q"""com.softwaremill.diffx.DerivedDiffLens(${c.prefix}.dd.value, $path)""" } - def ignoreMacro[T: c.WeakTypeTag, U: c.WeakTypeTag]( + def derivedIgnoreMacro[T: c.WeakTypeTag, U: c.WeakTypeTag]( c: blackbox.Context - )(path: c.Expr[T => U]): c.Tree = applyIgnored[T, U](c)(ignoredFromPathMacro(c)(path)) + )(path: c.Expr[T => U]): c.Tree = + applyIgnoredModified[T, U](c)(modifiedFromPathMacro(c)(path)) - private def applyIgnored[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)( + private def applyIgnoredModified[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)( path: c.Expr[List[String]] ): c.Tree = { + import c.universe._ + val lens = applyDerivedModified[T, U](c)(path) + q"""$lens.ignore()""" + } + + def ignoreMacro[T: c.WeakTypeTag, U: c.WeakTypeTag]( + c: blackbox.Context + )(path: c.Expr[T => U]): c.Tree = applyIgnored[T, U](c)(modifiedFromPathMacro(c)(path)) + + private def applyIgnored[T: c.WeakTypeTag, U: c.WeakTypeTag]( + c: blackbox.Context + )(path: c.Expr[List[String]]): c.Tree = { + import c.universe._ + val lens = applyModified[T, U](c)(path) + q"""$lens.ignore()""" + } + + def modifyMacro[T: c.WeakTypeTag, U: c.WeakTypeTag]( + c: blackbox.Context + )(path: c.Expr[T => U]): c.Tree = applyModified[T, U](c)(modifiedFromPathMacro(c)(path)) + + private def applyModified[T: c.WeakTypeTag, U: c.WeakTypeTag]( + c: blackbox.Context + )(path: c.Expr[List[String]]): c.Tree = { import c.universe._ q"""{ - ${c.prefix}.ignoreUnsafe($path:_*) + com.softwaremill.diffx.DiffLens(${c.prefix}, $path) }""" } /** Converts path to list of strings */ - def ignoredFromPathMacro[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)( + def modifiedFromPathMacro[T: c.WeakTypeTag, U: c.WeakTypeTag](c: blackbox.Context)( path: c.Expr[T => U] ): c.Expr[List[String]] = { import c.universe._ @@ -74,10 +98,13 @@ object IgnoreMacro { case _ => c.abort(c.enclosingPosition, s"$ShapeInfo, got: ${path.tree}") } - c.Expr[List[String]](q"${pathEls.collect { case TermPathElement(c) => - c.decodedName.toString - }}") + c.Expr[List[String]]( + q"(${pathEls.collect { case TermPathElement(c) => + c.decodedName.toString + }})" + ) } - private[diffx] def ignoredFromPath[T, U](path: T => U): List[String] = macro ignoredFromPathMacro[T, U] + private[diffx] def modifiedFromPath[T, U](path: T => U): List[String] = + macro modifiedFromPathMacro[T, U] } diff --git a/core/src/main/scala/com/softwaremill/diffx/TupleInstances.scala b/core/src/main/scala/com/softwaremill/diffx/TupleInstances.scala index d0a23159..4ac73bdf 100644 --- a/core/src/main/scala/com/softwaremill/diffx/TupleInstances.scala +++ b/core/src/main/scala/com/softwaremill/diffx/TupleInstances.scala @@ -6,7 +6,7 @@ trait TupleInstances { override def apply( left: (T1, T2), right: (T1, T2), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List("_1" -> d1.apply(left._1, right._1), "_2" -> d2.apply(left._2, right._2)).toMap if (results.values.forall(_.isIdentical)) { @@ -22,7 +22,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3), right: (T1, T2, T3), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), @@ -46,7 +46,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3, T4), right: (T1, T2, T3, T4), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), @@ -72,7 +72,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3, T4, T5), right: (T1, T2, T3, T4, T5), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), @@ -100,7 +100,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3, T4, T5, T6), right: (T1, T2, T3, T4, T5, T6), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), @@ -130,7 +130,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3, T4, T5, T6, T7), right: (T1, T2, T3, T4, T5, T6, T7), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), @@ -162,7 +162,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3, T4, T5, T6, T7, T8), right: (T1, T2, T3, T4, T5, T6, T7, T8), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), @@ -196,7 +196,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3, T4, T5, T6, T7, T8, T9), right: (T1, T2, T3, T4, T5, T6, T7, T8, T9), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), @@ -233,7 +233,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10), right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), @@ -272,7 +272,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11), right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), @@ -313,7 +313,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12), right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), @@ -356,7 +356,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13), right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), @@ -401,7 +401,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14), right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), @@ -448,7 +448,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15), right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), @@ -497,7 +497,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16), right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), @@ -548,7 +548,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17), right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), @@ -601,7 +601,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18), right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), @@ -656,7 +656,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19), right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), @@ -714,7 +714,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20), right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), @@ -774,7 +774,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21), right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), @@ -861,7 +861,7 @@ trait TupleInstances { override def apply( left: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22), right: (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22), - toIgnore: List[_root_.com.softwaremill.diffx.FieldPath] + context: DiffContext ): DiffResult = { val results = List( "_1" -> d1.apply(left._1, right._1), diff --git a/core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala b/core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala index ff9f2d4b..85e4d943 100644 --- a/core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala +++ b/core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala @@ -1,6 +1,6 @@ package com.softwaremill.diffx.generic -import com.softwaremill.diffx.{Diff, DiffResultObject, DiffResultValue, FieldPath, Identical, nullGuard} +import com.softwaremill.diffx.{Diff, DiffContext, DiffResultObject, DiffResultValue, FieldPath, Identical, nullGuard} import magnolia._ import scala.collection.immutable.ListMap @@ -8,18 +8,13 @@ import scala.collection.immutable.ListMap trait DiffMagnoliaDerivation extends LowPriority { type Typeclass[T] = Diff[T] - def combine[T](ctx: ReadOnlyCaseClass[Typeclass, T]): Diff[T] = { (left: T, right: T, toIgnore: List[FieldPath]) => + def combine[T](ctx: ReadOnlyCaseClass[Typeclass, T]): Diff[T] = { (left: T, right: T, context: DiffContext) => nullGuard(left, right) { (left, right) => val map = ListMap(ctx.parameters.map { p => val lType = p.dereference(left) val pType = p.dereference(right) - if (toIgnore.contains(List(p.label))) { - p.label -> Identical(lType) - } else { - val nestedIgnore = - if (toIgnore.exists(_.headOption.exists(h => h == p.label))) toIgnore.map(_.drop(1)) else Nil - p.label -> p.typeclass(lType, pType, nestedIgnore) - } + val fieldDiff = context.getOverride(p.label).map(_.asInstanceOf[Diff[p.PType]]).getOrElse(p.typeclass) + p.label -> fieldDiff(lType, pType, context.getNextStep(p.label)) }: _*) if (map.values.forall(p => p.isIdentical)) { Identical(left) @@ -29,12 +24,12 @@ trait DiffMagnoliaDerivation extends LowPriority { } } - def dispatch[T](ctx: SealedTrait[Typeclass, T]): Diff[T] = { (left: T, right: T, toIgnore: List[FieldPath]) => + def dispatch[T](ctx: SealedTrait[Typeclass, T]): Diff[T] = { (left: T, right: T, context: DiffContext) => nullGuard(left, right) { (left, right) => val lType = ctx.dispatch(left)(a => a) val rType = ctx.dispatch(right)(a => a) if (lType == rType) { - lType.typeclass(lType.cast(left), lType.cast(right), toIgnore) + lType.typeclass(lType.cast(left), lType.cast(right), context) } else { DiffResultValue(lType.typeName.full, rType.typeName.full) } @@ -44,7 +39,7 @@ trait DiffMagnoliaDerivation extends LowPriority { trait LowPriority { def fallback[T]: Diff[T] = - (left: T, right: T, toIgnore: List[FieldPath]) => { + (left: T, right: T, context: DiffContext) => { if (left != right) { DiffResultValue(left, right) } else { diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/ApproximateDiffForNumeric.scala b/core/src/main/scala/com/softwaremill/diffx/instances/ApproximateDiffForNumeric.scala new file mode 100644 index 00000000..5667aa65 --- /dev/null +++ b/core/src/main/scala/com/softwaremill/diffx/instances/ApproximateDiffForNumeric.scala @@ -0,0 +1,14 @@ +package com.softwaremill.diffx.instances + +import com.softwaremill.diffx._ + +private[diffx] class ApproximateDiffForNumeric[T: Numeric](epsilon: T) extends Diff[T] { + override def apply(left: T, right: T, context: DiffContext): DiffResult = { + val numeric = implicitly[Numeric[T]] + if (numeric.lt(numeric.abs(numeric.minus(left, right)), epsilon)) { + DiffResultValue(left, right) + } else { + Identical(left) + } + } +} diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForEither.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForEither.scala index 3c2f07ff..df49d45d 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForEither.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForEither.scala @@ -1,16 +1,16 @@ package com.softwaremill.diffx.instances -import com.softwaremill.diffx.{Diff, DiffResult, DiffResultValue, FieldPath} +import com.softwaremill.diffx.{Diff, DiffContext, DiffResult, DiffResultValue} private[diffx] class DiffForEither[L, R](ld: Diff[L], rd: Diff[R]) extends Diff[Either[L, R]] { override def apply( left: Either[L, R], right: Either[L, R], - toIgnore: List[FieldPath] + context: DiffContext ): DiffResult = { (left, right) match { - case (Left(v1), Left(v2)) => ld.apply(v1, v2, toIgnore) - case (Right(v1), Right(v2)) => rd.apply(v1, v2, toIgnore) + case (Left(v1), Left(v2)) => ld.apply(v1, v2, context) + case (Right(v1), Right(v2)) => rd.apply(v1, v2, context) case (v1, v2) => DiffResultValue(v1, v2) } } diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala index b1a40d15..338f894c 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala @@ -1,16 +1,17 @@ package com.softwaremill.diffx.instances import com.softwaremill.diffx._ + import scala.collection.immutable.ListMap private[diffx] class DiffForIterable[T, C[W] <: Iterable[W]](dot: Diff[Option[T]]) extends Diff[C[T]] { - override def apply(left: C[T], right: C[T], toIgnore: List[FieldPath]): DiffResult = nullGuard(left, right) { + override def apply(left: C[T], right: C[T], context: DiffContext): DiffResult = nullGuard(left, right) { (left, right) => val indexes = Range(0, Math.max(left.size, right.size)) val leftAsMap = left.toList.lift val rightAsMap = right.toList.lift val differences = ListMap(indexes.map { index => - index.toString -> (dot.apply(leftAsMap(index), rightAsMap(index), toIgnore) match { + index.toString -> (dot.apply(leftAsMap(index), rightAsMap(index), context) match { case DiffResultValue(Some(v), None) => DiffResultAdditional(v) case DiffResultValue(None, Some(v)) => DiffResultMissing(v) case d => d diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala index 538950a8..6e1ef2a0 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala @@ -11,13 +11,13 @@ private[diffx] class DiffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]] override def apply( left: C[K, V], right: C[K, V], - toIgnore: List[FieldPath] + context: DiffContext ): DiffResult = nullGuard(left, right) { (left, right) => val MatchingResults(unMatchedLeftKeys, unMatchedRightKeys, matchedKeys) = - matching[K](left.keySet, right.keySet, matcher, diffKey, toIgnore) + matching[K](left.keySet, right.keySet, matcher, diffKey, context) val leftDiffs = this.leftDiffs(left, unMatchedLeftKeys, unMatchedRightKeys) val rightDiffs = this.rightDiffs(right, unMatchedLeftKeys, unMatchedRightKeys) - val matchedDiffs = this.matchedDiffs(matchedKeys, left, right, toIgnore) + val matchedDiffs = this.matchedDiffs(matchedKeys, left, right, context) val diffs = leftDiffs ++ rightDiffs ++ matchedDiffs if (diffs.forall(p => p._1.isIdentical && p._2.isIdentical)) { Identical(left) @@ -30,11 +30,11 @@ private[diffx] class DiffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]] matchedKeys: scala.collection.Set[(K, K)], left: C[K, V], right: C[K, V], - toIgnore: List[FieldPath] + context: DiffContext ): List[(DiffResult, DiffResult)] = { matchedKeys.map { case (lKey, rKey) => val result = diffKey.apply(lKey, rKey) - result -> diffValue.apply(left.get(lKey), right.get(rKey), toIgnore) + result -> diffValue.apply(left.get(lKey), right.get(rKey), context) }.toList } diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForNumeric.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForNumeric.scala index 157c5e05..2789d381 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForNumeric.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForNumeric.scala @@ -3,7 +3,7 @@ package com.softwaremill.diffx.instances import com.softwaremill.diffx._ private[diffx] class DiffForNumeric[T: Numeric] extends Diff[T] { - override def apply(left: T, right: T, toIgnore: List[FieldPath]): DiffResult = { + override def apply(left: T, right: T, context: DiffContext): DiffResult = { val numeric = implicitly[Numeric[T]] if (!numeric.equiv(left, right)) { DiffResultValue(left, right) diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForOption.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForOption.scala index 0542269d..6b356339 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForOption.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForOption.scala @@ -3,9 +3,9 @@ package com.softwaremill.diffx.instances import com.softwaremill.diffx._ private[diffx] class DiffForOption[T](dt: Diff[T]) extends Diff[Option[T]] { - override def apply(left: Option[T], right: Option[T], toIgnore: List[FieldPath]): DiffResult = { + override def apply(left: Option[T], right: Option[T], context: DiffContext): DiffResult = { (left, right) match { - case (Some(l), Some(r)) => dt.apply(l, r, toIgnore) + case (Some(l), Some(r)) => dt.apply(l, r, context) case (None, None) => Identical(None) case (l, r) => DiffResultValue(l, r) } diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala index 64eac2b8..310d1cae 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala @@ -5,10 +5,10 @@ import com.softwaremill.diffx._ private[diffx] class DiffForSet[T, C[W] <: scala.collection.Set[W]](dt: Diff[T], matcher: ObjectMatcher[T]) extends Diff[C[T]] { - override def apply(left: C[T], right: C[T], toIgnore: List[FieldPath]): DiffResult = nullGuard(left, right) { + override def apply(left: C[T], right: C[T], context: DiffContext): DiffResult = nullGuard(left, right) { (left, right) => val MatchingResults(unMatchedLeftInstances, unMatchedRightInstances, matchedInstances) = - matching[T](left.toSet, right.toSet, matcher, dt, toIgnore) + matching[T](left.toSet, right.toSet, matcher, dt, context) val leftDiffs = unMatchedLeftInstances .diff(unMatchedRightInstances) .map(DiffResultAdditional(_)) @@ -17,7 +17,7 @@ private[diffx] class DiffForSet[T, C[W] <: scala.collection.Set[W]](dt: Diff[T], .diff(unMatchedLeftInstances) .map(DiffResultMissing(_)) .toList - val matchedDiffs = matchedInstances.map { case (l, r) => dt(l, r, toIgnore) }.toList + val matchedDiffs = matchedInstances.map { case (l, r) => dt(l, r, context) }.toList diffResultSet(left, leftDiffs, rightDiffs, matchedDiffs) } diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForString.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForString.scala index 0eccd8b2..000d2cd9 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForString.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForString.scala @@ -3,7 +3,7 @@ package com.softwaremill.diffx.instances import com.softwaremill.diffx._ private[diffx] class DiffForString extends Diff[String] { - override def apply(left: String, right: String, toIgnore: List[FieldPath]): DiffResult = nullGuard(left, right) { + override def apply(left: String, right: String, context: DiffContext): DiffResult = nullGuard(left, right) { (left, right) => val leftLines = left.split("\n").toList val rightLines = right.split("\n").toList diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffIgnoreIntTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffIgnoreIntTest.scala index 46b00a9f..176aff4a 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffIgnoreIntTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffIgnoreIntTest.scala @@ -16,7 +16,7 @@ class DiffIgnoreIntTest extends AnyFlatSpec with Matchers { implicit val d: Diff[Person] = Derived[Diff[Person]].ignore(_.name) compare(p1, p2) shouldBe DiffResultObject( "Person", - Map("name" -> Identical("p1"), "age" -> DiffResultValue(22, 11), "in" -> Identical(instant)) + Map("name" -> Identical(""), "age" -> DiffResultValue(22, 11), "in" -> Identical(instant)) ) } @@ -24,12 +24,14 @@ class DiffIgnoreIntTest extends AnyFlatSpec with Matchers { implicit val d: Diff[Person] = Derived[Diff[Person]].ignore(_.name) compare(p1, p2) shouldBe DiffResultObject( "Person", - Map("name" -> Identical("p1"), "age" -> DiffResultValue(22, 11), "in" -> Identical(instant)) + Map("name" -> Identical(""), "age" -> DiffResultValue(22, 11), "in" -> Identical(instant)) ) } it should "allow calling ignore multiple times" in { - implicit val d: Diff[Person] = Derived[Diff[Person]].ignore[Person, String](_.name).ignore[Person, Int](_.age) + implicit val d: Diff[Person] = Derived[Diff[Person]] + .ignore[Person, String](_.name) + .ignore[Person, Int](_.age) compare(p1, p2) shouldBe Identical(p1) } } diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala index a90732e5..c77037c9 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala @@ -68,13 +68,14 @@ class DiffTest extends AnyFreeSpec with Matchers { } "ignoring given fields" in { - implicit val d: Diff[Person] = Derived[Diff[Person]].ignoreUnsafe("name").ignoreUnsafe("age") + implicit val d: Diff[Person] = + Derived[Diff[Person]].modifyUnsafe("name")(Diff.ignored).modifyUnsafe("age")(Diff.ignored) val p3 = p2.copy(in = Instant.now()) compare(p1, p3) shouldBe DiffResultObject( "Person", Map( - "name" -> Identical(p1.name), - "age" -> Identical(p1.age), + "name" -> DiffResult.Ignored, + "age" -> DiffResult.Ignored, "in" -> DiffResultValue(p1.in, p3.in) ) ) @@ -102,7 +103,7 @@ class DiffTest extends AnyFreeSpec with Matchers { "nested products ignoring nested fields" in { val f1 = Family(p1, p2) val f2 = Family(p1, p1) - implicit val d: Diff[Family] = Derived[Diff[Family]].ignoreUnsafe("second", "name") + implicit val d: Diff[Family] = Derived[Diff[Family]].modifyUnsafe("second", "name")(Diff.identical) compare(f1, f2) shouldBe DiffResultObject( "Family", Map( @@ -123,7 +124,7 @@ class DiffTest extends AnyFreeSpec with Matchers { val p1p = p1.copy(name = "other") val f1 = Family(p1, p2) val f2 = Family(p1p, p2.copy(name = "other")) - implicit val d: Diff[Family] = Derived[Diff[Family]].ignoreUnsafe("second", "name") + implicit val d: Diff[Family] = Derived[Diff[Family]].modifyUnsafe("second", "name")(Diff.identical) compare(f1, f2) shouldBe DiffResultObject( "Family", Map( @@ -143,7 +144,7 @@ class DiffTest extends AnyFreeSpec with Matchers { "nested products ignoring nested products" in { val f1 = Family(p1, p2) val f2 = Family(p1, p1) - implicit val d: Diff[Family] = Derived[Diff[Family]].ignoreUnsafe("second") + implicit val d: Diff[Family] = Derived[Diff[Family]].modifyUnsafe("second")(Diff.identical) compare(f1, f2) shouldBe Identical(f1) } @@ -252,7 +253,7 @@ class DiffTest extends AnyFreeSpec with Matchers { "use ignored fields from elements" in { val o1 = Organization(List(p1, p2)) val o2 = Organization(List(p1, p1, p1)) - implicit val d: Diff[Organization] = Derived[Diff[Organization]].ignoreUnsafe("people", "name") + implicit val d: Diff[Organization] = Derived[Diff[Organization]].modifyUnsafe("people", "name")(Diff.identical) compare(o1, o2) shouldBe DiffResultObject( "Organization", Map( @@ -320,7 +321,7 @@ class DiffTest extends AnyFreeSpec with Matchers { } "ignored fields from elements" in { val p2m = p2.copy(age = 33, in = Instant.now()) - implicit val d: Diff[Person] = Derived[Diff[Person]].ignoreUnsafe("age") + implicit val d: Diff[Person] = Derived[Diff[Person]].modifyUnsafe("age")(Diff.identical) implicit val im: ObjectMatcher[Person] = ObjectMatcher.by(_.name) compare(Set(p1, p2), Set(p1, p2m)) shouldBe DiffResultSet( List( @@ -351,14 +352,16 @@ class DiffTest extends AnyFreeSpec with Matchers { "identical when products are identical using ignored" in { val p2m = p2.copy(age = 33, in = Instant.now()) - implicit val d: Diff[Person] = Derived[Diff[Person]].ignoreUnsafe("age").ignoreUnsafe("in") + implicit val d: Diff[Person] = Derived[Diff[Person]] + .modifyUnsafe("age")(Diff.identical) + .modifyUnsafe("in")(Diff.identical) compare(Set(p1, p2), Set(p1, p2m)) shouldBe Identical(Set(p1, p2)) } "propagate ignore fields to elements" in { val p2m = p2.copy(in = Instant.now()) implicit val im: ObjectMatcher[Person] = ObjectMatcher.by(_.name) - implicit val ds: Diff[Person] = Derived[Diff[Person]].ignoreUnsafe("age") + implicit val ds: Diff[Person] = Derived[Diff[Person]].modifyUnsafe("age")(Diff.identical) compare(Set(p1, p2), Set(p1, p2m)) shouldBe DiffResultSet( List( Identical(p1), @@ -382,7 +385,7 @@ class DiffTest extends AnyFreeSpec with Matchers { "override set instance" in { val p2m = p2.copy(age = 33) implicit def setDiff[T, C[W] <: scala.collection.Set[W]]: Diff[C[T]] = - (left: C[T], _: C[T], _: List[Any]) => Identical(left) + (left: C[T], _: C[T], _: DiffContext) => Identical(left) compare(Set(p1, p2), Set(p1, p2m)) shouldBe an[Identical[_]] } @@ -423,7 +426,7 @@ class DiffTest extends AnyFreeSpec with Matchers { } "propagate ignored fields to elements" in { - implicit val dm: Diff[Person] = Derived[Diff[Person]].ignoreUnsafe("age") + implicit val dm: Diff[Person] = Derived[Diff[Person]].modifyUnsafe("age")(Diff.identical) compare(Map("first" -> p1), Map("first" -> p2)) shouldBe DiffResultMap( Map( Identical("first") -> DiffResultObject( @@ -440,7 +443,9 @@ class DiffTest extends AnyFreeSpec with Matchers { "identical when products are identical using ignore" in { implicit val dm: Diff[Person] = - Derived[Diff[Person]].ignoreUnsafe("age").ignoreUnsafe("name") + Derived[Diff[Person]] + .modifyUnsafe("age")(Diff.identical) + .modifyUnsafe("name")(Diff.identical) compare(Map("first" -> p1), Map("first" -> p2)) shouldBe Identical(Map("first" -> p1)) } diff --git a/core/src/test/scala/com/softwaremill/diffx/test/IgnoreMacroTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/IgnoreMacroTest.scala deleted file mode 100644 index a6a5ab48..00000000 --- a/core/src/test/scala/com/softwaremill/diffx/test/IgnoreMacroTest.scala +++ /dev/null @@ -1,28 +0,0 @@ -package com.softwaremill.diffx.test - -import com.softwaremill.diffx.IgnoreMacro -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -class IgnoreMacroTest extends AnyFlatSpec with Matchers { - "IgnoreMacroTest" should "ignore field in nested products" in { - IgnoreMacro.ignoredFromPath[Family, String](_.first.name) shouldBe List("first", "name") - } - - it should "ignore fields in list of products" in { - IgnoreMacro.ignoredFromPath[Organization, String](_.people.each.name) shouldBe List("people", "name") - } - - it should "ignore fields in product wrapped with either" in { - IgnoreMacro.ignoredFromPath[Either[Person, Person], String](_.eachRight.name) shouldBe List("name") - IgnoreMacro.ignoredFromPath[Either[Person, Person], String](_.eachLeft.name) shouldBe List("name") - } - - it should "ignore fields in product wrapped with option" in { - IgnoreMacro.ignoredFromPath[Option[Person], String](_.each.name) shouldBe List("name") - } - - it should "ignore fields in map of products" in { - IgnoreMacro.ignoredFromPath[Map[String, Person], String](_.each.name) shouldBe List("name") - } -} diff --git a/core/src/test/scala/com/softwaremill/diffx/test/ModifyMacroTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/ModifyMacroTest.scala new file mode 100644 index 00000000..bcce2d71 --- /dev/null +++ b/core/src/test/scala/com/softwaremill/diffx/test/ModifyMacroTest.scala @@ -0,0 +1,28 @@ +package com.softwaremill.diffx.test + +import com.softwaremill.diffx.ModifyMacro +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class ModifyMacroTest extends AnyFlatSpec with Matchers { + it should "ignore field in nested products" in { + ModifyMacro.modifiedFromPath[Family, String](_.first.name) shouldBe List("first", "name") + } + + it should "ignore fields in list of products" in { + ModifyMacro.modifiedFromPath[Organization, String](_.people.each.name) shouldBe List("people", "name") + } + + it should "ignore fields in product wrapped with either" in { + ModifyMacro.modifiedFromPath[Either[Person, Person], String](_.eachRight.name) shouldBe List("name") + ModifyMacro.modifiedFromPath[Either[Person, Person], String](_.eachLeft.name) shouldBe List("name") + } + + it should "ignore fields in product wrapped with option" in { + ModifyMacro.modifiedFromPath[Option[Person], String](_.each.name) shouldBe List("name") + } + + it should "ignore fields in map of products" in { + ModifyMacro.modifiedFromPath[Map[String, Person], String](_.each.name) shouldBe List("name") + } +} From 88f3123eb29492b0ed1e6a1caaf7c4146b5fe333 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Thu, 17 Jun 2021 10:42:28 +0200 Subject: [PATCH 089/112] Allow diffForIterable to use objectMatchers --- .../scala/com/softwaremill/diffx/Diff.scala | 5 ++- .../softwaremill/diffx/ObjectMatcher.scala | 19 +++++++-- .../diffx/instances/DiffForIterable.scala | 40 ++++++++++++------- .../softwaremill/diffx/test/DiffTest.scala | 8 ++++ 4 files changed, 52 insertions(+), 20 deletions(-) diff --git a/core/src/main/scala/com/softwaremill/diffx/Diff.scala b/core/src/main/scala/com/softwaremill/diffx/Diff.scala index e3e10b2a..637bac01 100644 --- a/core/src/main/scala/com/softwaremill/diffx/Diff.scala +++ b/core/src/main/scala/com/softwaremill/diffx/Diff.scala @@ -60,8 +60,9 @@ object Diff extends MiddlePriorityDiff with TupleInstances { trait MiddlePriorityDiff extends DiffMagnoliaDerivation with LowPriorityDiff { implicit def diffForIterable[T, C[W] <: Iterable[W]](implicit - ddot: Diff[Option[T]] - ): Diff[C[T]] = new DiffForIterable[T, C](ddot) + dt: Diff[T], + matcher: ObjectMatcher[(Int, T)] + ): Diff[C[T]] = new DiffForIterable[T, C](dt, matcher) } trait LowPriorityDiff { diff --git a/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala b/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala index d020f605..9aa6e73c 100644 --- a/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala +++ b/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala @@ -7,11 +7,24 @@ trait ObjectMatcher[T] { def isSameObject(left: T, right: T): Boolean } -object ObjectMatcher { +object ObjectMatcher extends LowPriorityObjectMatcher { def apply[T: ObjectMatcher]: ObjectMatcher[T] = implicitly[ObjectMatcher[T]] - implicit def default[T]: ObjectMatcher[T] = (l: T, r: T) => l == r - def by[T, U: ObjectMatcher](f: T => U): ObjectMatcher[T] = (left: T, right: T) => ObjectMatcher[U].isSameObject(f(left), f(right)) + + def byValue[K, V: ObjectMatcher]: ObjectMatcher[(K, V)] = ObjectMatcher.by[(K, V), V](_._2) + + implicit def optionMatcher[T: ObjectMatcher]: ObjectMatcher[Option[T]] = (left: Option[T], right: Option[T]) => { + (left, right) match { + case (Some(l), Some(r)) => ObjectMatcher[T].isSameObject(l, r) + case _ => false + } + } + implicit def byKey[K: ObjectMatcher, V]: ObjectMatcher[(K, V)] = ObjectMatcher.by[(K, V), K](_._1) +} + +trait LowPriorityObjectMatcher { + implicit def default[T]: ObjectMatcher[T] = (l: T, r: T) => l == r + } diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala index 338f894c..8ca3e13c 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala @@ -1,30 +1,40 @@ package com.softwaremill.diffx.instances +import com.softwaremill.diffx.Matching.{MatchingResults, matching} import com.softwaremill.diffx._ -import scala.collection.immutable.ListMap +import scala.collection.immutable.{ListMap, ListSet} -private[diffx] class DiffForIterable[T, C[W] <: Iterable[W]](dot: Diff[Option[T]]) extends Diff[C[T]] { +private[diffx] class DiffForIterable[T, C[W] <: Iterable[W]]( + dt: Diff[T], + matcher: ObjectMatcher[(Int, T)] +) extends Diff[C[T]] { override def apply(left: C[T], right: C[T], context: DiffContext): DiffResult = nullGuard(left, right) { (left, right) => - val indexes = Range(0, Math.max(left.size, right.size)) + val keys = Range(0, Math.max(left.size, right.size)) + val leftAsMap = left.toList.lift val rightAsMap = right.toList.lift - val differences = ListMap(indexes.map { index => - index.toString -> (dot.apply(leftAsMap(index), rightAsMap(index), context) match { - case DiffResultValue(Some(v), None) => DiffResultAdditional(v) - case DiffResultValue(None, Some(v)) => DiffResultMissing(v) - case d => d - }) - }: _*) + val leftv2 = ListSet(keys.map(i => i -> leftAsMap(i)): _*).collect { case (k, Some(v)) => k -> v } + val rightv2 = ListSet(keys.map(i => i -> rightAsMap(i)): _*).collect { case (k, Some(v)) => k -> v } + + val MatchingResults(unMatchedLeftInstances, unMatchedRightInstances, matchedInstances) = + matching(leftv2, rightv2, matcher, dt.contramap[(Int, T)](_._2), context) + val leftDiffs = unMatchedLeftInstances + .diff(unMatchedRightInstances) + .collectFirst { case (k, v) => k -> DiffResultAdditional(v) } + .toList + val rightDiffs = unMatchedRightInstances + .diff(unMatchedLeftInstances) + .collectFirst { case (k, v) => k -> DiffResultMissing(v) } + .toList + val matchedDiffs = matchedInstances.map { case (l, r) => l._1 -> dt(l._2, r._2, context) }.toList - if (differences.values.forall(_.isIdentical)) { + val diffs = ListMap((matchedDiffs ++ leftDiffs ++ rightDiffs).map { case (k, v) => k.toString -> v }: _*) + if (diffs.forall { case (_, v) => v.isIdentical }) { Identical(left) } else { - DiffResultObject( - "List", - differences - ) + DiffResultObject("List", diffs) } } } diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala index c77037c9..ad0a9221 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala @@ -284,6 +284,14 @@ class DiffTest extends AnyFreeSpec with Matchers { compare(o1, o2) shouldBe Identical(Organization(List(p1, p2))) } + "compare lists using object matcher comparator" in { + val o1 = Organization(List(p1, p2)) + val o2 = Organization(List(p2, p1)) + implicit val om: ObjectMatcher[(Int, Person)] = ObjectMatcher.byValue[Int, Person](ObjectMatcher.by(_.name)) + implicit val dd: Diff[List[Person]] = Diff.diffForIterable + compare(o1, o2) shouldBe Identical(Organization(List(p1, p2))) + } + "should preserve order of elements" in { val l1 = List(1, 2, 3, 4, 5, 6) val l2 = List(1, 2, 3, 4, 5, 7) From a5d1b5ae940aa5a0cd1b519003432058e2b7abe8 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Thu, 17 Jun 2021 11:23:37 +0200 Subject: [PATCH 090/112] Map should use key-value object matcher --- .../scala/com/softwaremill/diffx/Diff.scala | 12 ++--- .../com/softwaremill/diffx/Matching.scala | 13 +++++ .../diffx/instances/DiffForIterable.scala | 2 +- .../diffx/instances/DiffForMap.scala | 54 +++++-------------- .../softwaremill/diffx/test/DiffTest.scala | 17 +++++- 5 files changed, 50 insertions(+), 48 deletions(-) diff --git a/core/src/main/scala/com/softwaremill/diffx/Diff.scala b/core/src/main/scala/com/softwaremill/diffx/Diff.scala index 637bac01..aebc8edd 100644 --- a/core/src/main/scala/com/softwaremill/diffx/Diff.scala +++ b/core/src/main/scala/com/softwaremill/diffx/Diff.scala @@ -44,15 +44,15 @@ object Diff extends MiddlePriorityDiff with TupleInstances { implicit def diffForNumeric[T: Numeric]: Diff[T] = new DiffForNumeric[T] implicit def diffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]](implicit - ddot: Diff[Option[V]], - ddk: Diff[K], - matcher: ObjectMatcher[K] - ): Diff[C[K, V]] = new DiffForMap[K, V, C](matcher, ddk, ddot) + dv: Diff[V], + dk: Diff[K], + matcher: ObjectMatcher[(K, V)] + ): Diff[C[K, V]] = new DiffForMap[K, V, C](matcher, dk, dv) implicit def diffForOptional[T](implicit ddt: Diff[T]): Diff[Option[T]] = new DiffForOption[T](ddt) implicit def diffForSet[T, C[W] <: scala.collection.Set[W]](implicit - ddt: Diff[T], + dt: Diff[T], matcher: ObjectMatcher[T] - ): Diff[C[T]] = new DiffForSet[T, C](ddt, matcher) + ): Diff[C[T]] = new DiffForSet[T, C](dt, matcher) implicit def diffForEither[L, R](implicit ld: Diff[L], rd: Diff[R]): Diff[Either[L, R]] = new DiffForEither[L, R](ld, rd) } diff --git a/core/src/main/scala/com/softwaremill/diffx/Matching.scala b/core/src/main/scala/com/softwaremill/diffx/Matching.scala index 666293b8..a8e37a17 100644 --- a/core/src/main/scala/com/softwaremill/diffx/Matching.scala +++ b/core/src/main/scala/com/softwaremill/diffx/Matching.scala @@ -16,6 +16,19 @@ private[diffx] object Matching { MatchingResults(left.diff(matchedKeys.map(_._1)), right.diff(matchedKeys.map(_._2)), matchedKeys) } + private[diffx] def matching[T]( + left: scala.collection.Set[T], + right: scala.collection.Set[T], + matcher: ObjectMatcher[T] + ): MatchingResults[T] = { + val matchedKeys = left.flatMap(l => + right.collectFirst { + case r if matcher.isSameObject(l, r) => l -> r + } + ) + MatchingResults(left.diff(matchedKeys.map(_._1)), right.diff(matchedKeys.map(_._2)), matchedKeys) + } + private[diffx] case class MatchingResults[T]( unmatchedLeft: scala.collection.Set[T], unmatchedRight: scala.collection.Set[T], diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala index 8ca3e13c..1a4bb73f 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala @@ -19,7 +19,7 @@ private[diffx] class DiffForIterable[T, C[W] <: Iterable[W]]( val rightv2 = ListSet(keys.map(i => i -> rightAsMap(i)): _*).collect { case (k, Some(v)) => k -> v } val MatchingResults(unMatchedLeftInstances, unMatchedRightInstances, matchedInstances) = - matching(leftv2, rightv2, matcher, dt.contramap[(Int, T)](_._2), context) + matching(leftv2, rightv2, matcher) val leftDiffs = unMatchedLeftInstances .diff(unMatchedRightInstances) .collectFirst { case (k, v) => k -> DiffResultAdditional(v) } diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala index 6e1ef2a0..5be7a90b 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala @@ -4,9 +4,9 @@ import com.softwaremill.diffx.Matching._ import com.softwaremill.diffx._ private[diffx] class DiffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]]( - matcher: ObjectMatcher[K], + matcher: ObjectMatcher[(K, V)], diffKey: Diff[K], - diffValue: Diff[Option[V]] + diffValue: Diff[V] ) extends Diff[C[K, V]] { override def apply( left: C[K, V], @@ -14,10 +14,18 @@ private[diffx] class DiffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]] context: DiffContext ): DiffResult = nullGuard(left, right) { (left, right) => val MatchingResults(unMatchedLeftKeys, unMatchedRightKeys, matchedKeys) = - matching[K](left.keySet, right.keySet, matcher, diffKey, context) - val leftDiffs = this.leftDiffs(left, unMatchedLeftKeys, unMatchedRightKeys) - val rightDiffs = this.rightDiffs(right, unMatchedLeftKeys, unMatchedRightKeys) - val matchedDiffs = this.matchedDiffs(matchedKeys, left, right, context) + matching(left.toSet, right.toSet, matcher, diffKey.contramap[(K, V)](_._1), context) + + val leftDiffs = unMatchedLeftKeys + .diff(unMatchedRightKeys) + .collectFirst { case (k, v) => DiffResultAdditional(k) -> DiffResultAdditional(v) } + .toList + val rightDiffs = unMatchedRightKeys + .diff(unMatchedLeftKeys) + .collectFirst { case (k, v) => DiffResultMissing(k) -> DiffResultMissing(v) } + .toList + val matchedDiffs = matchedKeys.map { case (l, r) => diffKey(l._1, r._1) -> diffValue(l._2, r._2, context) }.toList + val diffs = leftDiffs ++ rightDiffs ++ matchedDiffs if (diffs.forall(p => p._1.isIdentical && p._2.isIdentical)) { Identical(left) @@ -25,38 +33,4 @@ private[diffx] class DiffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]] DiffResultMap(diffs.toMap) } } - - private def matchedDiffs( - matchedKeys: scala.collection.Set[(K, K)], - left: C[K, V], - right: C[K, V], - context: DiffContext - ): List[(DiffResult, DiffResult)] = { - matchedKeys.map { case (lKey, rKey) => - val result = diffKey.apply(lKey, rKey) - result -> diffValue.apply(left.get(lKey), right.get(rKey), context) - }.toList - } - - private def rightDiffs( - right: C[K, V], - unMatchedLeftKeys: scala.collection.Set[K], - unMatchedRightKeys: scala.collection.Set[K] - ): List[(DiffResult, DiffResult)] = { - unMatchedRightKeys - .diff(unMatchedLeftKeys) - .map(k => DiffResultMissing(k) -> DiffResultMissing(right(k))) - .toList - } - - private def leftDiffs( - left: C[K, V], - unMatchedLeftKeys: scala.collection.Set[K], - unMatchedRightKeys: scala.collection.Set[K] - ): List[(DiffResult, DiffResult)] = { - unMatchedLeftKeys - .diff(unMatchedRightKeys) - .map(k => DiffResultAdditional(k) -> DiffResultAdditional(left(k))) - .toList - } } diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala index ad0a9221..900f0ea0 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala @@ -288,7 +288,6 @@ class DiffTest extends AnyFreeSpec with Matchers { val o1 = Organization(List(p1, p2)) val o2 = Organization(List(p2, p1)) implicit val om: ObjectMatcher[(Int, Person)] = ObjectMatcher.byValue[Int, Person](ObjectMatcher.by(_.name)) - implicit val dd: Diff[List[Person]] = Diff.diffForIterable compare(o1, o2) shouldBe Identical(Organization(List(p1, p2))) } @@ -307,6 +306,22 @@ class DiffTest extends AnyFreeSpec with Matchers { ) ) } + + "should not use values when matching using default key strategy" in { + val l1 = List(1, 2, 3, 4, 5, 6) + val l2 = List(1, 2, 4, 5, 6) + compare(l1, l2) shouldBe DiffResultObject( + "List", + ListMap( + "0" -> Identical(1), + "1" -> Identical(2), + "2" -> DiffResultValue(3, 4), + "3" -> DiffResultValue(4, 5), + "4" -> DiffResultValue(5, 6), + "5" -> DiffResultAdditional(6) + ) + ) + } } "sets" - { "identity" in { From d0eb96592c642609b7d3c17d6b9ed9011a65ff23 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Thu, 17 Jun 2021 11:28:42 +0200 Subject: [PATCH 091/112] Add test case for matching by value in map --- .../softwaremill/diffx/test/DiffTest.scala | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala index 900f0ea0..4a768449 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala @@ -491,7 +491,7 @@ class DiffTest extends AnyFreeSpec with Matchers { compare(a1, a2) shouldBe Identical(a1) } - "match values using object mapper" in { + "match keys using object mapper" in { implicit val om: ObjectMatcher[KeyModel] = ObjectMatcher.by(_.name) val uuid1 = UUID.randomUUID() val uuid2 = UUID.randomUUID() @@ -514,6 +514,30 @@ class DiffTest extends AnyFreeSpec with Matchers { ) ) } + + "match map entries by values" in { + implicit val om: ObjectMatcher[(KeyModel, String)] = ObjectMatcher.byValue + val uuid1 = UUID.randomUUID() + val uuid2 = UUID.randomUUID() + val a1 = MyLookup(Map(KeyModel(uuid1, "k1") -> "val1")) + val a2 = MyLookup(Map(KeyModel(uuid2, "k1") -> "val1")) + compare(a1, a2) shouldBe DiffResultObject( + "MyLookup", + Map( + "map" -> DiffResultMap( + Map( + DiffResultObject( + "KeyModel", + Map( + "id" -> DiffResultValue(uuid1, uuid2), + "name" -> Identical("k1") + ) + ) -> Identical("val1") + ) + ) + ) + ) + } } "ranges" - { "identical" in { From cf6393713cc86d367498eb216d67a15350793b48 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Thu, 17 Jun 2021 14:02:59 +0200 Subject: [PATCH 092/112] Allow replacement of object matcher in nested hierarchies --- .../scala/com/softwaremill/diffx/Diff.scala | 28 +++++- .../com/softwaremill/diffx/DiffContext.scala | 78 ++++++++++----- .../diffx/instances/DiffForIterable.scala | 3 +- .../diffx/instances/DiffForMap.scala | 3 +- .../diffx/instances/DiffForSet.scala | 3 +- .../diffx/test/DiffIgnoreIntTest.scala | 37 ------- .../test/DiffModifyIntegrationTest.scala | 98 +++++++++++++++++++ .../diffx/test/DiffSemiautoTest.scala | 8 ++ .../softwaremill/diffx/test/DiffTest.scala | 28 ++++-- 9 files changed, 212 insertions(+), 74 deletions(-) delete mode 100644 core/src/test/scala/com/softwaremill/diffx/test/DiffIgnoreIntTest.scala create mode 100644 core/src/test/scala/com/softwaremill/diffx/test/DiffModifyIntegrationTest.scala diff --git a/core/src/main/scala/com/softwaremill/diffx/Diff.scala b/core/src/main/scala/com/softwaremill/diffx/Diff.scala index aebc8edd..57465e5d 100644 --- a/core/src/main/scala/com/softwaremill/diffx/Diff.scala +++ b/core/src/main/scala/com/softwaremill/diffx/Diff.scala @@ -17,7 +17,17 @@ trait Diff[-T] { outer => def modifyUnsafe(path: String*)(diff: Diff[_]): Diff[T] = new Diff[T] { override def apply(left: T, right: T, context: DiffContext): DiffResult = - outer.apply(left, right, context.merge(DiffContext(Tree.fromList(path.toList, diff), List.empty))) + outer.apply(left, right, context.merge(DiffContext(Tree.fromList(path.toList, diff), List.empty, Tree.empty))) + } + + def modifyMatcherUnsafe(path: String*)(matcher: ObjectMatcher[_]): Diff[T] = + new Diff[T] { + override def apply(left: T, right: T, context: DiffContext): DiffResult = + outer.apply( + left, + right, + context.merge(DiffContext(Tree.empty, List.empty, Tree.fromList(path.toList, matcher))) + ) } } @@ -88,10 +98,26 @@ case class DiffLens[T, U](outer: Diff[T], path: List[String]) { outer.modifyUnsafe(path: _*)(d) } def ignore(): Diff[T] = outer.modifyUnsafe(path: _*)(Diff.ignored) + + def withMapMatcher[K, V](m: ObjectMatcher[(K, V)])(implicit ev1: U <:< scala.collection.Map[K, V]): Diff[T] = + outer.modifyMatcherUnsafe(path: _*)(m) + def withSetMatcher[V](m: ObjectMatcher[V])(implicit ev2: U <:< scala.collection.Set[V]): Diff[T] = + outer.modifyMatcherUnsafe(path: _*)(m) + def withListMatcher[V](m: ObjectMatcher[(Int, V)])(implicit ev3: U <:< Iterable[V]): Diff[T] = + outer.modifyMatcherUnsafe(path: _*)(m) } case class DerivedDiffLens[T, U](outer: Diff[T], path: List[String]) { def setTo(d: Diff[U]): Derived[Diff[T]] = { Derived(outer.modifyUnsafe(path: _*)(d)) } def ignore(): Derived[Diff[T]] = Derived(outer.modifyUnsafe(path: _*)(Diff.ignored)) + + def withMapMatcher[K, V](m: ObjectMatcher[(K, V)])(implicit + ev1: U <:< scala.collection.mutable.Map[K, V] + ): Derived[Diff[T]] = + Derived(outer.modifyMatcherUnsafe(path: _*)(m)) + def withSetMatcher[V](m: ObjectMatcher[V])(implicit ev2: U <:< scala.collection.mutable.Set[V]): Derived[Diff[T]] = + Derived(outer.modifyMatcherUnsafe(path: _*)(m)) + def withListMatcher[V](m: ObjectMatcher[(Int, V)])(implicit ev3: U <:< Iterable[V]): Derived[Diff[T]] = + Derived(outer.modifyMatcherUnsafe(path: _*)(m)) } diff --git a/core/src/main/scala/com/softwaremill/diffx/DiffContext.scala b/core/src/main/scala/com/softwaremill/diffx/DiffContext.scala index f2b54a7e..5fcd64bf 100644 --- a/core/src/main/scala/com/softwaremill/diffx/DiffContext.scala +++ b/core/src/main/scala/com/softwaremill/diffx/DiffContext.scala @@ -1,47 +1,71 @@ package com.softwaremill.diffx -case class DiffContext(overrides: Tree, path: FieldPath) { +case class DiffContext(overrides: Tree[Diff[_]], path: FieldPath, matcherOverrides: Tree[ObjectMatcher[_]]) { def merge(other: DiffContext): DiffContext = { - DiffContext(overrides.merge(other.overrides), List.empty) + DiffContext(overrides.merge(other.overrides), List.empty, matcherOverrides.merge(other.matcherOverrides)) } def getOverride(label: String): Option[Diff[_]] = { - overrides match { - case Tree.Leaf(_) => throw new IllegalStateException(s"Expected node, got leaf at $path") - case Tree.Node(tries) => - tries.get(label) match { - case Some(Tree.Leaf(v)) => Some(v) - case _ => None - } + treeOverride(label, overrides) + } + + def getMatcherOverride[T]: Option[ObjectMatcher[T]] = { + matcherOverrides match { + case Tree.Leaf(v) => Some(v.asInstanceOf[ObjectMatcher[T]]) + case Tree.Node(_) => None } } + + private def treeOverride[T](label: String, tree: Tree[T]) = { + tree match { + case Tree.Leaf(_) => throw new IllegalStateException(s"Expected node, got leaf at $path") + case Tree.Node(tries) => getOverrideFromNode(label, tries) + } + } + + private def getOverrideFromNode[T](label: String, tries: Map[String, Tree[T]]) = { + tries.get(label) match { + case Some(Tree.Leaf(v)) => Some(v) + case _ => None + } + } + def getNextStep(label: String): DiffContext = { - overrides match { - case Tree.Leaf(_) => throw new IllegalStateException(s"Expected node, got leaf at $path") - case Tree.Node(tries) => - val currentPath = path :+ label - tries.get(label) match { - case Some(value) => DiffContext(value, currentPath) - case None => DiffContext.Empty - } + val currentPath = path :+ label + (getNextOverride(label, overrides), getNextOverride(label, matcherOverrides)) match { + case (Some(d), Some(m)) => DiffContext(d, currentPath, m) + case (None, Some(m)) => DiffContext(Tree.empty, currentPath, m) + case (Some(d), None) => DiffContext(d, currentPath, Tree.empty) + case (None, None) => DiffContext(Tree.empty, currentPath, Tree.empty) + } + } + + private def getNextOverride[T](label: String, tree: Tree[T]) = { + tree match { + case Tree.Leaf(_) => None + case Tree.Node(tries) => tries.get(label) } } } object DiffContext { - val Empty: DiffContext = DiffContext(Tree.Node(Map.empty), List.empty) - def atPath(path: FieldPath, diff: Diff[_]): DiffContext = DiffContext(Tree.fromList(path, diff), List.empty) + val Empty: DiffContext = DiffContext(Tree.empty, List.empty, Tree.empty) + def atPath(path: FieldPath, diff: Diff[_]): DiffContext = Empty.copy(overrides = Tree.fromList(path, diff)) + def atPath(path: FieldPath, matcher: ObjectMatcher[_]): DiffContext = + Empty.copy(matcherOverrides = Tree.fromList(path, matcher)) } -sealed trait Tree { - def merge(tree: Tree): Tree +sealed trait Tree[T] { + def merge(tree: Tree[T]): Tree[T] } object Tree { - case class Leaf(v: Diff[_]) extends Tree { - override def merge(tree: Tree): Tree = tree + def empty[T]: Node[T] = Tree.Node[T](Map.empty) + + case class Leaf[T](v: T) extends Tree[T] { + override def merge(tree: Tree[T]): Tree[T] = tree } - case class Node(tries: Map[String, Tree]) extends Tree { - override def merge(tree: Tree): Tree = { + case class Node[T](tries: Map[String, Tree[T]]) extends Tree[T] { + override def merge(tree: Tree[T]): Tree[T] = { tree match { case Leaf(v) => Leaf(v) case Node(otherTries) => @@ -57,7 +81,7 @@ object Tree { } } } - def fromList(path: FieldPath, diff: Diff[_]): Tree = { - path.reverse.foldLeft(Leaf(diff): Tree)((acc, item) => Node(Map(item -> acc))) + def fromList[T](path: FieldPath, obj: T): Tree[T] = { + path.reverse.foldLeft(Leaf(obj): Tree[T])((acc, item) => Node(Map(item -> acc))) } } diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala index 1a4bb73f..2e53b617 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala @@ -18,8 +18,9 @@ private[diffx] class DiffForIterable[T, C[W] <: Iterable[W]]( val leftv2 = ListSet(keys.map(i => i -> leftAsMap(i)): _*).collect { case (k, Some(v)) => k -> v } val rightv2 = ListSet(keys.map(i => i -> rightAsMap(i)): _*).collect { case (k, Some(v)) => k -> v } + val adjustedMatcher = context.getMatcherOverride[(Int, T)].getOrElse(matcher) val MatchingResults(unMatchedLeftInstances, unMatchedRightInstances, matchedInstances) = - matching(leftv2, rightv2, matcher) + matching(leftv2, rightv2, adjustedMatcher) val leftDiffs = unMatchedLeftInstances .diff(unMatchedRightInstances) .collectFirst { case (k, v) => k -> DiffResultAdditional(v) } diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala index 5be7a90b..c6756deb 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala @@ -13,8 +13,9 @@ private[diffx] class DiffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]] right: C[K, V], context: DiffContext ): DiffResult = nullGuard(left, right) { (left, right) => + val adjustedMatcher = context.getMatcherOverride[(K, V)].getOrElse(matcher) val MatchingResults(unMatchedLeftKeys, unMatchedRightKeys, matchedKeys) = - matching(left.toSet, right.toSet, matcher, diffKey.contramap[(K, V)](_._1), context) + matching(left.toSet, right.toSet, adjustedMatcher, diffKey.contramap[(K, V)](_._1), context) val leftDiffs = unMatchedLeftKeys .diff(unMatchedRightKeys) diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala index 310d1cae..1f098823 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala @@ -7,8 +7,9 @@ private[diffx] class DiffForSet[T, C[W] <: scala.collection.Set[W]](dt: Diff[T], extends Diff[C[T]] { override def apply(left: C[T], right: C[T], context: DiffContext): DiffResult = nullGuard(left, right) { (left, right) => + val adjustedMatcher = context.getMatcherOverride[T].getOrElse(matcher) val MatchingResults(unMatchedLeftInstances, unMatchedRightInstances, matchedInstances) = - matching[T](left.toSet, right.toSet, matcher, dt, context) + matching[T](left.toSet, right.toSet, adjustedMatcher, dt, context) val leftDiffs = unMatchedLeftInstances .diff(unMatchedRightInstances) .map(DiffResultAdditional(_)) diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffIgnoreIntTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffIgnoreIntTest.scala deleted file mode 100644 index 176aff4a..00000000 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffIgnoreIntTest.scala +++ /dev/null @@ -1,37 +0,0 @@ -package com.softwaremill.diffx.test - -import com.softwaremill.diffx._ -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers -import com.softwaremill.diffx.generic.auto._ - -import java.time.Instant - -class DiffIgnoreIntTest extends AnyFlatSpec with Matchers { - val instant: Instant = Instant.now() - val p1 = Person("p1", 22, instant) - val p2 = Person("p2", 11, instant) - - it should "allow importing and exporting implicits" in { - implicit val d: Diff[Person] = Derived[Diff[Person]].ignore(_.name) - compare(p1, p2) shouldBe DiffResultObject( - "Person", - Map("name" -> Identical(""), "age" -> DiffResultValue(22, 11), "in" -> Identical(instant)) - ) - } - - it should "allow importing and exporting implicits using macro on derived instance" in { - implicit val d: Diff[Person] = Derived[Diff[Person]].ignore(_.name) - compare(p1, p2) shouldBe DiffResultObject( - "Person", - Map("name" -> Identical(""), "age" -> DiffResultValue(22, 11), "in" -> Identical(instant)) - ) - } - - it should "allow calling ignore multiple times" in { - implicit val d: Diff[Person] = Derived[Diff[Person]] - .ignore[Person, String](_.name) - .ignore[Person, Int](_.age) - compare(p1, p2) shouldBe Identical(p1) - } -} diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffModifyIntegrationTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffModifyIntegrationTest.scala new file mode 100644 index 00000000..3a392552 --- /dev/null +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffModifyIntegrationTest.scala @@ -0,0 +1,98 @@ +package com.softwaremill.diffx.test + +import com.softwaremill.diffx._ +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers +import com.softwaremill.diffx.generic.auto._ + +import java.time.Instant +import java.util.UUID + +class DiffModifyIntegrationTest extends AnyFlatSpec with Matchers { + val instant: Instant = Instant.now() + val p1 = Person("p1", 22, instant) + val p2 = Person("p2", 11, instant) + + it should "allow importing and exporting implicits" in { + implicit val d: Diff[Person] = Derived[Diff[Person]].ignore(_.name) + compare(p1, p2) shouldBe DiffResultObject( + "Person", + Map("name" -> Identical(""), "age" -> DiffResultValue(22, 11), "in" -> Identical(instant)) + ) + } + + it should "allow importing and exporting implicits using macro on derived instance" in { + implicit val d: Diff[Person] = Derived[Diff[Person]].ignore(_.name) + compare(p1, p2) shouldBe DiffResultObject( + "Person", + Map("name" -> Identical(""), "age" -> DiffResultValue(22, 11), "in" -> Identical(instant)) + ) + } + + it should "allow calling ignore multiple times" in { + implicit val d: Diff[Person] = Derived[Diff[Person]] + .ignore[Person, String](_.name) + .ignore[Person, Int](_.age) + compare(p1, p2) shouldBe Identical(p1) + } + + it should "compare lists using explicit object matcher comparator" in { + val o1 = Organization(List(p1, p2)) + val o2 = Organization(List(p2, p1)) + implicit val orgDiff: Diff[Organization] = Derived[Diff[Organization]] + .modify[Organization, List[Person]](_.people) + .withListMatcher( + ObjectMatcher.byValue[Int, Person](ObjectMatcher.by(_.name)) + ) + compare(o1, o2) shouldBe Identical(Organization(List(p1, p2))) + } + + it should "match map entries by values" in { + implicit val lookupDiff: Diff[MyLookup] = Derived[Diff[MyLookup]] + .modify[MyLookup, Map[KeyModel, String]](_.map) + .withMapMatcher( + ObjectMatcher.byValue[KeyModel, String] + ) + val uuid1 = UUID.randomUUID() + val uuid2 = UUID.randomUUID() + val a1 = MyLookup(Map(KeyModel(uuid1, "k1") -> "val1")) + val a2 = MyLookup(Map(KeyModel(uuid2, "k1") -> "val1")) + compare(a1, a2) shouldBe DiffResultObject( + "MyLookup", + Map( + "map" -> DiffResultMap( + Map( + DiffResultObject( + "KeyModel", + Map( + "id" -> DiffResultValue(uuid1, uuid2), + "name" -> Identical("k1") + ) + ) -> Identical("val1") + ) + ) + ) + ) + } + + it should "use overrided object matcher when comparing set" in { + implicit val lookupDiff: Diff[Startup] = Derived[Diff[Startup]] + .modify[Startup, Set[Person]](_.workers) + .withSetMatcher[Person](ObjectMatcher.by(_.name)) + val p2m = p2.copy(age = 33) + compare(Startup(Set(p1, p2)), Startup(Set(p1, p2m))) shouldBe DiffResultObject( + "Startup", + Map( + "workers" -> DiffResultSet( + List( + Identical(p1), + DiffResultObject( + "Person", + Map("name" -> Identical(p2.name), "age" -> DiffResultValue(p2.age, p2m.age), "in" -> Identical(p1.in)) + ) + ) + ) + ) + ) + } +} diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala index 7131bc89..b8cd6fd2 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala @@ -53,6 +53,14 @@ class DiffSemiautoTest extends AnyFreeSpec with Matchers { ProductA("1") ) } + + "should allow modifying derived diffs" in { + implicit val dACoproduct: Derived[Diff[ProductA]] = Diff.derived[ProductA].modify[ProductA, String](_.id).ignore() + + Diff.compare[ProductA](ProductA("1"), ProductA("2")) shouldBe Identical( + ProductA("1") + ) + } } sealed trait ACoproduct diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala index 4a768449..599cd1d1 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala @@ -291,6 +291,15 @@ class DiffTest extends AnyFreeSpec with Matchers { compare(o1, o2) shouldBe Identical(Organization(List(p1, p2))) } + "compare lists using explicit object matcher comparator" in { + val o1 = Organization(List(p1, p2)) + val o2 = Organization(List(p2, p1)) + implicit val orgDiff: Diff[Organization] = Derived[Diff[Organization]].modifyMatcherUnsafe("people")( + ObjectMatcher.byValue[Int, Person](ObjectMatcher.by(_.name)) + ) + compare(o1, o2) shouldBe Identical(Organization(List(p1, p2))) + } + "should preserve order of elements" in { val l1 = List(1, 2, 3, 4, 5, 6) val l2 = List(1, 2, 3, 4, 5, 7) @@ -415,12 +424,17 @@ class DiffTest extends AnyFreeSpec with Matchers { "set of products using instance matcher" in { val p2m = p2.copy(age = 33) implicit val im: ObjectMatcher[Person] = ObjectMatcher.by(_.name) - compare(Set(p1, p2), Set(p1, p2m)) shouldBe DiffResultSet( - List( - Identical(p1), - DiffResultObject( - "Person", - Map("name" -> Identical(p2.name), "age" -> DiffResultValue(p2.age, p2m.age), "in" -> Identical(p1.in)) + compare(Startup(Set(p1, p2)), Startup(Set(p1, p2m))) shouldBe DiffResultObject( + "Startup", + Map( + "workers" -> DiffResultSet( + List( + Identical(p1), + DiffResultObject( + "Person", + Map("name" -> Identical(p2.name), "age" -> DiffResultValue(p2.age, p2m.age), "in" -> Identical(p1.in)) + ) + ) ) ) ) @@ -672,6 +686,8 @@ case class Family(first: Person, second: Person) case class Organization(people: List[Person]) +case class Startup(workers: Set[Person]) + sealed trait Parent case class Bar(s: String, i: Int) extends Parent From 225024bb27aefb8afdc8ab6a8cb925a57bfb8d06 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Thu, 17 Jun 2021 19:02:43 +0200 Subject: [PATCH 093/112] Trade ability to ignore on child type for better type inference --- .../main/scala/com/softwaremill/diffx/Diff.scala | 16 +++++++++------- .../com/softwaremill/diffx/ModifyMacro.scala | 2 +- .../diffx/test/DiffModifyIntegrationTest.scala | 10 +++++----- .../diffx/test/DiffSemiautoTest.scala | 2 +- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/core/src/main/scala/com/softwaremill/diffx/Diff.scala b/core/src/main/scala/com/softwaremill/diffx/Diff.scala index 57465e5d..e7bc5904 100644 --- a/core/src/main/scala/com/softwaremill/diffx/Diff.scala +++ b/core/src/main/scala/com/softwaremill/diffx/Diff.scala @@ -11,9 +11,6 @@ trait Diff[-T] { outer => outer(f(left), f(right), context) } - def modify[S <: T, U](path: S => U): DiffLens[S, U] = macro ModifyMacro.modifyMacro[S, U] - def ignore[S <: T, U](path: S => U): Diff[S] = macro ModifyMacro.ignoreMacro[S, U] - def modifyUnsafe(path: String*)(diff: Diff[_]): Diff[T] = new Diff[T] { override def apply(left: T, right: T, context: DiffContext): DiffResult = @@ -82,8 +79,13 @@ trait LowPriorityDiff { implicit class RichDerivedDiff[T](val dd: Derived[Diff[T]]) { def contramap[R](f: R => T): Derived[Diff[R]] = Derived(dd.value.contramap(f)) - def modify[S <: T, U](path: S => U): DerivedDiffLens[S, U] = macro ModifyMacro.derivedModifyMacro[S, U] - def ignore[S <: T, U](path: S => U): Derived[Diff[S]] = macro ModifyMacro.derivedIgnoreMacro[S, U] + def modify[U](path: T => U): DerivedDiffLens[T, U] = macro ModifyMacro.derivedModifyMacro[T, U] + def ignore[U](path: T => U): Derived[Diff[T]] = macro ModifyMacro.derivedIgnoreMacro[T, U] + } + + implicit class RichDiff[T](val d: Diff[T]) { + def modify[U](path: T => U): DiffLens[T, U] = macro ModifyMacro.modifyMacro[T, U] + def ignore[U](path: T => U): Diff[T] = macro ModifyMacro.ignoreMacro[T, U] } } @@ -113,10 +115,10 @@ case class DerivedDiffLens[T, U](outer: Diff[T], path: List[String]) { def ignore(): Derived[Diff[T]] = Derived(outer.modifyUnsafe(path: _*)(Diff.ignored)) def withMapMatcher[K, V](m: ObjectMatcher[(K, V)])(implicit - ev1: U <:< scala.collection.mutable.Map[K, V] + ev1: U <:< scala.collection.Map[K, V] ): Derived[Diff[T]] = Derived(outer.modifyMatcherUnsafe(path: _*)(m)) - def withSetMatcher[V](m: ObjectMatcher[V])(implicit ev2: U <:< scala.collection.mutable.Set[V]): Derived[Diff[T]] = + def withSetMatcher[V](m: ObjectMatcher[V])(implicit ev2: U <:< scala.collection.Set[V]): Derived[Diff[T]] = Derived(outer.modifyMatcherUnsafe(path: _*)(m)) def withListMatcher[V](m: ObjectMatcher[(Int, V)])(implicit ev3: U <:< Iterable[V]): Derived[Diff[T]] = Derived(outer.modifyMatcherUnsafe(path: _*)(m)) diff --git a/core/src/main/scala/com/softwaremill/diffx/ModifyMacro.scala b/core/src/main/scala/com/softwaremill/diffx/ModifyMacro.scala index c75c83fa..19ac8cad 100644 --- a/core/src/main/scala/com/softwaremill/diffx/ModifyMacro.scala +++ b/core/src/main/scala/com/softwaremill/diffx/ModifyMacro.scala @@ -52,7 +52,7 @@ object ModifyMacro { )(path: c.Expr[List[String]]): c.Tree = { import c.universe._ q"""{ - com.softwaremill.diffx.DiffLens(${c.prefix}, $path) + com.softwaremill.diffx.DiffLens(${c.prefix}.d, $path) }""" } diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffModifyIntegrationTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffModifyIntegrationTest.scala index 3a392552..00d6be55 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffModifyIntegrationTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffModifyIntegrationTest.scala @@ -31,8 +31,8 @@ class DiffModifyIntegrationTest extends AnyFlatSpec with Matchers { it should "allow calling ignore multiple times" in { implicit val d: Diff[Person] = Derived[Diff[Person]] - .ignore[Person, String](_.name) - .ignore[Person, Int](_.age) + .ignore(_.name) + .ignore(_.age) compare(p1, p2) shouldBe Identical(p1) } @@ -40,7 +40,7 @@ class DiffModifyIntegrationTest extends AnyFlatSpec with Matchers { val o1 = Organization(List(p1, p2)) val o2 = Organization(List(p2, p1)) implicit val orgDiff: Diff[Organization] = Derived[Diff[Organization]] - .modify[Organization, List[Person]](_.people) + .modify(_.people) .withListMatcher( ObjectMatcher.byValue[Int, Person](ObjectMatcher.by(_.name)) ) @@ -49,7 +49,7 @@ class DiffModifyIntegrationTest extends AnyFlatSpec with Matchers { it should "match map entries by values" in { implicit val lookupDiff: Diff[MyLookup] = Derived[Diff[MyLookup]] - .modify[MyLookup, Map[KeyModel, String]](_.map) + .modify(_.map) .withMapMatcher( ObjectMatcher.byValue[KeyModel, String] ) @@ -77,7 +77,7 @@ class DiffModifyIntegrationTest extends AnyFlatSpec with Matchers { it should "use overrided object matcher when comparing set" in { implicit val lookupDiff: Diff[Startup] = Derived[Diff[Startup]] - .modify[Startup, Set[Person]](_.workers) + .modify(_.workers) .withSetMatcher[Person](ObjectMatcher.by(_.name)) val p2m = p2.copy(age = 33) compare(Startup(Set(p1, p2)), Startup(Set(p1, p2m))) shouldBe DiffResultObject( diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala index b8cd6fd2..3b1807a6 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala @@ -55,7 +55,7 @@ class DiffSemiautoTest extends AnyFreeSpec with Matchers { } "should allow modifying derived diffs" in { - implicit val dACoproduct: Derived[Diff[ProductA]] = Diff.derived[ProductA].modify[ProductA, String](_.id).ignore() + implicit val dACoproduct: Derived[Diff[ProductA]] = Diff.derived[ProductA].modify(_.id).ignore() Diff.compare[ProductA](ProductA("1"), ProductA("2")) shouldBe Identical( ProductA("1") From 043a1c546d954a7d727f4d7a725983f8cde118c7 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Fri, 18 Jun 2021 16:48:26 +0200 Subject: [PATCH 094/112] Fix eachLeft/eachRight --- .../com/softwaremill/diffx/ModifyMacro.scala | 9 ++++-- .../diffx/instances/DiffForEither.scala | 4 +-- .../test/DiffModifyIntegrationTest.scala | 29 +++++++++++++++++++ .../diffx/test/ModifyMacroTest.scala | 4 +-- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala/com/softwaremill/diffx/ModifyMacro.scala b/core/src/main/scala/com/softwaremill/diffx/ModifyMacro.scala index 19ac8cad..b9f1d35e 100644 --- a/core/src/main/scala/com/softwaremill/diffx/ModifyMacro.scala +++ b/core/src/main/scala/com/softwaremill/diffx/ModifyMacro.scala @@ -97,10 +97,13 @@ object ModifyMacro { case q"($arg) => $pathBody " => collectPathElements(pathBody, Nil) case _ => c.abort(c.enclosingPosition, s"$ShapeInfo, got: ${path.tree}") } - c.Expr[List[String]]( - q"(${pathEls.collect { case TermPathElement(c) => - c.decodedName.toString + q"(${pathEls.collect { + case TermPathElement(c) => c.decodedName.toString + case FunctorPathElement(_, method, _ @_*) if method.decodedName.toString == "eachLeft" => + method.decodedName.toString + case FunctorPathElement(_, method, _ @_*) if method.decodedName.toString == "eachRight" => + method.decodedName.toString }})" ) } diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForEither.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForEither.scala index df49d45d..7404ada7 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForEither.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForEither.scala @@ -9,8 +9,8 @@ private[diffx] class DiffForEither[L, R](ld: Diff[L], rd: Diff[R]) extends Diff[ context: DiffContext ): DiffResult = { (left, right) match { - case (Left(v1), Left(v2)) => ld.apply(v1, v2, context) - case (Right(v1), Right(v2)) => rd.apply(v1, v2, context) + case (Left(v1), Left(v2)) => ld.apply(v1, v2, context.getNextStep("eachLeft")) + case (Right(v1), Right(v2)) => rd.apply(v1, v2, context.getNextStep("eachRight")) case (v1, v2) => DiffResultValue(v1, v2) } } diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffModifyIntegrationTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffModifyIntegrationTest.scala index 00d6be55..473419e2 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffModifyIntegrationTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffModifyIntegrationTest.scala @@ -47,6 +47,35 @@ class DiffModifyIntegrationTest extends AnyFlatSpec with Matchers { compare(o1, o2) shouldBe Identical(Organization(List(p1, p2))) } + it should "ignore only on right" in { + case class Wrapper(e: Either[Person, Person]) + val e1 = Wrapper(Right(p1)) + val e2 = Wrapper(Right(p1.copy(name = p1.name + "_modified"))) + + implicit val wrapperDiff: Diff[Wrapper] = Derived[Diff[Wrapper]].ignore(_.e.eachRight.name) + + compare(e1, e2) shouldBe Identical(e1) + + val e3 = Wrapper(Left(p1)) + val e4 = Wrapper(Left(p1.copy(name = p1.name + "_modified"))) + + compare(e3, e4) should not be an[Identical[_]] + } + + it should "ignore only on left" in { + case class Wrapper(e: Either[Person, Person]) + val e1 = Wrapper(Right(p1)) + val e2 = Wrapper(Right(p1.copy(name = p1.name + "_modified"))) + + implicit val wrapperDiff: Diff[Wrapper] = Derived[Diff[Wrapper]].ignore(_.e.eachLeft.name) + + compare(e1, e2) should not be an[Identical[_]] + val e3 = Wrapper(Left(p1)) + val e4 = Wrapper(Left(p1.copy(name = p1.name + "_modified"))) + + compare(e3, e4) shouldBe an[Identical[_]] + } + it should "match map entries by values" in { implicit val lookupDiff: Diff[MyLookup] = Derived[Diff[MyLookup]] .modify(_.map) diff --git a/core/src/test/scala/com/softwaremill/diffx/test/ModifyMacroTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/ModifyMacroTest.scala index bcce2d71..6ecdf51e 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/ModifyMacroTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/ModifyMacroTest.scala @@ -14,8 +14,8 @@ class ModifyMacroTest extends AnyFlatSpec with Matchers { } it should "ignore fields in product wrapped with either" in { - ModifyMacro.modifiedFromPath[Either[Person, Person], String](_.eachRight.name) shouldBe List("name") - ModifyMacro.modifiedFromPath[Either[Person, Person], String](_.eachLeft.name) shouldBe List("name") + ModifyMacro.modifiedFromPath[Either[Person, Person], String](_.eachRight.name) shouldBe List("eachRight", "name") + ModifyMacro.modifiedFromPath[Either[Person, Person], String](_.eachLeft.name) shouldBe List("eachLeft", "name") } it should "ignore fields in product wrapped with option" in { From 8fab6e03beb715580a881ef396b3aa29c8513437 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Fri, 18 Jun 2021 16:51:30 +0200 Subject: [PATCH 095/112] Fix docs compilation --- build.sbt | 2 +- docs-sources/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index fc14f474..7944eaaf 100644 --- a/build.sbt +++ b/build.sbt @@ -27,7 +27,7 @@ lazy val commonSettings: Seq[Def.Setting[_]] = commonSmlBuildSettings ++ ossPubl val compileDocs: TaskKey[Unit] = taskKey[Unit]("Compiles docs module throwing away its output") compileDocs := { - (docs.jvm(scala213) / mdoc).toTask(" --out target/sttp-docs").value + (docs.jvm(scala213) / mdoc).toTask(" --out target/diffx-docs").value } val versionSpecificScalaSources = { diff --git a/docs-sources/README.md b/docs-sources/README.md index 00e723ef..9e97d167 100644 --- a/docs-sources/README.md +++ b/docs-sources/README.md @@ -171,7 +171,7 @@ instance of the `Diff` typeclass into the implicit scope. The whole process look ```scala mdoc:compile-only case class Person(name:String, age:Int) -implicit val modifiedDiff: Diff[Person] = Derived[Diff[Person]].ignore[Person,String](_.name) +implicit val modifiedDiff: Diff[Person] = Derived[Diff[Person]].ignore(_.name) ``` ## Customization @@ -200,7 +200,7 @@ sealed trait ABParent case class A(id: String, name: String) extends ABParent case class B(id: String, name: String) extends ABParent -implicit val diffA: Diff[A] = Derived[Diff[A]].ignore[A, String](_.id) +implicit val diffA: Diff[A] = Derived[Diff[A]].ignore(_.id) ``` ```scala mdoc val a1: ABParent = A("1", "X") From 54219e0e945edd837847545691add295a785b077 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Fri, 18 Jun 2021 17:32:54 +0200 Subject: [PATCH 096/112] Run docs compilation on ci --- .github/workflows/main.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index cec8a40b..73432a56 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -18,6 +18,8 @@ jobs: - uses: olafurpg/setup-scala@v12 with: java-version: adopt@1.11 + - name: Compile docs + run: sbt compileDocs - name: Run tests with sbt run: sbt test From 64e1ecaf83a2cd3450b2f22bcd16cebdd5218583 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Fri, 18 Jun 2021 17:38:08 +0200 Subject: [PATCH 097/112] Fix docs update configuration --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 7944eaaf..86589ed5 100644 --- a/build.sbt +++ b/build.sbt @@ -20,7 +20,7 @@ lazy val commonSettings: Seq[Def.Setting[_]] = commonSmlBuildSettings ++ ossPubl UpdateVersionInDocs(sLog.value, organization.value, version.value, List(file("docs-sources") / "README.md")) Def.task { (docs.jvm(scala213) / mdoc).toTask("").value - files1 ++ Seq(file("generated-docs"), file("README.md")) + files1 ++ Seq(file("README.md")) } }.value ) From f5f508754298dd21c9531f950ea7b482677a1952 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Fri, 18 Jun 2021 17:40:51 +0200 Subject: [PATCH 098/112] Release 0.5.0 --- README.md | 67 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 1a8270cb..25a1b74d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![diffx](https://github.com/softwaremill/diffx/raw/master/banner.png) -[![CI](https://github.com/softwaremill/diffx/workflows/CI/badge.svg)](https://github.com/softwaremill/diffx/actions?query=workflow%3A%22CI%22) +[![Build Status](https://travis-ci.org/softwaremill/diffx.svg?branch=master)](https://travis-ci.org/softwaremill/diffx) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.diffx/diffx-core_2.13/badge.svg)](https://search.maven.org/search?q=g:com.softwaremill.diffx) [![Gitter](https://badges.gitter.im/softwaremill/diffx.svg)](https://gitter.im/softwaremill/diffx?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Mergify Status](https://img.shields.io/endpoint.svg?url=https://gh.mergify.io/badges/softwaremill/diffx&style=flat)](https://mergify.io) @@ -38,7 +38,7 @@ The library is published for Scala 2.12 and 2.13. Add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-core" % "0.4.5" +"com.softwaremill.diffx" %% "diffx-core" % "0.5.0" ``` ```scala @@ -51,7 +51,11 @@ val right: Foo = Foo( List(123, 1234), Some(Bar("asdf", 5)) ) -// right: Foo = Foo(Bar("asdf", 5), List(123, 1234), Some(Bar("asdf", 5))) +// right: Foo = Foo( +// bar = Bar(s = "asdf", i = 5), +// b = List(123, 1234), +// parent = Some(value = Bar(s = "asdf", i = 5)) +// ) val left: Foo = Foo( Bar("asdf", 66), @@ -59,26 +63,41 @@ val left: Foo = Foo( Some(right) ) // left: Foo = Foo( -// Bar("asdf", 66), -// List(1234), -// Some(Foo(Bar("asdf", 5), List(123, 1234), Some(Bar("asdf", 5)))) +// bar = Bar(s = "asdf", i = 66), +// b = List(1234), +// parent = Some( +// value = Foo( +// bar = Bar(s = "asdf", i = 5), +// b = List(123, 1234), +// parent = Some(value = Bar(s = "asdf", i = 5)) +// ) +// ) // ) import com.softwaremill.diffx.generic.auto._ import com.softwaremill.diffx._ compare(left, right) // res0: DiffResult = DiffResultObject( -// "Foo", -// ListMap( +// name = "Foo", +// fields = ListMap( // "bar" -> DiffResultObject( -// "Bar", -// ListMap("s" -> Identical("asdf"), "i" -> DiffResultValue(66, 5)) +// name = "Bar", +// fields = ListMap( +// "s" -> Identical(value = "asdf"), +// "i" -> DiffResultValue(left = 66, right = 5) +// ) // ), // "b" -> DiffResultObject( -// "List", -// ListMap("0" -> DiffResultValue(1234, 123), "1" -> DiffResultMissing(1234)) +// name = "List", +// fields = ListMap( +// "0" -> DiffResultValue(left = 1234, right = 123), +// "1" -> DiffResultMissing(value = 1234) +// ) // ), -// "parent" -> DiffResultValue("repl.MdocSession.App.Foo", "repl.MdocSession.App.Bar") +// "parent" -> DiffResultValue( +// left = "repl.MdocSession.App.Foo", +// right = "repl.MdocSession.App.Bar" +// ) // ) // ) ``` @@ -131,7 +150,7 @@ If anyone has an idea how this could be improved, I am open for suggestions. To use with scalatest, add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-scalatest" % "0.4.5" % Test +"com.softwaremill.diffx" %% "diffx-scalatest" % "0.5.0" % Test ``` Then, extend the `com.softwaremill.diffx.scalatest.DiffMatcher` trait or `import com.softwaremill.diffx.scalatest.DiffMatcher._`. @@ -151,7 +170,7 @@ Giving you nice error messages: To use with specs2, add the following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-specs2" % "0.4.5" % Test +"com.softwaremill.diffx" %% "diffx-specs2" % "0.5.0" % Test ``` Then, extend the `com.softwaremill.diffx.specs2.DiffMatcher` trait or `import com.softwaremill.diffx.specs2.DiffMatcher._`. @@ -169,7 +188,7 @@ left must matchTo(right) To use with utest, add following dependency: ```scala -"com.softwaremill.diffx" %% "diffx-utest" % "0.4.5" % Test +"com.softwaremill.diffx" %% "diffx-utest" % "0.5.0" % Test ``` Then, mixin `DiffxAssertions` trait or add `import com.softwaremill.diffx.utest.DiffxAssertions._` to your test code. @@ -191,7 +210,7 @@ instance of the `Diff` typeclass into the implicit scope. The whole process look ```scala case class Person(name:String, age:Int) -implicit val modifiedDiff: Diff[Person] = Derived[Diff[Person]].ignore[Person,String](_.name) +implicit val modifiedDiff: Diff[Person] = Derived[Diff[Person]].ignore(_.name) ``` ## Customization @@ -220,16 +239,16 @@ sealed trait ABParent case class A(id: String, name: String) extends ABParent case class B(id: String, name: String) extends ABParent -implicit val diffA: Diff[A] = Derived[Diff[A]].ignore[A, String](_.id) +implicit val diffA: Diff[A] = Derived[Diff[A]].ignore(_.id) ``` ```scala val a1: ABParent = A("1", "X") -// a1: ABParent = A("1", "X") +// a1: ABParent = A(id = "1", name = "X") val a2: ABParent = A("2", "X") -// a2: ABParent = A("2", "X") +// a2: ABParent = A(id = "2", name = "X") compare(a1, a2) -// res6: DiffResult = Identical(A("1", "X")) +// res6: DiffResult = Identical(value = A(id = "1", name = "X")) ``` As you can see instead of summoning bare instance of `Diff` for given `A` we summoned `Derived[Diff[A]]`. @@ -245,17 +264,17 @@ with the compiler option `"-P:silencer:globalFilters=^magnolia: using fallback d - [com.softwaremill.common.tagging](https://github.com/softwaremill/scala-common) ```scala - "com.softwaremill.diffx" %% "diffx-tagging" % "0.4.5" + "com.softwaremill.diffx" %% "diffx-tagging" % "0.5.0" ``` `com.softwaremill.diffx.tagging.DiffTaggingSupport` - [eu.timepit.refined](https://github.com/fthomas/refined) ```scala - "com.softwaremill.diffx" %% "diffx-refined" % "0.4.5" + "com.softwaremill.diffx" %% "diffx-refined" % "0.5.0" ``` `com.softwaremill.diffx.refined.RefinedSupport` - [org.typelevel.cats](https://github.com/typelevel/cats) ```scala - "com.softwaremill.diffx" %% "diffx-cats" % "0.4.5" + "com.softwaremill.diffx" %% "diffx-cats" % "0.5.0" ``` `com.softwaremill.diffx.cats.DiffCatsInstances` From 07df05204567159b64fc4a322945bc0b08350a02 Mon Sep 17 00:00:00 2001 From: Kasper Kondzielski Date: Tue, 22 Jun 2021 17:07:36 +0300 Subject: [PATCH 099/112] Migrate documentation to rtd (#263) * Setup rtd * Setup rtd * Setup rtd * Setup rtd * Setup rtd * Restore default index.md * configure md * Use md parser * rtd * rtd * rtd * Add doc-tree to index * Working toctree * Add integrations * Add missing files * Add summary * Add docs on usage(ignoring and output) * Note about themes and env variable * Fix wording * Derivation * Change DiffDerivation to AutoDerivation * Extending * Fix approximate * Replacing * Fix mdoc compilation * Remove content from the readme and redirect to microsite * Fix indent * replacement/ignoring * sequences * Update readme * Fix ignoring example * Configure rtd edit on gh button * Fix badge url * Update version --- .readthedocs.yaml | 11 + README.md | 287 +----------------- build.sbt | 9 +- .../scala/com/softwaremill/diffx/Diff.scala | 2 +- ...fDerivation.scala => AutoDerivation.scala} | 4 +- .../instances/ApproximateDiffForNumeric.scala | 2 +- .../softwaremill/diffx/test/DiffTest.scala | 10 +- docs-sources/.gitignore | 2 + docs-sources/.python-version | 1 + docs-sources/Makefile | 19 ++ docs-sources/README.md | 253 --------------- docs-sources/conf.py | 192 ++++++++++++ docs-sources/index.md | 103 +++++++ docs-sources/integrations/cats.md | 36 +++ docs-sources/integrations/refined.md | 40 +++ docs-sources/integrations/tagging.md | 39 +++ docs-sources/make.bat | 35 +++ docs-sources/requirements.pip | 4 + docs-sources/test-frameworks/scalatest.md | 46 +++ docs-sources/test-frameworks/specs2.md | 44 +++ docs-sources/test-frameworks/summary.md | 9 + docs-sources/test-frameworks/utest.md | 44 +++ docs-sources/usage/derivation.md | 23 ++ docs-sources/usage/extending.md | 22 ++ docs-sources/usage/ignoring.md | 27 ++ docs-sources/usage/output.md | 49 +++ docs-sources/usage/replacing.md | 30 ++ docs-sources/usage/sequences.md | 66 ++++ docs-sources/watch.sh | 2 + generated-docs/out/.gitignore | 2 + generated-docs/out/.python-version | 1 + generated-docs/out/Makefile | 19 ++ generated-docs/out/conf.py | 192 ++++++++++++ generated-docs/out/index.md | 142 +++++++++ generated-docs/out/integrations/cats.md | 47 +++ generated-docs/out/integrations/refined.md | 49 +++ generated-docs/out/integrations/tagging.md | 46 +++ generated-docs/out/make.bat | 35 +++ generated-docs/out/requirements.pip | 4 + .../out/test-frameworks/scalatest.md | 46 +++ generated-docs/out/test-frameworks/specs2.md | 44 +++ generated-docs/out/test-frameworks/summary.md | 9 + generated-docs/out/test-frameworks/utest.md | 44 +++ generated-docs/out/usage/derivation.md | 23 ++ generated-docs/out/usage/extending.md | 22 ++ generated-docs/out/usage/ignoring.md | 24 ++ generated-docs/out/usage/output.md | 56 ++++ generated-docs/out/usage/replacing.md | 31 ++ generated-docs/out/usage/sequences.md | 82 +++++ generated-docs/out/watch.sh | 2 + 50 files changed, 1789 insertions(+), 542 deletions(-) create mode 100644 .readthedocs.yaml rename core/src/main/scala/com/softwaremill/diffx/generic/auto/{DiffDerivation.scala => AutoDerivation.scala} (74%) create mode 100644 docs-sources/.gitignore create mode 100644 docs-sources/.python-version create mode 100644 docs-sources/Makefile delete mode 100644 docs-sources/README.md create mode 100644 docs-sources/conf.py create mode 100644 docs-sources/index.md create mode 100644 docs-sources/integrations/cats.md create mode 100644 docs-sources/integrations/refined.md create mode 100644 docs-sources/integrations/tagging.md create mode 100644 docs-sources/make.bat create mode 100644 docs-sources/requirements.pip create mode 100644 docs-sources/test-frameworks/scalatest.md create mode 100644 docs-sources/test-frameworks/specs2.md create mode 100644 docs-sources/test-frameworks/summary.md create mode 100644 docs-sources/test-frameworks/utest.md create mode 100644 docs-sources/usage/derivation.md create mode 100644 docs-sources/usage/extending.md create mode 100644 docs-sources/usage/ignoring.md create mode 100644 docs-sources/usage/output.md create mode 100644 docs-sources/usage/replacing.md create mode 100644 docs-sources/usage/sequences.md create mode 100755 docs-sources/watch.sh create mode 100644 generated-docs/out/.gitignore create mode 100644 generated-docs/out/.python-version create mode 100644 generated-docs/out/Makefile create mode 100644 generated-docs/out/conf.py create mode 100644 generated-docs/out/index.md create mode 100644 generated-docs/out/integrations/cats.md create mode 100644 generated-docs/out/integrations/refined.md create mode 100644 generated-docs/out/integrations/tagging.md create mode 100644 generated-docs/out/make.bat create mode 100644 generated-docs/out/requirements.pip create mode 100644 generated-docs/out/test-frameworks/scalatest.md create mode 100644 generated-docs/out/test-frameworks/specs2.md create mode 100644 generated-docs/out/test-frameworks/summary.md create mode 100644 generated-docs/out/test-frameworks/utest.md create mode 100644 generated-docs/out/usage/derivation.md create mode 100644 generated-docs/out/usage/extending.md create mode 100644 generated-docs/out/usage/ignoring.md create mode 100644 generated-docs/out/usage/output.md create mode 100644 generated-docs/out/usage/replacing.md create mode 100644 generated-docs/out/usage/sequences.md create mode 100755 generated-docs/out/watch.sh diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..4d0de76a --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,11 @@ +# Required +version: 2 + +sphinx: + configuration: generated-docs/out/conf.py + +# Optionally set the version of Python and requirements required to build your docs +python: + version: 3.7 + install: + - requirements: generated-docs/out/requirements.pip \ No newline at end of file diff --git a/README.md b/README.md index 25a1b74d..27c4ef94 100644 --- a/README.md +++ b/README.md @@ -1,294 +1,23 @@ ![diffx](https://github.com/softwaremill/diffx/raw/master/banner.png) -[![Build Status](https://travis-ci.org/softwaremill/diffx.svg?branch=master)](https://travis-ci.org/softwaremill/diffx) +[![Build Status](https://img.shields.io/github/workflow/status/softwaremill/diffx/CI/master)](https://github.com/softwaremill/diffx/actions) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.diffx/diffx-core_2.13/badge.svg)](https://search.maven.org/search?q=g:com.softwaremill.diffx) [![Gitter](https://badges.gitter.im/softwaremill/diffx.svg)](https://gitter.im/softwaremill/diffx?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Mergify Status](https://img.shields.io/endpoint.svg?url=https://gh.mergify.io/badges/softwaremill/diffx&style=flat)](https://mergify.io) [![Scala Steward badge](https://img.shields.io/badge/Scala_Steward-helping-brightgreen.svg?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=)](https://scala-steward.org) -Pretty diffs for case classes. +Pretty diffs for case classes. -The library is published for Scala 2.12 and 2.13. +## Documentation -## Table of contents -- [goals of the project](#goals-of-the-project) -- [teaser](#teaser) -- [derivation](#derivation) -- [colors](#colors) -- integrations - - [scalatest](#scalatest-integration) - - [specs2](#specs2-integration) - - [utest](#utest-integration) - - [other](#other-3rd-party-libraries-support) -- [ignoring](#ignoring) -- [customization](#customization) -- [similar projects](#similar-projects) -- [commercial support](#commercial-support) +diffx documentation is available at [diffx-scala.readthedocs.io](https://diffx-scala.readthedocs.io). -## Goals of the project +## Modifying documentation +The documentation is typechecked using `mdoc`. The sources for the documentation exist in `docs-sources`. Don't modify the generated documentation in `generated-docs`, as these files will get overwritten! -- human-readable case class diffs -- support for popular testing frameworks -- OOTB collections support -- OOTB non-case class support -- smaller compilation overhead compared to shapless based solutions (thanks to magnolia <3) -- programmer friendly and type safe api for partial ignore +When generating documentation, it's best to set the version to the current one, so that the generated doc files don't include modifications with the current snapshot version. -## Teaser -Add the following dependency: - -```scala -"com.softwaremill.diffx" %% "diffx-core" % "0.5.0" -``` - -```scala -sealed trait Parent -case class Bar(s: String, i: Int) extends Parent -case class Foo(bar: Bar, b: List[Int], parent: Option[Parent]) extends Parent - -val right: Foo = Foo( - Bar("asdf", 5), - List(123, 1234), - Some(Bar("asdf", 5)) -) -// right: Foo = Foo( -// bar = Bar(s = "asdf", i = 5), -// b = List(123, 1234), -// parent = Some(value = Bar(s = "asdf", i = 5)) -// ) - -val left: Foo = Foo( - Bar("asdf", 66), - List(1234), - Some(right) -) -// left: Foo = Foo( -// bar = Bar(s = "asdf", i = 66), -// b = List(1234), -// parent = Some( -// value = Foo( -// bar = Bar(s = "asdf", i = 5), -// b = List(123, 1234), -// parent = Some(value = Bar(s = "asdf", i = 5)) -// ) -// ) -// ) - -import com.softwaremill.diffx.generic.auto._ -import com.softwaremill.diffx._ -compare(left, right) -// res0: DiffResult = DiffResultObject( -// name = "Foo", -// fields = ListMap( -// "bar" -> DiffResultObject( -// name = "Bar", -// fields = ListMap( -// "s" -> Identical(value = "asdf"), -// "i" -> DiffResultValue(left = 66, right = 5) -// ) -// ), -// "b" -> DiffResultObject( -// name = "List", -// fields = ListMap( -// "0" -> DiffResultValue(left = 1234, right = 123), -// "1" -> DiffResultMissing(value = 1234) -// ) -// ), -// "parent" -> DiffResultValue( -// left = "repl.MdocSession.App.Foo", -// right = "repl.MdocSession.App.Bar" -// ) -// ) -// ) -``` - -Will result in: - -![example](https://github.com/softwaremill/diff-x/blob/master/example.png?raw=true) - - -## Derivation - -Diffx supports auto and semi-auto derivation. - -For semi-auto derivation you don't need any additional import, just define your instances using: -```scala -case class Product(name: String) -case class Basket(products: List[Product]) - -implicit val productDiff = Diff.derived[Product] -implicit val basketDiff = Diff.derived[Basket] -``` - -To use auto derivation add following import - -`import com.softwaremill.diffx.generic.auto._` - -or - -extend trait - -`com.softwaremill.diffx.generic.DiffDerivation` - -**Auto derivation will have a huge impact on compilation times**, because of that it is recommended to use `semi-auto` derivation. - - -## Colors - -When running tests through sbt, default diffx's colors work well on both dark and light backgrounds. -Unfortunately Intellij Idea forces the default color to red when displaying test's error. -This means that it is impossible to print something with the standard default color (either white or black depending on the color scheme). - -To have better colors, external information about the desired theme is required. -Specify environment variable `DIFFX_COLOR_THEME` and set it to either `light` or `dark`. -I had to specify it in `/etc/environment` rather than home profile for Intellij Idea to picked it up. - -If anyone has an idea how this could be improved, I am open for suggestions. - -## Scalatest integration - -To use with scalatest, add the following dependency: - -```scala -"com.softwaremill.diffx" %% "diffx-scalatest" % "0.5.0" % Test -``` - -Then, extend the `com.softwaremill.diffx.scalatest.DiffMatcher` trait or `import com.softwaremill.diffx.scalatest.DiffMatcher._`. -After that you will be able to use syntax such as: - -```scala -import org.scalatest.matchers.should.Matchers._ -import com.softwaremill.diffx.scalatest.DiffMatcher._ - -left should matchTo(right) -``` - -Giving you nice error messages: - -## Specs2 integration - -To use with specs2, add the following dependency: - -```scala -"com.softwaremill.diffx" %% "diffx-specs2" % "0.5.0" % Test -``` - -Then, extend the `com.softwaremill.diffx.specs2.DiffMatcher` trait or `import com.softwaremill.diffx.specs2.DiffMatcher._`. -After that you will be able to use syntax such as: - -```scala -import org.specs2.matcher.MustMatchers.{left => _, right => _, _} -import com.softwaremill.diffx.specs2.DiffMatcher._ - -left must matchTo(right) -``` - -## Utest integration - -To use with utest, add following dependency: - -```scala -"com.softwaremill.diffx" %% "diffx-utest" % "0.5.0" % Test -``` - -Then, mixin `DiffxAssertions` trait or add `import com.softwaremill.diffx.utest.DiffxAssertions._` to your test code. -To assert using diffx use `assertEquals` as follows: - -```scala -import com.softwaremill.diffx.utest.DiffxAssertions._ -assertEqual(left, right) -``` - -## Ignoring - -Fields can be excluded from comparision by calling the `ignore` method on the `Diff` instance. -Since `Diff` instances are immutable, the `ignore` method creates a copy of the instance with modified logic. -You can use this instance explicitly. -If you still would like to use it implicitly, you first need to summon the instance of the `Diff` typeclass using -the `Derived` typeclass wrapper: `Derived[Diff[Person]]`. Thanks to that trick, later you will be able to put your modified -instance of the `Diff` typeclass into the implicit scope. The whole process looks as follows: - -```scala -case class Person(name:String, age:Int) -implicit val modifiedDiff: Diff[Person] = Derived[Diff[Person]].ignore(_.name) -``` - -## Customization - -If you'd like to implement custom matching logic for the given type, create an implicit `Diff` instance for that -type, and make sure it's in scope when any `Diff` instances depending on that type are created. - -Consider following example with `NonEmptyList` from cats. `NonEmptyList` is implemented as case class so diffx -will create a `Diff[NonEmptyList]` typeclass instance using magnolia derivation. - -Obviously that's not what we usually want. In most of the cases we would like `NonEmptyList` to be compared as a list. -Diffx already has an instance of a typeclass for a list. One more thing to do is to use that typeclass by converting `NonEmptyList` to list which can be done using `contramap` method. - -The final code looks as follows: - -```scala -import cats.data.NonEmptyList -implicit def nelDiff[T: Diff]: Diff[NonEmptyList[T]] = - Diff[List[T]].contramap[NonEmptyList[T]](_.toList) -``` - -And here's an example of customizing the `Diff` instance for a child class of a sealed trait - -```scala -sealed trait ABParent -case class A(id: String, name: String) extends ABParent -case class B(id: String, name: String) extends ABParent - -implicit val diffA: Diff[A] = Derived[Diff[A]].ignore(_.id) -``` -```scala -val a1: ABParent = A("1", "X") -// a1: ABParent = A(id = "1", name = "X") -val a2: ABParent = A("2", "X") -// a2: ABParent = A(id = "2", name = "X") - -compare(a1, a2) -// res6: DiffResult = Identical(value = A(id = "1", name = "X")) -``` - -As you can see instead of summoning bare instance of `Diff` for given `A` we summoned `Derived[Diff[A]]`. -This is required in order to workaround self reference error. - -You may need to add `-Wmacros:after` Scala compiler option to make sure to check for unused implicits -after macro expansion. -If you get warnings from Magnolia which looks like `magnolia: using fallback derivation for TYPE`, -you can use the [Silencer](https://github.com/ghik/silencer) compiler plugin to silent the warning -with the compiler option `"-P:silencer:globalFilters=^magnolia: using fallback derivation.*$"` - -## Other 3rd party libraries support - -- [com.softwaremill.common.tagging](https://github.com/softwaremill/scala-common) - ```scala - "com.softwaremill.diffx" %% "diffx-tagging" % "0.5.0" - ``` - `com.softwaremill.diffx.tagging.DiffTaggingSupport` -- [eu.timepit.refined](https://github.com/fthomas/refined) - ```scala - "com.softwaremill.diffx" %% "diffx-refined" % "0.5.0" - ``` - `com.softwaremill.diffx.refined.RefinedSupport` -- [org.typelevel.cats](https://github.com/typelevel/cats) - ```scala - "com.softwaremill.diffx" %% "diffx-cats" % "0.5.0" - ``` - `com.softwaremill.diffx.cats.DiffCatsInstances` - -## Similar projects - -There is a number of similar projects from which diffx draws inspiration. - -Below is a list of some of them, which I am aware of, with their main differences: -- [xotai/diff](https://github.com/xdotai/diff) - based on shapeless, seems not to be activly developed anymore -- [ratatool-diffy](https://github.com/spotify/ratatool/tree/master/ratatool-diffy) - the main purpose is to compare large data sets stored on gs or hdfs - -## Commercial Support - -We offer commercial support for diffx and related technologies, as well as development services. [Contact us](https://softwaremill.com) to learn more about our offer! +That is, in sbt run: `set version := "0.5.0"`, before running `mdoc` in `docs`. ## Copyright diff --git a/build.sbt b/build.sbt index 86589ed5..1a224237 100644 --- a/build.sbt +++ b/build.sbt @@ -16,11 +16,10 @@ lazy val commonSettings: Seq[Def.Setting[_]] = commonSmlBuildSettings ++ ossPubl scmInfo := Some(ScmInfo(url("https://github.com/softwaremill/diffx"), "git@github.com:softwaremill/diffx.git")), ideSkipProject := (scalaVersion.value != scalaIdeaVersion) || thisProjectRef.value.project.contains("JS"), updateDocs := Def.taskDyn { - val files1 = - UpdateVersionInDocs(sLog.value, organization.value, version.value, List(file("docs-sources") / "README.md")) + val files1 = UpdateVersionInDocs(sLog.value, organization.value, version.value) Def.task { (docs.jvm(scala213) / mdoc).toTask("").value - files1 ++ Seq(file("README.md")) + files1 ++ Seq(file("generated-docs/out")) } }.value ) @@ -188,9 +187,9 @@ lazy val docs = (projectMatrix in file("generated-docs")) // important: it must mdocVariables := Map( "VERSION" -> version.value ), - mdocOut := file(".") + mdocOut := file("generated-docs/out") ) - .dependsOn(core, scalatest, specs2, utest, refined, tagging) + .dependsOn(core, scalatest, specs2, utest, refined, tagging, cats) .jvmPlatform(scalaVersions = List(scala213)) val testJVM = taskKey[Unit]("Test JVM projects") diff --git a/core/src/main/scala/com/softwaremill/diffx/Diff.scala b/core/src/main/scala/com/softwaremill/diffx/Diff.scala index e7bc5904..76518227 100644 --- a/core/src/main/scala/com/softwaremill/diffx/Diff.scala +++ b/core/src/main/scala/com/softwaremill/diffx/Diff.scala @@ -39,7 +39,7 @@ object Diff extends MiddlePriorityDiff with TupleInstances { /** Create a Diff instance using [[Object#equals]] */ def useEquals[T]: Diff[T] = Diff.fallback[T] - def approximateNumericDiff[T: Numeric](epsilon: T): Diff[T] = + def approximate[T: Numeric](epsilon: T): Diff[T] = new ApproximateDiffForNumeric[T](epsilon) def derived[T]: Derived[Diff[T]] = macro MagnoliaDerivedMacro.derivedGen[T] diff --git a/core/src/main/scala/com/softwaremill/diffx/generic/auto/DiffDerivation.scala b/core/src/main/scala/com/softwaremill/diffx/generic/auto/AutoDerivation.scala similarity index 74% rename from core/src/main/scala/com/softwaremill/diffx/generic/auto/DiffDerivation.scala rename to core/src/main/scala/com/softwaremill/diffx/generic/auto/AutoDerivation.scala index db32e629..2a2b3240 100644 --- a/core/src/main/scala/com/softwaremill/diffx/generic/auto/DiffDerivation.scala +++ b/core/src/main/scala/com/softwaremill/diffx/generic/auto/AutoDerivation.scala @@ -2,9 +2,9 @@ package com.softwaremill.diffx.generic import com.softwaremill.diffx.{Derived, Diff} -package object auto extends DiffDerivation +package object auto extends AutoDerivation -trait DiffDerivation extends DiffMagnoliaDerivation { +trait AutoDerivation extends DiffMagnoliaDerivation { implicit def diffForCaseClass[T]: Derived[Diff[T]] = macro MagnoliaDerivedMacro.derivedGen[T] // Implicit conversion diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/ApproximateDiffForNumeric.scala b/core/src/main/scala/com/softwaremill/diffx/instances/ApproximateDiffForNumeric.scala index 5667aa65..ba0d08b3 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/ApproximateDiffForNumeric.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/ApproximateDiffForNumeric.scala @@ -5,7 +5,7 @@ import com.softwaremill.diffx._ private[diffx] class ApproximateDiffForNumeric[T: Numeric](epsilon: T) extends Diff[T] { override def apply(left: T, right: T, context: DiffContext): DiffResult = { val numeric = implicitly[Numeric[T]] - if (numeric.lt(numeric.abs(numeric.minus(left, right)), epsilon)) { + if (numeric.lt(epsilon, numeric.abs(numeric.minus(left, right)))) { DiffResultValue(left, right) } else { Identical(left) diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala index 599cd1d1..96ceba77 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala @@ -24,6 +24,14 @@ class DiffTest extends AnyFreeSpec with Matchers { "contravariant" in { compare(Some(1), Option(1)) shouldBe Identical(1) } + "approximate - identical" in { + val diff = Diff.approximate[Double](0.05) + diff(0.12, 0.14) shouldBe Identical(0.12) + } + "approximate - different" in { + val diff = Diff.approximate[Double](0.05) + diff(0.12, 0.19) shouldBe DiffResultValue(0.12, 0.19) + } } "options" - { @@ -287,7 +295,7 @@ class DiffTest extends AnyFreeSpec with Matchers { "compare lists using object matcher comparator" in { val o1 = Organization(List(p1, p2)) val o2 = Organization(List(p2, p1)) - implicit val om: ObjectMatcher[(Int, Person)] = ObjectMatcher.byValue[Int, Person](ObjectMatcher.by(_.name)) + implicit val om: ObjectMatcher[(Int, Person)] = ObjectMatcher.byValue(ObjectMatcher.by(_.name)) compare(o1, o2) shouldBe Identical(Organization(List(p1, p2))) } diff --git a/docs-sources/.gitignore b/docs-sources/.gitignore new file mode 100644 index 00000000..7bb92e53 --- /dev/null +++ b/docs-sources/.gitignore @@ -0,0 +1,2 @@ +_build +_build_html \ No newline at end of file diff --git a/docs-sources/.python-version b/docs-sources/.python-version new file mode 100644 index 00000000..0b2eb36f --- /dev/null +++ b/docs-sources/.python-version @@ -0,0 +1 @@ +3.7.2 diff --git a/docs-sources/Makefile b/docs-sources/Makefile new file mode 100644 index 00000000..298ea9e2 --- /dev/null +++ b/docs-sources/Makefile @@ -0,0 +1,19 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs-sources/README.md b/docs-sources/README.md deleted file mode 100644 index 9e97d167..00000000 --- a/docs-sources/README.md +++ /dev/null @@ -1,253 +0,0 @@ -![diffx](https://github.com/softwaremill/diffx/raw/master/banner.png) - -[![Build Status](https://travis-ci.org/softwaremill/diffx.svg?branch=master)](https://travis-ci.org/softwaremill/diffx) -[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.diffx/diffx-core_2.13/badge.svg)](https://search.maven.org/search?q=g:com.softwaremill.diffx) -[![Gitter](https://badges.gitter.im/softwaremill/diffx.svg)](https://gitter.im/softwaremill/diffx?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) -[![Mergify Status](https://img.shields.io/endpoint.svg?url=https://gh.mergify.io/badges/softwaremill/diffx&style=flat)](https://mergify.io) -[![Scala Steward badge](https://img.shields.io/badge/Scala_Steward-helping-brightgreen.svg?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=)](https://scala-steward.org) - -Pretty diffs for case classes. - -The library is published for Scala 2.12 and 2.13. - -## Table of contents -- [goals of the project](#goals-of-the-project) -- [teaser](#teaser) -- [derivation](#derivation) -- [colors](#colors) -- integrations - - [scalatest](#scalatest-integration) - - [specs2](#specs2-integration) - - [utest](#utest-integration) - - [other](#other-3rd-party-libraries-support) -- [ignoring](#ignoring) -- [customization](#customization) -- [similar projects](#similar-projects) -- [commercial support](#commercial-support) - -## Goals of the project - -- human-readable case class diffs -- support for popular testing frameworks -- OOTB collections support -- OOTB non-case class support -- smaller compilation overhead compared to shapless based solutions (thanks to magnolia <3) -- programmer friendly and type safe api for partial ignore - -## Teaser -Add the following dependency: - -```scala -"com.softwaremill.diffx" %% "diffx-core" % "@VERSION@" -``` - -```scala mdoc -sealed trait Parent -case class Bar(s: String, i: Int) extends Parent -case class Foo(bar: Bar, b: List[Int], parent: Option[Parent]) extends Parent - -val right: Foo = Foo( - Bar("asdf", 5), - List(123, 1234), - Some(Bar("asdf", 5)) -) - -val left: Foo = Foo( - Bar("asdf", 66), - List(1234), - Some(right) -) - -import com.softwaremill.diffx.generic.auto._ -import com.softwaremill.diffx._ -compare(left, right) -``` - -Will result in: - -![example](https://github.com/softwaremill/diff-x/blob/master/example.png?raw=true) - - -## Derivation - -Diffx supports auto and semi-auto derivation. - -For semi-auto derivation you don't need any additional import, just define your instances using: -```scala mdoc:compile-only -case class Product(name: String) -case class Basket(products: List[Product]) - -implicit val productDiff = Diff.derived[Product] -implicit val basketDiff = Diff.derived[Basket] -``` - -To use auto derivation add following import - -`import com.softwaremill.diffx.generic.auto._` - -or - -extend trait - -`com.softwaremill.diffx.generic.DiffDerivation` - -**Auto derivation will have a huge impact on compilation times**, because of that it is recommended to use `semi-auto` derivation. - - -## Colors - -When running tests through sbt, default diffx's colors work well on both dark and light backgrounds. -Unfortunately Intellij Idea forces the default color to red when displaying test's error. -This means that it is impossible to print something with the standard default color (either white or black depending on the color scheme). - -To have better colors, external information about the desired theme is required. -Specify environment variable `DIFFX_COLOR_THEME` and set it to either `light` or `dark`. -I had to specify it in `/etc/environment` rather than home profile for Intellij Idea to picked it up. - -If anyone has an idea how this could be improved, I am open for suggestions. - -## Scalatest integration - -To use with scalatest, add the following dependency: - -```scala -"com.softwaremill.diffx" %% "diffx-scalatest" % "@VERSION@" % Test -``` - -Then, extend the `com.softwaremill.diffx.scalatest.DiffMatcher` trait or `import com.softwaremill.diffx.scalatest.DiffMatcher._`. -After that you will be able to use syntax such as: - -```scala mdoc:compile-only -import org.scalatest.matchers.should.Matchers._ -import com.softwaremill.diffx.scalatest.DiffMatcher._ - -left should matchTo(right) -``` - -Giving you nice error messages: - -## Specs2 integration - -To use with specs2, add the following dependency: - -```scala -"com.softwaremill.diffx" %% "diffx-specs2" % "@VERSION@" % Test -``` - -Then, extend the `com.softwaremill.diffx.specs2.DiffMatcher` trait or `import com.softwaremill.diffx.specs2.DiffMatcher._`. -After that you will be able to use syntax such as: - -```scala mdoc:compile-only -import org.specs2.matcher.MustMatchers.{left => _, right => _, _} -import com.softwaremill.diffx.specs2.DiffMatcher._ - -left must matchTo(right) -``` - -## Utest integration - -To use with utest, add following dependency: - -```scala -"com.softwaremill.diffx" %% "diffx-utest" % "@VERSION@" % Test -``` - -Then, mixin `DiffxAssertions` trait or add `import com.softwaremill.diffx.utest.DiffxAssertions._` to your test code. -To assert using diffx use `assertEquals` as follows: - -```scala mdoc:compile-only -import com.softwaremill.diffx.utest.DiffxAssertions._ -assertEqual(left, right) -``` - -## Ignoring - -Fields can be excluded from comparision by calling the `ignore` method on the `Diff` instance. -Since `Diff` instances are immutable, the `ignore` method creates a copy of the instance with modified logic. -You can use this instance explicitly. -If you still would like to use it implicitly, you first need to summon the instance of the `Diff` typeclass using -the `Derived` typeclass wrapper: `Derived[Diff[Person]]`. Thanks to that trick, later you will be able to put your modified -instance of the `Diff` typeclass into the implicit scope. The whole process looks as follows: - -```scala mdoc:compile-only -case class Person(name:String, age:Int) -implicit val modifiedDiff: Diff[Person] = Derived[Diff[Person]].ignore(_.name) -``` - -## Customization - -If you'd like to implement custom matching logic for the given type, create an implicit `Diff` instance for that -type, and make sure it's in scope when any `Diff` instances depending on that type are created. - -Consider following example with `NonEmptyList` from cats. `NonEmptyList` is implemented as case class so diffx -will create a `Diff[NonEmptyList]` typeclass instance using magnolia derivation. - -Obviously that's not what we usually want. In most of the cases we would like `NonEmptyList` to be compared as a list. -Diffx already has an instance of a typeclass for a list. One more thing to do is to use that typeclass by converting `NonEmptyList` to list which can be done using `contramap` method. - -The final code looks as follows: - -```scala mdoc:nest -import cats.data.NonEmptyList -implicit def nelDiff[T: Diff]: Diff[NonEmptyList[T]] = - Diff[List[T]].contramap[NonEmptyList[T]](_.toList) -``` - -And here's an example of customizing the `Diff` instance for a child class of a sealed trait - -```scala mdoc:silent -sealed trait ABParent -case class A(id: String, name: String) extends ABParent -case class B(id: String, name: String) extends ABParent - -implicit val diffA: Diff[A] = Derived[Diff[A]].ignore(_.id) -``` -```scala mdoc -val a1: ABParent = A("1", "X") -val a2: ABParent = A("2", "X") - -compare(a1, a2) -``` - -As you can see instead of summoning bare instance of `Diff` for given `A` we summoned `Derived[Diff[A]]`. -This is required in order to workaround self reference error. - -You may need to add `-Wmacros:after` Scala compiler option to make sure to check for unused implicits -after macro expansion. -If you get warnings from Magnolia which looks like `magnolia: using fallback derivation for TYPE`, -you can use the [Silencer](https://github.com/ghik/silencer) compiler plugin to silent the warning -with the compiler option `"-P:silencer:globalFilters=^magnolia: using fallback derivation.*$"` - -## Other 3rd party libraries support - -- [com.softwaremill.common.tagging](https://github.com/softwaremill/scala-common) - ```scala - "com.softwaremill.diffx" %% "diffx-tagging" % "@VERSION@" - ``` - `com.softwaremill.diffx.tagging.DiffTaggingSupport` -- [eu.timepit.refined](https://github.com/fthomas/refined) - ```scala - "com.softwaremill.diffx" %% "diffx-refined" % "@VERSION@" - ``` - `com.softwaremill.diffx.refined.RefinedSupport` -- [org.typelevel.cats](https://github.com/typelevel/cats) - ```scala - "com.softwaremill.diffx" %% "diffx-cats" % "@VERSION@" - ``` - `com.softwaremill.diffx.cats.DiffCatsInstances` - -## Similar projects - -There is a number of similar projects from which diffx draws inspiration. - -Below is a list of some of them, which I am aware of, with their main differences: -- [xotai/diff](https://github.com/xdotai/diff) - based on shapeless, seems not to be activly developed anymore -- [ratatool-diffy](https://github.com/spotify/ratatool/tree/master/ratatool-diffy) - the main purpose is to compare large data sets stored on gs or hdfs - -## Commercial Support - -We offer commercial support for diffx and related technologies, as well as development services. [Contact us](https://softwaremill.com) to learn more about our offer! - -## Copyright - -Copyright (C) 2019 SoftwareMill [https://softwaremill.com](https://softwaremill.com). diff --git a/docs-sources/conf.py b/docs-sources/conf.py new file mode 100644 index 00000000..53a552c9 --- /dev/null +++ b/docs-sources/conf.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- +# +# sttp documentation build configuration file, created by +# sphinx-quickstart on Thu Oct 12 15:51:09 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'diffx-scala' +copyright = '2021, SoftwareMill' +author = 'SoftwareMill' + +# The short X.Y version +version = '' +# The full version, including alpha/beta/rc tags +release = '' + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +from recommonmark.parser import CommonMarkParser +from recommonmark.transform import AutoStructify + +source_parsers = { + '.md': CommonMarkParser, +} + +source_suffix = ['.rst', '.md'] + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = { + '**': [ + 'about.html', + 'navigation.html', + 'relations.html', # needs 'show_related': True theme option to display + 'searchbox.html', + 'donate.html', + ] +} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'diffx-scaladoc' + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'diffx-scala.tex', 'diffx-scala Documentation', + 'SoftwareMill', 'manual'), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'diffx-scala', 'diffx-scala Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'diffx-scala', 'diffx-scala Documentation', + author, 'diffx-scala', 'One line description of project.', + 'Miscellaneous'), +] + +highlight_language = 'scala' + +# configure edit on github: https://docs.readthedocs.io/en/latest/guides/vcs.html +html_context = { + 'display_github': True, # Integrate GitHub + 'github_user': 'softwaremill', # Username + 'github_repo': 'diffx', # Repo name + 'github_version': 'master', # Version + 'conf_py_path': '/docs-sources/', # Path in the checkout to the docs root +} + +# app setup hook +def setup(app): + app.add_config_value('recommonmark_config', { + 'auto_toc_tree_section': 'Contents', + 'enable_auto_doc_ref': False + }, True) + app.add_transform(AutoStructify) + diff --git a/docs-sources/index.md b/docs-sources/index.md new file mode 100644 index 00000000..74a81536 --- /dev/null +++ b/docs-sources/index.md @@ -0,0 +1,103 @@ +# diffx: Pretty diffs for case classes + +Welcome! + +[diffx](https://github.com/softwaremill/diffx) is an open-source library which aims to display differences between +complex structures in a way that they are easily noticeable. + +Here's a quick example of diffx in action: + +```scala mdoc +sealed trait Parent +case class Bar(s: String, i: Int) extends Parent +case class Foo(bar: Bar, b: List[Int], parent: Option[Parent]) extends Parent + +val right: Foo = Foo( + Bar("asdf", 5), + List(123, 1234), + Some(Bar("asdf", 5)) +) + +val left: Foo = Foo( + Bar("asdf", 66), + List(1234), + Some(right) +) + +import com.softwaremill.diffx.generic.auto._ +import com.softwaremill.diffx._ +compare(left, right) +``` + +Will result in: + +![](https://github.com/softwaremill/diffx/blob/master/example.png?raw=true) + +`diffx` is available for Scala 2.12 and 2.13 both jvm and js. + +The core of `diffx` comes in a single jar. + +To integrate with the test framework of your choice, you'll need to use one of the integration modules. +See the section on [test-frameworks](test-frameworks/summary.md) for a brief overview of supported test frameworks. + +*Auto-derivation is used throughout the documentation for the sake of clarity. Head over to [derivation](usage/derivation.md) for more details* + +## Tips and tricks + +You may need to add `-Wmacros:after` Scala compiler option to make sure to check for unused implicits +after macro expansion. +If you get warnings from Magnolia which looks like `magnolia: using fallback derivation for TYPE`, +you can use the [Silencer](https://github.com/ghik/silencer) compiler plugin to silent the warning +with the compiler option `"-P:silencer:globalFilters=^magnolia: using fallback derivation.*$"` + +## Similar projects + +There is a number of similar projects from which diffx draws inspiration. + +Below is a list of some of them, which I am aware of, with their main differences: +- [xotai/diff](https://github.com/xdotai/diff) - based on shapeless, seems not to be activly developed anymore +- [ratatool-diffy](https://github.com/spotify/ratatool/tree/master/ratatool-diffy) - the main purpose is to compare large data sets stored on gs or hdfs + + +## Sponsors + +Development and maintenance of diffx is sponsored by [SoftwareMill](https://softwaremill.com), +a software development and consulting company. We help clients scale their business through software. Our areas of expertise include backends, distributed systems, blockchain, machine learning and data analytics. + +[![](https://files.softwaremill.com/logo/logo.png "SoftwareMill")](https://softwaremill.com) + +# Table of contents + +```eval_rst +.. toctree:: + :maxdepth: 1 + :caption: Test frameworks + + test-frameworks/scalatest + test-frameworks/specs2 + test-frameworks/utest + test-frameworks/summary + +.. toctree:: + :maxdepth: 1 + :caption: Integrations + + integrations/cats + integrations/tagging + integrations/refined + +.. toctree:: + :maxdepth: 1 + :caption: usage + + usage/derivation + usage/ignoring + usage/replacing + usage/extending + usage/sequences + usage/output +``` + +## Copyright + +Copyright (C) 2019 SoftwareMill [https://softwaremill.com](https://softwaremill.com). diff --git a/docs-sources/integrations/cats.md b/docs-sources/integrations/cats.md new file mode 100644 index 00000000..93b44157 --- /dev/null +++ b/docs-sources/integrations/cats.md @@ -0,0 +1,36 @@ +# cats + +This module contains integration layer between [org.typelevel.cats](https://github.com/typelevel/cats) and `diffx` + +## sbt + +```scala +"com.softwaremill.diffx" %% "diffx-cats" % "@VERSION@" % Test +``` + +## mill + +```scala +ivy"com.softwaremill.diffx::diffx-cats::@VERSION@" +``` + +## Usage + +Assuming you have some data types from the cats library in your hierarchy: +```scala mdoc:silent +import cats.data._ +case class TestData(ints: NonEmptyList[String]) + +val t1 = TestData(NonEmptyList.one("a")) +val t2 = TestData(NonEmptyList.one("b")) +``` + +all you need to do is to put additional diffx implicits into current scope: + +```scala mdoc +import com.softwaremill.diffx.compare +import com.softwaremill.diffx.generic.auto._ + +import com.softwaremill.diffx.cats._ +compare(t1, t2) +``` \ No newline at end of file diff --git a/docs-sources/integrations/refined.md b/docs-sources/integrations/refined.md new file mode 100644 index 00000000..cf4ad830 --- /dev/null +++ b/docs-sources/integrations/refined.md @@ -0,0 +1,40 @@ +# refined + +This module contains integration layer between [eu.timepit.refined](https://github.com/fthomas/refined) and `diffx` + +## sbt + +```scala +"com.softwaremill.diffx" %% "diffx-refined" % "@VERSION@" % Test +``` + +## mill + +```scala +ivy"com.softwaremill.diffx::diffx-refined::@VERSION@" +``` + +## Usage + +Assuming you have some refined types in your hierarchy: + +```scala mdoc:silent +import eu.timepit.refined.types.numeric.PosInt +import eu.timepit.refined.auto._ +import eu.timepit.refined.types.string.NonEmptyString + +case class TestData(posInt: PosInt, nonEmptyString: NonEmptyString) + +val t1 = TestData(1, "foo") +val t2 = TestData(1, "bar") +``` + +all you need to do is to put additional diffx implicits into current scope: + +```scala mdoc +import com.softwaremill.diffx.compare +import com.softwaremill.diffx.generic.auto._ + +import com.softwaremill.diffx.refined._ +compare(t1, t2) +``` \ No newline at end of file diff --git a/docs-sources/integrations/tagging.md b/docs-sources/integrations/tagging.md new file mode 100644 index 00000000..f64f7767 --- /dev/null +++ b/docs-sources/integrations/tagging.md @@ -0,0 +1,39 @@ +# tagging + +This module contains integration layer between [com.softwaremill.common.tagging](https://github.com/softwaremill/scala-common) and `diffx` + +## sbt + +```scala +"com.softwaremill.diffx" %% "diffx-tagging" % "@VERSION@" +``` + +## mill + +```scala +ivy"com.softwaremill.diffx::diffx-tagging::@VERSION@" +``` + +## Usage + +Assuming you have some tagged types in your hierarchy: + +```scala mdoc:silent +import com.softwaremill.tagging._ +sealed trait T1 +sealed trait T2 +case class TestData(p1: Int @@ T1, p2: Int @@ T2) + +val t1 = TestData(1.taggedWith[T1], 1.taggedWith[T2]) +val t2 = TestData(1.taggedWith[T1], 3.taggedWith[T2]) +``` + +all you need to do is to put additional diffx implicits into current scope: + +```scala mdoc +import com.softwaremill.diffx.compare +import com.softwaremill.diffx.generic.auto._ + +import com.softwaremill.diffx.tagging._ +compare(t1, t2) +``` \ No newline at end of file diff --git a/docs-sources/make.bat b/docs-sources/make.bat new file mode 100644 index 00000000..7893348a --- /dev/null +++ b/docs-sources/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/docs-sources/requirements.pip b/docs-sources/requirements.pip new file mode 100644 index 00000000..cb4b2b97 --- /dev/null +++ b/docs-sources/requirements.pip @@ -0,0 +1,4 @@ +sphinx_rtd_theme==0.4.3 +recommonmark==0.5.0 +sphinx==2.0.1 +sphinx-autobuild==0.7.1 diff --git a/docs-sources/test-frameworks/scalatest.md b/docs-sources/test-frameworks/scalatest.md new file mode 100644 index 00000000..54ce0052 --- /dev/null +++ b/docs-sources/test-frameworks/scalatest.md @@ -0,0 +1,46 @@ +# scalatest + +To use with scalatest, add the following dependency: + +## sbt + +```scala +"com.softwaremill.diffx" %% "diffx-scalatest" % "@VERSION@" % Test +``` + +## mill + +```scala +ivy"com.softwaremill.diffx::diffx-scalatest::@VERSION@" +``` + +## Usage + +Then, extend the `com.softwaremill.diffx.scalatest.DiffMatcher` trait or `import com.softwaremill.diffx.scalatest.DiffMatcher._`. +After that you will be able to use syntax such as: + +```scala mdoc:compile-only +import org.scalatest.matchers.should.Matchers._ +import com.softwaremill.diffx.scalatest.DiffMatcher._ +import com.softwaremill.diffx.generic.auto._ + +sealed trait Parent +case class Bar(s: String, i: Int) extends Parent +case class Foo(bar: Bar, b: List[Int], parent: Option[Parent]) extends Parent + +val right: Foo = Foo( + Bar("asdf", 5), + List(123, 1234), + Some(Bar("asdf", 5)) +) + +val left: Foo = Foo( + Bar("asdf", 66), + List(1234), + Some(right) +) + +left should matchTo(right) +``` + +###### TODO add info about misleading compilation error when using above syntax \ No newline at end of file diff --git a/docs-sources/test-frameworks/specs2.md b/docs-sources/test-frameworks/specs2.md new file mode 100644 index 00000000..3e592e7a --- /dev/null +++ b/docs-sources/test-frameworks/specs2.md @@ -0,0 +1,44 @@ +# specs2 + +To use with specs2, add the following dependency: + +## sbt + +```scala +"com.softwaremill.diffx" %% "diffx-specs2" % "@VERSION@" % Test +``` + +## mill + +```scala +ivy"com.softwaremill.diffx::diffx-specs2::@VERSION@" +``` + +## Usage + +Then, extend the `com.softwaremill.diffx.specs2.DiffMatcher` trait or `import com.softwaremill.diffx.specs2.DiffMatcher._`. +After that you will be able to use syntax such as: + +```scala mdoc:compile-only +import org.specs2.matcher.MustMatchers.{left => _, right => _, _} +import com.softwaremill.diffx.specs2.DiffMatcher._ +import com.softwaremill.diffx.generic.auto._ + +sealed trait Parent +case class Bar(s: String, i: Int) extends Parent +case class Foo(bar: Bar, b: List[Int], parent: Option[Parent]) extends Parent + +val right: Foo = Foo( + Bar("asdf", 5), + List(123, 1234), + Some(Bar("asdf", 5)) +) + +val left: Foo = Foo( + Bar("asdf", 66), + List(1234), + Some(right) +) + +left must matchTo(right) +``` diff --git a/docs-sources/test-frameworks/summary.md b/docs-sources/test-frameworks/summary.md new file mode 100644 index 00000000..c31cf754 --- /dev/null +++ b/docs-sources/test-frameworks/summary.md @@ -0,0 +1,9 @@ +# summary + +Following test frameworks are supported by diffx: +- [scalatest](scalatest.md) +- [specs2](specs2.md) +- [utest](utest.md) + +Didn't find your favourite testing library? Don't hesitate and let us know, or you can add it on your own , +as all that needs to be done is to call `compare` function. \ No newline at end of file diff --git a/docs-sources/test-frameworks/utest.md b/docs-sources/test-frameworks/utest.md new file mode 100644 index 00000000..99d6ebcc --- /dev/null +++ b/docs-sources/test-frameworks/utest.md @@ -0,0 +1,44 @@ +# utest + +To use with utest, add following dependency: + +## sbt + +```scala +"com.softwaremill.diffx" %% "diffx-utest" % "@VERSION@" % Test +``` + +## mill + +```scala +ivy"com.softwaremill.diffx::diffx-utest::@VERSION@" +``` + +## Usage + +Then, mixin `DiffxAssertions` trait or add `import com.softwaremill.diffx.utest.DiffxAssertions._` to your test code. +To assert using diffx use `assertEquals` as follows: + +```scala mdoc:compile-only +import com.softwaremill.diffx.utest.DiffxAssertions._ +import com.softwaremill.diffx.generic.auto._ + +sealed trait Parent +case class Bar(s: String, i: Int) extends Parent +case class Foo(bar: Bar, b: List[Int], parent: Option[Parent]) extends Parent + +val right: Foo = Foo( + Bar("asdf", 5), + List(123, 1234), + Some(Bar("asdf", 5)) +) + +val left: Foo = Foo( + Bar("asdf", 66), + List(1234), + Some(right) +) + +assertEqual(left, right) +``` + diff --git a/docs-sources/usage/derivation.md b/docs-sources/usage/derivation.md new file mode 100644 index 00000000..8b9ec049 --- /dev/null +++ b/docs-sources/usage/derivation.md @@ -0,0 +1,23 @@ +# derivation + +Diffx supports auto and semi-auto derivation. + +For semi-auto derivation you don't need any additional import, just define your instances using: +```scala mdoc:compile-only +import com.softwaremill.diffx._ +case class Product(name: String) +case class Basket(products: List[Product]) + +implicit val productDiff = Diff.derived[Product] +implicit val basketDiff = Diff.derived[Basket] +``` + +To use auto derivation add following import + +`import com.softwaremill.diffx.generic.auto._` + +or extend trait + +`com.softwaremill.diffx.generic.AutoDerivation` + +**Auto derivation might have a huge impact on compilation times**, because of that it is recommended to use `semi-auto` derivation. diff --git a/docs-sources/usage/extending.md b/docs-sources/usage/extending.md new file mode 100644 index 00000000..1f345964 --- /dev/null +++ b/docs-sources/usage/extending.md @@ -0,0 +1,22 @@ +# extending + +If you'd like to implement custom matching logic for the given type, create an implicit `Diff` instance for that +type, and make sure it's in scope when any `Diff` instances depending on that type are created. + +Consider following example with `NonEmptyList` from cats. `NonEmptyList` is implemented as case class, +so the default behavior of diffx would be to create a `Diff[NonEmptyList]` typeclass instance using magnolia derivation. + +Obviously that's not what we usually want. In most of the cases we would like `NonEmptyList` to be compared as a list. +Diffx already has an instance of a typeclass for a list (for any iterable to be precise). +All we need to do is to use that typeclass by converting `NonEmptyList` to list which can be done using `contramap` method. + +The final code looks as follows: + +```scala mdoc:compile-only +import com.softwaremill.diffx._ +import _root_.cats.data.NonEmptyList +implicit def nelDiff[T: Diff]: Diff[NonEmptyList[T]] = + Diff[List[T]].contramap[NonEmptyList[T]](_.toList) +``` + +*Note: There is a [diffx-cats](../integrations/cats.md) module, so you don't have to do this* \ No newline at end of file diff --git a/docs-sources/usage/ignoring.md b/docs-sources/usage/ignoring.md new file mode 100644 index 00000000..bb462e12 --- /dev/null +++ b/docs-sources/usage/ignoring.md @@ -0,0 +1,27 @@ +# ignoring + +```scala mdoc:invisible +import com.softwaremill.diffx.generic.auto._ +import com.softwaremill.diffx._ +``` + +Fields can be excluded from comparison by calling the `ignore` method on the `Diff` instance. +Since `Diff` instances are immutable, the `ignore` method creates a copy of the instance with modified logic. +You can use this instance explicitly. + +```scala mdoc:compile-only +case class Person(name:String, age:Int) +val modifiedDiff: Diff[Person] = Diff[Person].ignore(_.name) +``` + +If you still would like to use it implicitly, you first need to summon the instance of the `Diff` typeclass using +the `Derived` typeclass wrapper: `Derived[Diff[Person]]`. Thanks to that trick, later you will be able to put your modified +instance of the `Diff` typeclass into the implicit scope. The whole process looks as follows: + +```scala mdoc:silent +case class Person(name:String, age:Int) +implicit val modifiedDiff: Diff[Person] = Derived[Diff[Person]].ignore(_.age) +``` +```scala mdoc +compare(Person("bob", 25), Person("bob", 30)) +``` \ No newline at end of file diff --git a/docs-sources/usage/output.md b/docs-sources/usage/output.md new file mode 100644 index 00000000..e125a5c2 --- /dev/null +++ b/docs-sources/usage/output.md @@ -0,0 +1,49 @@ +# output + +```scala mdoc:invisible +import com.softwaremill.diffx._ +import com.softwaremill.diffx.generic.auto._ +``` + +`diffx` does its best to show the difference in the most readable way, but obviously the default configuration won't +cover all the use-cases. Because of that, there are few ways how you can modify its output. + +## colors & signs + +I found it confusing to use the terms `expected`/`actual` as there seems to be no golden rule whether to keep expected on the right side or on the left side. +Because of that, diffx refers to the values that are compared as `left` and `right` value. + +By default, the difference is shown in the following form: + +`leftColor(leftValue) -> rightColor(rightValue)` + +which in terms of missing/additional values e.g. in collections looks as follows: + +`leftColor(additionalValue)` in case the value was present on the left-hand side and absent on the right side +`rightColor(missingValue)` in case the value was absent on the left-hand side and present on the right side + + +Where, by default, `rightColor` is green and `leftColor` is red. + +Colors can be customized providing an implicit instance of `ConsoleColorConfig` class. +In fact `rightColor` and `leftColor` are functions `string => string` so they can be modified to do whatever you want with the output. +One example of that would be to use some special characters instead of colors, which might be useful on some environments like e.g. CI. + +````scala mdoc:compile-only +val colorConfigWithPlusMinus: ConsoleColorConfig = + ConsoleColorConfig(default = identity, arrow = identity, right = s => "+" + s, left = s => "-" + s) +```` + +There are two predefined set of colors - light and dark theme. +The default theme is dark, and it can be changed using environment variable - `DIFFX_COLOR_THEME`(`light`/`dark`). + +## skipping identical + +In some cases it might be desired to skip rendering the identical fields, to do that simple set `showIgnored` to `false`. + +```scala mdoc +case class Person(name:String, age:Int) + +val result = compare(Person("Bob", 23), Person("Alice", 23)) +result.show(renderIdentical = false) +``` \ No newline at end of file diff --git a/docs-sources/usage/replacing.md b/docs-sources/usage/replacing.md new file mode 100644 index 00000000..691e329a --- /dev/null +++ b/docs-sources/usage/replacing.md @@ -0,0 +1,30 @@ +# replacing + +Sometimes you might want to compare some nested values using a different comparator but +the type they share is not unique within that hierarchy. + +Consider following example: +```scala mdoc +case class Person(age: Int, weight: Int) +``` + +If we would like to compare `weight` differently than `age` we would have to introduce a new type for `weight` +in order to provide a different `Diff` typeclass for only that field. While in general, it is a good idea to have your types +very precise it might not always be practical or even possible. Fortunately, diffx comes with a mechanism which allows +the replacement of nested diff instances. + +First we need to acquire a lens at given path using `modify` method, +and then we can call `setTo` to replace a particular instance. + +```scala mdoc:silent +import com.softwaremill.diffx._ +implicit val diffPerson: Derived[Diff[Person]] = Diff.derived[Person].modify(_.weight) + .setTo(Diff.approximate(epsilon=5)) +``` + +```scala mdoc +compare(Person(23, 60), Person(23, 62)) +``` + +In fact, replacement is so powerful that ignoring is implemented as a replacement +with the `Diff.ignore` instance. \ No newline at end of file diff --git a/docs-sources/usage/sequences.md b/docs-sources/usage/sequences.md new file mode 100644 index 00000000..55c32120 --- /dev/null +++ b/docs-sources/usage/sequences.md @@ -0,0 +1,66 @@ +# sequences + +`diffx` provides instances for many containers from scala's standard library (e.g. lists, sets, maps), however +not all collections can be simply compared. Ordered collections like lists or vectors are compared by default by +comparing elements under the same indexes. +Maps, by default, are compared by comparing values under the respective keys. +For unordered collections there is an `ObjectMapper` typeclass which defines how elements should be paired. + +## object matcher + +In general, it is a very simple interface, with a bunch of factory methods. +```scala mdoc:compile-only +trait ObjectMatcher[T] { + def isSameObject(left: T, right: T): Boolean +} +``` + +It is mostly useful when comparing unordered collections like sets: + +```scala mdoc:silent +import com.softwaremill.diffx._ +import com.softwaremill.diffx.generic.auto._ +case class Person(id: String, name: String) + +implicit val personMatcher: ObjectMatcher[Person] = ObjectMatcher.by(_.id) +val bob = Person("1","Bob") +``` +```scala mdoc +compare(Set(bob), Set(bob, Person("2","Alice"))) +``` + +It can be also used to modify how the entries from maps are paired. +In below example we tell `diffx` to compare these maps by paring entries by values using the defined `personMatcher` +```scala mdoc:reset:silent +import com.softwaremill.diffx._ +import com.softwaremill.diffx.generic.auto._ +case class Person(id: String, name: String) + +val personMatcher: ObjectMatcher[Person] = ObjectMatcher.by(_.id) +implicit val om: ObjectMatcher[(String, Person)] = ObjectMatcher.byValue(personMatcher) +val bob = Person("1","Bob") +``` + +```scala mdoc +compare(Map("1" -> bob), Map("2" -> bob)) +``` + +Last but not least you can use `objectMatcher` to customize paring when comparing indexed collections. +Such collections are treated similarly to maps (they use key-value object matcher), +but the key type is bound to `Int`. + +```scala mdoc:reset:silent +import com.softwaremill.diffx._ +import com.softwaremill.diffx.generic.auto._ +case class Person(id: String, name: String) + +implicit val personMatcher: ObjectMatcher[(Int, Person)] = + ObjectMatcher.byValue(ObjectMatcher.by(_.id)) +val bob = Person("1","Bob") +val alice = Person("2","Alice") +``` +```scala mdoc +compare(List(bob, alice), List(alice, bob)) +``` + +*Note: `ObjectMatcher` can be also passed explicitly, either upon creation or during modification* \ No newline at end of file diff --git a/docs-sources/watch.sh b/docs-sources/watch.sh new file mode 100755 index 00000000..24c43727 --- /dev/null +++ b/docs-sources/watch.sh @@ -0,0 +1,2 @@ +#!/bin/bash +sphinx-autobuild . _build/html diff --git a/generated-docs/out/.gitignore b/generated-docs/out/.gitignore new file mode 100644 index 00000000..7bb92e53 --- /dev/null +++ b/generated-docs/out/.gitignore @@ -0,0 +1,2 @@ +_build +_build_html \ No newline at end of file diff --git a/generated-docs/out/.python-version b/generated-docs/out/.python-version new file mode 100644 index 00000000..0b2eb36f --- /dev/null +++ b/generated-docs/out/.python-version @@ -0,0 +1 @@ +3.7.2 diff --git a/generated-docs/out/Makefile b/generated-docs/out/Makefile new file mode 100644 index 00000000..298ea9e2 --- /dev/null +++ b/generated-docs/out/Makefile @@ -0,0 +1,19 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/generated-docs/out/conf.py b/generated-docs/out/conf.py new file mode 100644 index 00000000..53a552c9 --- /dev/null +++ b/generated-docs/out/conf.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- +# +# sttp documentation build configuration file, created by +# sphinx-quickstart on Thu Oct 12 15:51:09 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'diffx-scala' +copyright = '2021, SoftwareMill' +author = 'SoftwareMill' + +# The short X.Y version +version = '' +# The full version, including alpha/beta/rc tags +release = '' + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +from recommonmark.parser import CommonMarkParser +from recommonmark.transform import AutoStructify + +source_parsers = { + '.md': CommonMarkParser, +} + +source_suffix = ['.rst', '.md'] + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = { + '**': [ + 'about.html', + 'navigation.html', + 'relations.html', # needs 'show_related': True theme option to display + 'searchbox.html', + 'donate.html', + ] +} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'diffx-scaladoc' + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'diffx-scala.tex', 'diffx-scala Documentation', + 'SoftwareMill', 'manual'), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'diffx-scala', 'diffx-scala Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'diffx-scala', 'diffx-scala Documentation', + author, 'diffx-scala', 'One line description of project.', + 'Miscellaneous'), +] + +highlight_language = 'scala' + +# configure edit on github: https://docs.readthedocs.io/en/latest/guides/vcs.html +html_context = { + 'display_github': True, # Integrate GitHub + 'github_user': 'softwaremill', # Username + 'github_repo': 'diffx', # Repo name + 'github_version': 'master', # Version + 'conf_py_path': '/docs-sources/', # Path in the checkout to the docs root +} + +# app setup hook +def setup(app): + app.add_config_value('recommonmark_config', { + 'auto_toc_tree_section': 'Contents', + 'enable_auto_doc_ref': False + }, True) + app.add_transform(AutoStructify) + diff --git a/generated-docs/out/index.md b/generated-docs/out/index.md new file mode 100644 index 00000000..da277e5c --- /dev/null +++ b/generated-docs/out/index.md @@ -0,0 +1,142 @@ +# diffx: Pretty diffs for case classes + +Welcome! + +[diffx](https://github.com/softwaremill/diffx) is an open-source library which aims to display differences between +complex structures in a way that they are easily noticeable. + +Here's a quick example of diffx in action: + +```scala +sealed trait Parent +case class Bar(s: String, i: Int) extends Parent +case class Foo(bar: Bar, b: List[Int], parent: Option[Parent]) extends Parent + +val right: Foo = Foo( + Bar("asdf", 5), + List(123, 1234), + Some(Bar("asdf", 5)) +) +// right: Foo = Foo( +// bar = Bar(s = "asdf", i = 5), +// b = List(123, 1234), +// parent = Some(value = Bar(s = "asdf", i = 5)) +// ) + +val left: Foo = Foo( + Bar("asdf", 66), + List(1234), + Some(right) +) +// left: Foo = Foo( +// bar = Bar(s = "asdf", i = 66), +// b = List(1234), +// parent = Some( +// value = Foo( +// bar = Bar(s = "asdf", i = 5), +// b = List(123, 1234), +// parent = Some(value = Bar(s = "asdf", i = 5)) +// ) +// ) +// ) + +import com.softwaremill.diffx.generic.auto._ +import com.softwaremill.diffx._ +compare(left, right) +// res0: DiffResult = DiffResultObject( +// name = "Foo", +// fields = ListMap( +// "bar" -> DiffResultObject( +// name = "Bar", +// fields = ListMap( +// "s" -> Identical(value = "asdf"), +// "i" -> DiffResultValue(left = 66, right = 5) +// ) +// ), +// "b" -> DiffResultObject( +// name = "List", +// fields = ListMap( +// "0" -> DiffResultValue(left = 1234, right = 123), +// "1" -> DiffResultMissing(value = 1234) +// ) +// ), +// "parent" -> DiffResultValue( +// left = "repl.MdocSession.App.Foo", +// right = "repl.MdocSession.App.Bar" +// ) +// ) +// ) +``` + +Will result in: + +![](https://github.com/softwaremill/diffx/blob/master/example.png?raw=true) + +`diffx` is available for Scala 2.12 and 2.13 both jvm and js. + +The core of `diffx` comes in a single jar. + +To integrate with the test framework of your choice, you'll need to use one of the integration modules. +See the section on [test-frameworks](test-frameworks/summary.md) for a brief overview of supported test frameworks. + +*Auto-derivation is used throughout the documentation for the sake of clarity. Head over to [derivation](usage/derivation.md) for more details* + +## Tips and tricks + +You may need to add `-Wmacros:after` Scala compiler option to make sure to check for unused implicits +after macro expansion. +If you get warnings from Magnolia which looks like `magnolia: using fallback derivation for TYPE`, +you can use the [Silencer](https://github.com/ghik/silencer) compiler plugin to silent the warning +with the compiler option `"-P:silencer:globalFilters=^magnolia: using fallback derivation.*$"` + +## Similar projects + +There is a number of similar projects from which diffx draws inspiration. + +Below is a list of some of them, which I am aware of, with their main differences: +- [xotai/diff](https://github.com/xdotai/diff) - based on shapeless, seems not to be activly developed anymore +- [ratatool-diffy](https://github.com/spotify/ratatool/tree/master/ratatool-diffy) - the main purpose is to compare large data sets stored on gs or hdfs + + +## Sponsors + +Development and maintenance of diffx is sponsored by [SoftwareMill](https://softwaremill.com), +a software development and consulting company. We help clients scale their business through software. Our areas of expertise include backends, distributed systems, blockchain, machine learning and data analytics. + +[![](https://files.softwaremill.com/logo/logo.png "SoftwareMill")](https://softwaremill.com) + +# Table of contents + +```eval_rst +.. toctree:: + :maxdepth: 1 + :caption: Test frameworks + + test-frameworks/scalatest + test-frameworks/specs2 + test-frameworks/utest + test-frameworks/summary + +.. toctree:: + :maxdepth: 1 + :caption: Integrations + + integrations/cats + integrations/tagging + integrations/refined + +.. toctree:: + :maxdepth: 1 + :caption: usage + + usage/derivation + usage/ignoring + usage/replacing + usage/extending + usage/sequences + usage/output +``` + +## Copyright + +Copyright (C) 2019 SoftwareMill [https://softwaremill.com](https://softwaremill.com). diff --git a/generated-docs/out/integrations/cats.md b/generated-docs/out/integrations/cats.md new file mode 100644 index 00000000..48ad58f0 --- /dev/null +++ b/generated-docs/out/integrations/cats.md @@ -0,0 +1,47 @@ +# cats + +This module contains integration layer between [org.typelevel.cats](https://github.com/typelevel/cats) and `diffx` + +## sbt + +```scala +"com.softwaremill.diffx" %% "diffx-cats" % "0.5.0" % Test +``` + +## mill + +```scala +ivy"com.softwaremill.diffx::diffx-cats::0.5.0" +``` + +## Usage + +Assuming you have some data types from the cats library in your hierarchy: +```scala +import cats.data._ +case class TestData(ints: NonEmptyList[String]) + +val t1 = TestData(NonEmptyList.one("a")) +val t2 = TestData(NonEmptyList.one("b")) +``` + +all you need to do is to put additional diffx implicits into current scope: + +```scala +import com.softwaremill.diffx.compare +import com.softwaremill.diffx.generic.auto._ + +import com.softwaremill.diffx.cats._ +compare(t1, t2) +// res0: com.softwaremill.diffx.DiffResult = DiffResultObject( +// name = "TestData", +// fields = ListMap( +// "ints" -> DiffResultObject( +// name = "List", +// fields = ListMap( +// "0" -> DiffResultString(diffs = List(DiffResultValue(left = "a", right = "b"))) +// ) +// ) +// ) +// ) +``` \ No newline at end of file diff --git a/generated-docs/out/integrations/refined.md b/generated-docs/out/integrations/refined.md new file mode 100644 index 00000000..531ee832 --- /dev/null +++ b/generated-docs/out/integrations/refined.md @@ -0,0 +1,49 @@ +# refined + +This module contains integration layer between [eu.timepit.refined](https://github.com/fthomas/refined) and `diffx` + +## sbt + +```scala +"com.softwaremill.diffx" %% "diffx-refined" % "0.5.0" % Test +``` + +## mill + +```scala +ivy"com.softwaremill.diffx::diffx-refined::0.5.0" +``` + +## Usage + +Assuming you have some refined types in your hierarchy: + +```scala +import eu.timepit.refined.types.numeric.PosInt +import eu.timepit.refined.auto._ +import eu.timepit.refined.types.string.NonEmptyString + +case class TestData(posInt: PosInt, nonEmptyString: NonEmptyString) + +val t1 = TestData(1, "foo") +val t2 = TestData(1, "bar") +``` + +all you need to do is to put additional diffx implicits into current scope: + +```scala +import com.softwaremill.diffx.compare +import com.softwaremill.diffx.generic.auto._ + +import com.softwaremill.diffx.refined._ +compare(t1, t2) +// res0: com.softwaremill.diffx.DiffResult = DiffResultObject( +// name = "TestData", +// fields = ListMap( +// "posInt" -> Identical(value = 1), +// "nonEmptyString" -> DiffResultString( +// diffs = List(DiffResultValue(left = "foo", right = "bar")) +// ) +// ) +// ) +``` \ No newline at end of file diff --git a/generated-docs/out/integrations/tagging.md b/generated-docs/out/integrations/tagging.md new file mode 100644 index 00000000..97c6d5f2 --- /dev/null +++ b/generated-docs/out/integrations/tagging.md @@ -0,0 +1,46 @@ +# tagging + +This module contains integration layer between [com.softwaremill.common.tagging](https://github.com/softwaremill/scala-common) and `diffx` + +## sbt + +```scala +"com.softwaremill.diffx" %% "diffx-tagging" % "0.5.0" +``` + +## mill + +```scala +ivy"com.softwaremill.diffx::diffx-tagging::0.5.0" +``` + +## Usage + +Assuming you have some tagged types in your hierarchy: + +```scala +import com.softwaremill.tagging._ +sealed trait T1 +sealed trait T2 +case class TestData(p1: Int @@ T1, p2: Int @@ T2) + +val t1 = TestData(1.taggedWith[T1], 1.taggedWith[T2]) +val t2 = TestData(1.taggedWith[T1], 3.taggedWith[T2]) +``` + +all you need to do is to put additional diffx implicits into current scope: + +```scala +import com.softwaremill.diffx.compare +import com.softwaremill.diffx.generic.auto._ + +import com.softwaremill.diffx.tagging._ +compare(t1, t2) +// res0: com.softwaremill.diffx.DiffResult = DiffResultObject( +// name = "TestData", +// fields = ListMap( +// "p1" -> Identical(value = 1), +// "p2" -> DiffResultValue(left = 1, right = 3) +// ) +// ) +``` \ No newline at end of file diff --git a/generated-docs/out/make.bat b/generated-docs/out/make.bat new file mode 100644 index 00000000..7893348a --- /dev/null +++ b/generated-docs/out/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/generated-docs/out/requirements.pip b/generated-docs/out/requirements.pip new file mode 100644 index 00000000..cb4b2b97 --- /dev/null +++ b/generated-docs/out/requirements.pip @@ -0,0 +1,4 @@ +sphinx_rtd_theme==0.4.3 +recommonmark==0.5.0 +sphinx==2.0.1 +sphinx-autobuild==0.7.1 diff --git a/generated-docs/out/test-frameworks/scalatest.md b/generated-docs/out/test-frameworks/scalatest.md new file mode 100644 index 00000000..5cbbb559 --- /dev/null +++ b/generated-docs/out/test-frameworks/scalatest.md @@ -0,0 +1,46 @@ +# scalatest + +To use with scalatest, add the following dependency: + +## sbt + +```scala +"com.softwaremill.diffx" %% "diffx-scalatest" % "0.5.0" % Test +``` + +## mill + +```scala +ivy"com.softwaremill.diffx::diffx-scalatest::0.5.0" +``` + +## Usage + +Then, extend the `com.softwaremill.diffx.scalatest.DiffMatcher` trait or `import com.softwaremill.diffx.scalatest.DiffMatcher._`. +After that you will be able to use syntax such as: + +```scala +import org.scalatest.matchers.should.Matchers._ +import com.softwaremill.diffx.scalatest.DiffMatcher._ +import com.softwaremill.diffx.generic.auto._ + +sealed trait Parent +case class Bar(s: String, i: Int) extends Parent +case class Foo(bar: Bar, b: List[Int], parent: Option[Parent]) extends Parent + +val right: Foo = Foo( + Bar("asdf", 5), + List(123, 1234), + Some(Bar("asdf", 5)) +) + +val left: Foo = Foo( + Bar("asdf", 66), + List(1234), + Some(right) +) + +left should matchTo(right) +``` + +###### TODO add info about misleading compilation error when using above syntax \ No newline at end of file diff --git a/generated-docs/out/test-frameworks/specs2.md b/generated-docs/out/test-frameworks/specs2.md new file mode 100644 index 00000000..8125ce0d --- /dev/null +++ b/generated-docs/out/test-frameworks/specs2.md @@ -0,0 +1,44 @@ +# specs2 + +To use with specs2, add the following dependency: + +## sbt + +```scala +"com.softwaremill.diffx" %% "diffx-specs2" % "0.5.0" % Test +``` + +## mill + +```scala +ivy"com.softwaremill.diffx::diffx-specs2::0.5.0" +``` + +## Usage + +Then, extend the `com.softwaremill.diffx.specs2.DiffMatcher` trait or `import com.softwaremill.diffx.specs2.DiffMatcher._`. +After that you will be able to use syntax such as: + +```scala +import org.specs2.matcher.MustMatchers.{left => _, right => _, _} +import com.softwaremill.diffx.specs2.DiffMatcher._ +import com.softwaremill.diffx.generic.auto._ + +sealed trait Parent +case class Bar(s: String, i: Int) extends Parent +case class Foo(bar: Bar, b: List[Int], parent: Option[Parent]) extends Parent + +val right: Foo = Foo( + Bar("asdf", 5), + List(123, 1234), + Some(Bar("asdf", 5)) +) + +val left: Foo = Foo( + Bar("asdf", 66), + List(1234), + Some(right) +) + +left must matchTo(right) +``` diff --git a/generated-docs/out/test-frameworks/summary.md b/generated-docs/out/test-frameworks/summary.md new file mode 100644 index 00000000..c31cf754 --- /dev/null +++ b/generated-docs/out/test-frameworks/summary.md @@ -0,0 +1,9 @@ +# summary + +Following test frameworks are supported by diffx: +- [scalatest](scalatest.md) +- [specs2](specs2.md) +- [utest](utest.md) + +Didn't find your favourite testing library? Don't hesitate and let us know, or you can add it on your own , +as all that needs to be done is to call `compare` function. \ No newline at end of file diff --git a/generated-docs/out/test-frameworks/utest.md b/generated-docs/out/test-frameworks/utest.md new file mode 100644 index 00000000..06140bd3 --- /dev/null +++ b/generated-docs/out/test-frameworks/utest.md @@ -0,0 +1,44 @@ +# utest + +To use with utest, add following dependency: + +## sbt + +```scala +"com.softwaremill.diffx" %% "diffx-utest" % "0.5.0" % Test +``` + +## mill + +```scala +ivy"com.softwaremill.diffx::diffx-utest::0.5.0" +``` + +## Usage + +Then, mixin `DiffxAssertions` trait or add `import com.softwaremill.diffx.utest.DiffxAssertions._` to your test code. +To assert using diffx use `assertEquals` as follows: + +```scala +import com.softwaremill.diffx.utest.DiffxAssertions._ +import com.softwaremill.diffx.generic.auto._ + +sealed trait Parent +case class Bar(s: String, i: Int) extends Parent +case class Foo(bar: Bar, b: List[Int], parent: Option[Parent]) extends Parent + +val right: Foo = Foo( + Bar("asdf", 5), + List(123, 1234), + Some(Bar("asdf", 5)) +) + +val left: Foo = Foo( + Bar("asdf", 66), + List(1234), + Some(right) +) + +assertEqual(left, right) +``` + diff --git a/generated-docs/out/usage/derivation.md b/generated-docs/out/usage/derivation.md new file mode 100644 index 00000000..ac477338 --- /dev/null +++ b/generated-docs/out/usage/derivation.md @@ -0,0 +1,23 @@ +# derivation + +Diffx supports auto and semi-auto derivation. + +For semi-auto derivation you don't need any additional import, just define your instances using: +```scala +import com.softwaremill.diffx._ +case class Product(name: String) +case class Basket(products: List[Product]) + +implicit val productDiff = Diff.derived[Product] +implicit val basketDiff = Diff.derived[Basket] +``` + +To use auto derivation add following import + +`import com.softwaremill.diffx.generic.auto._` + +or extend trait + +`com.softwaremill.diffx.generic.AutoDerivation` + +**Auto derivation might have a huge impact on compilation times**, because of that it is recommended to use `semi-auto` derivation. diff --git a/generated-docs/out/usage/extending.md b/generated-docs/out/usage/extending.md new file mode 100644 index 00000000..59c74798 --- /dev/null +++ b/generated-docs/out/usage/extending.md @@ -0,0 +1,22 @@ +# extending + +If you'd like to implement custom matching logic for the given type, create an implicit `Diff` instance for that +type, and make sure it's in scope when any `Diff` instances depending on that type are created. + +Consider following example with `NonEmptyList` from cats. `NonEmptyList` is implemented as case class, +so the default behavior of diffx would be to create a `Diff[NonEmptyList]` typeclass instance using magnolia derivation. + +Obviously that's not what we usually want. In most of the cases we would like `NonEmptyList` to be compared as a list. +Diffx already has an instance of a typeclass for a list (for any iterable to be precise). +All we need to do is to use that typeclass by converting `NonEmptyList` to list which can be done using `contramap` method. + +The final code looks as follows: + +```scala +import com.softwaremill.diffx._ +import _root_.cats.data.NonEmptyList +implicit def nelDiff[T: Diff]: Diff[NonEmptyList[T]] = + Diff[List[T]].contramap[NonEmptyList[T]](_.toList) +``` + +*Note: There is a [diffx-cats](../integrations/cats.md) module, so you don't have to do this* \ No newline at end of file diff --git a/generated-docs/out/usage/ignoring.md b/generated-docs/out/usage/ignoring.md new file mode 100644 index 00000000..ab57c774 --- /dev/null +++ b/generated-docs/out/usage/ignoring.md @@ -0,0 +1,24 @@ +# ignoring + + +Fields can be excluded from comparison by calling the `ignore` method on the `Diff` instance. +Since `Diff` instances are immutable, the `ignore` method creates a copy of the instance with modified logic. +You can use this instance explicitly. + +```scala +case class Person(name:String, age:Int) +val modifiedDiff: Diff[Person] = Diff[Person].ignore(_.name) +``` + +If you still would like to use it implicitly, you first need to summon the instance of the `Diff` typeclass using +the `Derived` typeclass wrapper: `Derived[Diff[Person]]`. Thanks to that trick, later you will be able to put your modified +instance of the `Diff` typeclass into the implicit scope. The whole process looks as follows: + +```scala +case class Person(name:String, age:Int) +implicit val modifiedDiff: Diff[Person] = Derived[Diff[Person]].ignore(_.age) +``` +```scala +compare(Person("bob", 25), Person("bob", 30)) +// res1: DiffResult = Identical(value = Person(name = "bob", age = 25)) +``` \ No newline at end of file diff --git a/generated-docs/out/usage/output.md b/generated-docs/out/usage/output.md new file mode 100644 index 00000000..3eb89240 --- /dev/null +++ b/generated-docs/out/usage/output.md @@ -0,0 +1,56 @@ +# output + + +`diffx` does its best to show the difference in the most readable way, but obviously the default configuration won't +cover all the use-cases. Because of that, there are few ways how you can modify its output. + +## colors & signs + +I found it confusing to use the terms `expected`/`actual` as there seems to be no golden rule whether to keep expected on the right side or on the left side. +Because of that, diffx refers to the values that are compared as `left` and `right` value. + +By default, the difference is shown in the following form: + +`leftColor(leftValue) -> rightColor(rightValue)` + +which in terms of missing/additional values e.g. in collections looks as follows: + +`leftColor(additionalValue)` in case the value was present on the left-hand side and absent on the right side +`rightColor(missingValue)` in case the value was absent on the left-hand side and present on the right side + + +Where, by default, `rightColor` is green and `leftColor` is red. + +Colors can be customized providing an implicit instance of `ConsoleColorConfig` class. +In fact `rightColor` and `leftColor` are functions `string => string` so they can be modified to do whatever you want with the output. +One example of that would be to use some special characters instead of colors, which might be useful on some environments like e.g. CI. + +````scala +val colorConfigWithPlusMinus: ConsoleColorConfig = + ConsoleColorConfig(default = identity, arrow = identity, right = s => "+" + s, left = s => "-" + s) +```` + +There are two predefined set of colors - light and dark theme. +The default theme is dark, and it can be changed using environment variable - `DIFFX_COLOR_THEME`(`light`/`dark`). + +## skipping identical + +In some cases it might be desired to skip rendering the identical fields, to do that simple set `showIgnored` to `false`. + +```scala +case class Person(name:String, age:Int) + +val result = compare(Person("Bob", 23), Person("Alice", 23)) +// result: DiffResult = DiffResultObject( +// name = "Person", +// fields = ListMap( +// "name" -> DiffResultString( +// diffs = List(DiffResultValue(left = "Bob", right = "Alice")) +// ), +// "age" -> Identical(value = 23) +// ) +// ) +result.show(renderIdentical = false) +// res1: String = """Person( +// name: Bob -> Alice)""" +``` \ No newline at end of file diff --git a/generated-docs/out/usage/replacing.md b/generated-docs/out/usage/replacing.md new file mode 100644 index 00000000..1bec35f2 --- /dev/null +++ b/generated-docs/out/usage/replacing.md @@ -0,0 +1,31 @@ +# replacing + +Sometimes you might want to compare some nested values using a different comparator but +the type they share is not unique within that hierarchy. + +Consider following example: +```scala +case class Person(age: Int, weight: Int) +``` + +If we would like to compare `weight` differently than `age` we would have to introduce a new type for `weight` +in order to provide a different `Diff` typeclass for only that field. While in general, it is a good idea to have your types +very precise it might not always be practical or even possible. Fortunately, diffx comes with a mechanism which allows +the replacement of nested diff instances. + +First we need to acquire a lens at given path using `modify` method, +and then we can call `setTo` to replace a particular instance. + +```scala +import com.softwaremill.diffx._ +implicit val diffPerson: Derived[Diff[Person]] = Diff.derived[Person].modify(_.weight) + .setTo(Diff.approximate(epsilon=5)) +``` + +```scala +compare(Person(23, 60), Person(23, 62)) +// res0: DiffResult = Identical(value = Person(age = 23, weight = 60)) +``` + +In fact, replacement is so powerful that ignoring is implemented as a replacement +with the `Diff.ignore` instance. \ No newline at end of file diff --git a/generated-docs/out/usage/sequences.md b/generated-docs/out/usage/sequences.md new file mode 100644 index 00000000..46353c9d --- /dev/null +++ b/generated-docs/out/usage/sequences.md @@ -0,0 +1,82 @@ +# sequences + +`diffx` provides instances for many containers from scala's standard library (e.g. lists, sets, maps), however +not all collections can be simply compared. Ordered collections like lists or vectors are compared by default by +comparing elements under the same indexes. +Maps, by default, are compared by comparing values under the respective keys. +For unordered collections there is an `ObjectMapper` typeclass which defines how elements should be paired. + +## object matcher + +In general, it is a very simple interface, with a bunch of factory methods. +```scala +trait ObjectMatcher[T] { + def isSameObject(left: T, right: T): Boolean +} +``` + +It is mostly useful when comparing unordered collections like sets: + +```scala +import com.softwaremill.diffx._ +import com.softwaremill.diffx.generic.auto._ +case class Person(id: String, name: String) + +implicit val personMatcher: ObjectMatcher[Person] = ObjectMatcher.by(_.id) +val bob = Person("1","Bob") +``` +```scala +compare(Set(bob), Set(bob, Person("2","Alice"))) +// res1: DiffResult = DiffResultSet( +// diffs = List( +// DiffResultMissing(value = Person(id = "2", name = "Alice")), +// Identical(value = Person(id = "1", name = "Bob")) +// ) +// ) +``` + +It can be also used to modify how the entries from maps are paired. +In below example we tell `diffx` to compare these maps by paring entries by values using the defined `personMatcher` +```scala +import com.softwaremill.diffx._ +import com.softwaremill.diffx.generic.auto._ +case class Person(id: String, name: String) + +val personMatcher: ObjectMatcher[Person] = ObjectMatcher.by(_.id) +implicit val om: ObjectMatcher[(String, Person)] = ObjectMatcher.byValue(personMatcher) +val bob = Person("1","Bob") +``` + +```scala +compare(Map("1" -> bob), Map("2" -> bob)) +// res3: DiffResult = DiffResultMap( +// fields = Map( +// DiffResultString(diffs = List(DiffResultValue(left = "1", right = "2"))) -> Identical( +// value = Person(id = "1", name = "Bob") +// ) +// ) +// ) +``` + +Last but not least you can use `objectMatcher` to customize paring when comparing indexed collections. +Such collections are treated similarly to maps (they use key-value object matcher), +but the key type is bound to `Int`. + +```scala +import com.softwaremill.diffx._ +import com.softwaremill.diffx.generic.auto._ +case class Person(id: String, name: String) + +implicit val personMatcher: ObjectMatcher[(Int, Person)] = + ObjectMatcher.byValue(ObjectMatcher.by(_.id)) +val bob = Person("1","Bob") +val alice = Person("2","Alice") +``` +```scala +compare(List(bob, alice), List(alice, bob)) +// res5: DiffResult = Identical( +// value = List(Person(id = "1", name = "Bob"), Person(id = "2", name = "Alice")) +// ) +``` + +*Note: `ObjectMatcher` can be also passed explicitly, either upon creation or during modification* \ No newline at end of file diff --git a/generated-docs/out/watch.sh b/generated-docs/out/watch.sh new file mode 100755 index 00000000..24c43727 --- /dev/null +++ b/generated-docs/out/watch.sh @@ -0,0 +1,2 @@ +#!/bin/bash +sphinx-autobuild . _build/html From b68740742145399aeed0cfaf27273f967360cac3 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Tue, 22 Jun 2021 16:10:11 +0200 Subject: [PATCH 100/112] Release 0.5.1 --- generated-docs/out/integrations/cats.md | 4 ++-- generated-docs/out/integrations/refined.md | 4 ++-- generated-docs/out/integrations/tagging.md | 4 ++-- generated-docs/out/test-frameworks/scalatest.md | 4 ++-- generated-docs/out/test-frameworks/specs2.md | 4 ++-- generated-docs/out/test-frameworks/utest.md | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/generated-docs/out/integrations/cats.md b/generated-docs/out/integrations/cats.md index 48ad58f0..0f6b6fb8 100644 --- a/generated-docs/out/integrations/cats.md +++ b/generated-docs/out/integrations/cats.md @@ -5,13 +5,13 @@ This module contains integration layer between [org.typelevel.cats](https://gith ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-cats" % "0.5.0" % Test +"com.softwaremill.diffx" %% "diffx-cats" % "0.5.1" % Test ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-cats::0.5.0" +ivy"com.softwaremill.diffx::diffx-cats::0.5.1" ``` ## Usage diff --git a/generated-docs/out/integrations/refined.md b/generated-docs/out/integrations/refined.md index 531ee832..e1eec5b7 100644 --- a/generated-docs/out/integrations/refined.md +++ b/generated-docs/out/integrations/refined.md @@ -5,13 +5,13 @@ This module contains integration layer between [eu.timepit.refined](https://gith ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-refined" % "0.5.0" % Test +"com.softwaremill.diffx" %% "diffx-refined" % "0.5.1" % Test ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-refined::0.5.0" +ivy"com.softwaremill.diffx::diffx-refined::0.5.1" ``` ## Usage diff --git a/generated-docs/out/integrations/tagging.md b/generated-docs/out/integrations/tagging.md index 97c6d5f2..fd2bfb76 100644 --- a/generated-docs/out/integrations/tagging.md +++ b/generated-docs/out/integrations/tagging.md @@ -5,13 +5,13 @@ This module contains integration layer between [com.softwaremill.common.tagging] ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-tagging" % "0.5.0" +"com.softwaremill.diffx" %% "diffx-tagging" % "0.5.1" ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-tagging::0.5.0" +ivy"com.softwaremill.diffx::diffx-tagging::0.5.1" ``` ## Usage diff --git a/generated-docs/out/test-frameworks/scalatest.md b/generated-docs/out/test-frameworks/scalatest.md index 5cbbb559..3f2f269c 100644 --- a/generated-docs/out/test-frameworks/scalatest.md +++ b/generated-docs/out/test-frameworks/scalatest.md @@ -5,13 +5,13 @@ To use with scalatest, add the following dependency: ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-scalatest" % "0.5.0" % Test +"com.softwaremill.diffx" %% "diffx-scalatest" % "0.5.1" % Test ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-scalatest::0.5.0" +ivy"com.softwaremill.diffx::diffx-scalatest::0.5.1" ``` ## Usage diff --git a/generated-docs/out/test-frameworks/specs2.md b/generated-docs/out/test-frameworks/specs2.md index 8125ce0d..c37e503c 100644 --- a/generated-docs/out/test-frameworks/specs2.md +++ b/generated-docs/out/test-frameworks/specs2.md @@ -5,13 +5,13 @@ To use with specs2, add the following dependency: ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-specs2" % "0.5.0" % Test +"com.softwaremill.diffx" %% "diffx-specs2" % "0.5.1" % Test ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-specs2::0.5.0" +ivy"com.softwaremill.diffx::diffx-specs2::0.5.1" ``` ## Usage diff --git a/generated-docs/out/test-frameworks/utest.md b/generated-docs/out/test-frameworks/utest.md index 06140bd3..5f7381f0 100644 --- a/generated-docs/out/test-frameworks/utest.md +++ b/generated-docs/out/test-frameworks/utest.md @@ -5,13 +5,13 @@ To use with utest, add following dependency: ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-utest" % "0.5.0" % Test +"com.softwaremill.diffx" %% "diffx-utest" % "0.5.1" % Test ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-utest::0.5.0" +ivy"com.softwaremill.diffx::diffx-utest::0.5.1" ``` ## Usage From 316ba305774ebce0d00797b699c57f4073a2f426 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Tue, 22 Jun 2021 16:13:25 +0200 Subject: [PATCH 101/112] Remove todo --- docs-sources/test-frameworks/scalatest.md | 2 -- generated-docs/out/test-frameworks/scalatest.md | 2 -- 2 files changed, 4 deletions(-) diff --git a/docs-sources/test-frameworks/scalatest.md b/docs-sources/test-frameworks/scalatest.md index 54ce0052..fac5261f 100644 --- a/docs-sources/test-frameworks/scalatest.md +++ b/docs-sources/test-frameworks/scalatest.md @@ -42,5 +42,3 @@ val left: Foo = Foo( left should matchTo(right) ``` - -###### TODO add info about misleading compilation error when using above syntax \ No newline at end of file diff --git a/generated-docs/out/test-frameworks/scalatest.md b/generated-docs/out/test-frameworks/scalatest.md index 3f2f269c..87d54fee 100644 --- a/generated-docs/out/test-frameworks/scalatest.md +++ b/generated-docs/out/test-frameworks/scalatest.md @@ -42,5 +42,3 @@ val left: Foo = Foo( left should matchTo(right) ``` - -###### TODO add info about misleading compilation error when using above syntax \ No newline at end of file From 637bb6e226c1f2539639489618b880b1cb90103b Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Tue, 22 Jun 2021 22:40:36 +0200 Subject: [PATCH 102/112] Add note about disabling red foreground in IJ --- docs-sources/usage/output.md | 9 +++++++++ generated-docs/out/usage/output.md | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/docs-sources/usage/output.md b/docs-sources/usage/output.md index e125a5c2..c022e53f 100644 --- a/docs-sources/usage/output.md +++ b/docs-sources/usage/output.md @@ -8,6 +8,15 @@ import com.softwaremill.diffx.generic.auto._ `diffx` does its best to show the difference in the most readable way, but obviously the default configuration won't cover all the use-cases. Because of that, there are few ways how you can modify its output. +## intellij idea + +If you are running tests using **IntelliJ IDEA**'s test runner, you will want +to turn off the red text coloring it uses for test failure outputs because +it interferes with difflicious' color outputs. + +In File | Settings | Editor | Color Scheme | Console Colors | Console | Error Output, uncheck the red foreground color. +(Solution provided by Jacob Wang @jatcwang) + ## colors & signs I found it confusing to use the terms `expected`/`actual` as there seems to be no golden rule whether to keep expected on the right side or on the left side. diff --git a/generated-docs/out/usage/output.md b/generated-docs/out/usage/output.md index 3eb89240..2d71164c 100644 --- a/generated-docs/out/usage/output.md +++ b/generated-docs/out/usage/output.md @@ -4,6 +4,15 @@ `diffx` does its best to show the difference in the most readable way, but obviously the default configuration won't cover all the use-cases. Because of that, there are few ways how you can modify its output. +## intellij idea + +If you are running tests using **IntelliJ IDEA**'s test runner, you will want +to turn off the red text coloring it uses for test failure outputs because +it interferes with difflicious' color outputs. + +In File | Settings | Editor | Color Scheme | Console Colors | Console | Error Output, uncheck the red foreground color. +(Solution provided by Jacob Wang @jatcwang) + ## colors & signs I found it confusing to use the terms `expected`/`actual` as there seems to be no golden rule whether to keep expected on the right side or on the left side. From a88246a4c22fb9f0e31f08c1c2f2a8afcc190918 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Tue, 22 Jun 2021 22:54:40 +0200 Subject: [PATCH 103/112] Add integration with munit --- build.sbt | 20 ++++++++- docs-sources/index.md | 1 + docs-sources/test-frameworks/munit.md | 44 +++++++++++++++++++ docs-sources/test-frameworks/summary.md | 1 + generated-docs/out/index.md | 1 + generated-docs/out/test-frameworks/munit.md | 44 +++++++++++++++++++ generated-docs/out/test-frameworks/summary.md | 1 + .../diffx/munit/DiffxAssertions.scala | 17 +++++++ .../diffx/munit/MunitAssertTest.scala | 18 ++++++++ 9 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 docs-sources/test-frameworks/munit.md create mode 100644 generated-docs/out/test-frameworks/munit.md create mode 100644 munit/src/main/scala/com/softwaremill/diffx/munit/DiffxAssertions.scala create mode 100644 munit/src/test/scala/com/softwaremill/diffx/munit/MunitAssertTest.scala diff --git a/build.sbt b/build.sbt index 1a224237..58c456e1 100644 --- a/build.sbt +++ b/build.sbt @@ -117,6 +117,22 @@ lazy val utest = (projectMatrix in file("utest")) scalaVersions = List(scala212, scala213) ) +lazy val munit = (projectMatrix in file("munit")) + .settings(commonSettings) + .settings( + name := "diffx-munit", + libraryDependencies ++= Seq( + "org.scalameta" %%% "munit" % "0.7.26" + ) + ) + .dependsOn(core) + .jvmPlatform( + scalaVersions = List(scala212, scala213) + ) + .jsPlatform( + scalaVersions = List(scala212, scala213) + ) + lazy val tagging = (projectMatrix in file("tagging")) .settings(commonSettings) .settings( @@ -189,7 +205,7 @@ lazy val docs = (projectMatrix in file("generated-docs")) // important: it must ), mdocOut := file("generated-docs/out") ) - .dependsOn(core, scalatest, specs2, utest, refined, tagging, cats) + .dependsOn(core, scalatest, specs2, utest, refined, tagging, cats, munit) .jvmPlatform(scalaVersions = List(scala213)) val testJVM = taskKey[Unit]("Test JVM projects") @@ -198,7 +214,7 @@ val testJS = taskKey[Unit]("Test JS projects") val allAggregates = core.projectRefs ++ scalatest.projectRefs ++ specs2.projectRefs ++ utest.projectRefs ++ cats.projectRefs ++ - refined.projectRefs ++ tagging.projectRefs ++ docs.projectRefs + refined.projectRefs ++ tagging.projectRefs ++ docs.projectRefs ++ munit.projectRefs def filterProject(p: String => Boolean) = ScopeFilter(inProjects(allAggregates.filter(pr => p(display(pr.project))): _*)) diff --git a/docs-sources/index.md b/docs-sources/index.md index 74a81536..baa4a378 100644 --- a/docs-sources/index.md +++ b/docs-sources/index.md @@ -76,6 +76,7 @@ a software development and consulting company. We help clients scale their busin test-frameworks/scalatest test-frameworks/specs2 test-frameworks/utest + test-frameworks/munit test-frameworks/summary .. toctree:: diff --git a/docs-sources/test-frameworks/munit.md b/docs-sources/test-frameworks/munit.md new file mode 100644 index 00000000..eec4e838 --- /dev/null +++ b/docs-sources/test-frameworks/munit.md @@ -0,0 +1,44 @@ +# munit + +To use with munit, add following dependency: + +## sbt + +```scala +"com.softwaremill.diffx" %% "diffx-munit" % "@VERSION@" % Test +``` + +## mill + +```scala +ivy"com.softwaremill.diffx::diffx-munit::@VERSION@" +``` + +## Usage + +Then, mixin `DiffxAssertions` trait or add `import com.softwaremill.diffx.munit.DiffxAssertions._` to your test code. +To assert using diffx use `assertEquals` as follows: + +```scala mdoc:compile-only +import com.softwaremill.diffx.munit.DiffxAssertions._ +import com.softwaremill.diffx.generic.auto._ + +sealed trait Parent +case class Bar(s: String, i: Int) extends Parent +case class Foo(bar: Bar, b: List[Int], parent: Option[Parent]) extends Parent + +val right: Foo = Foo( + Bar("asdf", 5), + List(123, 1234), + Some(Bar("asdf", 5)) +) + +val left: Foo = Foo( + Bar("asdf", 66), + List(1234), + Some(right) +) + +assertEqual(left, right) +``` + diff --git a/docs-sources/test-frameworks/summary.md b/docs-sources/test-frameworks/summary.md index c31cf754..69e091e7 100644 --- a/docs-sources/test-frameworks/summary.md +++ b/docs-sources/test-frameworks/summary.md @@ -4,6 +4,7 @@ Following test frameworks are supported by diffx: - [scalatest](scalatest.md) - [specs2](specs2.md) - [utest](utest.md) +- [munit](munit.md) Didn't find your favourite testing library? Don't hesitate and let us know, or you can add it on your own , as all that needs to be done is to call `compare` function. \ No newline at end of file diff --git a/generated-docs/out/index.md b/generated-docs/out/index.md index da277e5c..cf4abdb6 100644 --- a/generated-docs/out/index.md +++ b/generated-docs/out/index.md @@ -115,6 +115,7 @@ a software development and consulting company. We help clients scale their busin test-frameworks/scalatest test-frameworks/specs2 test-frameworks/utest + test-frameworks/munit test-frameworks/summary .. toctree:: diff --git a/generated-docs/out/test-frameworks/munit.md b/generated-docs/out/test-frameworks/munit.md new file mode 100644 index 00000000..e17a1a13 --- /dev/null +++ b/generated-docs/out/test-frameworks/munit.md @@ -0,0 +1,44 @@ +# munit + +To use with munit, add following dependency: + +## sbt + +```scala +"com.softwaremill.diffx" %% "diffx-munit" % "0.5.1" % Test +``` + +## mill + +```scala +ivy"com.softwaremill.diffx::diffx-munit::0.5.1" +``` + +## Usage + +Then, mixin `DiffxAssertions` trait or add `import com.softwaremill.diffx.munit.DiffxAssertions._` to your test code. +To assert using diffx use `assertEquals` as follows: + +```scala +import com.softwaremill.diffx.munit.DiffxAssertions._ +import com.softwaremill.diffx.generic.auto._ + +sealed trait Parent +case class Bar(s: String, i: Int) extends Parent +case class Foo(bar: Bar, b: List[Int], parent: Option[Parent]) extends Parent + +val right: Foo = Foo( + Bar("asdf", 5), + List(123, 1234), + Some(Bar("asdf", 5)) +) + +val left: Foo = Foo( + Bar("asdf", 66), + List(1234), + Some(right) +) + +assertEqual(left, right) +``` + diff --git a/generated-docs/out/test-frameworks/summary.md b/generated-docs/out/test-frameworks/summary.md index c31cf754..69e091e7 100644 --- a/generated-docs/out/test-frameworks/summary.md +++ b/generated-docs/out/test-frameworks/summary.md @@ -4,6 +4,7 @@ Following test frameworks are supported by diffx: - [scalatest](scalatest.md) - [specs2](specs2.md) - [utest](utest.md) +- [munit](munit.md) Didn't find your favourite testing library? Don't hesitate and let us know, or you can add it on your own , as all that needs to be done is to call `compare` function. \ No newline at end of file diff --git a/munit/src/main/scala/com/softwaremill/diffx/munit/DiffxAssertions.scala b/munit/src/main/scala/com/softwaremill/diffx/munit/DiffxAssertions.scala new file mode 100644 index 00000000..6dc75c45 --- /dev/null +++ b/munit/src/main/scala/com/softwaremill/diffx/munit/DiffxAssertions.scala @@ -0,0 +1,17 @@ +package com.softwaremill.diffx.munit + +import com.softwaremill.diffx.{ConsoleColorConfig, Diff, DiffResultDifferent} +import munit.Assertions._ +import munit.Location + +trait DiffxAssertions { + def assertEqual[T: Diff](t1: T, t2: T)(implicit c: ConsoleColorConfig, loc: Location): Unit = { + val result = Diff.compare(t1, t2) + result match { + case different: DiffResultDifferent => fail(different.show())(loc) + case _ => // do nothing + } + } +} + +object DiffxAssertions extends DiffxAssertions diff --git a/munit/src/test/scala/com/softwaremill/diffx/munit/MunitAssertTest.scala b/munit/src/test/scala/com/softwaremill/diffx/munit/MunitAssertTest.scala new file mode 100644 index 00000000..79c325e4 --- /dev/null +++ b/munit/src/test/scala/com/softwaremill/diffx/munit/MunitAssertTest.scala @@ -0,0 +1,18 @@ +package com.softwaremill.diffx.munit + +import com.softwaremill.diffx.generic.auto._ + +class MunitAssertTest extends munit.FunSuite with DiffxAssertions { +// uncomment to run +// test("failing test") { +// val n = Person("n1", 11) +// assertEqual(n, Person("n2", 12)) +// } + + test("hello") { + val n = Person("n1", 11) + assertEqual(n, Person("n1", 11)) + } +} + +case class Person(name: String, age: Int) From b2a1b6fc2dd8e06b0aa8f2fd943a3ca40fcacc50 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Wed, 23 Jun 2021 06:48:24 +0200 Subject: [PATCH 104/112] Improve objectMatcher --- .../scala/com/softwaremill/diffx/ObjectMatcher.scala | 2 ++ docs-sources/usage/replacing.md | 12 +++++++++++- docs-sources/usage/sequences.md | 6 +++--- generated-docs/out/usage/replacing.md | 12 +++++++++++- generated-docs/out/usage/sequences.md | 6 +++--- 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala b/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala index 9aa6e73c..45a83bf7 100644 --- a/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala +++ b/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala @@ -14,6 +14,8 @@ object ObjectMatcher extends LowPriorityObjectMatcher { ObjectMatcher[U].isSameObject(f(left), f(right)) def byValue[K, V: ObjectMatcher]: ObjectMatcher[(K, V)] = ObjectMatcher.by[(K, V), V](_._2) + def byValue[K, V, U: ObjectMatcher](f: V => U): ObjectMatcher[(K, V)] = + ObjectMatcher.by[(K, V), V](_._2)(ObjectMatcher.by[V, U](f)) implicit def optionMatcher[T: ObjectMatcher]: ObjectMatcher[Option[T]] = (left: Option[T], right: Option[T]) => { (left, right) match { diff --git a/docs-sources/usage/replacing.md b/docs-sources/usage/replacing.md index 691e329a..7304c0e9 100644 --- a/docs-sources/usage/replacing.md +++ b/docs-sources/usage/replacing.md @@ -27,4 +27,14 @@ compare(Person(23, 60), Person(23, 62)) ``` In fact, replacement is so powerful that ignoring is implemented as a replacement -with the `Diff.ignore` instance. \ No newline at end of file +with the `Diff.ignore` instance. + +You can use the same mechanism to set particular object matcher for given nested collection in the hierarchy. +Depending, whether it is list, set or map a respective method needs to be called: +```scala mdoc:silent +case class Organization(peopleList: List[Person], peopleSet: Set[Person], peopleMap: Map[String, Person]) +implicit val diffOrg: Derived[Diff[Organization]] = Diff.derived[Organization] + .modify(_.peopleList).withListMatcher[Person](ObjectMatcher.byValue(_.age)) + .modify(_.peopleSet).withSetMatcher[Person](ObjectMatcher.by(_.age)) + .modify(_.peopleMap).withMapMatcher[String,Person](ObjectMatcher.byValue(_.age)) +``` \ No newline at end of file diff --git a/docs-sources/usage/sequences.md b/docs-sources/usage/sequences.md index 55c32120..a197870f 100644 --- a/docs-sources/usage/sequences.md +++ b/docs-sources/usage/sequences.md @@ -54,8 +54,7 @@ import com.softwaremill.diffx._ import com.softwaremill.diffx.generic.auto._ case class Person(id: String, name: String) -implicit val personMatcher: ObjectMatcher[(Int, Person)] = - ObjectMatcher.byValue(ObjectMatcher.by(_.id)) +implicit val personMatcher: ObjectMatcher[(Int, Person)] = ObjectMatcher.byValue(_.id) val bob = Person("1","Bob") val alice = Person("2","Alice") ``` @@ -63,4 +62,5 @@ val alice = Person("2","Alice") compare(List(bob, alice), List(alice, bob)) ``` -*Note: `ObjectMatcher` can be also passed explicitly, either upon creation or during modification* \ No newline at end of file +*Note: `ObjectMatcher` can be also passed explicitly, either upon creation or during modification* +*See [replacing](replacing.md) for details.* \ No newline at end of file diff --git a/generated-docs/out/usage/replacing.md b/generated-docs/out/usage/replacing.md index 1bec35f2..79c5d873 100644 --- a/generated-docs/out/usage/replacing.md +++ b/generated-docs/out/usage/replacing.md @@ -28,4 +28,14 @@ compare(Person(23, 60), Person(23, 62)) ``` In fact, replacement is so powerful that ignoring is implemented as a replacement -with the `Diff.ignore` instance. \ No newline at end of file +with the `Diff.ignore` instance. + +You can use the same mechanism to set particular object matcher for given nested collection in the hierarchy. +Depending, whether it is list, set or map a respective method needs to be called: +```scala +case class Organization(peopleList: List[Person], peopleSet: Set[Person], peopleMap: Map[String, Person]) +implicit val diffOrg: Derived[Diff[Organization]] = Diff.derived[Organization] + .modify(_.peopleList).withListMatcher[Person](ObjectMatcher.byValue(_.age)) + .modify(_.peopleSet).withSetMatcher[Person](ObjectMatcher.by(_.age)) + .modify(_.peopleMap).withMapMatcher[String,Person](ObjectMatcher.byValue(_.age)) +``` \ No newline at end of file diff --git a/generated-docs/out/usage/sequences.md b/generated-docs/out/usage/sequences.md index 46353c9d..6b4cedb6 100644 --- a/generated-docs/out/usage/sequences.md +++ b/generated-docs/out/usage/sequences.md @@ -67,8 +67,7 @@ import com.softwaremill.diffx._ import com.softwaremill.diffx.generic.auto._ case class Person(id: String, name: String) -implicit val personMatcher: ObjectMatcher[(Int, Person)] = - ObjectMatcher.byValue(ObjectMatcher.by(_.id)) +implicit val personMatcher: ObjectMatcher[(Int, Person)] = ObjectMatcher.byValue(_.id) val bob = Person("1","Bob") val alice = Person("2","Alice") ``` @@ -79,4 +78,5 @@ compare(List(bob, alice), List(alice, bob)) // ) ``` -*Note: `ObjectMatcher` can be also passed explicitly, either upon creation or during modification* \ No newline at end of file +*Note: `ObjectMatcher` can be also passed explicitly, either upon creation or during modification* +*See [replacing](replacing.md) for details.* \ No newline at end of file From 5dab53e42a96c0abbf20ca01e4c8c7ed75a2b39a Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Wed, 23 Jun 2021 07:19:06 +0200 Subject: [PATCH 105/112] Fix type inference issue --- core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala index 96ceba77..dc473a0c 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala @@ -295,7 +295,7 @@ class DiffTest extends AnyFreeSpec with Matchers { "compare lists using object matcher comparator" in { val o1 = Organization(List(p1, p2)) val o2 = Organization(List(p2, p1)) - implicit val om: ObjectMatcher[(Int, Person)] = ObjectMatcher.byValue(ObjectMatcher.by(_.name)) + implicit val om: ObjectMatcher[(Int, Person)] = ObjectMatcher.byValue(_.name) compare(o1, o2) shouldBe Identical(Organization(List(p1, p2))) } From 80042d2a728a7fc20905dca814e49b3d0aebc0c9 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Wed, 23 Jun 2021 07:46:43 +0200 Subject: [PATCH 106/112] Fix munit integration --- build.sbt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 58c456e1..b5671b72 100644 --- a/build.sbt +++ b/build.sbt @@ -123,14 +123,16 @@ lazy val munit = (projectMatrix in file("munit")) name := "diffx-munit", libraryDependencies ++= Seq( "org.scalameta" %%% "munit" % "0.7.26" - ) + ), + testFrameworks += new TestFramework("munit.Framework") ) .dependsOn(core) .jvmPlatform( scalaVersions = List(scala212, scala213) ) .jsPlatform( - scalaVersions = List(scala212, scala213) + scalaVersions = List(scala212, scala213), + settings = commonSettings ++ Seq(scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }) ) lazy val tagging = (projectMatrix in file("tagging")) From 142860c3ea08f1975ff77ce161ad9ed42e258adb Mon Sep 17 00:00:00 2001 From: Kasper Kondzielski Date: Wed, 23 Jun 2021 21:56:21 +0200 Subject: [PATCH 107/112] Display ignored fields as ignored (#267) * Display ignored fields as ignored * Fix tuple instances and tests * Fix testFrameworks integrations --- .../scala/com/softwaremill/diffx/Diff.scala | 1 - .../com/softwaremill/diffx/DiffResult.scala | 38 ++- .../com/softwaremill/diffx/DiffxSupport.scala | 2 +- .../softwaremill/diffx/TupleInstances.scala | 122 ++----- .../generic/DiffMagnoliaDerivation.scala | 18 +- .../instances/ApproximateDiffForNumeric.scala | 2 +- .../diffx/instances/DiffForIterable.scala | 6 +- .../diffx/instances/DiffForMap.scala | 6 +- .../diffx/instances/DiffForNumeric.scala | 2 +- .../diffx/instances/DiffForOption.scala | 2 +- .../diffx/instances/DiffForSet.scala | 6 +- .../diffx/instances/DiffForString.scala | 4 +- .../test/DiffModifyIntegrationTest.scala | 35 +- .../diffx/test/DiffResultTest.scala | 26 +- .../diffx/test/DiffSemiautoTest.scala | 14 +- .../softwaremill/diffx/test/DiffTest.scala | 302 ++++++++++++------ .../diffx/munit/DiffxAssertions.scala | 7 +- .../diffx/refined/RefinedSupportTest.scala | 4 +- .../diffx/scalatest/DiffMatcher.scala | 13 +- .../diffx/specs2/DiffMatcher.scala | 12 +- .../tagging/test/DiffTaggingSupportTest.scala | 4 +- .../diffx/utest/DiffxAssertions.scala | 7 +- 22 files changed, 336 insertions(+), 297 deletions(-) diff --git a/core/src/main/scala/com/softwaremill/diffx/Diff.scala b/core/src/main/scala/com/softwaremill/diffx/Diff.scala index 76518227..789d1c4b 100644 --- a/core/src/main/scala/com/softwaremill/diffx/Diff.scala +++ b/core/src/main/scala/com/softwaremill/diffx/Diff.scala @@ -31,7 +31,6 @@ trait Diff[-T] { outer => object Diff extends MiddlePriorityDiff with TupleInstances { def apply[T: Diff]: Diff[T] = implicitly[Diff[T]] - def identical[T]: Diff[T] = (left: T, _: T, _: DiffContext) => Identical(left) def ignored[T]: Diff[T] = (_: T, _: T, _: DiffContext) => DiffResult.Ignored def compare[T: Diff](left: T, right: T): DiffResult = apply[T].apply(left, right) diff --git a/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala b/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala index a6d2bb04..0fb9f667 100644 --- a/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala +++ b/core/src/main/scala/com/softwaremill/diffx/DiffResult.scala @@ -9,14 +9,16 @@ trait DiffResult extends Product with Serializable { showIndented(indentLevel, renderIdentical) private[diffx] def showIndented(indent: Int, renderIdentical: Boolean)(implicit c: ConsoleColorConfig): String + + protected def i(indent: Int): String = " " * indent } object DiffResult { private[diffx] final val indentLevel = 5 - val Ignored = Identical("") + val Ignored: IdenticalValue[Any] = IdenticalValue("") } -case class DiffResultObject(name: String, fields: Map[String, DiffResult]) extends DiffResultDifferent { +case class DiffResultObject(name: String, fields: Map[String, DiffResult]) extends DiffResult { override private[diffx] def showIndented(indent: Int, renderIdentical: Boolean)(implicit c: ConsoleColorConfig ): String = { @@ -41,13 +43,15 @@ case class DiffResultObject(name: String, fields: Map[String, DiffResult]) exten ) = { s"${i(indent)}${defaultColor(s"$field: ")}" } + + override def isIdentical: Boolean = fields.values.forall(_.isIdentical) } -case class DiffResultMap(fields: Map[DiffResult, DiffResult]) extends DiffResultDifferent { +case class DiffResultMap(entries: Map[DiffResult, DiffResult]) extends DiffResult { override private[diffx] def showIndented(indent: Int, renderIdentical: Boolean)(implicit c: ConsoleColorConfig ): String = { - val showFields = fields + val showFields = entries .filter { case (k, v) => renderIdentical || !v.isIdentical || !k.isIdentical } @@ -71,9 +75,11 @@ case class DiffResultMap(fields: Map[DiffResult, DiffResult]) extends DiffResult ) = { s"${i(indent)}${defaultColor(s"${key.showIndented(indent + indentLevel, renderIdentical)}")}" } + + override def isIdentical: Boolean = entries.forall { case (k, v) => k.isIdentical && v.isIdentical } } -case class DiffResultSet(diffs: List[DiffResult]) extends DiffResultDifferent { +case class DiffResultSet(diffs: List[DiffResult]) extends DiffResult { override private[diffx] def showIndented(indent: Int, renderIdentical: Boolean)(implicit c: ConsoleColorConfig ): String = { @@ -82,42 +88,44 @@ case class DiffResultSet(diffs: List[DiffResult]) extends DiffResultDifferent { .map(f => s"${i(indent)}${f.showIndented(indent + indentLevel, renderIdentical)}") showFields.mkString(defaultColor("Set(\n"), ",\n", defaultColor(")")) } + + override def isIdentical: Boolean = diffs.forall(_.isIdentical) } -case class DiffResultString(diffs: List[DiffResult]) extends DiffResultDifferent { +case class DiffResultString(diffs: List[DiffResult]) extends DiffResult { override private[diffx] def showIndented(indent: Int, renderIdentical: Boolean)(implicit c: ConsoleColorConfig ): String = { s"${diffs.map(_.showIndented(indent, renderIdentical)).mkString("\n")}" } -} -trait DiffResultDifferent extends DiffResult { - override def isIdentical: Boolean = false - - protected def i(indent: Int): String = " " * indent + override def isIdentical: Boolean = diffs.forall(_.isIdentical) } -case class DiffResultValue[T](left: T, right: T) extends DiffResultDifferent { +case class DiffResultValue[T](left: T, right: T) extends DiffResult { override def showIndented(indent: Int, renderIdentical: Boolean)(implicit c: ConsoleColorConfig): String = showChange(s"$left", s"$right") + + override def isIdentical: Boolean = false } -case class Identical[T](value: T) extends DiffResult { +case class IdenticalValue[T](value: T) extends DiffResult { override def isIdentical: Boolean = true override def showIndented(indent: Int, renderIdentical: Boolean)(implicit c: ConsoleColorConfig): String = defaultColor(s"$value") } -case class DiffResultMissing[T](value: T) extends DiffResultDifferent { +case class DiffResultMissing[T](value: T) extends DiffResult { override def showIndented(indent: Int, renderIdentical: Boolean)(implicit c: ConsoleColorConfig): String = { rightColor(s"$value") } + override def isIdentical: Boolean = false } -case class DiffResultAdditional[T](value: T) extends DiffResultDifferent { +case class DiffResultAdditional[T](value: T) extends DiffResult { override def showIndented(indent: Int, renderIdentical: Boolean)(implicit c: ConsoleColorConfig): String = { leftColor(s"$value") } + override def isIdentical: Boolean = false } diff --git a/core/src/main/scala/com/softwaremill/diffx/DiffxSupport.scala b/core/src/main/scala/com/softwaremill/diffx/DiffxSupport.scala index 67b8276e..486eaa94 100644 --- a/core/src/main/scala/com/softwaremill/diffx/DiffxSupport.scala +++ b/core/src/main/scala/com/softwaremill/diffx/DiffxSupport.scala @@ -12,7 +12,7 @@ trait DiffxSupport extends DiffxEitherSupport with DiffxConsoleSupport with Diff if ((left == null && right != null) || (left != null && right == null)) { DiffResultValue(left, right) } else if (left == null && right == null) { - Identical(null) + IdenticalValue(null) } else { compareNotNull(left, right) } diff --git a/core/src/main/scala/com/softwaremill/diffx/TupleInstances.scala b/core/src/main/scala/com/softwaremill/diffx/TupleInstances.scala index 4ac73bdf..0312208f 100644 --- a/core/src/main/scala/com/softwaremill/diffx/TupleInstances.scala +++ b/core/src/main/scala/com/softwaremill/diffx/TupleInstances.scala @@ -9,11 +9,7 @@ trait TupleInstances { context: DiffContext ): DiffResult = { val results = List("_1" -> d1.apply(left._1, right._1), "_2" -> d2.apply(left._2, right._2)).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple2", results) - } + DiffResultObject("Tuple2", results) } } @@ -29,11 +25,7 @@ trait TupleInstances { "_2" -> d2.apply(left._2, right._2), "_3" -> d3.apply(left._3, right._3) ).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple3", results) - } + DiffResultObject("Tuple3", results) } } @@ -54,11 +46,7 @@ trait TupleInstances { "_3" -> d3.apply(left._3, right._3), "_4" -> d4.apply(left._4, right._4) ).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple4", results) - } + DiffResultObject("Tuple4", results) } } @@ -81,11 +69,7 @@ trait TupleInstances { "_4" -> d4.apply(left._4, right._4), "_5" -> d5.apply(left._5, right._5) ).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple5", results) - } + DiffResultObject("Tuple5", results) } } @@ -110,11 +94,7 @@ trait TupleInstances { "_5" -> d5.apply(left._5, right._5), "_6" -> d6.apply(left._6, right._6) ).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple6", results) - } + DiffResultObject("Tuple6", results) } } @@ -141,11 +121,7 @@ trait TupleInstances { "_6" -> d6.apply(left._6, right._6), "_7" -> d7.apply(left._7, right._7) ).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple7", results) - } + DiffResultObject("Tuple7", results) } } @@ -174,11 +150,7 @@ trait TupleInstances { "_7" -> d7.apply(left._7, right._7), "_8" -> d8.apply(left._8, right._8) ).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple8", results) - } + DiffResultObject("Tuple8", results) } } @@ -209,11 +181,7 @@ trait TupleInstances { "_8" -> d8.apply(left._8, right._8), "_9" -> d9.apply(left._9, right._9) ).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple9", results) - } + DiffResultObject("Tuple9", results) } } @@ -247,11 +215,7 @@ trait TupleInstances { "_9" -> d9.apply(left._9, right._9), "_10" -> d10.apply(left._10, right._10) ).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple10", results) - } + DiffResultObject("Tuple10", results) } } @@ -287,11 +251,7 @@ trait TupleInstances { "_10" -> d10.apply(left._10, right._10), "_11" -> d11.apply(left._11, right._11) ).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple11", results) - } + DiffResultObject("Tuple11", results) } } @@ -329,11 +289,7 @@ trait TupleInstances { "_11" -> d11.apply(left._11, right._11), "_12" -> d12.apply(left._12, right._12) ).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple12", results) - } + DiffResultObject("Tuple12", results) } } @@ -374,7 +330,7 @@ trait TupleInstances { "_13" -> d13.apply(left._13, right._13) ).toMap if (results.values.forall(_.isIdentical)) { - Identical(left) + IdenticalValue(left) } else { DiffResultObject("Tuple13", results) } @@ -419,11 +375,7 @@ trait TupleInstances { "_13" -> d13.apply(left._13, right._13), "_14" -> d14.apply(left._14, right._14) ).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple14", results) - } + DiffResultObject("Tuple14", results) } } @@ -467,11 +419,7 @@ trait TupleInstances { "_14" -> d14.apply(left._14, right._14), "_15" -> d15.apply(left._15, right._15) ).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple15", results) - } + DiffResultObject("Tuple15", results) } } @@ -517,11 +465,7 @@ trait TupleInstances { "_15" -> d15.apply(left._15, right._15), "_16" -> d16.apply(left._16, right._16) ).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple16", results) - } + DiffResultObject("Tuple16", results) } } @@ -569,11 +513,7 @@ trait TupleInstances { "_16" -> d16.apply(left._16, right._16), "_17" -> d17.apply(left._17, right._17) ).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple17", results) - } + DiffResultObject("Tuple17", results) } } @@ -623,11 +563,7 @@ trait TupleInstances { "_17" -> d17.apply(left._17, right._17), "_18" -> d18.apply(left._18, right._18) ).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple18", results) - } + DiffResultObject("Tuple18", results) } } @@ -679,11 +615,7 @@ trait TupleInstances { "_18" -> d18.apply(left._18, right._18), "_19" -> d19.apply(left._19, right._19) ).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple19", results) - } + DiffResultObject("Tuple19", results) } } @@ -738,11 +670,7 @@ trait TupleInstances { "_19" -> d19.apply(left._19, right._19), "_20" -> d20.apply(left._20, right._20) ).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple20", results) - } + DiffResultObject("Tuple20", results) } } @@ -799,11 +727,7 @@ trait TupleInstances { "_20" -> d20.apply(left._20, right._20), "_21" -> d21.apply(left._21, right._21) ).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple21", results) - } + DiffResultObject("Tuple21", results) } } @@ -887,11 +811,7 @@ trait TupleInstances { "_21" -> d21.apply(left._21, right._21), "_22" -> d22.apply(left._22, right._22) ).toMap - if (results.values.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultObject("Tuple22", results) - } + DiffResultObject("Tuple22", results) } } diff --git a/core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala b/core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala index 85e4d943..f77456ed 100644 --- a/core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala +++ b/core/src/main/scala/com/softwaremill/diffx/generic/DiffMagnoliaDerivation.scala @@ -1,6 +1,14 @@ package com.softwaremill.diffx.generic -import com.softwaremill.diffx.{Diff, DiffContext, DiffResultObject, DiffResultValue, FieldPath, Identical, nullGuard} +import com.softwaremill.diffx.{ + Diff, + DiffContext, + DiffResultObject, + DiffResultValue, + FieldPath, + IdenticalValue, + nullGuard +} import magnolia._ import scala.collection.immutable.ListMap @@ -16,11 +24,7 @@ trait DiffMagnoliaDerivation extends LowPriority { val fieldDiff = context.getOverride(p.label).map(_.asInstanceOf[Diff[p.PType]]).getOrElse(p.typeclass) p.label -> fieldDiff(lType, pType, context.getNextStep(p.label)) }: _*) - if (map.values.forall(p => p.isIdentical)) { - Identical(left) - } else { - DiffResultObject(ctx.typeName.short, map) - } + DiffResultObject(ctx.typeName.short, map) } } @@ -43,7 +47,7 @@ trait LowPriority { if (left != right) { DiffResultValue(left, right) } else { - Identical(left) + IdenticalValue(left) } } } diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/ApproximateDiffForNumeric.scala b/core/src/main/scala/com/softwaremill/diffx/instances/ApproximateDiffForNumeric.scala index ba0d08b3..e784bc23 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/ApproximateDiffForNumeric.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/ApproximateDiffForNumeric.scala @@ -8,7 +8,7 @@ private[diffx] class ApproximateDiffForNumeric[T: Numeric](epsilon: T) extends D if (numeric.lt(epsilon, numeric.abs(numeric.minus(left, right)))) { DiffResultValue(left, right) } else { - Identical(left) + IdenticalValue(left) } } } diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala index 2e53b617..8e772395 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala @@ -32,10 +32,6 @@ private[diffx] class DiffForIterable[T, C[W] <: Iterable[W]]( val matchedDiffs = matchedInstances.map { case (l, r) => l._1 -> dt(l._2, r._2, context) }.toList val diffs = ListMap((matchedDiffs ++ leftDiffs ++ rightDiffs).map { case (k, v) => k.toString -> v }: _*) - if (diffs.forall { case (_, v) => v.isIdentical }) { - Identical(left) - } else { - DiffResultObject("List", diffs) - } + DiffResultObject("List", diffs) } } diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala index c6756deb..d7983864 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala @@ -28,10 +28,6 @@ private[diffx] class DiffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]] val matchedDiffs = matchedKeys.map { case (l, r) => diffKey(l._1, r._1) -> diffValue(l._2, r._2, context) }.toList val diffs = leftDiffs ++ rightDiffs ++ matchedDiffs - if (diffs.forall(p => p._1.isIdentical && p._2.isIdentical)) { - Identical(left) - } else { - DiffResultMap(diffs.toMap) - } + DiffResultMap(diffs.toMap) } } diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForNumeric.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForNumeric.scala index 2789d381..801e77a7 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForNumeric.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForNumeric.scala @@ -8,7 +8,7 @@ private[diffx] class DiffForNumeric[T: Numeric] extends Diff[T] { if (!numeric.equiv(left, right)) { DiffResultValue(left, right) } else { - Identical(left) + IdenticalValue(left) } } } diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForOption.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForOption.scala index 6b356339..6c76bd62 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForOption.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForOption.scala @@ -6,7 +6,7 @@ private[diffx] class DiffForOption[T](dt: Diff[T]) extends Diff[Option[T]] { override def apply(left: Option[T], right: Option[T], context: DiffContext): DiffResult = { (left, right) match { case (Some(l), Some(r)) => dt.apply(l, r, context) - case (None, None) => Identical(None) + case (None, None) => IdenticalValue(None) case (l, r) => DiffResultValue(l, r) } } diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala index 1f098823..18e9e5fa 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForSet.scala @@ -29,10 +29,6 @@ private[diffx] class DiffForSet[T, C[W] <: scala.collection.Set[W]](dt: Diff[T], matchedDiffs: List[DiffResult] ): DiffResult = { val diffs = leftDiffs ++ rightDiffs ++ matchedDiffs - if (diffs.forall(_.isIdentical)) { - Identical(left) - } else { - DiffResultSet(diffs) - } + DiffResultSet(diffs) } } diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForString.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForString.scala index 000d2cd9..c8a82db2 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForString.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForString.scala @@ -14,7 +14,7 @@ private[diffx] class DiffForString extends Diff[String] { (leftAsMap(i), rightAsMap(i)) match { case (Some(lv), Some(rv)) => if (lv == rv) { - Identical(lv) + IdenticalValue(lv) } else { DiffResultValue(lv, rv) } @@ -24,7 +24,7 @@ private[diffx] class DiffForString extends Diff[String] { } }.toList if (partialResults.forall(_.isIdentical)) { - Identical(left) + IdenticalValue(left) } else { DiffResultString(partialResults) } diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffModifyIntegrationTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffModifyIntegrationTest.scala index 473419e2..6e759772 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffModifyIntegrationTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffModifyIntegrationTest.scala @@ -17,7 +17,7 @@ class DiffModifyIntegrationTest extends AnyFlatSpec with Matchers { implicit val d: Diff[Person] = Derived[Diff[Person]].ignore(_.name) compare(p1, p2) shouldBe DiffResultObject( "Person", - Map("name" -> Identical(""), "age" -> DiffResultValue(22, 11), "in" -> Identical(instant)) + Map("name" -> IdenticalValue(""), "age" -> DiffResultValue(22, 11), "in" -> IdenticalValue(instant)) ) } @@ -25,7 +25,7 @@ class DiffModifyIntegrationTest extends AnyFlatSpec with Matchers { implicit val d: Diff[Person] = Derived[Diff[Person]].ignore(_.name) compare(p1, p2) shouldBe DiffResultObject( "Person", - Map("name" -> Identical(""), "age" -> DiffResultValue(22, 11), "in" -> Identical(instant)) + Map("name" -> IdenticalValue(""), "age" -> DiffResultValue(22, 11), "in" -> IdenticalValue(instant)) ) } @@ -33,7 +33,7 @@ class DiffModifyIntegrationTest extends AnyFlatSpec with Matchers { implicit val d: Diff[Person] = Derived[Diff[Person]] .ignore(_.name) .ignore(_.age) - compare(p1, p2) shouldBe Identical(p1) + compare(p1, p2).isIdentical shouldBe true } it should "compare lists using explicit object matcher comparator" in { @@ -44,7 +44,7 @@ class DiffModifyIntegrationTest extends AnyFlatSpec with Matchers { .withListMatcher( ObjectMatcher.byValue[Int, Person](ObjectMatcher.by(_.name)) ) - compare(o1, o2) shouldBe Identical(Organization(List(p1, p2))) + compare(o1, o2).isIdentical shouldBe true } it should "ignore only on right" in { @@ -54,12 +54,12 @@ class DiffModifyIntegrationTest extends AnyFlatSpec with Matchers { implicit val wrapperDiff: Diff[Wrapper] = Derived[Diff[Wrapper]].ignore(_.e.eachRight.name) - compare(e1, e2) shouldBe Identical(e1) + compare(e1, e2).isIdentical shouldBe true val e3 = Wrapper(Left(p1)) val e4 = Wrapper(Left(p1.copy(name = p1.name + "_modified"))) - compare(e3, e4) should not be an[Identical[_]] + compare(e3, e4).isIdentical shouldBe false } it should "ignore only on left" in { @@ -69,11 +69,11 @@ class DiffModifyIntegrationTest extends AnyFlatSpec with Matchers { implicit val wrapperDiff: Diff[Wrapper] = Derived[Diff[Wrapper]].ignore(_.e.eachLeft.name) - compare(e1, e2) should not be an[Identical[_]] + compare(e1, e2).isIdentical shouldBe false val e3 = Wrapper(Left(p1)) val e4 = Wrapper(Left(p1.copy(name = p1.name + "_modified"))) - compare(e3, e4) shouldBe an[Identical[_]] + compare(e3, e4).isIdentical shouldBe true } it should "match map entries by values" in { @@ -95,9 +95,9 @@ class DiffModifyIntegrationTest extends AnyFlatSpec with Matchers { "KeyModel", Map( "id" -> DiffResultValue(uuid1, uuid2), - "name" -> Identical("k1") + "name" -> IdenticalValue("k1") ) - ) -> Identical("val1") + ) -> IdenticalValue("val1") ) ) ) @@ -114,10 +114,21 @@ class DiffModifyIntegrationTest extends AnyFlatSpec with Matchers { Map( "workers" -> DiffResultSet( List( - Identical(p1), DiffResultObject( "Person", - Map("name" -> Identical(p2.name), "age" -> DiffResultValue(p2.age, p2m.age), "in" -> Identical(p1.in)) + Map( + "name" -> IdenticalValue(p1.name), + "age" -> IdenticalValue(p1.age), + "in" -> IdenticalValue(p1.in) + ) + ), + DiffResultObject( + "Person", + Map( + "name" -> IdenticalValue(p2.name), + "age" -> DiffResultValue(p2.age, p2m.age), + "in" -> IdenticalValue(p1.in) + ) ) ) ) diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffResultTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffResultTest.scala index 5707c62c..de292239 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffResultTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffResultTest.scala @@ -10,7 +10,7 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport "diff set output" - { "it should show a simple difference" in { - val output = DiffResultSet(List(Identical("a"), DiffResultValue("1", "2"))).show() + val output = DiffResultSet(List(IdenticalValue("a"), DiffResultValue("1", "2"))).show() output shouldBe s"""Set( | a, @@ -19,7 +19,7 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport "it should show an indented difference" in { val output = - DiffResultSet(List(Identical("a"), DiffResultValue("1", "2"))).show() + DiffResultSet(List(IdenticalValue("a"), DiffResultValue("1", "2"))).show() output shouldBe s"""Set( | a, @@ -27,7 +27,7 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport } "it should show a nested list difference" in { - val output = DiffResultSet(List(Identical("a"), DiffResultSet(List(Identical("b"))))).show() + val output = DiffResultSet(List(IdenticalValue("a"), DiffResultSet(List(IdenticalValue("b"))))).show() output shouldBe s"""Set( | a, @@ -36,14 +36,14 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport } "it should show null" in { - val output = DiffResultSet(List(Identical(null), DiffResultValue(null, null))).show() + val output = DiffResultSet(List(IdenticalValue(null), DiffResultValue(null, null))).show() output shouldBe s"""Set( | null, | null -> null)""".stripMargin } "it shouldn't render identical elements" in { - val output = DiffResultSet(List(Identical("a"), DiffResultValue("1", "2"))).show(renderIdentical = false) + val output = DiffResultSet(List(IdenticalValue("a"), DiffResultValue("1", "2"))).show(renderIdentical = false) output shouldBe s"""Set( | 1 -> 2)""".stripMargin @@ -53,7 +53,7 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport "diff map output" - { "it should show a simple diff" in { val output = - DiffResultMap(Map(Identical("a") -> DiffResultValue(1, 2), DiffResultMissing("b") -> DiffResultMissing(3))) + DiffResultMap(Map(IdenticalValue("a") -> DiffResultValue(1, 2), DiffResultMissing("b") -> DiffResultMissing(3))) .show() output shouldBe s"""Map( @@ -63,7 +63,7 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport "it should show an indented diff" in { val output = - DiffResultMap(Map(Identical("a") -> DiffResultValue(1, 2), DiffResultMissing("b") -> DiffResultMissing(3))) + DiffResultMap(Map(IdenticalValue("a") -> DiffResultValue(1, 2), DiffResultMissing("b") -> DiffResultMissing(3))) .show() output shouldBe s"""Map( @@ -73,7 +73,7 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport "it should show a nested diff" in { val output = - DiffResultMap(Map(Identical("a") -> DiffResultMap(Map(Identical("b") -> DiffResultValue(1, 2))))) + DiffResultMap(Map(IdenticalValue("a") -> DiffResultMap(Map(IdenticalValue("b") -> DiffResultValue(1, 2))))) .show() output shouldBe s"""Map( @@ -85,9 +85,9 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport val output = DiffResultMap( Map( - Identical("a") -> DiffResultValue(1, 2), - DiffResultValue("b", "c") -> Identical(3), - Identical("d") -> Identical(4) + IdenticalValue("a") -> DiffResultValue(1, 2), + DiffResultValue("b", "c") -> IdenticalValue(3), + IdenticalValue("d") -> IdenticalValue(4) ) ) .show(renderIdentical = false) @@ -105,7 +105,7 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport val output = DiffResultObject( "List", - Map("0" -> DiffResultValue(1234, 123), "1" -> DiffResultMissing(1234), "2" -> Identical(1234)) + Map("0" -> DiffResultValue(1234, 123), "1" -> DiffResultMissing(1234), "2" -> IdenticalValue(1234)) ) .show()(colorConfigWithPlusMinus) output shouldBe @@ -118,7 +118,7 @@ class DiffResultTest extends AnyFreeSpec with Matchers with DiffxConsoleSupport "it should not render identical fields" in { val output = DiffResultObject( "List", - Map("0" -> DiffResultValue(1234, 123), "1" -> DiffResultMissing(1234), "2" -> Identical(1234)) + Map("0" -> DiffResultValue(1234, 123), "1" -> DiffResultMissing(1234), "2" -> IdenticalValue(1234)) ) .show(renderIdentical = false) output shouldBe diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala index 3b1807a6..8dc87737 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffSemiautoTest.scala @@ -1,7 +1,7 @@ package com.softwaremill.diffx.test import com.softwaremill.diffx.test.ACoproduct.ProductA -import com.softwaremill.diffx.{Derived, Diff, Identical} +import com.softwaremill.diffx.{Derived, Diff, IdenticalValue} import org.scalatest.freespec.AnyFreeSpec import org.scalatest.matchers.should.Matchers @@ -41,25 +41,19 @@ class DiffSemiautoTest extends AnyFreeSpec with Matchers { "should work for coproducts" in { implicit val dACoproduct: Derived[Diff[ACoproduct]] = Diff.derived[ACoproduct] - Diff.compare[ACoproduct](ProductA("1"), ProductA("1")) shouldBe Identical( - ProductA("1") - ) + Diff.compare[ACoproduct](ProductA("1"), ProductA("1")).isIdentical shouldBe true } "should allow ignoring on derived diffs" in { implicit val dACoproduct: Derived[Diff[ProductA]] = Diff.derived[ProductA].ignore(_.id) - Diff.compare[ProductA](ProductA("1"), ProductA("2")) shouldBe Identical( - ProductA("1") - ) + Diff.compare[ProductA](ProductA("1"), ProductA("2")).isIdentical shouldBe true } "should allow modifying derived diffs" in { implicit val dACoproduct: Derived[Diff[ProductA]] = Diff.derived[ProductA].modify(_.id).ignore() - Diff.compare[ProductA](ProductA("1"), ProductA("2")) shouldBe Identical( - ProductA("1") - ) + Diff.compare[ProductA](ProductA("1"), ProductA("2")).isIdentical shouldBe true } } diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala index dc473a0c..9d0fca4f 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala @@ -19,14 +19,14 @@ class DiffTest extends AnyFreeSpec with Matchers { compare(1, 2) shouldBe DiffResultValue(1, 2) } "identity" in { - compare(1, 1) shouldBe Identical(1) + compare(1, 1) shouldBe IdenticalValue(1) } "contravariant" in { - compare(Some(1), Option(1)) shouldBe Identical(1) + compare(Some(1), Option(1)) shouldBe IdenticalValue(1) } "approximate - identical" in { val diff = Diff.approximate[Double](0.05) - diff(0.12, 0.14) shouldBe Identical(0.12) + diff(0.12, 0.14) shouldBe IdenticalValue(0.12) } "approximate - different" in { val diff = Diff.approximate[Double](0.05) @@ -42,7 +42,7 @@ class DiffTest extends AnyFreeSpec with Matchers { "products" - { "identity" in { - compare(p1, p1) shouldBe Identical(p1) + compare(p1, p1).isIdentical shouldBe true } "nullable" in { @@ -55,7 +55,7 @@ class DiffTest extends AnyFreeSpec with Matchers { Map( "name" -> DiffResultString(List(DiffResultValue(p1.name, p2.name))), "age" -> DiffResultValue(p1.age, p2.age), - "in" -> Identical(instant) + "in" -> IdenticalValue(instant) ) ) } @@ -66,13 +66,25 @@ class DiffTest extends AnyFreeSpec with Matchers { Map( "name" -> DiffResultValue(null, p2.name), "age" -> DiffResultValue(p1.age, p2.age), - "in" -> Identical(instant) + "in" -> IdenticalValue(instant) ) ) } "two nulls should be equal" in { - compare(p1.copy(name = null), p1.copy(name = null)) shouldBe an[Identical[_]] + compare(p1.copy(name = null), p1.copy(name = null)).isIdentical shouldBe true + } + + "ignored fields should be different than identical" in { + implicit val d: Diff[Person] = Derived[Diff[Person]].modifyUnsafe("name")(Diff.ignored) + compare(p1, p1.copy(name = "other")) shouldBe DiffResultObject( + "Person", + Map( + "name" -> DiffResult.Ignored, + "age" -> IdenticalValue(p1.age), + "in" -> IdenticalValue(p1.in) + ) + ) } "ignoring given fields" in { @@ -95,13 +107,20 @@ class DiffTest extends AnyFreeSpec with Matchers { compare(f1, f2) shouldBe DiffResultObject( "Family", Map( - "first" -> Identical(p1), + "first" -> DiffResultObject( + "Person", + Map( + "name" -> IdenticalValue(p1.name), + "age" -> IdenticalValue(p1.age), + "in" -> IdenticalValue(p1.in) + ) + ), "second" -> DiffResultObject( "Person", Map( "name" -> DiffResultString(List(DiffResultValue(p2.name, p1.name))), "age" -> DiffResultValue(p2.age, p1.age), - "in" -> Identical(instant) + "in" -> IdenticalValue(instant) ) ) ) @@ -111,17 +130,24 @@ class DiffTest extends AnyFreeSpec with Matchers { "nested products ignoring nested fields" in { val f1 = Family(p1, p2) val f2 = Family(p1, p1) - implicit val d: Diff[Family] = Derived[Diff[Family]].modifyUnsafe("second", "name")(Diff.identical) + implicit val d: Diff[Family] = Derived[Diff[Family]].modifyUnsafe("second", "name")(Diff.ignored) compare(f1, f2) shouldBe DiffResultObject( "Family", Map( - "first" -> Identical(p1), + "first" -> DiffResultObject( + "Person", + Map( + "name" -> IdenticalValue(p1.name), + "age" -> IdenticalValue(p1.age), + "in" -> IdenticalValue(p1.in) + ) + ), "second" -> DiffResultObject( "Person", Map( - "name" -> Identical(p2.name), + "name" -> DiffResult.Ignored, "age" -> DiffResultValue(p2.age, p1.age), - "in" -> Identical(instant) + "in" -> IdenticalValue(instant) ) ) ) @@ -132,7 +158,7 @@ class DiffTest extends AnyFreeSpec with Matchers { val p1p = p1.copy(name = "other") val f1 = Family(p1, p2) val f2 = Family(p1p, p2.copy(name = "other")) - implicit val d: Diff[Family] = Derived[Diff[Family]].modifyUnsafe("second", "name")(Diff.identical) + implicit val d: Diff[Family] = Derived[Diff[Family]].modifyUnsafe("second", "name")(Diff.ignored) compare(f1, f2) shouldBe DiffResultObject( "Family", Map( @@ -140,11 +166,18 @@ class DiffTest extends AnyFreeSpec with Matchers { "Person", Map( "name" -> DiffResultString(List(DiffResultValue(p1.name, p1p.name))), - "age" -> Identical(p1.age), - "in" -> Identical(instant) + "age" -> IdenticalValue(p1.age), + "in" -> IdenticalValue(instant) ) ), - "second" -> Identical(p2) + "second" -> DiffResultObject( + "Person", + Map( + "name" -> DiffResult.Ignored, + "age" -> IdenticalValue(p2.age), + "in" -> IdenticalValue(p2.in) + ) + ) ) ) } @@ -152,8 +185,8 @@ class DiffTest extends AnyFreeSpec with Matchers { "nested products ignoring nested products" in { val f1 = Family(p1, p2) val f2 = Family(p1, p1) - implicit val d: Diff[Family] = Derived[Diff[Family]].modifyUnsafe("second")(Diff.identical) - compare(f1, f2) shouldBe Identical(f1) + implicit val d: Diff[Family] = Derived[Diff[Family]].modifyUnsafe("second")(Diff.ignored) + compare(f1, f2).isIdentical shouldBe true } "list of products" in { @@ -165,13 +198,20 @@ class DiffTest extends AnyFreeSpec with Matchers { "people" -> DiffResultObject( "List", Map( - "0" -> Identical(p1), + "0" -> DiffResultObject( + "Person", + Map( + "name" -> IdenticalValue(p1.name), + "age" -> IdenticalValue(p1.age), + "in" -> IdenticalValue(p1.in) + ) + ), "1" -> DiffResultObject( "Person", Map( "name" -> DiffResultString(List(DiffResultValue(p2.name, p1.name))), "age" -> DiffResultValue(p2.age, p1.age), - "in" -> Identical(instant) + "in" -> IdenticalValue(instant) ) ), "2" -> DiffResultMissing(Person(p1.name, p1.age, instant)) @@ -180,6 +220,37 @@ class DiffTest extends AnyFreeSpec with Matchers { ) ) } + + "identical list of products" in { + val o1 = Organization(List(p1, p2)) + val o2 = Organization(List(p1, p2)) + compare(o1, o2) shouldBe DiffResultObject( + "Organization", + Map( + "people" -> DiffResultObject( + "List", + Map( + "0" -> DiffResultObject( + "Person", + Map( + "name" -> IdenticalValue(p1.name), + "age" -> IdenticalValue(p1.age), + "in" -> IdenticalValue(p1.in) + ) + ), + "1" -> DiffResultObject( + "Person", + Map( + "name" -> IdenticalValue(p2.name), + "age" -> IdenticalValue(p2.age), + "in" -> IdenticalValue(p2.in) + ) + ) + ) + ) + ) + ) + } } "coproducts" - { @@ -196,7 +267,7 @@ class DiffTest extends AnyFreeSpec with Matchers { "sealed trait objects" - { "identity" in { - compare[TsDirection](TsDirection.Outgoing, TsDirection.Outgoing) shouldBe an[Identical[_]] + compare[TsDirection](TsDirection.Outgoing, TsDirection.Outgoing).isIdentical shouldBe true } "diff" in { compare[TsDirection](TsDirection.Outgoing, TsDirection.Incoming) shouldBe DiffResultValue( @@ -207,7 +278,7 @@ class DiffTest extends AnyFreeSpec with Matchers { } "identity" in { - compare(left, left) shouldBe an[Identical[_]] + compare(left, left).isIdentical shouldBe true } "nullable" in { @@ -218,7 +289,7 @@ class DiffTest extends AnyFreeSpec with Matchers { compare(left, right) shouldBe DiffResultObject( "Foo", Map( - "bar" -> DiffResultObject("Bar", Map("s" -> Identical("asdf"), "i" -> DiffResultValue(66, 5))), + "bar" -> DiffResultObject("Bar", Map("s" -> IdenticalValue("asdf"), "i" -> DiffResultValue(66, 5))), "b" -> DiffResultObject("List", Map("0" -> DiffResultValue(1234, 123), "1" -> DiffResultMissing(1234))), "parent" -> DiffResultValue("com.softwaremill.diffx.test.Foo", "com.softwaremill.diffx.test.Bar") ) @@ -244,7 +315,7 @@ class DiffTest extends AnyFreeSpec with Matchers { "collections" - { "list" - { "identical" in { - compare(List("a"), List("a")) shouldBe Identical(List("a")) + compare(List("a"), List("a")) shouldBe DiffResultObject("List", Map("0" -> IdenticalValue("a"))) } "nullable" in { @@ -261,20 +332,27 @@ class DiffTest extends AnyFreeSpec with Matchers { "use ignored fields from elements" in { val o1 = Organization(List(p1, p2)) val o2 = Organization(List(p1, p1, p1)) - implicit val d: Diff[Organization] = Derived[Diff[Organization]].modifyUnsafe("people", "name")(Diff.identical) + implicit val d: Diff[Organization] = Derived[Diff[Organization]].modifyUnsafe("people", "name")(Diff.ignored) compare(o1, o2) shouldBe DiffResultObject( "Organization", Map( "people" -> DiffResultObject( "List", Map( - "0" -> Identical(p1), + "0" -> DiffResultObject( + "Person", + Map( + "name" -> DiffResult.Ignored, + "age" -> IdenticalValue(p1.age), + "in" -> IdenticalValue(p1.in) + ) + ), "1" -> DiffResultObject( "Person", Map( - "name" -> Identical(p2.name), + "name" -> DiffResult.Ignored, "age" -> DiffResultValue(p2.age, p1.age), - "in" -> Identical(instant) + "in" -> IdenticalValue(instant) ) ), "2" -> DiffResultMissing(Person(p1.name, p1.age, instant)) @@ -289,14 +367,14 @@ class DiffTest extends AnyFreeSpec with Matchers { val o2 = Organization(List(p2, p1)) implicit val om: ObjectMatcher[Person] = ObjectMatcher.by(_.name) implicit val dd: Diff[List[Person]] = Diff[Set[Person]].contramap(_.toSet) - compare(o1, o2) shouldBe Identical(Organization(List(p1, p2))) + compare(o1, o2).isIdentical shouldBe true } "compare lists using object matcher comparator" in { val o1 = Organization(List(p1, p2)) val o2 = Organization(List(p2, p1)) implicit val om: ObjectMatcher[(Int, Person)] = ObjectMatcher.byValue(_.name) - compare(o1, o2) shouldBe Identical(Organization(List(p1, p2))) + compare(o1, o2).isIdentical shouldBe true } "compare lists using explicit object matcher comparator" in { @@ -305,7 +383,7 @@ class DiffTest extends AnyFreeSpec with Matchers { implicit val orgDiff: Diff[Organization] = Derived[Diff[Organization]].modifyMatcherUnsafe("people")( ObjectMatcher.byValue[Int, Person](ObjectMatcher.by(_.name)) ) - compare(o1, o2) shouldBe Identical(Organization(List(p1, p2))) + compare(o1, o2).isIdentical shouldBe true } "should preserve order of elements" in { @@ -314,11 +392,11 @@ class DiffTest extends AnyFreeSpec with Matchers { compare(l1, l2) shouldBe DiffResultObject( "List", ListMap( - "0" -> Identical(1), - "1" -> Identical(2), - "2" -> Identical(3), - "3" -> Identical(4), - "4" -> Identical(5), + "0" -> IdenticalValue(1), + "1" -> IdenticalValue(2), + "2" -> IdenticalValue(3), + "3" -> IdenticalValue(4), + "4" -> IdenticalValue(5), "5" -> DiffResultValue(6, 7) ) ) @@ -330,8 +408,8 @@ class DiffTest extends AnyFreeSpec with Matchers { compare(l1, l2) shouldBe DiffResultObject( "List", ListMap( - "0" -> Identical(1), - "1" -> Identical(2), + "0" -> IdenticalValue(1), + "1" -> IdenticalValue(2), "2" -> DiffResultValue(3, 4), "3" -> DiffResultValue(4, 5), "4" -> DiffResultValue(5, 6), @@ -342,7 +420,7 @@ class DiffTest extends AnyFreeSpec with Matchers { } "sets" - { "identity" in { - compare(Set(1), Set(1)) shouldBe an[Identical[_]] + compare(Set(1), Set(1)).isIdentical shouldBe true } "nullable" in { @@ -353,24 +431,31 @@ class DiffTest extends AnyFreeSpec with Matchers { val diffResult = compare(Set(1, 2, 3, 4, 5), Set(1, 2, 3, 4)).asInstanceOf[DiffResultSet] diffResult.diffs should contain theSameElementsAs List( DiffResultAdditional(5), - Identical(4), - Identical(3), - Identical(2), - Identical(1) + IdenticalValue(4), + IdenticalValue(3), + IdenticalValue(2), + IdenticalValue(1) ) } "ignored fields from elements" in { val p2m = p2.copy(age = 33, in = Instant.now()) - implicit val d: Diff[Person] = Derived[Diff[Person]].modifyUnsafe("age")(Diff.identical) + implicit val d: Diff[Person] = Derived[Diff[Person]].modifyUnsafe("age")(Diff.ignored) implicit val im: ObjectMatcher[Person] = ObjectMatcher.by(_.name) compare(Set(p1, p2), Set(p1, p2m)) shouldBe DiffResultSet( List( - Identical(p1), DiffResultObject( "Person", Map( - "name" -> Identical(p2.name), - "age" -> Identical(p2.age), + "name" -> IdenticalValue(p1.name), + "age" -> DiffResult.Ignored, + "in" -> IdenticalValue(p1.in) + ) + ), + DiffResultObject( + "Person", + Map( + "name" -> IdenticalValue(p2.name), + "age" -> DiffResult.Ignored, "in" -> DiffResultValue(p1.in, p2m.in) ) ) @@ -383,33 +468,40 @@ class DiffTest extends AnyFreeSpec with Matchers { val diffResult = compare(mSet(1, 2, 3, 4, 5), mSet(1, 2, 3, 4)).asInstanceOf[DiffResultSet] diffResult.diffs should contain theSameElementsAs List( DiffResultAdditional(5), - Identical(4), - Identical(3), - Identical(2), - Identical(1) + IdenticalValue(4), + IdenticalValue(3), + IdenticalValue(2), + IdenticalValue(1) ) } "identical when products are identical using ignored" in { val p2m = p2.copy(age = 33, in = Instant.now()) implicit val d: Diff[Person] = Derived[Diff[Person]] - .modifyUnsafe("age")(Diff.identical) - .modifyUnsafe("in")(Diff.identical) - compare(Set(p1, p2), Set(p1, p2m)) shouldBe Identical(Set(p1, p2)) + .modifyUnsafe("age")(Diff.ignored) + .modifyUnsafe("in")(Diff.ignored) + compare(Set(p1, p2), Set(p1, p2m)).isIdentical shouldBe true } "propagate ignore fields to elements" in { val p2m = p2.copy(in = Instant.now()) implicit val im: ObjectMatcher[Person] = ObjectMatcher.by(_.name) - implicit val ds: Diff[Person] = Derived[Diff[Person]].modifyUnsafe("age")(Diff.identical) + implicit val ds: Diff[Person] = Derived[Diff[Person]].modifyUnsafe("age")(Diff.ignored) compare(Set(p1, p2), Set(p1, p2m)) shouldBe DiffResultSet( List( - Identical(p1), DiffResultObject( "Person", Map( - "name" -> Identical(p2.name), - "age" -> Identical(p2.age), + "name" -> IdenticalValue(p1.name), + "age" -> DiffResult.Ignored, + "in" -> IdenticalValue(p1.in) + ) + ), + DiffResultObject( + "Person", + Map( + "name" -> IdenticalValue(p2.name), + "age" -> DiffResult.Ignored, "in" -> DiffResultValue(p1.in, p2m.in) ) ) @@ -419,14 +511,25 @@ class DiffTest extends AnyFreeSpec with Matchers { "set of products" in { val p2m = p2.copy(age = 33) compare(Set(p1, p2), Set(p1, p2m)) shouldBe DiffResultSet( - List(DiffResultAdditional(p2), DiffResultMissing(p2m), Identical(p1)) + List( + DiffResultAdditional(p2), + DiffResultMissing(p2m), + DiffResultObject( + "Person", + Map( + "name" -> IdenticalValue(p1.name), + "age" -> IdenticalValue(p1.age), + "in" -> IdenticalValue(p1.in) + ) + ) + ) ) } "override set instance" in { val p2m = p2.copy(age = 33) implicit def setDiff[T, C[W] <: scala.collection.Set[W]]: Diff[C[T]] = - (left: C[T], _: C[T], _: DiffContext) => Identical(left) - compare(Set(p1, p2), Set(p1, p2m)) shouldBe an[Identical[_]] + (left: C[T], _: C[T], _: DiffContext) => IdenticalValue(left) + compare(Set(p1, p2), Set(p1, p2m)).isIdentical shouldBe true } "set of products using instance matcher" in { @@ -437,10 +540,21 @@ class DiffTest extends AnyFreeSpec with Matchers { Map( "workers" -> DiffResultSet( List( - Identical(p1), DiffResultObject( "Person", - Map("name" -> Identical(p2.name), "age" -> DiffResultValue(p2.age, p2m.age), "in" -> Identical(p1.in)) + Map( + "name" -> IdenticalValue(p1.name), + "age" -> IdenticalValue(p1.age), + "in" -> IdenticalValue(p1.in) + ) + ), + DiffResultObject( + "Person", + Map( + "name" -> IdenticalValue(p2.name), + "age" -> DiffResultValue(p2.age, p2m.age), + "in" -> IdenticalValue(p1.in) + ) ) ) ) @@ -451,7 +565,7 @@ class DiffTest extends AnyFreeSpec with Matchers { "maps" - { "identical" in { val m1 = Map("a" -> 1) - compare(m1, m1) shouldBe an[Identical[_]] + compare(m1, m1).isIdentical shouldBe true } "nullable" in { @@ -461,25 +575,25 @@ class DiffTest extends AnyFreeSpec with Matchers { "simple diff" in { val m1 = Map("a" -> 1) val m2 = Map("a" -> 2) - compare(m1, m2) shouldBe DiffResultMap(Map(Identical("a") -> DiffResultValue(1, 2))) + compare(m1, m2) shouldBe DiffResultMap(Map(IdenticalValue("a") -> DiffResultValue(1, 2))) } "simple diff - mutable map" in { val m1 = scala.collection.Map("a" -> 1) val m2 = scala.collection.Map("a" -> 2) - compare(m1, m2) shouldBe DiffResultMap(Map(Identical("a") -> DiffResultValue(1, 2))) + compare(m1, m2) shouldBe DiffResultMap(Map(IdenticalValue("a") -> DiffResultValue(1, 2))) } "propagate ignored fields to elements" in { - implicit val dm: Diff[Person] = Derived[Diff[Person]].modifyUnsafe("age")(Diff.identical) + implicit val dm: Diff[Person] = Derived[Diff[Person]].modifyUnsafe("age")(Diff.ignored) compare(Map("first" -> p1), Map("first" -> p2)) shouldBe DiffResultMap( Map( - Identical("first") -> DiffResultObject( + IdenticalValue("first") -> DiffResultObject( "Person", Map( "name" -> DiffResultString(List(DiffResultValue(p1.name, p2.name))), - "age" -> Identical(p1.age), - "in" -> Identical(p1.in) + "age" -> DiffResult.Ignored, + "in" -> IdenticalValue(p1.in) ) ) ) @@ -489,9 +603,9 @@ class DiffTest extends AnyFreeSpec with Matchers { "identical when products are identical using ignore" in { implicit val dm: Diff[Person] = Derived[Diff[Person]] - .modifyUnsafe("age")(Diff.identical) - .modifyUnsafe("name")(Diff.identical) - compare(Map("first" -> p1), Map("first" -> p2)) shouldBe Identical(Map("first" -> p1)) + .modifyUnsafe("age")(Diff.ignored) + .modifyUnsafe("name")(Diff.ignored) + compare(Map("first" -> p1), Map("first" -> p2)).isIdentical shouldBe true } "maps by values" in { @@ -502,7 +616,7 @@ class DiffTest extends AnyFreeSpec with Matchers { compare( Map[String, Person]("i1" -> person), Map[String, Person]("i2" -> person) - ) shouldBe Identical(List(person)) + ).isIdentical shouldBe true } "ignore part of map's key using keys's diff specification" in { @@ -510,7 +624,7 @@ class DiffTest extends AnyFreeSpec with Matchers { val a1 = MyLookup(Map(KeyModel(UUID.randomUUID(), "k1") -> "val1")) val a2 = MyLookup(Map(KeyModel(UUID.randomUUID(), "k1") -> "val1")) - compare(a1, a2) shouldBe Identical(a1) + compare(a1, a2).isIdentical shouldBe true } "match keys using object mapper" in { @@ -528,9 +642,9 @@ class DiffTest extends AnyFreeSpec with Matchers { "KeyModel", Map( "id" -> DiffResultValue(uuid1, uuid2), - "name" -> Identical("k1") + "name" -> IdenticalValue("k1") ) - ) -> Identical("val1") + ) -> IdenticalValue("val1") ) ) ) @@ -552,9 +666,9 @@ class DiffTest extends AnyFreeSpec with Matchers { "KeyModel", Map( "id" -> DiffResultValue(uuid1, uuid2), - "name" -> Identical("k1") + "name" -> IdenticalValue("k1") ) - ) -> Identical("val1") + ) -> IdenticalValue("val1") ) ) ) @@ -565,7 +679,7 @@ class DiffTest extends AnyFreeSpec with Matchers { "identical" in { val r1 = 0 until 100 val r2 = 0 until 100 - compare(r1, r2) shouldBe Identical(r1) + compare(r1, r2) shouldBe IdenticalValue(r1) } "dif" in { val r1 = 0 until 100 @@ -587,7 +701,7 @@ class DiffTest extends AnyFreeSpec with Matchers { val not = new HasCustomEquals("not") val diffInstance = Diff.useEquals[HasCustomEquals] - diffInstance.apply(a, z) shouldBe Identical(a) + diffInstance.apply(a, z) shouldBe IdenticalValue(a) diffInstance.apply(a, not) shouldBe DiffResultValue(a, not) } } @@ -597,7 +711,7 @@ class DiffTest extends AnyFreeSpec with Matchers { val left = "scalaIsAwesome" val right = "scalaIsAwesome" - compare(left, right) shouldBe Identical(left) + compare(left, right) shouldBe IdenticalValue(left) } "different strings should be different" in { @@ -620,9 +734,9 @@ class DiffTest extends AnyFreeSpec with Matchers { compare(left, right) shouldBe DiffResultString( List( - Identical("first"), + IdenticalValue("first"), DiffResultValue("second", "sec???"), - Identical("third"), + IdenticalValue("third"), DiffResultAdditional("fourth") ) ) @@ -631,12 +745,12 @@ class DiffTest extends AnyFreeSpec with Matchers { "either" - { "equal rights should be identical" in { val e1: Either[String, String] = Right("a") - compare(e1, e1) shouldBe Identical("a") + compare(e1, e1) shouldBe IdenticalValue("a") } "equal lefts should be identical" in { val e1: Either[String, String] = Left("a") - compare(e1, e1) shouldBe Identical("a") + compare(e1, e1) shouldBe IdenticalValue("a") } "left and right should be different" in { val e1: Either[String, String] = Left("a") @@ -647,41 +761,41 @@ class DiffTest extends AnyFreeSpec with Matchers { "tuples" - { "tuple2" - { "equal tuples should be identical" in { - compare((1, 2), (1, 2)) shouldBe Identical((1, 2)) + compare((1, 2), (1, 2)).isIdentical shouldBe true } "different first element should make them different" in { compare((1, 2), (3, 2)) shouldBe DiffResultObject( "Tuple2", - Map("_1" -> DiffResultValue(1, 3), "_2" -> Identical(2)) + Map("_1" -> DiffResultValue(1, 3), "_2" -> IdenticalValue(2)) ) } "different second element should make them different" in { compare((1, 3), (1, 2)) shouldBe DiffResultObject( "Tuple2", - Map("_1" -> Identical(1), "_2" -> DiffResultValue(3, 2)) + Map("_1" -> IdenticalValue(1), "_2" -> DiffResultValue(3, 2)) ) } } "tuple3" - { "equal tuples should be identical" in { - compare((1, 2, 3), (1, 2, 3)) shouldBe Identical((1, 2, 3)) + compare((1, 2, 3), (1, 2, 3)).isIdentical shouldBe true } "different first element should make them different" in { compare((1, 2, 3), (4, 2, 3)) shouldBe DiffResultObject( "Tuple3", - Map("_1" -> DiffResultValue(1, 4), "_2" -> Identical(2), "_3" -> Identical(3)) + Map("_1" -> DiffResultValue(1, 4), "_2" -> IdenticalValue(2), "_3" -> IdenticalValue(3)) ) } "different second element should make them different" in { compare((1, 2, 3), (1, 4, 3)) shouldBe DiffResultObject( "Tuple3", - Map("_1" -> Identical(1), "_2" -> DiffResultValue(2, 4), "_3" -> Identical(3)) + Map("_1" -> IdenticalValue(1), "_2" -> DiffResultValue(2, 4), "_3" -> IdenticalValue(3)) ) } "different third element should make them different" in { compare((1, 2, 3), (1, 2, 4)) shouldBe DiffResultObject( "Tuple3", - Map("_1" -> Identical(1), "_2" -> Identical(2), "_3" -> DiffResultValue(3, 4)) + Map("_1" -> IdenticalValue(1), "_2" -> IdenticalValue(2), "_3" -> DiffResultValue(3, 4)) ) } } diff --git a/munit/src/main/scala/com/softwaremill/diffx/munit/DiffxAssertions.scala b/munit/src/main/scala/com/softwaremill/diffx/munit/DiffxAssertions.scala index 6dc75c45..6b0f6018 100644 --- a/munit/src/main/scala/com/softwaremill/diffx/munit/DiffxAssertions.scala +++ b/munit/src/main/scala/com/softwaremill/diffx/munit/DiffxAssertions.scala @@ -1,15 +1,14 @@ package com.softwaremill.diffx.munit -import com.softwaremill.diffx.{ConsoleColorConfig, Diff, DiffResultDifferent} +import com.softwaremill.diffx.{ConsoleColorConfig, Diff} import munit.Assertions._ import munit.Location trait DiffxAssertions { def assertEqual[T: Diff](t1: T, t2: T)(implicit c: ConsoleColorConfig, loc: Location): Unit = { val result = Diff.compare(t1, t2) - result match { - case different: DiffResultDifferent => fail(different.show())(loc) - case _ => // do nothing + if (!result.isIdentical) { + fail(result.show())(loc) } } } diff --git a/refined/src/test/scala/com/softwaremill/diffx/refined/RefinedSupportTest.scala b/refined/src/test/scala/com/softwaremill/diffx/refined/RefinedSupportTest.scala index 7137cac1..84313a6c 100644 --- a/refined/src/test/scala/com/softwaremill/diffx/refined/RefinedSupportTest.scala +++ b/refined/src/test/scala/com/softwaremill/diffx/refined/RefinedSupportTest.scala @@ -1,6 +1,6 @@ package com.softwaremill.diffx.refined -import com.softwaremill.diffx.{DiffResultObject, DiffResultString, DiffResultValue, Identical, _} +import com.softwaremill.diffx.{DiffResultObject, DiffResultString, DiffResultValue, IdenticalValue, _} import eu.timepit.refined.types.numeric.PosInt import eu.timepit.refined.auto._ import eu.timepit.refined.types.string.NonEmptyString @@ -14,7 +14,7 @@ class RefinedSupportTest extends AnyFlatSpec with Matchers { val testData2 = TestData(1, "bar") compare(testData1, testData2) shouldBe DiffResultObject( "TestData", - Map("posInt" -> Identical(1), "nonEmptyString" -> DiffResultString(List(DiffResultValue("foo", "bar")))) + Map("posInt" -> IdenticalValue(1), "nonEmptyString" -> DiffResultString(List(DiffResultValue("foo", "bar")))) ) } } diff --git a/scalatest/src/main/scala/com/softwaremill/diffx/scalatest/DiffMatcher.scala b/scalatest/src/main/scala/com/softwaremill/diffx/scalatest/DiffMatcher.scala index f2722e88..8ba7bdff 100644 --- a/scalatest/src/main/scala/com/softwaremill/diffx/scalatest/DiffMatcher.scala +++ b/scalatest/src/main/scala/com/softwaremill/diffx/scalatest/DiffMatcher.scala @@ -1,15 +1,16 @@ package com.softwaremill.diffx.scalatest -import com.softwaremill.diffx.{ConsoleColorConfig, Diff, DiffResultDifferent} +import com.softwaremill.diffx.{ConsoleColorConfig, Diff} import org.scalatest.matchers.{MatchResult, Matcher} trait DiffMatcher { def matchTo[A: Diff](right: A)(implicit c: ConsoleColorConfig): Matcher[A] = { left => - Diff[A].apply(left, right) match { - case c: DiffResultDifferent => - val diff = c.show().split('\n').mkString(Console.RESET, s"${Console.RESET}\n${Console.RESET}", Console.RESET) - MatchResult(matches = false, s"Matching error:\n$diff", "") - case _ => MatchResult(matches = true, "", "") + val result = Diff[A].apply(left, right) + if (!result.isIdentical) { + val diff = result.show().split('\n').mkString(Console.RESET, s"${Console.RESET}\n${Console.RESET}", Console.RESET) + MatchResult(matches = false, s"Matching error:\n$diff", "") + } else { + MatchResult(matches = true, "", "") } } } diff --git a/specs2/src/main/scala/com/softwaremill/diffx/specs2/DiffMatcher.scala b/specs2/src/main/scala/com/softwaremill/diffx/specs2/DiffMatcher.scala index 81a603cc..3d4b3714 100644 --- a/specs2/src/main/scala/com/softwaremill/diffx/specs2/DiffMatcher.scala +++ b/specs2/src/main/scala/com/softwaremill/diffx/specs2/DiffMatcher.scala @@ -1,6 +1,6 @@ package com.softwaremill.diffx.specs2 -import com.softwaremill.diffx.{ConsoleColorConfig, Diff, DiffResultDifferent} +import com.softwaremill.diffx.{ConsoleColorConfig, Diff} import org.specs2.matcher.{Expectable, MatchResult, Matcher} trait DiffMatcher { @@ -14,11 +14,13 @@ trait DiffMatcher { diff.apply(left.value, right).isIdentical }, okMessage = "", - koMessage = diff.apply(left.value, right) match { - case c: DiffResultDifferent => - c.show() - case _ => + koMessage = { + val diffResult = diff.apply(left.value, right) + if (!diffResult.isIdentical) { + diffResult.show() + } else { "" + } }, left ) diff --git a/tagging/src/test/scala/com/softwaremill/diffx/tagging/test/DiffTaggingSupportTest.scala b/tagging/src/test/scala/com/softwaremill/diffx/tagging/test/DiffTaggingSupportTest.scala index 73689af0..0a0b1191 100644 --- a/tagging/src/test/scala/com/softwaremill/diffx/tagging/test/DiffTaggingSupportTest.scala +++ b/tagging/src/test/scala/com/softwaremill/diffx/tagging/test/DiffTaggingSupportTest.scala @@ -1,6 +1,6 @@ package com.softwaremill.diffx.tagging.test -import com.softwaremill.diffx.{Diff, DiffResultObject, DiffResultValue, Identical} +import com.softwaremill.diffx.{Diff, DiffResultObject, DiffResultValue, IdenticalValue} import com.softwaremill.diffx.tagging._ import com.softwaremill.tagging._ import com.softwaremill.tagging.@@ @@ -15,7 +15,7 @@ class DiffTaggingSupportTest extends AnyFlatSpec with Matchers { val p2 = 1.taggedWith[T2] compare(TestData(p1, p2), TestData(p11, p2)) shouldBe DiffResultObject( "TestData", - Map("p1" -> DiffResultValue(p1, p11), "p2" -> Identical(p2)) + Map("p1" -> DiffResultValue(p1, p11), "p2" -> IdenticalValue(p2)) ) } diff --git a/utest/src/main/scala/com/softwaremill/diffx/utest/DiffxAssertions.scala b/utest/src/main/scala/com/softwaremill/diffx/utest/DiffxAssertions.scala index 7387ecfd..9d06dca4 100644 --- a/utest/src/main/scala/com/softwaremill/diffx/utest/DiffxAssertions.scala +++ b/utest/src/main/scala/com/softwaremill/diffx/utest/DiffxAssertions.scala @@ -1,15 +1,14 @@ package com.softwaremill.diffx.utest -import com.softwaremill.diffx.{ConsoleColorConfig, Diff, DiffResultDifferent} +import com.softwaremill.diffx.{ConsoleColorConfig, Diff} import utest.AssertionError trait DiffxAssertions { def assertEqual[T: Diff](t1: T, t2: T)(implicit c: ConsoleColorConfig): Unit = { val result = Diff.compare(t1, t2) - result match { - case different: DiffResultDifferent => throw AssertionError(different.show(), Seq.empty, null) - case _ => // do nothing + if (!result.isIdentical) { + throw AssertionError(result.show(), Seq.empty, null) } } } From 49f32cd8085d0ab2930756723dc1c1946ba9ad94 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Thu, 24 Jun 2021 00:13:11 +0200 Subject: [PATCH 108/112] Add scala-docs for ObjectMatcher --- .../com/softwaremill/diffx/ObjectMatcher.scala | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala b/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala index 45a83bf7..03d241c3 100644 --- a/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala +++ b/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala @@ -1,8 +1,8 @@ package com.softwaremill.diffx -/* - Used to pair elements within unordered containers like sets - */ +/** Defines how the elements within collections are paired + * @tparam T + */ trait ObjectMatcher[T] { def isSameObject(left: T, right: T): Boolean } @@ -10,12 +10,16 @@ trait ObjectMatcher[T] { object ObjectMatcher extends LowPriorityObjectMatcher { def apply[T: ObjectMatcher]: ObjectMatcher[T] = implicitly[ObjectMatcher[T]] + /** Given product of type T and its property U, match that products using U's objectMatcher */ def by[T, U: ObjectMatcher](f: T => U): ObjectMatcher[T] = (left: T, right: T) => ObjectMatcher[U].isSameObject(f(left), f(right)) + /** Given key-value (K,V) pairs match them using V's objectMatcher */ def byValue[K, V: ObjectMatcher]: ObjectMatcher[(K, V)] = ObjectMatcher.by[(K, V), V](_._2) + + /** Given key-value (K,V) pairs, where V is a type of product and U is a property of V, match them using U's objectMatcher */ def byValue[K, V, U: ObjectMatcher](f: V => U): ObjectMatcher[(K, V)] = - ObjectMatcher.by[(K, V), V](_._2)(ObjectMatcher.by[V, U](f)) + ObjectMatcher.byValue[K, V](ObjectMatcher.by[V, U](f)) implicit def optionMatcher[T: ObjectMatcher]: ObjectMatcher[Option[T]] = (left: Option[T], right: Option[T]) => { (left, right) match { @@ -23,10 +27,11 @@ object ObjectMatcher extends LowPriorityObjectMatcher { case _ => false } } + + /** Given key-value (K,V) pairs, match them using K's objectMatcher */ implicit def byKey[K: ObjectMatcher, V]: ObjectMatcher[(K, V)] = ObjectMatcher.by[(K, V), K](_._1) } trait LowPriorityObjectMatcher { implicit def default[T]: ObjectMatcher[T] = (l: T, r: T) => l == r - } From b6e34603401cfe590e9f55acb003896b0e1ace9d Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Thu, 24 Jun 2021 00:15:02 +0200 Subject: [PATCH 109/112] Release 0.5.2 --- generated-docs/out/index.md | 2 +- generated-docs/out/integrations/cats.md | 4 +- generated-docs/out/integrations/refined.md | 6 +-- generated-docs/out/integrations/tagging.md | 6 +-- generated-docs/out/test-frameworks/munit.md | 4 +- .../out/test-frameworks/scalatest.md | 4 +- generated-docs/out/test-frameworks/specs2.md | 4 +- generated-docs/out/test-frameworks/utest.md | 4 +- generated-docs/out/usage/ignoring.md | 8 +++- generated-docs/out/usage/output.md | 2 +- generated-docs/out/usage/replacing.md | 8 +++- generated-docs/out/usage/sequences.md | 38 ++++++++++++++++--- 12 files changed, 64 insertions(+), 26 deletions(-) diff --git a/generated-docs/out/index.md b/generated-docs/out/index.md index cf4abdb6..7749e947 100644 --- a/generated-docs/out/index.md +++ b/generated-docs/out/index.md @@ -49,7 +49,7 @@ compare(left, right) // "bar" -> DiffResultObject( // name = "Bar", // fields = ListMap( -// "s" -> Identical(value = "asdf"), +// "s" -> IdenticalValue(value = "asdf"), // "i" -> DiffResultValue(left = 66, right = 5) // ) // ), diff --git a/generated-docs/out/integrations/cats.md b/generated-docs/out/integrations/cats.md index 0f6b6fb8..c1a1a13f 100644 --- a/generated-docs/out/integrations/cats.md +++ b/generated-docs/out/integrations/cats.md @@ -5,13 +5,13 @@ This module contains integration layer between [org.typelevel.cats](https://gith ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-cats" % "0.5.1" % Test +"com.softwaremill.diffx" %% "diffx-cats" % "0.5.2" % Test ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-cats::0.5.1" +ivy"com.softwaremill.diffx::diffx-cats::0.5.2" ``` ## Usage diff --git a/generated-docs/out/integrations/refined.md b/generated-docs/out/integrations/refined.md index e1eec5b7..a434272b 100644 --- a/generated-docs/out/integrations/refined.md +++ b/generated-docs/out/integrations/refined.md @@ -5,13 +5,13 @@ This module contains integration layer between [eu.timepit.refined](https://gith ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-refined" % "0.5.1" % Test +"com.softwaremill.diffx" %% "diffx-refined" % "0.5.2" % Test ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-refined::0.5.1" +ivy"com.softwaremill.diffx::diffx-refined::0.5.2" ``` ## Usage @@ -40,7 +40,7 @@ compare(t1, t2) // res0: com.softwaremill.diffx.DiffResult = DiffResultObject( // name = "TestData", // fields = ListMap( -// "posInt" -> Identical(value = 1), +// "posInt" -> IdenticalValue(value = 1), // "nonEmptyString" -> DiffResultString( // diffs = List(DiffResultValue(left = "foo", right = "bar")) // ) diff --git a/generated-docs/out/integrations/tagging.md b/generated-docs/out/integrations/tagging.md index fd2bfb76..1c822e61 100644 --- a/generated-docs/out/integrations/tagging.md +++ b/generated-docs/out/integrations/tagging.md @@ -5,13 +5,13 @@ This module contains integration layer between [com.softwaremill.common.tagging] ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-tagging" % "0.5.1" +"com.softwaremill.diffx" %% "diffx-tagging" % "0.5.2" ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-tagging::0.5.1" +ivy"com.softwaremill.diffx::diffx-tagging::0.5.2" ``` ## Usage @@ -39,7 +39,7 @@ compare(t1, t2) // res0: com.softwaremill.diffx.DiffResult = DiffResultObject( // name = "TestData", // fields = ListMap( -// "p1" -> Identical(value = 1), +// "p1" -> IdenticalValue(value = 1), // "p2" -> DiffResultValue(left = 1, right = 3) // ) // ) diff --git a/generated-docs/out/test-frameworks/munit.md b/generated-docs/out/test-frameworks/munit.md index e17a1a13..66c9a0ad 100644 --- a/generated-docs/out/test-frameworks/munit.md +++ b/generated-docs/out/test-frameworks/munit.md @@ -5,13 +5,13 @@ To use with munit, add following dependency: ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-munit" % "0.5.1" % Test +"com.softwaremill.diffx" %% "diffx-munit" % "0.5.2" % Test ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-munit::0.5.1" +ivy"com.softwaremill.diffx::diffx-munit::0.5.2" ``` ## Usage diff --git a/generated-docs/out/test-frameworks/scalatest.md b/generated-docs/out/test-frameworks/scalatest.md index 87d54fee..e344a5ba 100644 --- a/generated-docs/out/test-frameworks/scalatest.md +++ b/generated-docs/out/test-frameworks/scalatest.md @@ -5,13 +5,13 @@ To use with scalatest, add the following dependency: ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-scalatest" % "0.5.1" % Test +"com.softwaremill.diffx" %% "diffx-scalatest" % "0.5.2" % Test ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-scalatest::0.5.1" +ivy"com.softwaremill.diffx::diffx-scalatest::0.5.2" ``` ## Usage diff --git a/generated-docs/out/test-frameworks/specs2.md b/generated-docs/out/test-frameworks/specs2.md index c37e503c..138281d9 100644 --- a/generated-docs/out/test-frameworks/specs2.md +++ b/generated-docs/out/test-frameworks/specs2.md @@ -5,13 +5,13 @@ To use with specs2, add the following dependency: ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-specs2" % "0.5.1" % Test +"com.softwaremill.diffx" %% "diffx-specs2" % "0.5.2" % Test ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-specs2::0.5.1" +ivy"com.softwaremill.diffx::diffx-specs2::0.5.2" ``` ## Usage diff --git a/generated-docs/out/test-frameworks/utest.md b/generated-docs/out/test-frameworks/utest.md index 5f7381f0..3366c7b3 100644 --- a/generated-docs/out/test-frameworks/utest.md +++ b/generated-docs/out/test-frameworks/utest.md @@ -5,13 +5,13 @@ To use with utest, add following dependency: ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-utest" % "0.5.1" % Test +"com.softwaremill.diffx" %% "diffx-utest" % "0.5.2" % Test ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-utest::0.5.1" +ivy"com.softwaremill.diffx::diffx-utest::0.5.2" ``` ## Usage diff --git a/generated-docs/out/usage/ignoring.md b/generated-docs/out/usage/ignoring.md index ab57c774..0f940e06 100644 --- a/generated-docs/out/usage/ignoring.md +++ b/generated-docs/out/usage/ignoring.md @@ -20,5 +20,11 @@ implicit val modifiedDiff: Diff[Person] = Derived[Diff[Person]].ignore(_.age) ``` ```scala compare(Person("bob", 25), Person("bob", 30)) -// res1: DiffResult = Identical(value = Person(name = "bob", age = 25)) +// res1: DiffResult = DiffResultObject( +// name = "Person", +// fields = ListMap( +// "name" -> IdenticalValue(value = "bob"), +// "age" -> IdenticalValue(value = "") +// ) +// ) ``` \ No newline at end of file diff --git a/generated-docs/out/usage/output.md b/generated-docs/out/usage/output.md index 2d71164c..5dabb465 100644 --- a/generated-docs/out/usage/output.md +++ b/generated-docs/out/usage/output.md @@ -56,7 +56,7 @@ val result = compare(Person("Bob", 23), Person("Alice", 23)) // "name" -> DiffResultString( // diffs = List(DiffResultValue(left = "Bob", right = "Alice")) // ), -// "age" -> Identical(value = 23) +// "age" -> IdenticalValue(value = 23) // ) // ) result.show(renderIdentical = false) diff --git a/generated-docs/out/usage/replacing.md b/generated-docs/out/usage/replacing.md index 79c5d873..d666ec41 100644 --- a/generated-docs/out/usage/replacing.md +++ b/generated-docs/out/usage/replacing.md @@ -24,7 +24,13 @@ implicit val diffPerson: Derived[Diff[Person]] = Diff.derived[Person].modify(_.w ```scala compare(Person(23, 60), Person(23, 62)) -// res0: DiffResult = Identical(value = Person(age = 23, weight = 60)) +// res0: DiffResult = DiffResultObject( +// name = "Person", +// fields = ListMap( +// "age" -> IdenticalValue(value = 23), +// "weight" -> IdenticalValue(value = 60) +// ) +// ) ``` In fact, replacement is so powerful that ignoring is implemented as a replacement diff --git a/generated-docs/out/usage/sequences.md b/generated-docs/out/usage/sequences.md index 6b4cedb6..6d9615ff 100644 --- a/generated-docs/out/usage/sequences.md +++ b/generated-docs/out/usage/sequences.md @@ -30,7 +30,13 @@ compare(Set(bob), Set(bob, Person("2","Alice"))) // res1: DiffResult = DiffResultSet( // diffs = List( // DiffResultMissing(value = Person(id = "2", name = "Alice")), -// Identical(value = Person(id = "1", name = "Bob")) +// DiffResultObject( +// name = "Person", +// fields = ListMap( +// "id" -> IdenticalValue(value = "1"), +// "name" -> IdenticalValue(value = "Bob") +// ) +// ) // ) // ) ``` @@ -50,9 +56,13 @@ val bob = Person("1","Bob") ```scala compare(Map("1" -> bob), Map("2" -> bob)) // res3: DiffResult = DiffResultMap( -// fields = Map( -// DiffResultString(diffs = List(DiffResultValue(left = "1", right = "2"))) -> Identical( -// value = Person(id = "1", name = "Bob") +// entries = Map( +// DiffResultString(diffs = List(DiffResultValue(left = "1", right = "2"))) -> DiffResultObject( +// name = "Person", +// fields = ListMap( +// "id" -> IdenticalValue(value = "1"), +// "name" -> IdenticalValue(value = "Bob") +// ) // ) // ) // ) @@ -73,8 +83,24 @@ val alice = Person("2","Alice") ``` ```scala compare(List(bob, alice), List(alice, bob)) -// res5: DiffResult = Identical( -// value = List(Person(id = "1", name = "Bob"), Person(id = "2", name = "Alice")) +// res5: DiffResult = DiffResultObject( +// name = "List", +// fields = ListMap( +// "0" -> DiffResultObject( +// name = "Person", +// fields = ListMap( +// "id" -> IdenticalValue(value = "1"), +// "name" -> IdenticalValue(value = "Bob") +// ) +// ), +// "1" -> DiffResultObject( +// name = "Person", +// fields = ListMap( +// "id" -> IdenticalValue(value = "2"), +// "name" -> IdenticalValue(value = "Alice") +// ) +// ) +// ) // ) ``` From c98d68ea91351e9ffbf9993d529b067d9c9577b9 Mon Sep 17 00:00:00 2001 From: Kasper Kondzielski Date: Mon, 5 Jul 2021 22:12:12 +0200 Subject: [PATCH 110/112] Update sbt to 1.5.4 (#271) --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index d404814a..77df8ac3 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.6 \ No newline at end of file +sbt.version=1.5.4 \ No newline at end of file From 43a6132cb964b96f216ce27d2a17b4fd9b6973c7 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Mon, 5 Jul 2021 22:13:15 +0200 Subject: [PATCH 111/112] ObjectMatcher should not relay on generic tuple type but rather has its own internal MapEntry class --- .../scala/com/softwaremill/diffx/Diff.scala | 9 ++++---- .../softwaremill/diffx/ObjectMatcher.scala | 9 +++++--- .../diffx/instances/DiffForIterable.scala | 15 ++++++------- .../diffx/instances/DiffForMap.scala | 21 +++++++++++++------ .../softwaremill/diffx/test/DiffTest.scala | 13 ++++++++++-- docs-sources/usage/sequences.md | 8 ++++--- 6 files changed, 50 insertions(+), 25 deletions(-) diff --git a/core/src/main/scala/com/softwaremill/diffx/Diff.scala b/core/src/main/scala/com/softwaremill/diffx/Diff.scala index 789d1c4b..71636a74 100644 --- a/core/src/main/scala/com/softwaremill/diffx/Diff.scala +++ b/core/src/main/scala/com/softwaremill/diffx/Diff.scala @@ -1,4 +1,5 @@ package com.softwaremill.diffx +import com.softwaremill.diffx.ObjectMatcher.{IterableEntry, MapEntry} import com.softwaremill.diffx.generic.{DiffMagnoliaDerivation, MagnoliaDerivedMacro} import com.softwaremill.diffx.instances._ @@ -52,7 +53,7 @@ object Diff extends MiddlePriorityDiff with TupleInstances { implicit def diffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]](implicit dv: Diff[V], dk: Diff[K], - matcher: ObjectMatcher[(K, V)] + matcher: ObjectMatcher[MapEntry[K, V]] ): Diff[C[K, V]] = new DiffForMap[K, V, C](matcher, dk, dv) implicit def diffForOptional[T](implicit ddt: Diff[T]): Diff[Option[T]] = new DiffForOption[T](ddt) implicit def diffForSet[T, C[W] <: scala.collection.Set[W]](implicit @@ -67,7 +68,7 @@ trait MiddlePriorityDiff extends DiffMagnoliaDerivation with LowPriorityDiff { implicit def diffForIterable[T, C[W] <: Iterable[W]](implicit dt: Diff[T], - matcher: ObjectMatcher[(Int, T)] + matcher: ObjectMatcher[IterableEntry[T]] ): Diff[C[T]] = new DiffForIterable[T, C](dt, matcher) } @@ -113,12 +114,12 @@ case class DerivedDiffLens[T, U](outer: Diff[T], path: List[String]) { } def ignore(): Derived[Diff[T]] = Derived(outer.modifyUnsafe(path: _*)(Diff.ignored)) - def withMapMatcher[K, V](m: ObjectMatcher[(K, V)])(implicit + def withMapMatcher[K, V](m: ObjectMatcher[MapEntry[K, V]])(implicit ev1: U <:< scala.collection.Map[K, V] ): Derived[Diff[T]] = Derived(outer.modifyMatcherUnsafe(path: _*)(m)) def withSetMatcher[V](m: ObjectMatcher[V])(implicit ev2: U <:< scala.collection.Set[V]): Derived[Diff[T]] = Derived(outer.modifyMatcherUnsafe(path: _*)(m)) - def withListMatcher[V](m: ObjectMatcher[(Int, V)])(implicit ev3: U <:< Iterable[V]): Derived[Diff[T]] = + def withListMatcher[V](m: ObjectMatcher[IterableEntry[V]])(implicit ev3: U <:< Iterable[V]): Derived[Diff[T]] = Derived(outer.modifyMatcherUnsafe(path: _*)(m)) } diff --git a/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala b/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala index 03d241c3..483a8c6c 100644 --- a/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala +++ b/core/src/main/scala/com/softwaremill/diffx/ObjectMatcher.scala @@ -15,10 +15,10 @@ object ObjectMatcher extends LowPriorityObjectMatcher { ObjectMatcher[U].isSameObject(f(left), f(right)) /** Given key-value (K,V) pairs match them using V's objectMatcher */ - def byValue[K, V: ObjectMatcher]: ObjectMatcher[(K, V)] = ObjectMatcher.by[(K, V), V](_._2) + def byValue[K, V: ObjectMatcher]: ObjectMatcher[MapEntry[K, V]] = ObjectMatcher.by[MapEntry[K, V], V](_.value) /** Given key-value (K,V) pairs, where V is a type of product and U is a property of V, match them using U's objectMatcher */ - def byValue[K, V, U: ObjectMatcher](f: V => U): ObjectMatcher[(K, V)] = + def byValue[K, V, U: ObjectMatcher](f: V => U): ObjectMatcher[MapEntry[K, V]] = ObjectMatcher.byValue[K, V](ObjectMatcher.by[V, U](f)) implicit def optionMatcher[T: ObjectMatcher]: ObjectMatcher[Option[T]] = (left: Option[T], right: Option[T]) => { @@ -29,7 +29,10 @@ object ObjectMatcher extends LowPriorityObjectMatcher { } /** Given key-value (K,V) pairs, match them using K's objectMatcher */ - implicit def byKey[K: ObjectMatcher, V]: ObjectMatcher[(K, V)] = ObjectMatcher.by[(K, V), K](_._1) + implicit def byKey[K: ObjectMatcher, V]: ObjectMatcher[MapEntry[K, V]] = ObjectMatcher.by[MapEntry[K, V], K](_.key) + + type IterableEntry[T] = MapEntry[Int, T] + case class MapEntry[K, V](key: K, value: V) } trait LowPriorityObjectMatcher { diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala index 8e772395..6ff225df 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForIterable.scala @@ -1,13 +1,14 @@ package com.softwaremill.diffx.instances import com.softwaremill.diffx.Matching.{MatchingResults, matching} +import com.softwaremill.diffx.ObjectMatcher.{IterableEntry, MapEntry} import com.softwaremill.diffx._ import scala.collection.immutable.{ListMap, ListSet} private[diffx] class DiffForIterable[T, C[W] <: Iterable[W]]( dt: Diff[T], - matcher: ObjectMatcher[(Int, T)] + matcher: ObjectMatcher[IterableEntry[T]] ) extends Diff[C[T]] { override def apply(left: C[T], right: C[T], context: DiffContext): DiffResult = nullGuard(left, right) { (left, right) => @@ -15,21 +16,21 @@ private[diffx] class DiffForIterable[T, C[W] <: Iterable[W]]( val leftAsMap = left.toList.lift val rightAsMap = right.toList.lift - val leftv2 = ListSet(keys.map(i => i -> leftAsMap(i)): _*).collect { case (k, Some(v)) => k -> v } - val rightv2 = ListSet(keys.map(i => i -> rightAsMap(i)): _*).collect { case (k, Some(v)) => k -> v } + val leftv2 = ListSet(keys.map(i => i -> leftAsMap(i)): _*).collect { case (k, Some(v)) => MapEntry(k, v) } + val rightv2 = ListSet(keys.map(i => i -> rightAsMap(i)): _*).collect { case (k, Some(v)) => MapEntry(k, v) } - val adjustedMatcher = context.getMatcherOverride[(Int, T)].getOrElse(matcher) + val adjustedMatcher = context.getMatcherOverride[IterableEntry[T]].getOrElse(matcher) val MatchingResults(unMatchedLeftInstances, unMatchedRightInstances, matchedInstances) = matching(leftv2, rightv2, adjustedMatcher) val leftDiffs = unMatchedLeftInstances .diff(unMatchedRightInstances) - .collectFirst { case (k, v) => k -> DiffResultAdditional(v) } + .collectFirst { case MapEntry(k, v) => k -> DiffResultAdditional(v) } .toList val rightDiffs = unMatchedRightInstances .diff(unMatchedLeftInstances) - .collectFirst { case (k, v) => k -> DiffResultMissing(v) } + .collectFirst { case MapEntry(k, v) => k -> DiffResultMissing(v) } .toList - val matchedDiffs = matchedInstances.map { case (l, r) => l._1 -> dt(l._2, r._2, context) }.toList + val matchedDiffs = matchedInstances.map { case (l, r) => l.key -> dt(l.value, r.value, context) }.toList val diffs = ListMap((matchedDiffs ++ leftDiffs ++ rightDiffs).map { case (k, v) => k.toString -> v }: _*) DiffResultObject("List", diffs) diff --git a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala index d7983864..5af3e599 100644 --- a/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala +++ b/core/src/main/scala/com/softwaremill/diffx/instances/DiffForMap.scala @@ -1,10 +1,11 @@ package com.softwaremill.diffx.instances import com.softwaremill.diffx.Matching._ +import com.softwaremill.diffx.ObjectMatcher.MapEntry import com.softwaremill.diffx._ private[diffx] class DiffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]]( - matcher: ObjectMatcher[(K, V)], + matcher: ObjectMatcher[MapEntry[K, V]], diffKey: Diff[K], diffValue: Diff[V] ) extends Diff[C[K, V]] { @@ -13,19 +14,27 @@ private[diffx] class DiffForMap[K, V, C[KK, VV] <: scala.collection.Map[KK, VV]] right: C[K, V], context: DiffContext ): DiffResult = nullGuard(left, right) { (left, right) => - val adjustedMatcher = context.getMatcherOverride[(K, V)].getOrElse(matcher) + val adjustedMatcher = context.getMatcherOverride[MapEntry[K, V]].getOrElse(matcher) val MatchingResults(unMatchedLeftKeys, unMatchedRightKeys, matchedKeys) = - matching(left.toSet, right.toSet, adjustedMatcher, diffKey.contramap[(K, V)](_._1), context) + matching( + left.map { case (k, v) => MapEntry.apply(k, v) }.toSet, + right.map { case (k, v) => MapEntry.apply(k, v) }.toSet, + adjustedMatcher, + diffKey.contramap[MapEntry[K, V]](_.key), + context + ) val leftDiffs = unMatchedLeftKeys .diff(unMatchedRightKeys) - .collectFirst { case (k, v) => DiffResultAdditional(k) -> DiffResultAdditional(v) } + .collectFirst { case MapEntry(k, v) => DiffResultAdditional(k) -> DiffResultAdditional(v) } .toList val rightDiffs = unMatchedRightKeys .diff(unMatchedLeftKeys) - .collectFirst { case (k, v) => DiffResultMissing(k) -> DiffResultMissing(v) } + .collectFirst { case MapEntry(k, v) => DiffResultMissing(k) -> DiffResultMissing(v) } .toList - val matchedDiffs = matchedKeys.map { case (l, r) => diffKey(l._1, r._1) -> diffValue(l._2, r._2, context) }.toList + val matchedDiffs = matchedKeys.map { case (l, r) => + diffKey(l.key, r.key) -> diffValue(l.value, r.value, context) + }.toList val diffs = leftDiffs ++ rightDiffs ++ matchedDiffs DiffResultMap(diffs.toMap) diff --git a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala index 9d0fca4f..b8864d64 100644 --- a/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala +++ b/core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala @@ -1,5 +1,6 @@ package com.softwaremill.diffx.test +import com.softwaremill.diffx.ObjectMatcher.{IterableEntry, MapEntry} import com.softwaremill.diffx.generic.auto._ import com.softwaremill.diffx._ import org.scalatest.freespec.AnyFreeSpec @@ -373,7 +374,15 @@ class DiffTest extends AnyFreeSpec with Matchers { "compare lists using object matcher comparator" in { val o1 = Organization(List(p1, p2)) val o2 = Organization(List(p2, p1)) - implicit val om: ObjectMatcher[(Int, Person)] = ObjectMatcher.byValue(_.name) + implicit val om: ObjectMatcher[IterableEntry[Person]] = ObjectMatcher.byValue(_.name) + compare(o1, o2).isIdentical shouldBe true + } + + "compare lists using object matcher comparator when matching by pair" in { + val p2WithSameNameAsP1 = p2.copy(name = p1.name) + val o1 = Organization(List(p1, p2WithSameNameAsP1)) + val o2 = Organization(List(p2WithSameNameAsP1, p1)) + implicit val om: ObjectMatcher[IterableEntry[Person]] = ObjectMatcher.byValue(p => (p.name, p.age)) compare(o1, o2).isIdentical shouldBe true } @@ -652,7 +661,7 @@ class DiffTest extends AnyFreeSpec with Matchers { } "match map entries by values" in { - implicit val om: ObjectMatcher[(KeyModel, String)] = ObjectMatcher.byValue + implicit val om: ObjectMatcher[MapEntry[KeyModel, String]] = ObjectMatcher.byValue val uuid1 = UUID.randomUUID() val uuid2 = UUID.randomUUID() val a1 = MyLookup(Map(KeyModel(uuid1, "k1") -> "val1")) diff --git a/docs-sources/usage/sequences.md b/docs-sources/usage/sequences.md index a197870f..f3e5b0c6 100644 --- a/docs-sources/usage/sequences.md +++ b/docs-sources/usage/sequences.md @@ -34,10 +34,11 @@ In below example we tell `diffx` to compare these maps by paring entries by valu ```scala mdoc:reset:silent import com.softwaremill.diffx._ import com.softwaremill.diffx.generic.auto._ +import com.softwaremill.diffx.ObjectMatcher.MapEntry case class Person(id: String, name: String) val personMatcher: ObjectMatcher[Person] = ObjectMatcher.by(_.id) -implicit val om: ObjectMatcher[(String, Person)] = ObjectMatcher.byValue(personMatcher) +implicit val om: ObjectMatcher[MapEntry[String, Person]] = ObjectMatcher.byValue(personMatcher) val bob = Person("1","Bob") ``` @@ -47,14 +48,15 @@ compare(Map("1" -> bob), Map("2" -> bob)) Last but not least you can use `objectMatcher` to customize paring when comparing indexed collections. Such collections are treated similarly to maps (they use key-value object matcher), -but the key type is bound to `Int`. +but the key type is bound to `Int` (`IterableEntry` is an alias for `MapEntry[Int,V]`). ```scala mdoc:reset:silent import com.softwaremill.diffx._ import com.softwaremill.diffx.generic.auto._ +import com.softwaremill.diffx.ObjectMatcher.IterableEntry case class Person(id: String, name: String) -implicit val personMatcher: ObjectMatcher[(Int, Person)] = ObjectMatcher.byValue(_.id) +implicit val personMatcher: ObjectMatcher[IterableEntry[Person]] = ObjectMatcher.byValue(_.id) val bob = Person("1","Bob") val alice = Person("2","Alice") ``` From 8fe6a0ae8e24b1965588b1d4f01cd0b14f864f12 Mon Sep 17 00:00:00 2001 From: ghostbuster91 Date: Mon, 5 Jul 2021 22:26:58 +0200 Subject: [PATCH 112/112] Release 0.5.3 --- generated-docs/out/integrations/cats.md | 4 ++-- generated-docs/out/integrations/refined.md | 4 ++-- generated-docs/out/integrations/tagging.md | 4 ++-- generated-docs/out/test-frameworks/munit.md | 4 ++-- generated-docs/out/test-frameworks/scalatest.md | 4 ++-- generated-docs/out/test-frameworks/specs2.md | 4 ++-- generated-docs/out/test-frameworks/utest.md | 4 ++-- generated-docs/out/usage/sequences.md | 8 +++++--- 8 files changed, 19 insertions(+), 17 deletions(-) diff --git a/generated-docs/out/integrations/cats.md b/generated-docs/out/integrations/cats.md index c1a1a13f..3ceadf7e 100644 --- a/generated-docs/out/integrations/cats.md +++ b/generated-docs/out/integrations/cats.md @@ -5,13 +5,13 @@ This module contains integration layer between [org.typelevel.cats](https://gith ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-cats" % "0.5.2" % Test +"com.softwaremill.diffx" %% "diffx-cats" % "0.5.3" % Test ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-cats::0.5.2" +ivy"com.softwaremill.diffx::diffx-cats::0.5.3" ``` ## Usage diff --git a/generated-docs/out/integrations/refined.md b/generated-docs/out/integrations/refined.md index a434272b..392d4473 100644 --- a/generated-docs/out/integrations/refined.md +++ b/generated-docs/out/integrations/refined.md @@ -5,13 +5,13 @@ This module contains integration layer between [eu.timepit.refined](https://gith ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-refined" % "0.5.2" % Test +"com.softwaremill.diffx" %% "diffx-refined" % "0.5.3" % Test ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-refined::0.5.2" +ivy"com.softwaremill.diffx::diffx-refined::0.5.3" ``` ## Usage diff --git a/generated-docs/out/integrations/tagging.md b/generated-docs/out/integrations/tagging.md index 1c822e61..4cdb69c3 100644 --- a/generated-docs/out/integrations/tagging.md +++ b/generated-docs/out/integrations/tagging.md @@ -5,13 +5,13 @@ This module contains integration layer between [com.softwaremill.common.tagging] ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-tagging" % "0.5.2" +"com.softwaremill.diffx" %% "diffx-tagging" % "0.5.3" ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-tagging::0.5.2" +ivy"com.softwaremill.diffx::diffx-tagging::0.5.3" ``` ## Usage diff --git a/generated-docs/out/test-frameworks/munit.md b/generated-docs/out/test-frameworks/munit.md index 66c9a0ad..7d006250 100644 --- a/generated-docs/out/test-frameworks/munit.md +++ b/generated-docs/out/test-frameworks/munit.md @@ -5,13 +5,13 @@ To use with munit, add following dependency: ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-munit" % "0.5.2" % Test +"com.softwaremill.diffx" %% "diffx-munit" % "0.5.3" % Test ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-munit::0.5.2" +ivy"com.softwaremill.diffx::diffx-munit::0.5.3" ``` ## Usage diff --git a/generated-docs/out/test-frameworks/scalatest.md b/generated-docs/out/test-frameworks/scalatest.md index e344a5ba..707d6d39 100644 --- a/generated-docs/out/test-frameworks/scalatest.md +++ b/generated-docs/out/test-frameworks/scalatest.md @@ -5,13 +5,13 @@ To use with scalatest, add the following dependency: ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-scalatest" % "0.5.2" % Test +"com.softwaremill.diffx" %% "diffx-scalatest" % "0.5.3" % Test ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-scalatest::0.5.2" +ivy"com.softwaremill.diffx::diffx-scalatest::0.5.3" ``` ## Usage diff --git a/generated-docs/out/test-frameworks/specs2.md b/generated-docs/out/test-frameworks/specs2.md index 138281d9..8b15ee8d 100644 --- a/generated-docs/out/test-frameworks/specs2.md +++ b/generated-docs/out/test-frameworks/specs2.md @@ -5,13 +5,13 @@ To use with specs2, add the following dependency: ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-specs2" % "0.5.2" % Test +"com.softwaremill.diffx" %% "diffx-specs2" % "0.5.3" % Test ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-specs2::0.5.2" +ivy"com.softwaremill.diffx::diffx-specs2::0.5.3" ``` ## Usage diff --git a/generated-docs/out/test-frameworks/utest.md b/generated-docs/out/test-frameworks/utest.md index 3366c7b3..cc076585 100644 --- a/generated-docs/out/test-frameworks/utest.md +++ b/generated-docs/out/test-frameworks/utest.md @@ -5,13 +5,13 @@ To use with utest, add following dependency: ## sbt ```scala -"com.softwaremill.diffx" %% "diffx-utest" % "0.5.2" % Test +"com.softwaremill.diffx" %% "diffx-utest" % "0.5.3" % Test ``` ## mill ```scala -ivy"com.softwaremill.diffx::diffx-utest::0.5.2" +ivy"com.softwaremill.diffx::diffx-utest::0.5.3" ``` ## Usage diff --git a/generated-docs/out/usage/sequences.md b/generated-docs/out/usage/sequences.md index 6d9615ff..7cf33e9e 100644 --- a/generated-docs/out/usage/sequences.md +++ b/generated-docs/out/usage/sequences.md @@ -46,10 +46,11 @@ In below example we tell `diffx` to compare these maps by paring entries by valu ```scala import com.softwaremill.diffx._ import com.softwaremill.diffx.generic.auto._ +import com.softwaremill.diffx.ObjectMatcher.MapEntry case class Person(id: String, name: String) val personMatcher: ObjectMatcher[Person] = ObjectMatcher.by(_.id) -implicit val om: ObjectMatcher[(String, Person)] = ObjectMatcher.byValue(personMatcher) +implicit val om: ObjectMatcher[MapEntry[String, Person]] = ObjectMatcher.byValue(personMatcher) val bob = Person("1","Bob") ``` @@ -70,14 +71,15 @@ compare(Map("1" -> bob), Map("2" -> bob)) Last but not least you can use `objectMatcher` to customize paring when comparing indexed collections. Such collections are treated similarly to maps (they use key-value object matcher), -but the key type is bound to `Int`. +but the key type is bound to `Int` (`IterableEntry` is an alias for `MapEntry[Int,V]`). ```scala import com.softwaremill.diffx._ import com.softwaremill.diffx.generic.auto._ +import com.softwaremill.diffx.ObjectMatcher.IterableEntry case class Person(id: String, name: String) -implicit val personMatcher: ObjectMatcher[(Int, Person)] = ObjectMatcher.byValue(_.id) +implicit val personMatcher: ObjectMatcher[IterableEntry[Person]] = ObjectMatcher.byValue(_.id) val bob = Person("1","Bob") val alice = Person("2","Alice") ```