Skip to content

Commit

Permalink
Merge pull request #30 from geirolz/macros
Browse files Browse the repository at this point in the history
Add Macros for Scala 2
  • Loading branch information
geirolz authored Aug 10, 2022
2 parents 25e59e4 + 9b794e4 commit a1315d6
Show file tree
Hide file tree
Showing 58 changed files with 1,174 additions and 496 deletions.
16 changes: 9 additions & 7 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
include:
- scala: 2.13.8
name: Scala2
test-tasks: coverage test coverageReport
test-tasks: coverage test coverageReport generate-docs
- scala: 3.1.3
name: Scala3
test-tasks: test # scoverage doesn’t support Scala 3
Expand All @@ -39,10 +39,11 @@ jobs:
- uses: actions/checkout@v3

#----------- JDK -----------
- name: Set up JDK 11
uses: actions/setup-java@v1
- name: Setup JDK
uses: actions/setup-java@v3
with:
java-version: 11
distribution: "liberica"
java-version: 17

#----------- CACHE -----------
- name: Cache SBT
Expand Down Expand Up @@ -71,10 +72,11 @@ jobs:
- uses: actions/checkout@v3

#----------- JDK -----------
- name: Set up JDK 11
uses: actions/setup-java@v1
- name: Setup JDK
uses: actions/setup-java@v3
with:
java-version: 11
distribution: "liberica"
java-version: 17

#----------- CACHE -----------
- name: Cache SBT
Expand Down
1 change: 1 addition & 0 deletions .java-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
17.0
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,22 @@ libraryDependencies += "com.github.geirolz" %% "cats-xml" % "0.0.3"
This library is not production ready yet. There is a lot of work to do to complete it:
- ~~There are some performance issues loading xml from strings or files due the fact that there is a double
conversion, We need to parse bytes directly into `cats-xml` classes.~~
- Creates macro to derive `Decoder` and `Encoder`. This is not straightforward, distinguish between a Node and an Attribute ca
~~- Creates macro to derive `Decoder` and `Encoder`. This is not straightforward, distinguish between a Node and an Attribute ca
can be done in some way thinking about attributes with primitives and value classes BUT distinguish between a Node/Attribute and Text
is hard, probably an annotation or a custom Decoder/Encoder is required.
is hard, probably an annotation or a custom Decoder/Encoder is required.~~
- Reach a good code coverage with the tests (using munit)
- Improve documentation
- Literal macros to check XML strings at compile time

Contributions are more than welcome 💪

Given `Foo` class
## Modules
- [Effect](docs/compiled/effect.md)
- [Generic](docs/compiled/generic.md)
- [Standard](docs/compiled/standard.md)

