-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 ).