-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; _ -> <<<>/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, <<<<<>/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) -> <<<>/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>>.