src/automata@fsevent@path.erl

-module(automata@fsevent@path).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/automata/fsevent/path.gleam").
-export([path_to_string/1, path_segments/1, path_is_absolute/1, path_equals/2, path_starts_with/2, normalize/1]).
-export_type([normalized_path/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.

-opaque normalized_path() :: {normalized_path,
        binary(),
        list(binary()),
        boolean()}.

-file("src/automata/fsevent/path.gleam", 43).
?DOC(" The canonical string rendering of a normalised path.\n").
-spec path_to_string(normalized_path()) -> binary().
path_to_string(Path) ->
    erlang:element(2, Path).

-file("src/automata/fsevent/path.gleam", 48).
?DOC(" The path's segments in source order (the empty root has no segments).\n").
-spec path_segments(normalized_path()) -> list(binary()).
path_segments(Path) ->
    erlang:element(3, Path).

-file("src/automata/fsevent/path.gleam", 56).
?DOC(
    " `True` when the path is rooted: it either begins with a leading\n"
    " separator after normalisation (`/foo`, `\\\\srv\\share` → `//srv/share`)\n"
    " or its first segment is a Windows drive letter (`C:\\foo` → first\n"
    " segment `C:`). Relative paths return `False`.\n"
).
-spec path_is_absolute(normalized_path()) -> boolean().
path_is_absolute(Path) ->
    erlang:element(4, Path).

-file("src/automata/fsevent/path.gleam", 61).
?DOC(" Structural equality on the canonicalised representation.\n").
-spec path_equals(normalized_path(), normalized_path()) -> boolean().
path_equals(Left, Right) ->
    erlang:element(2, Left) =:= erlang:element(2, Right).

-file("src/automata/fsevent/path.gleam", 81).
-spec segments_start_with(list(binary()), list(binary())) -> boolean().
segments_start_with(Path, Prefix) ->
    case Prefix of
        [] ->
            true;

        [Head_prefix | Rest_prefix] ->
            case Path of
                [] ->
                    false;

                [Head_path | Rest_path] ->
                    case Head_path =:= Head_prefix of
                        false ->
                            false;

                        true ->
                            segments_start_with(Rest_path, Rest_prefix)
                    end
            end
    end.

-file("src/automata/fsevent/path.gleam", 71).
?DOC(
    " `True` when `path` is `prefix` itself or sits beneath `prefix` as a\n"
    " proper ancestor relation (segment-wise; `/var/log` does not start\n"
    " with `/var/lo`).\n"
).
-spec path_starts_with(normalized_path(), normalized_path()) -> boolean().
path_starts_with(Path, Prefix) ->
    case erlang:element(4, Prefix) =:= erlang:element(4, Path) of
        false ->
            false;

        true ->
            segments_start_with(
                erlang:element(3, Path),
                erlang:element(3, Prefix)
            )
    end.

-file("src/automata/fsevent/path.gleam", 96).
-spec validate_segments(binary(), list(binary())) -> {ok, nil} |
    {error, automata@fsevent@ast:fsevent_error()}.
validate_segments(Original, Segments) ->
    case Segments of
        [] ->
            {ok, nil};

        [Head | Rest] ->
            case Head of
                <<"."/utf8>> ->
                    {error, {path_contains_dot_segment, Original, <<"."/utf8>>}};

                <<".."/utf8>> ->
                    {error,
                        {path_contains_dot_segment, Original, <<".."/utf8>>}};

                _ ->
                    validate_segments(Original, Rest)
            end
    end.

-file("src/automata/fsevent/path.gleam", 118).
-spec is_drive_letter(binary()) -> boolean().
is_drive_letter(Segment) ->
    case Segment of
        <<"A:"/utf8>> ->
            true;

        <<"B:"/utf8>> ->
            true;

        <<"C:"/utf8>> ->
            true;

        <<"D:"/utf8>> ->
            true;

        <<"E:"/utf8>> ->
            true;

        <<"F:"/utf8>> ->
            true;

        <<"G:"/utf8>> ->
            true;

        <<"H:"/utf8>> ->
            true;

        <<"I:"/utf8>> ->
            true;

        <<"J:"/utf8>> ->
            true;

        <<"K:"/utf8>> ->
            true;

        <<"L:"/utf8>> ->
            true;

        <<"M:"/utf8>> ->
            true;

        <<"N:"/utf8>> ->
            true;

        <<"O:"/utf8>> ->
            true;

        <<"P:"/utf8>> ->
            true;

        <<"Q:"/utf8>> ->
            true;

        <<"R:"/utf8>> ->
            true;

        <<"S:"/utf8>> ->
            true;

        <<"T:"/utf8>> ->
            true;

        <<"U:"/utf8>> ->
            true;

        <<"V:"/utf8>> ->
            true;

        <<"W:"/utf8>> ->
            true;

        <<"X:"/utf8>> ->
            true;

        <<"Y:"/utf8>> ->
            true;

        <<"Z:"/utf8>> ->
            true;

        <<"a:"/utf8>> ->
            true;

        <<"b:"/utf8>> ->
            true;

        <<"c:"/utf8>> ->
            true;

        <<"d:"/utf8>> ->
            true;

        <<"e:"/utf8>> ->
            true;

        <<"f:"/utf8>> ->
            true;

        <<"g:"/utf8>> ->
            true;

        <<"h:"/utf8>> ->
            true;

        <<"i:"/utf8>> ->
            true;

        <<"j:"/utf8>> ->
            true;

        <<"k:"/utf8>> ->
            true;

        <<"l:"/utf8>> ->
            true;

        <<"m:"/utf8>> ->
            true;

        <<"n:"/utf8>> ->
            true;

        <<"o:"/utf8>> ->
            true;

        <<"p:"/utf8>> ->
            true;

        <<"q:"/utf8>> ->
            true;

        <<"r:"/utf8>> ->
            true;

        <<"s:"/utf8>> ->
            true;

        <<"t:"/utf8>> ->
            true;

        <<"u:"/utf8>> ->
            true;

        <<"v:"/utf8>> ->
            true;

        <<"w:"/utf8>> ->
            true;

        <<"x:"/utf8>> ->
            true;

        <<"y:"/utf8>> ->
            true;

        <<"z:"/utf8>> ->
            true;

        _ ->
            false
    end.

-file("src/automata/fsevent/path.gleam", 111).
-spec starts_with_drive_letter(list(binary())) -> boolean().
starts_with_drive_letter(Segments) ->
    case Segments of
        [First | _] ->
            is_drive_letter(First);

        [] ->
            false
    end.

-file("src/automata/fsevent/path.gleam", 176).
-spec build(binary(), list(binary())) -> normalized_path().
build(Unified, Segments) ->
    Joined = gleam@string:join(Segments, <<"/"/utf8>>),
    Slash_prefix = gleam_stdlib:string_starts_with(Unified, <<"/"/utf8>>),
    Absolute = Slash_prefix orelse starts_with_drive_letter(Segments),
    Value = case {Slash_prefix, Joined} of
        {true, <<""/utf8>>} ->
            <<"/"/utf8>>;

        {true, _} ->
            <<"/"/utf8, Joined/binary>>;

        {false, _} ->
            Joined
    end,
    {normalized_path, Value, Segments, Absolute}.

-file("src/automata/fsevent/path.gleam", 23).
?DOC(
    " Canonicalise a string into a `NormalizedPath`.\n"
    "\n"
    " The library does not call `os.path.realpath`-style resolution: it\n"
    " works at the string level so it stays pure and produces the same\n"
    " output on every platform regardless of the underlying filesystem.\n"
).
-spec normalize(binary()) -> {ok, normalized_path()} |
    {error, automata@fsevent@ast:fsevent_error()}.
normalize(Path) ->
    case Path of
        <<""/utf8>> ->
            {error, empty_path};

        _ ->
            case gleam_stdlib:contains_string(Path, <<"\x{0000}"/utf8>>) of
                true ->
                    {error, {path_contains_null_byte, Path}};

                false ->
                    Unified = gleam@string:replace(
                        Path,
                        <<"\\"/utf8>>,
                        <<"/"/utf8>>
                    ),
                    Raw_segments = gleam@string:split(Unified, <<"/"/utf8>>),
                    Segments = gleam@list:filter(
                        Raw_segments,
                        fun(S) -> S /= <<""/utf8>> end
                    ),
                    case validate_segments(Path, Segments) of
                        {error, Error} ->
                            {error, Error};

                        {ok, _} ->
                            {ok, build(Unified, Segments)}
                    end
            end
    end.