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