Skip to content

Commit

Permalink
Migrate documentation to rtd (#263)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ghostbuster91 authored Jun 22, 2021
1 parent f5f5087 commit 07df052
Show file tree
Hide file tree
Showing 50 changed files with 1,789 additions and 542 deletions.
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

0 comments on commit 07df052

Please sign in to comment.