diff --git a/README.md b/README.md index 7567c2a..b7ac8f5 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,6 @@ This library is not production ready yet. There is a lot of work to do to comple - Reach a good code coverage with the tests (using munit) - Improve documentation -Contributions are more than welcome! 💪 Given `Foo` class ```scala @@ -79,7 +78,7 @@ val node: XmlNode = XmlNode("Foo") val result: Modifier.Result[XmlNode] = Root .modify(_.withText("NEW")) .apply(node) -// result: Either[ModifierFailure, XmlNode] = Right( +// result: Modifier.Result[XmlNode] = Right( // value = NEW // ) -``` +``` \ No newline at end of file diff --git a/build.sbt b/build.sbt index bf684a4..4526445 100644 --- a/build.sbt +++ b/build.sbt @@ -210,4 +210,4 @@ def scalacSettings(scalaVersion: String): Seq[String] = } //=============================== ALIASES =============================== -addCommandAlias("check", ";clean;test") +addCommandAlias("check", "scalafmtAll;clean;coverage;test;coverageAggregate") diff --git a/core/example/untitled.sc b/core/example/untitled.sc index 7082c3d..e708ee8 100644 --- a/core/example/untitled.sc +++ b/core/example/untitled.sc @@ -1,52 +1,73 @@ import cats.xml.codec.Decoder import cats.xml.XmlNode +import cats.xml.cursor.NodeCursor.Root import cats.xml.implicits._ +import cats.xml.modifier.Modifier //// //////############### PARSING from NODESEQ ############### ////val n1: XmlNode = TEST //// //////############### CURSOR ############### -////val node: XmlNode = -//// -//// -//// -//// -//// -//// -//// -//// -//// -//// -//// LOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREA LOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREA -//// -//// -//// -//// -//// -//// -//// -//// -//// -//// +val node: XmlNode = + xml""" + + + + + + + + + + LOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREA LOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREA + + + + + + + + + + """.get + + + //// ////node.findDeepChild("roar") ////Xml.toNodeSeq(node) //// -////val result1: CursorResult[Int] = -//// Root -//// .down("foo") -//// .down("bar") -//// .down("root") -//// .down("foo") -//// .down("bar") -//// .down("root") -//// .down("foo") -//// .down("bar") -//// .down("roar") -//// .attr("a") -//// .as[Int] -//// .focus(node) +//val result1: FreeCursor.Result[Int] = +// Root +// .down("foo") +// .down("bar") +// .down("root") +// .down("foo") +// .down("bar") +// .down("root") +// .down("foo") +// .down("bar") +// .down("roar") +// .attr("a") +// .as[Int] +// .focus(node) + +val result1: Modifier.Result[XmlNode] = + Root + .down("foo") + .down("bar") + .down("root") + .down("foo") + .down("bar") + .down("root") + .down("foo") + .down("bar") + .down("roar") + .attr("a") + .modify(_ => "TEST") + .apply(node) + //// //// ////val result1: CursorResult[Int] = diff --git a/core/src/main/scala/cats/xml/XmlAttribute.scala b/core/src/main/scala/cats/xml/XmlAttribute.scala index c7abf83..84a20dd 100644 --- a/core/src/main/scala/cats/xml/XmlAttribute.scala +++ b/core/src/main/scala/cats/xml/XmlAttribute.scala @@ -1,10 +1,16 @@ package cats.xml -import cats.{Eq, Show} +import cats.{Endo, Eq, Show} import cats.xml.codec.DataEncoder case class XmlAttribute(key: String, value: XmlData) extends Xml with Serializable { + def map[T: DataEncoder](f: XmlData => T): XmlAttribute = + map(value => DataEncoder[T].encode(f(value))) + + def map(f: Endo[XmlData]): XmlAttribute = + XmlAttribute(key, f(value)) + override def toString: String = XmlAttribute.stringify(this) override def equals(obj: Any): Boolean = diff --git a/core/src/main/scala/cats/xml/XmlNode.scala b/core/src/main/scala/cats/xml/XmlNode.scala index d929adf..07c2f34 100644 --- a/core/src/main/scala/cats/xml/XmlNode.scala +++ b/core/src/main/scala/cats/xml/XmlNode.scala @@ -35,9 +35,6 @@ class XmlNode private ( def withAttributes(attr: XmlAttribute, attrs: XmlAttribute*): XmlNode = updateAttrs(_ => attr +: attrs) - def withAttributesMap(values: Map[String, String]): XmlNode = - updateAttrs(_ => XmlAttribute.fromMap(values)) - def prependAttr(newAttr: XmlAttribute): XmlNode = updateAttrs(ls => newAttr +: ls) @@ -50,6 +47,9 @@ class XmlNode private ( def updateAttrs(f: Endo[Seq[XmlAttribute]]): XmlNode = copy(attributes = f(attributes)) + def updateAttr(key: String)(f: Endo[XmlAttribute]): XmlNode = + updateAttrs(_.map(attr => if (attr.key == key) f(attr) else attr)) + // ------ CONTENT ------ val hasChildren: Boolean = children.nonEmpty @@ -107,18 +107,18 @@ class XmlNode private ( def findDeepChild(thatLabel: String): Option[XmlNode] = deepSubNodes.find(_.label == thatLabel) - def deepSubNodes: List[XmlNode] = { + def deepSubNodes: LazyList[XmlNode] = { @tailrec - def rec(left: List[XmlNode], acc: List[XmlNode]): List[XmlNode] = + def rec(left: List[XmlNode], acc: LazyList[XmlNode]): LazyList[XmlNode] = left match { case Nil => acc case head :: tail => rec(tail, acc ++ head.deepSubNodes) } content.children match { - case Nil => Nil - case currentNodeChildren => rec(currentNodeChildren, Nil) + case Nil => LazyList.empty + case currentNodeChildren => rec(currentNodeChildren, LazyList.empty) } } diff --git a/core/src/main/scala/cats/xml/codec/Codec.scala b/core/src/main/scala/cats/xml/codec/Codec.scala index 0d91522..3e9e729 100644 --- a/core/src/main/scala/cats/xml/codec/Codec.scala +++ b/core/src/main/scala/cats/xml/codec/Codec.scala @@ -1,7 +1,6 @@ package cats.xml.codec import cats.xml.Xml -import cats.xml.cursor.Cursor /** Isomorphism */ @@ -10,9 +9,6 @@ case class Codec[T] private ( encoder: Encoder[T] ) { - def decodeCursorResult(cursorResult: Cursor.Result[Xml]): Decoder.Result[T] = - decoder.decodeCursorResult(cursorResult) - def decode(xml: Xml): Decoder.Result[T] = decoder.decode(xml) diff --git a/core/src/main/scala/cats/xml/codec/Encoder.scala b/core/src/main/scala/cats/xml/codec/Encoder.scala index c809e66..c29bbc3 100644 --- a/core/src/main/scala/cats/xml/codec/Encoder.scala +++ b/core/src/main/scala/cats/xml/codec/Encoder.scala @@ -49,8 +49,9 @@ object DataEncoder extends DataEncoderPrimitivesInstances { } private[xml] trait DataEncoderPrimitivesInstances { - implicit val encoderUnit: DataEncoder[Unit] = DataEncoder.of(_ => XmlNull) - implicit val encoderString: DataEncoder[String] = DataEncoder.of(XmlString(_)) + implicit val encoderXmlData: DataEncoder[XmlData] = DataEncoder.of(identity) + implicit val encoderUnit: DataEncoder[Unit] = DataEncoder.of(_ => XmlNull) + implicit val encoderString: DataEncoder[String] = DataEncoder.of(XmlString(_)) implicit val encoderBoolean: DataEncoder[Boolean] = encoderString.contramap { case true => "true" case false => "false" diff --git a/core/src/main/scala/cats/xml/cursor/AttrCursor.scala b/core/src/main/scala/cats/xml/cursor/AttrCursor.scala index 5653c1a..53ec82f 100644 --- a/core/src/main/scala/cats/xml/cursor/AttrCursor.scala +++ b/core/src/main/scala/cats/xml/cursor/AttrCursor.scala @@ -1,19 +1,32 @@ package cats.xml.cursor import cats.Show -import cats.xml.{XmlAttribute, XmlNode} +import cats.xml.{XmlAttribute, XmlData, XmlNode} +import cats.xml.codec.DataEncoder import cats.xml.cursor.AttrCursor.Op import cats.xml.cursor.Cursor.CursorOp +import cats.xml.modifier.{Modifier, ModifierFailure} /** Horizontal cursor for node attributes */ class AttrCursor(protected val vCursor: NodeCursor, op: AttrCursor.Op) - extends HCursor[XmlAttribute, NodeCursor, AttrCursor] { + extends HCursor[XmlAttribute, NodeCursor, AttrCursor] { $this => import cats.implicits.* lazy val path: String = s"${vCursor.path}$op" + // modify + def modify[T: DataEncoder](f: XmlData => T): Modifier[XmlNode] = + Modifier(node => + $this.focus(node) match { + case Right(attr) => + vCursor.modify(_.updateAttr(attr.key)(_ => attr.map(f)))(node) + case Left(failure) => + ModifierFailure.CursorFailed(failure).asLeft + } + ) + // focus override def focus(xml: XmlNode): Cursor.Result[XmlAttribute] = { diff --git a/core/src/main/scala/cats/xml/cursor/NodeCursor.scala b/core/src/main/scala/cats/xml/cursor/NodeCursor.scala index 97957ef..3b5169c 100644 --- a/core/src/main/scala/cats/xml/cursor/NodeCursor.scala +++ b/core/src/main/scala/cats/xml/cursor/NodeCursor.scala @@ -3,7 +3,7 @@ package cats.xml.cursor import cats.{Endo, Show} import cats.xml.XmlNode import cats.xml.cursor.Cursor.CursorOp -import cats.xml.modifier.Modifier +import cats.xml.modifier.{Modifier, ModifierFailure} import scala.annotation.tailrec import scala.collection.mutable.ListBuffer @@ -17,7 +17,16 @@ sealed trait NodeCursor extends Dynamic with VCursor[XmlNode, NodeCursor] { override lazy val path: String = CursorOp.buildOpsPath(history) def modify(modifier: Endo[XmlNode]): Modifier[XmlNode] = - Modifier.fromNodeCursor(this, modifier) + Modifier(node => { + val nodeClone = node.copy() + focus(nodeClone) match { + case Right(focus) => + focus.mute(modifier) + Right(focus) + case Left(failure) => + Left(ModifierFailure.CursorFailed(failure)) + } + }) // node def selectDynamic(nodeName: String): NodeCursor = diff --git a/core/src/main/scala/cats/xml/cursor/TextCursor.scala b/core/src/main/scala/cats/xml/cursor/TextCursor.scala index 4d32cfb..c79a8e6 100644 --- a/core/src/main/scala/cats/xml/cursor/TextCursor.scala +++ b/core/src/main/scala/cats/xml/cursor/TextCursor.scala @@ -2,17 +2,27 @@ package cats.xml.cursor import cats.xml.{XmlData, XmlNode} import cats.xml.codec.DataEncoder -import cats.xml.modifier.Modifier +import cats.xml.modifier.{Modifier, ModifierFailure} /** Vertical cursor for node Text */ class TextCursor(protected[xml] val lastCursor: NodeCursor) extends VCursor[XmlData, NodeCursor] { + $this => + + import cats.implicits.* override lazy val path: String = lastCursor.path // modify - def modify[T: DataEncoder](f: Option[XmlData] => T): Modifier[XmlNode] = - Modifier.fromTextCursor(this, f) + def modify[T: DataEncoder](f: XmlData => T): Modifier[XmlNode] = + Modifier(node => + $this.focus(node) match { + case Right(textValue) => + lastCursor.modify(_.withText(f(textValue)))(node) + case Left(failure) => + ModifierFailure.CursorFailed(failure).asLeft + } + ) // focus override def focus(node: XmlNode): Cursor.Result[XmlData] = diff --git a/core/src/main/scala/cats/xml/modifier/Modifier.scala b/core/src/main/scala/cats/xml/modifier/Modifier.scala index 441cd7d..16b825e 100644 --- a/core/src/main/scala/cats/xml/modifier/Modifier.scala +++ b/core/src/main/scala/cats/xml/modifier/Modifier.scala @@ -1,11 +1,8 @@ package cats.xml.modifier -import cats.{Endo, Monoid} -import cats.xml.{XmlData, XmlNode} -import cats.xml.codec.DataEncoder -import cats.xml.cursor.{NodeCursor, TextCursor} +import cats.Monoid -/** Create a modified copy of input [[XmlNode]] +/** Create a modified copy of input 'XmlNode' */ trait Modifier[T] { $this => @@ -36,27 +33,6 @@ object Modifier extends ModifierInstances { type Result[+T] = Either[ModifierFailure, T] - def fromNodeCursor( - cursor: NodeCursor, - modifier: Endo[XmlNode] - ): Modifier[XmlNode] = - Modifier(node => { - val nodeClone = node.copy() - cursor.focus(nodeClone) match { - case Right(focus) => - focus.mute(modifier) - focus.asRight - case Left(failure) => - ModifierFailure.CursorFailed(failure).asLeft - } - }) - - def fromTextCursor[T: DataEncoder]( - cursor: TextCursor, - modifier: Option[XmlData] => T - ): Modifier[XmlNode] = - cursor.lastCursor.modify(n => n.withText(modifier(n.text))) - def apply[T]( f: T => Modifier.Result[T] ): Modifier[T] = (input: T) => f(input) diff --git a/docs/compiled/README.md b/docs/compiled/README.md index f427f88..b7ac8f5 100644 --- a/docs/compiled/README.md +++ b/docs/compiled/README.md @@ -78,7 +78,7 @@ val node: XmlNode = XmlNode("Foo") val result: Modifier.Result[XmlNode] = Root .modify(_.withText("NEW")) .apply(node) -// result: Either[ModifierFailure, XmlNode] = Right( +// result: Modifier.Result[XmlNode] = Right( // value = NEW // ) ``` \ No newline at end of file