Current section
Files
Jump to
Current section
Files
src/fio@handle.erl
-module(fio@handle).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/fio/handle.gleam").
-export([open/2, close/1, with/3, read_chunk/2, read_all_bits/1, read_all/1, write/2, write_bits/2, seek/2, tell/1, fold_chunks/4]).
-export_type([open_mode/0, file_handle/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 open_mode() :: read_only | write_only | append_only.
-opaque file_handle() :: {file_handle, fio@internal@io:raw_handle()}.
-file("src/fio/handle.gleam", 96).
?DOC(
" Open a file and return a `FileHandle`.\n"
"\n"
" Returns `Error(FioError)` if the file cannot be opened (e.g. `Enoent`,\n"
" `Eacces`). Use `close` to release the underlying OS file descriptor\n"
" when done.\n"
).
-spec open(binary(), open_mode()) -> {ok, file_handle()} |
{error, fio@error:fio_error()}.
open(Path, Mode) ->
Mode_str = case Mode of
read_only ->
<<"r"/utf8>>;
write_only ->
<<"w"/utf8>>;
append_only ->
<<"a"/utf8>>
end,
_pipe = fio@internal@io:open_handle(Path, Mode_str),
gleam@result:map(_pipe, fun(Field@0) -> {file_handle, Field@0} end).
-file("src/fio/handle.gleam", 107).
?DOC(" Close a `FileHandle`, releasing the underlying OS file descriptor.\n").
-spec close(file_handle()) -> {ok, nil} | {error, fio@error:fio_error()}.
close(Handle) ->
fio@internal@io:close_handle(erlang:element(2, Handle)).
-file("src/fio/handle.gleam", 129).
?DOC(
" Open a file, run `callback` with the handle, then **always** close it.\n"
"\n"
" This is the recommended way to use file handles. It is equivalent to\n"
" try-finally in other languages: the handle is closed even if `callback`\n"
" returns `Error` or if the Gleam runtime panics.\n"
"\n"
" Designed for use with Gleam's `use` syntax:\n"
"\n"
" ```gleam\n"
" pub fn word_count(path: String) -> Result(Int, error.FioError) {\n"
" use h <- handle.with(path, handle.ReadOnly)\n"
" use text <- result.try(handle.read_all(h))\n"
" Ok(string.split(text, \" \") |> list.length)\n"
" }\n"
" ```\n"
"\n"
" The return type of `callback` determines the return type of `with`.\n"
" The handle is **always** closed before `with` returns.\n"
).
-spec with(
binary(),
open_mode(),
fun((file_handle()) -> {ok, EMK} | {error, fio@error:fio_error()})
) -> {ok, EMK} | {error, fio@error:fio_error()}.
with(Path, Mode, Callback) ->
gleam@result:'try'(
open(Path, Mode),
fun(H) ->
Outcome = Callback(H),
case close(H) of
{ok, _} ->
Outcome;
{error, Close_err} ->
case Outcome of
{error, _} ->
Outcome;
{ok, _} ->
{error, Close_err}
end
end
end
).
-file("src/fio/handle.gleam", 165).
?DOC(
" Read up to `size` bytes from the **current cursor position** of\n"
" the handle.\n"
"\n"
" - `Ok(Some(data))` — a chunk was read; cursor advances by `|data|` bytes.\n"
" - `Ok(None)` — end of file reached; cursor stays at the end.\n"
" - `Error(FioError)` — a read error occurred.\n"
"\n"
" Use `seek` to move the cursor before calling `read_chunk` if you need\n"
" to read from a specific offset.\n"
"\n"
" This is the primitive on which higher-level streaming can be built.\n"
).
-spec read_chunk(file_handle(), integer()) -> {ok,
gleam@option:option(bitstring())} |
{error, fio@error:fio_error()}.
read_chunk(Handle, Size) ->
fio@internal@io:read_chunk(erlang:element(2, Handle), Size).
-file("src/fio/handle.gleam", 182).
-spec do_read_all_bits(file_handle(), bitstring()) -> {ok, bitstring()} |
{error, fio@error:fio_error()}.
do_read_all_bits(Handle, Acc) ->
gleam@result:'try'(
fio@internal@io:read_chunk(erlang:element(2, Handle), 65536),
fun(Chunk) -> case Chunk of
none ->
{ok, Acc};
{some, Data} ->
do_read_all_bits(Handle, gleam@bit_array:append(Acc, Data))
end end
).
-file("src/fio/handle.gleam", 176).
?DOC(
" Read all remaining bytes from the handle into a `BitArray`.\n"
"\n"
" Reads sequentially in 64 KiB chunks until EOF. Suitable for files of\n"
" arbitrary size (only the final result is materialised in memory).\n"
).
-spec read_all_bits(file_handle()) -> {ok, bitstring()} |
{error, fio@error:fio_error()}.
read_all_bits(Handle) ->
do_read_all_bits(Handle, <<>>).
-file("src/fio/handle.gleam", 196).
?DOC(
" Read all remaining content from the handle as a UTF-8 `String`.\n"
"\n"
" Returns `Error(NotUtf8(\"(handle)\"))` if the bytes are not valid UTF-8.\n"
).
-spec read_all(file_handle()) -> {ok, binary()} | {error, fio@error:fio_error()}.
read_all(Handle) ->
gleam@result:'try'(
read_all_bits(Handle),
fun(Bits) -> _pipe = gleam@bit_array:to_string(Bits),
gleam@result:map_error(
_pipe,
fun(_) -> {not_utf8, <<"(handle)"/utf8>>} end
) end
).
-file("src/fio/handle.gleam", 210).
?DOC(
" Write a UTF-8 string to the handle at the **current cursor position**.\n"
"\n"
" The cursor advances by the number of bytes written.\n"
" Use `seek` to write at a specific offset.\n"
).
-spec write(file_handle(), binary()) -> {ok, nil} |
{error, fio@error:fio_error()}.
write(Handle, Content) ->
fio@internal@io:write_handle(erlang:element(2, Handle), Content).
-file("src/fio/handle.gleam", 218).
?DOC(
" Write raw bytes to the handle at the **current cursor position**.\n"
"\n"
" The cursor advances by the number of bytes written.\n"
" Use `seek` to write at a specific offset.\n"
).
-spec write_bits(file_handle(), bitstring()) -> {ok, nil} |
{error, fio@error:fio_error()}.
write_bits(Handle, Content) ->
fio@internal@io:write_handle_bits(erlang:element(2, Handle), Content).
-file("src/fio/handle.gleam", 250).
?DOC(
" Move the file cursor to `position` bytes from the start of the file.\n"
"\n"
" After `seek`, the next `read_chunk`, `read_all_bits`, `write`, or\n"
" `write_bits` call will operate from the new offset.\n"
"\n"
" ```gleam\n"
" // Re-read the first 10 bytes of a file\n"
" let assert Ok(h) = handle.open(path, handle.ReadOnly)\n"
" let assert Ok(_) = handle.seek(h, 0) // already at 0 after open\n"
" let assert Ok(a) = handle.read_chunk(h, 10)\n"
" let assert Ok(_) = handle.seek(h, 0) // back to start\n"
" let assert Ok(b) = handle.read_chunk(h, 10)\n"
" // a == b\n"
" ```\n"
"\n"
" **Append mode note**: in `AppendOnly` mode, `seek` moves the read cursor\n"
" (useful for mixed-use handles) but writes are always forced to end-of-file\n"
" by the OS regardless of cursor position. This is standard POSIX behaviour.\n"
"\n"
" Passing an offset beyond the end of the file is allowed on most\n"
" platforms (a subsequent write will create a sparse region).\n"
).
-spec seek(file_handle(), integer()) -> {ok, nil} |
{error, fio@error:fio_error()}.
seek(Handle, Position) ->
fio@internal@io:seek(erlang:element(2, Handle), Position).
-file("src/fio/handle.gleam", 264).
?DOC(
" Return the current byte offset of the file cursor.\n"
"\n"
" Useful to record a position before reading a block so you can return\n"
" to it later with `seek`.\n"
"\n"
" ```gleam\n"
" let assert Ok(start) = handle.tell(h) // save position\n"
" let assert Ok(_) = handle.read_chunk(h, 64) // advance cursor\n"
" let assert Ok(_) = handle.seek(h, start) // restore position\n"
" ```\n"
).
-spec tell(file_handle()) -> {ok, integer()} | {error, fio@error:fio_error()}.
tell(Handle) ->
fio@internal@io:tell(erlang:element(2, Handle)).
-file("src/fio/handle.gleam", 294).
-spec do_fold_chunks(
file_handle(),
integer(),
ENJ,
fun((ENJ, bitstring()) -> ENJ)
) -> {ok, ENJ} | {error, fio@error:fio_error()}.
do_fold_chunks(Handle, Chunk_size, Acc, F) ->
gleam@result:'try'(
read_chunk(Handle, Chunk_size),
fun(Chunk) -> case Chunk of
none ->
{ok, Acc};
{some, Data} ->
do_fold_chunks(Handle, Chunk_size, F(Acc, Data), F)
end end
).
-file("src/fio/handle.gleam", 285).
?DOC(
" Fold over all remaining chunks of the file, accumulating a result.\n"
"\n"
" Reads `chunk_size` bytes at a time from the **current cursor position**\n"
" until EOF, calling `f(acc, chunk)` for each chunk. Returns `Ok(final_acc)`\n"
" when EOF is reached cleanly, or `Error(FioError)` on the first read failure.\n"
"\n"
" ```gleam\n"
" // Count bytes in a large file without loading it all into memory\n"
" use h <- handle.with(path, handle.ReadOnly)\n"
" handle.fold_chunks(h, 65_536, 0, fn(acc, chunk) {\n"
" acc + bit_array.byte_size(chunk)\n"
" })\n"
" ```\n"
).
-spec fold_chunks(file_handle(), integer(), ENG, fun((ENG, bitstring()) -> ENG)) -> {ok,
ENG} |
{error, fio@error:fio_error()}.
fold_chunks(Handle, Chunk_size, Initial, F) ->
do_fold_chunks(Handle, Chunk_size, Initial, F).