Skip to content

Commit

Permalink
Add AttrCursor modify
Browse files Browse the repository at this point in the history
  • Loading branch information
david.geirola committed Apr 8, 2022
1 parent 41b72b6 commit 084c21c
Show file tree
Hide file tree
Showing 12 changed files with 119 additions and 88 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = <Foo name="Foo" age="10">NEW</Foo>
// )
```
```
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,4 @@ def scalacSettings(scalaVersion: String): Seq[String] =
}

//=============================== ALIASES ===============================
addCommandAlias("check", ";clean;test")
addCommandAlias("check", "scalafmtAll;clean;coverage;test;coverageAggregate")
93 changes: 57 additions & 36 deletions core/example/untitled.sc
Original file line number Diff line number Diff line change
@@ -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 = <root>TEST</root>
////
//////############### CURSOR ###############
////val node: XmlNode =
//// <root>
//// <foo>
//// <bar>
//// <root>
//// <foo>
//// <bar>
//// <root>
//// <foo>
//// <bar>
//// <roar a="1" b="2" c="3">
//// LOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREA LOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREA
//// </roar>
//// </bar>
//// </foo>
//// </root>
//// </bar>
//// </foo>
//// </root>
//// </bar>
//// </foo>
//// </root>
val node: XmlNode =
xml"""<root>
<foo>
<bar>
<root>
<foo>
<bar>
<root>
<foo>
<bar>
<roar a="1" b="2" c="3">
LOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREA LOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREALOREA
</roar>
</bar>
</foo>
</root>
</bar>
</foo>
</root>
</bar>
</foo>
</root>""".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] =
Expand Down
8 changes: 7 additions & 1 deletion core/src/main/scala/cats/xml/XmlAttribute.scala
Original file line number Diff line number Diff line change
@@ -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 =
Expand Down
14 changes: 7 additions & 7 deletions core/src/main/scala/cats/xml/XmlNode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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

Expand Down Expand Up @@ -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)
}
}

Expand Down
4 changes: 0 additions & 4 deletions core/src/main/scala/cats/xml/codec/Codec.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cats.xml.codec

import cats.xml.Xml
import cats.xml.cursor.Cursor

/** Isomorphism
*/
Expand All @@ -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)

Expand Down
5 changes: 3 additions & 2 deletions core/src/main/scala/cats/xml/codec/Encoder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
17 changes: 15 additions & 2 deletions core/src/main/scala/cats/xml/cursor/AttrCursor.scala
Original file line number Diff line number Diff line change
@@ -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] = {

Expand Down
13 changes: 11 additions & 2 deletions core/src/main/scala/cats/xml/cursor/NodeCursor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 =
Expand Down
16 changes: 13 additions & 3 deletions core/src/main/scala/cats/xml/cursor/TextCursor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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] =
Expand Down
28 changes: 2 additions & 26 deletions core/src/main/scala/cats/xml/modifier/Modifier.scala
Original file line number Diff line number Diff line change
@@ -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 =>

Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion docs/compiled/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <Foo name="Foo" age="10">NEW</Foo>
// )
```

0 comments on commit 084c21c

Please sign in to comment.