Packages

A gleam dev proxy to enable live reload and smoother DX

Current section

Files

Jump to
olive src olive@watcher.erl
Raw

src/olive@watcher.erl

-module(olive@watcher).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([start/2]).
-export_type([watcher_error/0, message/0, state/0, internal_msg/0, watch_events/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 `watcher` module watches a list of dir and triggers a `FilesChanged` message\n"
" to be handled by calling code.\n"
).
-type watcher_error() :: no_file_watcher_supported_for_os |
{no_file_watcher_installed, gleam@dynamic:dynamic_()}.
-type message() :: {files_changed, binary()}.
-type state() :: {state,
gleam@option:option(gleam@erlang@process:timer()),
gleam@erlang@process:subject(message())}.
-opaque internal_msg() :: {trigger_files_changed, binary()} | ignore_changes.
-type watch_events() :: event_needing_rebuild | other_events.
-file("src/olive/watcher.gleam", 57).
-spec check_watcher_install() -> {ok, nil} | {error, binary()}.
check_watcher_install() ->
case olive_ffi:check_watcher_installed() of
{error, no_file_watcher_supported_for_os} ->
{error, <<"No file watcher supported for your OS"/utf8>>};
{error, {no_file_watcher_installed, Watcher}} ->
{error,
<<"No file watcher installed, please install: "/utf8,
(gleam@string:inspect(Watcher))/binary>>};
{ok, _} ->
{ok, nil}
end.
-file("src/olive/watcher.gleam", 94).
-spec maybe_cancel_timer(gleam@option:option(gleam@erlang@process:timer())) -> nil.
maybe_cancel_timer(Timer) ->
case Timer of
none ->
nil;
{some, Timer@1} ->
gleam@erlang@process:cancel_timer(Timer@1),
nil
end.
-file("src/olive/watcher.gleam", 75).
-spec do_loop(internal_msg(), state()) -> gleam@otp@actor:next(any(), state()).
do_loop(Msg, State) ->
case Msg of
ignore_changes ->
gleam@otp@actor:continue(State);
{trigger_files_changed, File_name} ->
maybe_cancel_timer(erlang:element(2, State)),
Timer = gleam@erlang@process:send_after(
erlang:element(3, State),
50,
{files_changed, File_name}
),
gleam@otp@actor:continue(
begin
_record = State,
{state, {some, Timer}, erlang:element(3, _record)}
end
)
end.
-file("src/olive/watcher.gleam", 146).
-spec erlang_string_to_string_decoder() -> gleam@dynamic@decode:decoder(binary()).
erlang_string_to_string_decoder() ->
gleam@dynamic@decode:new_primitive_decoder(
<<"ErlangString"/utf8>>,
fun(Data) -> _pipe = olive_ffi:coerce(Data),
_pipe@1 = unicode:characters_to_binary(_pipe),
{ok, _pipe@1} end
).
-file("src/olive/watcher.gleam", 153).
?DOC(" Converts an atom to an event\n").
-spec atom_to_watch_events() -> gleam@dynamic@decode:decoder(watch_events()).
atom_to_watch_events() ->
Modified = erlang:binary_to_atom(<<"modified"/utf8>>),
Renamed = erlang:binary_to_atom(<<"renamed"/utf8>>),
gleam@dynamic@decode:new_primitive_decoder(
<<"Atom"/utf8>>,
fun(Data) -> case gleam_erlang_ffi:atom_from_dynamic(Data) of
{ok, Ev} when Ev =:= Modified ->
{ok, event_needing_rebuild};
{ok, Ev@1} when Ev@1 =:= Renamed ->
{ok, event_needing_rebuild};
_ ->
{ok, other_events}
end end
).
-file("src/olive/watcher.gleam", 115).
-spec watch_decoder(gleam@dynamic:dynamic_()) -> internal_msg().
watch_decoder(Msg) ->
Decoder = begin
gleam@dynamic@decode:subfield(
[2, 0],
erlang_string_to_string_decoder(),
fun(File_name) ->
gleam@dynamic@decode:subfield(
[2, 1],
gleam@dynamic@decode:list(atom_to_watch_events()),
fun(Events) ->
gleam@dynamic@decode:success({File_name, Events})
end
)
end
)
end,
case gleam@dynamic@decode:run(Msg, Decoder) of
{ok, {File_name@1, Events@1}} ->
case {filepath:extension(File_name@1),
gleam@list:contains(Events@1, event_needing_rebuild)} of
{{ok, <<"gleam"/utf8>>}, true} ->
{trigger_files_changed, File_name@1};
{_, _} ->
ignore_changes
end;
{error, _} ->
olive@logging:error(<<"Error occured while watching files"/utf8>>),
ignore_changes
end.
-file("src/olive/watcher.gleam", 104).
-spec watch_folder(binary()) -> gleam@erlang@process:selector(internal_msg()).
watch_folder(Dir) ->
Atom = erlang:binary_to_atom(<<"fs_watcher_"/utf8, Dir/binary>>),
_assert_subject = fs:start_link(Atom, Dir),
{ok, _} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
value => _assert_fail,
module => <<"olive/watcher"/utf8>>,
function => <<"watch_folder"/utf8>>,
line => 107})
end,
fs:subscribe(Atom),
Selectors = begin
_pipe = gleam_erlang_ffi:new_selector(),
gleam@erlang@process:selecting_anything(_pipe, fun watch_decoder/1)
end,
Selectors.
-file("src/olive/watcher.gleam", 69).
-spec start_watchers(list(binary())) -> gleam@erlang@process:selector(internal_msg()).
start_watchers(Dirs) ->
_pipe = Dirs,
_pipe@1 = gleam@list:map(_pipe, fun watch_folder/1),
gleam@list:fold(
_pipe@1,
gleam_erlang_ffi:new_selector(),
fun gleam_erlang_ffi:merge_selector/2
).
-file("src/olive/watcher.gleam", 47).
-spec init_watcher(
olive@config:config(),
gleam@erlang@process:subject(message())
) -> gleam@otp@actor:init_result(state(), internal_msg()).
init_watcher(Config, Watch_subject) ->
case check_watcher_install() of
{error, Err} ->
{failed, Err};
{ok, _} ->
Selectors = start_watchers(erlang:element(7, Config)),
{ready, {state, none, Watch_subject}, Selectors}
end.
-file("src/olive/watcher.gleam", 39).
-spec start(olive@config:config(), gleam@erlang@process:subject(message())) -> {ok,
gleam@erlang@process:subject(internal_msg())} |
{error, gleam@otp@actor:start_error()}.
start(Config, Watch_subject) ->
gleam@otp@actor:start_spec(
{spec,
fun() -> init_watcher(Config, Watch_subject) end,
5000,
fun do_loop/2}
).