diff --git a/compiler.sbt b/compiler.sbt new file mode 100644 index 0000000..7fc6b54 --- /dev/null +++ b/compiler.sbt @@ -0,0 +1,3 @@ +import org.typelevel.scalacoptions.ScalacOptions + +tpolecatExcludeOptions += ScalacOptions.warnUnusedImports diff --git a/dependencies.sbt b/dependencies.sbt index 12092bc..1cb199e 100644 --- a/dependencies.sbt +++ b/dependencies.sbt @@ -1,5 +1,12 @@ ThisBuild / scalaVersion := "2.13.14" +/** + * Parsley is a fast and modern parser combinator library for Scala. + * @see [[https://github.com/j-mie6/parsley]] + */ +libraryDependencies += + "com.github.j-mie6" %% "parsley" % "4.5.2" + /** * ScalaTest is the most flexible and most popular testing tool in the Scala ecosystem. * @see [[https://www.scalatest.org/]] diff --git a/src/main/scala/ahlers/tree/path/Mapped.scala b/src/main/scala/ahlers/tree/path/Mapped.scala new file mode 100644 index 0000000..75c4f9a --- /dev/null +++ b/src/main/scala/ahlers/tree/path/Mapped.scala @@ -0,0 +1,26 @@ +package ahlers.tree.path + +case class Mapped() +object Mapped { + + case class Path(toSegments: Seq[Path.Segment]) + + object Path { + sealed trait Segment + + object Segment { + case class Key(toKey: String) extends Segment + case class Index(toIndex: Int) extends Segment + } + } + + sealed trait Value + + object Value { + case class Text(toText: String) extends Value + case class Integer(toBigInt: BigInt) extends Value + case class Decimal(toBigDecimal: BigDecimal) extends Value + case class Boolean(toBoolean: Boolean) extends Value + } + +} diff --git a/src/main/scala/ahlers/tree/path/expressions/Expression.scala b/src/main/scala/ahlers/tree/path/expressions/Expression.scala new file mode 100644 index 0000000..61dc524 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/expressions/Expression.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.expressions + +trait Expression { + def toText: String +} diff --git a/src/main/scala/ahlers/tree/path/expressions/Logical.scala b/src/main/scala/ahlers/tree/path/expressions/Logical.scala new file mode 100644 index 0000000..31785b7 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/expressions/Logical.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.expressions + +case class Logical() extends Expression { + override val toText: String = "Logical" +} diff --git a/src/main/scala/ahlers/tree/path/expressions/Selector.scala b/src/main/scala/ahlers/tree/path/expressions/Selector.scala new file mode 100644 index 0000000..eaeac3e --- /dev/null +++ b/src/main/scala/ahlers/tree/path/expressions/Selector.scala @@ -0,0 +1,7 @@ +package ahlers.tree.path.expressions + +import ahlers.tree.path.operators.Operator + +case class Selector(toOperators: Seq[Operator]) extends Expression { + val toText: String = toOperators.map(_.toText).mkString +} diff --git a/src/main/scala/ahlers/tree/path/filterOperators/FilterOperator.scala b/src/main/scala/ahlers/tree/path/filterOperators/FilterOperator.scala new file mode 100644 index 0000000..5a8f737 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/filterOperators/FilterOperator.scala @@ -0,0 +1,8 @@ +package ahlers.tree.path.filterOperators + +/** + * @see [[https://github.com/json-path/JsonPath/blob/45333e0a310af70ad48d34d306da30af1e8e6314/README.md#filter-operators]] + */ +trait FilterOperator { + def toText: String +} diff --git a/src/main/scala/ahlers/tree/path/filterOperators/HasSize.scala b/src/main/scala/ahlers/tree/path/filterOperators/HasSize.scala new file mode 100644 index 0000000..6412067 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/filterOperators/HasSize.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.filterOperators + +object HasSize extends FilterOperator { + override val toText: String = "size" +} diff --git a/src/main/scala/ahlers/tree/path/filterOperators/IsAnyOf.scala b/src/main/scala/ahlers/tree/path/filterOperators/IsAnyOf.scala new file mode 100644 index 0000000..6e645e5 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/filterOperators/IsAnyOf.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.filterOperators + +object IsAnyOf extends FilterOperator { + override val toText: String = "anyof" +} diff --git a/src/main/scala/ahlers/tree/path/filterOperators/IsEmpty.scala b/src/main/scala/ahlers/tree/path/filterOperators/IsEmpty.scala new file mode 100644 index 0000000..54751ab --- /dev/null +++ b/src/main/scala/ahlers/tree/path/filterOperators/IsEmpty.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.filterOperators + +object IsEmpty extends FilterOperator { + override val toText: String = "empty" +} diff --git a/src/main/scala/ahlers/tree/path/filterOperators/IsEqualTo.scala b/src/main/scala/ahlers/tree/path/filterOperators/IsEqualTo.scala new file mode 100644 index 0000000..9ce7c93 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/filterOperators/IsEqualTo.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.filterOperators + +object IsEqualTo extends FilterOperator { + override val toText: String = "==" +} diff --git a/src/main/scala/ahlers/tree/path/filterOperators/IsGreaterThan.scala b/src/main/scala/ahlers/tree/path/filterOperators/IsGreaterThan.scala new file mode 100644 index 0000000..e45b1a9 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/filterOperators/IsGreaterThan.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.filterOperators + +object IsGreaterThan extends FilterOperator { + override val toText: String = ">" +} diff --git a/src/main/scala/ahlers/tree/path/filterOperators/IsGreaterThanOrEqualTo.scala b/src/main/scala/ahlers/tree/path/filterOperators/IsGreaterThanOrEqualTo.scala new file mode 100644 index 0000000..9131e60 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/filterOperators/IsGreaterThanOrEqualTo.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.filterOperators + +object IsGreaterThanOrEqualTo extends FilterOperator { + override val toText: String = ">=" +} diff --git a/src/main/scala/ahlers/tree/path/filterOperators/IsIn.scala b/src/main/scala/ahlers/tree/path/filterOperators/IsIn.scala new file mode 100644 index 0000000..df4a2a1 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/filterOperators/IsIn.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.filterOperators + +object IsIn extends FilterOperator { + override val toText: String = "in" +} diff --git a/src/main/scala/ahlers/tree/path/filterOperators/IsLessThan.scala b/src/main/scala/ahlers/tree/path/filterOperators/IsLessThan.scala new file mode 100644 index 0000000..0515ad1 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/filterOperators/IsLessThan.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.filterOperators + +object IsLessThan extends FilterOperator { + override val toText: String = "<" +} diff --git a/src/main/scala/ahlers/tree/path/filterOperators/IsLessThanOrEqualTo.scala b/src/main/scala/ahlers/tree/path/filterOperators/IsLessThanOrEqualTo.scala new file mode 100644 index 0000000..6d6d49a --- /dev/null +++ b/src/main/scala/ahlers/tree/path/filterOperators/IsLessThanOrEqualTo.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.filterOperators + +object IsLessThanOrEqualTo extends FilterOperator { + override val toText: String = "<=" +} diff --git a/src/main/scala/ahlers/tree/path/filterOperators/IsMatchOf.scala b/src/main/scala/ahlers/tree/path/filterOperators/IsMatchOf.scala new file mode 100644 index 0000000..d397a18 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/filterOperators/IsMatchOf.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.filterOperators + +object IsMatchOf extends FilterOperator { + override val toText: String = "=~" +} diff --git a/src/main/scala/ahlers/tree/path/filterOperators/IsNoneOf.scala b/src/main/scala/ahlers/tree/path/filterOperators/IsNoneOf.scala new file mode 100644 index 0000000..269e76a --- /dev/null +++ b/src/main/scala/ahlers/tree/path/filterOperators/IsNoneOf.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.filterOperators + +object IsNoneOf extends FilterOperator { + override val toText: String = "noneof" +} diff --git a/src/main/scala/ahlers/tree/path/filterOperators/IsNotEqualTo.scala b/src/main/scala/ahlers/tree/path/filterOperators/IsNotEqualTo.scala new file mode 100644 index 0000000..d69d767 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/filterOperators/IsNotEqualTo.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.filterOperators + +object IsNotEqualTo extends FilterOperator { + override val toText: String = "!=" +} diff --git a/src/main/scala/ahlers/tree/path/filterOperators/IsNotIn.scala b/src/main/scala/ahlers/tree/path/filterOperators/IsNotIn.scala new file mode 100644 index 0000000..4bc9c04 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/filterOperators/IsNotIn.scala @@ -0,0 +1,7 @@ +package ahlers.tree.path.filterOperators + +object IsNotIn extends FilterOperator { + + /** \m/ */ + override val toText: String = "nin" +} diff --git a/src/main/scala/ahlers/tree/path/filterOperators/IsSubsetOf.scala b/src/main/scala/ahlers/tree/path/filterOperators/IsSubsetOf.scala new file mode 100644 index 0000000..bd246e0 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/filterOperators/IsSubsetOf.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.filterOperators + +object IsSubsetOf extends FilterOperator { + override val toText: String = "subsetof" +} diff --git a/src/main/scala/ahlers/tree/path/operators/ArrayIndexes.scala b/src/main/scala/ahlers/tree/path/operators/ArrayIndexes.scala new file mode 100644 index 0000000..b096a34 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/operators/ArrayIndexes.scala @@ -0,0 +1,7 @@ +package ahlers.tree.path.operators + +import ahlers.tree.path.terms.Index + +case class ArrayIndexes(toIndexes: Seq[Index]) extends Operator { + override val toText: String = toIndexes.map(_.toText).mkString("[", ",", "]") +} diff --git a/src/main/scala/ahlers/tree/path/operators/ArraySlice.scala b/src/main/scala/ahlers/tree/path/operators/ArraySlice.scala new file mode 100644 index 0000000..afb46ba --- /dev/null +++ b/src/main/scala/ahlers/tree/path/operators/ArraySlice.scala @@ -0,0 +1,17 @@ +package ahlers.tree.path.operators + +import ahlers.tree.path.terms.Index + +sealed trait ArraySlice extends Operator + +object ArraySlice { + case class LeftBounded(start: Index) extends ArraySlice { + val toText: String = s"[${start.toText}:]" + } + case class RightBounded(end: Index) extends ArraySlice { + val toText: String = s"[:${end.toText}]" + } + case class Bounded(start: Index, end: Index) extends ArraySlice { + val toText: String = s"[${start.toText}:${end.toText}]" + } +} diff --git a/src/main/scala/ahlers/tree/path/operators/BracketNotatedChildren.scala b/src/main/scala/ahlers/tree/path/operators/BracketNotatedChildren.scala new file mode 100644 index 0000000..7cfbecf --- /dev/null +++ b/src/main/scala/ahlers/tree/path/operators/BracketNotatedChildren.scala @@ -0,0 +1,23 @@ +package ahlers.tree.path.operators + +import ahlers.tree.path.terms.Name + +case class BracketNotatedChildren(toNames: Seq[BracketNotatedChildren.Child]) extends Operator { + override val toText: String = toNames.map(_.toText).mkString("[", ",", "]") +} + +object BracketNotatedChildren { + sealed trait Child { + def toText: String + } + object Child { + case class MatchingName(toName: Name) extends Child { + val toText: String = s"'${toName.toText}'" + } + + case object MatchingWildcard extends Child { + val toWildcard: Wildcard.type = Wildcard + val toText: String = s"${toWildcard.toText}" + } + } +} diff --git a/src/main/scala/ahlers/tree/path/operators/CurrentNode.scala b/src/main/scala/ahlers/tree/path/operators/CurrentNode.scala new file mode 100644 index 0000000..4146d91 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/operators/CurrentNode.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.operators + +case object CurrentNode extends Operator { + override val toText: String = "@" +} diff --git a/src/main/scala/ahlers/tree/path/operators/DeepScan.scala b/src/main/scala/ahlers/tree/path/operators/DeepScan.scala new file mode 100644 index 0000000..50d3e4a --- /dev/null +++ b/src/main/scala/ahlers/tree/path/operators/DeepScan.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.operators + +case object DeepScan extends Operator { + override val toText: String = ".." +} diff --git a/src/main/scala/ahlers/tree/path/operators/DotNotatedChild.scala b/src/main/scala/ahlers/tree/path/operators/DotNotatedChild.scala new file mode 100644 index 0000000..c3da5a4 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/operators/DotNotatedChild.scala @@ -0,0 +1,16 @@ +package ahlers.tree.path.operators + +import ahlers.tree.path.terms.Name + +sealed trait DotNotatedChild extends Operator + +object DotNotatedChild { + case class MatchingName(toName: Name) extends DotNotatedChild { + val toText: String = s".${toName.toText}" + } + + case object MatchingWildcard extends DotNotatedChild { + val toWildcard: Wildcard.type = Wildcard + val toText: String = s".${toWildcard.toText}" + } +} diff --git a/src/main/scala/ahlers/tree/path/operators/Operator.scala b/src/main/scala/ahlers/tree/path/operators/Operator.scala new file mode 100644 index 0000000..9a062b3 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/operators/Operator.scala @@ -0,0 +1,8 @@ +package ahlers.tree.path.operators + +/** + * @see [[https://github.com/json-path/JsonPath/blob/45333e0a310af70ad48d34d306da30af1e8e6314/README.md#operators]] + */ +trait Operator { + def toText: String +} diff --git a/src/main/scala/ahlers/tree/path/operators/RootElement.scala b/src/main/scala/ahlers/tree/path/operators/RootElement.scala new file mode 100644 index 0000000..49fa8e6 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/operators/RootElement.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.operators + +case object RootElement extends Operator { + override val toText: String = "$" +} diff --git a/src/main/scala/ahlers/tree/path/operators/Wildcard.scala b/src/main/scala/ahlers/tree/path/operators/Wildcard.scala new file mode 100644 index 0000000..93f30a7 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/operators/Wildcard.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.operators + +case object Wildcard extends Operator { + override val toText: String = "*" +} diff --git a/src/main/scala/ahlers/tree/path/parsers/expression.scala b/src/main/scala/ahlers/tree/path/parsers/expression.scala new file mode 100644 index 0000000..e24db5b --- /dev/null +++ b/src/main/scala/ahlers/tree/path/parsers/expression.scala @@ -0,0 +1,14 @@ +package ahlers.tree.path.parsers + +import ahlers.tree.path.expressions.Expression +import ahlers.tree.path.expressions.Selector +import parsley.Parsley +import parsley.Parsley.some + +object expression { + + val selector: Parsley[Selector] = some(operator.any).map(Selector) + + val any: Parsley[Expression] = selector + +} diff --git a/src/main/scala/ahlers/tree/path/parsers/filterOperator.scala b/src/main/scala/ahlers/tree/path/parsers/filterOperator.scala new file mode 100644 index 0000000..e9d254d --- /dev/null +++ b/src/main/scala/ahlers/tree/path/parsers/filterOperator.scala @@ -0,0 +1,38 @@ +package ahlers.tree.path.parsers + +import ahlers.tree.path.filterOperators.HasSize +import ahlers.tree.path.filterOperators.IsAnyOf +import ahlers.tree.path.filterOperators.IsEmpty +import ahlers.tree.path.filterOperators.IsEqualTo +import ahlers.tree.path.filterOperators.IsGreaterThan +import ahlers.tree.path.filterOperators.IsGreaterThanOrEqualTo +import ahlers.tree.path.filterOperators.IsIn +import ahlers.tree.path.filterOperators.IsLessThan +import ahlers.tree.path.filterOperators.IsLessThanOrEqualTo +import ahlers.tree.path.filterOperators.IsMatchOf +import ahlers.tree.path.filterOperators.IsNoneOf +import ahlers.tree.path.filterOperators.IsNotEqualTo +import ahlers.tree.path.filterOperators.IsNotIn +import ahlers.tree.path.filterOperators.IsSubsetOf +import parsley.Parsley +import parsley.character.char +import parsley.character.string + +object filterOperator { + + val isEqualTo: Parsley[IsEqualTo.type] = string("==").as(IsEqualTo) + val isNotEqualTo: Parsley[IsNotEqualTo.type] = string("!=").as(IsNotEqualTo) + val isLessThan: Parsley[IsLessThan.type] = char('<').as(IsLessThan) + val isLessThanOrEqualTo: Parsley[IsLessThanOrEqualTo.type] = string("<=").as(IsLessThanOrEqualTo) + val isGreaterThan: Parsley[IsGreaterThan.type] = char('>').as(IsGreaterThan) + val isGreaterThanOrEqualTo: Parsley[IsGreaterThanOrEqualTo.type] = string(">=").as(IsGreaterThanOrEqualTo) + val isMatchOf: Parsley[IsMatchOf.type] = string("=~").as(IsMatchOf) + val isIn: Parsley[IsIn.type] = string("in").as(IsIn) + val isNotIn: Parsley[IsNotIn.type] = string("nin").as(IsNotIn) + val isSubsetOf: Parsley[IsSubsetOf.type] = string("subsetof").as(IsSubsetOf) + val isAnyOf: Parsley[IsAnyOf.type] = string("anyof").as(IsAnyOf) + val isNoneOf: Parsley[IsNoneOf.type] = string("noneof").as(IsNoneOf) + val hasSize: Parsley[HasSize.type] = string("size").as(HasSize) + val isEmpty: Parsley[IsEmpty.type] = string("empty").as(IsEmpty) + +} diff --git a/src/main/scala/ahlers/tree/path/parsers/operator.scala b/src/main/scala/ahlers/tree/path/parsers/operator.scala new file mode 100644 index 0000000..5fb956a --- /dev/null +++ b/src/main/scala/ahlers/tree/path/parsers/operator.scala @@ -0,0 +1,59 @@ +package ahlers.tree.path.parsers + +import ahlers.tree.path.operators.ArrayIndexes +import ahlers.tree.path.operators.ArraySlice +import ahlers.tree.path.operators.BracketNotatedChildren +import ahlers.tree.path.operators.CurrentNode +import ahlers.tree.path.operators.DeepScan +import ahlers.tree.path.operators.DotNotatedChild +import ahlers.tree.path.operators.Operator +import ahlers.tree.path.operators.RootElement +import ahlers.tree.path.operators.Wildcard +import ahlers.tree.path.parsers.term.index +import ahlers.tree.path.parsers.term.name +import parsley.Parsley +import parsley.Parsley.atomic +import parsley.character.char +import parsley.character.string +import parsley.character.whitespaces +import parsley.combinator.sepBy1 + +object operator { + + val rootElement: Parsley[RootElement.type] = char('$').as(RootElement) + + val currentNode: Parsley[CurrentNode.type] = char('@').as(CurrentNode) + + val wildcard: Parsley[Wildcard.type] = char('*').as(Wildcard) + + val deepScan: Parsley[DeepScan.type] = string("..").as(DeepScan) + + val dotNotatedChildMatchingName: Parsley[DotNotatedChild.MatchingName] = (char('.') *> name).map(DotNotatedChild.MatchingName) + val dotNotatedChildMatchingWildcard: Parsley[DotNotatedChild.MatchingWildcard.type] = (char('.') *> wildcard).as(DotNotatedChild.MatchingWildcard) + val dotNotatedChild: Parsley[DotNotatedChild] = atomic(dotNotatedChildMatchingName) | atomic(dotNotatedChildMatchingWildcard) + + val bracketNotatedChildren: Parsley[BracketNotatedChildren] = { + import BracketNotatedChildren.Child + val childMatchingName: Parsley[Child.MatchingName] = (char('\'') *> name <* char('\'')).map(Child.MatchingName) + val childMatchingWildcard: Parsley[Child.MatchingWildcard.type] = wildcard.as(Child.MatchingWildcard) + (char('[') *> sepBy1(atomic(childMatchingName) | atomic(childMatchingWildcard), whitespaces *> char(',') <* whitespaces) <* char(']')).map(BracketNotatedChildren(_)) + } + + val arrayIndexes: Parsley[ArrayIndexes] = (char('[') *> sepBy1(index, whitespaces *> char(',') <* whitespaces) <* char(']')).map(ArrayIndexes) + + val arraySliceLeftBounded: Parsley[ArraySlice.LeftBounded] = (char('[') *> index <~ char(':') <* char(']')).map(ArraySlice.LeftBounded) + val arraySliceRightBounded: Parsley[ArraySlice.RightBounded] = (char('[') *> char(':') ~> index <~ char(']')).map(ArraySlice.RightBounded) + val arraySliceBounded: Parsley[ArraySlice.Bounded] = (char('[') *> index <~> char(':') *> index <~ char(']')).map((ArraySlice.Bounded(_, _)).tupled) + val arraySlice: Parsley[ArraySlice] = atomic(arraySliceLeftBounded) | atomic(arraySliceRightBounded) | atomic(arraySliceBounded) + + val any: Parsley[Operator] = + atomic(rootElement) | + atomic(currentNode) | + atomic(wildcard) | + atomic(deepScan) | + atomic(dotNotatedChild) | + atomic(bracketNotatedChildren) | + atomic(arrayIndexes) | + atomic(arraySlice) + +} diff --git a/src/main/scala/ahlers/tree/path/parsers/term.scala b/src/main/scala/ahlers/tree/path/parsers/term.scala new file mode 100644 index 0000000..a16f791 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/parsers/term.scala @@ -0,0 +1,16 @@ +package ahlers.tree.path.parsers + +import ahlers.tree.path.terms.Index +import ahlers.tree.path.terms.Name +import parsley.Parsley +import parsley.character.satisfy +import parsley.character.stringOfSome + +object term { + val index: Parsley[Index] = stringOfSome(_.isDigit).map(_.toInt).map(Index) + + val name: Parsley[Name] = { + val isValid: Set[Char] = ('a' to 'z').toSet ++ ('A' to 'Z').toSet ++ ('0' to '9').toSet + stringOfSome(satisfy(isValid)).map(Name) + } +} diff --git a/src/main/scala/ahlers/tree/path/terms/Index.scala b/src/main/scala/ahlers/tree/path/terms/Index.scala new file mode 100644 index 0000000..bae5360 --- /dev/null +++ b/src/main/scala/ahlers/tree/path/terms/Index.scala @@ -0,0 +1,5 @@ +package ahlers.tree.path.terms + +case class Index(toInt: Int) { + val toText: String = toInt.toString +} diff --git a/src/main/scala/ahlers/tree/path/terms/Name.scala b/src/main/scala/ahlers/tree/path/terms/Name.scala new file mode 100644 index 0000000..860ed9c --- /dev/null +++ b/src/main/scala/ahlers/tree/path/terms/Name.scala @@ -0,0 +1,3 @@ +package ahlers.tree.path.terms + +case class Name(toText: String) diff --git a/src/test/scala/ahlers/tree/path/expressions/algebra.scala b/src/test/scala/ahlers/tree/path/expressions/algebra.scala new file mode 100644 index 0000000..0b7a6b4 --- /dev/null +++ b/src/test/scala/ahlers/tree/path/expressions/algebra.scala @@ -0,0 +1,18 @@ +package ahlers.tree.path.expressions + +private object algebra { + + sealed abstract class IsExpression private (val toExpression: Expression) + object IsExpression { + case class OfSelector(override val toExpression: Selector) extends IsExpression(toExpression) + case class OfLogical(override val toExpression: Logical) extends IsExpression(toExpression) + case class OfUnknown(override val toExpression: Expression) extends IsExpression(toExpression) + + def apply(expression: Expression): IsExpression = expression match { + case expression: Selector => OfSelector(expression) + case expression: Logical => OfLogical(expression) + case expression => OfUnknown(expression) + } + } + +} diff --git a/src/test/scala/ahlers/tree/path/expressions/diffx/instances.scala b/src/test/scala/ahlers/tree/path/expressions/diffx/instances.scala new file mode 100644 index 0000000..b42391c --- /dev/null +++ b/src/test/scala/ahlers/tree/path/expressions/diffx/instances.scala @@ -0,0 +1,26 @@ +package ahlers.tree.path.expressions.diffx + +import ahlers.tree.path.expressions.Expression +import ahlers.tree.path.expressions.Logical +import ahlers.tree.path.expressions.Selector +import ahlers.tree.path.expressions.algebra.IsExpression +import ahlers.tree.path.operators.diffx.instances._ +import com.softwaremill.diffx.Diff + +object instances { + + implicit val diffSelector: Diff[Selector] = Diff.derived + + implicit val diffLogical: Diff[Logical] = Diff.derived + + implicit val diffExpression: Diff[Expression] = { + implicit val diffIsExpression: Diff[IsExpression] = { + import IsExpression.OfUnknown + implicit val diffOfUnknown: Diff[OfUnknown] = Diff.useEquals + Diff.derived + } + + Diff[IsExpression].contramap(IsExpression(_)) + } + +} diff --git a/src/test/scala/ahlers/tree/path/expressions/scalacheck/instances.scala b/src/test/scala/ahlers/tree/path/expressions/scalacheck/instances.scala new file mode 100644 index 0000000..0a3e5d6 --- /dev/null +++ b/src/test/scala/ahlers/tree/path/expressions/scalacheck/instances.scala @@ -0,0 +1,28 @@ +package ahlers.tree.path.expressions.scalacheck + +import ahlers.tree.path.expressions.Expression +import ahlers.tree.path.expressions.Logical +import ahlers.tree.path.expressions.Selector +import ahlers.tree.path.operators.Operator +import ahlers.tree.path.operators.scalacheck.instances._ +import magnolify.scalacheck.semiauto._ +import org.scalacheck.Arbitrary +import org.scalacheck.Arbitrary.arbitrary +import org.scalacheck.Gen + +object instances { + + implicit val arbSelector: Arbitrary[Selector] = Arbitrary(for { + head <- arbitrary[Operator] + tail <- arbitrary[Seq[Operator]] + } yield Selector(head +: tail)) + + implicit val arbLogical: Arbitrary[Logical] = ArbitraryDerivation[Logical] + + implicit val arbExpression: Arbitrary[Expression] = Arbitrary(Gen.oneOf( + arbitrary[Selector], + arbitrary[Selector], + // arbitrary[Logical], + )) + +} diff --git a/src/test/scala/ahlers/tree/path/filterOperators/algebra.scala b/src/test/scala/ahlers/tree/path/filterOperators/algebra.scala new file mode 100644 index 0000000..0c735fb --- /dev/null +++ b/src/test/scala/ahlers/tree/path/filterOperators/algebra.scala @@ -0,0 +1,42 @@ +package ahlers.tree.path.filterOperators + +private object algebra { + + sealed abstract class IsFilterOperator private (val toFilterOperator: FilterOperator) + object IsFilterOperator { + case object OfIsEqual extends IsFilterOperator(IsEqualTo) + case object OfIsNotEqual extends IsFilterOperator(IsNotEqualTo) + case object OfIsLessThan extends IsFilterOperator(IsLessThan) + case object OfIsLessThanOrEqualTo extends IsFilterOperator(IsLessThanOrEqualTo) + case object OfIsGreaterThan extends IsFilterOperator(IsGreaterThan) + case object OfIsGreaterThanOrEqualTo extends IsFilterOperator(IsGreaterThanOrEqualTo) + case object OfIsMatchOf extends IsFilterOperator(IsMatchOf) + case object OfIsIn extends IsFilterOperator(IsIn) + case object OfIsNotIn extends IsFilterOperator(IsNotIn) + case object OfIsSubsetOf extends IsFilterOperator(IsSubsetOf) + case object OfIsAnyOf extends IsFilterOperator(IsAnyOf) + case object OfIsNoneOf extends IsFilterOperator(IsNoneOf) + case object OfHasSize extends IsFilterOperator(HasSize) + case object OfIsEmpty extends IsFilterOperator(IsEmpty) + case class OfUnknown(override val toFilterOperator: FilterOperator) extends IsFilterOperator(toFilterOperator) + + def apply(operator: FilterOperator): IsFilterOperator = operator match { + case IsEqualTo => OfIsEqual + case IsNotEqualTo => OfIsNotEqual + case IsLessThan => OfIsLessThan + case IsLessThanOrEqualTo => OfIsLessThanOrEqualTo + case IsGreaterThan => OfIsGreaterThan + case IsGreaterThanOrEqualTo => OfIsGreaterThanOrEqualTo + case IsMatchOf => OfIsMatchOf + case IsIn => OfIsIn + case IsNotIn => OfIsNotIn + case IsSubsetOf => OfIsSubsetOf + case IsAnyOf => OfIsAnyOf + case IsNoneOf => OfIsNoneOf + case HasSize => OfHasSize + case IsEmpty => OfIsEmpty + case operator => OfUnknown(operator) + } + } + +} diff --git a/src/test/scala/ahlers/tree/path/filterOperators/diffx/instances.scala b/src/test/scala/ahlers/tree/path/filterOperators/diffx/instances.scala new file mode 100644 index 0000000..f8aa147 --- /dev/null +++ b/src/test/scala/ahlers/tree/path/filterOperators/diffx/instances.scala @@ -0,0 +1,34 @@ +package ahlers.tree.path.filterOperators.diffx + +import ahlers.tree.path.filterOperators._ +import ahlers.tree.path.filterOperators.algebra.IsFilterOperator +import com.softwaremill.diffx.Diff + +object instances { + + implicit val diffIsEqual: Diff[IsEqualTo.type] = Diff.derived + implicit val diffIsNotEqual: Diff[IsNotEqualTo.type] = Diff.derived + implicit val diffIsLessThan: Diff[IsLessThan.type] = Diff.derived + implicit val diffIsLessThanOrEqualTo: Diff[IsLessThanOrEqualTo.type] = Diff.derived + implicit val diffIsGreaterThan: Diff[IsGreaterThan.type] = Diff.derived + implicit val diffIsGreaterThanOrEqualTo: Diff[IsGreaterThanOrEqualTo.type] = Diff.derived + implicit val diffIsMatchOf: Diff[IsMatchOf.type] = Diff.derived + implicit val diffIsIn: Diff[IsIn.type] = Diff.derived + implicit val diffIsNotIn: Diff[IsNotIn.type] = Diff.derived + implicit val diffIsSubsetOf: Diff[IsSubsetOf.type] = Diff.derived + implicit val diffIsAnyOf: Diff[IsAnyOf.type] = Diff.derived + implicit val diffIsNoneOf: Diff[IsNoneOf.type] = Diff.derived + implicit val diffHasSize: Diff[HasSize.type] = Diff.derived + implicit val diffIsEmpty: Diff[IsEmpty.type] = Diff.derived + + implicit val diffFilterOperator: Diff[FilterOperator] = { + implicit val diffIsFilterOperator: Diff[IsFilterOperator] = { + import IsFilterOperator.OfUnknown + implicit val diffOfUnknown: Diff[OfUnknown] = Diff.useEquals + Diff.derived + } + + Diff[IsFilterOperator].contramap(IsFilterOperator(_)) + } + +} diff --git a/src/test/scala/ahlers/tree/path/filterOperators/scalacheck/instances.scala b/src/test/scala/ahlers/tree/path/filterOperators/scalacheck/instances.scala new file mode 100644 index 0000000..ff38cf0 --- /dev/null +++ b/src/test/scala/ahlers/tree/path/filterOperators/scalacheck/instances.scala @@ -0,0 +1,42 @@ +package ahlers.tree.path.filterOperators.scalacheck + +import ahlers.tree.path.filterOperators._ +import org.scalacheck.Arbitrary +import org.scalacheck.Arbitrary.arbitrary +import org.scalacheck.Gen + +object instances { + + implicit val arbIsEqual: Arbitrary[IsEqualTo.type] = Arbitrary(Gen.const(IsEqualTo)) + implicit val arbIsNotEqual: Arbitrary[IsNotEqualTo.type] = Arbitrary(Gen.const(IsNotEqualTo)) + implicit val arbIsLessThan: Arbitrary[IsLessThan.type] = Arbitrary(Gen.const(IsLessThan)) + implicit val arbIsLessThanOrEqualTo: Arbitrary[IsLessThanOrEqualTo.type] = Arbitrary(Gen.const(IsLessThanOrEqualTo)) + implicit val arbIsGreaterThan: Arbitrary[IsGreaterThan.type] = Arbitrary(Gen.const(IsGreaterThan)) + implicit val arbIsGreaterThanOrEqualTo: Arbitrary[IsGreaterThanOrEqualTo.type] = Arbitrary(Gen.const(IsGreaterThanOrEqualTo)) + implicit val arbIsMatchOf: Arbitrary[IsMatchOf.type] = Arbitrary(Gen.const(IsMatchOf)) + implicit val arbIsIn: Arbitrary[IsIn.type] = Arbitrary(Gen.const(IsIn)) + implicit val arbIsNotIn: Arbitrary[IsNotIn.type] = Arbitrary(Gen.const(IsNotIn)) + implicit val arbIsSubsetOf: Arbitrary[IsSubsetOf.type] = Arbitrary(Gen.const(IsSubsetOf)) + implicit val arbIsAnyOf: Arbitrary[IsAnyOf.type] = Arbitrary(Gen.const(IsAnyOf)) + implicit val arbIsNoneOf: Arbitrary[IsNoneOf.type] = Arbitrary(Gen.const(IsNoneOf)) + implicit val arbHasSize: Arbitrary[HasSize.type] = Arbitrary(Gen.const(HasSize)) + implicit val arbIsEmpty: Arbitrary[IsEmpty.type] = Arbitrary(Gen.const(IsEmpty)) + + implicit val arbFilterOperator: Arbitrary[FilterOperator] = Arbitrary(Gen.oneOf( + arbitrary[IsEqualTo.type], + arbitrary[IsNotEqualTo.type], + arbitrary[IsLessThan.type], + arbitrary[IsLessThanOrEqualTo.type], + arbitrary[IsGreaterThan.type], + arbitrary[IsGreaterThanOrEqualTo.type], + arbitrary[IsMatchOf.type], + arbitrary[IsIn.type], + arbitrary[IsNotIn.type], + arbitrary[IsSubsetOf.type], + arbitrary[IsAnyOf.type], + arbitrary[IsNoneOf.type], + arbitrary[HasSize.type], + arbitrary[IsEmpty.type], + )) + +} diff --git a/src/test/scala/ahlers/tree/path/operators/algebra.scala b/src/test/scala/ahlers/tree/path/operators/algebra.scala new file mode 100644 index 0000000..1b87781 --- /dev/null +++ b/src/test/scala/ahlers/tree/path/operators/algebra.scala @@ -0,0 +1,30 @@ +package ahlers.tree.path.operators + +private object algebra { + + sealed abstract class IsOperator private (val toOperator: Operator) + object IsOperator { + case object OfRootElement extends IsOperator(RootElement) + case object OfCurrentNode extends IsOperator(CurrentNode) + case object OfWildcard extends IsOperator(Wildcard) + case object OfDeepScan extends IsOperator(DeepScan) + case class OfDotNotatedChild(override val toOperator: DotNotatedChild) extends IsOperator(toOperator) + case class OfBracketNotatedChildren(override val toOperator: BracketNotatedChildren) extends IsOperator(toOperator) + case class OfArrayIndexes(override val toOperator: ArrayIndexes) extends IsOperator(toOperator) + case class OfArraySlice(override val toOperator: ArraySlice) extends IsOperator(toOperator) + case class OfUnknown(override val toOperator: Operator) extends IsOperator(toOperator) + + def apply(operator: Operator): IsOperator = operator match { + case RootElement => OfRootElement + case CurrentNode => OfCurrentNode + case Wildcard => OfWildcard + case DeepScan => OfDeepScan + case operator: DotNotatedChild => OfDotNotatedChild(operator) + case operator: BracketNotatedChildren => OfBracketNotatedChildren(operator) + case operator: ArrayIndexes => OfArrayIndexes(operator) + case operator: ArraySlice => OfArraySlice(operator) + case operator => OfUnknown(operator) + } + } + +} diff --git a/src/test/scala/ahlers/tree/path/operators/diffx/instances.scala b/src/test/scala/ahlers/tree/path/operators/diffx/instances.scala new file mode 100644 index 0000000..ac3da8a --- /dev/null +++ b/src/test/scala/ahlers/tree/path/operators/diffx/instances.scala @@ -0,0 +1,53 @@ +package ahlers.tree.path.operators.diffx + +import ahlers.tree.path.operators.ArrayIndexes +import ahlers.tree.path.operators.ArraySlice +import ahlers.tree.path.operators.BracketNotatedChildren +import ahlers.tree.path.operators.CurrentNode +import ahlers.tree.path.operators.DeepScan +import ahlers.tree.path.operators.DotNotatedChild +import ahlers.tree.path.operators.Operator +import ahlers.tree.path.operators.RootElement +import ahlers.tree.path.operators.Wildcard +import ahlers.tree.path.operators.algebra.IsOperator +import ahlers.tree.path.terms.diffx.instances._ +import com.softwaremill.diffx.Diff + +object instances { + + implicit val diffCurrentNode: Diff[CurrentNode.type] = Diff.derived + + implicit val diffRootElement: Diff[RootElement.type] = Diff.derived + + implicit val diffWildcard: Diff[Wildcard.type] = Diff.derived + + implicit val diffDeepScan: Diff[DeepScan.type] = Diff.derived + + implicit val diffDotNotatedChildMatchingName: Diff[DotNotatedChild.MatchingName] = Diff.derived + implicit val diffDotNotatedChildMatchingWildcard: Diff[DotNotatedChild.MatchingWildcard.type] = Diff.derived + implicit val diffDotNotatedChild: Diff[DotNotatedChild] = Diff.derived + + implicit val diffBracketNotatedChildren: Diff[BracketNotatedChildren] = { + import BracketNotatedChildren.Child + implicit val diffChild: Diff[Child] = Diff.derived + Diff.derived + } + + implicit val diffArrayIndexes: Diff[ArrayIndexes] = Diff.derived + + implicit val diffArraySliceLeftBounded: Diff[ArraySlice.LeftBounded] = Diff.derived + implicit val diffArraySliceRightBounded: Diff[ArraySlice.RightBounded] = Diff.derived + implicit val diffArraySliceBounded: Diff[ArraySlice.Bounded] = Diff.derived + implicit val diffArraySlice: Diff[ArraySlice] = Diff.derived + + implicit val diffOperator: Diff[Operator] = { + implicit val diffIsOperator: Diff[IsOperator] = { + import IsOperator.OfUnknown + implicit val diffOfUnknown: Diff[OfUnknown] = Diff.useEquals + Diff.derived + } + + Diff[IsOperator].contramap(IsOperator(_)) + } + +} diff --git a/src/test/scala/ahlers/tree/path/operators/scalacheck/instances.scala b/src/test/scala/ahlers/tree/path/operators/scalacheck/instances.scala new file mode 100644 index 0000000..41080b4 --- /dev/null +++ b/src/test/scala/ahlers/tree/path/operators/scalacheck/instances.scala @@ -0,0 +1,66 @@ +package ahlers.tree.path.operators.scalacheck + +import ahlers.tree.path.operators.ArrayIndexes +import ahlers.tree.path.operators.ArraySlice +import ahlers.tree.path.operators.BracketNotatedChildren +import ahlers.tree.path.operators.CurrentNode +import ahlers.tree.path.operators.DeepScan +import ahlers.tree.path.operators.DotNotatedChild +import ahlers.tree.path.operators.Operator +import ahlers.tree.path.operators.RootElement +import ahlers.tree.path.operators.Wildcard +import ahlers.tree.path.operators.algebra.IsOperator +import ahlers.tree.path.terms.Index +import ahlers.tree.path.terms.Name +import ahlers.tree.path.terms.scalacheck.instances._ +import magnolify.scalacheck.semiauto._ +import org.scalacheck.Arbitrary +import org.scalacheck.Arbitrary.arbitrary +import org.scalacheck.Gen + +object instances { + + implicit val arbCurrentNode: Arbitrary[CurrentNode.type] = Arbitrary(Gen.const(CurrentNode)) + + implicit val arbRootElement: Arbitrary[RootElement.type] = Arbitrary(Gen.const(RootElement)) + + implicit val arbWildcard: Arbitrary[Wildcard.type] = Arbitrary(Gen.const(Wildcard)) + + implicit val arbDeepScan: Arbitrary[DeepScan.type] = Arbitrary(Gen.const(DeepScan)) + + implicit val arbDotNotatedChildMatchingName: Arbitrary[DotNotatedChild.MatchingName] = Arbitrary(arbitrary[Name].map(DotNotatedChild.MatchingName)) + implicit val arbDotNotatedChildMatchingWildcard: Arbitrary[DotNotatedChild.MatchingWildcard.type] = ArbitraryDerivation[DotNotatedChild.MatchingWildcard.type] + implicit val arbDotNotatedChild: Arbitrary[DotNotatedChild] = ArbitraryDerivation[DotNotatedChild] + + implicit val arbBracketNotatedChildren: Arbitrary[BracketNotatedChildren] = Arbitrary { + import BracketNotatedChildren.Child + implicit val arbChild: Arbitrary[Child] = ArbitraryDerivation[Child] + + for { + head <- arbitrary[Child] + tail <- arbitrary[Seq[Child]] + } yield BracketNotatedChildren(head +: tail) + } + + implicit val arbArrayIndexes: Arbitrary[ArrayIndexes] = Arbitrary(for { + head <- arbitrary[Index] + tail <- arbitrary[Seq[Index]] + } yield ArrayIndexes(head +: tail)) + + implicit val arbArraySliceLeftBounded: Arbitrary[ArraySlice.LeftBounded] = ArbitraryDerivation[ArraySlice.LeftBounded] + implicit val arbArraySliceRightBounded: Arbitrary[ArraySlice.RightBounded] = ArbitraryDerivation[ArraySlice.RightBounded] + implicit val arbArraySliceBounded: Arbitrary[ArraySlice.Bounded] = ArbitraryDerivation[ArraySlice.Bounded] + implicit val arbArraySlice: Arbitrary[ArraySlice] = ArbitraryDerivation[ArraySlice] + + implicit val arbOperator: Arbitrary[Operator] = Arbitrary(Gen.oneOf( + arbitrary[CurrentNode.type], + arbitrary[RootElement.type], + arbitrary[Wildcard.type], + arbitrary[DeepScan.type], + arbitrary[DotNotatedChild], + arbitrary[BracketNotatedChildren], + arbitrary[ArrayIndexes], + arbitrary[ArraySlice], + )) + +} diff --git a/src/test/scala/ahlers/tree/path/parsers/ExpressionSpec.scala b/src/test/scala/ahlers/tree/path/parsers/ExpressionSpec.scala new file mode 100644 index 0000000..c250a32 --- /dev/null +++ b/src/test/scala/ahlers/tree/path/parsers/ExpressionSpec.scala @@ -0,0 +1,34 @@ +package ahlers.tree.path.parsers + +import ahlers.tree.path.expressions.Expression +import ahlers.tree.path.expressions.Selector +import ahlers.tree.path.expressions.diffx.instances._ +import ahlers.tree.path.expressions.scalacheck.instances._ +import com.softwaremill.diffx.scalatest.DiffShouldMatcher._ +import org.scalatest.wordspec.AnyWordSpec +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks._ +import parsley.Success +import parsley.diffx.instances._ + +class ExpressionSpec extends AnyWordSpec { + + "Selector" should { + val parser = expression.selector + + s"""accept sequence of operators""" in { + forAll { expression: Selector => + parser.parse(expression.toText).shouldMatchTo(Success(expression)) + } + } + } + + "Expression" should { + val parser = expression.any + s"""accept any expression""" in { + forAll { expression: Expression => + parser.parse(expression.toText).shouldMatchTo(Success(expression)) + } + } + } + +} diff --git a/src/test/scala/ahlers/tree/path/parsers/FilterOperatorSpec.scala b/src/test/scala/ahlers/tree/path/parsers/FilterOperatorSpec.scala new file mode 100644 index 0000000..289b87f --- /dev/null +++ b/src/test/scala/ahlers/tree/path/parsers/FilterOperatorSpec.scala @@ -0,0 +1,281 @@ +package ahlers.tree.path.parsers + +import ahlers.tree.path.filterOperators.HasSize +import ahlers.tree.path.filterOperators.IsAnyOf +import ahlers.tree.path.filterOperators.IsEmpty +import ahlers.tree.path.filterOperators.IsEqualTo +import ahlers.tree.path.filterOperators.IsGreaterThan +import ahlers.tree.path.filterOperators.IsGreaterThanOrEqualTo +import ahlers.tree.path.filterOperators.IsIn +import ahlers.tree.path.filterOperators.IsLessThan +import ahlers.tree.path.filterOperators.IsLessThanOrEqualTo +import ahlers.tree.path.filterOperators.IsMatchOf +import ahlers.tree.path.filterOperators.IsNoneOf +import ahlers.tree.path.filterOperators.IsNotEqualTo +import ahlers.tree.path.filterOperators.IsNotIn +import ahlers.tree.path.filterOperators.IsSubsetOf +import ahlers.tree.path.filterOperators.diffx.instances._ +import ahlers.tree.path.filterOperators.scalacheck.instances._ +import com.softwaremill.diffx.scalatest.DiffShouldMatcher._ +import org.scalatest.matchers.should.Matchers._ +import org.scalatest.wordspec.AnyWordSpec +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks._ +import parsley.Failure +import parsley.Success +import parsley.diffx.instances._ + +class FilterOperatorSpec extends AnyWordSpec { + + "IsEqualTo" should { + val parser = filterOperator.isEqualTo + val pattern = "^==$".r + + s"""accept $pattern""" in { + val filterOperator = IsEqualTo + parser.parse(filterOperator.toText).shouldMatchTo(Success(filterOperator)) + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "IsNotEqualTo" should { + val parser = filterOperator.isNotEqualTo + val pattern = "^!=$".r + + s"""accept $pattern""" in { + val filterOperator = IsNotEqualTo + parser.parse(filterOperator.toText).shouldMatchTo(Success(filterOperator)) + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "IsLessThan" should { + val parser = filterOperator.isLessThan + val pattern = "^<$".r + + s"""accept $pattern""" in { + val filterOperator = IsLessThan + parser.parse(filterOperator.toText).shouldMatchTo(Success(filterOperator)) + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "IsLessThanOrEqualTo" should { + val parser = filterOperator.isLessThanOrEqualTo + val pattern = "^<=$".r + + s"""accept $pattern""" in { + val filterOperator = IsLessThanOrEqualTo + parser.parse(filterOperator.toText).shouldMatchTo(Success(filterOperator)) + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "IsGreaterThan" should { + val parser = filterOperator.isGreaterThan + val pattern = "^>$".r + + s"""accept $pattern""" in { + val filterOperator = IsGreaterThan + parser.parse(filterOperator.toText).shouldMatchTo(Success(filterOperator)) + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "IsGreaterThanOrEqualTo" should { + val parser = filterOperator.isGreaterThanOrEqualTo + val pattern = "^>=$".r + + s"""accept $pattern""" in { + val filterOperator = IsGreaterThanOrEqualTo + parser.parse(filterOperator.toText).shouldMatchTo(Success(filterOperator)) + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "IsMatchOf" should { + val parser = filterOperator.isMatchOf + val pattern = "^~=$".r + + s"""accept $pattern""" in { + val filterOperator = IsMatchOf + parser.parse(filterOperator.toText).shouldMatchTo(Success(filterOperator)) + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "IsIn" should { + val parser = filterOperator.isIn + val pattern = "^in$".r + + s"""accept $pattern""" in { + val filterOperator = IsIn + parser.parse(filterOperator.toText).shouldMatchTo(Success(filterOperator)) + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "IsNotIn" should { + val parser = filterOperator.isNotIn + val pattern = "^nin$".r + + s"""accept $pattern""" in { + val filterOperator = IsNotIn + parser.parse(filterOperator.toText).shouldMatchTo(Success(filterOperator)) + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "IsSubsetOf" should { + val parser = filterOperator.isSubsetOf + val pattern = "^subsetof$".r + + s"""accept $pattern""" in { + val filterOperator = IsSubsetOf + parser.parse(filterOperator.toText).shouldMatchTo(Success(filterOperator)) + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "IsAnyOf" should { + val parser = filterOperator.isAnyOf + val pattern = "^anyof$".r + + s"""accept $pattern""" in { + val filterOperator = IsAnyOf + parser.parse(filterOperator.toText).shouldMatchTo(Success(filterOperator)) + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "IsNoneOf" should { + val parser = filterOperator.isNoneOf + val pattern = "^noneof$".r + + s"""accept $pattern""" in { + val filterOperator = IsNoneOf + parser.parse(filterOperator.toText).shouldMatchTo(Success(filterOperator)) + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "HasSize" should { + val parser = filterOperator.hasSize + val pattern = "^size$".r + + s"""accept $pattern""" in { + val filterOperator = HasSize + parser.parse(filterOperator.toText).shouldMatchTo(Success(filterOperator)) + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "IsEmpty" should { + val parser = filterOperator.isEmpty + val pattern = "^empty$".r + + s"""accept $pattern""" in { + val filterOperator = IsEmpty + parser.parse(filterOperator.toText).shouldMatchTo(Success(filterOperator)) + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + +} diff --git a/src/test/scala/ahlers/tree/path/parsers/OperatorSpec.scala b/src/test/scala/ahlers/tree/path/parsers/OperatorSpec.scala new file mode 100644 index 0000000..c9b6eca --- /dev/null +++ b/src/test/scala/ahlers/tree/path/parsers/OperatorSpec.scala @@ -0,0 +1,268 @@ +package ahlers.tree.path.parsers + +import ahlers.tree.path.operators._ +import ahlers.tree.path.operators.diffx.instances._ +import ahlers.tree.path.operators.scalacheck.instances._ +import com.softwaremill.diffx.scalatest.DiffShouldMatcher._ +import org.scalatest.matchers.should.Matchers._ +import org.scalatest.wordspec.AnyWordSpec +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks._ +import parsley.Failure +import parsley.Success +import parsley.diffx.instances._ + +class OperatorSpec extends AnyWordSpec { + + "RootElement" should { + val parser = operator.rootElement + val pattern = "^\\$$".r + + s"""accept $pattern""" in { + val operator = RootElement + parser.parse(operator.toText).shouldMatchTo(Success(operator)) + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "CurrentNode" should { + val parser = operator.currentNode + val pattern = "^@$".r + + s"""accept $pattern""" in { + val operator = CurrentNode + parser.parse(operator.toText).shouldMatchTo(Success(operator)) + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "Wildcard" should { + val parser = operator.wildcard + val pattern = "^\\*$".r + + s"""accept $pattern""" in { + val operator = Wildcard + parser.parse(operator.toText).shouldMatchTo(Success(operator)) + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "DeepScan" should { + val parser = operator.deepScan + val pattern = "^\\.\\.$".r + + s"""accept $pattern""" in { + val operator = DeepScan + parser.parse(operator.toText).shouldMatchTo(Success(operator)) + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "DotNotatedChild.MatchingName" should { + val parser = operator.dotNotatedChildMatchingName + val pattern = "^\\.[a-zA-Z0-9]+$".r + + s"""accept $pattern""" in { + forAll { operator: DotNotatedChild.MatchingName => + parser.parse(operator.toText).shouldMatchTo(Success(operator)) + } + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "DotNotatedChild.MatchingWildcard" should { + val parser = operator.dotNotatedChildMatchingWildcard + val pattern = "^\\.\\*$".r + + s"""accept $pattern""" in { + val operator = DotNotatedChild.MatchingWildcard + parser.parse(operator.toText).shouldMatchTo(Success(operator)) + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "DotNotatedChild" should { + val parser = operator.dotNotatedChild + val pattern = "^\\.([a-zA-Z0-9]+|\\*)$".r + + s"""accept $pattern""" in { + forAll { operator: DotNotatedChild => + parser.parse(operator.toText).shouldMatchTo(Success(operator)) + } + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "BracketNotatedChild" should { + val parser = operator.bracketNotatedChildren + val pattern = "^\\[('[a-zA-Z0-9]+'|\\*)\\w*(,\\w*'[a-zA-Z0-9]+'|\\*)\\]$".r + + s"""accept $pattern""" in { + forAll { operator: BracketNotatedChildren => + parser.parse(operator.toText).shouldMatchTo(Success(operator)) + } + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "ArrayIndexes" should { + val parser = operator.arrayIndexes + val pattern = "^\\[[0-9]+\\w*(,\\w*[0-9]+)\\]$".r + + s"""accept $pattern""" in { + forAll { operator: ArrayIndexes => + parser.parse(operator.toText).shouldMatchTo(Success(operator)) + } + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "ArraySlice.LeftBounded" should { + val parser = operator.arraySliceLeftBounded + val pattern = "^\\[\\d+:\\]$".r + + s"""accept $pattern""" in { + forAll { operator: ArraySlice.LeftBounded => + parser.parse(operator.toText).shouldMatchTo(Success(operator)) + } + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "ArraySlice.RightBounded" should { + val parser = operator.arraySliceRightBounded + val pattern = "^\\[:\\d+\\]$".r + + s"""accept $pattern""" in { + forAll { operator: ArraySlice.RightBounded => + parser.parse(operator.toText).shouldMatchTo(Success(operator)) + } + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "ArraySlice.Bounded" should { + val parser = operator.arraySliceBounded + val pattern = "^\\[\\d+:\\d+\\]$".r + + s"""accept $pattern""" in { + forAll { operator: ArraySlice.Bounded => + parser.parse(operator.toText).shouldMatchTo(Success(operator)) + } + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "ArraySlice" should { + val parser = operator.arraySlice + val pattern = "^(\\[\\d+:\\]|\\[:\\d+\\]|\\[\\d+:\\d+\\])$".r + + s"""accept $pattern""" in { + forAll { operator: ArraySlice => + parser.parse(operator.toText).shouldMatchTo(Success(operator)) + } + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "Operator" should { + val parser = operator.any + + """accept any operator""" in { + forAll { operator: Operator => + parser.parse(operator.toText).shouldMatchTo(Success(operator)) + } + } + } + +} diff --git a/src/test/scala/ahlers/tree/path/parsers/TermSpec.scala b/src/test/scala/ahlers/tree/path/parsers/TermSpec.scala new file mode 100644 index 0000000..2206215 --- /dev/null +++ b/src/test/scala/ahlers/tree/path/parsers/TermSpec.scala @@ -0,0 +1,54 @@ +package ahlers.tree.path.parsers + +import ahlers.tree.path.terms.Index +import ahlers.tree.path.terms.Name +import ahlers.tree.path.terms.diffx.instances._ +import ahlers.tree.path.terms.scalacheck.instances._ +import com.softwaremill.diffx.scalatest.DiffShouldMatcher.convertToAnyShouldMatcher +import org.scalatest.matchers.should.Matchers._ +import org.scalatest.wordspec.AnyWordSpec +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks._ +import parsley.Failure +import parsley.Success +import parsley.diffx.instances._ + +class TermSpec extends AnyWordSpec { + + "Index" should { + val parser = term.index + val pattern = "^[0-9]+$".r + + s"""accept $pattern""" in { + forAll { index: Index => + parser.parse(index.toText).shouldMatchTo(Success(index)) + } + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } + + "Name" should { + val parser = term.name + val pattern = "^[a-zA-Z0-9]+$".r + + s"""accept $pattern""" in { + forAll { name: Name => + parser.parse(name.toText).shouldMatchTo(Success(name)) + } + } + + "reject else" in { + forAll { input: String => + whenever(!pattern.matches(input)) { + parser.parse(input).shouldBe(a[Failure[_]]) + } + } + } + } +} diff --git a/src/test/scala/ahlers/tree/path/terms/diffx/instances.scala b/src/test/scala/ahlers/tree/path/terms/diffx/instances.scala new file mode 100644 index 0000000..8240a97 --- /dev/null +++ b/src/test/scala/ahlers/tree/path/terms/diffx/instances.scala @@ -0,0 +1,12 @@ +package ahlers.tree.path.terms.diffx + +import ahlers.tree.path.terms.Index +import ahlers.tree.path.terms.Name +import com.softwaremill.diffx.Diff + +object instances { + + implicit val diffIndex: Diff[Index] = Diff.derived + implicit val diffName: Diff[Name] = Diff.derived + +} diff --git a/src/test/scala/ahlers/tree/path/terms/scalacheck/instances.scala b/src/test/scala/ahlers/tree/path/terms/scalacheck/instances.scala new file mode 100644 index 0000000..4914b26 --- /dev/null +++ b/src/test/scala/ahlers/tree/path/terms/scalacheck/instances.scala @@ -0,0 +1,17 @@ +package ahlers.tree.path.terms.scalacheck + +import ahlers.tree.path.terms.Index +import ahlers.tree.path.terms.Name +import org.scalacheck.Arbitrary +import org.scalacheck.Gen + +object instances { + + implicit val arbIndex: Arbitrary[Index] = Arbitrary(Gen.posNum[Int].map(Index)) + + implicit val arbName: Arbitrary[Name] = Arbitrary(for { + head <- Gen.alphaNumChar + tail <- Gen.alphaNumStr + } yield Name(s"$head$tail")) + +} diff --git a/src/test/scala/parsley/diffx/instances.scala b/src/test/scala/parsley/diffx/instances.scala new file mode 100644 index 0000000..ee494d0 --- /dev/null +++ b/src/test/scala/parsley/diffx/instances.scala @@ -0,0 +1,20 @@ +package parsley.diffx + +import com.softwaremill.diffx.Diff +import com.softwaremill.diffx.DiffResultValue +import parsley.Failure +import parsley.Result +import parsley.Success + +object instances { + + implicit def diffSuccess[A: Diff]: Diff[Success[A]] = Diff.derived + implicit def diffFailure[E: Diff]: Diff[Failure[E]] = Diff[E].contramap(_.msg) + + implicit def diffResult[E: Diff, A: Diff]: Diff[Result[E, A]] = { + case (left: Success[A], right: Success[A], context) => Diff[Success[A]].apply(left, right, context) + case (left: Failure[E], right: Failure[E], context) => Diff[Failure[E]].apply(left, right, context) + case (left, right, _) => DiffResultValue(left, right) + } + +}