-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1643f45
commit 28f4ba7
Showing
11 changed files
with
328 additions
and
276 deletions.
There are no files selected for viewing
18 changes: 18 additions & 0 deletions
18
circe-yaml-common/src/main/scala/io/circe/yaml/common/Parser.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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]] | ||
} |
33 changes: 33 additions & 0 deletions
33
circe-yaml-common/src/main/scala/io/circe/yaml/common/Printer.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
137 changes: 137 additions & 0 deletions
137
circe-yaml-v12/src/main/scala/io/circe/yaml/v12/ParserImpl.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.