-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", 53). -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", 28). -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", 61). -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", 68). -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", 75). -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", 83). -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", 90). -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", 97). -spec as_dict(yaml_value()) -> gleam@option:option(gleam@dict:dict(binary(), yaml_value())). as_dict(Value) -> case Value of {mapping, Pairs} -> {some, maps:from_list(Pairs)}; _ -> none end. -file("src/taffy/value.gleam", 104). -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", 111). -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", 118). -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", 145). -spec find_duplicate_key(list({binary(), yaml_value()}), list(binary())) -> gleam@option:option(binary()). find_duplicate_key(Pairs, Seen) -> case Pairs of [] -> none; [{K, _} | Rest] -> case gleam@list:contains(Seen, K) of true -> {some, K}; false -> find_duplicate_key(Rest, [K | Seen]) end end. -file("src/taffy/value.gleam", 130). ?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, []) 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", 162). ?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", 170). -spec is_null(yaml_value()) -> boolean(). is_null(Value) -> case Value of null -> true; _ -> false end. -file("src/taffy/value.gleam", 255). -spec escape_for_double_quote(binary()) -> binary(). escape_for_double_quote(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", 263). -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", 248). -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", 329). -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", 337). -spec append_unique( list({binary(), yaml_value()}), list({binary(), yaml_value()}), list(binary()) ) -> list({binary(), yaml_value()}). append_unique(Acc, To_add, Seen) -> case To_add of [] -> Acc; [{K, V} | Rest] -> case gleam@list:contains(Seen, K) of true -> append_unique(Acc, Rest, Seen); false -> append_unique(lists:append(Acc, [{K, V}]), Rest, [K | Seen]) end end. -file("src/taffy/value.gleam", 306). -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 ), Own_keys = begin _pipe = Own@2, gleam@list:map(_pipe, fun(P@1) -> erlang:element(1, P@1) end) end, append_unique(Own@2, Merged, Own_keys) end. -file("src/taffy/value.gleam", 295). ?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", 217). -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", 207). -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", 239). -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", 185). -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", 200). -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", 181). ?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>>.