Skip to main content

src/yum@yaml.erl

-module(yum@yaml).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/yum/yaml.gleam").
-export([parse_stream/1, parse/1, from_node/1, resolve/1, root/1, directives/1, diagnostics/1, get/2, get_keys/1, get_values/1, decode/2, to_string/1]).
-export_type([decode_error/0, directive/0, yaml/0, yaml_internal/0]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

?MODULEDOC(
    " Parse, resolve, query, decode, and emit YAML documents.\n"
    "\n"
    " This is the main public module. It parses strings into opaque YAML\n"
    " documents, resolves YAML-level metadata such as aliases and tags, retrieves\n"
    " nested nodes by path, decodes documents with `gleam/dynamic/decode`, and\n"
    " emits YAML strings from parsed or built documents.\n"
    "\n"
    " ```gleam\n"
    " import gleam/option\n"
    " import yum/yaml\n"
    " import yum/yaml/node\n"
    "\n"
    " pub fn example() {\n"
    "   let assert Ok(document) = yaml.parse(\"name: yum\")\n"
    "\n"
    "   let name =\n"
    "     document\n"
    "     |> yaml.get([node.Key(\"name\")])\n"
    "     |> option.map(node.as_string)\n"
    "\n"
    "   assert name == option.Some(Ok(\"yum\"))\n"
    " }\n"
    " ```\n"
    "\n"
    " Use [`parse`](#parse) when you want a single YAML document value. Use\n"
    " [`parse_stream`](#parse_stream) for YAML streams containing zero or more\n"
    " explicit documents.\n"
    "\n"
    " Parsed YAML is raw syntax. Pipe it into [`resolve`](#resolve) to run the\n"
    " semantic YAML phase that validates anchors, aliases, directives, and tags.\n"
    "\n"
).

-type decode_error() :: {parse_error, yum@yaml@error:yaml_error()} |
    {resolve_error, list(yum@yaml@diagnostic:diagnostic())} |
    {unable_to_decode, list(gleam@dynamic@decode:decode_error())}.

-type directive() :: {directive, binary(), list(binary()), yum@yaml@node:span()}.

-opaque yaml() :: {raw, yaml_internal()} |
    {resolved, yaml_internal(), list(yum@yaml@diagnostic:diagnostic())}.

-type yaml_internal() :: {yaml_internal,
        yum@yaml@node:node_(),
        list(yum@yaml@document:directive())}.

-file("src/yum/yaml.gleam", 280).
-spec raw_from_document(yum@yaml@document:document()) -> yaml().
raw_from_document(Document) ->
    {raw,
        {yaml_internal,
            yum@yaml@document:root(Document),
            yum@yaml@document:directives(Document)}}.

-file("src/yum/yaml.gleam", 273).
-spec count_indents(binary()) -> integer().
count_indents(Input) ->
    case Input of
        <<" "/utf8, Rest/binary>> ->
            1 + count_indents(Rest);

        _ ->
            0
    end.

-file("src/yum/yaml.gleam", 265).
-spec find_min_indent(list(binary())) -> {ok, integer()} |
    {error, yum@yaml@error:yaml_error()}.
find_min_indent(Lines) ->
    _pipe = Lines,
    _pipe@1 = gleam@list:map(_pipe, fun count_indents/1),
    _pipe@2 = gleam@list:map(_pipe@1, fun gleam@int:negate/1),
    _pipe@3 = gleam@list:max(_pipe@2, fun gleam@int:compare/2),
    gleam@result:replace_error(
        _pipe@3,
        yum@yaml@error:indent_normalization_error()
    ).

-file("src/yum/yaml.gleam", 254).
?DOC(
    " Normalizes the indentation of the YAML file.\n"
    " If all lines have <X leading whitespace, all lines will be trimmed X spaces\n"
    " going forward.\n"
).
-spec normalize_indents(binary()) -> {ok, binary()} |
    {error, yum@yaml@error:yaml_error()}.
normalize_indents(Input) ->
    Lines = gleam@string:split(Input, <<"\n"/utf8>>),
    gleam@result:'try'(
        find_min_indent(Lines),
        fun(X) ->
            Min_indent = gleam@int:absolute_value(X),
            _pipe = Lines,
            _pipe@1 = gleam@list:map(
                _pipe,
                fun(_capture) ->
                    gleam@string:drop_start(_capture, Min_indent)
                end
            ),
            _pipe@2 = gleam@string:join(_pipe@1, <<"\n"/utf8>>),
            {ok, _pipe@2}
        end
    ).

-file("src/yum/yaml.gleam", 240).
?DOC(
    " Normalizes whitespace in the YAML file.\n"
    " By default, turns every un-escaped tab (`\\t`) into the given amount of\n"
    " whitespaces.\n"
).
-spec normalize_whitespace(binary(), integer()) -> {ok, binary()} |
    {error, yum@yaml@error:yaml_error()}.
normalize_whitespace(Input, Tab_equivalent) ->
    gleam@bool:guard(Tab_equivalent =:= 0, {ok, Input}, fun() -> _pipe = Input,
            _pipe@1 = gleam@string:replace(
                _pipe,
                <<"\t"/utf8>>,
                gleam@string:repeat(<<" "/utf8>>, Tab_equivalent)
            ),
            {ok, _pipe@1} end).

-file("src/yum/yaml.gleam", 111).
-spec parse_document_stream(binary()) -> {ok,
        list(yum@yaml@document:document())} |
    {error, yum@yaml@error:yaml_error()}.
parse_document_stream(Input) ->
    gleam@result:'try'(
        normalize_whitespace(Input, 0),
        fun(Input@1) ->
            gleam@result:'try'(
                normalize_indents(Input@1),
                fun(Input@2) ->
                    gleam@result:'try'(
                        yum@yaml@lexer:lex(Input@2),
                        fun(Tokens) ->
                            gleam@result:'try'(
                                yum@yaml@parser:parse_document_stream(Tokens),
                                fun(Parsed) -> {ok, Parsed} end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/yum/yaml.gleam", 105).
?DOC(" Parses a YAML stream into a list of YAML documents.\n").
-spec parse_stream(binary()) -> {ok, list(yaml())} |
    {error, yum@yaml@error:yaml_error()}.
parse_stream(Input) ->
    _pipe = Input,
    _pipe@1 = parse_document_stream(_pipe),
    gleam@result:map(
        _pipe@1,
        fun(_capture) -> gleam@list:map(_capture, fun raw_from_document/1) end
    ).

-file("src/yum/yaml.gleam", 93).
?DOC(
    " Parses a YAML file into a YAML document.\n"
    "\n"
    " Follows the [YAML 1.2 specification](https://yaml.org/spec/1.2.2/)\n"
).
-spec parse(binary()) -> {ok, yaml()} | {error, yum@yaml@error:yaml_error()}.
parse(Input) ->
    gleam@result:'try'(parse_stream(Input), fun(Documents) -> case Documents of
                [Document] ->
                    {ok, Document};

                [_, _ | _] ->
                    {error, yum@yaml@error:multiple_documents()};

                [] ->
                    {error, yum@yaml@error:unexpected_end_of_input()}
            end end).

-file("src/yum/yaml.gleam", 125).
?DOC(
    " Creates a raw YAML document from a node.\n"
    "\n"
    " This is useful with [`yum/yaml/builder`](./yaml/builder.html), which builds\n"
    " node trees.\n"
).
-spec from_node(yum@yaml@node:node_()) -> yaml().
from_node(Node) ->
    {raw, {yaml_internal, Node, []}}.

-file("src/yum/yaml.gleam", 192).
-spec resolve_internal(yaml_internal()) -> {ok, yaml()} |
    {error, list(yum@yaml@diagnostic:diagnostic())}.
resolve_internal(Internal) ->
    {yaml_internal, Root, Directives} = Internal,
    case yum@yaml@resolver:resolve(Root, Directives) of
        {ok, {Root@1, Diagnostics}} ->
            {ok, {resolved, {yaml_internal, Root@1, Directives}, Diagnostics}};

        {error, Diagnostics@1} ->
            {error, Diagnostics@1}
    end.

-file("src/yum/yaml.gleam", 133).
?DOC(
    " Resolves raw YAML into composed YAML.\n"
    "\n"
    " Calling this on already-resolved YAML is a no-op.\n"
).
-spec resolve(yaml()) -> {ok, yaml()} |
    {error, list(yum@yaml@diagnostic:diagnostic())}.
resolve(Yaml) ->
    case Yaml of
        {resolved, _, _} ->
            {ok, Yaml};

        {raw, Internal} ->
            resolve_internal(Internal)
    end.

-file("src/yum/yaml.gleam", 292).
-spec internal(yaml()) -> yaml_internal().
internal(Yaml) ->
    case Yaml of
        {raw, Internal} ->
            Internal;

        {resolved, Internal, _} ->
            Internal
    end.

-file("src/yum/yaml.gleam", 142).
?DOC(" Returns the document root node.\n").
-spec root(yaml()) -> yum@yaml@node:node_().
root(Yaml) ->
    {yaml_internal, Root, _} = internal(Yaml),
    Root.

-file("src/yum/yaml.gleam", 287).
-spec public_directive(yum@yaml@document:directive()) -> directive().
public_directive(Directive) ->
    {directive, Name, Parameters, Span} = Directive,
    {directive, Name, Parameters, Span}.

-file("src/yum/yaml.gleam", 149).
?DOC(" Returns the document directives.\n").
-spec directives(yaml()) -> list(directive()).
directives(Yaml) ->
    {yaml_internal, _, Directives} = internal(Yaml),
    gleam@list:map(Directives, fun public_directive/1).

-file("src/yum/yaml.gleam", 156).
?DOC(" Returns non-fatal diagnostics collected while resolving the document.\n").
-spec diagnostics(yaml()) -> list(yum@yaml@diagnostic:diagnostic()).
diagnostics(Yaml) ->
    case Yaml of
        {raw, _} ->
            [];

        {resolved, _, Diagnostics} ->
            Diagnostics
    end.

-file("src/yum/yaml.gleam", 165).
?DOC(" Returns a nested node by mapping key or sequence index.\n").
-spec get(yaml(), list(yum@yaml@node:path_segment())) -> gleam@option:option(yum@yaml@node:node_()).
get(Yaml, Path) ->
    _pipe = Yaml,
    _pipe@1 = root(_pipe),
    yum@yaml@node:get(_pipe@1, Path).

-file("src/yum/yaml.gleam", 176).
?DOC(
    " Returns all keys from the root mapping.\n"
    "\n"
    " The keys are returned as nodes because YAML mappings can use scalar,\n"
    " sequence, or mapping nodes as keys. Returns [`ExpectedKind`](./yaml/node.html#AccessError)\n"
    " when the document root is not a mapping.\n"
).
-spec get_keys(yaml()) -> {ok, list(yum@yaml@node:node_())} |
    {error, yum@yaml@node:access_error()}.
get_keys(Yaml) ->
    _pipe = Yaml,
    _pipe@1 = root(_pipe),
    yum@yaml@node:get_keys(_pipe@1).

-file("src/yum/yaml.gleam", 186).
?DOC(
    " Returns all values from the root mapping.\n"
    "\n"
    " Values are returned in source order. Returns [`ExpectedKind`](./yaml/node.html#AccessError)\n"
    " when the document root is not a mapping.\n"
).
-spec get_values(yaml()) -> {ok, list(yum@yaml@node:node_())} |
    {error, yum@yaml@node:access_error()}.
get_values(Yaml) ->
    _pipe = Yaml,
    _pipe@1 = root(_pipe),
    yum@yaml@node:get_values(_pipe@1).

-file("src/yum/yaml.gleam", 207).
?DOC(
    " Parses YAML and decodes it using a\n"
    " [`gleam/dynamic/decode`](https://hexdocs.pm/gleam_stdlib/gleam/dynamic/decode.html)\n"
    " decoder.\n"
).
-spec decode(binary(), gleam@dynamic@decode:decoder(PNQ)) -> {ok, PNQ} |
    {error, decode_error()}.
decode(Input, Decoder) ->
    gleam@result:'try'(
        begin
            _pipe = parse(Input),
            gleam@result:map_error(
                _pipe,
                fun(Field@0) -> {parse_error, Field@0} end
            )
        end,
        fun(Document) ->
            gleam@result:'try'(
                begin
                    _pipe@1 = Document,
                    _pipe@2 = resolve(_pipe@1),
                    gleam@result:map_error(
                        _pipe@2,
                        fun(Field@0) -> {resolve_error, Field@0} end
                    )
                end,
                fun(Document@1) -> _pipe@3 = Document@1,
                    _pipe@4 = root(_pipe@3),
                    _pipe@5 = yum@yaml@dynamic:from_node(_pipe@4),
                    _pipe@6 = gleam@dynamic@decode:run(_pipe@5, Decoder),
                    gleam@result:map_error(
                        _pipe@6,
                        fun(Field@0) -> {unable_to_decode, Field@0} end
                    ) end
            )
        end
    ).

-file("src/yum/yaml.gleam", 230).
?DOC(" Emits a deterministic YAML string from a YAML document.\n").
-spec to_string(yaml()) -> binary().
to_string(Yaml) ->
    _pipe = Yaml,
    _pipe@1 = root(_pipe),
    yum@yaml@emitter:to_string(_pipe@1).