-module(automata@fsevent@diff). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/automata/fsevent/diff.gleam"). -export([compare_entry/2, diff/3]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -file("src/automata/fsevent/diff.gleam", 70). -spec ops_for_creation(automata@fsevent@entry:entry()) -> gleam@set:set(automata@fsevent@ast:op()). ops_for_creation(E) -> case automata@fsevent@entry:entry_kind(E) of file -> case automata@fsevent@entry:entry_size(E) > 0 of true -> automata@fsevent@op:ops_from_list([create, write]); false -> automata@fsevent@op:single_op(create) end; _ -> automata@fsevent@op:single_op(create) end. -file("src/automata/fsevent/diff.gleam", 92). -spec content_changed( automata@fsevent@entry:entry(), automata@fsevent@entry:entry() ) -> boolean(). content_changed(Old, New) -> case automata@fsevent@entry:entries_have_both_hashes(Old, New) of true -> not automata@fsevent@entry:entries_content_hash_equal(Old, New); false -> (automata@fsevent@entry:entry_size(Old) /= automata@fsevent@entry:entry_size( New )) orelse (automata@fsevent@entry:entry_mtime(Old) /= automata@fsevent@entry:entry_mtime( New )) end. -file("src/automata/fsevent/diff.gleam", 81). -spec ops_for_modification( automata@fsevent@entry:entry(), automata@fsevent@entry:entry() ) -> gleam@set:set(automata@fsevent@ast:op()). ops_for_modification(Old, New) -> Mode_changed = automata@fsevent@entry:entry_mode(Old) /= automata@fsevent@entry:entry_mode( New ), Content_changed = content_changed(Old, New), case {Content_changed, Mode_changed} of {false, false} -> automata@fsevent@op:empty_ops(); {false, true} -> automata@fsevent@op:single_op(chmod); {true, false} -> automata@fsevent@op:single_op(write); {true, true} -> automata@fsevent@op:ops_from_list([write, chmod]) end. -file("src/automata/fsevent/diff.gleam", 57). ?DOC(false). -spec compare_entry( gleam@option:option(automata@fsevent@entry:entry()), gleam@option:option(automata@fsevent@entry:entry()) ) -> gleam@set:set(automata@fsevent@ast:op()). compare_entry(Old, New) -> case {Old, New} of {none, none} -> automata@fsevent@op:empty_ops(); {none, {some, E}} -> ops_for_creation(E); {{some, _}, none} -> automata@fsevent@op:single_op(remove); {{some, O}, {some, N}} -> case automata@fsevent@entry:entry_kind(O) =:= automata@fsevent@entry:entry_kind( N ) of false -> automata@fsevent@op:ops_from_list([remove, create]); true -> ops_for_modification(O, N) end end. -file("src/automata/fsevent/diff.gleam", 130). -spec apply_rename( binary(), gleam@set:set(automata@fsevent@ast:op()), automata@fsevent@snapshot:snapshot(), gleam@option:option(automata@fsevent@entry:entry()), gleam@dict:dict(binary(), automata@fsevent@path:normalized_path()) ) -> {gleam@set:set(automata@fsevent@ast:op()), gleam@option:option(automata@fsevent@path:normalized_path())}. apply_rename(Key, Base_ops, Prev, Curr_entry, Renames) -> case Curr_entry of none -> {Base_ops, none}; {some, Curr} -> case gleam_stdlib:map_get(Renames, Key) of {error, _} -> {Base_ops, none}; {ok, From} -> From_key = automata@fsevent@path:path_to_string(From), Prev_at_from = automata@fsevent@snapshot:lookup_by_key( Prev, From_key ), Modification_ops = case Prev_at_from of {some, Old} -> ops_for_modification(Old, Curr); none -> gleam@set:delete(Base_ops, create) end, With_rename = gleam@set:insert(Modification_ops, rename), {With_rename, {some, From}} end end. -file("src/automata/fsevent/diff.gleam", 160). -spec apply_op_mask( {ok, automata@fsevent@event:watch_event()} | {error, nil}, automata@fsevent@watch:watch() ) -> {ok, automata@fsevent@event:watch_event()} | {error, nil}. apply_op_mask(Result, Watch) -> case Result of {error, _} -> Result; {ok, Ev} -> Masked = gleam@set:intersection( automata@fsevent@event:event_ops(Ev), automata@fsevent@watch:watch_op_mask(Watch) ), case gleam@set:size(Masked) =:= 0 of true -> {error, nil}; false -> case automata@fsevent@event:multi_op( automata@fsevent@event:event_path(Ev), Masked, case gleam@set:contains(Masked, rename) of true -> automata@fsevent@event:event_renamed_from(Ev); false -> none end ) of {ok, Masked_ev} -> {ok, Masked_ev}; {error, _} -> {error, nil} end end end. -file("src/automata/fsevent/diff.gleam", 190). -spec primary_path( gleam@option:option(automata@fsevent@entry:entry()), gleam@option:option(automata@fsevent@entry:entry()) ) -> gleam@option:option(automata@fsevent@path:normalized_path()). primary_path(Curr_entry, Prev_entry) -> case Curr_entry of {some, E} -> {some, automata@fsevent@entry:entry_path(E)}; none -> case Prev_entry of {some, E@1} -> {some, automata@fsevent@entry:entry_path(E@1)}; none -> none end end. -file("src/automata/fsevent/diff.gleam", 204). -spec path_union( automata@fsevent@snapshot:snapshot(), automata@fsevent@snapshot:snapshot() ) -> list(binary()). path_union(Prev, Curr) -> Prev_keys = gleam@set:from_list(automata@fsevent@snapshot:paths(Prev)), Curr_keys = gleam@set:from_list(automata@fsevent@snapshot:paths(Curr)), _pipe = gleam@set:union(Prev_keys, Curr_keys), gleam@set:to_list(_pipe). -file("src/automata/fsevent/diff.gleam", 211). -spec lookup_by_key(automata@fsevent@snapshot:snapshot(), binary()) -> gleam@option:option(automata@fsevent@entry:entry()). lookup_by_key(S, Key) -> automata@fsevent@snapshot:lookup_by_key(S, Key). -file("src/automata/fsevent/diff.gleam", 101). -spec build_event_for( binary(), automata@fsevent@snapshot:snapshot(), automata@fsevent@snapshot:snapshot(), gleam@dict:dict(binary(), automata@fsevent@path:normalized_path()) ) -> {ok, automata@fsevent@event:watch_event()} | {error, nil}. build_event_for(Key, Prev, Curr, Renames) -> Prev_entry = lookup_by_key(Prev, Key), Curr_entry = lookup_by_key(Curr, Key), Base_ops = compare_entry(Prev_entry, Curr_entry), {Final_ops, Renamed_from} = apply_rename( Key, Base_ops, Prev, Curr_entry, Renames ), case gleam@set:size(Final_ops) =:= 0 of true -> {error, nil}; false -> Target_path = primary_path(Curr_entry, Prev_entry), case Target_path of none -> {error, nil}; {some, P} -> case automata@fsevent@event:multi_op( P, Final_ops, Renamed_from ) of {ok, Ev} -> {ok, Ev}; {error, _} -> {error, nil} end end end. -file("src/automata/fsevent/diff.gleam", 242). -spec match_disappearance_to_appearance( list(automata@fsevent@entry:entry()), list(automata@fsevent@entry:entry()), gleam@set:set(binary()), gleam@set:set(binary()), gleam@dict:dict(binary(), automata@fsevent@path:normalized_path()) ) -> gleam@dict:dict(binary(), automata@fsevent@path:normalized_path()). match_disappearance_to_appearance( Prev_entries, Curr_entries, Curr_paths, Prev_paths, Acc ) -> Disappeared = begin _pipe = Prev_entries, gleam@list:filter( _pipe, fun(E) -> not gleam@set:contains( Curr_paths, automata@fsevent@snapshot:entry_key(E) ) end ) end, Appeared = begin _pipe@1 = Curr_entries, gleam@list:filter( _pipe@1, fun(E@1) -> not gleam@set:contains( Prev_paths, automata@fsevent@snapshot:entry_key(E@1) ) end ) end, case {Disappeared, Appeared} of {[Old], [New]} -> gleam@dict:insert( Acc, automata@fsevent@snapshot:entry_key(New), automata@fsevent@entry:entry_path(Old) ); {_, _} -> Acc end. -file("src/automata/fsevent/diff.gleam", 262). -spec group_by_file_id(list(automata@fsevent@entry:entry())) -> gleam@dict:dict(binary(), list(automata@fsevent@entry:entry())). group_by_file_id(Entries) -> gleam@list:fold( Entries, maps:new(), fun(Acc, E) -> case automata@fsevent@entry:entry_file_id(E) of none -> Acc; {some, Id} -> case gleam_stdlib:map_get(Acc, Id) of {ok, Existing} -> gleam@dict:insert(Acc, Id, [E | Existing]); {error, _} -> gleam@dict:insert(Acc, Id, [E]) end end end ). -file("src/automata/fsevent/diff.gleam", 215). -spec detect_renames( automata@fsevent@snapshot:snapshot(), automata@fsevent@snapshot:snapshot() ) -> gleam@dict:dict(binary(), automata@fsevent@path:normalized_path()). detect_renames(Prev, Curr) -> Prev_groups = group_by_file_id( automata@fsevent@snapshot:entries_unsorted(Prev) ), Curr_groups = group_by_file_id( automata@fsevent@snapshot:entries_unsorted(Curr) ), Prev_paths = gleam@set:from_list(automata@fsevent@snapshot:paths(Prev)), Curr_paths = gleam@set:from_list(automata@fsevent@snapshot:paths(Curr)), _pipe = Prev_groups, _pipe@1 = maps:to_list(_pipe), gleam@list:fold( _pipe@1, maps:new(), fun(Acc, Pair) -> {File_id, Prev_entries} = Pair, case gleam_stdlib:map_get(Curr_groups, File_id) of {error, _} -> Acc; {ok, Curr_entries} -> match_disappearance_to_appearance( Prev_entries, Curr_entries, Curr_paths, Prev_paths, Acc ) end end ). -file("src/automata/fsevent/diff.gleam", 275). -spec renames_suppressed_paths( gleam@dict:dict(binary(), automata@fsevent@path:normalized_path()) ) -> gleam@set:set(binary()). renames_suppressed_paths(Renames) -> _pipe = Renames, _pipe@1 = maps:to_list(_pipe), _pipe@2 = gleam@list:map( _pipe@1, fun(Pair) -> automata@fsevent@path:path_to_string(erlang:element(2, Pair)) end ), gleam@set:from_list(_pipe@2). -file("src/automata/fsevent/diff.gleam", 27). ?DOC( " Compute the events emitted when `prev` is replaced by `curr` under\n" " the subscription described by `watch`.\n" "\n" " The result list is sorted by canonical path so the output is\n" " deterministic across the BEAM and JavaScript targets.\n" "\n" " Rename detection: when the same `file_id` is observed at two\n" " different paths across `prev` and `curr` and the disappearance and\n" " appearance pair up one-to-one, the differ folds the would-be\n" " `Remove` (at the old path) and `Create` (at the new path) into a\n" " single `Rename` event whose `renamed_from` carries the old path.\n" " If no `file_id` is available, or the pairing is ambiguous, the\n" " rename is reported as a `Remove`/`Create` pair instead.\n" ). -spec diff( automata@fsevent@snapshot:snapshot(), automata@fsevent@snapshot:snapshot(), automata@fsevent@watch:watch() ) -> list(automata@fsevent@event:watch_event()). diff(Prev, Curr, Watch) -> Renames = detect_renames(Prev, Curr), Suppressed_old_paths = renames_suppressed_paths(Renames), Union = path_union(Prev, Curr), _pipe = Union, _pipe@2 = gleam@list:filter_map( _pipe, fun(Key) -> case gleam@set:contains(Suppressed_old_paths, Key) of true -> {error, nil}; false -> _pipe@1 = build_event_for(Key, Prev, Curr, Renames), apply_op_mask(_pipe@1, Watch) end end ), gleam@list:sort( _pipe@2, fun(A, B) -> gleam@string:compare( automata@fsevent@path:path_to_string( automata@fsevent@event:event_path(A) ), automata@fsevent@path:path_to_string( automata@fsevent@event:event_path(B) ) ) end ).