From 28f4ba781e207aaa7effb832c641b939f358aa8b Mon Sep 17 00:00:00 2001 From: Ondra Pelech Date: Tue, 18 Oct 2022 00:08:11 +0200 Subject: [PATCH] circe-yaml-common --- .../scala/io/circe/yaml/common/Parser.scala | 18 +++ .../scala/io/circe/yaml/common/Printer.scala | 33 ++++ .../main/scala/io/circe/yaml/v12/Parser.scala | 152 +----------------- .../scala/io/circe/yaml/v12/ParserImpl.scala | 137 ++++++++++++++++ .../scala/io/circe/yaml/v12/Printer.scala | 129 +-------------- .../scala/io/circe/yaml/v12/PrinterImpl.scala | 88 ++++++++++ .../scala/io/circe/yaml/v12/package.scala | 18 +++ .../io/circe/yaml/v12/PrinterTests.scala | 2 +- .../src/main/scala/io/circe/yaml/Parser.scala | 2 +- .../main/scala/io/circe/yaml/Printer.scala | 7 +- .../main/scala/io/circe/yaml/package.scala | 18 +++ 11 files changed, 328 insertions(+), 276 deletions(-) create mode 100644 circe-yaml-common/src/main/scala/io/circe/yaml/common/Parser.scala create mode 100644 circe-yaml-common/src/main/scala/io/circe/yaml/common/Printer.scala create mode 100644 circe-yaml-v12/src/main/scala/io/circe/yaml/v12/ParserImpl.scala create mode 100644 circe-yaml-v12/src/main/scala/io/circe/yaml/v12/PrinterImpl.scala create mode 100644 circe-yaml-v12/src/main/scala/io/circe/yaml/v12/package.scala create mode 100644 circe-yaml/src/main/scala/io/circe/yaml/package.scala diff --git a/circe-yaml-common/src/main/scala/io/circe/yaml/common/Parser.scala b/circe-yaml-common/src/main/scala/io/circe/yaml/common/Parser.scala new file mode 100644 index 00000000..4d9378c5 --- /dev/null +++ b/circe-yaml-common/src/main/scala/io/circe/yaml/common/Parser.scala @@ -0,0 +1,18 @@ +package io.circe.yaml.common + +import io.circe.{ Json, ParsingFailure } +import java.io.Reader + +trait Parser { + + /** + * Parse YAML from the given [[Reader]], returning either [[ParsingFailure]] or [[Json]] + * + * @param yaml + * @return + */ + def parse(yaml: Reader): Either[ParsingFailure, Json] + def parse(yaml: String): Either[ParsingFailure, Json] + def parseDocuments(yaml: Reader): Stream[Either[ParsingFailure, Json]] + def parseDocuments(yaml: String): Stream[Either[ParsingFailure, Json]] +} diff --git a/circe-yaml-common/src/main/scala/io/circe/yaml/common/Printer.scala b/circe-yaml-common/src/main/scala/io/circe/yaml/common/Printer.scala new file mode 100644 index 00000000..7b77f30b --- /dev/null +++ b/circe-yaml-common/src/main/scala/io/circe/yaml/common/Printer.scala @@ -0,0 +1,33 @@ +package io.circe.yaml.common + +import io.circe.Json + +trait Printer { + def pretty(json: Json): String +} + +object Printer { + + sealed trait FlowStyle + object FlowStyle { + case object Flow extends FlowStyle + case object Block extends FlowStyle + } + + sealed trait LineBreak + object LineBreak { + case object Unix extends LineBreak + case object Windows extends LineBreak + case object Mac extends LineBreak + } + + sealed trait StringStyle + object StringStyle { + case object Plain extends StringStyle + case object DoubleQuoted extends StringStyle + case object SingleQuoted extends StringStyle + case object Literal extends StringStyle + case object Folded extends StringStyle + } + +} diff --git a/circe-yaml-v12/src/main/scala/io/circe/yaml/v12/Parser.scala b/circe-yaml-v12/src/main/scala/io/circe/yaml/v12/Parser.scala index 94d7021b..a0b8e087 100644 --- a/circe-yaml-v12/src/main/scala/io/circe/yaml/v12/Parser.scala +++ b/circe-yaml-v12/src/main/scala/io/circe/yaml/v12/Parser.scala @@ -1,153 +1,7 @@ package io.circe.yaml.v12 -import cats.syntax.either._ -import io.circe._ -import java.io.{ Reader, StringReader } -import java.util.Optional +import io.circe.yaml.common import org.snakeyaml.engine.v2.api.LoadSettings -import org.snakeyaml.engine.v2.composer.Composer -import org.snakeyaml.engine.v2.constructor.StandardConstructor -import org.snakeyaml.engine.v2.nodes._ -import org.snakeyaml.engine.v2.scanner.StreamReader -import scala.collection.JavaConverters._ - -trait Parser { - - /** - * Parse YAML from the given [[Reader]], returning either [[ParsingFailure]] or [[Json]] - * - * @param yaml - * @return - */ - def parse(yaml: Reader): Either[ParsingFailure, Json] - def parse(yaml: String): Either[ParsingFailure, Json] - def parseDocuments(yaml: Reader): Stream[Either[ParsingFailure, Json]] - def parseDocuments(yaml: String): Stream[Either[ParsingFailure, Json]] -} - -class ParserImpl(settings: LoadSettings) extends Parser { - - /** - * Parse YAML from the given [[Reader]], returning either [[ParsingFailure]] or [[Json]] - * @param yaml - * @return - */ - def parse(yaml: Reader): Either[ParsingFailure, Json] = for { - parsed <- parseSingle(yaml) - json <- yamlToJson(parsed) - } yield json - - def parse(yaml: String): Either[ParsingFailure, Json] = - parse(new StringReader(yaml)) - - def parseDocuments(yaml: Reader): Stream[Either[ParsingFailure, Json]] = parseStream(yaml).map(yamlToJson) - def parseDocuments(yaml: String): Stream[Either[ParsingFailure, Json]] = parseDocuments(new StringReader(yaml)) - - private[this] def asScala[T](ot: Optional[T]): Option[T] = - if (ot.isPresent) Some(ot.get()) else None - - private[this] def createComposer(reader: Reader) = - new Composer(settings, new org.snakeyaml.engine.v2.parser.ParserImpl(settings, new StreamReader(settings, reader))) - - private[this] def parseSingle(reader: Reader): Either[ParsingFailure, Node] = - Either.catchNonFatal { - val composer = createComposer(reader) - asScala(composer.getSingleNode) - } match { - case Left(err) => Left(ParsingFailure(err.getMessage, err)) - case Right(None) => Left(ParsingFailure("no document found", new RuntimeException("no document found"))) - case Right(Some(value)) => Right(value) - } - - private[this] def parseStream(reader: Reader) = - createComposer(reader).asScala.toStream - - private[this] object CustomTag { - def unapply(tag: Tag): Option[String] = if (!tag.getValue.startsWith(Tag.PREFIX)) - Some(tag.getValue) - else - None - } - - private[this] class FlatteningConstructor(settings: LoadSettings) extends StandardConstructor(settings) { - def flatten(node: MappingNode): MappingNode = { - flattenMapping(node) - node - } - - def construct(node: ScalarNode): Object = super.construct(node) // to make the method public - } - - private[this] def yamlToJson(node: Node): Either[ParsingFailure, Json] = { - // Isn't thread-safe internally, may hence not be shared - val flattener: FlatteningConstructor = new FlatteningConstructor(settings) - - def convertScalarNode(node: ScalarNode) = Either - .catchNonFatal(node.getTag match { - case Tag.INT if node.getValue.startsWith("0x") || node.getValue.contains("_") => - Json.fromJsonNumber(flattener.construct(node) match { - case int: Integer => JsonLong(int.toLong) - case long: java.lang.Long => JsonLong(long) - case bigint: java.math.BigInteger => - JsonDecimal(bigint.toString) - case other => throw new NumberFormatException(s"Unexpected number type: ${other.getClass}") - }) - case Tag.INT | Tag.FLOAT => - JsonNumber.fromString(node.getValue).map(Json.fromJsonNumber).getOrElse { - throw new NumberFormatException(s"Invalid numeric string ${node.getValue}") - } - case Tag.BOOL => - Json.fromBoolean(flattener.construct(node) match { - case b: java.lang.Boolean => b - case _ => throw new IllegalArgumentException(s"Invalid boolean string ${node.getValue}") - }) - case Tag.NULL => Json.Null - case CustomTag(other) => - Json.fromJsonObject(JsonObject.singleton(other.stripPrefix("!"), Json.fromString(node.getValue))) - case _ => Json.fromString(node.getValue) - }) - .leftMap { err => - ParsingFailure(err.getMessage, err) - } - - def convertKeyNode(node: Node) = node match { - case scalar: ScalarNode => Right(scalar.getValue) - case _ => Left(ParsingFailure("Only string keys can be represented in JSON", null)) - } - - if (node == null) { - Right(Json.False) - } else { - node match { - case mapping: MappingNode => - flattener - .flatten(mapping) - .getValue - .asScala - .foldLeft( - Either.right[ParsingFailure, JsonObject](JsonObject.empty) - ) { (objEither, tup) => - for { - obj <- objEither - key <- convertKeyNode(tup.getKeyNode) - value <- yamlToJson(tup.getValueNode) - } yield obj.add(key, value) - } - .map(Json.fromJsonObject) - case sequence: SequenceNode => - sequence.getValue.asScala - .foldLeft(Either.right[ParsingFailure, List[Json]](List.empty[Json])) { (arrEither, node) => - for { - arr <- arrEither - value <- yamlToJson(node) - } yield value :: arr - } - .map(arr => Json.fromValues(arr.reverse)) - case scalar: ScalarNode => convertScalarNode(scalar) - } - } - } -} object Parser { final case class Config( @@ -160,7 +14,7 @@ object Parser { useMarks: Boolean = true ) - def make(config: Config = Config()): Parser = { + def make(config: Config = Config()): common.Parser = { import config._ new ParserImpl( LoadSettings.builder @@ -175,5 +29,5 @@ object Parser { ) } - lazy val default: Parser = make() + lazy val default: common.Parser = make() } diff --git a/circe-yaml-v12/src/main/scala/io/circe/yaml/v12/ParserImpl.scala b/circe-yaml-v12/src/main/scala/io/circe/yaml/v12/ParserImpl.scala new file mode 100644 index 00000000..39537233 --- /dev/null +++ b/circe-yaml-v12/src/main/scala/io/circe/yaml/v12/ParserImpl.scala @@ -0,0 +1,137 @@ +package io.circe.yaml.v12 + +import cats.syntax.either._ +import io.circe._ +import io.circe.yaml.common +import java.io.{ Reader, StringReader } +import java.util.Optional +import org.snakeyaml.engine.v2.api.LoadSettings +import org.snakeyaml.engine.v2.composer.Composer +import org.snakeyaml.engine.v2.constructor.StandardConstructor +import org.snakeyaml.engine.v2.nodes._ +import org.snakeyaml.engine.v2.scanner.StreamReader +import scala.collection.JavaConverters._ + +class ParserImpl(settings: LoadSettings) extends common.Parser { + + /** + * Parse YAML from the given [[Reader]], returning either [[ParsingFailure]] or [[Json]] + * @param yaml + * @return + */ + def parse(yaml: Reader): Either[ParsingFailure, Json] = for { + parsed <- parseSingle(yaml) + json <- yamlToJson(parsed) + } yield json + + def parse(yaml: String): Either[ParsingFailure, Json] = + parse(new StringReader(yaml)) + + def parseDocuments(yaml: Reader): Stream[Either[ParsingFailure, Json]] = parseStream(yaml).map(yamlToJson) + def parseDocuments(yaml: String): Stream[Either[ParsingFailure, Json]] = parseDocuments(new StringReader(yaml)) + + private[this] def asScala[T](ot: Optional[T]): Option[T] = + if (ot.isPresent) Some(ot.get()) else None + + private[this] def createComposer(reader: Reader) = + new Composer(settings, new org.snakeyaml.engine.v2.parser.ParserImpl(settings, new StreamReader(settings, reader))) + + private[this] def parseSingle(reader: Reader): Either[ParsingFailure, Node] = + Either.catchNonFatal { + val composer = createComposer(reader) + asScala(composer.getSingleNode) + } match { + case Left(err) => Left(ParsingFailure(err.getMessage, err)) + case Right(None) => Left(ParsingFailure("no document found", new RuntimeException("no document found"))) + case Right(Some(value)) => Right(value) + } + + private[this] def parseStream(reader: Reader) = + createComposer(reader).asScala.toStream + + private[this] object CustomTag { + def unapply(tag: Tag): Option[String] = if (!tag.getValue.startsWith(Tag.PREFIX)) + Some(tag.getValue) + else + None + } + + private[this] class FlatteningConstructor(settings: LoadSettings) extends StandardConstructor(settings) { + def flatten(node: MappingNode): MappingNode = { + flattenMapping(node) + node + } + + def construct(node: ScalarNode): Object = super.construct(node) // to make the method public + } + + private[this] def yamlToJson(node: Node): Either[ParsingFailure, Json] = { + // Isn't thread-safe internally, may hence not be shared + val flattener: FlatteningConstructor = new FlatteningConstructor(settings) + + def convertScalarNode(node: ScalarNode) = Either + .catchNonFatal(node.getTag match { + case Tag.INT if node.getValue.startsWith("0x") || node.getValue.contains("_") => + Json.fromJsonNumber(flattener.construct(node) match { + case int: Integer => JsonLong(int.toLong) + case long: java.lang.Long => JsonLong(long) + case bigint: java.math.BigInteger => + JsonDecimal(bigint.toString) + case other => throw new NumberFormatException(s"Unexpected number type: ${other.getClass}") + }) + case Tag.INT | Tag.FLOAT => + JsonNumber.fromString(node.getValue).map(Json.fromJsonNumber).getOrElse { + throw new NumberFormatException(s"Invalid numeric string ${node.getValue}") + } + case Tag.BOOL => + Json.fromBoolean(flattener.construct(node) match { + case b: java.lang.Boolean => b + case _ => throw new IllegalArgumentException(s"Invalid boolean string ${node.getValue}") + }) + case Tag.NULL => Json.Null + case CustomTag(other) => + Json.fromJsonObject(JsonObject.singleton(other.stripPrefix("!"), Json.fromString(node.getValue))) + case _ => Json.fromString(node.getValue) + }) + .leftMap { err => + ParsingFailure(err.getMessage, err) + } + + def convertKeyNode(node: Node) = node match { + case scalar: ScalarNode => Right(scalar.getValue) + case _ => Left(ParsingFailure("Only string keys can be represented in JSON", null)) + } + + if (node == null) { + Right(Json.False) + } else { + node match { + case mapping: MappingNode => + flattener + .flatten(mapping) + .getValue + .asScala + .foldLeft( + Either.right[ParsingFailure, JsonObject](JsonObject.empty) + ) { (objEither, tup) => + for { + obj <- objEither + key <- convertKeyNode(tup.getKeyNode) + value <- yamlToJson(tup.getValueNode) + } yield obj.add(key, value) + } + .map(Json.fromJsonObject) + case sequence: SequenceNode => + sequence.getValue.asScala + .foldLeft(Either.right[ParsingFailure, List[Json]](List.empty[Json])) { (arrEither, node) => + for { + arr <- arrEither + value <- yamlToJson(node) + } yield value :: arr + } + .map(arr => Json.fromValues(arr.reverse)) + case scalar: ScalarNode => convertScalarNode(scalar) + } + } + } +} diff --git a/circe-yaml-v12/src/main/scala/io/circe/yaml/v12/Printer.scala b/circe-yaml-v12/src/main/scala/io/circe/yaml/v12/Printer.scala index eb35ffa0..a883910b 100644 --- a/circe-yaml-v12/src/main/scala/io/circe/yaml/v12/Printer.scala +++ b/circe-yaml-v12/src/main/scala/io/circe/yaml/v12/Printer.scala @@ -1,96 +1,10 @@ package io.circe.yaml.v12 -import io.circe._ -import io.circe.yaml.v12.Printer._ -import java.io.StringWriter -import org.snakeyaml.engine.v2.api.{ DumpSettings, StreamDataWriter } -import org.snakeyaml.engine.v2.common -import org.snakeyaml.engine.v2.emitter.Emitter -import org.snakeyaml.engine.v2.nodes._ -import org.snakeyaml.engine.v2.serializer.Serializer +import io.circe.yaml.common +import io.circe.yaml.common.Printer._ +import org.snakeyaml.engine.v2.api.DumpSettings import scala.collection.JavaConverters._ -trait Printer { - def pretty(json: Json): String -} - -class PrinterImpl( - stringStyle: StringStyle, - preserveOrder: Boolean, - dropNullKeys: Boolean, - mappingStyle: FlowStyle, - sequenceStyle: FlowStyle, - options: DumpSettings -) extends Printer { - - import PrinterImpl._ - - def pretty(json: Json): String = { - val writer = new StreamToStringWriter - val serializer = new Serializer(options, new Emitter(options, writer)) - serializer.emitStreamStart() - serializer.serializeDocument(jsonToYaml(json)) - serializer.emitStreamEnd() - writer.toString - } - - private def isBad(s: String): Boolean = s.indexOf('\u0085') >= 0 || s.indexOf('\ufeff') >= 0 - private def hasNewline(s: String): Boolean = s.indexOf('\n') >= 0 - - private def scalarStyle(value: String): common.ScalarStyle = - if (isBad(value)) common.ScalarStyle.DOUBLE_QUOTED else common.ScalarStyle.PLAIN - - private def stringScalarStyle(value: String): common.ScalarStyle = - if (isBad(value)) common.ScalarStyle.DOUBLE_QUOTED - else if (stringStyle == StringStyle.Plain && hasNewline(value)) common.ScalarStyle.LITERAL - else StringStyle.toScalarStyle(stringStyle) - - private def scalarNode(tag: Tag, value: String) = new ScalarNode(tag, value, scalarStyle(value)) - private def stringNode(value: String) = new ScalarNode(Tag.STR, value, stringScalarStyle(value)) - private def keyNode(value: String) = new ScalarNode(Tag.STR, value, scalarStyle(value)) - - private def jsonToYaml(json: Json): Node = { - - def convertObject(obj: JsonObject) = { - val fields = if (preserveOrder) obj.keys else obj.keys.toSet - val m = obj.toMap - val childNodes = fields.flatMap { key => - val value = m(key) - if (!dropNullKeys || !value.isNull) Some(new NodeTuple(keyNode(key), jsonToYaml(value))) - else None - } - new MappingNode( - Tag.MAP, - childNodes.toList.asJava, - if (mappingStyle == FlowStyle.Flow) common.FlowStyle.FLOW else common.FlowStyle.BLOCK - ) - } - - json.fold( - scalarNode(Tag.NULL, "null"), - bool => scalarNode(Tag.BOOL, bool.toString), - number => scalarNode(numberTag(number), number.toString), - str => stringNode(str), - arr => - new SequenceNode( - Tag.SEQ, - arr.map(jsonToYaml).asJava, - if (sequenceStyle == FlowStyle.Flow) common.FlowStyle.FLOW else common.FlowStyle.BLOCK - ), - obj => convertObject(obj) - ) - } -} - -object PrinterImpl { - private def numberTag(number: JsonNumber): Tag = - if (number.toString.contains(".")) Tag.FLOAT else Tag.INT - - private class StreamToStringWriter extends StringWriter with StreamDataWriter { - override def flush(): Unit = super.flush() // to fix "conflicting members" - } -} - object Printer { final case class Config( preserveOrder: Boolean = false, @@ -108,7 +22,7 @@ object Printer { explicitEnd: Boolean = false ) - def make(config: Config = Config()): Printer = { + def make(config: Config = Config()): common.Printer = { import config._ new PrinterImpl( stringStyle, @@ -123,7 +37,7 @@ object Printer { .setSplitLines(splitLines) .setIndicatorIndent(indicatorIndent) .setTagDirective(tags.asJava) - .setDefaultScalarStyle(StringStyle.toScalarStyle(stringStyle)) + .setDefaultScalarStyle(stringStyle.toScalarStyle) .setExplicitStart(explicitStart) .setExplicitEnd(explicitEnd) .setBestLineBreak { @@ -137,36 +51,7 @@ object Printer { ) } - lazy val spaces2: Printer = make() - lazy val spaces4: Printer = make(Config(indent = 4)) + lazy val spaces2: common.Printer = make() + lazy val spaces4: common.Printer = make(Config(indent = 4)) - sealed trait FlowStyle - object FlowStyle { - case object Flow extends FlowStyle - case object Block extends FlowStyle - } - - sealed trait StringStyle - object StringStyle { - case object Plain extends StringStyle - case object DoubleQuoted extends StringStyle - case object SingleQuoted extends StringStyle - case object Literal extends StringStyle - case object Folded extends StringStyle - - def toScalarStyle(style: StringStyle): common.ScalarStyle = style match { - case StringStyle.Plain => common.ScalarStyle.PLAIN - case StringStyle.DoubleQuoted => common.ScalarStyle.DOUBLE_QUOTED - case StringStyle.SingleQuoted => common.ScalarStyle.SINGLE_QUOTED - case StringStyle.Literal => common.ScalarStyle.LITERAL - case StringStyle.Folded => common.ScalarStyle.FOLDED - } - } - - sealed trait LineBreak - object LineBreak { - case object Unix extends LineBreak - case object Windows extends LineBreak - case object Mac extends LineBreak - } } diff --git a/circe-yaml-v12/src/main/scala/io/circe/yaml/v12/PrinterImpl.scala b/circe-yaml-v12/src/main/scala/io/circe/yaml/v12/PrinterImpl.scala new file mode 100644 index 00000000..03317e9e --- /dev/null +++ b/circe-yaml-v12/src/main/scala/io/circe/yaml/v12/PrinterImpl.scala @@ -0,0 +1,88 @@ +package io.circe.yaml.v12 + +import io.circe.{ Json, JsonNumber, JsonObject } +import io.circe.yaml.common.Printer._ +import java.io.StringWriter +import org.snakeyaml.engine.v2.api.{ DumpSettings, StreamDataWriter } +import org.snakeyaml.engine.v2.common +import org.snakeyaml.engine.v2.emitter.Emitter +import org.snakeyaml.engine.v2.nodes._ +import org.snakeyaml.engine.v2.serializer.Serializer +import scala.collection.JavaConverters._ + +class PrinterImpl( + stringStyle: StringStyle, + preserveOrder: Boolean, + dropNullKeys: Boolean, + mappingStyle: FlowStyle, + sequenceStyle: FlowStyle, + options: DumpSettings +) extends io.circe.yaml.common.Printer { + + import PrinterImpl._ + + def pretty(json: Json): String = { + val writer = new StreamToStringWriter + val serializer = new Serializer(options, new Emitter(options, writer)) + serializer.emitStreamStart() + serializer.serializeDocument(jsonToYaml(json)) + serializer.emitStreamEnd() + writer.toString + } + + private def isBad(s: String): Boolean = s.indexOf('\u0085') >= 0 || s.indexOf('\ufeff') >= 0 + private def hasNewline(s: String): Boolean = s.indexOf('\n') >= 0 + + private def scalarStyle(value: String): common.ScalarStyle = + if (isBad(value)) common.ScalarStyle.DOUBLE_QUOTED else common.ScalarStyle.PLAIN + + private def stringScalarStyle(value: String): common.ScalarStyle = + if (isBad(value)) common.ScalarStyle.DOUBLE_QUOTED + else if (stringStyle == StringStyle.Plain && hasNewline(value)) common.ScalarStyle.LITERAL + else stringStyle.toScalarStyle + + private def scalarNode(tag: Tag, value: String) = new ScalarNode(tag, value, scalarStyle(value)) + private def stringNode(value: String) = new ScalarNode(Tag.STR, value, stringScalarStyle(value)) + private def keyNode(value: String) = new ScalarNode(Tag.STR, value, scalarStyle(value)) + + private def jsonToYaml(json: Json): Node = { + + def convertObject(obj: JsonObject) = { + val fields = if (preserveOrder) obj.keys else obj.keys.toSet + val m = obj.toMap + val childNodes = fields.flatMap { key => + val value = m(key) + if (!dropNullKeys || !value.isNull) Some(new NodeTuple(keyNode(key), jsonToYaml(value))) + else None + } + new MappingNode( + Tag.MAP, + childNodes.toList.asJava, + if (mappingStyle == FlowStyle.Flow) common.FlowStyle.FLOW else common.FlowStyle.BLOCK + ) + } + + json.fold( + scalarNode(Tag.NULL, "null"), + bool => scalarNode(Tag.BOOL, bool.toString), + number => scalarNode(numberTag(number), number.toString), + str => stringNode(str), + arr => + new SequenceNode( + Tag.SEQ, + arr.map(jsonToYaml).asJava, + if (sequenceStyle == FlowStyle.Flow) common.FlowStyle.FLOW else common.FlowStyle.BLOCK + ), + obj => convertObject(obj) + ) + } +} + +object PrinterImpl { + private def numberTag(number: JsonNumber): Tag = + if (number.toString.contains(".")) Tag.FLOAT else Tag.INT + + private class StreamToStringWriter extends StringWriter with StreamDataWriter { + override def flush(): Unit = super.flush() // to fix "conflicting members" + } +} diff --git a/circe-yaml-v12/src/main/scala/io/circe/yaml/v12/package.scala b/circe-yaml-v12/src/main/scala/io/circe/yaml/v12/package.scala new file mode 100644 index 00000000..56b6d162 --- /dev/null +++ b/circe-yaml-v12/src/main/scala/io/circe/yaml/v12/package.scala @@ -0,0 +1,18 @@ +package io.circe.yaml + +import io.circe.yaml.common.Printer.StringStyle +import org.snakeyaml.engine.v2.common.ScalarStyle + +package object v12 { + + implicit class StringStyleOps(private val style: StringStyle) extends AnyVal { + def toScalarStyle: ScalarStyle = style match { + case StringStyle.Plain => ScalarStyle.PLAIN + case StringStyle.DoubleQuoted => ScalarStyle.DOUBLE_QUOTED + case StringStyle.SingleQuoted => ScalarStyle.SINGLE_QUOTED + case StringStyle.Literal => ScalarStyle.LITERAL + case StringStyle.Folded => ScalarStyle.FOLDED + } + } + +} diff --git a/circe-yaml-v12/src/test/scala/io/circe/yaml/v12/PrinterTests.scala b/circe-yaml-v12/src/test/scala/io/circe/yaml/v12/PrinterTests.scala index 6aebfb26..01fdaaee 100644 --- a/circe-yaml-v12/src/test/scala/io/circe/yaml/v12/PrinterTests.scala +++ b/circe-yaml-v12/src/test/scala/io/circe/yaml/v12/PrinterTests.scala @@ -1,7 +1,7 @@ package io.circe.yaml.v12 import io.circe.Json -import io.circe.yaml.v12.Printer.{ FlowStyle, LineBreak, StringStyle } +import io.circe.yaml.common.Printer.{ FlowStyle, LineBreak, StringStyle } import org.scalatest.freespec.AnyFreeSpec import org.scalatest.matchers.should.Matchers diff --git a/circe-yaml/src/main/scala/io/circe/yaml/Parser.scala b/circe-yaml/src/main/scala/io/circe/yaml/Parser.scala index 8d5d6c32..46f1c3b7 100644 --- a/circe-yaml/src/main/scala/io/circe/yaml/Parser.scala +++ b/circe-yaml/src/main/scala/io/circe/yaml/Parser.scala @@ -11,7 +11,7 @@ import scala.collection.JavaConverters._ final case class Parser( maxAliasesForCollections: Int = 50 -) { +) extends yaml.common.Parser { private val loaderOptions = { val options = new LoaderOptions() diff --git a/circe-yaml/src/main/scala/io/circe/yaml/Printer.scala b/circe-yaml/src/main/scala/io/circe/yaml/Printer.scala index 87fe2ee7..4e38de43 100644 --- a/circe-yaml/src/main/scala/io/circe/yaml/Printer.scala +++ b/circe-yaml/src/main/scala/io/circe/yaml/Printer.scala @@ -25,7 +25,7 @@ final case class Printer( explicitStart: Boolean = false, explicitEnd: Boolean = false, version: YamlVersion = YamlVersion.Auto -) { +) extends yaml.common.Printer { def pretty(json: Json): String = { val rootTag = yamlTag(json) @@ -44,7 +44,7 @@ final case class Printer( options.setSplitLines(splitLines) options.setIndicatorIndent(indicatorIndent) options.setTags(tags.asJava) - options.setDefaultScalarStyle(StringStyle.toScalarStyle(stringStyle)) + options.setDefaultScalarStyle(stringStyle.toScalarStyle) options.setLineBreak(lineBreak match { case LineBreak.Unix => DumperOptions.LineBreak.UNIX case LineBreak.Windows => DumperOptions.LineBreak.WIN @@ -69,7 +69,7 @@ final case class Printer( private def stringScalarStyle(value: String): DumperOptions.ScalarStyle = if (isBad(value)) DumperOptions.ScalarStyle.DOUBLE_QUOTED else if (stringStyle == StringStyle.Plain && hasNewline(value)) DumperOptions.ScalarStyle.LITERAL - else StringStyle.toScalarStyle(stringStyle) + else stringStyle.toScalarStyle private def scalarNode(tag: Tag, value: String) = new ScalarNode(tag, value, null, null, scalarStyle(value)) private def stringNode(value: String) = new ScalarNode(Tag.STR, value, null, null, stringScalarStyle(value)) @@ -113,6 +113,7 @@ object Printer { val spaces2 = Printer() val spaces4 = Printer(indent = 4) + // TODO: at next compatibility break, unify on io.circe.yaml.common sealed trait FlowStyle object FlowStyle { case object Flow extends FlowStyle diff --git a/circe-yaml/src/main/scala/io/circe/yaml/package.scala b/circe-yaml/src/main/scala/io/circe/yaml/package.scala new file mode 100644 index 00000000..ea813c22 --- /dev/null +++ b/circe-yaml/src/main/scala/io/circe/yaml/package.scala @@ -0,0 +1,18 @@ +package io.circe + +import io.circe.yaml.Printer.StringStyle +import org.yaml.snakeyaml.DumperOptions.ScalarStyle + +package object yaml { + + implicit class StringStyleOps(private val style: StringStyle) extends AnyVal { + def toScalarStyle: ScalarStyle = style match { + case StringStyle.Plain => ScalarStyle.PLAIN + case StringStyle.DoubleQuoted => ScalarStyle.DOUBLE_QUOTED + case StringStyle.SingleQuoted => ScalarStyle.SINGLE_QUOTED + case StringStyle.Literal => ScalarStyle.LITERAL + case StringStyle.Folded => ScalarStyle.FOLDED + } + } + +}