Current section

Files

Jump to
filespy src filespy.erl
Raw

src/filespy.erl

-module(filespy).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-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, spec/1, 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(HXB) :: {change, binary(), list(event())} | {custom, HXB}.
-opaque builder(HXC, HXD, HXE, HXF, HXG) :: {builder,
list(binary()),
gleam@option:option(fun((change(HXG), HXC) -> gleam@otp@actor:next(change(HXG), HXC))),
gleam@option:option(fun(() -> HXC))} |
{gleam_phantom, HXD, HXE, HXF}.
-file("src/filespy.gleam", 78).
?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", 90).
?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(HXV, any(), HXX, HXY, HXZ), binary()) -> builder(HXV, has_directories(), HXX, HXY, HXZ).
add_dir(Builder, Directory) ->
{builder,
[Directory | erlang:element(2, Builder)],
erlang:element(3, Builder),
erlang:element(4, Builder)}.
-file("src/filespy.gleam", 101).
?DOC(" Add multiple directories at once\n").
-spec add_dirs(builder(HYK, any(), HYM, HYN, HYO), list(binary())) -> builder(HYK, has_directories(), HYM, HYN, HYO).
add_dirs(Builder, Directories) ->
{builder,
lists:append(Directories, erlang:element(2, Builder)),
erlang:element(3, Builder),
erlang:element(4, Builder)}.
-file("src/filespy.gleam", 154).
?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(HZM, HZN, no_handler(), HZO, HZP),
fun((change(HZP), HZM) -> gleam@otp@actor:next(change(HZP), HZM))
) -> builder(HZM, HZN, has_handler(), HZO, HZP).
set_actor_handler(Builder, Handler) ->
{builder,
erlang:element(2, Builder),
{some, Handler},
erlang:element(4, Builder)}.
-file("src/filespy.gleam", 170).
?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(IAC, IAD, IAE, no_initial_state(), IAF), IAC) -> builder(IAC, IAD, IAE, has_initial_state(), IAF).
set_initial_state(Builder, State) ->
{builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
{some, fun() -> State end}}.
-file("src/filespy.gleam", 129).
?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, HZA, no_handler(), no_initial_state(), HZB),
fun((binary(), event()) -> nil)
) -> builder(nil, HZA, has_handler(), has_initial_state(), HZB).
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", 186).
?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(IAQ, IAR, IAS, no_initial_state(), IAT),
fun(() -> IAQ)
) -> builder(IAQ, IAR, IAS, has_initial_state(), IAT).
set_initializer(Builder, Initializer) ->
{builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
{some, Initializer}}.
-file("src/filespy.gleam", 204).
?DOC(" Decode an atom to an `Event`.\n").
-spec event_decoder() -> gleam@dynamic@decode:decoder(event()).
event_decoder() ->
gleam@dynamic@decode:then(
{decoder, fun gleam@dynamic@decode:decode_dynamic/1},
fun(Content) -> case gleam_erlang_ffi:atom_from_dynamic(Content) of
{error, _} ->
gleam@dynamic@decode:failure(created, <<"Event"/utf8>>);
{ok, 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 ->
gleam@dynamic@decode:success(created);
Ev@1 when Ev@1 =:= Deleted ->
gleam@dynamic@decode:success(deleted);
Ev@2 when Ev@2 =:= Modified ->
gleam@dynamic@decode:success(modified);
Ev@3 when Ev@3 =:= Closed ->
gleam@dynamic@decode:success(closed);
Ev@4 when Ev@4 =:= Renamed ->
gleam@dynamic@decode:success(renamed);
Ev@5 when Ev@5 =:= Removed ->
gleam@dynamic@decode:success(deleted);
Ev@6 when Ev@6 =:= Attrib ->
gleam@dynamic@decode:success(attribute);
Other ->
gleam@dynamic@decode:success({unknown, Other})
end
end end
).
-file("src/filespy.gleam", 232).
?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", 238).
?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", 250).
?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:selecting_anything(
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", 259).
?DOC(" Get an actor `Spec` for the watcher\n").
-spec spec(
builder(IBK, has_directories(), has_handler(), has_initial_state(), IBL)
) -> gleam@otp@actor:spec(IBK, change(IBL)).
spec(Builder) ->
_assert_subject = erlang:element(3, Builder),
{some, Handler} = case _assert_subject of
{some, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
value => _assert_fail,
module => <<"filespy"/utf8>>,
function => <<"spec"/utf8>>,
line => 262})
end,
_assert_subject@1 = erlang:element(4, Builder),
{some, Initializer} = case _assert_subject@1 of
{some, _} -> _assert_subject@1;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
value => _assert_fail@1,
module => <<"filespy"/utf8>>,
function => <<"spec"/utf8>>,
line => 263})
end,
{spec,
fun() ->
{Oks, Errs} = begin
_pipe = erlang:element(2, Builder),
_pipe@2 = gleam@list:map(
_pipe,
fun(Dir) ->
Atom = erlang:binary_to_atom(
<<"fs_watcher"/utf8, Dir/binary>>
),
_pipe@1 = fs:start_link(Atom, Dir),
gleam@result:map(_pipe@1, fun(Pid) -> {Pid, Atom} end)
end
),
gleam@result:partition(_pipe@2)
end,
case Errs of
[] ->
_pipe@3 = Oks,
gleam@list:each(
_pipe@3,
fun(Res) ->
{_, Atom@1} = Res,
fs:subscribe(Atom@1)
end
),
{ready, Initializer(), selector()};
Errs@1 ->
gleam@list:each(
Oks,
fun(Res@1) ->
{Pid@1, _} = Res@1,
gleam@erlang@process:kill(Pid@1)
end
),
{failed,
<<"Failed to start watcher: "/utf8,
(gleam@string:inspect(Errs@1))/binary>>}
end
end,
5000,
Handler}.
-file("src/filespy.gleam", 305).
?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(), IBV)
) -> {ok, gleam@erlang@process:subject(change(IBV))} |
{error, gleam@otp@actor:start_error()}.
start(Builder) ->
_pipe = spec(Builder),
gleam@otp@actor:start_spec(_pipe).