Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:

jobs:
scalafmt:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04

steps:
- uses: actions/checkout@v4
Expand All @@ -20,7 +20,7 @@ jobs:
- run: sbt scalafmtCheckAll

test:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
Expand All @@ -44,7 +44,7 @@ jobs:
publish:
needs: test
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -67,7 +67,7 @@ jobs:
documentation:
needs: test
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-drafter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:

jobs:
update_release_draft:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
# Drafts your next Release notes as Pull Requests are merged into "main"
- uses: release-drafter/release-drafter@v6
Expand Down
2 changes: 1 addition & 1 deletion core/shared/src/main/scala/org/virtuslab/yaml/Node.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import org.virtuslab.yaml.syntax.YamlPrimitive
* ADT that corresponds to the YAML representation graph nodes https://yaml.org/spec/1.2/spec.html#id2764044
*/
sealed trait Node {
private[yaml] def pos: Option[Range]
def pos: Option[Range]
def tag: Tag
def as[T](implicit
c: YamlDecoder[T],
Expand Down
104 changes: 81 additions & 23 deletions core/shared/src/main/scala/org/virtuslab/yaml/Yaml.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,105 @@ import org.virtuslab.yaml.internal.load.compose.ComposerImpl
import org.virtuslab.yaml.internal.load.parse.Parser
import org.virtuslab.yaml.internal.load.parse.ParserImpl
import org.virtuslab.yaml.internal.load.reader.Tokenizer
import scala.annotation.tailrec

package object yaml {

implicit class StringOps(val str: String) extends AnyVal {
/**
* Parse YAML from the given string.
*/
def parseYaml(str: String): Either[YamlError, Node] =
for {
events <- {
val parser = ParserImpl(Tokenizer.make(str))
parser.getEvents()
}
node <- ComposerImpl.fromEvents(events)
} yield node

/**
* Parse YAML from the given [[String]], returning either [[YamlError]] or [[T]].
*
/** Parse multiple YAML documents from the given string.
*/
def parseManyYamls(str: String): Either[YamlError, List[Node]] =
for {
events <- {
val parser = ParserImpl(Tokenizer.make(str))
parser.getEvents()
}
nodes <- ComposerImpl.multipleFromEvents(events)
} yield nodes

/**
* Parse a YAML document from the given [[String]], returning either [[YamlError]] or [[T]].
*
* According to the specification:
* - [[Parser]] takes input string and produces sequence of events
* - then [[Composer]] produces a representation graph from events
* - finally [[YamlDecoder]] (construct phase from the YAML spec) constructs data type [[T]] from the YAML representation.
* - finally [[YamlDecoder]] (construct phase from the YAML spec) constructs data type [[T]] from the YAML representation.
*/
def decodeYaml[T](str: String)(implicit
c: YamlDecoder[T],
settings: LoadSettings = LoadSettings.empty
): Either[YamlError, T] =
for {
node <- parseYaml(str)
t <- node.as[T]
} yield t

/**
* Decode all YAML documents from the given [[String]], returning either [[YamlError]] or a list of [[T]].
* The error will be the first failure in parsing or converting to [[T]].
*/
def decodeManyYamls[T](str: String)(implicit
c: YamlDecoder[T],
settings: LoadSettings = LoadSettings.empty
): Either[YamlError, List[T]] =
parseManyYamls(str).flatMap { nodes =>
@tailrec
def parseNodes(n: List[Node], out: List[T]): Either[YamlError, List[T]] =
n match {
case Nil => Right(out.reverse)
case head :: tail =>
head.as[T] match {
case Left(error) => Left(error)
case Right(value) => parseNodes(tail, value :: out)
}
}

parseNodes(nodes, Nil)
}

implicit class StringOps(val str: String) extends AnyVal {

/**
* Parse YAML from the given [[String]], returning either [[YamlError]] or [[T]].
*
* According to the specification:
* - [[Parser]] takes input string and produces sequence of events
* - then [[Composer]] produces a representation graph from events
* - finally [[YamlDecoder]] (construct phase from the YAML spec) constructs data type [[T]] from the YAML representation.
*/
def as[T](implicit
c: YamlDecoder[T],
settings: LoadSettings = LoadSettings.empty
): Either[YamlError, T] =
for {
events <- {
val parser = ParserImpl(Tokenizer.make(str))
parser.getEvents()
}
node <- ComposerImpl.fromEvents(events)
t <- node.as[T]
} yield t
): Either[YamlError, T] = decodeYaml[T](str)

def asNode: Either[YamlError, Node] =
for {
events <- {
val parser = ParserImpl(Tokenizer.make(str))
parser.getEvents()
}
node <- ComposerImpl.fromEvents(events)
} yield node
/**
* Parse YAML from the given [[String]], returning either [[YamlError]] or a list of [[T]].
* The error will be the first failure in parsing or converting to [[T]].
*/
def asMany[T](implicit
c: YamlDecoder[T],
settings: LoadSettings = LoadSettings.empty
): Either[YamlError, List[T]] = decodeManyYamls[T](str)

def asNode: Either[YamlError, Node] = parseYaml(str)
}

implicit class AnyOps[T](val t: T) extends AnyVal {

/**
* Serialize a [[T]] into a YAML string.
*
*
* According to the specification:
* - [[YamlEncoder]] encode type [[T]] into [[Node]]
* - [[Serializer]] serializes [[Node]] into sequence of [[Event]]s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ object PresenterImpl extends Presenter {
val stack = new mutable.Stack[EventKind]
val newline = System.lineSeparator()

var toplevelNode = true // toplevel node should't insert newline and increase indent
var indent = 0
var toplevelNode = true // toplevel node should't insert newline and increase indent
var indent = 0
var requirePreceedingSpace = false

def parseNode(events: List[EventKind]): List[EventKind] =
def parseNode(events: List[EventKind], priorContentThisLine: Boolean): List[EventKind] =
events match {
case head :: tail =>
head match {
Expand All @@ -33,12 +34,15 @@ object PresenterImpl extends Presenter {
case Scalar(value, _, NodeEventMetadata(_, tag)) =>
insertSequencePadding()
// todo escape string using doublequotes
if (priorContentThisLine) {
sb.append(" ")
}
if (tag.contains(Tag.nullTag)) sb.append("!!null")
else sb.append(value)
sb.append(newline)
tail
case DocumentStart(_) => parseNode(tail)
case DocumentEnd(_) => parseNode(tail)
case DocumentStart(_) => parseNode(tail, priorContentThisLine = false)
case DocumentEnd(_) => parseNode(tail, priorContentThisLine = false)
case _ => events
}
case Nil => Nil
Expand All @@ -52,7 +56,7 @@ object PresenterImpl extends Presenter {
tail
case Scalar(value, _, _) :: tail =>
appendKey(value)
val rest = parseNode(tail)
val rest = parseNode(tail, priorContentThisLine = true)
parseMapping(rest)
case _ => events
}
Expand All @@ -65,20 +69,20 @@ object PresenterImpl extends Presenter {
popAndDecreaseIndent()
tail
case _ =>
val rest = parseNode(events)
val rest = parseNode(events, priorContentThisLine = true)
parseSequence(rest)
}

def appendKey(value: String) = {
sb.append(" " * indent)
sb.append(value)
sb.append(": ")
sb.append(":")
}

def insertSequencePadding() = stack.headOption match {
case Some(_: SequenceStart) =>
sb.append(" " * indent)
sb.append("- ")
sb.append("-")
case _ => ()
}

Expand All @@ -96,7 +100,7 @@ object PresenterImpl extends Presenter {
stack.pop()
}

parseNode(events.toList)
parseNode(events.toList, priorContentThisLine = false)
sb.result()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ import org.virtuslab.yaml.internal.load.parse.NodeEventMetadata
import org.virtuslab.yaml.internal.load.parse.ParserImpl

/**
* Composing takes a series of serialization events and produces a representation graph.
* Composing takes a series of serialization events and produces a representation graph.
* It can fail due to any of several reasons e.g. unexpected event.
* Returns either [[YamlError]] or [[Node]]
* Returns either [[YamlError]] or [[Node]](s)
*/
trait Composer {
def fromEvents(events: List[Event]): Either[YamlError, Node]
def multipleFromEvents(events: List[Event]): Either[YamlError, List[Node]]
}

object ComposerImpl extends Composer {
Expand All @@ -36,6 +37,26 @@ object ComposerImpl extends Composer {
case _ => composeNode(events, mutable.Map.empty).map(_.node)
}

override def multipleFromEvents(events: List[Event]): Either[YamlError, List[Node]] = {
val aliases = mutable.Map.empty[Anchor, Node]

@tailrec
def go(out: List[Node], remaining: List[Event]): Either[YamlError, List[Node]] =
remaining.headOption.map(_.kind) match {
case None | Some(EventKind.StreamEnd) =>
Right(out.reverse)
case Some(_: EventKind.DocumentEnd) =>
go(out, remaining.tail)
case _ =>
composeNode(remaining, aliases) match {
case Right(Result(node, tail)) => go(node :: out, tail)
case Left(error) => Left(error)
}
}

go(List.empty, events)
}

private def composeNode(
events: List[Event],
aliases: mutable.Map[Anchor, Node]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ class YamlEncoderSuite extends munit.FunSuite:
)

val expected =
s"""-
s"""-
| int: 1
| double: 1.997
|-
|-
| int: 2
| double: 2.997
|""".stripMargin
Expand All @@ -50,10 +50,10 @@ class YamlEncoderSuite extends munit.FunSuite:
)

val expected =
s"""-
s"""-
| - 1
| - 2
|-
|-
| - 3
| - 4
|""".stripMargin
Expand All @@ -67,10 +67,10 @@ class YamlEncoderSuite extends munit.FunSuite:
val data = Data(Nested(1, "one"), Nested(2, "two"))

val expected =
s"""first:
s"""first:
| a: 1
| b: one
|second:
|second:
| a: 2
| b: two
|""".stripMargin
Expand Down Expand Up @@ -115,9 +115,9 @@ class YamlEncoderSuite extends munit.FunSuite:

val data = Person(Address("Anytown"), Seq(1, 2))
val expected =
s"""address:
s"""address:
| city: Anytown
|ints:
|ints:
| - 1
| - 2
|""".stripMargin
Expand Down Expand Up @@ -155,15 +155,15 @@ class YamlEncoderSuite extends munit.FunSuite:
)
)
val expected = """version: 3.9
|services:
| web:
|services:
| web:
| build: .
| ports:
| ports:
| - 5000:5000
| volumes:
| volumes:
| - .:/code
| - logvolume01:/var/log
| redis:
| redis:
| image: redis:alpine
|""".stripMargin

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ class NodeCreationSuite extends munit.FunSuite:

assertEquals(
node.asYaml,
"""|-
"""|-
| key: value
|-
|-
| key2: value2
| seq:
| seq:
| - v1
| - v2
|- standalone value
Expand Down
Loading