YAML 1.0, 1.1 and 1.2 for MoonBit.
test {
// A `#|` literal carries no last break, so the text it stands for ends in one.
let src =
#|server:
#| host: 0.0.0.0
#| port: 12000
#|limits:
#| files: 1000
#| bytes: 1048576
let text = src + "\n"
let config = @moonyaml.loads(text)
assert_eq(
config.stringify(),
"{\"server\":{\"host\":\"0.0.0.0\",\"port\":12000},\"limits\":{\"files\":1000,\"bytes\":1048576}}",
)
assert_eq(@moonyaml.dumps(config), text)
}test {
let src =
#|# the deployment
#|name: web
#|ports: [80, 443]
let text = src + "\n"
let doc = @moonyaml.parse(text)
// The document, written back with the comment and the flow sequence it had.
assert_eq(@moonyaml.emit(doc), text)
// The value it means.
assert_eq(doc.to_json().stringify(), "{\"name\":\"web\",\"ports\":[80,443]}")
}moon add moonbitstack/moonyaml| Package | What it is |
|---|---|
| moonbitstack/moonyaml | YAML 1.2.2's core schema, the writer, the document model, and the typed codec |
| moonbitstack/moonyaml/yaml11 | YAML 1.1: yes/no/on/off as booleans, 014 as octal, sexagesimals, 1_000, and the << merge key |
| moonbitstack/moonyaml/yaml10 | YAML 1.0's types, which 1.1 inherited but did not always keep |
| moonbitstack/moonyaml/strict | The strict dialect: block structure only, every scalar a string, no flow style, no anchors, no tags, no repeated keys |
| moonbitstack/moonyaml/any | Reads the version a document asks for with %YAML, and 1.2.2 when it asks for nothing |
| Function | Gives back |
|---|---|
| loads(src) / loads_all(src) | Json — the one document, or every document in the stream |
| load(bytes) / load_all(bytes) | The same, decoding UTF-8 and skipping a leading BOM |
| loads_as[T](src) / load_as[T](bytes) | A typed value, through @json.FromJson |
| dumps(value) / dumps_all(values) | YAML text |
| dump(value) / dump_all(values) | The same, as Bytes |
| dumps_of[T](value) / dumps_of_all[T](values) | The same, through @json.ToJson |
| round_trip[T](src, adjust~) | A typed value written back, for checking that a decode is a decode |
| parse(src) / parse_all(src) | Node — the document, comments and all |
| emit(node) | YAML text that reads back as the same document |
| events_all(nodes) / node.events() | The parse, as the event stream a reader would report |
| declared_version(src) | The %YAML a document asks for, or None |
| Collections | Block mappings and sequences, flow {} and [], nested in either direction, a sequence in its parent key's own column, a collection as a key |
| Scalars | Plain over several lines (breaks fold, blank lines do not), single- and double-quoted with the escapes of §5.7, literal \| and folded > with chomping and explicit indentation |
| Structure | Anchors and aliases, tags including %TAG handles and verbatim !<…>, %YAML, --- and ... markers, ? key explicit pairs, complex keys |
| Resolution | The core schema of §10.2: null/~, booleans, decimal, 0o and 0x integers, floats with exponents, .inf and .nan |
test {
let src = "enabled: yes\nmask: 014\nstarts: 12:30\n"
assert_eq(
@moonyaml.loads(src).stringify(),
"{\"enabled\":\"yes\",\"mask\":14,\"starts\":\"12:30\"}",
)
assert_eq(
@yaml11.loads(src).stringify(),
"{\"enabled\":true,\"mask\":12,\"starts\":750}",
)
assert_eq(
@strict.loads(src).stringify(),
"{\"enabled\":\"yes\",\"mask\":\"014\",\"starts\":\"12:30\"}",
)
}test {
let src =
#|# the deployment
#|name: web
#|ports: [80, 443]
#|motd: |
#| one
#| two
let text = src + "\n"
let doc = @moonyaml.parse(text)
match doc.kind {
Mapping(pairs) => assert_eq(pairs[2].0.text, "motd")
_ => fail("a mapping")
}
assert_eq(@moonyaml.emit(doc), text)
}test {
let src = "limits: &shared {files: 1000}\nupload: *shared\n"
// A value has no anchors in it: the alias is expanded, and both sides are the
// same mapping.
assert_eq(
@moonyaml.loads(src).stringify(),
"{\"limits\":{\"files\":1000},\"upload\":{\"files\":1000}}",
)
// A document keeps them, and writes them back as they were written.
assert_eq(@moonyaml.emit(@moonyaml.parse(src)), src)
// Written from the value, the anchors a writer invents are its own.
assert_eq(
@moonyaml.dumps(@moonyaml.loads(src)),
"limits: &a0\n files: 1000\nupload: *a0\n",
)
}test {
let doc = @moonyaml.parse("name: web\n")
assert_eq(
doc.events(),
"+DOC\n +MAP\n =VAL :name\n =VAL :web\n -MAP\n-DOC\n",
)
}+DOC
+MAP
=VAL :name
=VAL :web
-MAP
-DOCtest {
let nested = "a:\n b:\n c: 1\n"
assert_eq(@moonyaml.emit(@moonyaml.parse(nested)), nested)
assert_eq(
@moonyaml.emit(@moonyaml.parse(nested), indent=4),
"a:\n b:\n c: 1\n",
)
assert_eq(
@moonyaml.dumps(@moonyaml.loads("b: 1\na: 2\n"), sort=true),
"a: 2\nb: 1\n",
)
assert_eq(
@moonyaml.dumps(@moonyaml.loads("a: {b: [1, 2]}\n"), style=@moonyaml.flow),
"{a: {b: [1, 2]}}\n",
)
}| Flavor | Preset | Why that value |
|---|---|---|
| depth | 500 | Deeper than any configuration file nests, shallow enough that the reader's own recursion cannot be driven off the stack |
| duplicates | Last | A mapping names each key once (§3.2.1.1); where one is written twice, the last is the one the document ends with. First and Reject are there because a repeated key is usually a mistake |
| aliases | true | An alias is part of the language; false refuses one outright |
| budget | 10 000 000 | Nodes one document's aliases may expand to. A dozen lines of aliases can name billions of nodes, so the expansion is counted and capped rather than performed |
| Style | Preset | Why that value |
|---|---|---|
| indent | 2 | Two columns per level, which is what a hand-written configuration file uses and what fits a diff |
| width | 80 | Where a collection stops fitting on a line, and where a plain scalar that runs past it is folded at a space |
| layout | Block | Block style reads better in a diff; Flow writes JSON's own shape, Auto decides per collection by width |
| quotes | Minimal | Quote only what has to be quoted, which is what a person writing YAML does |
| anchors | true | A collection that appears twice is written once and referenced |
| sort | false | A document keeps the order it was written in; a caller who wants a stable file turns this on |
test {
let docs = @moonyaml.loads_all("a: 1\n---\nb: 2\n")
assert_eq(@moonyaml.dumps_all(docs[:]), "---\na: 1\n---\nb: 2\n")
// Concatenating two streams still reads as four documents, not as a fault.
let text = @moonyaml.dumps_all(docs[:])
assert_eq(@moonyaml.loads_all(text + text).length(), 4)
}test {
let refused = fn(src : String) -> String {
try {
@moonyaml.loads(src) |> ignore
"read"
} catch {
e => e.to_string()
}
}
assert_eq(refused("a: [1, 2\n"), "1:4: ends mid-document")
assert_eq(refused("a: *nope\n"), "1:4: no anchor named `nope`")
}test {
let said = try {
@moonyaml.loads("a: 1\na: 2\n", flavor=@moonyaml.Flavor::new(duplicates=Reject)) |> ignore
"read"
} catch {
e => e.to_string()
}
assert_eq(said, "2:1: the key `a` is written twice")
}moon run src/examples/tour # everything it reads, in one file
moon run src/examples/versions # one document, read five ways
moon run src/examples/document # the document model, and a round trippub(all) struct Flavor {
depth : Int
duplicates : Duplicates
aliases : Bool
budget : Int
} derive(Eq, Debug)pub(all) struct Node {
kind : Kind
text : String
style : ScalarStyle
flow : Bool
anchor : String?
tag : String?
before : Array[String]
after : String?
explicit : Bool
closed : Bool
}let budget : Intfn declared_version(src : StringView) -> (Int, Int)?let depth : Intlet indent : Intlet config : Config = @moonyaml.loads_as(src)let width : IntInstall
Download zipYAML 1.0, 1.1 and 1.2 for MoonBit.