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