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, change/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 change() :: {source_change, binary()} | {priv_change, binary()}.
-type message() :: {files_changed, list(change())}.
-type state() :: {state,
gleam@option:option(gleam@erlang@process:timer()),
integer(),
gleam@erlang@process:subject(message()),
gleam@erlang@process:subject(internal_msg()),
gleam@set:set(change())}.
-opaque internal_msg() :: {trigger_files_changed,
binary(),
olive@config:directory()} |
trigger_watcher |
ignore_changes.
-type watch_events() :: updated_file | other_events.
-file("src/olive/watcher.gleam", 78).
-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", 139).
-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", 104).
-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_watcher ->
gleam@erlang@process:send(
erlang:element(4, State),
{files_changed, gleam@set:to_list(erlang:element(6, State))}
),
gleam@otp@actor:continue(
begin
_record = State,
{state,
none,
erlang:element(3, _record),
erlang:element(4, _record),
erlang:element(5, _record),
gleam@set:new()}
end
);
{trigger_files_changed, File_name, Dir} ->
maybe_cancel_timer(erlang:element(2, State)),
New_change = case Dir of
{source_directory, _} ->
{source_change, File_name};
{priv_directory, _} ->
{priv_change, File_name}
end,
Current_changes = gleam@set:insert(
erlang:element(6, State),
New_change
),
Timer = gleam@erlang@process:send_after(
erlang:element(5, State),
erlang:element(3, State),
trigger_watcher
),
gleam@otp@actor:continue(
begin
_record@1 = State,
{state,
{some, Timer},
erlang:element(3, _record@1),
erlang:element(4, _record@1),
erlang:element(5, _record@1),
Current_changes}
end
)
end.
-file("src/olive/watcher.gleam", 172).
-spec listen_directory_watcher(
gleam@erlang@process:selector(internal_msg()),
gleam@erlang@process:subject(internal_msg())
) -> any().
listen_directory_watcher(Selector, Subject) ->
Msg = gleam_erlang_ffi:select(Selector),
gleam@erlang@process:send(Subject, Msg),
listen_directory_watcher(Selector, Subject).
-file("src/olive/watcher.gleam", 232).
-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", 239).
?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, updated_file};
{ok, Ev@1} when Ev@1 =:= Renamed ->
{ok, updated_file};
_ ->
{ok, other_events}
end end
).
-file("src/olive/watcher.gleam", 181).
-spec watch_decoder(
olive@logging:logger(),
olive@config:directory(),
gleam@dynamic:dynamic_()
) -> internal_msg().
watch_decoder(Logger, Dir, 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), Dir} of
{{ok, {File_name@1, Events@1}}, {source_directory, _}} ->
case {filepath:extension(File_name@1),
gleam@list:contains(Events@1, updated_file)} of
{{ok, <<"gleam"/utf8>>}, true} ->
{trigger_files_changed, File_name@1, Dir};
{_, _} ->
ignore_changes
end;
{{ok, {File_name@2, Events@2}}, {priv_directory, _}} ->
case gleam@list:contains(Events@2, updated_file) of
true ->
{trigger_files_changed, File_name@2, Dir};
false ->
ignore_changes
end;
{{error, Decode_errors}, _} ->
Msg@1 = gleam@list:map(
Decode_errors,
fun(Error) ->
{decode_error, Expected, Found, Path} = Error,
<<<<<<<<<<"Expected "/utf8, Expected/binary>>/binary,
" at ["/utf8>>/binary,
(gleam@string:join(Path, <<","/utf8>>))/binary>>/binary,
"] but found "/utf8>>/binary,
Found/binary>>
end
),
olive@logging:error(
Logger,
<<"Error occured while watching files:\n"/utf8,
(gleam@string:join(Msg@1, <<"\n"/utf8>>))/binary>>
),
ignore_changes
end.
-file("src/olive/watcher.gleam", 149).
-spec start_directory_watcher(
olive@logging:logger(),
gleam@erlang@process:subject(internal_msg()),
olive@config:directory()
) -> gleam@erlang@process:pid_().
start_directory_watcher(Logger, Subject, Dir) ->
gleam@erlang@process:start(
fun() ->
Atom = erlang:binary_to_atom(
<<"fs_watcher_"/utf8, (erlang:element(2, Dir))/binary>>
),
_assert_subject = fs:start_link(Atom, erlang:element(2, 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 => <<"start_directory_watcher"/utf8>>,
line => 160})
end,
fs:subscribe(Atom),
Selector = begin
_pipe = gleam_erlang_ffi:new_selector(),
gleam@erlang@process:selecting_anything(
_pipe,
fun(_capture) -> watch_decoder(Logger, Dir, _capture) end
)
end,
listen_directory_watcher(Selector, Subject)
end,
true
).
-file("src/olive/watcher.gleam", 90).
-spec start_directory_watchers(
olive@config:config(),
gleam@erlang@process:subject(internal_msg())
) -> nil.
start_directory_watchers(Config, Subject) ->
_pipe = erlang:element(8, Config),
gleam@list:each(
_pipe,
fun(_capture) ->
start_directory_watcher(
erlang:element(2, Config),
Subject,
_capture
)
end
).
-file("src/olive/watcher.gleam", 60).
-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, _} ->
Subject = gleam@erlang@process:new_subject(),
start_directory_watchers(Config, Subject),
Selector = begin
_pipe = gleam_erlang_ffi:new_selector(),
gleam@erlang@process:selecting(
_pipe,
Subject,
fun gleam@function:identity/1
)
end,
{ready,
{state,
none,
erlang:element(9, Config),
Watch_subject,
Subject,
gleam@set:new()},
Selector}
end.
-file("src/olive/watcher.gleam", 52).
-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}
).