Current section
Files
Jump to
Current section
Files
src/filespy.erl
-module(filespy).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/filespy.gleam").
-export([new/0, add_dir/2, add_dirs/2, set_actor_handler/2, set_initial_state/2, set_handler/2, set_initializer/2, selector/0, start/1]).
-export_type([no_directories/0, has_directories/0, no_handler/0, has_handler/0, no_initial_state/0, has_initial_state/0, event/0, change/1, builder/5]).
-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(
" This module is a wrapper around the erlang [FS](https://fs.n2o.dev/)\n"
" library. It allows you to create a listener for file change events for any\n"
" operating system.\n"
"\n"
" Note: on Linux and BSD, you need `inotify-tools` installed.\n"
).
-type no_directories() :: any().
-type has_directories() :: any().
-type no_handler() :: any().
-type has_handler() :: any().
-type no_initial_state() :: any().
-type has_initial_state() :: any().
-type event() :: created |
modified |
closed |
deleted |
renamed |
attribute |
{unknown, gleam@erlang@atom:atom_()}.
-type change(FSW) :: {change, binary(), list(event())} | {custom, FSW}.
-opaque builder(FSX, FSY, FSZ, FTA, FTB) :: {builder,
list(binary()),
gleam@option:option(fun((FSX, change(FTB)) -> gleam@otp@actor:next(FSX, change(FTB)))),
gleam@option:option(fun((gleam@erlang@process:subject(change(FTB))) -> {ok,
gleam@otp@actor:initialised(FSX, change(FTB), gleam@erlang@process:subject(change(FTB)))} |
{error, binary()}))} |
{gleam_phantom, FSY, FSZ, FTA}.
-file("src/filespy.gleam", 84).
?DOC(
" Create a new builder\n"
"\n"
" Use this with the `add_dir` and `handler` functions to configure the\n"
" watcher\n"
).
-spec new() -> builder(any(), no_directories(), no_handler(), no_initial_state(), nil).
new() ->
{builder, [], none, none}.
-file("src/filespy.gleam", 96).
?DOC(
" Add a directory to watch\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" filespy.new()\n"
" |> filespy.add_dir(\"./watched\")\n"
" ```\n"
).
-spec add_dir(builder(FTQ, any(), FTS, FTT, FTU), binary()) -> builder(FTQ, has_directories(), FTS, FTT, FTU).
add_dir(Builder, Directory) ->
{builder,
[Directory | erlang:element(2, Builder)],
erlang:element(3, Builder),
erlang:element(4, Builder)}.
-file("src/filespy.gleam", 107).
?DOC(" Add multiple directories at once\n").
-spec add_dirs(builder(FUF, any(), FUH, FUI, FUJ), list(binary())) -> builder(FUF, has_directories(), FUH, FUI, FUJ).
add_dirs(Builder, Directories) ->
{builder,
lists:append(Directories, erlang:element(2, Builder)),
erlang:element(3, Builder),
erlang:element(4, Builder)}.
-file("src/filespy.gleam", 160).
?DOC(
" Set the actor handler\n"
"\n"
" Use this if you want to have a more thorough control over the generated\n"
" actor. You will also need to set an initial state, with `set_initial_state`.\n"
).
-spec set_actor_handler(
builder(FVH, FVI, no_handler(), FVJ, FVK),
fun((FVH, change(FVK)) -> gleam@otp@actor:next(FVH, change(FVK)))
) -> builder(FVH, FVI, has_handler(), FVJ, FVK).
set_actor_handler(Builder, Handler) ->
{builder,
erlang:element(2, Builder),
{some, Handler},
erlang:element(4, Builder)}.
-file("src/filespy.gleam", 176).
?DOC(
" Set the initial state\n"
"\n"
" Use this if you want to have a more thorough control over the generated\n"
" actor. You'll only have access to this state if you set your handler with\n"
" `set_actor_handler`.\n"
).
-spec set_initial_state(builder(FVX, FVY, FVZ, no_initial_state(), FWA), FVX) -> builder(FVX, FVY, FVZ, has_initial_state(), FWA).
set_initial_state(Builder, State) ->
{builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
{some,
fun(Self) ->
{ok,
begin
_pipe = gleam@otp@actor:initialised(State),
gleam@otp@actor:returning(_pipe, Self)
end}
end}}.
-file("src/filespy.gleam", 135).
?DOC(
" Set the handler\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" filespy.new()\n"
" |> filespy.add_dir(\"./watched\")\n"
" |> filespy.set_handler(fn (path: String, event: filespy.Event) {\n"
" case event {\n"
" filespy.Created -> {\n"
" io.println(\"File \" <> path <> \" created\")\n"
" }\n"
" _ -> {\n"
" io.println(\"Something else happened to \" <> path)\n"
" }\n"
" }\n"
" })\n"
).
-spec set_handler(
builder(nil, FUV, no_handler(), no_initial_state(), FUW),
fun((binary(), event()) -> nil)
) -> builder(nil, FUV, has_handler(), has_initial_state(), FUW).
set_handler(Builder, Handler) ->
Wrapped_handler = fun(_, Event) ->
case Event of
{change, Path, Events} ->
gleam@list:each(Events, fun(Ev) -> Handler(Path, Ev) end);
_ ->
nil
end,
gleam@otp@actor:continue(nil)
end,
_pipe = Builder,
_pipe@1 = set_actor_handler(_pipe, Wrapped_handler),
set_initial_state(_pipe@1, nil).
-file("src/filespy.gleam", 197).
?DOC(
" Set the initializer\n"
"\n"
" Use this if you want to, for example, return a subject in the state. The\n"
" initializer will run in the actor's `on_init`, so it'll run under the\n"
" watcher process\n"
).
-spec set_initializer(
builder(FWL, FWM, FWN, no_initial_state(), FWO),
fun((gleam@erlang@process:subject(change(FWO))) -> {ok,
gleam@otp@actor:initialised(FWL, change(FWO), gleam@erlang@process:subject(change(FWO)))} |
{error, binary()})
) -> builder(FWL, FWM, FWN, has_initial_state(), FWO).
set_initializer(Builder, Initializer) ->
{builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
{some, Initializer}}.
-file("src/filespy.gleam", 219).
?DOC(" Decode an atom to an `Event`.\n").
-spec event_decoder() -> gleam@dynamic@decode:decoder(event()).
event_decoder() ->
gleam@dynamic@decode:map(
gleam@erlang@atom:decoder(),
fun(Event) ->
Created = erlang:binary_to_atom(<<"created"/utf8>>),
Deleted = erlang:binary_to_atom(<<"deleted"/utf8>>),
Modified = erlang:binary_to_atom(<<"modified"/utf8>>),
Closed = erlang:binary_to_atom(<<"closed"/utf8>>),
Renamed = erlang:binary_to_atom(<<"renamed"/utf8>>),
Attrib = erlang:binary_to_atom(<<"attribute"/utf8>>),
Removed = erlang:binary_to_atom(<<"removed"/utf8>>),
case Event of
Ev when Ev =:= Created ->
created;
Ev@1 when Ev@1 =:= Deleted ->
deleted;
Ev@2 when Ev@2 =:= Modified ->
modified;
Ev@3 when Ev@3 =:= Closed ->
closed;
Ev@4 when Ev@4 =:= Renamed ->
renamed;
Ev@5 when Ev@5 =:= Removed ->
deleted;
Ev@6 when Ev@6 =:= Attrib ->
attribute;
Other ->
{unknown, Other}
end
end
).
-file("src/filespy.gleam", 242).
?DOC(
" Decode a [`charlist`](https://hexdocs.pm/gleam_erlang/gleam/erlang/charlist.html#Charlist)\n"
" from a `Dynamic` value, and convert it to `String`.\n"
).
-spec charlist_decoder() -> gleam@dynamic@decode:decoder(binary()).
charlist_decoder() ->
gleam@dynamic@decode:map(
{decoder, fun gleam@dynamic@decode:decode_dynamic/1},
fun(Chars) ->
unicode:characters_to_binary(filespy_ffi:identity(Chars))
end
).
-file("src/filespy.gleam", 248).
?DOC(" Decode `fs` events. Return type is `#(Pid, FileEvent, #(Path, List(Event)))`.\n").
-spec change_decoder() -> gleam@dynamic@decode:decoder({gleam@dynamic:dynamic_(),
gleam@dynamic:dynamic_(),
{binary(), list(event())}}).
change_decoder() ->
gleam@dynamic@decode:field(
0,
{decoder, fun gleam@dynamic@decode:decode_dynamic/1},
fun(Pid) ->
gleam@dynamic@decode:field(
1,
{decoder, fun gleam@dynamic@decode:decode_dynamic/1},
fun(File_event) ->
gleam@dynamic@decode:field(
2,
begin
gleam@dynamic@decode:field(
0,
charlist_decoder(),
fun(Path) ->
gleam@dynamic@decode:field(
1,
gleam@dynamic@decode:list(
event_decoder()
),
fun(Events) ->
gleam@dynamic@decode:success(
{Path, Events}
)
end
)
end
)
end,
fun(All_events) ->
gleam@dynamic@decode:success(
{Pid, File_event, All_events}
)
end
)
end
)
end
).
-file("src/filespy.gleam", 260).
?DOC(" Get a `Selector` which can be used to select for filesystem events.\n").
-spec selector() -> gleam@erlang@process:selector(change(any())).
selector() ->
gleam@erlang@process:select_other(
gleam_erlang_ffi:new_selector(),
fun(Event) -> case gleam@dynamic@decode:run(Event, change_decoder()) of
{ok, {_, _, {Path, Events}}} ->
{change, Path, Events};
_ ->
{change, <<""/utf8>>, []}
end end
).
-file("src/filespy.gleam", 273).
?DOC(
" Start an actor which will receive filesystem events\n"
"\n"
" In order for this to work, you'll need to have set some directories to be\n"
" watched, with `add_dir`, and set a handler with either `set_handler` or\n"
" `set_actor_handler` and `set_initial_state`\n"
).
-spec start(
builder(any(), has_directories(), has_handler(), has_initial_state(), FXS)
) -> {ok, gleam@otp@actor:started(gleam@erlang@process:subject(change(FXS)))} |
{error, gleam@otp@actor:start_error()}.
start(Builder) ->
Handler@1 = case erlang:element(3, Builder) of
{some, Handler} -> Handler;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"filespy"/utf8>>,
function => <<"start"/utf8>>,
line => 276,
value => _assert_fail,
start => 7891,
'end' => 7933,
pattern_start => 7902,
pattern_end => 7915})
end,
Initializer@1 = case erlang:element(4, Builder) of
{some, Initializer} -> Initializer;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"filespy"/utf8>>,
function => <<"start"/utf8>>,
line => 277,
value => _assert_fail@1,
start => 7936,
'end' => 7986,
pattern_start => 7947,
pattern_end => 7964})
end,
Composed_initializer = fun(Self) ->
{Oks, Errs} = begin
_pipe = erlang:element(2, Builder),
_pipe@2 = gleam@list:map(
_pipe,
fun(Dir) ->
Watcher = erlang:binary_to_atom(
<<"fs_watcher"/utf8, Dir/binary>>
),
_pipe@1 = fs:start_link(Watcher, Dir),
gleam@result:map(_pipe@1, fun(Pid) -> {Pid, Watcher} end)
end
),
gleam@result:partition(_pipe@2)
end,
case Errs of
[] ->
_pipe@3 = Oks,
gleam@list:each(
_pipe@3,
fun(Res) ->
{_, Watcher@1} = Res,
fs:subscribe(Watcher@1)
end
),
gleam@result:map(Initializer@1(Self), fun(A) -> _pipe@4 = A,
gleam@otp@actor:selecting(_pipe@4, selector()) end);
Errs@1 ->
gleam@list:each(
Oks,
fun(Res@1) ->
{Pid@1, _} = Res@1,
gleam@erlang@process:kill(Pid@1)
end
),
{error,
<<"Failed to start watcher: "/utf8,
(gleam@string:inspect(Errs@1))/binary>>}
end
end,
_pipe@5 = gleam@otp@actor:new_with_initialiser(5000, Composed_initializer),
_pipe@6 = gleam@otp@actor:on_message(_pipe@5, Handler@1),
gleam@otp@actor:start(_pipe@6).