Sign in

    moonyaml

    YAML for MoonBit.

    yaml
    config
    moonbit
    Download zip
    Version
    0.1.2
    License
    Apache-2.0
    Last updated
    2 days ago
    Downloads
    1K

    No dependencies

    #moonyaml

    YAML for MoonBit, in five versions and two shapes.

    A document can be read as a value — the Json tree every reader in this family produces — or as a document, a tree that keeps the comments, the anchors, the tags and the style it was written with, so it can be written back. Both come from one scanner: a version is a resolver, not a fork.

    let config = @moonyaml.loads(
    #|server:
    #| host: 0.0.0.0
    #| port: 12000
    #|limits: &shared { files: 1000, bytes: 1048576 }
    #|upload: *shared
    )

    @moonyaml.dumps(config) // back to YAML, block style
    @moonyaml.loads_all(stream) // every document in a `---` stream

    let doc = @moonyaml.parse(src) // keeps the comments too
    @moonyaml.emit(doc) // and writes them back
    doc.to_json() // what it means, for a caller who wants a value

    #Install

    moon add moonbitstack/moonyaml

    #The packages

    PackageWhat it is
    moonbitstack/moonyamlYAML 1.2.2's core schema, the writer, the document model, and the typed codec
    moonbitstack/moonyaml/yaml11YAML 1.1: yes/no/on/off as booleans, 014 as octal, sexagesimals, 1_000, and the << merge key
    moonbitstack/moonyaml/yaml10YAML 1.0's types, which 1.1 inherited but did not always keep
    moonbitstack/moonyaml/strictStrictYAML: block structure only, every scalar a string, no flow style, no anchors, no tags, no duplicate keys
    moonbitstack/moonyaml/anyReads the version a document asks for with %YAML, and 1.2.2 when it asks for nothing

    They are packages rather than a flag because a version is a promise about meaning: yes is a boolean in one and a string in another, and a reader that decides which by looking at the file is a reader whose output a caller cannot type. any exists for the caller who is reading files they did not write, and it is the only package that guesses — from the document, once, at the top.

    #The interface

    FunctionGives 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

    Each version package carries the same loads/loads_all/load/load_all/ dumps/dumps_all, so a caller can take one as a parameter. tests/versions holds a Version record for exactly that, and the examples use it.

    #What it reads

    CollectionsBlock mappings and sequences, flow {} and [], nested in either direction, a sequence in its parent key's own column
    ScalarsPlain 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
    StructureAnchors and aliases, tags including %TAG handles and verbatim !<…>, %YAML, --- and ... markers, ? key explicit pairs, complex keys
    ResolutionThe core schema of §10.2: null/~, booleans, decimal, 0o and 0x integers, floats with exponents, .inf and .nan

    yes and no are strings here, as YAML 1.2 says they are. They are booleans in 1.1, which is yaml11, and the difference is a package boundary rather than a flag — see the suite table below for exactly where the versions part company.

    #The versions, measured

    The YAML Test Suite is 402 cases. This build reads 363 of them as the suite says — 271 compared against the JSON the suite gives, 92 refused where the suite requires a refusal — and answers two differently; 37 cases carry no JSON at all (complex keys, duplicate keys) and are skipped by the comparison.

    VersionSame as 1.2.2Reads differentlyRefused by bothRefused only hereRead only here
    1.2.2 (core)292011000
    1.1291111000
    1.0290211000
    StrictYAML155251101120

    Every version reads every document 1.2.2 reads and refuses every document it refuses; the columns that would show otherwise are zero for each of them. That is the claim the shared scanner makes, and the table is the test that checks it: it is asserted in src/tests/versions/suite_wbtest.mbt, so a change that moves a number moves this README too or fails the build.

    What the differing cases are: 12:30 is a sexagesimal (750) in 1.1 and 1.0 and a string in 1.2; !!-tagged documents resolve against the 1.0 tag set differently; StrictYAML reads every scalar as a string and refuses the 112 documents that use flow style, anchors, aliases or tags.

    #The two cases read differently

    Both are tab faults the suite wants refused and this reader reads:

    CaseWhy
    DK95/2A tab where a continuation line's indentation belongs
    Y79Y/4A tab-only line at the head of a block scalar

    PyYAML, libyaml and ruamel.yaml all read both, which is why this reader does too: refusing them would refuse documents that load everywhere else. The list is pinned in src/tests/core/harness.mbt and cannot rot — a case that starts passing fails the test.

    #The document model, measured

    Read as a document, 278 of the 292 documents this reader reads agree value for value with the value reader. The other 14 are listed in src/tests/core/doc_wbtest.mbt with their reason: twelve are constructs the value reader understands and the document reader refuses to guess at (an implicit key in a flow collection, an alias where a key belongs), one is a comment after an alias, one is an explicit document that holds nothing.

    #Configuration

    Two records, each with presets, each field overridable, and every knob reachable as a named argument on the call:

    @moonyaml.loads(src, flavor=@moonyaml.Flavor::new(duplicates=Reject))
    @moonyaml.loads(src, flavor={ ..@moonyaml.strict, budget: 1000 })
    @moonyaml.dumps(value, style=@moonyaml.flow)
    @moonyaml.dumps(value, indent=4, sort=true)

    A flat argument wins over a field of the record, which wins over the preset's value. There is no other way to change a default, and no default is hidden.

    FlavorPresetWhere the number comes from
    depth500Between Jackson's 1000 and serde_json's 128, and the same as this family's JSON reader
    duplicatesLastWhat every loader in reach does. First and Reject are there because a key written twice is usually a mistake
    aliasestrueAn alias is part of the language; false refuses one outright
    budget10 000 000An alias can name a node holding aliases, so a dozen lines can expand to billions — the "billion laughs", which libyaml and PyYAML's safe_load both still perform faithfully. There is no common value to follow, so this takes the safe side

    StylePresetWhere the number comes from
    indent2What Kubernetes manifests and Compose files use; the value kubectl writes
    width80Where a flow collection stops fitting on a line — the width every formatter has used since the terminal
    layoutBlockBlock style reads better in a diff; Flow writes JSON's own shape, Auto decides per collection by width
    quotesMinimalQuote only what has to be quoted, which is what a person writing YAML does
    anchorstrueA collection that appears twice is written once and referenced, which is what PyYAML and ruamel.yaml do with a shared object
    sortfalseA document keeps the order it was written in; a caller who wants a stable file turns this on

    dumps_all opens every document with its own ---. Both conventions are in use — PyYAML and Go's yaml.v3 write the marker only between documents, kubectl writes it before each — and this takes the one that stays correct when documents are concatenated. The reader accepts either.

    #What it refuses

    Everything it cannot resolve to one reading. YAML's own answer to ambiguity is to be permissive, which is how a configuration file comes to mean something other than what it looks like; this reader raises Refused with a line and a column instead:

    a: [1, 2 => 1:4: ends mid-document a: *nope => 1:4: no anchor named `nope` a: 1, a: 2 => 2:1: the key `a` is written twice

    A mapping key that is itself a collection is refused by loads, because the tree it produces has string keys and there is no honest way to spell a sequence as one — parse keeps it, since a document has keys that are not strings.

    A tag from a vocabulary this reader does not have — a local !foo, a !!python/object, an unregistered tag:example.com,2000:app/foo — is not one of those. §10.4 permits a processor to resolve such a node as if it carried no tag, and that is what happens: the scalar keeps the type its text has, so !!nope 1 is the number 1. PyYAML raises ConstructorError instead, and the difference is deliberate — a reader that refuses !!python/object cannot read the file at all, and one that quietly turns it into a mapping has invented a type nobody asked for. parse keeps the tag on the node, so a document can be written back with it intact.

    #Where this differs from the readers you know

    PyYAML 6ruamel.yamllibyaml (C)moonyaml
    yes, onbooleanboolean (1.1 mode)booleanstring in moonyaml, boolean in yaml11
    01412121214 in moonyaml (§10.2 makes it decimal), 12 in yaml11
    0o14stringstringstring12 in moonyaml, string in yaml11
    Duplicate keyslast wins, silentlywarning, then last winslast winslast wins, or First/Reject — a choice
    Unknown tagConstructorErrorConstructorErroraccepted, tag ignoredaccepted, resolved untagged (§10.4); parse keeps it
    Alias expansionunboundedunboundedunboundedcapped at 10 000 000 nodes
    A collection as a keytuple keytuple keyacceptedrefused by loads, kept by parse
    Comments, styledroppedkeptdroppeddropped by loads, kept by parse
    Repeats on writeanchor + aliasanchor + alias—anchor + alias, or anchors: false
    --- on writebetween documentsbetween documents—before every document
    Tabs in indentationread as separationread as separationread as separationread as separation

    ruamel.yaml is the only one of them that keeps comments, and it is the model the document tree here follows: read, change, write, and get the file back.

    #Tests

    moon test --target all # 61 tests, four backends

    Beyond the unit tests there are four gates, and each is a test rather than a script, so it runs in CI:

    • the suite, as YAML 1.2.2, against every case (src/tests/core)
    • the suite beside 1.2.2, for every version (src/tests/versions)
    • the document model beside the value model, over the same suite (src/tests/core/doc_wbtest.mbt)
    • the divergence lists, which fail in either direction: a new difference is a decision, and a listed difference that starts passing is a stale claim

    scripts/gen_suite.py regenerates the suite data from a checkout of yaml-test-suite, and scripts/sync_diverged.py rewrites a divergence list from a run. Neither is needed to build or test.

    #Examples

    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 trip

    The guide and the API reference are at moonbitstack.github.io/moonyaml.

    That site is plain files in site/ — a homepage, the guide, and the three programs above with the output they print — and there is no build step: what is in the repository is what is served. .github/workflows/site.yml publishes it to GitHub Pages on every change, after checking that no relative link in it dangles.

    #Licence

    Apache-2.0. See LICENSE.

    Malformed

    Why a document did not become the value the caller asked for.

    Two things can go wrong, and they are different things: the YAML may not have been read at all, or it may have been read and not be the shape a type needs. Both carry where — the reader's position, or the path into the document — so a caller can say which line to look at.
    impl Show for Malformed

    Malformed::output

    fn Malformed::output(self : Malformed, logger : &Logger) -> Unit

    Malformed::to_string

    fn Malformed::to_string(self : Malformed) -> String

    Refused

    pub(all) suberror Refused {
    Unexpected(At, Char)
    Truncated(At)
    BadEscape(At)
    BadIndent(At)
    BadTag(At, String)
    BadAnchor(At, String)
    Repeated(At, String)
    Trailing(At)
    TooDeep(At)
    Exceeded(at~ : At, limit~ : Int, got~ : Int)
    BadUtf8(At)
    Disallowed(At, String)
    } derive(Eq,
    Debug
    )

    Why a document was refused.

    YAML's own answer to ambiguity is to be permissive, which is how a configuration file comes to mean something other than what it looks like. This reader refuses instead: anything it cannot resolve to one reading is an error with a position, never a guess.
    impl Show for Refused

    Refused::at

    fn Refused::at(self : Refused) -> At

    Where the refusal happened.

    Every variant carries a position, so a caller that wants to report the line and column does not have to match all twelve of them to get at it.

    Refused::equal

    fn Refused::equal(Refused, Refused) -> Bool

    Refused::not_equal

    fn Refused::not_equal(x : Refused, y : Refused) -> Bool

    Refused::output

    fn Refused::output(self : Refused, logger : &Logger) -> Unit

    Refused::to_repr

    Refused::to_string

    fn Refused::to_string(self : Refused) -> String

    pub(all) struct At {
    at : Int
    line : Int
    column : Int
    } derive(Eq,
    Debug
    )

    Where in the source something is: the offset in code units, and the line and column a person counts from one.

    Every refusal carries one, because a configuration file that will not load is read by a person who needs to know which line to look at.
    impl Show for At

    At::equal

    fn At::equal(At, At) -> Bool

    At::not_equal

    fn At::not_equal(x : At, y : At) -> Bool

    At::output

    fn At::output(self : At, logger : &Logger) -> Unit

    At::to_repr

    At::to_string

    fn At::to_string(self : At) -> String

    Duplicates

    pub(all) enum Duplicates {
    Last
    First
    Reject
    } derive(Eq,
    Debug
    )

    What to do when a mapping names the same key twice.

    YAML 1.2.2 §3.2.1.1 says the keys of a mapping are unique and leaves the handling of a duplicate to the processor. Every loader in reach takes the last one, which is what Last does; First and Reject are here because a configuration file with a key written twice is usually a mistake someone would rather be told about.

    Duplicates::equal

    fn Duplicates::equal(Duplicates, Duplicates) -> Bool

    Duplicates::not_equal

    fn Duplicates::not_equal(x : Duplicates, y : Duplicates) -> Bool

    Flavor

    pub(all) struct Flavor {
    depth : Int
    duplicates : Duplicates
    aliases : Bool
    budget : Int
    } derive(Eq,
    Debug
    )

    How a document is read: the limits, and what to do with a duplicate key.

    The preset is [strict]. Build another with [Flavor::new], or update one in place with { ..strict, depth: 64 }.

    Flavor::equal

    fn Flavor::equal(Flavor, Flavor) -> Bool

    Flavor::new

    fn Flavor::new(depth? : Int, duplicates? : Duplicates, aliases? : Bool, budget? : Int) -> Flavor

    A flavor by name, every knob with the preset's value.

    Flavor::not_equal

    fn Flavor::not_equal(x : Flavor, y : Flavor) -> Bool

    Flavor::to_repr

    Kind

    pub(all) enum Kind {
    Null
    Bool(Bool)
    Number(Double)
    Str(String)
    Sequence(Array[Node])
    Mapping(Array[(Node, Node)])
    Alias(String)
    }

    What a node is.

    An alias is a node of its own rather than a copy of what it names: a document with an anchor and three aliases is three references, and writing it back as twelve copies would be a different document.

    Layout

    pub(all) enum Layout {
    Block
    Flow
    Auto
    } derive(Eq,
    Debug
    )

    How a collection is laid out.

    Block is the style a configuration file is written in: one entry per line, a person can read it and a diff shows one change per line. Flow is the brace and bracket form, which JSON shares and which a machine-generated file often wants. Auto picks per collection: flow when it holds nothing but scalars and the whole of it fits in [Style::width], block otherwise. A collection of collections stays block, because that is the shape of a configuration file.

    Layout::equal

    fn Layout::equal(Layout, Layout) -> Bool

    Layout::not_equal

    fn Layout::not_equal(x : Layout, y : Layout) -> Bool

    Layout::to_repr

    Node

    pub(all) struct Node {
    kind : Kind
    text : String
    style : ScalarStyle
    flow : Bool
    anchor : String?
    tag : String?
    before : Array[String]
    after : String?
    }

    One node of a document.

    Node::anchored

    fn Node::anchored(self : Node, name : String) -> Node

    This node, anchored.

    Node::commented

    fn Node::commented(self : Node, before~ : Array[String], after~ : String) -> Node

    This node, with the comments written around it.

    Node::in_flow

    fn Node::in_flow(self : Node, flow : Bool) -> Node

    This node, written in flow style.

    Node::map

    fn Node::map(pairs : Array[(Node, Node)]) -> Node

    A block mapping.

    Node::of_json

    fn Node::of_json(value : Json) -> Node

    A tree for a Json value: the document a program would write for it.

    Node::of_kind

    fn Node::of_kind(kind : Kind, text : String) -> Node

    A scalar of a known kind, keeping the spelling it was read from.

    Node::same

    fn Node::same(self : Node, other : Node) -> Bool

    Whether two documents would be written the same way.

    The comparison a round trip is: read, write, read again, and the second tree is the first one. Positions are not in the tree, so this is equality of content.

    Node::scalar

    fn Node::scalar(text : String) -> Node

    A plain scalar, which is how most of YAML is written.

    Node::seq

    fn Node::seq(items : Array[Node]) -> Node

    A block sequence.

    Node::styled

    fn Node::styled(self : Node, style : ScalarStyle) -> Node

    This node, written in a particular scalar style.

    Node::tagged

    fn Node::tagged(self : Node, tag : String) -> Node

    This node, tagged.

    Node::to_json

    fn Node::to_json(self : Node) -> Json

    The Json this document denotes, with its aliases expanded.

    Quotes

    pub(all) enum Quotes {
    Minimal
    Single
    Double
    } derive(Eq,
    Debug
    )

    Which quote a string gets when it needs one.

    Minimal quotes only what would otherwise read as something else, which is what makes the output look written rather than generated. Single and Double are for a caller whose output feeds a second reader that is happier with one form.

    Quotes::equal

    fn Quotes::equal(Quotes, Quotes) -> Bool

    Quotes::not_equal

    fn Quotes::not_equal(x : Quotes, y : Quotes) -> Bool

    Quotes::to_repr

    Rules

    pub(all) struct Rules {
    yes_no : Bool
    letters : Bool
    octal : Bool
    binary : Bool
    underscores : Bool
    sexagesimal : Bool
    strings : Bool
    } derive(Eq,
    Debug
    )

    What a YAML version resolves an unquoted scalar to.

    The three released versions disagree with each other, and the disagreements are all here rather than in the scanner: 1.1 reads yes as a boolean, 014 as twelve and 1:30 as ninety, 1.2 reads all three as strings, and StrictYAML reads every scalar as a string whatever it looks like. A version is a set of rules over one reader, which is why this library has one parser and not four.

    A dialect nobody has published yet — a configuration language of your own, a pre-1.0 variant — is a Rules of your own, passed through [Schema]. Nothing here is closed.

    Rules::equal

    fn Rules::equal(Rules, Rules) -> Bool

    Rules::not_equal

    fn Rules::not_equal(x : Rules, y : Rules) -> Bool

    Rules::to_repr

    ScalarStyle

    pub(all) enum ScalarStyle {
    Plain
    Single
    Double
    Literal(Int)
    Folded(Int)
    } derive(Eq)

    How a scalar was written.

    A block scalar carries the chomping indicator that was written with it, because | and |- are different strings at the end of a line.

    ScalarStyle::equal

    fn ScalarStyle::equal(ScalarStyle, ScalarStyle) -> Bool

    ScalarStyle::not_equal

    fn ScalarStyle::not_equal(x : ScalarStyle, y : ScalarStyle) -> Bool

    Schema

    pub(all) struct Schema {
    resolve : (String, Bool, String?, At, Rules) -> Json raise Refused
    rules : Rules
    merge : Bool
    tags : Bool
    anchors : Bool
    flow : Bool
    secondary : String
    primary : String
    }

    What a YAML version does that the scanner cannot decide for it.

    The scanner in this package is shared; what a version resolves a scalar to, and what it allows besides, is a Schema.

    The one in this package is [core], the core schema of §10.2. The v11, v10, strict and any packages beside it are each a schema over the same scanner, which is why a version is a resolver rather than a fork of the reader.

    Style

    pub(all) struct Style {
    indent : Int
    width : Int
    layout : Layout
    quotes : Quotes
    anchors : Bool
    sort : Bool
    } derive(Eq,
    Debug
    )

    How a document is written: the indentation, the line width, the layout, the quotes, and whether a repeated subtree is written once and referred to after.

    The preset is [style]. Build another with [Style::new], or update one in place with { ..style, width: 120 }.

    Style::equal

    fn Style::equal(Style, Style) -> Bool

    Style::new

    fn Style::new(indent? : Int, width? : Int, layout? : Layout, quotes? : Quotes, anchors? : Bool, sort? : Bool) -> Style

    A style by name, every knob with the preset's value.

    Style::not_equal

    fn Style::not_equal(x : Style, y : Style) -> Bool

    Style::to_repr

    ancient_rules

    let ancient_rules : Rules

    YAML 1.0's, which is 1.1 plus the single letters y and n. The 1.1 revision removed them because a lone n is more often a name than a no.

    budget

    let budget : Int

    How many nodes the aliases in one document may expand to.

    An alias can name a node that itself contains aliases, so a dozen lines can expand to billions — the "billion laughs" of YAML, which libyaml and PyYAML's safe_load both still perform faithfully. There is no common value to follow here, so this takes the safe side: enough for any real document, far short of exhausting memory. Flavor::new(budget=...) raises it, and aliases=false refuses them outright.

    core

    let core : Schema

    The core schema of YAML 1.2.2 §10.2: what this package reads.

    core_rules

    let core_rules : Rules

    YAML 1.2's core schema: nothing but true/false, null, and the decimal, 0o and 0x numbers of §10.2.

    declared_version

    fn declared_version(src : StringView) -> (Int, Int)?

    The version a stream declares in its first %YAML directive, if it declares one.

    A reader may pick its rules from the document itself rather than from the caller, which is what [any] does. Only the first directive counts: §6.8.1 allows one per document, and the one that decides how the rest is read has to come before the rest.

    depth

    let depth : Int

    How deep a document may nest.

    500 is what this family uses for JSON, between Jackson's 1000 and serde_json's 128, and a YAML document that nests deeper than a JSON one would is not a configuration file.

    dump

    fn dump(value : Json, style? : Style, rules? : Rules, indent? : Int, sort? : Bool) -> Bytes

    The same, as UTF-8 bytes — the s on [dumps] is the one Python puts on the form that answers a string.

    dump_all

    fn dump_all(values : ArrayView[Json], style? : Style, rules? : Rules, indent? : Int, sort? : Bool) -> Bytes

    The same, as UTF-8 bytes.

    dumps

    fn dumps(value : Json, style? : Style, rules? : Rules, indent? : Int, sort? : Bool) -> String

    Write a Json tree as a YAML document.

    Block style by default, which is what a configuration file is written in and what a person reads. A string is quoted only when plain would read back as something else — a number, a boolean, a null, or not at all — so the output looks like a file someone wrote rather than one a program emitted.

    This is the reason to have a writer at all: a generator that builds YAML by joining strings gets the escaping wrong the first time a value contains a colon, and gets the indentation wrong the first time a value is a list.

    style says how; indent and sort override the two knobs that change from call to call. A flat parameter wins over the record — see "Configuration" in the README.

    dumps_all

    fn dumps_all(values : ArrayView[Json], style? : Style, rules? : Rules, indent? : Int, sort? : Bool) -> String

    A stream of documents, each opened by its own ---.

    The separator goes before each document rather than between them. The two conventions are both in use — PyYAML and Go's yaml.v3 write it only between, kubectl writes it before each — and this takes the one that stays correct under concatenation: two streams joined end to end still read as their documents, and appending one is appending lines. The reader accepts either.

    dumps_of

    fn[T : ToJson] dumps_of(value : T, style? : Style, indent? : Int, sort? : Bool) -> String

    A value of type T, written as a YAML document.

    The other direction of [loads_as], through the same ToJson trait, so a type that writes itself as JSON writes itself as YAML without knowing it.

    dumps_of_all

    fn[T : ToJson] dumps_of_all(values : ArrayView[T], style? : Style, indent? : Int, sort? : Bool) -> String

    A stream of values, each written as its own document.

    emit

    fn emit(node : Node, indent? : Int, from? : Int) -> String

    Emit a document as YAML, keeping the styles and comments it was read with.

    indent is how far a nested block is indented, and from is how far the root already is — a document embedded in another one needs the second.

    flow

    let flow : Style

    The same document, all in flow style — the form JSON shares.

    indent

    let indent : Int

    How much a nested level is indented.

    Two spaces: what Kubernetes manifests, Compose files and every configuration example in the wild use, and what a reader's eye is trained on.

    legacy_rules

    let legacy_rules : Rules

    YAML 1.1's type resolution (§10.3 of the 1.1 specification, which is what libyaml, PyYAML and Ruby's Psych implement): the booleans, the octal and binary integers, the underscored numbers, and the base-60 form.

    load

    fn load(src : BytesView, flavor? : Flavor, schema? : Schema, bom? : Bool) -> Json raise Refused

    The one document in src, decoded as UTF-8.

    bom skips a leading byte-order mark, which YAML 1.2.2 §5.2 allows at the start of a stream and which an editor on Windows will happily write.

    load_all

    fn load_all(src : BytesView, flavor? : Flavor, schema? : Schema, bom? : Bool) -> Array[Json] raise Refused

    Every document in src, decoded as UTF-8.

    load_as

    fn[T :
    FromJson
    ] load_as(src : BytesView, flavor? : Flavor, schema? : Schema, bom? : Bool) -> T raise Malformed

    The one document in src, decoded as UTF-8, as a value of type T.

    loads

    fn loads(src : StringView, flavor? : Flavor, schema? : Schema) -> Json raise Refused

    The one document in src.

    Raises [Refused::Trailing] if a second document follows, which is what tells a caller expecting one file's worth of configuration that it got a stream.

    schema says which YAML version reads it; the default is [core]. A document that opens with %YAML 1.1 is not read as 1.2 by default — pick the version, or let the any package pick it for you.

    loads_all

    fn loads_all(src : StringView, flavor? : Flavor, schema? : Schema) -> Array[Json] raise Refused

    Every document in src, in order.

    A stream with no documents at all is an empty array, not a null: nothing is not the same as one document containing nothing.

    loads_as

    fn[T :
    FromJson
    ] loads_as(src : StringView, flavor? : Flavor, schema? : Schema) -> T raise Malformed

    The one document in src, as a value of type T.

    let config : Config = @moonyaml.loads_as(src)

    T is anything that implements core's FromJson, which is the same trait a JSON document is read through: a type written for moonjson needs no second implementation to be read from YAML. That is the point of reading into the same Json tree — the shape of a value is one question, and which text it arrived in is another.

    The default schema is 1.2's core. Read a 1.1 file with @moonyaml/v11's schema, or let @moonyaml/any pick it from the document.

    parse

    fn parse(src : StringView, flavor? : Flavor, schema? : Schema) -> Node raise Refused

    One document, as a tree.

    parse_all

    fn parse_all(src : StringView, flavor? : Flavor, schema? : Schema) -> Array[Node] raise Refused

    Every document in the stream, as trees.

    A stream with nothing in it holds no document, which is what loads_all says too; parse is where a single document is owed, and it gives the null one.

    round_trip

    fn[T :
    FromJson
    + ToJson] round_trip(src : StringView, adjust? : (T) -> Unit, flavor? : Flavor, schema? : Schema) -> String raise Malformed

    Read a document as T, let adjust change it, and write it back.

    The three steps a configuration loader actually takes: read what the user wrote, fill in what they left out, write the whole of it back. adjust is where the defaults go, and it is also what says which type this is — a call with no adjust cannot tell.

    strict

    let strict : Flavor

    The preset: refuse what is ambiguous, take the last of a repeated key, expand aliases within the budget.

    style

    let style : Style

    The preset: two-space block style, folded at 80 columns, quoting only what has to be quoted, anchors for what repeats.

    Block, not Auto: what this writes is a configuration file, and a configuration file is read one entry to a line. Auto is a knob for output whose reader is a machine.

    width

    let width : Int

    The column a writer folds at.

    80: the width yamllint defaults to, which is itself the width of a terminal and of the Python PEP 8 convention the tooling around YAML grew up beside.