Current section
Files
Jump to
Current section
Files
src/fio.erl
-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, ETI} | {error, fio@error:fio_error()})
) -> {ok, ETI} | {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, ETN} | {error, fio@error:fio_error()})
) -> {ok, ETN} | {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(), EUB, fun((EUB, bitstring()) -> EUB)) -> {ok,
EUB} |
{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, EUQ} | {error, fio@error:fio_error()})
) -> {ok, EUQ} | {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, EUV} | {error, fio@error:fio_error()})
) -> {ok, EUV} | {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", 437).
?DOC(" Reads a file and splits it into lines.\n").
-spec read_lines(binary()) -> {ok, list(binary())} |
{error, fio@error:fio_error()}.
read_lines(Path) ->
gleam@result:'try'(
read(Path),
fun(Content) ->
Lines = gleam@string:split(Content, <<"\n"/utf8>>),
{ok, Lines}
end
).
-file("src/fio.gleam", 445).
?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", 456).
?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", 463).
?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", 479).
?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", 489).
?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"
).
-spec atomic(
binary(),
fun((binary()) -> {ok, EVQ} | {error, fio@error:fio_error()})
) -> {ok, EVQ} | {error, fio@error:fio_error()}.
atomic(Path, Callback) ->
Tmp = <<<<Path/binary, ".tmp."/utf8>>/binary,
(fio_ffi:unique_name(<<"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.