Skip to main content

src/yum@yaml@node.erl

-module(yum@yaml@node).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/yum/yaml/node.gleam").
-export([new/3, synthetic_span/0, synthetic/1, kind/1, kind_name/1, span/1, style/1, tag/1, anchor/1, alias/1, with_tag/2, with_anchor/2, with_alias/2, as_mapping/1, get_keys/1, get_values/1, as_sequence/1, as_string/1, as_bool/1, as_int/1, as_float/1, as_null/1, get_index/2, get_key/2, get/2]).
-export_type([node_/0, kind/0, kind_name/0, access_error/0, style/0, span/0, position/0, path_segment/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(
    " Inspect and query YAML nodes.\n"
    "\n"
    " A [`Node`](#Node) is one value inside a YAML document: a scalar, sequence,\n"
    " or mapping. This module provides accessors for the node kind, source span,\n"
    " source style, tags, anchors, aliases, typed scalar values, and nested path\n"
    " lookup.\n"
    "\n"
    " Nodes are used by both raw and resolved YAML documents. Parsing creates\n"
    " nodes with the structure and metadata found in the source. Resolving\n"
    " validates YAML-level metadata such as aliases and tags, and may expand or\n"
    " compose parts of the tree, but a resolved document still contains nodes.\n"
    "\n"
    " For example, [`tag`](#tag), [`anchor`](#anchor), and [`alias`](#alias)\n"
    " expose YAML metadata attached to a node. That metadata is not the same as\n"
    " the node's semantic [`Kind`](#Kind). Nodes created with\n"
    " [`yum/yaml/builder`](./builder.html) are synthetic and use\n"
    " [`synthetic_span`](#synthetic_span).\n"
).

-opaque node_() :: {node,
        kind(),
        span(),
        style(),
        gleam@option:option(binary()),
        gleam@option:option(binary()),
        gleam@option:option(binary())}.

-type kind() :: null |
    {bool, boolean()} |
    {int, integer()} |
    {float, float()} |
    pos_inf |
    neg_inf |
    nan |
    {string, binary()} |
    {sequence, list(node_())} |
    {mapping, list({node_(), node_()})}.

-type kind_name() :: null_kind |
    bool_kind |
    int_kind |
    float_kind |
    pos_inf_kind |
    neg_inf_kind |
    nan_kind |
    string_kind |
    sequence_kind |
    mapping_kind.

-type access_error() :: {expected_kind, kind_name(), kind_name(), span()}.

-type style() :: plain_scalar |
    single_quoted_scalar |
    double_quoted_scalar |
    literal_block_scalar |
    folded_block_scalar |
    block_sequence |
    flow_sequence |
    block_mapping |
    flow_mapping |
    synthetic.

-type span() :: {span, position(), position()}.

-type position() :: {position, integer(), integer()}.

-type path_segment() :: {key, binary()} | {index, integer()}.

-file("src/yum/yaml/node.gleam", 258).
?DOC(
    " Creates a YAML node with explicit metadata.\n"
    "\n"
    " Most callers should prefer [`yum/yaml.parse`](../yaml.html#parse) plus\n"
    " [`yum/yaml.root`](../yaml.html#root) for parsed input or\n"
    " [`yum/yaml/builder`](./builder.html) for generated YAML. This constructor is\n"
    " public for tools that need to synthesize nodes while preserving their own\n"
    " source metadata.\n"
).
-spec new(kind(), span(), style()) -> node_().
new(Kind, Span, Style) ->
    {node, Kind, Span, Style, none, none, none}.

-file("src/yum/yaml/node.gleam", 271).
?DOC(" Returns the placeholder span used for generated nodes with no source.\n").
-spec synthetic_span() -> span().
synthetic_span() ->
    {span, {position, 0, 0}, {position, 0, 0}}.

-file("src/yum/yaml/node.gleam", 265).
?DOC(
    " Creates a generated node with no original source location.\n"
    "\n"
    " This is the lower-level constructor used by [`yum/yaml/builder`](./builder.html).\n"
).
-spec synthetic(kind()) -> node_().
synthetic(Kind) ->
    new(Kind, synthetic_span(), synthetic).

-file("src/yum/yaml/node.gleam", 279).
?DOC(
    " Returns the semantic kind and value of a node.\n"
    "\n"
    " This is the broadest accessor. Prefer the stricter `as_*` functions when a\n"
    " caller expects one specific YAML kind and wants a typed error for mismatches.\n"
).
-spec kind(node_()) -> kind().
kind(Node) ->
    erlang:element(2, Node).

-file("src/yum/yaml/node.gleam", 566).
-spec kind_name_of(kind()) -> kind_name().
kind_name_of(Kind) ->
    case Kind of
        null ->
            null_kind;

        {bool, _} ->
            bool_kind;

        {int, _} ->
            int_kind;

        {float, _} ->
            float_kind;

        pos_inf ->
            pos_inf_kind;

        neg_inf ->
            neg_inf_kind;

        nan ->
            nan_kind;

        {string, _} ->
            string_kind;

        {sequence, _} ->
            sequence_kind;

        {mapping, _} ->
            mapping_kind
    end.

-file("src/yum/yaml/node.gleam", 285).
?DOC(" Returns the node kind without its associated value.\n").
-spec kind_name(node_()) -> kind_name().
kind_name(Node) ->
    _pipe = erlang:element(2, Node),
    kind_name_of(_pipe).

-file("src/yum/yaml/node.gleam", 294).
?DOC(
    " Returns the source span for a node.\n"
    "\n"
    " Parsed nodes use 1-based row and column positions. Synthetic nodes created\n"
    " with [`yum/yaml/builder`](./builder.html) use [`synthetic_span`](#synthetic_span).\n"
).
-spec span(node_()) -> span().
span(Node) ->
    erlang:element(3, Node).

-file("src/yum/yaml/node.gleam", 302).
?DOC(
    " Returns the source style used to write a node.\n"
    "\n"
    " Style describes presentation, such as plain versus quoted scalars or block\n"
    " versus flow collections. It does not change the node's semantic kind.\n"
).
-spec style(node_()) -> style().
style(Node) ->
    erlang:element(4, Node).

-file("src/yum/yaml/node.gleam", 332).
?DOC(
    " Returns the YAML tag attached to a node, if one was written or added.\n"
    "\n"
    " Tags are metadata, not value casts. A tagged scalar keeps its parsed\n"
    " [`Kind`](#Kind); for example `!!str 123` is still parsed as an integer today,\n"
    " while the tag is available through this function.\n"
    "\n"
    " On raw YAML, this returns the tag form captured by the parser. On resolved\n"
    " YAML, tag handles are expanded where possible. For example:\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(\"value: !!str 123\")\n"
    "   let assert option.Some(value) = document |> yaml.get([node.Key(\"value\")])\n"
    "\n"
    "   assert node.tag(value) == option.Some(\"!str\")\n"
    "\n"
    "   let assert Ok(document) = yaml.resolve(document)\n"
    "   let assert option.Some(value) = document |> yaml.get([node.Key(\"value\")])\n"
    "\n"
    "   assert node.tag(value) == option.Some(\"tag:yaml.org,2002:str\")\n"
    " }\n"
    " ```\n"
).
-spec tag(node_()) -> gleam@option:option(binary()).
tag(Node) ->
    erlang:element(5, Node).

-file("src/yum/yaml/node.gleam", 341).
?DOC(
    " Returns the YAML anchor name attached to a node, if one was written or added.\n"
    "\n"
    " For source YAML like `defaults: &base { retries: 1 }`, the value node under\n"
    " `defaults` has anchor `base`. Resolving validates duplicate anchors but does\n"
    " not remove anchor metadata from nodes.\n"
).
-spec anchor(node_()) -> gleam@option:option(binary()).
anchor(Node) ->
    erlang:element(6, Node).

-file("src/yum/yaml/node.gleam", 350).
?DOC(
    " Returns the YAML alias name attached to a node, if one was written or added.\n"
    "\n"
    " For source YAML like `copy: *base`, the value node under `copy` has alias\n"
    " `base`. Resolving checks that aliases refer to anchors seen earlier in the\n"
    " document, but alias metadata can still be inspected on the node.\n"
).
-spec alias(node_()) -> gleam@option:option(binary()).
alias(Node) ->
    erlang:element(7, Node).

-file("src/yum/yaml/node.gleam", 359).
?DOC(
    " Returns a copy of the node with tag metadata.\n"
    "\n"
    " This function only sets metadata. It does not validate tag syntax or change\n"
    " the node's [`Kind`](#Kind).\n"
).
-spec with_tag(node_(), binary()) -> node_().
with_tag(Node, Tag) ->
    {node,
        erlang:element(2, Node),
        erlang:element(3, Node),
        erlang:element(4, Node),
        {some, Tag},
        erlang:element(6, Node),
        erlang:element(7, Node)}.

-file("src/yum/yaml/node.gleam", 368).
?DOC(
    " Returns a copy of the node with anchor metadata.\n"
    "\n"
    " This function only sets metadata. Use [`yum/yaml.resolve`](../yaml.html#resolve)\n"
    " to validate anchor and alias relationships.\n"
).
-spec with_anchor(node_(), binary()) -> node_().
with_anchor(Node, Anchor) ->
    {node,
        erlang:element(2, Node),
        erlang:element(3, Node),
        erlang:element(4, Node),
        erlang:element(5, Node),
        {some, Anchor},
        erlang:element(7, Node)}.

-file("src/yum/yaml/node.gleam", 377).
?DOC(
    " Returns a copy of the node with alias metadata.\n"
    "\n"
    " This function only sets metadata. Use [`yum/yaml.resolve`](../yaml.html#resolve)\n"
    " to check whether the alias refers to a known anchor.\n"
).
-spec with_alias(node_(), binary()) -> node_().
with_alias(Node, Alias) ->
    {node,
        erlang:element(2, Node),
        erlang:element(3, Node),
        erlang:element(4, Node),
        erlang:element(5, Node),
        erlang:element(6, Node),
        {some, Alias}}.

-file("src/yum/yaml/node.gleam", 562).
-spec expected(node_(), kind_name()) -> access_error().
expected(Node, Kind) ->
    {expected_kind, Kind, kind_name(Node), erlang:element(3, Node)}.

-file("src/yum/yaml/node.gleam", 384).
?DOC(
    " Returns mapping entries when the node is a mapping.\n"
    "\n"
    " Returns [`ExpectedKind`](#AccessError) when the node is not a mapping.\n"
).
-spec as_mapping(node_()) -> {ok, list({node_(), node_()})} |
    {error, access_error()}.
as_mapping(Node) ->
    case erlang:element(2, Node) of
        {mapping, Entries} ->
            {ok, Entries};

        _ ->
            {error, expected(Node, mapping_kind)}
    end.

-file("src/yum/yaml/node.gleam", 396).
?DOC(
    " Returns all keys from a mapping node.\n"
    "\n"
    " The keys are returned as nodes because YAML mappings can use scalar,\n"
    " sequence, or mapping nodes as keys. Returns [`ExpectedKind`](#AccessError)\n"
    " when the node is not a mapping.\n"
).
-spec get_keys(node_()) -> {ok, list(node_())} | {error, access_error()}.
get_keys(Node) ->
    _pipe = Node,
    _pipe@1 = as_mapping(_pipe),
    gleam@result:map(
        _pipe@1,
        fun(_capture) ->
            gleam@list:map(
                _capture,
                fun(Entry) ->
                    {Key, _} = Entry,
                    Key
                end
            )
        end
    ).

-file("src/yum/yaml/node.gleam", 411).
?DOC(
    " Returns all values from a mapping node.\n"
    "\n"
    " Values are returned in source order. Returns [`ExpectedKind`](#AccessError)\n"
    " when the node is not a mapping.\n"
).
-spec get_values(node_()) -> {ok, list(node_())} | {error, access_error()}.
get_values(Node) ->
    _pipe = Node,
    _pipe@1 = as_mapping(_pipe),
    gleam@result:map(
        _pipe@1,
        fun(Node@1) ->
            gleam@list:map(
                Node@1,
                fun(Entry) ->
                    {_, Value} = Entry,
                    Value
                end
            )
        end
    ).

-file("src/yum/yaml/node.gleam", 425).
?DOC(
    " Returns sequence entries when the node is a sequence.\n"
    "\n"
    " Returns [`ExpectedKind`](#AccessError) when the node is not a sequence.\n"
).
-spec as_sequence(node_()) -> {ok, list(node_())} | {error, access_error()}.
as_sequence(Node) ->
    case erlang:element(2, Node) of
        {sequence, Entries} ->
            {ok, Entries};

        _ ->
            {error, expected(Node, sequence_kind)}
    end.

-file("src/yum/yaml/node.gleam", 435).
?DOC(
    " Returns the string value when the node is a string.\n"
    "\n"
    " Returns [`ExpectedKind`](#AccessError) when the node is not a string.\n"
).
-spec as_string(node_()) -> {ok, binary()} | {error, access_error()}.
as_string(Node) ->
    case erlang:element(2, Node) of
        {string, Value} ->
            {ok, Value};

        _ ->
            {error, expected(Node, string_kind)}
    end.

-file("src/yum/yaml/node.gleam", 445).
?DOC(
    " Returns the boolean value when the node is a boolean.\n"
    "\n"
    " Returns [`ExpectedKind`](#AccessError) when the node is not a boolean.\n"
).
-spec as_bool(node_()) -> {ok, boolean()} | {error, access_error()}.
as_bool(Node) ->
    case erlang:element(2, Node) of
        {bool, Value} ->
            {ok, Value};

        _ ->
            {error, expected(Node, bool_kind)}
    end.

-file("src/yum/yaml/node.gleam", 455).
?DOC(
    " Returns the integer value when the node is an integer.\n"
    "\n"
    " Returns [`ExpectedKind`](#AccessError) when the node is not an integer.\n"
).
-spec as_int(node_()) -> {ok, integer()} | {error, access_error()}.
as_int(Node) ->
    case erlang:element(2, Node) of
        {int, Value} ->
            {ok, Value};

        _ ->
            {error, expected(Node, int_kind)}
    end.

-file("src/yum/yaml/node.gleam", 467).
?DOC(
    " Returns the finite float value when the node is a float.\n"
    "\n"
    " Returns [`ExpectedKind`](#AccessError) when the node is not a finite float.\n"
    " Special float values have separate kinds: [`PosInf`](#Kind),\n"
    " [`NegInf`](#Kind), and [`Nan`](#Kind).\n"
).
-spec as_float(node_()) -> {ok, float()} | {error, access_error()}.
as_float(Node) ->
    case erlang:element(2, Node) of
        {float, Value} ->
            {ok, Value};

        _ ->
            {error, expected(Node, float_kind)}
    end.

-file("src/yum/yaml/node.gleam", 477).
?DOC(
    " Returns `Nil` when the node is null.\n"
    "\n"
    " Returns [`ExpectedKind`](#AccessError) when the node is not null.\n"
).
-spec as_null(node_()) -> {ok, nil} | {error, access_error()}.
as_null(Node) ->
    case erlang:element(2, Node) of
        null ->
            {ok, nil};

        _ ->
            {error, expected(Node, null_kind)}
    end.

-file("src/yum/yaml/node.gleam", 546).
?DOC(
    " Returns a sequence item by zero-based index.\n"
    "\n"
    " Returns `None` when the node is not a sequence or when the index is out of\n"
    " bounds. Negative indexes always return `None`.\n"
).
-spec get_index(node_(), integer()) -> gleam@option:option(node_()).
get_index(Node, Index) ->
    case Index < 0 of
        true ->
            none;

        false ->
            case as_sequence(Node) of
                {ok, Entries} ->
                    _pipe = Entries,
                    _pipe@1 = gleam@list:drop(_pipe, Index),
                    _pipe@2 = gleam@list:first(_pipe@1),
                    gleam@option:from_result(_pipe@2);

                {error, _} ->
                    none
            end
    end.

-file("src/yum/yaml/node.gleam", 526).
?DOC(
    " Returns a mapping value by string key.\n"
    "\n"
    " Only string keys are matched. Returns `None` when the node is not a mapping,\n"
    " when the key is not present, or when a mapping entry uses a non-string key.\n"
).
-spec get_key(node_(), binary()) -> gleam@option:option(node_()).
get_key(Node, Key) ->
    case as_mapping(Node) of
        {ok, Entries} ->
            _pipe = Entries,
            _pipe@1 = gleam@list:find_map(
                _pipe,
                fun(Entry) ->
                    {Entry_key, Value} = Entry,
                    case as_string(Entry_key) of
                        {ok, Entry_key@1} when Entry_key@1 =:= Key ->
                            {ok, Value};

                        _ ->
                            {error, nil}
                    end
                end
            ),
            gleam@option:from_result(_pipe@1);

        {error, _} ->
            none
    end.

-file("src/yum/yaml/node.gleam", 510).
?DOC(
    " Returns a nested node by following mapping keys and sequence indexes.\n"
    "\n"
    " This is a convenience wrapper around [`get_key`](#get_key) and\n"
    " [`get_index`](#get_index). It returns `None` when any path segment does not\n"
    " match the current node.\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(\"jobs:\\n  test:\\n    run: gleam test\")\n"
    "   let root = yaml.root(document)\n"
    "\n"
    "   let run = root |> node.get([\n"
    "     node.Key(\"jobs\"),\n"
    "     node.Key(\"test\"),\n"
    "     node.Key(\"run\"),\n"
    "   ])\n"
    "\n"
    "   let value = run |> option.map(node.as_string)\n"
    "\n"
    "   assert value == option.Some(Ok(\"gleam test\"))\n"
    " }\n"
    " ```\n"
).
-spec get(node_(), list(path_segment())) -> gleam@option:option(node_()).
get(Node, Path) ->
    case Path of
        [] ->
            {some, Node};

        [{key, Key} | Rest] ->
            _pipe = get_key(Node, Key),
            gleam@option:then(_pipe, fun(_capture) -> get(_capture, Rest) end);

        [{index, Index} | Rest@1] ->
            _pipe@1 = get_index(Node, Index),
            gleam@option:then(
                _pipe@1,
                fun(_capture@1) -> get(_capture@1, Rest@1) end
            )
    end.