-module(fio). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/fio.gleam"). -export([read/1, read_bits/1, write/2, write_bits/2, append/2, append_bits/2, write_atomic/2, write_bits_atomic/2, delete/1, delete_directory/1, delete_all/1, exists/1, file_info/1, link_info/1, is_directory/1, is_file/1, is_symlink/1, copy/2, rename/2, create_symlink/2, create_hard_link/2, read_link/1, set_permissions/2, set_permissions_octal/2, create_directory/1, create_directory_all/1, list/1, current_directory/0, tmp_dir/0, touch/1, with_temp_file/1, with_temp_directory/1, list_recursive/1, copy_directory/2, ensure_file/1, copy_if_newer/2, read_fold/4, checksum/2, verify_checksum/3, join/2, split/1, base_name/1, directory_name/1, extension/1, strip_extension/1, stem/1, with_extension/2, join_all/1, is_absolute/1, expand/1, safe_relative/1, with_opened/3, with_writer/2, write_new/2, write_if_changed/2, read_lines/1, write_lines/2, stream_bytes/1, stream/1, explain/1, atomic/2]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -file("src/fio.gleam", 15). ?DOC(" Read a UTF-8 text file. Returns `NotUtf8(path)` on invalid UTF-8.\n"). -spec read(binary()) -> {ok, binary()} | {error, fio@error:fio_error()}. read(Path) -> fio@internal@io:read(Path). -file("src/fio.gleam", 20). ?DOC(" Read a file as raw bytes (`BitArray`).\n"). -spec read_bits(binary()) -> {ok, bitstring()} | {error, fio@error:fio_error()}. read_bits(Path) -> fio@internal@io:read_bits(Path). -file("src/fio.gleam", 27). ?DOC(" Write UTF-8 text to a file (creates/overwrites).\n"). -spec write(binary(), binary()) -> {ok, nil} | {error, fio@error:fio_error()}. write(Path, Content) -> fio@internal@io:write(Path, Content). -file("src/fio.gleam", 32). ?DOC(" Write raw bytes (`BitArray`) to a file (creates/overwrites).\n"). -spec write_bits(binary(), bitstring()) -> {ok, nil} | {error, fio@error:fio_error()}. write_bits(Path, Content) -> fio@internal@io:write_bits(Path, Content). -file("src/fio.gleam", 37). ?DOC(" Append UTF-8 text to a file (creates if missing).\n"). -spec append(binary(), binary()) -> {ok, nil} | {error, fio@error:fio_error()}. append(Path, Content) -> fio@internal@io:append(Path, Content). -file("src/fio.gleam", 42). ?DOC(" Append raw bytes (`BitArray`) to a file.\n"). -spec append_bits(binary(), bitstring()) -> {ok, nil} | {error, fio@error:fio_error()}. append_bits(Path, Content) -> fio@internal@io:append_bits(Path, Content). -file("src/fio.gleam", 49). ?DOC( " Write UTF-8 text atomically: writes to a sibling temp file, then renames\n" " it into place with a single `rename(2)` syscall.\n" " Readers never observe partial content. Returns `AtomicFailed` on error.\n" ). -spec write_atomic(binary(), binary()) -> {ok, nil} | {error, fio@error:fio_error()}. write_atomic(Path, Content) -> fio@internal@io:write_atomic(Path, Content). -file("src/fio.gleam", 55). ?DOC( " Write raw bytes (`BitArray`) atomically.\n" " Same atomic guarantee as `write_atomic`.\n" ). -spec write_bits_atomic(binary(), bitstring()) -> {ok, nil} | {error, fio@error:fio_error()}. write_bits_atomic(Path, Content) -> fio@internal@io:write_bits_atomic(Path, Content). -file("src/fio.gleam", 65). ?DOC(" Delete a file (not a directory).\n"). -spec delete(binary()) -> {ok, nil} | {error, fio@error:fio_error()}. delete(Path) -> fio@internal@io:delete_file(Path). -file("src/fio.gleam", 70). ?DOC(" Delete an empty directory.\n"). -spec delete_directory(binary()) -> {ok, nil} | {error, fio@error:fio_error()}. delete_directory(Path) -> fio@internal@io:delete_directory(Path). -file("src/fio.gleam", 75). ?DOC(" Delete a path recursively; idempotent (succeeds if missing).\n"). -spec delete_all(binary()) -> {ok, nil} | {error, fio@error:fio_error()}. delete_all(Path) -> fio@internal@io:delete_recursive(Path). -file("src/fio.gleam", 82). ?DOC(" Check if a path exists (file, directory, or symlink).\n"). -spec exists(binary()) -> boolean(). exists(Path) -> fio@internal@io:exists(Path). -file("src/fio.gleam", 87). ?DOC(" Get file metadata (follows symlinks).\n"). -spec file_info(binary()) -> {ok, fio@types:file_info()} | {error, fio@error:fio_error()}. file_info(Path) -> fio@internal@io:file_info(Path). -file("src/fio.gleam", 92). ?DOC(" Get file metadata without following symlinks.\n"). -spec link_info(binary()) -> {ok, fio@types:file_info()} | {error, fio@error:fio_error()}. link_info(Path) -> fio@internal@io:link_info(Path). -file("src/fio.gleam", 97). ?DOC(" Check if a path is a directory (follows symlinks).\n"). -spec is_directory(binary()) -> {ok, boolean()} | {error, fio@error:fio_error()}. is_directory(Path) -> fio@internal@io:is_directory(Path). -file("src/fio.gleam", 102). ?DOC(" Check if a path is a regular file (follows symlinks).\n"). -spec is_file(binary()) -> {ok, boolean()} | {error, fio@error:fio_error()}. is_file(Path) -> fio@internal@io:is_file(Path). -file("src/fio.gleam", 107). ?DOC(" Check if a path is a symbolic link (does not follow symlinks).\n"). -spec is_symlink(binary()) -> {ok, boolean()} | {error, fio@error:fio_error()}. is_symlink(Path) -> fio@internal@io:is_symlink(Path). -file("src/fio.gleam", 114). ?DOC(" Copy a file from source to destination.\n"). -spec copy(binary(), binary()) -> {ok, nil} | {error, fio@error:fio_error()}. copy(Src, Dest) -> fio@internal@io:copy_file(Src, Dest). -file("src/fio.gleam", 119). ?DOC(" Rename or move a file or directory.\n"). -spec rename(binary(), binary()) -> {ok, nil} | {error, fio@error:fio_error()}. rename(Src, Dest) -> fio@internal@io:rename(Src, Dest). -file("src/fio.gleam", 126). ?DOC(" Create a symbolic link.\n"). -spec create_symlink(binary(), binary()) -> {ok, nil} | {error, fio@error:fio_error()}. create_symlink(Target, Link) -> fio@internal@io:create_symlink(Target, Link). -file("src/fio.gleam", 134). ?DOC(" Create a hard link to an existing file.\n"). -spec create_hard_link(binary(), binary()) -> {ok, nil} | {error, fio@error:fio_error()}. create_hard_link(Target, Link) -> fio@internal@io:create_hard_link(Target, Link). -file("src/fio.gleam", 142). ?DOC(" Read the target path of a symbolic link.\n"). -spec read_link(binary()) -> {ok, binary()} | {error, fio@error:fio_error()}. read_link(Path) -> fio@internal@io:read_link(Path). -file("src/fio.gleam", 149). ?DOC(" Set file permissions using the `FilePermissions` type.\n"). -spec set_permissions(binary(), fio@types:file_permissions()) -> {ok, nil} | {error, fio@error:fio_error()}. set_permissions(Path, Permissions) -> fio@internal@io:set_permissions(Path, Permissions). -file("src/fio.gleam", 157). ?DOC(" Set file permissions with an octal integer.\n"). -spec set_permissions_octal(binary(), integer()) -> {ok, nil} | {error, fio@error:fio_error()}. set_permissions_octal(Path, Mode) -> fio@internal@io:set_permissions_octal(Path, Mode). -file("src/fio.gleam", 164). ?DOC(" Create a directory. Parent directory must exist.\n"). -spec create_directory(binary()) -> {ok, nil} | {error, fio@error:fio_error()}. create_directory(Path) -> fio@internal@io:create_directory(Path). -file("src/fio.gleam", 169). ?DOC(" Create a directory and all parent directories.\n"). -spec create_directory_all(binary()) -> {ok, nil} | {error, fio@error:fio_error()}. create_directory_all(Path) -> fio@internal@io:create_directory_all(Path). -file("src/fio.gleam", 174). ?DOC(" List the contents of a directory (names only).\n"). -spec list(binary()) -> {ok, list(binary())} | {error, fio@error:fio_error()}. list(Path) -> fio@internal@io:list_directory(Path). -file("src/fio.gleam", 181). ?DOC(" Get the current working directory.\n"). -spec current_directory() -> {ok, binary()} | {error, fio@error:fio_error()}. current_directory() -> fio@internal@io:current_directory(). -file("src/fio.gleam", 186). ?DOC(" Get the system temporary directory path.\n"). -spec tmp_dir() -> binary(). tmp_dir() -> fio@internal@io:tmp_dir(). -file("src/fio.gleam", 191). ?DOC(" Touch a file: create or update modification time.\n"). -spec touch(binary()) -> {ok, nil} | {error, fio@error:fio_error()}. touch(Path) -> fio@internal@io:touch(Path). -file("src/fio.gleam", 199). ?DOC( " Run a callback with a path to a temporary file that is automatically\n" " deleted when the callback returns (even if it returns an Error).\n" ). -spec with_temp_file( fun((binary()) -> {ok, EWV} | {error, fio@error:fio_error()}) ) -> {ok, EWV} | {error, fio@error:fio_error()}. with_temp_file(Callback) -> Path = fio@path:join( fio@internal@io:tmp_dir(), fio_ffi:unique_name(<<"fio_tmp_file_"/utf8>>) ), Result = Callback(Path), _ = fio@internal@io:delete_file(Path), Result. -file("src/fio.gleam", 212). ?DOC( " Run a callback with a path to a temporary directory that is automatically\n" " deleted (recursively) when the callback returns.\n" ). -spec with_temp_directory( fun((binary()) -> {ok, EXA} | {error, fio@error:fio_error()}) ) -> {ok, EXA} | {error, fio@error:fio_error()}. with_temp_directory(Callback) -> Path = fio@path:join( fio@internal@io:tmp_dir(), fio_ffi:unique_name(<<"fio_tmp_dir_"/utf8>>) ), Result = begin _pipe = fio@internal@io:create_directory(Path), gleam@result:'try'(_pipe, fun(_) -> Callback(Path) end) end, _ = fio@internal@io:delete_recursive(Path), Result. -file("src/fio.gleam", 228). ?DOC(" Recursively list files and directories (paths relative to `path`).\n"). -spec list_recursive(binary()) -> {ok, list(binary())} | {error, fio@error:fio_error()}. list_recursive(Path) -> fio@recursive:list_recursive(Path). -file("src/fio.gleam", 233). ?DOC(" Recursively copy a directory and its contents.\n"). -spec copy_directory(binary(), binary()) -> {ok, nil} | {error, fio@error:fio_error()}. copy_directory(Src, Dest) -> fio@recursive:copy_directory(Src, Dest). -file("src/fio.gleam", 241). ?DOC( " Create a file if it does not already exist.\n" " If the file already exists this is a no-op and returns `Ok(Nil)`.\n" ). -spec ensure_file(binary()) -> {ok, nil} | {error, fio@error:fio_error()}. ensure_file(Path) -> case fio@internal@io:exists(Path) of true -> {ok, nil}; false -> fio@internal@io:write(Path, <<""/utf8>>) end. -file("src/fio.gleam", 252). ?DOC( " Copy `src` to `dest` only when `src` is newer than `dest`.\n" "\n" " If `dest` does not exist the copy always happens.\n" " Returns `Ok(True)` when a copy was performed, `Ok(False)` when skipped.\n" ). -spec copy_if_newer(binary(), binary()) -> {ok, boolean()} | {error, fio@error:fio_error()}. copy_if_newer(Src, Dest) -> gleam@result:'try'( fio@internal@io:file_info(Src), fun(Src_info) -> case fio@internal@io:file_info(Dest) of {error, enoent} -> gleam@result:'try'( fio@internal@io:copy_file(Src, Dest), fun(_) -> {ok, true} end ); {error, E} -> {error, E}; {ok, Dest_info} -> case erlang:element(10, Src_info) > erlang:element( 10, Dest_info ) of false -> {ok, false}; true -> gleam@result:'try'( fio@internal@io:copy_file(Src, Dest), fun(_) -> {ok, true} end ) end end end ). -file("src/fio.gleam", 284). ?DOC( " Read a file in chunks, folding each chunk into an accumulator.\n" "\n" " Opens the file, reads it in `chunk_size`-byte pieces, and calls `f` on each\n" " chunk until EOF. The file handle is always closed before returning.\n" "\n" " ```gleam\n" " // Count bytes without loading the whole file into memory\n" " fio.read_fold(\"big.bin\", 65_536, 0, fn(acc, chunk) {\n" " acc + bit_array.byte_size(chunk)\n" " })\n" " ```\n" ). -spec read_fold(binary(), integer(), EXO, fun((EXO, bitstring()) -> EXO)) -> {ok, EXO} | {error, fio@error:fio_error()}. read_fold(Path, Chunk_size, Initial, F) -> fio@handle:with( Path, read_only, fun(H) -> fio@handle:fold_chunks(H, Chunk_size, Initial, F) end ). -file("src/fio.gleam", 298). ?DOC( " Compute a file checksum using the specified algorithm.\n" " Returns a hex-encoded string.\n" ). -spec checksum(binary(), fio@types:hash_algorithm()) -> {ok, binary()} | {error, fio@error:fio_error()}. checksum(Path, Algorithm) -> Algo_str = case Algorithm of sha256 -> <<"sha256"/utf8>>; sha512 -> <<"sha512"/utf8>>; md5 -> <<"md5"/utf8>> end, fio@internal@io:checksum(Path, Algo_str). -file("src/fio.gleam", 311). ?DOC(" Verify that a file's checksum matches the expected hex-encoded hash.\n"). -spec verify_checksum(binary(), binary(), fio@types:hash_algorithm()) -> {ok, boolean()} | {error, fio@error:fio_error()}. verify_checksum(Path, Expected, Algorithm) -> _pipe = checksum(Path, Algorithm), gleam@result:map(_pipe, fun(Actual) -> Actual =:= Expected end). -file("src/fio.gleam", 323). ?DOC(" Join two path segments.\n"). -spec join(binary(), binary()) -> binary(). join(Left, Right) -> fio@path:join(Left, Right). -file("src/fio.gleam", 328). ?DOC(" Split a path into its segments.\n"). -spec split(binary()) -> list(binary()). split(Path_str) -> fio@path:split(Path_str). -file("src/fio.gleam", 333). ?DOC(" Get the base name (filename) of a path.\n"). -spec base_name(binary()) -> binary(). base_name(Path_str) -> fio@path:base_name(Path_str). -file("src/fio.gleam", 338). ?DOC(" Get the directory portion of a path.\n"). -spec directory_name(binary()) -> binary(). directory_name(Path_str) -> fio@path:directory_name(Path_str). -file("src/fio.gleam", 343). ?DOC(" Get the file extension (without dot).\n"). -spec extension(binary()) -> {ok, binary()} | {error, nil}. extension(Path_str) -> fio@path:extension(Path_str). -file("src/fio.gleam", 348). ?DOC(" Remove the extension from a path.\n"). -spec strip_extension(binary()) -> binary(). strip_extension(Path_str) -> fio@path:strip_extension(Path_str). -file("src/fio.gleam", 353). ?DOC(" Get the stem (filename without extension).\n"). -spec stem(binary()) -> binary(). stem(Path_str) -> fio@path:stem(Path_str). -file("src/fio.gleam", 358). ?DOC(" Change the extension of a path.\n"). -spec with_extension(binary(), binary()) -> binary(). with_extension(Path_str, Ext) -> fio@path:with_extension(Path_str, Ext). -file("src/fio.gleam", 363). ?DOC(" Join a list of path segments.\n"). -spec join_all(list(binary())) -> binary(). join_all(Segments) -> fio@path:join_all(Segments). -file("src/fio.gleam", 368). ?DOC(" Check if a path is absolute.\n"). -spec is_absolute(binary()) -> boolean(). is_absolute(Path_str) -> fio@path:is_absolute(Path_str). -file("src/fio.gleam", 374). ?DOC( " Expand/normalize a path, resolving `.` and `..` segments.\n" " Returns `Error(Nil)` if `..` would go above the root.\n" ). -spec expand(binary()) -> {ok, binary()} | {error, nil}. expand(Path_str) -> fio@path:expand(Path_str). -file("src/fio.gleam", 380). ?DOC( " Validate that a path is a safe relative path (does not escape root).\n" " On Windows it also normalizes backslashes into `/`.\n" ). -spec safe_relative(binary()) -> {ok, binary()} | {error, nil}. safe_relative(Path_str) -> fio@path:safe_relative(Path_str). -file("src/fio.gleam", 390). ?DOC( " Evaluates a callback with an opened file handle and guarantees the handle\n" " is closed at the end, even in case of errors.\n" ). -spec with_opened( binary(), fio@handle:open_mode(), fun((fio@handle:file_handle()) -> {ok, EYD} | {error, fio@error:fio_error()}) ) -> {ok, EYD} | {error, fio@error:fio_error()}. with_opened(Path, Mode, Callback) -> fio@handle:with(Path, Mode, Callback). -file("src/fio.gleam", 400). ?DOC( " Evaluates a callback with a file handle opened for writing and guarantees\n" " the handle is closed at the end.\n" ). -spec with_writer( binary(), fun((fio@handle:file_handle()) -> {ok, EYI} | {error, fio@error:fio_error()}) ) -> {ok, EYI} | {error, fio@error:fio_error()}. with_writer(Path, Callback) -> fio@handle:with(Path, write_only, Callback). -file("src/fio.gleam", 413). ?DOC( " Writes content to a file only if it doesn't exist yet. Returns an `Eexist`\n" " error if it does.\n" ). -spec write_new(binary(), binary()) -> {ok, nil} | {error, fio@error:fio_error()}. write_new(Path, Content) -> case exists(Path) of true -> {error, eexist}; false -> write(Path, Content) end. -file("src/fio.gleam", 422). ?DOC( " Writes content to a file. If the file already has identical content, it\n" " skips rewriting and returns `False`. If it overwrote or created, returns `True`.\n" ). -spec write_if_changed(binary(), binary()) -> {ok, boolean()} | {error, fio@error:fio_error()}. write_if_changed(Path, Content) -> case read(Path) of {ok, Existing} when Existing =:= Content -> {ok, false}; _ -> gleam@result:'try'(write(Path, Content), fun(_) -> {ok, true} end) end. -file("src/fio.gleam", 438). ?DOC( " Reads a file and splits it into lines.\n" " Both `\\n` (Unix) and `\\r\\n` (Windows) line endings are normalised.\n" ). -spec read_lines(binary()) -> {ok, list(binary())} | {error, fio@error:fio_error()}. read_lines(Path) -> gleam@result:'try'( read(Path), fun(Content) -> {ok, gleam@string:split( gleam@string:replace( Content, <<"\r\n"/utf8>>, <<"\n"/utf8>> ), <<"\n"/utf8>> )} end ). -file("src/fio.gleam", 444). ?DOC(" Joins lines with newlines and writes to a file.\n"). -spec write_lines(binary(), list(binary())) -> {ok, nil} | {error, fio@error:fio_error()}. write_lines(Path, Lines) -> Content = gleam@string:join(Lines, <<"\n"/utf8>>), write(Path, Content). -file("src/fio.gleam", 455). ?DOC( " Reads a file in chunks and returns all chunks as a list of `BitArray`.\n" " Uses a 64 KiB chunk size. Returns `Error` if the file cannot be opened.\n" ). -spec stream_bytes(binary()) -> {ok, list(bitstring())} | {error, fio@error:fio_error()}. stream_bytes(Path) -> _pipe = read_fold(Path, 65536, [], fun(Acc, Chunk) -> [Chunk | Acc] end), gleam@result:map(_pipe, fun lists:reverse/1). -file("src/fio.gleam", 462). ?DOC( " Reads a file in chunks and returns all chunks as a list of `String`.\n" " Returns `Error(NotUtf8)` if any chunk is not valid UTF-8.\n" ). -spec stream(binary()) -> {ok, list(binary())} | {error, fio@error:fio_error()}. stream(Path) -> gleam@result:'try'( stream_bytes(Path), fun(Chunks) -> _pipe = gleam@list:map( Chunks, fun(Bits) -> case gleam@bit_array:to_string(Bits) of {ok, S} -> {ok, S}; {error, _} -> {error, {not_utf8, Path}} end end ), gleam@result:all(_pipe) end ). -file("src/fio.gleam", 478). ?DOC(" Explains a FioError in a CLI-friendly format.\n"). -spec explain(fio@error:fio_error()) -> binary(). explain(Err) -> fio@error:describe(Err). -file("src/fio.gleam", 490). ?DOC( " Executes a callback providing a temporary file path to write to.\n" " If the callback succeeds, the temporary file is atomically renamed to `path`.\n" " The temporary file is placed in the same directory as `path` using the\n" " same `.__fio_tmp_*` prefix as `write_atomic`.\n" ). -spec atomic( binary(), fun((binary()) -> {ok, EZD} | {error, fio@error:fio_error()}) ) -> {ok, EZD} | {error, fio@error:fio_error()}. atomic(Path, Callback) -> Dir = fio@path:directory_name(Path), Tmp = fio@path:join( Dir, <<".__fio_tmp_"/utf8, (fio_ffi:unique_name(<<"fio_atomic_"/utf8>>))/binary>> ), Res = Callback(Tmp), case Res of {ok, Val} -> case rename(Tmp, Path) of {ok, _} -> {ok, Val}; {error, E} -> _ = delete(Tmp), {error, E} end; {error, E@1} -> _ = delete(Tmp), {error, E@1} end.