-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.