Packages

Complete, safe, ergonomic file operations for all Gleam targets

Current section

Files

Jump to
fio src fio@recursive.erl
Raw

src/fio@recursive.erl

-module(fio@recursive).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/fio/recursive.gleam").
-export([list_recursive/1, copy_directory/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/recursive.gleam", 13).
-spec inode_key(fio@types:file_info(), binary()) -> binary().
inode_key(Info, Fallback_path) ->
case erlang:element(5, Info) of
0 ->
Fallback_path;
_ ->
<<<<<<"dev:"/utf8,
(erlang:integer_to_binary(erlang:element(8, Info)))/binary>>/binary,
";ino:"/utf8>>/binary,
(erlang:integer_to_binary(erlang:element(5, Info)))/binary>>
end.
-file("src/fio/recursive.gleam", 45).
-spec do_list_recursive(
binary(),
binary(),
gleam@set:set(binary()),
list(binary())
) -> {ok, list(binary())} | {error, fio@error:fio_error()}.
do_list_recursive(Root, Current_rel, Visited, Acc) ->
Current_dir = case Current_rel of
<<""/utf8>> ->
Root;
Rel ->
fio@path:join(Root, Rel)
end,
gleam@result:'try'(
fio@internal@io:list_directory(Current_dir),
fun(Items) ->
gleam@list:try_fold(
Items,
Acc,
fun(Inner_acc, Item) ->
Item_rel = case Current_rel of
<<""/utf8>> ->
Item;
Rel@1 ->
fio@path:join(Rel@1, Item)
end,
Full_path = fio@path:join(Root, Item_rel),
gleam@result:'try'(
fio@internal@io:is_directory(Full_path),
fun(Is_dir) -> case Is_dir of
false ->
{ok, [Item_rel | Inner_acc]};
true ->
gleam@result:'try'(
fio@internal@io:file_info(Full_path),
fun(Info) ->
Key = inode_key(Info, Full_path),
case gleam@set:contains(
Visited,
Key
) of
true ->
{ok, [Item_rel | Inner_acc]};
false ->
do_list_recursive(
Root,
Item_rel,
gleam@set:insert(
Visited,
Key
),
[Item_rel | Inner_acc]
)
end
end
)
end end
)
end
)
end
).
-file("src/fio/recursive.gleam", 29).
?DOC(
" Recursively list files and directories (paths relative to `path`).\n"
"\n"
" Uses a flat string accumulator for O(n) traversal.\n"
"\n"
" **Symlink loop safety**: before descending into any directory, its real\n"
" `(dev, inode)` pair (obtained via `stat`, which follows symlinks) is\n"
" checked against the `visited` set. If already seen, the entry is listed\n"
" but not descended into, breaking any A->B->A or deeper circular chains.\n"
).
-spec list_recursive(binary()) -> {ok, list(binary())} |
{error, fio@error:fio_error()}.
list_recursive(Path) ->
gleam@result:'try'(
fio@internal@io:is_directory(Path),
fun(Is_dir) -> case Is_dir of
true ->
gleam@result:'try'(
fio@internal@io:file_info(Path),
fun(Root_info) ->
Visited = gleam@set:from_list(
[inode_key(Root_info, Path)]
),
gleam@result:'try'(
do_list_recursive(
Path,
<<""/utf8>>,
Visited,
[]
),
fun(Acc) -> {ok, lists:reverse(Acc)} end
)
end
);
false ->
{error, enotdir}
end end
).
-file("src/fio/recursive.gleam", 102).
-spec do_copy_directory(binary(), binary(), gleam@set:set(binary())) -> {ok,
nil} |
{error, fio@error:fio_error()}.
do_copy_directory(Src, Dest, Visited) ->
gleam@result:'try'(
fio@internal@io:create_directory_all(Dest),
fun(_) ->
gleam@result:'try'(
fio@internal@io:list_directory(Src),
fun(Items) ->
gleam@list:try_each(
Items,
fun(Item) ->
Src_path = fio@path:join(Src, Item),
Dest_path = fio@path:join(Dest, Item),
gleam@result:'try'(
fio@internal@io:is_directory(Src_path),
fun(Is_dir_item) -> case Is_dir_item of
false ->
fio@internal@io:copy_file(
Src_path,
Dest_path
);
true ->
gleam@result:'try'(
fio@internal@io:file_info(
Src_path
),
fun(Info) ->
Key = inode_key(
Info,
Src_path
),
case gleam@set:contains(
Visited,
Key
) of
true ->
{ok, nil};
false ->
do_copy_directory(
Src_path,
Dest_path,
gleam@set:insert(
Visited,
Key
)
)
end
end
)
end end
)
end
)
end
)
end
).
-file("src/fio/recursive.gleam", 90).
?DOC(
" Recursively copy `src` directory into `dest` (creates parents).\n"
"\n"
" **Symlink loop safety**: directory symlinks whose resolved inode has\n"
" already been visited are skipped silently rather than followed forever.\n"
).
-spec copy_directory(binary(), binary()) -> {ok, nil} |
{error, fio@error:fio_error()}.
copy_directory(Src, Dest) ->
gleam@result:'try'(
fio@internal@io:is_directory(Src),
fun(Is_dir) -> case Is_dir of
false ->
{error, enotdir};
true ->
gleam@result:'try'(
fio@internal@io:file_info(Src),
fun(Root_info) ->
Visited = gleam@set:from_list(
[inode_key(Root_info, Src)]
),
do_copy_directory(Src, Dest, Visited)
end
)
end end
).