Skip to content
This repository was archived by the owner on Jan 24, 2025. It is now read-only.

Migrate documentation to rtd #263

Merged
merged 34 commits into from
Jun 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e86bbe0
Setup rtd
ghostbuster91 Jun 18, 2021
86e79ce
Setup rtd
ghostbuster91 Jun 18, 2021
2ea95ba
Setup rtd
ghostbuster91 Jun 18, 2021
31f6b47
Setup rtd
ghostbuster91 Jun 18, 2021
a6b103d
Setup rtd
ghostbuster91 Jun 18, 2021
7fef852
Restore default index.md
ghostbuster91 Jun 18, 2021
7a14ecf
configure md
ghostbuster91 Jun 18, 2021
ff247f3
Use md parser
ghostbuster91 Jun 18, 2021
6fb3127
rtd
ghostbuster91 Jun 18, 2021
bf6a49c
rtd
ghostbuster91 Jun 18, 2021
5667510
rtd
ghostbuster91 Jun 18, 2021
e1031b5
Add doc-tree to index
ghostbuster91 Jun 18, 2021
3ec13ae
Working toctree
ghostbuster91 Jun 21, 2021
af088eb
Add integrations
ghostbuster91 Jun 21, 2021
ce7763c
Add missing files
ghostbuster91 Jun 21, 2021
9b39920
Add summary
ghostbuster91 Jun 21, 2021
e38bfc5
Add docs on usage(ignoring and output)
ghostbuster91 Jun 22, 2021
2f1b0b2
Note about themes and env variable
ghostbuster91 Jun 22, 2021
bf3db3c
Fix wording
ghostbuster91 Jun 22, 2021
078674f
Derivation
ghostbuster91 Jun 22, 2021
cc2d175
Change DiffDerivation to AutoDerivation
ghostbuster91 Jun 22, 2021
d71a066
Extending
ghostbuster91 Jun 22, 2021
f62fedd
Fix approximate
ghostbuster91 Jun 22, 2021
b9ca537
Replacing
ghostbuster91 Jun 22, 2021
ca3a8eb
Fix mdoc compilation
ghostbuster91 Jun 22, 2021
8379932
Remove content from the readme and redirect to microsite
ghostbuster91 Jun 22, 2021
07e6251
Fix indent
ghostbuster91 Jun 22, 2021
c631033
replacement/ignoring
ghostbuster91 Jun 22, 2021
dc55922
sequences
ghostbuster91 Jun 22, 2021
79e2c1e
Update readme
ghostbuster91 Jun 22, 2021
5e02ab9
Fix ignoring example
ghostbuster91 Jun 22, 2021
3ff5d27
Configure rtd edit on gh button
ghostbuster91 Jun 22, 2021
4e3338f
Fix badge url
ghostbuster91 Jun 22, 2021
28af257
Update version
ghostbuster91 Jun 22, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -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
287 changes: 8 additions & 279 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
9 changes: 4 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ lazy val commonSettings: Seq[Def.Setting[_]] = commonSmlBuildSettings ++ ossPubl
scmInfo := Some(ScmInfo(url("https://github.com/softwaremill/diffx"), "[email protected]: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
)
Expand Down Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/com/softwaremill/diffx/Diff.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 9 additions & 1 deletion core/src/test/scala/com/softwaremill/diffx/test/DiffTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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" - {
Expand Down Expand Up @@ -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)))
}

Expand Down
2 changes: 2 additions & 0 deletions docs-sources/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
_build
_build_html
Loading