diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5805311..bf51aec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,14 +29,16 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - scala: [2.12, 2.13, 3] + scala: [2.13, 3] java: [temurin@11, temurin@17] - project: [rootJVM] + project: [rootJS, rootJVM, rootNative] exclude: - - scala: 2.12 - java: temurin@17 - scala: 3 java: temurin@17 + - project: rootJS + java: temurin@17 + - project: rootNative + java: temurin@17 runs-on: ${{ matrix.os }} timeout-minutes: 60 steps: @@ -82,6 +84,14 @@ jobs: if: matrix.java == 'temurin@11' && matrix.os == 'ubuntu-latest' run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' 'scalafixAll --check' + - name: scalaJSLink + if: matrix.project == 'rootJS' + run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' Test/scalaJSLinkerResult + + - name: nativeLink + if: matrix.project == 'rootNative' + run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' Test/nativeLink + - name: Test run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' test @@ -95,11 +105,11 @@ jobs: - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: mkdir -p circe-yaml-v12/target circe-yaml-common/target circe-yaml/target project/target + run: mkdir -p circe-yaml-v12/target circe-yaml-common/jvm/target circe-yaml-common/native/target circe-yaml-common/js/target circe-yaml/target circe-yaml-scalayaml/jvm/target circe-yaml-scalayaml/js/target circe-yaml-scalayaml/native/target project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: tar cf targets.tar circe-yaml-v12/target circe-yaml-common/target circe-yaml/target project/target + run: tar cf targets.tar circe-yaml-v12/target circe-yaml-common/jvm/target circe-yaml-common/native/target circe-yaml-common/js/target circe-yaml/target circe-yaml-scalayaml/jvm/target circe-yaml-scalayaml/js/target circe-yaml-scalayaml/native/target project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') @@ -149,12 +159,12 @@ jobs: if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false' run: sbt +update - - name: Download target directories (2.12, rootJVM) + - name: Download target directories (2.13, rootJS) uses: actions/download-artifact@v4 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-rootJVM + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootJS - - name: Inflate target directories (2.12, rootJVM) + - name: Inflate target directories (2.13, rootJS) run: | tar xf targets.tar rm targets.tar @@ -169,6 +179,26 @@ jobs: tar xf targets.tar rm targets.tar + - name: Download target directories (2.13, rootNative) + uses: actions/download-artifact@v4 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootNative + + - name: Inflate target directories (2.13, rootNative) + run: | + tar xf targets.tar + rm targets.tar + + - name: Download target directories (3, rootJS) + uses: actions/download-artifact@v4 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootJS + + - name: Inflate target directories (3, rootJS) + run: | + tar xf targets.tar + rm targets.tar + - name: Download target directories (3, rootJVM) uses: actions/download-artifact@v4 with: @@ -179,6 +209,16 @@ jobs: tar xf targets.tar rm targets.tar + - name: Download target directories (3, rootNative) + uses: actions/download-artifact@v4 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootNative + + - name: Inflate target directories (3, rootNative) + run: | + tar xf targets.tar + rm targets.tar + - name: Import signing key if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE == '' env: @@ -246,5 +286,5 @@ jobs: - name: Submit Dependencies uses: scalacenter/sbt-dependency-submission@v2 with: - modules-ignore: rootjs_2.12 rootjs_2.13 rootjs_3 rootjvm_2.12 rootjvm_2.13 rootjvm_3 rootnative_2.12 rootnative_2.13 rootnative_3 + modules-ignore: rootjs_2.13 rootjs_3 rootjvm_2.13 rootjvm_3 rootnative_2.13 rootnative_3 configs-ignore: test scala-tool scala-doc-tool test-internal diff --git a/build.sbt b/build.sbt index 901db29..c123b0f 100644 --- a/build.sbt +++ b/build.sbt @@ -1,25 +1,23 @@ ThisBuild / tlBaseVersion := "0.15" ThisBuild / circeRootOfCodeCoverage := None ThisBuild / startYear := Some(2016) -ThisBuild / scalafixScalaBinaryVersion := "2.12" ThisBuild / tlFatalWarnings := false //TODO: ... fix this someday ThisBuild / githubWorkflowBuildMatrixFailFast := Some(false) val Versions = new { - val circe = "0.14.7" + val circe = "0.14.9" val discipline = "1.7.0" val scalaCheck = "1.18.0" - val scalaTest = "3.2.19" + val scalaTest = "3.2.18" val scalaTestPlus = "3.2.18.0" val snakeYaml = "2.2" val snakeYamlEngine = "2.7" val previousCirceYamls = Set("0.14.0", "0.14.1", "0.14.2") - val scala212 = "2.12.19" val scala213 = "2.13.14" val scala3 = "3.3.3" - val scalaVersions = Seq(scala212, scala213, scala3) + val scalaVersions = Seq(scala213, scala3) } ThisBuild / scalaVersion := Versions.scala213 @@ -28,22 +26,23 @@ ThisBuild / crossScalaVersions := Versions.scalaVersions val root = tlCrossRootProject.aggregate( `circe-yaml-common`, `circe-yaml`, - `circe-yaml-v12` + `circe-yaml-v12`, + `circe-yaml-scalayaml` ) -lazy val `circe-yaml-common` = project +lazy val `circe-yaml-common` = crossProject(JSPlatform, JVMPlatform, NativePlatform) .in(file("circe-yaml-common")) .settings( description := "Library for converting between SnakeYAML's AST (YAML 2.0) and circe's AST", libraryDependencies ++= Seq( - "io.circe" %% "circe-core" % Versions.circe + "io.circe" %%% "circe-core" % Versions.circe ), - tlVersionIntroduced := List("2.12", "2.13", "3").map(_ -> "0.14.3").toMap + tlVersionIntroduced := List("2.13", "3").map(_ -> "0.15.3").toMap ) lazy val `circe-yaml` = project .in(file("circe-yaml")) - .dependsOn(`circe-yaml-common`) + .dependsOn(`circe-yaml-common`.jvm) .settings( description := "Library for converting between SnakeYAML's AST (YAML 2.0) and circe's AST", libraryDependencies ++= Seq( @@ -54,12 +53,13 @@ lazy val `circe-yaml` = project "org.scalacheck" %% "scalacheck" % Versions.scalaCheck % Test, "org.scalatest" %% "scalatest" % Versions.scalaTest % Test, "org.scalatestplus" %% "scalacheck-1-17" % Versions.scalaTestPlus % Test - ) + ), + tlVersionIntroduced := List("2.13", "3").map(_ -> "0.14.3").toMap ) lazy val `circe-yaml-v12` = project .in(file("circe-yaml-v12")) - .dependsOn(`circe-yaml-common`) + .dependsOn(`circe-yaml-common`.jvm) .settings( description := "Library for converting between snakeyaml-engine's AST (YAML 2.0) and circe's AST", libraryDependencies ++= Seq( @@ -71,7 +71,18 @@ lazy val `circe-yaml-v12` = project "org.scalatest" %% "scalatest" % Versions.scalaTest % Test, "org.scalatestplus" %% "scalacheck-1-17" % Versions.scalaTestPlus % Test ), - tlVersionIntroduced := List("2.12", "2.13", "3").map(_ -> "0.14.3").toMap + tlVersionIntroduced := List("2.13", "3").map(_ -> "0.14.3").toMap + ) + +lazy val `circe-yaml-scalayaml` = crossProject(JSPlatform, JVMPlatform, NativePlatform) + .dependsOn(`circe-yaml-common`) + .settings( + description := "Library for converting between scala-yaml AST and circe's AST", + libraryDependencies ++= Seq( + "org.virtuslab" %%% "scala-yaml" % "0.1.0", + "org.scalatest" %%% "scalatest" % Versions.scalaTest % Test + ), + tlVersionIntroduced := List("2.13", "3").map(_ -> "0.15.3").toMap ) ThisBuild / developers := List( diff --git a/circe-yaml-common/src/main/scala/io/circe/yaml/common/Parser.scala b/circe-yaml-common/shared/src/main/scala/io/circe/yaml/common/Parser.scala similarity index 100% rename from circe-yaml-common/src/main/scala/io/circe/yaml/common/Parser.scala rename to circe-yaml-common/shared/src/main/scala/io/circe/yaml/common/Parser.scala diff --git a/circe-yaml-common/src/main/scala/io/circe/yaml/common/Printer.scala b/circe-yaml-common/shared/src/main/scala/io/circe/yaml/common/Printer.scala similarity index 100% rename from circe-yaml-common/src/main/scala/io/circe/yaml/common/Printer.scala rename to circe-yaml-common/shared/src/main/scala/io/circe/yaml/common/Printer.scala diff --git a/circe-yaml-scalayaml/shared/src/main/scala/io/circe/yaml/scalayaml/Parser.scala b/circe-yaml-scalayaml/shared/src/main/scala/io/circe/yaml/scalayaml/Parser.scala new file mode 100644 index 0000000..fcceb69 --- /dev/null +++ b/circe-yaml-scalayaml/shared/src/main/scala/io/circe/yaml/scalayaml/Parser.scala @@ -0,0 +1,112 @@ +/* + * Copyright 2016 circe + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.circe.yaml.scalayaml + +import cats.data.ValidatedNel +import cats.syntax.all._ +import io.circe +import io.circe.Decoder +import io.circe.Json +import io.circe.ParsingFailure +import org.virtuslab.yaml._ + +import java.io.Reader +import scala.collection.mutable + +object Parser extends io.circe.yaml.common.Parser { + + private def readerToString(yaml: Reader): String = { + val buffer = new Array[Char](4 * 1024) + val builder = new mutable.StringBuilder(4 * 1024) + var readBytes = -1 + while ({ readBytes = yaml.read(buffer); readBytes } > 0) + builder.appendAll(buffer, 0, readBytes) + builder.result() + } + + override def parse(yaml: Reader): Either[ParsingFailure, Json] = { + val string = readerToString(yaml) + parse(string) + } + + override def parseDocuments(yaml: Reader): Stream[Either[ParsingFailure, Json]] = { + val string = readerToString(yaml) + val parsed = parse(string) + Stream(parsed) + } + + override def parseDocuments(yaml: String): Stream[Either[ParsingFailure, Json]] = { + val parsed = parse(yaml) + Stream(parsed) + } + + override def decode[A: Decoder](input: Reader): Either[circe.Error, A] = { + val string = readerToString(input) + val parsed = parse(string) + parsed.flatMap(_.as[A]) + } + + override def decodeAccumulating[A: Decoder](input: Reader): ValidatedNel[circe.Error, A] = + decode(input).toValidatedNel + + override def parse(input: String): Either[ParsingFailure, Json] = { + val node = input.asNode + node match { + case Right(node) => nodeToJson(node) + case Left(error) => Left(errorToFailure(error)) + } + } + + private def scalarNodeToJson(node: Node.ScalarNode): Either[ParsingFailure, Json] = { + val parsed = YamlDecoder.forAny.construct(node).left.map(errorToFailure) + parsed.flatMap { + case null | None => Json.Null.asRight + case value: String => Json.fromString(value).asRight + case value: Int => Json.fromInt(value).asRight + case double: Double => + Json.fromDouble(double).toRight(ParsingFailure(s"${node.value} cannot be represented as a JSON number.", null)) + case value: Boolean => Json.fromBoolean(value).asRight + case value: Long => Json.fromLong(value).asRight + case float: Float => + Json.fromFloat(float).toRight(ParsingFailure(s"${node.value} cannot be represented as a JSON number.", null)) + case value: BigDecimal => Json.fromBigDecimal(value).asRight + case value: BigInt => Json.fromBigInt(value).asRight + case value: Byte => Json.fromInt(value.toInt).asRight + case value: Short => Json.fromInt(value.toInt).asRight + case value => ParsingFailure(s"Can't map ${value.getClass.getSimpleName} (${node.value}) to JSON.", null).asLeft + } + } + + private def nodeToJson(node: Node): Either[ParsingFailure, Json] = node match { + case node: Node.ScalarNode => + scalarNodeToJson(node) + case Node.SequenceNode(nodes, _) => + val values = nodes.traverse(nodeToJson) + values.map(Json.fromValues) + case Node.MappingNode(mappings, _) => + val fields = mappings.toList.traverse { + case (Node.ScalarNode(key, _), value) => + nodeToJson(value).map(key -> _) + case (node, _) => + Left(ParsingFailure(s"Unexpected ${node.getClass.getSimpleName} type, expected ScalarNode.", null)) + } + fields.map(Json.fromFields) + } + + private def errorToFailure(error: YamlError): ParsingFailure = + ParsingFailure(s"Parsing failed: ${error.msg}", error) +} diff --git a/circe-yaml-scalayaml/shared/src/main/scala/io/circe/yaml/scalayaml/Printer.scala b/circe-yaml-scalayaml/shared/src/main/scala/io/circe/yaml/scalayaml/Printer.scala new file mode 100644 index 0000000..fdb7d73 --- /dev/null +++ b/circe-yaml-scalayaml/shared/src/main/scala/io/circe/yaml/scalayaml/Printer.scala @@ -0,0 +1,38 @@ +/* + * Copyright 2016 circe + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.circe.yaml.scalayaml + +import io.circe.Json +import org.virtuslab.yaml.Node +import org.virtuslab.yaml.NodeOps + +object Printer extends io.circe.yaml.common.Printer { + + override def pretty(json: Json): String = { + val node = jsonToNode(json) + node.asYaml + } + + private def jsonToNode(json: Json): Node = json match { + case Json.JArray(value) => + Node.SequenceNode(value.map(jsonToNode): _*) + case Json.JObject(value) => + Node.MappingNode(value.toMap.map { case (key, value) => (Node.ScalarNode(key): Node) -> jsonToNode(value) }) + case json => + Node.ScalarNode(json.toString) + } +} diff --git a/circe-yaml-scalayaml/shared/src/main/scala/io/circe/yaml/scalayaml/parser/package.scala b/circe-yaml-scalayaml/shared/src/main/scala/io/circe/yaml/scalayaml/parser/package.scala new file mode 100644 index 0000000..f2a70b9 --- /dev/null +++ b/circe-yaml-scalayaml/shared/src/main/scala/io/circe/yaml/scalayaml/parser/package.scala @@ -0,0 +1,44 @@ +/* + * Copyright 2016 circe + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.circe.yaml.scalayaml + +import cats.data.ValidatedNel +import io.circe.Decoder +import io.circe.Error +import io.circe.Json +import io.circe.ParsingFailure + +import java.io.Reader + +package object parser extends io.circe.yaml.common.Parser { + + /** + * Parse YAML from the given [[Reader]], returning either [[ParsingFailure]] or [[Json]] + * @param yaml + * @return + */ + def parse(yaml: Reader): Either[ParsingFailure, Json] = Parser.parse(yaml) + + def parse(yaml: String): Either[ParsingFailure, Json] = Parser.parse(yaml) + + def parseDocuments(yaml: Reader): Stream[Either[ParsingFailure, Json]] = Parser.parseDocuments(yaml) + def parseDocuments(yaml: String): Stream[Either[ParsingFailure, Json]] = Parser.parseDocuments(yaml) + + final def decode[A: Decoder](input: Reader): Either[Error, A] = Parser.decode[A](input) + final def decodeAccumulating[A: Decoder](input: Reader): ValidatedNel[Error, A] = + Parser.decodeAccumulating[A](input) +} diff --git a/circe-yaml-scalayaml/shared/src/main/scala/io/circe/yaml/scalayaml/printer/package.scala b/circe-yaml-scalayaml/shared/src/main/scala/io/circe/yaml/scalayaml/printer/package.scala new file mode 100644 index 0000000..a19a434 --- /dev/null +++ b/circe-yaml-scalayaml/shared/src/main/scala/io/circe/yaml/scalayaml/printer/package.scala @@ -0,0 +1,28 @@ +/* + * Copyright 2016 circe + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.circe.yaml.scalayaml + +import io.circe.Json + +package object printer extends io.circe.yaml.common.Printer { + + /** + * A default printer implementation using Snake YAML. + */ + def print(tree: Json): String = pretty(tree) + def pretty(tree: Json): String = Printer.pretty(tree) +} diff --git a/circe-yaml-scalayaml/shared/src/test/scala/io/circe/yaml/scalayaml/ParserTests.scala b/circe-yaml-scalayaml/shared/src/test/scala/io/circe/yaml/scalayaml/ParserTests.scala new file mode 100644 index 0000000..be7dd28 --- /dev/null +++ b/circe-yaml-scalayaml/shared/src/test/scala/io/circe/yaml/scalayaml/ParserTests.scala @@ -0,0 +1,194 @@ +/* + * Copyright 2016 circe + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.circe.yaml.scalayaml + +import io.circe.Json +import io.circe.syntax._ +import org.scalatest.EitherValues +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +import java.io.StringReader + +class ParserTests extends AnyFlatSpec with Matchers with EitherValues { + // the laws should do a pretty good job of surfacing errors; these are mainly to ensure test coverage + + "Parser.parse" should "fail on invalid tagged numbers" in { + assert(parser.parse("!!int 12foo").isLeft) + } + + it should "fail to parse complex keys" in { + assert( + parser + .parse(""" + |? - foo + | - bar + |: 1 + """.stripMargin) + .isLeft + ) + } + + it should "fail to parse invalid YAML" in { + assert( + parser + .parse( + """foo: - bar""" + ) + .isLeft + ) + } + + it should "parse yes as true" in { + assert( + parser + .parse( + """foo: yes""" + ) + .isRight + ) + } + + it should "parse hexadecimal" in { + assert( + parser + .parse( + """[0xFF, 0xff, 0xabcd]""" + ) + .contains(Seq(0xff, 0xff, 0xabcd).asJson) + ) + } + + it should "parse decimal with underscore breaks" in { + assert( + parser + .parse( + """foo: !!int 1_000_000""" + ) + .contains(Map("foo" -> 1000000).asJson) + ) + } + + it should "parse aliases" in { + assert( + Parser + .parse( + """ + | aliases: + | - &alias1 + | foo: + | bar + | baz: + | - *alias1 + | - *alias1 + |""".stripMargin + ) + .isRight + ) + } + + "Parser.parseDocuments" should "fail on invalid tagged numbers" in { + val result = parser.parseDocuments(new StringReader("!!int 12foo")).toList + assert(result.size == 1) + assert(result.head.isLeft) + } + + it should "fail to parse complex keys" in { + val result = parser + .parseDocuments(new StringReader(""" + |? - foo + | - bar + |: 1""".stripMargin)) + .toList + assert(result.size == 1) + assert(result.head.isLeft) + } + + it should "fail to parse invalid YAML" in { + val result = parser.parseDocuments(new StringReader("""foo: - bar""")).toList + assert(result.size == 1) + assert(result.head.isLeft) + assert(result.head.isInstanceOf[Either[io.circe.ParsingFailure, Json]]) + } + + it should "parse yes as true" in { + val result = parser.parseDocuments(new StringReader("""foo: yes""")).toList + assert(result.size == 1) + assert(result.head.isRight) + } + + it should "parse hexadecimal" in { + val result = parser.parseDocuments(new StringReader("""[0xFF, 0xff, 0xabcd]""")).toList + assert(result.size == 1) + assert(result.head.contains(Seq(0xff, 0xff, 0xabcd).asJson)) + } + + it should "parse decimal with underscore breaks" in { + val result = parser.parseDocuments(new StringReader("""foo: !!int 1_000_000""")).toList + assert(result.size == 1) + assert(result.head.contains(Map("foo" -> 1000000).asJson)) + } + + it should "parse aliases" in { + val result = parser + .parseDocuments( + new StringReader( + """ + | aliases: + | - &alias1 + | foo: + | bar + | baz: + | - *alias1 + | - *alias1 + |""".stripMargin + ) + ) + .toList + assert(result.size == 1) + assert(result.head.isRight) + } + + it should "parse when within depth limits" in { + assert( + Parser + .parse( + """ + | foo: + | bar: + | baz + |""".stripMargin + ) + .isRight + ) + } + + it should "parse when within code point limit" in { + assert( + Parser // 1MB + .parse( + """ + | foo: + | bar: + | baz + |""".stripMargin + ) + .isRight + ) + } + +} diff --git a/circe-yaml-scalayaml/shared/src/test/scala/io/circe/yaml/scalayaml/PrinterTests.scala b/circe-yaml-scalayaml/shared/src/test/scala/io/circe/yaml/scalayaml/PrinterTests.scala new file mode 100644 index 0000000..ccbd027 --- /dev/null +++ b/circe-yaml-scalayaml/shared/src/test/scala/io/circe/yaml/scalayaml/PrinterTests.scala @@ -0,0 +1,101 @@ +/* + * Copyright 2016 circe + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.circe.yaml.scalayaml + +import io.circe.Json +import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.matchers.should.Matchers + +class PrinterTests extends AnyFreeSpec with Matchers { + + "Flow style" - { + val json = Json.obj("foo" -> Json.arr((0 until 3).map(_.toString).map(Json.fromString): _*)) + + "Block" in { + val printer = Printer + printer.pretty(json) shouldEqual + """foo: + | - "0" + | - "1" + | - "2" + |""".stripMargin + } + + } + + "Preserves order" - { + val kvPairs = Seq("d" -> 4, "a" -> 1, "b" -> 2, "c" -> 3) + val json = Json.obj(kvPairs.map { case (k, v) => k -> Json.fromInt(v) }: _*) + "true" in { + val printer = Printer + printer.pretty(json) shouldEqual + """d: 4 + |a: 1 + |b: 2 + |c: 3 + |""".stripMargin + } + } + + "Scalar style" - { + val foos = Seq.fill(40)("foo") + val foosPlain = foos.mkString(" ") + val foosFolded = Seq(foos.take(20), foos.slice(20, 40)).map(_.mkString(" ")).mkString("\n ") + val json = Json.obj("foo" -> Json.fromString(foosPlain)) + + "Plain" in { + val printer = Printer + printer.pretty(json) shouldEqual + s"""foo: "$foosPlain" + |""".stripMargin + } + + } + + "Plain with newlines" in { + val json = Json.obj("foo" -> Json.fromString("abc\nxyz\n")) + val printer = Printer + printer.pretty(json) shouldEqual + "foo: \"abc\\nxyz\\n\"\n" + } + + "Drop null keys" in { + val json = Json.obj("nullField" -> Json.Null, "nonNullField" -> Json.fromString("foo")) + Printer.pretty(json) shouldEqual "nullField: null\nnonNullField: \"foo\"\n" + } + + "Root integer" in { + val json = Json.fromInt(10) + Printer.pretty(json) shouldEqual "10\n" + } + + "Root float" in { + val json = Json.fromDoubleOrNull(22.22) + Printer.pretty(json) shouldEqual "22.22\n" + } + + "Line break" - { + val json = Json.arr(Json.fromString("foo"), Json.fromString("bar")) + + "Unix" in { + Printer.pretty(json) shouldEqual + "- \"foo\"\n- \"bar\"\n" + } + + } + +} diff --git a/project/plugins.sbt b/project/plugins.sbt index d8b09df..6bc7411 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1 +1,4 @@ addSbtPlugin("io.circe" % "sbt-circe-org" % "0.4.1") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.2") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.12.1")