## Example
Given
```scala
case class Foo(
foo: Option[String],
Expand Down Expand Up @@ -61,7 +68,7 @@ val encoder: Encoder[Foo] = Encoder.of(t =>
XmlNode("Foo")
.withAttributes(
"foo" := t.foo.getOrElse("ERROR"),
"bar" := t.bar,
"bar" := t.bar
)
.withText(t.text)
)
Expand All @@ -86,4 +93,4 @@ val result: Modifier.Result[XmlNode] = Root
// result: Modifier.Result[XmlNode] = Right(
// value = <Foo name="Foo" age="10">NEW</Foo>
// )
```
```
58 changes: 30 additions & 28 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import sbt.project

val prjName = "cats-xml"
val org = "com.github.geirolz"
lazy val scala213 = "2.13.8"
lazy val scala31 = "3.1.3"
lazy val supportedScalaVersions = List(scala213, scala31)
lazy val prjName = "cats-xml"
lazy val org = "com.github.geirolz"

//## global project to no publish ##
val copyReadMe = taskKey[Unit]("Copy generated README to main folder.")
lazy val catsxml: Project = project
lazy val `cats-xml`: Project = project
.in(file("."))
.settings(
inThisBuild(
Expand All @@ -27,23 +30,21 @@ lazy val catsxml: Project = project
.settings(baseSettings)
.settings(noPublishSettings)
.settings(
name := prjName,
description := "A purely functional XML library",
organization := org,

// docs
copyReadMe := IO.copyFile(file("docs/compiled/README.md"), file("README.md")),
(Compile / compile) := (Compile / compile)
.dependsOn(copyReadMe.toTask.dependsOn((docs / mdoc).toTask("")))
.value
name := prjName,
description := "A purely functional XML library",
organization := org,
crossScalaVersions := Nil
)
.settings(
copyReadMe := IO.copyFile(file("docs/compiled/README.md"), file("README.md"))
)
.aggregate(docs, core, metrics, utils, effect, scalaxml)

lazy val docs: Project =
project
.in(file("docs"))
.enablePlugins(MdocPlugin)
.dependsOn(core)
.dependsOn(core, effect, generic, scalaxml)
.settings(
baseSettings,
noPublishSettings,
Expand All @@ -60,39 +61,39 @@ lazy val docs: Project =
)
)

lazy val utils: Project =
buildModule(
prjModuleName = "utils",
toPublish = false,
folder = "."
).settings(
libraryDependencies ++= ProjectDependencies.Utils.dedicated
)

lazy val core: Project =
buildModule(
prjModuleName = "core",
toPublish = true,
folder = "."
)
).dependsOn(utils)

lazy val metrics: Project =
buildModule(
prjModuleName = "metrics",
toPublish = false,
folder = "."
).dependsOn(core, effect, scalaxml, generic)
).dependsOn(core, utils, effect, scalaxml, generic)
.settings(
libraryDependencies ++= ProjectDependencies.Metrics.dedicated
)

lazy val utils: Project =
buildModule(
prjModuleName = "utils",
toPublish = false,
folder = "."
).settings(
libraryDependencies ++= ProjectDependencies.Utils.dedicated
)

// modules
lazy val effect: Project =
buildModule(
prjModuleName = "effect",
toPublish = true,
folder = "modules"
).dependsOn(core)
).dependsOn(core, utils)
.settings(
libraryDependencies ++= ProjectDependencies.Effect.dedicated
)
Expand All @@ -102,7 +103,7 @@ lazy val scalaxml: Project =
prjModuleName = "standard",
toPublish = true,
folder = "modules"
).dependsOn(core)
).dependsOn(core, utils)
.settings(
libraryDependencies ++= ProjectDependencies.Standard.dedicated
)
Expand Down Expand Up @@ -152,8 +153,8 @@ lazy val noPublishSettings: Seq[Def.Setting[_]] = Seq(

lazy val baseSettings: Seq[Def.Setting[_]] = Seq(
// scala
crossScalaVersions := List("2.13.8", "3.1.3"),
scalaVersion := crossScalaVersions.value.head,
crossScalaVersions := supportedScalaVersions,
scalaVersion := supportedScalaVersions.head,
scalacOptions ++= scalacSettings(scalaVersion.value),
// dependencies
resolvers ++= ProjectResolvers.all,
Expand Down Expand Up @@ -231,3 +232,4 @@ def scalacSettings(scalaVersion: String): Seq[String] =

//=============================== ALIASES ===============================
addCommandAlias("check", "scalafmtAll;clean;coverage;test;coverageAggregate")
addCommandAlias("generate-docs", "mdoc;copyReadMe;")
20 changes: 8 additions & 12 deletions core/src/main/scala/cats/xml/Xml.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package cats.xml

import cats.{MonadThrow, Show}
import cats.{xml, Show}
import cats.xml.codec.Decoder
import cats.xml.Xml.XmlNull

Expand Down Expand Up @@ -42,11 +42,7 @@ object Xml {
case object XmlNull extends Xml with XmlData

final lazy val Null: Xml & XmlData = XmlNull

def fromString[F[_]: MonadThrow](xmlString: String)(implicit
parser: XmlParser[F]
): F[XmlNode] =
parser.parseString(xmlString)
final lazy val Data: XmlData.type = xml.XmlData
}

sealed trait XmlData extends Xml with Serializable {
Expand All @@ -65,12 +61,6 @@ sealed trait XmlData extends Xml with Serializable {
}
object XmlData {

case class XmlString(value: String) extends XmlData
case class XmlNumber[T <: Number](value: T) extends XmlData
case class XmlArray[T <: XmlData](value: Array[T]) extends XmlData
case class XmlByte(value: Byte) extends XmlData
case class XmlBool(value: Boolean) extends XmlData

lazy val True: XmlData = fromBoolean(true)
lazy val False: XmlData = fromBoolean(false)
lazy val empty: XmlData = fromString("")
Expand All @@ -83,6 +73,12 @@ object XmlData {
def fromByte(value: Byte): XmlData = XmlByte(value)
def fromBoolean(value: Boolean): XmlData = XmlBool(value)

private[xml] case class XmlString(value: String) extends XmlData
private[xml] case class XmlNumber[T <: Number](value: T) extends XmlData
private[xml] case class XmlArray[T <: XmlData](value: Array[T]) extends XmlData
private[xml] case class XmlByte(value: Byte) extends XmlData
private[xml] case class XmlBool(value: Boolean) extends XmlData

// TODO: TO CHECK EQ TO DECODE STRING
implicit val showXmlData: Show[XmlData] = {
case XmlNull => ""
Expand Down
36 changes: 1 addition & 35 deletions core/src/main/scala/cats/xml/XmlPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cats.xml

import cats.xml.Xml.XmlNull
import cats.xml.XmlNode.XmlNodeGroup
import cats.xml.utils.format.Indentator

import scala.annotation.{tailrec, unused}
import scala.collection.mutable
Expand All @@ -22,41 +23,6 @@ object XmlPrinter {
implicit val default: Config = Config()
}

case class Indentator(char: Char, size: Int, depth: Int, indentation: String) {

private val unit = Indentator.buildString(char, size, 1)

def forward: Indentator = this.copy(
depth = depth + 1,
indentation = indentation + unit
)

def backward: Indentator = this.copy(
depth = depth - 1,
indentation = if (indentation.nonEmpty) indentation.drop(1) else indentation
)
}
object Indentator {

def root(char: Char, size: Int): Indentator =
build(
char = char,
size = size,
depth = 0
)

def build(char: Char, size: Int, depth: Int): Indentator =
Indentator(
char = char,
size = size,
depth = depth,
indentation = (0 until size * depth).map(_ => char).mkString
)

def buildString(char: Char, size: Int, depth: Int): String =
(0 until size * depth).map(_ => char).mkString
}

// --------------------------------------------------------------//
def stringify(xml: Xml): String =
prettyString(xml = xml)(
Expand Down
6 changes: 3 additions & 3 deletions core/src/main/scala/cats/xml/codec/Decoder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ sealed private[xml] trait DecoderDataInstances {
implicit val decodeLong: Decoder[Long] = decodeString.emapTry(s => Try(s.toLong))
implicit val decodeFloat: Decoder[Float] = decodeString.emapTry(s => Try(s.toFloat))
implicit val decodeDouble: Decoder[Double] = decodeString.emapTry(s => Try(s.toDouble))
implicit val decodeBigDecimal: Decoder[BigDecimal] =
decodeString.emapTry(s => Try(BigDecimal(s)))
implicit val decodeBigDecimal: Decoder[BigDecimal] = decodeString.emapTry(s => Try(BigDecimal(s)))
implicit val decodeBigInt: Decoder[BigInt] = decodeString.emapTry(s => Try(BigInt(s)))
}

sealed private[xml] trait DecoderLifterInstances { this: DecoderDataInstances =>
Expand Down Expand Up @@ -197,7 +197,7 @@ sealed private[xml] trait DecoderLifterInstances { this: DecoderDataInstances =>
.flatMapF(str => {
str
.split(",")
.map(s => Decoder[T].decode(XmlString(s)))
.map(s => Decoder[T].decode(Xml.Data.fromString(s)))
.toVector
.sequence
.map(_.to(f))
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/cats/xml/codec/DecoderFailure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import cats.data.NonEmptyList
import cats.xml.Xml
import cats.xml.codec.DecoderFailure.DecoderFailureException
import cats.xml.cursor.CursorFailure
import cats.xml.utils.ErrorKeeper
import cats.xml.utils.UnderlyingThrowable

sealed trait DecoderFailure {

Expand All @@ -17,7 +17,7 @@ object DecoderFailure extends DecoderFailureSyntax {
case class NoTextAvailable(subject: Xml) extends DecoderFailure
case class CursorFailed(failure: CursorFailure) extends DecoderFailure
case class CoproductNoMatch[+T](actual: Any, coproductValues: Seq[T]) extends DecoderFailure
case class Error(error: Throwable) extends DecoderFailure with ErrorKeeper
case class Error(error: Throwable) extends DecoderFailure with UnderlyingThrowable
case class Custom(message: String) extends DecoderFailure

case class DecoderFailureException(failures: NonEmptyList[DecoderFailure])
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/cats/xml/codec/Encoder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cats.xml.codec

import cats.xml.{Xml, XmlData}
import cats.Contravariant
import cats.xml.XmlData.XmlString

// T => XML
trait Encoder[-T] {
Expand Down Expand Up @@ -55,7 +54,7 @@ private[xml] trait EncoderPrimitivesInstances {

implicit val encoderXmlData: DataEncoder[XmlData] = DataEncoder.of(identity)
implicit val encoderUnit: DataEncoder[Unit] = DataEncoder.of(_ => Xml.Null)
implicit val encoderString: DataEncoder[String] = DataEncoder.of(XmlString(_))
implicit val encoderString: DataEncoder[String] = DataEncoder.of(Xml.Data.fromString(_))
implicit val encoderBoolean: DataEncoder[Boolean] = encoderString.contramap {
case true => "true"
case false => "false"
Expand All @@ -66,6 +65,7 @@ private[xml] trait EncoderPrimitivesInstances {
implicit val encoderFloat: DataEncoder[Float] = encoderString.contramap(_.toString)
implicit val encoderDouble: DataEncoder[Double] = encoderString.contramap(_.toString)
implicit val encoderBigDecimal: DataEncoder[BigDecimal] = encoderString.contramap(_.toString)
implicit val encoderBigInt: DataEncoder[BigInt] = encoderString.contramap(_.toString)
}

// #################### DATA ENCODER ####################
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/cats/xml/cursor/CursorFailure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import cats.{Eq, Show}
import cats.data.NonEmptyList
import cats.xml.codec.DecoderFailure
import cats.xml.cursor.CursorFailure.CursorFailureException
import cats.xml.utils.ErrorKeeper
import cats.xml.utils.UnderlyingThrowable
import cats.xml.XmlNode

/** A coproduct ADT to represent the `Cursor` possible failures.
Expand Down Expand Up @@ -52,7 +52,7 @@ object CursorFailure {
case class LeftBoundLimitAttr(path: String, lastKey: String) extends FailedAttribute with Missing
case class RightBoundLimitAttr(path: String, lastKey: String) extends FailedAttribute with Missing
case class Custom(message: String) extends CursorFailure
case class Error(error: Throwable) extends CursorFailure with ErrorKeeper
case class Error(error: Throwable) extends CursorFailure with UnderlyingThrowable

case class CursorFailureException(failure: CursorFailure)
extends RuntimeException(s"Cursor failure: $failure")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ object JavaConverters extends JavaConvertersSyntax {
}
}

trait JavaConvertersSyntax {
private[xml] sealed trait JavaConvertersSyntax {

implicit class JDocumentOps(doc: JDocument) {
def asCatsXml: XmlNode = JavaConverters.nodeFromJavaDocument(doc)
Expand Down
Loading

0 comments on commit a1315d6

Please sign in to comment.