Packages

A YAML 1.2 parser for Gleam, with an optional native fast_yaml C NIF backend.

Current section

Files

Jump to
taffy src taffy@value.erl
Raw

src/taffy@value.erl

-module(taffy@value).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/taffy/value.gleam").
-export([to_string/1, as_string/1, as_int/1, as_float/1, as_bool/1, as_list/1, as_dict/1, as_pairs/1, get/2, index/2, check_no_duplicates/1, size/1, is_null/1, resolve_merges/1, to_yaml/1]).
-export_type([yaml_value/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(
" The `YamlValue` type and direct accessors. Most users want the\n"
" re-exports on the top-level `taffy` module; this module is exposed for\n"
" pattern-matching (`value.Null`, `value.Mapping(_)`) and for direct\n"
" `YamlValue` construction in tests.\n"
).
-type yaml_value() :: null |
{bool, boolean()} |
{int, integer()} |
{float, float()} |
{string, binary()} |
{sequence, list(yaml_value())} |
{mapping, list({binary(), yaml_value()})}.
-file("src/taffy/value.gleam", 55).
-spec escape_string(binary()) -> binary().
escape_string(S) ->
_pipe = S,
_pipe@1 = gleam@string:replace(_pipe, <<"\\"/utf8>>, <<"\\\\"/utf8>>),
_pipe@2 = gleam@string:replace(_pipe@1, <<"\""/utf8>>, <<"\\\""/utf8>>),
_pipe@3 = gleam@string:replace(_pipe@2, <<"\n"/utf8>>, <<"\\n"/utf8>>),
gleam@string:replace(_pipe@3, <<"\t"/utf8>>, <<"\\t"/utf8>>).
-file("src/taffy/value.gleam", 30).
-spec to_string(yaml_value()) -> binary().
to_string(Value) ->
case Value of
null ->
<<"null"/utf8>>;
{bool, true} ->
<<"true"/utf8>>;
{bool, false} ->
<<"false"/utf8>>;
{int, I} ->
erlang:integer_to_binary(I);
{float, F} ->
gleam_stdlib:float_to_string(F);
{string, S} ->
<<<<"\""/utf8, (escape_string(S))/binary>>/binary, "\""/utf8>>;
{sequence, Items} ->
Inner = begin
_pipe = Items,
_pipe@1 = gleam@list:map(_pipe, fun to_string/1),
gleam@string:join(_pipe@1, <<", "/utf8>>)
end,
<<<<"["/utf8, Inner/binary>>/binary, "]"/utf8>>;
{mapping, Pairs} ->
Inner@1 = begin
_pipe@2 = Pairs,
_pipe@3 = gleam@list:map(
_pipe@2,
fun(Pair) ->
<<<<(erlang:element(1, Pair))/binary, ": "/utf8>>/binary,
(to_string(erlang:element(2, Pair)))/binary>>
end
),
gleam@string:join(_pipe@3, <<", "/utf8>>)
end,
<<<<"{"/utf8, Inner@1/binary>>/binary, "}"/utf8>>
end.
-file("src/taffy/value.gleam", 63).
-spec as_string(yaml_value()) -> gleam@option:option(binary()).
as_string(Value) ->
case Value of
{string, S} ->
{some, S};
_ ->
none
end.
-file("src/taffy/value.gleam", 70).
-spec as_int(yaml_value()) -> gleam@option:option(integer()).
as_int(Value) ->
case Value of
{int, I} ->
{some, I};
_ ->
none
end.
-file("src/taffy/value.gleam", 77).
-spec as_float(yaml_value()) -> gleam@option:option(float()).
as_float(Value) ->
case Value of
{float, F} ->
{some, F};
{int, I} ->
{some, erlang:float(I)};
_ ->
none
end.
-file("src/taffy/value.gleam", 85).
-spec as_bool(yaml_value()) -> gleam@option:option(boolean()).
as_bool(Value) ->
case Value of
{bool, B} ->
{some, B};
_ ->
none
end.
-file("src/taffy/value.gleam", 92).
-spec as_list(yaml_value()) -> gleam@option:option(list(yaml_value())).
as_list(Value) ->
case Value of
{sequence, Items} ->
{some, Items};
_ ->
none
end.
-file("src/taffy/value.gleam", 106).
-spec pairs_to_first_wins_dict(list({binary(), yaml_value()})) -> gleam@dict:dict(binary(), yaml_value()).
pairs_to_first_wins_dict(Pairs) ->
gleam@list:fold(
Pairs,
maps:new(),
fun(Acc, P) -> case gleam@dict:has_key(Acc, erlang:element(1, P)) of
true ->
Acc;
false ->
gleam@dict:insert(
Acc,
erlang:element(1, P),
erlang:element(2, P)
)
end end
).
-file("src/taffy/value.gleam", 99).
-spec as_dict(yaml_value()) -> gleam@option:option(gleam@dict:dict(binary(), yaml_value())).
as_dict(Value) ->
case Value of
{mapping, Pairs} ->
{some, pairs_to_first_wins_dict(Pairs)};
_ ->
none
end.
-file("src/taffy/value.gleam", 117).
-spec as_pairs(yaml_value()) -> gleam@option:option(list({binary(),
yaml_value()})).
as_pairs(Value) ->
case Value of
{mapping, Pairs} ->
{some, Pairs};
_ ->
none
end.
-file("src/taffy/value.gleam", 124).
-spec get(yaml_value(), binary()) -> gleam@option:option(yaml_value()).
get(Value, Key) ->
case Value of
{mapping, Pairs} ->
_pipe = gleam@list:key_find(Pairs, Key),
gleam@option:from_result(_pipe);
_ ->
none
end.
-file("src/taffy/value.gleam", 131).
-spec index(yaml_value(), integer()) -> gleam@option:option(yaml_value()).
index(Value, Idx) ->
case Value of
{sequence, Items} ->
_pipe = gleam@list:drop(Items, Idx),
_pipe@1 = gleam@list:first(_pipe),
gleam@option:from_result(_pipe@1);
_ ->
none
end.
-file("src/taffy/value.gleam", 158).
-spec find_duplicate_key(
list({binary(), yaml_value()}),
gleam@set:set(binary())
) -> gleam@option:option(binary()).
find_duplicate_key(Pairs, Seen) ->
case Pairs of
[] ->
none;
[{K, _} | Rest] ->
case gleam@set:contains(Seen, K) of
true ->
{some, K};
false ->
find_duplicate_key(Rest, gleam@set:insert(Seen, K))
end
end.
-file("src/taffy/value.gleam", 143).
?DOC(false).
-spec check_no_duplicates(yaml_value()) -> {ok, nil} | {error, binary()}.
check_no_duplicates(Value) ->
case Value of
{mapping, Pairs} ->
case find_duplicate_key(Pairs, gleam@set:new()) of
{some, Key} ->
{error, Key};
none ->
gleam@list:try_each(
Pairs,
fun(P) -> check_no_duplicates(erlang:element(2, P)) end
)
end;
{sequence, Items} ->
gleam@list:try_each(Items, fun check_no_duplicates/1);
_ ->
{ok, nil}
end.
-file("src/taffy/value.gleam", 175).
?DOC(
" Count the total number of nodes in a value tree (including the root).\n"
" Used by the parser to budget alias expansion against billion-laughs\n"
" attacks; exposed so callers can apply their own size policies.\n"
).
-spec size(yaml_value()) -> integer().
size(Value) ->
case Value of
{sequence, Items} ->
gleam@list:fold(Items, 1, fun(Acc, Item) -> Acc + size(Item) end);
{mapping, Pairs} ->
gleam@list:fold(
Pairs,
1,
fun(Acc@1, P) -> Acc@1 + size(erlang:element(2, P)) end
);
_ ->
1
end.
-file("src/taffy/value.gleam", 183).
-spec is_null(yaml_value()) -> boolean().
is_null(Value) ->
case Value of
null ->
true;
_ ->
false
end.
-file("src/taffy/value.gleam", 293).
-spec pad2_hex(integer()) -> binary().
pad2_hex(N) ->
S = begin
_pipe = gleam@int:to_base16(N),
string:lowercase(_pipe)
end,
case string:length(S) of
1 ->
<<"0"/utf8, S/binary>>;
_ ->
S
end.
-file("src/taffy/value.gleam", 274).
-spec escape_codepoint(integer()) -> binary().
escape_codepoint(Cp) ->
case gleam_stdlib:identity(Cp) of
0 ->
<<"\\0"/utf8>>;
7 ->
<<"\\a"/utf8>>;
8 ->
<<"\\b"/utf8>>;
9 ->
<<"\\t"/utf8>>;
10 ->
<<"\\n"/utf8>>;
11 ->
<<"\\v"/utf8>>;
12 ->
<<"\\f"/utf8>>;
13 ->
<<"\\r"/utf8>>;
27 ->
<<"\\e"/utf8>>;
34 ->
<<"\\\""/utf8>>;
92 ->
<<"\\\\"/utf8>>;
127 ->
<<"\\x7f"/utf8>>;
N when N < 32 ->
<<"\\x"/utf8, (pad2_hex(N))/binary>>;
_ ->
gleam_stdlib:utf_codepoint_list_to_string([Cp])
end.
-file("src/taffy/value.gleam", 268).
-spec escape_for_double_quote(binary()) -> binary().
escape_for_double_quote(S) ->
_pipe = gleam@string:to_utf_codepoints(S),
_pipe@1 = gleam@list:map(_pipe, fun escape_codepoint/1),
erlang:list_to_binary(_pipe@1).
-file("src/taffy/value.gleam", 301).
-spec is_plain_safe(binary()) -> boolean().
is_plain_safe(S) ->
case S of
<<""/utf8>> ->
false;
_ ->
First = begin
_pipe = gleam@string:first(S),
gleam@result:unwrap(_pipe, <<""/utf8>>)
end,
Bad_starts = [<<"-"/utf8>>,
<<"?"/utf8>>,
<<":"/utf8>>,
<<"&"/utf8>>,
<<"*"/utf8>>,
<<"!"/utf8>>,
<<"|"/utf8>>,
<<">"/utf8>>,
<<"%"/utf8>>,
<<"@"/utf8>>,
<<"`"/utf8>>,
<<","/utf8>>,
<<"["/utf8>>,
<<"]"/utf8>>,
<<"{"/utf8>>,
<<"}"/utf8>>,
<<"#"/utf8>>,
<<"'"/utf8>>,
<<"\""/utf8>>,
<<" "/utf8>>,
<<"\t"/utf8>>],
Bad_substrings = [<<": "/utf8>>,
<<" #"/utf8>>,
<<"\n"/utf8>>,
<<"\t"/utf8>>,
<<"'"/utf8>>,
<<"\""/utf8>>],
Ambiguous_scalars = [<<"true"/utf8>>,
<<"True"/utf8>>,
<<"TRUE"/utf8>>,
<<"false"/utf8>>,
<<"False"/utf8>>,
<<"FALSE"/utf8>>,
<<"null"/utf8>>,
<<"Null"/utf8>>,
<<"NULL"/utf8>>,
<<"~"/utf8>>,
<<"yes"/utf8>>,
<<"Yes"/utf8>>,
<<"YES"/utf8>>,
<<"no"/utf8>>,
<<"No"/utf8>>,
<<"NO"/utf8>>,
<<"on"/utf8>>,
<<"On"/utf8>>,
<<"ON"/utf8>>,
<<"off"/utf8>>,
<<"Off"/utf8>>,
<<"OFF"/utf8>>],
Looks_numeric = case {gleam_stdlib:parse_int(S),
gleam_stdlib:parse_float(S)} of
{{error, _}, {error, _}} ->
false;
{_, _} ->
true
end,
((not gleam@list:contains(Bad_starts, First) andalso not gleam@list:any(
Bad_substrings,
fun(_capture) -> gleam_stdlib:contains_string(S, _capture) end
))
andalso not gleam@list:contains(Ambiguous_scalars, S))
andalso not Looks_numeric
end.
-file("src/taffy/value.gleam", 261).
-spec emit_string(binary()) -> binary().
emit_string(S) ->
case is_plain_safe(S) of
true ->
S;
false ->
<<<<"\""/utf8, (escape_for_double_quote(S))/binary>>/binary,
"\""/utf8>>
end.
-file("src/taffy/value.gleam", 365).
-spec extract_merge_pairs(yaml_value()) -> list({binary(), yaml_value()}).
extract_merge_pairs(Value) ->
case Value of
{mapping, Pairs} ->
Pairs;
{sequence, Items} ->
gleam@list:flat_map(Items, fun extract_merge_pairs/1);
_ ->
[]
end.
-file("src/taffy/value.gleam", 381).
-spec collect_unique(
list({binary(), yaml_value()}),
gleam@set:set(binary()),
list({binary(), yaml_value()})
) -> list({binary(), yaml_value()}).
collect_unique(To_add, Seen, Acc) ->
case To_add of
[] ->
lists:reverse(Acc);
[{K, V} | Rest] ->
case gleam@set:contains(Seen, K) of
true ->
collect_unique(Rest, Seen, Acc);
false ->
collect_unique(
Rest,
gleam@set:insert(Seen, K),
[{K, V} | Acc]
)
end
end.
-file("src/taffy/value.gleam", 373).
-spec append_unique(
list({binary(), yaml_value()}),
list({binary(), yaml_value()}),
gleam@set:set(binary())
) -> list({binary(), yaml_value()}).
append_unique(Own, To_add, Seen) ->
lists:append(Own, collect_unique(To_add, Seen, [])).
-file("src/taffy/value.gleam", 344).
-spec merge_pairs(list({binary(), yaml_value()})) -> list({binary(),
yaml_value()}).
merge_pairs(Pairs) ->
{Own@1, Merged_sources} = gleam@list:fold(
Pairs,
{[], []},
fun(Acc, P) ->
{Own, Sources} = Acc,
case erlang:element(1, P) =:= <<"<<"/utf8>> of
true ->
{Own, [erlang:element(2, P) | Sources]};
false ->
{[P | Own], Sources}
end
end
),
Own@2 = lists:reverse(Own@1),
Merged_sources@1 = lists:reverse(Merged_sources),
case Merged_sources@1 of
[] ->
Own@2;
_ ->
Merged = gleam@list:flat_map(
Merged_sources@1,
fun extract_merge_pairs/1
),
Seen = gleam@list:fold(
Own@2,
gleam@set:new(),
fun(S, P@1) -> gleam@set:insert(S, erlang:element(1, P@1)) end
),
append_unique(Own@2, Merged, Seen)
end.
-file("src/taffy/value.gleam", 333).
?DOC(false).
-spec resolve_merges(yaml_value()) -> yaml_value().
resolve_merges(Value) ->
case Value of
{mapping, Pairs} ->
Pairs@1 = gleam@list:map(
Pairs,
fun(P) ->
{erlang:element(1, P), resolve_merges(erlang:element(2, P))}
end
),
{mapping, merge_pairs(Pairs@1)};
{sequence, Items} ->
{sequence, gleam@list:map(Items, fun resolve_merges/1)};
_ ->
Value
end.
-file("src/taffy/value.gleam", 230).
-spec emit_after_dash(yaml_value(), integer()) -> binary().
emit_after_dash(Value, Indent) ->
case Value of
{mapping, []} ->
<<"{}"/utf8>>;
{sequence, []} ->
<<"[]"/utf8>>;
{mapping, Pairs} ->
case Pairs of
[First | Rest] ->
{K, V} = First,
Head = <<<<(emit_string(K))/binary, ":"/utf8>>/binary,
(emit_value_part(V, Indent + 2))/binary>>,
case Rest of
[] ->
Head;
_ ->
<<<<Head/binary, "\n"/utf8>>/binary,
(emit_mapping(Rest, Indent))/binary>>
end;
[] ->
<<"{}"/utf8>>
end;
{sequence, _} ->
<<"\n"/utf8, (emit(Value, Indent))/binary>>;
_ ->
emit(Value, Indent)
end.
-file("src/taffy/value.gleam", 220).
-spec emit_mapping(list({binary(), yaml_value()}), integer()) -> binary().
emit_mapping(Pairs, Indent) ->
Pad = gleam@string:repeat(<<" "/utf8>>, Indent),
_pipe = Pairs,
_pipe@1 = gleam@list:map(
_pipe,
fun(Pair) ->
{K, V} = Pair,
<<<<<<Pad/binary, (emit_string(K))/binary>>/binary, ":"/utf8>>/binary,
(emit_value_part(V, Indent + 2))/binary>>
end
),
gleam@string:join(_pipe@1, <<"\n"/utf8>>).
-file("src/taffy/value.gleam", 252).
-spec emit_value_part(yaml_value(), integer()) -> binary().
emit_value_part(Value, Indent) ->
case Value of
{mapping, []} ->
<<" {}"/utf8>>;
{sequence, []} ->
<<" []"/utf8>>;
{mapping, _} ->
<<"\n"/utf8, (emit(Value, Indent))/binary>>;
{sequence, _} ->
<<"\n"/utf8, (emit(Value, Indent))/binary>>;
_ ->
<<" "/utf8, (emit(Value, Indent))/binary>>
end.
-file("src/taffy/value.gleam", 198).
-spec emit(yaml_value(), integer()) -> binary().
emit(Value, Indent) ->
case Value of
null ->
<<"null"/utf8>>;
{bool, true} ->
<<"true"/utf8>>;
{bool, false} ->
<<"false"/utf8>>;
{int, I} ->
erlang:integer_to_binary(I);
{float, F} ->
gleam_stdlib:float_to_string(F);
{string, S} ->
emit_string(S);
{sequence, []} ->
<<"[]"/utf8>>;
{sequence, Items} ->
emit_sequence(Items, Indent);
{mapping, []} ->
<<"{}"/utf8>>;
{mapping, Pairs} ->
emit_mapping(Pairs, Indent)
end.
-file("src/taffy/value.gleam", 213).
-spec emit_sequence(list(yaml_value()), integer()) -> binary().
emit_sequence(Items, Indent) ->
Pad = gleam@string:repeat(<<" "/utf8>>, Indent),
_pipe = Items,
_pipe@1 = gleam@list:map(
_pipe,
fun(Item) ->
<<<<Pad/binary, "- "/utf8>>/binary,
(emit_after_dash(Item, Indent + 2))/binary>>
end
),
gleam@string:join(_pipe@1, <<"\n"/utf8>>).
-file("src/taffy/value.gleam", 194).
?DOC(
" Emit a value as block-style YAML. The output ends with a trailing\n"
" newline. Strings are quoted only when they would otherwise be parsed as\n"
" a different scalar type (numbers, booleans, null) or contain characters\n"
" that aren't safe to render plain.\n"
).
-spec to_yaml(yaml_value()) -> binary().
to_yaml(Value) ->
<<(emit(Value, 0))/binary, "\n"/utf8>>.