Current section
Files
Jump to
Current section
Files
src/fio@observer.erl
-module(fio@observer).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/fio/observer.gleam").
-export([emit/5, trace/4, trace_bytes/4, format/1, fan_out/2, noop_sink/1]).
-export_type([event/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.
-type event() :: {event,
binary(),
binary(),
{ok, nil} | {error, fio@error:fio_error()},
gleam@option:option(integer())}.
-file("src/fio/observer.gleam", 100).
?DOC(
" Emit an `Event` to `sink` after `result` is produced, then return `result`\n"
" unchanged.\n"
"\n"
" This is the lowest-level building block. Use `trace` when you do not have\n"
" a byte count to report.\n"
"\n"
" ```gleam\n"
" fio.read_bits(\"data.bin\")\n"
" |> observer.emit(\"read_bits\", \"data.bin\", option.None, my_sink)\n"
" ```\n"
).
-spec emit(
{ok, FBF} | {error, fio@error:fio_error()},
binary(),
binary(),
gleam@option:option(integer()),
fun((event()) -> nil)
) -> {ok, FBF} | {error, fio@error:fio_error()}.
emit(Result, Op, Path, Bytes, Sink) ->
Outcome = gleam@result:map(Result, fun(_) -> nil end),
Sink({event, Op, Path, Outcome, Bytes}),
Result.
-file("src/fio/observer.gleam", 125).
?DOC(
" Like `emit` but without a byte count (`bytes` is always `None`).\n"
"\n"
" Use this for operations where byte count is not meaningful (delete, rename,\n"
" touch, create_directory, etc.):\n"
"\n"
" ```gleam\n"
" fio.delete(\"old.txt\")\n"
" |> observer.trace(\"delete\", \"old.txt\", my_sink)\n"
" ```\n"
).
-spec trace(
{ok, FBL} | {error, fio@error:fio_error()},
binary(),
binary(),
fun((event()) -> nil)
) -> {ok, FBL} | {error, fio@error:fio_error()}.
trace(Result, Op, Path, Sink) ->
emit(Result, Op, Path, none, Sink).
-file("src/fio/observer.gleam", 141).
?DOC(
" Like `trace` but automatically infers the byte count from the result when\n"
" the operation returns a `BitArray` (e.g. `fio.read_bits`).\n"
"\n"
" ```gleam\n"
" fio.read_bits(\"archive.tar.gz\")\n"
" |> observer.trace_bytes(\"read_bits\", \"archive.tar.gz\", my_sink)\n"
" ```\n"
).
-spec trace_bytes(
{ok, bitstring()} | {error, fio@error:fio_error()},
binary(),
binary(),
fun((event()) -> nil)
) -> {ok, bitstring()} | {error, fio@error:fio_error()}.
trace_bytes(Result, Op, Path, Sink) ->
Bytes = case Result of
{ok, Data} ->
{some, erlang:byte_size(Data)};
{error, _} ->
none
end,
emit(Result, Op, Path, Bytes, Sink).
-file("src/fio/observer.gleam", 166).
?DOC(
" Format an `Event` as a human-readable string.\n"
" Useful when building simple logging sinks.\n"
"\n"
" ```gleam\n"
" fn log_sink(event: Event) -> Nil {\n"
" io.println(observer.format(event))\n"
" }\n"
" ```\n"
).
-spec format(event()) -> binary().
format(Event) ->
Status = case erlang:element(4, Event) of
{ok, _} ->
<<"ok"/utf8>>;
{error, E} ->
<<<<"err("/utf8, (fio@error:describe(E))/binary>>/binary, ")"/utf8>>
end,
Bytes_str = case erlang:element(5, Event) of
none ->
<<""/utf8>>;
{some, N} ->
<<" bytes="/utf8, (erlang:integer_to_binary(N))/binary>>
end,
<<<<<<<<<<<<"[fio] "/utf8, (erlang:element(2, Event))/binary>>/binary,
" "/utf8>>/binary,
(erlang:element(3, Event))/binary>>/binary,
" -> "/utf8>>/binary,
Status/binary>>/binary,
Bytes_str/binary>>.
-file("src/fio/observer.gleam", 186).
?DOC(
" Combine two sinks into one: both receive every event in order.\n"
"\n"
" Useful for fan-out — log to stdout AND record in a test buffer:\n"
"\n"
" ```gleam\n"
" let combined = observer.fan_out(log_sink, test_recorder_sink)\n"
" fio.write(\"out.txt\", data) |> observer.trace(\"write\", \"out.txt\", combined)\n"
" ```\n"
).
-spec fan_out(fun((event()) -> nil), fun((event()) -> nil)) -> fun((event()) -> nil).
fan_out(First, Second) ->
fun(Event) ->
First(Event),
Second(Event)
end.
-file("src/fio/observer.gleam", 203).
?DOC(
" A no-op sink that discards all events.\n"
" Useful as a default/placeholder argument when observability is optional.\n"
"\n"
" ```gleam\n"
" pub fn copy(src, dest, sink: observer.Sink) {\n"
" fio.copy(src, dest) |> observer.trace(\"copy\", src, sink)\n"
" }\n"
" // caller passes observer.noop_sink when not interested\n"
" copy(\"a.txt\", \"b.txt\", observer.noop_sink)\n"
" ```\n"
).
-spec noop_sink(event()) -> nil.
noop_sink(_) ->
nil.