Skip to content

Commit 5b2f585

Browse files
authored
Extend matchers and add Scaladocs (#1)
1 parent 97b5771 commit 5b2f585

File tree

15 files changed

+461
-73
lines changed

15 files changed

+461
-73
lines changed

.scalafmt.conf

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,9 @@ style = default
22
maxColumn = 120
33

44
align.openParenCallSite = false
5-
align.openParenDefnSite = false
5+
align.openParenDefnSite = false
6+
7+
# don't align arrows in match statements
8+
align.tokens = []
9+
10+
docstrings = JavaDoc

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ sudo: false
44
scala:
55
- 2.11.11
66
- 2.12.7
7+
- 2.13.0-M5
78
jdk:
89
- oraclejdk8
910

build.sbt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import PgpKeys.{publishLocalSigned, publishSigned}
22
import com.typesafe.sbt.SbtGit.GitKeys._
33

44
organization := "software.purpledragon.xml"
5-
version := "0.0.3"
5+
version := "0.0.4-SNAPSHOT"
66

77
scalaVersion := "2.12.7"
88
crossScalaVersions := Seq(scalaVersion.value, "2.11.12", "2.13.0-M5")
@@ -58,6 +58,9 @@ lazy val root = project
5858
git.remoteRepo := "git@github.com:stringbean/scala-xml-compare.git",
5959
paradoxProperties in Paradox ++= Map(
6060
"scaladoc.software.purpledragon.xml.base_url" -> ".../api"
61+
),
62+
scalacOptions in Compile in doc ++= Seq(
63+
"-doc-root-content", baseDirectory.value + "/root-scaladoc.txt"
6164
)
6265
)
63-
.enablePlugins(ScalaUnidocPlugin, GhpagesPlugin, ParadoxSitePlugin)
66+
.enablePlugins(ScalaUnidocPlugin, GhpagesPlugin, ParadoxSitePlugin)

