Packages

Complete, safe, ergonomic file operations for all Gleam targets

Current section

Files

Jump to
fio src fio@path.erl
Raw

src/fio@path.erl

-module(fio@path).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/fio/path.gleam").
-export([join/2, split/1, base_name/1, directory_name/1, extension/1, strip_extension/1, is_absolute/1, expand/1, stem/1, with_extension/2, join_all/1, safe_relative/1]).
-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/path.gleam", 6).
?DOC(" Join two path segments.\n").
-spec join(binary(), binary()) -> binary().
join(Left, Right) ->
filepath:join(Left, Right).
-file("src/fio/path.gleam", 11).
?DOC(" Split a path into its segments.\n").
-spec split(binary()) -> list(binary()).
split(Path) ->
filepath:split(Path).
-file("src/fio/path.gleam", 16).
?DOC(" Get the base name (filename) of a path.\n").
-spec base_name(binary()) -> binary().
base_name(Path) ->
filepath:base_name(Path).
-file("src/fio/path.gleam", 21).
?DOC(" Get the directory portion of a path.\n").
-spec directory_name(binary()) -> binary().
directory_name(Path) ->
filepath:directory_name(Path).
-file("src/fio/path.gleam", 26).
?DOC(" Get the file extension (without dot).\n").
-spec extension(binary()) -> {ok, binary()} | {error, nil}.
extension(Path) ->
filepath:extension(Path).
-file("src/fio/path.gleam", 31).
?DOC(" Remove the extension from a path.\n").
-spec strip_extension(binary()) -> binary().
strip_extension(Path) ->
filepath:strip_extension(Path).
-file("src/fio/path.gleam", 36).
?DOC(" Check if a path is absolute.\n").
-spec is_absolute(binary()) -> boolean().
is_absolute(Path) ->
filepath:is_absolute(Path).
-file("src/fio/path.gleam", 42).
?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) ->
filepath:expand(Path).
-file("src/fio/path.gleam", 49).
?DOC(" Get the stem (filename without extension).\n").
-spec stem(binary()) -> binary().
stem(Path) ->
Name = base_name(Path),
case extension(Path) of
{ok, _} ->
strip_extension(Name);
{error, _} ->
Name
end.
-file("src/fio/path.gleam", 58).
?DOC(" Change the extension of a path.\n").
-spec with_extension(binary(), binary()) -> binary().
with_extension(Path, Ext) ->
<<<<(strip_extension(Path))/binary, "."/utf8>>/binary, Ext/binary>>.
-file("src/fio/path.gleam", 73).
-spec join_all_loop(binary(), list(binary())) -> binary().
join_all_loop(Acc, Segments) ->
case Segments of
[] ->
Acc;
[Next | Rest] ->
join_all_loop(join(Acc, Next), Rest)
end.
-file("src/fio/path.gleam", 63).
?DOC(" Join a list of path segments.\n").
-spec join_all(list(binary())) -> binary().
join_all(Segments) ->
case Segments of
[] ->
<<"."/utf8>>;
[First | Rest] ->
join_all_loop(First, Rest)
end.
-file("src/fio/path.gleam", 80).
-spec is_windows_drive(binary()) -> boolean().
is_windows_drive(Path) ->
case string:length(Path) >= 2 of
true ->
First = string:lowercase(gleam@string:slice(Path, 0, 1)),
Second = gleam@string:slice(Path, 1, 1),
(Second =:= <<":"/utf8>>) andalso gleam_stdlib:contains_string(
<<"abcdefghijklmnopqrstuvwxyz"/utf8>>,
First
);
false ->
false
end.
-file("src/fio/path.gleam", 114).
-spec normalize_separators(binary()) -> binary().
normalize_separators(Path) ->
gleam@string:join(gleam@string:split(Path, <<"\\"/utf8>>), <<"/"/utf8>>).
-file("src/fio/path.gleam", 97).
?DOC(
" Validate that a path is a safe relative path (does not escape root).\n"
"\n"
" This is primarily intended for sanitizing user-provided paths. On Windows,\n"
" backslashes are treated as separators and are normalized to forward slashes.\n"
).
-spec safe_relative(binary()) -> {ok, binary()} | {error, nil}.
safe_relative(Path) ->
Normalized = normalize_separators(Path),
case is_absolute(Normalized) orelse is_windows_drive(Normalized) of
true ->
{error, nil};
false ->
case expand(Normalized) of
{ok, Expanded} ->
case is_absolute(Expanded) of
true ->
{error, nil};
false ->
{ok, Expanded}
end;
{error, _} ->
{error, nil}
end
end.