src/molt@internal@document@structure.erl
-module(molt@internal@document@structure).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/molt/internal/document/structure.gleam").
-export([ensure_exists/3, merge_values/4]).
-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(false).
-file("src/molt/internal/document/structure.gleam", 97).
?DOC(false).
-spec do_build(
molt@types:document(),
list(molt@types:path_segment()),
molt@types:toml_kind()
) -> {ok, molt@types:document()} | {error, molt@error:molt_error()}.
do_build(Doc, Segments, Kind) ->
Key_only = molt@internal@path:path_to_table_header(Segments),
Header = case Kind of
array_of_tables ->
molt@internal@cst@builder:build_empty_array_of_tables(Key_only);
_ ->
molt@internal@cst@builder:build_empty_table(Key_only)
end,
_pipe = molt@cst:insert_table_node(erlang:element(4, Doc), Header),
gleam@result:map(
_pipe,
fun(_capture) ->
molt@internal@document@primitives:rebuild(Doc, _capture)
end
).
-file("src/molt/internal/document/structure.gleam", 317).
?DOC(false).
-spec rehome_scope(
gleam@dict:dict(molt@types:index_key(), molt@types:index_entry()),
list(list(molt@types:path_segment()))
) -> list(molt@types:path_segment()).
rehome_scope(Idx, Paths) ->
case Paths of
[] ->
[];
[First | _] ->
case molt@internal@document@index:get_path(Idx, First) of
{ok, {index_scalar_value, Container}} ->
Container;
{ok, {index_array_value, Container}} ->
Container;
{ok, {index_inline_table_value, Container}} ->
Container;
_ ->
[]
end
end.
-file("src/molt/internal/document/structure.gleam", 298).
?DOC(false).
-spec order_by_source(
list(list(molt@types:path_segment())),
gleam@dict:dict(molt@types:index_key(), molt@types:index_entry()),
molt@types:document()
) -> list(list(molt@types:path_segment())).
order_by_source(Paths, Idx, Doc) ->
Scope = rehome_scope(Idx, Paths),
Ordered = case molt@cst:get(erlang:element(4, Doc), Scope) of
{ok, Scope_node} ->
_pipe = molt@internal@cst@elements:child_key_paths(Scope_node),
_pipe@1 = gleam@list:map(
_pipe,
fun(Segs) ->
lists:append(
Scope,
gleam@list:map(
Segs,
fun(Field@0) -> {key_segment, Field@0} end
)
)
end
),
gleam@list:filter(
_pipe@1,
fun(_capture) -> gleam@list:contains(Paths, _capture) end
);
_ ->
[]
end,
Leftover = gleam@list:filter(
Paths,
fun(P) -> not gleam@list:contains(Ordered, P) end
),
lists:append(Ordered, Leftover).
-file("src/molt/internal/document/structure.gleam", 354).
?DOC(false).
-spec path_starts_with(
list(molt@types:path_segment()),
list(molt@types:path_segment())
) -> boolean().
path_starts_with(Full, Prefix) ->
case {Prefix, Full} of
{[], _} ->
true;
{[_ | _], []} ->
false;
{[P | Ps], [F | Fs]} when P =:= F ->
path_starts_with(Fs, Ps);
{_, _} ->
false
end.
-file("src/molt/internal/document/structure.gleam", 330).
?DOC(false).
-spec collect_value_descendants_outside(
gleam@dict:dict(molt@types:index_key(), molt@types:index_entry()),
list(molt@types:path_segment())
) -> list(list(molt@types:path_segment())).
collect_value_descendants_outside(Idx, Prefix) ->
Prefix_len = erlang:length(Prefix),
gleam@dict:fold(
Idx,
[],
fun(Acc, Key, Entry) ->
Full_path = molt@internal@document@index:key_to_path(Key),
case path_starts_with(Full_path, Prefix) of
false ->
Acc;
true ->
case Entry of
{index_scalar_value, Container} ->
case erlang:length(Container) < Prefix_len of
true ->
[Full_path | Acc];
false ->
Acc
end;
{index_array_value, Container} ->
case erlang:length(Container) < Prefix_len of
true ->
[Full_path | Acc];
false ->
Acc
end;
{index_inline_table_value, Container} ->
case erlang:length(Container) < Prefix_len of
true ->
[Full_path | Acc];
false ->
Acc
end;
_ ->
Acc
end
end
end
).
-file("src/molt/internal/document/structure.gleam", 234).
?DOC(false).
-spec do_promote_implicit(
molt@types:document(),
gleam@dict:dict(molt@types:index_key(), molt@types:index_entry()),
list(molt@types:path_segment())
) -> {ok, molt@types:document()} | {error, molt@error:molt_error()}.
do_promote_implicit(Doc, Idx, Segments) ->
To_rehome = begin
_pipe = collect_value_descendants_outside(Idx, Segments),
order_by_source(_pipe, Idx, Doc)
end,
gleam@result:'try'(
gleam@list:try_map(
To_rehome,
fun(Full_path) ->
gleam@result:'try'(
molt@cst:get(erlang:element(4, Doc), Full_path),
fun(Old_kv) ->
New_key_segments = gleam@list:drop(
Full_path,
erlang:length(Segments)
),
New_key_names = gleam@list:filter_map(
New_key_segments,
fun(Seg) -> case Seg of
{key_segment, Name} ->
{ok,
molt@internal@utils:quote_key(Name)};
{index_segment, _} ->
{error, nil}
end end
),
New_kv = molt@internal@cst@elements:rewrite_kv_key_in_place(
Old_kv,
New_key_names
),
{ok, {Full_path, New_kv}}
end
)
end
),
fun(Staged) ->
gleam@result:'try'(
gleam@list:try_fold(
Staged,
erlang:element(4, Doc),
fun(Tree, Item) ->
{Full_path@1, _} = Item,
molt@cst:delete(Tree, Full_path@1)
end
),
fun(Deleted_tree) ->
Key_only = molt@internal@path:path_to_table_header(Segments),
Header = molt@internal@cst@builder:build_empty_table(
Key_only
),
gleam@result:'try'(
molt@cst:insert_table_node(Deleted_tree, Header),
fun(With_header) ->
Section_cst_path = gleam@list:map(
Key_only,
fun(Field@0) -> {key_segment, Field@0} end
),
gleam@result:'try'(
gleam@list:try_fold(
Staged,
With_header,
fun(Tree@1, Item@1) ->
{_, New_kv@1} = Item@1,
molt@cst:insert_kv(
Tree@1,
Section_cst_path,
New_kv@1,
kv_at_end
)
end
),
fun(New_tree) ->
{ok,
molt@internal@document@primitives:rebuild(
Doc,
New_tree
)}
end
)
end
)
end
)
end
).
-file("src/molt/internal/document/structure.gleam", 215).
?DOC(false).
-spec promote_implicit_to_concrete(
molt@types:document(),
list(molt@types:path_segment())
) -> {ok, molt@types:document()} | {error, molt@error:molt_error()}.
promote_implicit_to_concrete(Doc, Segments) ->
molt@internal@document@index:with_index(
Doc,
fun(Idx) -> case molt@internal@document@index:resolve(Idx, Segments) of
{hit, _, {index_implicit_table, _}} ->
do_promote_implicit(Doc, Idx, Segments);
{hit, _, {index_table, _}} ->
{ok, Doc};
{hit, _, Entry} ->
{error,
{type_mismatch,
{some, molt@internal@path:to_string(Segments)},
<<"implicit table"/utf8>>,
molt@internal@utils:index_entry_to_string(Entry)}};
{miss, _, _, _} ->
{error, molt@error:not_found_path(Segments)};
{fresh, _} ->
{error, molt@error:not_found_path(Segments)}
end end
).
-file("src/molt/internal/document/structure.gleam", 31).
?DOC(false).
-spec ensure_exists(molt@types:document(), binary(), molt@types:toml_kind()) -> {ok,
molt@types:document()} |
{error, molt@error:molt_error()}.
ensure_exists(Doc, P, Kind) ->
gleam@bool:guard(
(Kind /= table) andalso (Kind /= array_of_tables),
{error,
{type_mismatch,
{some, P},
<<"Table or ArrayOfTables kind"/utf8>>,
molt@internal@utils:toml_kind(Kind)}},
fun() ->
gleam@result:'try'(
molt@internal@path:parse(P),
fun(Segments) ->
molt@internal@document@index:with_index(
Doc,
fun(Idx) ->
Mismatch = fun(Got) ->
{error,
{type_mismatch,
{some, P},
molt@internal@utils:toml_kind(Kind),
Got}}
end,
case {molt@internal@document@index:resolve(
Idx,
Segments
),
Kind} of
{{hit, _, {index_implicit_table, _}}, table} ->
promote_implicit_to_concrete(Doc, Segments);
{{hit, _, {index_table, _}}, table} ->
{ok, Doc};
{{hit,
_,
{index_array_of_tables_entry, _, _, _}},
table} ->
{ok, Doc};
{{hit, _, {index_array_of_tables, _, _}},
array_of_tables} ->
{ok, Doc};
{{hit, _, Entry}, _} ->
Mismatch(
molt@internal@utils:index_entry_to_string(
Entry
)
);
{{miss, _, Ancestor, Tail}, _} ->
case Ancestor of
{index_scalar_value, _} ->
Mismatch(
molt@internal@utils:index_entry_to_string(
Ancestor
)
);
{index_array_value, _} ->
Mismatch(
molt@internal@utils:index_entry_to_string(
Ancestor
)
);
{index_inline_table_value, _} ->
Mismatch(
molt@internal@utils:index_entry_to_string(
Ancestor
)
);
_ ->
case molt@internal@path:contains_index(
Tail
) of
true ->
{error,
molt@error:not_found(P)};
false ->
do_build(
Doc,
Segments,
Kind
)
end
end;
{{fresh, _}, _} ->
case molt@internal@path:contains_index(
Segments
) of
true ->
{error, molt@error:not_found(P)};
false ->
do_build(Doc, Segments, Kind)
end
end
end
)
end
)
end
).
-file("src/molt/internal/document/structure.gleam", 194).
?DOC(false).
-spec overwrite_leaf(
molt@types:document(),
list(molt@types:path_segment()),
molt@value:value()
) -> {ok, molt@types:document()} | {error, molt@error:molt_error()}.
overwrite_leaf(Doc, Full, V) ->
gleam@result:'try'(
begin
_pipe = molt@internal@cst@query:get_cursor(
erlang:element(4, Doc),
Full
),
gleam@result:replace_error(_pipe, molt@error:not_found_path(Full))
end,
fun(Cursor) ->
New_node = molt@internal@document@primitives:rebuild_kv_value(
erlang:element(2, Cursor),
molt@value:to_cst(V)
),
_pipe@1 = greenwood@zipper:set_focus(Cursor, New_node),
_pipe@2 = greenwood@zipper:unzip(_pipe@1),
_pipe@3 = molt@internal@document@primitives:rebuild(Doc, _pipe@2),
{ok, _pipe@3}
end
).
-file("src/molt/internal/document/structure.gleam", 155).
?DOC(false).
-spec merge_one_entry(
molt@types:document(),
list(molt@types:path_segment()),
{binary(), molt@value:value()},
molt@ops:conflict_strategy()
) -> {ok, molt@types:document()} | {error, molt@error:molt_error()}.
merge_one_entry(Doc, Base, Entry, On_conflict) ->
{Rel_key, V} = Entry,
gleam@result:'try'(
molt@internal@path:parse(Rel_key),
fun(Rel_segments) ->
gleam@bool:guard(
molt@internal@path:contains_index(Rel_segments),
{error,
{invalid_path,
<<"merge_values entry key must not contain index segments: "/utf8,
Rel_key/binary>>}},
fun() ->
molt@internal@document@index:with_index(
Doc,
fun(Idx) ->
Full = lists:append(Base, Rel_segments),
case molt@internal@document@index:resolve(Idx, Full) of
{hit, _, Existing} ->
case On_conflict of
on_conflict_error ->
{error,
{already_exists,
molt@internal@path:to_string(
Full
),
Existing}};
on_conflict_skip ->
{ok, Doc};
on_conflict_overwrite ->
overwrite_leaf(Doc, Full, V)
end;
{miss, _, _, _} ->
Kv = molt@internal@cst@builder:build_kv_from_path(
molt@internal@path:path_to_table_header(
Rel_segments
),
molt@value:to_cst(V)
),
_pipe = molt@cst:insert_kv(
erlang:element(4, Doc),
Base,
Kv,
kv_at_end
),
gleam@result:map(
_pipe,
fun(_capture) ->
molt@internal@document@primitives:rebuild(
Doc,
_capture
)
end
);
{fresh, _} ->
Kv = molt@internal@cst@builder:build_kv_from_path(
molt@internal@path:path_to_table_header(
Rel_segments
),
molt@value:to_cst(V)
),
_pipe = molt@cst:insert_kv(
erlang:element(4, Doc),
Base,
Kv,
kv_at_end
),
gleam@result:map(
_pipe,
fun(_capture) ->
molt@internal@document@primitives:rebuild(
Doc,
_capture
)
end
)
end
end
)
end
)
end
).
-file("src/molt/internal/document/structure.gleam", 114).
?DOC(false).
-spec merge_values(
molt@types:document(),
binary(),
list({binary(), molt@value:value()}),
molt@ops:conflict_strategy()
) -> {ok, molt@types:document()} | {error, molt@error:molt_error()}.
merge_values(Doc, P, Entries, On_conflict) ->
gleam@result:'try'(
molt@internal@path:parse(P),
fun(Segments) ->
molt@internal@document@index:with_index(
Doc,
fun(Idx) ->
gleam@result:'try'(
case molt@internal@document@index:resolve(Idx, Segments) of
{hit, _, {index_table, _}} ->
{ok, nil};
{hit, _, {index_array_of_tables_entry, _, _, _}} ->
{ok, nil};
{hit, _, Entry} ->
{error,
{type_mismatch,
{some, P},
<<"concrete table"/utf8>>,
molt@internal@utils:index_entry_to_string(
Entry
)}};
{miss, _, _, _} ->
{error, molt@error:not_found(P)};
{fresh, _} ->
{error, molt@error:not_found(P)}
end,
fun(_) ->
gleam@result:'try'(
gleam@list:try_fold(
Entries,
Doc,
fun(D, Entry@1) ->
merge_one_entry(
D,
Segments,
Entry@1,
On_conflict
)
end
),
fun(Merged) ->
case molt@internal@validate:count(
erlang:element(4, Merged)
) of
0 ->
{ok, Merged};
_ ->
{error,
{invalid_operation,
<<"merge_values"/utf8>>,
{some,
<<<<"an entry would redefine an existing table or value at \""/utf8,
P/binary>>/binary,
"\""/utf8>>}}}
end
end
)
end
)
end
)
end
).