project/SettingsPlugin.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import com.lucidchart.sbt.scalafmt.ScalafmtCorePlugin.autoImport._
2-
import de.heikoseeberger.sbtheader.HeaderPlugin.autoImport._
32
import sbt.Keys._
43
import sbt._
54
import sbt.plugins.JvmPlugin
@@ -33,12 +32,12 @@ object SettingsPlugin extends AutoPlugin {
3332
libraryDependencies ++= (libraryDependencies in LocalRootProject).value,
3433
scalafmtVersion := "1.5.1",
3534
autoAPIMappings := true,
36-
headerLicense := Some(HeaderLicense.ALv2("2017", "Michael Stringer")),
35+
startYear := Some(2017),
3736
licenses += ("Apache-2.0", url("https://opensource.org/licenses/Apache-2.0")),
3837
developers := List(
3938
Developer("stringbean", "Michael Stringer", "@the_stringbean", url("https://github.com/stringbean"))
4039
),
41-
organizationName := "Purple Dragon Software",
40+
organizationName := "Michael Stringer",
4241
organizationHomepage := Some(url("https://purpledragon.software")),
4342
homepage := Some(url("https://stringbean.github.io/scala-xml-compare")),
4443
scmInfo := Some(ScmInfo(url("https://github.com/stringbean/scala-xml-compare"), "https://github.com/stringbean/scala-xml-compare.git")),

project/plugins.sbt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// code style
22
addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0")
3-
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1")
3+
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.0-M5")
44
addSbtPlugin("com.lucidchart" % "sbt-scalafmt" % "1.15")
55
addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.0.0")
66

@@ -12,4 +12,4 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2-1")
1212
addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.2")
1313
addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.2")
1414
addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.3.2")
15-
addSbtPlugin("com.lightbend.paradox" % "sbt-paradox" % "0.3.3")
15+
addSbtPlugin("com.lightbend.paradox" % "sbt-paradox" % "0.3.3")

root-scaladoc.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
This is the documentation for the [[https://stringbean.github.io/scala-xml-compare scala-xml-compare]] project.
2+
3+
The main [[software.purpledragon.xml.compare compare]] package contains utilities for comparing XML documents and
4+
generating diffs.
5+
6+
The following test frameworks are supported:
7+
- Scalatest in [[software.purpledragon.xml.scalatest `xml-scalatest`]]
8+
- specs2 in [[software.purpledragon.xml.specs2 `xml-specs2`]]
Lines changed: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,54 @@
1+
/*
2+
* Copyright 2017 Michael Stringer
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package software.purpledragon.xml.compare
218

319
import software.purpledragon.xml.compare.options.DiffOption._
420
import software.purpledragon.xml.compare.options.DiffOptions
521

622
import scala.xml.{Node, Text}
723

24+
/**
25+
* Utility for comparing XML documents.
26+
*/
827
object XmlCompare {
9-
private type Check = (Node, Node, DiffOptions) => XmlDiff
28+
private type Check = (Node, Node, DiffOptions, Seq[String]) => XmlDiff
1029

11-
val DefaultOptions: DiffOptions = Set(IgnorePrefix)
30+
/**
31+
* Default [[software.purpledragon.xml.compare.options.DiffOption.DiffOption DiffOption]]s to use during XML comparison.
32+
*
33+
* Currently these are:
34+
* - [[software.purpledragon.xml.compare.options.DiffOption.IgnoreNamespacePrefix IgnoreNamespacePrefix]]
35+
*/
36+
val DefaultOptions: DiffOptions = Set(IgnoreNamespacePrefix)
1237

38+
/**
39+
* Compares two XML documents. This will perform a recursive scan of all the nodes in each document, checking each
40+
* for node name, namespace and text.
41+
*
42+
* @param left the first XML document to compare.
43+
* @param right the second XML document to compare.
44+
* @param options configuration options to control the way the comparison is performed.
45+
* @return results of the XML comparison.
46+
*/
1347
def compare(left: Node, right: Node, options: DiffOptions = DefaultOptions): XmlDiff = {
48+
compareNodes(left, right, options, Nil)
49+
}
50+
51+
private def compareNodes(left: Node, right: Node, options: DiffOptions, path: Seq[String]): XmlDiff = {
1452
val checks: Seq[Check] = Seq(
1553
compareNamespace,
1654
compareText,
@@ -22,51 +60,55 @@ object XmlCompare {
2260
// already failed
2361
status
2462
} else {
25-
check(left, right, options)
63+
check(left, right, options, path)
2664
}
2765
}
2866
}
2967

30-
private def compareNamespace(left: Node, right: Node, options: DiffOptions): XmlDiff = {
68+
private def compareNamespace(left: Node, right: Node, options: DiffOptions, path: Seq[String]): XmlDiff = {
3169
if (left.label != right.label) {
32-
XmlDiffers("different label", left.label, right.label)
33-
} else if (left.namespace != right.namespace) {
34-
XmlDiffers("different namespace", left.namespace, right.namespace)
35-
} else if (left.prefix != right.prefix && !options.contains(IgnorePrefix)) {
36-
XmlDiffers("different prefix", left.prefix, right.prefix)
70+
XmlDiffers("different label", left.label, right.label, extendPath(path, left))
71+
} else if (left.namespace != right.namespace && !options.contains(IgnoreNamespace)) {
72+
XmlDiffers("different namespace", left.namespace, right.namespace, extendPath(path, left))
73+
} else if (left.prefix != right.prefix && !options.contains(IgnoreNamespacePrefix) &&
74+
!options.contains(IgnoreNamespace)) {
75+
XmlDiffers("different namespace prefix", left.prefix, right.prefix, extendPath(path, left))
3776
} else {
3877
XmlEqual
3978
}
4079
}
4180

42-
private def compareText(left: Node, right: Node, options: DiffOptions): XmlDiff = {
43-
val leftText = left.child.collect({case t: Text => t}).map(_.text.trim).mkString
44-
val rightText = right.child.collect({case t: Text => t}).map(_.text.trim).mkString
81+
private def compareText(left: Node, right: Node, options: DiffOptions, path: Seq[String]): XmlDiff = {
82+
val leftText = left.child.collect({ case t: Text => t }).map(_.text.trim).mkString
83+
val rightText = right.child.collect({ case t: Text => t }).map(_.text.trim).mkString
4584

4685
if (leftText != rightText) {
47-
XmlDiffers("different text", leftText, rightText)
86+
XmlDiffers("different text", leftText, rightText, extendPath(path, left))
4887
} else {
4988
XmlEqual
5089
}
5190
}
5291

53-
private def compareChildren(left: Node, right: Node, options: DiffOptions): XmlDiff = {
92+
private def compareChildren(left: Node, right: Node, options: DiffOptions, path: Seq[String]): XmlDiff = {
5493
val leftChildren = left.child.filterNot(_.isInstanceOf[Text])
5594
val rightChildren = right.child.filterNot(_.isInstanceOf[Text])
5695

5796
if (leftChildren.size != rightChildren.size) {
58-
XmlDiffers("child count", leftChildren.size, rightChildren.size)
97+
XmlDiffers("different child count", leftChildren.size, rightChildren.size, extendPath(path, left))
5998
} else {
60-
val matchedChildren = leftChildren.zip(rightChildren)
61-
62-
matchedChildren.foldLeft[XmlDiff](XmlEqual) { case (status, (leftChild, rightChild)) =>
63-
if (!status.isEqual) {
64-
// already failed
65-
status
66-
} else {
67-
compare(leftChild, rightChild, options)
68-
}
99+
leftChildren.zip(rightChildren).foldLeft[XmlDiff](XmlEqual) {
100+
case (status, (leftChild, rightChild)) =>
101+
if (!status.isEqual) {
102+
// already failed
103+
status
104+
} else {
105+
compareNodes(leftChild, rightChild, options, extendPath(path, left))
106+
}
69107
}
70108
}
71109
}
110+
111+
private def extendPath(path: Seq[String], node: Node): Seq[String] = {
112+
path :+ node.nameToString(new StringBuilder()).toString
113+
}
72114
}
Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,56 @@
1+
/*
2+
* Copyright 2017 Michael Stringer
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package software.purpledragon.xml.compare
218

19+
/**
20+
* Results of a comparison between XML documents. Can be either [[XmlEqual]] or [[XmlDiffers]] (with details of the
21+
* differences).
22+
*/
323
sealed trait XmlDiff {
24+
25+
/** `true` if the XML documents are the same. */
426
def isEqual: Boolean
27+
28+
/** Descriptive message containing the differences (if any). */
529
def message: String
30+
31+
/** Path of the first difference (if any). */
32+
def failurePath: Seq[String]
633
}
734

35+
/**
36+
* Result of a successful comparison between XML documents.
37+
*/
838
object XmlEqual extends XmlDiff {
9-
override def isEqual: Boolean = true
10-
override def toString: String = "XmlEqual"
11-
override def message: String = ""
39+
override val isEqual = true
40+
override val toString: String = "XmlEqual"
41+
override val message = ""
42+
override val failurePath: Seq[String] = Nil
1243
}
1344

14-
case class XmlDiffers(reason: String, left: Any, right: Any) extends XmlDiff {
15-
override def isEqual: Boolean = false
16-
17-
override def message: String = s"$reason - $left != $right"
45+
/**
46+
* Result of an unsuccessful comparison between XML documents.
47+
*
48+
* @param reason descriptive reason for the comparison failing.
49+
* @param left the left-hand value in the failed comparison.
50+
* @param right the right-hand value in the failed comparison.
51+
* @param failurePath path to the failed node.
52+
*/
53+
case class XmlDiffers(reason: String, left: Any, right: Any, failurePath: Seq[String]) extends XmlDiff {
54+
override val isEqual = false
55+
override val message = s"$reason: [$left] != [$right]"
1856
}
Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,52 @@
1+
/*
2+
* Copyright 2017 Michael Stringer
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package software.purpledragon.xml.compare.options
218

19+
/**
20+
* Configuration options for XML comparisons.
21+
*/
322
object DiffOption extends Enumeration {
423
type DiffOption = Value
524

6-
val IgnorePrefix = DiffOption
25+
/**
26+
* Ignores XML namespace prefixes on elements.
27+
*
28+
* Enabling this makes this:
29+
* {{{
30+
* <t:example xmlns:t="http://example.com">5</t:example>
31+
* }}}
32+
* equal to:
33+
* {{{
34+
* <f:example xmlns:f="http://example.com">5</f:example>
35+
* }}}
36+
*/
37+
val IgnoreNamespacePrefix: DiffOption.Value = Value
38+
39+
/**
40+
* Ignores XML namespaces completely.
41+
*
42+
* Enabling this makes this:
43+
* {{{
44+
* <t:example xmlns:t="http://example.com">5</t:example>
45+
* }}}
46+
* equal to:
47+
* {{{
48+
* <f:example xmlns:f="http://example.org">5</f:example>
49+
* }}}
50+
*/
51+
val IgnoreNamespace: DiffOption.Value = Value
752
}
Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
1+
/*
2+
* Copyright 2017 Michael Stringer
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package software.purpledragon.xml.compare
218

19+
import software.purpledragon.xml.compare.options.DiffOption.DiffOption
20+
321
package object options {
4-
type DiffOptions = Set[DiffOption.type]
22+
23+
/**
24+
* Shorthand for a collection of [[DiffOption]]s.
25+
*/
26+
type DiffOptions = Set[DiffOption]
527
}

0 commit comments

Comments
 (0)