Skip to main content

src/proute@discover.erl

-module(proute@discover).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/proute/discover.gleam").
-export([discover_mount/1, describe_error/1, page_module_path/1]).
-export_type([mount_routes/0, page_route/0, route_kind/0, route_segment/0, route_param/0, discover_error/0]).

-type mount_routes() :: {mount_routes,
        proute@config:mount(),
        list(page_route())}.

-type page_route() :: {page_route,
        route_kind(),
        binary(),
        binary(),
        list(route_segment()),
        list(route_param()),
        binary(),
        binary()}.

-type route_kind() :: home | not_found | static | dynamic.

-type route_segment() :: {static_segment, binary()} |
    {dynamic_segment, binary()}.

-type route_param() :: {route_param, binary(), binary()}.

-type discover_error() :: {pages_directory_unreadable, binary()} |
    {missing_not_found, binary(), binary()} |
    {unsupported_catch_all, binary()} |
    {invalid_page_module_path, binary()} |
    {invalid_page_segment, binary(), binary()} |
    {invalid_route_param, binary(), binary()} |
    {duplicate_route_param, binary(), binary()} |
    {invalid_constructor, binary(), binary()} |
    {duplicate_path, binary(), binary(), binary()} |
    {duplicate_constructor, binary(), binary(), binary()} |
    {duplicate_helper, binary(), binary(), binary()}.

-file("src/proute/discover.gleam", 453).
-spec route_segment_sort_key(route_segment()) -> binary().
route_segment_sort_key(Segment) ->
    case Segment of
        {static_segment, Value} ->
            <<"1:"/utf8, Value/binary>>;

        {dynamic_segment, _} ->
            <<"2:"/utf8>>
    end.

-file("src/proute/discover.gleam", 436).
-spec route_sort_key(page_route()) -> binary().
route_sort_key(Route) ->
    case erlang:element(2, Route) of
        home ->
            <<"0:"/utf8, (erlang:element(4, Route))/binary>>;

        static ->
            <<<<<<"1:"/utf8,
                        (begin
                            _pipe = erlang:element(5, Route),
                            _pipe@1 = gleam@list:map(
                                _pipe,
                                fun route_segment_sort_key/1
                            ),
                            _pipe@2 = (fun(Keys) ->
                                lists:append(Keys, [<<"0"/utf8>>])
                            end)(_pipe@1),
                            gleam@string:join(_pipe@2, <<"/"/utf8>>)
                        end)/binary>>/binary,
                    ":"/utf8>>/binary,
                (erlang:element(4, Route))/binary>>;

        dynamic ->
            <<<<<<"1:"/utf8,
                        (begin
                            _pipe = erlang:element(5, Route),
                            _pipe@1 = gleam@list:map(
                                _pipe,
                                fun route_segment_sort_key/1
                            ),
                            _pipe@2 = (fun(Keys) ->
                                lists:append(Keys, [<<"0"/utf8>>])
                            end)(_pipe@1),
                            gleam@string:join(_pipe@2, <<"/"/utf8>>)
                        end)/binary>>/binary,
                    ":"/utf8>>/binary,
                (erlang:element(4, Route))/binary>>;

        not_found ->
            <<"2:"/utf8, (erlang:element(4, Route))/binary>>
    end.

-file("src/proute/discover.gleam", 431).
-spec sort_routes(list(page_route())) -> list(page_route()).
sort_routes(Routes) ->
    _pipe = Routes,
    gleam@list:sort(
        _pipe,
        fun(A, B) ->
            gleam@string:compare(route_sort_key(A), route_sort_key(B))
        end
    ).

-file("src/proute/discover.gleam", 406).
-spec reject_duplicate_helpers(
    list(page_route()),
    gleam@dict:dict(binary(), page_route())
) -> {ok, nil} | {error, discover_error()}.
reject_duplicate_helpers(Routes, Seen) ->
    case Routes of
        [] ->
            {ok, nil};

        [Route | Rest] ->
            Helper = proute@names:helper_name(erlang:element(3, Route)),
            case {proute@names:is_valid_label(Helper),
                gleam_stdlib:map_get(Seen, Helper)} of
                {false, _} ->
                    {error,
                        {invalid_constructor,
                            erlang:element(7, Route),
                            erlang:element(3, Route)}};

                {true, {ok, First}} ->
                    {error,
                        {duplicate_helper,
                            Helper,
                            erlang:element(7, First),
                            erlang:element(7, Route)}};

                {true, {error, _}} ->
                    reject_duplicate_helpers(
                        Rest,
                        gleam@dict:insert(Seen, Helper, Route)
                    )
            end
    end.

-file("src/proute/discover.gleam", 383).
-spec reject_duplicate_constructors(
    list(page_route()),
    gleam@dict:dict(binary(), page_route())
) -> {ok, nil} | {error, discover_error()}.
reject_duplicate_constructors(Routes, Seen) ->
    case Routes of
        [] ->
            {ok, nil};

        [Route | Rest] ->
            case gleam_stdlib:map_get(Seen, erlang:element(3, Route)) of
                {ok, First} ->
                    {error,
                        {duplicate_constructor,
                            erlang:element(3, Route),
                            erlang:element(7, First),
                            erlang:element(7, Route)}};

                {error, _} ->
                    reject_duplicate_constructors(
                        Rest,
                        gleam@dict:insert(Seen, erlang:element(3, Route), Route)
                    )
            end
    end.

-file("src/proute/discover.gleam", 537).
-spec route_pattern_path(page_route()) -> binary().
route_pattern_path(Route) ->
    _pipe = erlang:element(4, Route),
    _pipe@1 = gleam@string:split(_pipe, <<"/"/utf8>>),
    _pipe@2 = gleam@list:map(
        _pipe@1,
        fun(Segment) ->
            case gleam_stdlib:string_starts_with(Segment, <<":"/utf8>>) of
                true ->
                    <<":_"/utf8>>;

                false ->
                    Segment
            end
        end
    ),
    gleam@string:join(_pipe@2, <<"/"/utf8>>).

-file("src/proute/discover.gleam", 351).
-spec reject_duplicate_paths(
    list(page_route()),
    gleam@dict:dict(binary(), page_route())
) -> {ok, nil} | {error, discover_error()}.
reject_duplicate_paths(Routes, Seen) ->
    case Routes of
        [] ->
            {ok, nil};

        [Route | Rest] ->
            Key = route_pattern_path(Route),
            case gleam_stdlib:map_get(Seen, Key) of
                {ok, First} ->
                    {error,
                        {duplicate_path,
                            Key,
                            erlang:element(7, First),
                            erlang:element(7, Route)}};

                {error, _} ->
                    reject_duplicate_paths(
                        Rest,
                        gleam@dict:insert(Seen, Key, Route)
                    )
            end
    end.

-file("src/proute/discover.gleam", 373).
-spec require_not_found(proute@config:mount(), list(page_route())) -> {ok, nil} |
    {error, discover_error()}.
require_not_found(Mount, Routes) ->
    case gleam@list:any(
        Routes,
        fun(Route) -> erlang:element(2, Route) =:= not_found end
    ) of
        true ->
            {ok, nil};

        false ->
            {error,
                {missing_not_found,
                    erlang:element(2, Mount),
                    erlang:element(3, Mount)}}
    end.

-file("src/proute/discover.gleam", 520).
-spec validate_params(
    binary(),
    list(route_param()),
    gleam@dict:dict(binary(), nil)
) -> {ok, nil} | {error, discover_error()}.
validate_params(Source_file, Params, Seen) ->
    case Params of
        [] ->
            {ok, nil};

        [Param | Rest] ->
            case {proute@names:is_valid_label(erlang:element(2, Param)),
                gleam@dict:has_key(Seen, erlang:element(2, Param))} of
                {false, _} ->
                    {error,
                        {invalid_route_param,
                            Source_file,
                            erlang:element(2, Param)}};

                {true, true} ->
                    {error,
                        {duplicate_route_param,
                            Source_file,
                            erlang:element(2, Param)}};

                {true, false} ->
                    validate_params(
                        Source_file,
                        Rest,
                        gleam@dict:insert(Seen, erlang:element(2, Param), nil)
                    )
            end
    end.

-file("src/proute/discover.gleam", 510).
-spec validate_constructor(binary(), binary()) -> {ok, nil} |
    {error, discover_error()}.
validate_constructor(Source_file, Constructor) ->
    case proute@names:is_valid_type_name(Constructor) of
        true ->
            {ok, nil};

        false ->
            {error, {invalid_constructor, Source_file, Constructor}}
    end.

-file("src/proute/discover.gleam", 502).
-spec validate_route(binary(), page_route()) -> {ok, nil} |
    {error, discover_error()}.
validate_route(Source_file, Route) ->
    gleam@result:'try'(
        validate_constructor(Source_file, erlang:element(3, Route)),
        fun(_) ->
            validate_params(Source_file, erlang:element(6, Route), maps:new())
        end
    ).

-file("src/proute/discover.gleam", 300).
-spec dynamic_params(list(route_segment())) -> list(route_param()).
dynamic_params(Segments) ->
    _pipe = Segments,
    gleam@list:filter_map(_pipe, fun(Segment) -> case Segment of
                {dynamic_segment, Name} ->
                    {ok, {route_param, Name, <<"String"/utf8>>}};

                {static_segment, _} ->
                    {error, nil}
            end end).

-file("src/proute/discover.gleam", 344).
-spec route_segment_path(route_segment()) -> binary().
route_segment_path(Segment) ->
    case Segment of
        {static_segment, Value} ->
            Value;

        {dynamic_segment, Name} ->
            <<":"/utf8, Name/binary>>
    end.

-file("src/proute/discover.gleam", 330).
-spec route_path(binary(), list(route_segment())) -> binary().
route_path(Route_root, Segments) ->
    Suffix = begin
        _pipe = Segments,
        _pipe@1 = gleam@list:map(_pipe, fun route_segment_path/1),
        gleam@string:join(_pipe@1, <<"/"/utf8>>)
    end,
    case {Route_root, Suffix} of
        {<<"/"/utf8>>, <<""/utf8>>} ->
            <<"/"/utf8>>;

        {<<"/"/utf8>>, Suffix@1} ->
            <<"/"/utf8, Suffix@1/binary>>;

        {Route_root@1, <<""/utf8>>} ->
            Route_root@1;

        {Route_root@2, Suffix@2} ->
            <<<<Route_root@2/binary, "/"/utf8>>/binary, Suffix@2/binary>>
    end.

-file("src/proute/discover.gleam", 293).
-spec is_dynamic_segment(route_segment()) -> boolean().
is_dynamic_segment(Segment) ->
    case Segment of
        {dynamic_segment, _} ->
            true;

        {static_segment, _} ->
            false
    end.

-file("src/proute/discover.gleam", 282).
-spec route_kind(list(route_segment())) -> route_kind().
route_kind(Segments) ->
    case Segments of
        [] ->
            home;

        _ ->
            case gleam@list:any(Segments, fun is_dynamic_segment/1) of
                true ->
                    dynamic;

                false ->
                    static
            end
    end.

-file("src/proute/discover.gleam", 319).
-spec default_constructor(binary(), binary()) -> binary().
default_constructor(Value, Default) ->
    case Value of
        <<""/utf8>> ->
            Default;

        _ ->
            Value
    end.

-file("src/proute/discover.gleam", 469).
-spec drop_suffix(binary(), binary()) -> binary().
drop_suffix(Value, Suffix) ->
    case gleam_stdlib:string_ends_with(Value, Suffix) of
        true ->
            gleam@string:drop_end(Value, string:length(Suffix));

        false ->
            Value
    end.

-file("src/proute/discover.gleam", 326).
-spec constructor_word(binary()) -> binary().
constructor_word(Segment) ->
    drop_suffix(Segment, <<"_"/utf8>>).

-file("src/proute/discover.gleam", 310).
-spec constructor_name(list(binary())) -> binary().
constructor_name(Raw_segments) ->
    _pipe = Raw_segments,
    _pipe@1 = gleam@list:filter(
        _pipe,
        fun(Segment) -> Segment /= <<"home_"/utf8>> end
    ),
    _pipe@2 = gleam@list:map(_pipe@1, fun constructor_word/1),
    _pipe@3 = gleam@string:join(_pipe@2, <<"_"/utf8>>),
    _pipe@4 = proute@names:pascal_case(_pipe@3),
    default_constructor(_pipe@4, <<"Home"/utf8>>).

-file("src/proute/discover.gleam", 275).
-spec route_segment(binary()) -> route_segment().
route_segment(Segment) ->
    case gleam_stdlib:string_ends_with(Segment, <<"_"/utf8>>) of
        true ->
            {dynamic_segment, drop_suffix(Segment, <<"_"/utf8>>)};

        false ->
            {static_segment, Segment}
    end.

-file("src/proute/discover.gleam", 264).
-spec route_segments(list(binary())) -> list(route_segment()).
route_segments(Raw_segments) ->
    _pipe = Raw_segments,
    _pipe@1 = gleam@list:index_map(
        _pipe,
        fun(Segment, Index) ->
            case (Segment =:= <<"home_"/utf8>>) andalso (Index =:= (erlang:length(
                Raw_segments
            )
            - 1)) of
                true ->
                    [];

                false ->
                    [route_segment(Segment)]
            end
        end
    ),
    lists:append(_pipe@1).

-file("src/proute/discover.gleam", 226).
-spec regular_route(binary(), proute@config:mount(), list(binary()), binary()) -> page_route().
regular_route(Source_file, Mount, Raw_segments, Page_module) ->
    Route_segments = route_segments(Raw_segments),
    Constructor = <<(erlang:element(9, Mount))/binary,
        (constructor_name(Raw_segments))/binary>>,
    {page_route,
        route_kind(Route_segments),
        Constructor,
        route_path(erlang:element(4, Mount), Route_segments),
        Route_segments,
        dynamic_params(Route_segments),
        Source_file,
        Page_module}.

-file("src/proute/discover.gleam", 489).
-spec validate_raw_segments(binary(), list(binary())) -> {ok, nil} |
    {error, discover_error()}.
validate_raw_segments(Source_file, Raw_segments) ->
    _pipe = Raw_segments,
    gleam@list:try_each(
        _pipe,
        fun(Segment) -> case proute@names:is_valid_module_segment(Segment) of
                true ->
                    {ok, nil};

                false ->
                    {error, {invalid_page_segment, Source_file, Segment}}
            end end
    ).

-file("src/proute/discover.gleam", 246).
-spec not_found_route(binary(), proute@config:mount(), binary()) -> page_route().
not_found_route(Source_file, Mount, Page_module) ->
    Segments = [{static_segment, <<"not_found"/utf8>>}],
    {page_route,
        not_found,
        <<"NotFound"/utf8>>,
        route_path(erlang:element(4, Mount), Segments),
        Segments,
        [],
        Source_file,
        Page_module}.

-file("src/proute/discover.gleam", 460).
-spec relative_source_path(binary(), binary()) -> binary().
relative_source_path(Source_file, Root) ->
    Prefix = <<Root/binary, "/"/utf8>>,
    case gleam_stdlib:string_starts_with(Source_file, Prefix) of
        true ->
            gleam@string:drop_start(Source_file, string:length(Prefix));

        false ->
            Source_file
    end.

-file("src/proute/discover.gleam", 194).
-spec route_from_file(binary(), proute@config:mount()) -> {ok, page_route()} |
    {error, discover_error()}.
route_from_file(Source_file, Mount) ->
    Relative = relative_source_path(Source_file, erlang:element(3, Mount)),
    Without_extension = drop_suffix(Relative, <<".gleam"/utf8>>),
    Raw_segments = gleam@string:split(Without_extension, <<"/"/utf8>>),
    case gleam@list:last(Raw_segments) of
        {ok, <<"all_"/utf8>>} ->
            {error, {unsupported_catch_all, Source_file}};

        {ok, <<"not_found_"/utf8>>} ->
            gleam@result:'try'(
                validate_raw_segments(Source_file, Raw_segments),
                fun(_) ->
                    gleam@result:'try'(
                        begin
                            _pipe = proute@names:module_path_from_file(
                                Source_file
                            ),
                            gleam@result:map_error(
                                _pipe,
                                fun(_) ->
                                    {invalid_page_module_path, Source_file}
                                end
                            )
                        end,
                        fun(Page_module) ->
                            {ok,
                                not_found_route(Source_file, Mount, Page_module)}
                        end
                    )
                end
            );

        {ok, _} ->
            gleam@result:'try'(
                validate_raw_segments(Source_file, Raw_segments),
                fun(_) ->
                    gleam@result:'try'(
                        begin
                            _pipe@1 = proute@names:module_path_from_file(
                                Source_file
                            ),
                            gleam@result:map_error(
                                _pipe@1,
                                fun(_) ->
                                    {invalid_page_module_path, Source_file}
                                end
                            )
                        end,
                        fun(Page_module@1) ->
                            Route = regular_route(
                                Source_file,
                                Mount,
                                Raw_segments,
                                Page_module@1
                            ),
                            gleam@result:'try'(
                                validate_route(Source_file, Route),
                                fun(_) -> {ok, Route} end
                            )
                        end
                    )
                end
            );

        {error, _} ->
            {error, {pages_directory_unreadable, erlang:element(3, Mount)}}
    end.

-file("src/proute/discover.gleam", 79).
-spec is_wire_module(binary()) -> boolean().
is_wire_module(Source_file) ->
    gleam_stdlib:string_ends_with(Source_file, <<"/wire.gleam"/utf8>>).

-file("src/proute/discover.gleam", 483).
-spec join_path(list(binary())) -> binary().
join_path(Parts) ->
    _pipe = Parts,
    _pipe@1 = gleam@list:filter(_pipe, fun(Part) -> Part /= <<""/utf8>> end),
    gleam@string:join(_pipe@1, <<"/"/utf8>>).

-file("src/proute/discover.gleam", 169).
-spec walk_entries(binary(), list(binary()), list(binary())) -> {ok,
        list(binary())} |
    {error, discover_error()}.
walk_entries(Directory, Entries, Files) ->
    case Entries of
        [] ->
            {ok, lists:reverse(Files)};

        [Entry | Rest] ->
            Path = join_path([Directory, Entry]),
            case simplifile_erl:is_directory(Path) of
                {ok, true} ->
                    gleam@result:'try'(
                        walk_pages(Path),
                        fun(Nested) ->
                            walk_entries(
                                Directory,
                                Rest,
                                lists:append(lists:reverse(Nested), Files)
                            )
                        end
                    );

                _ ->
                    walk_entries(Directory, Rest, [Path | Files])
            end
    end.

-file("src/proute/discover.gleam", 162).
-spec walk_pages(binary()) -> {ok, list(binary())} | {error, discover_error()}.
walk_pages(Root) ->
    case simplifile_erl:read_directory(Root) of
        {error, _} ->
            {error, {pages_directory_unreadable, Root}};

        {ok, Entries} ->
            walk_entries(
                Root,
                begin
                    _pipe = Entries,
                    gleam@list:sort(_pipe, fun gleam@string:compare/2)
                end,
                []
            )
    end.

-file("src/proute/discover.gleam", 59).
-spec discover_mount(proute@config:mount()) -> {ok, mount_routes()} |
    {error, discover_error()}.
discover_mount(Mount) ->
    gleam@result:'try'(
        walk_pages(erlang:element(3, Mount)),
        fun(Files) ->
            gleam@result:'try'(
                begin
                    _pipe = Files,
                    _pipe@1 = gleam@list:filter(
                        _pipe,
                        fun(_capture) ->
                            gleam_stdlib:string_ends_with(
                                _capture,
                                <<".gleam"/utf8>>
                            )
                        end
                    ),
                    _pipe@2 = gleam@list:filter(
                        _pipe@1,
                        fun(File) -> not is_wire_module(File) end
                    ),
                    gleam@list:try_map(
                        _pipe@2,
                        fun(_capture@1) ->
                            route_from_file(_capture@1, Mount)
                        end
                    )
                end,
                fun(Routes) ->
                    gleam@result:'try'(
                        require_not_found(Mount, Routes),
                        fun(_) ->
                            gleam@result:'try'(
                                reject_duplicate_paths(Routes, maps:new()),
                                fun(_) ->
                                    gleam@result:'try'(
                                        reject_duplicate_constructors(
                                            Routes,
                                            maps:new()
                                        ),
                                        fun(_) ->
                                            gleam@result:'try'(
                                                reject_duplicate_helpers(
                                                    Routes,
                                                    maps:new()
                                                ),
                                                fun(_) ->
                                                    {ok,
                                                        {mount_routes,
                                                            Mount,
                                                            sort_routes(Routes)}}
                                                end
                                            )
                                        end
                                    )
                                end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/proute/discover.gleam", 83).
-spec describe_error(discover_error()) -> binary().
describe_error(Error) ->
    case Error of
        {pages_directory_unreadable, Path} ->
            <<<<"Could not read pages directory "/utf8,
                    (gleam@string:inspect(Path))/binary>>/binary,
                "."/utf8>>;

        {missing_not_found, Mount_name, Pages} ->
            <<<<<<<<"Mount "/utf8, (gleam@string:inspect(Mount_name))/binary>>/binary,
                        " at "/utf8>>/binary,
                    (gleam@string:inspect(Pages))/binary>>/binary,
                " is missing not_found_.gleam. Add not_found_.gleam so proute can generate the NotFound route."/utf8>>;

        {unsupported_catch_all, Source_file} ->
            <<<<"Unsupported catch-all page "/utf8,
                    (gleam@string:inspect(Source_file))/binary>>/binary,
                ": all_.gleam is reserved but not generated yet."/utf8>>;

        {invalid_page_module_path, Source_file@1} ->
            <<<<"Invalid page module path "/utf8,
                    (gleam@string:inspect(Source_file@1))/binary>>/binary,
                ": page files must live under a src directory so generated imports can be module-relative."/utf8>>;

        {invalid_page_segment, Source_file@2, Segment} ->
            <<<<<<<<"Invalid page path segment "/utf8,
                            (gleam@string:inspect(Segment))/binary>>/binary,
                        " in "/utf8>>/binary,
                    (gleam@string:inspect(Source_file@2))/binary>>/binary,
                ": use valid Gleam module path segments."/utf8>>;

        {invalid_route_param, Source_file@3, Param} ->
            <<<<<<<<"Invalid route parameter "/utf8,
                            (gleam@string:inspect(Param))/binary>>/binary,
                        " in "/utf8>>/binary,
                    (gleam@string:inspect(Source_file@3))/binary>>/binary,
                ": dynamic route parameters must be valid Gleam labels."/utf8>>;

        {duplicate_route_param, Source_file@4, Param@1} ->
            <<<<<<<<"Duplicate route parameter "/utf8,
                            (gleam@string:inspect(Param@1))/binary>>/binary,
                        " in "/utf8>>/binary,
                    (gleam@string:inspect(Source_file@4))/binary>>/binary,
                "."/utf8>>;

        {invalid_constructor, Source_file@5, Constructor} ->
            <<<<<<<<"Invalid route constructor "/utf8,
                            (gleam@string:inspect(Constructor))/binary>>/binary,
                        " from "/utf8>>/binary,
                    (gleam@string:inspect(Source_file@5))/binary>>/binary,
                "."/utf8>>;

        {duplicate_path, Path@1, First_file, Second_file} ->
            <<<<<<<<<<<<"Duplicate route path "/utf8,
                                    (gleam@string:inspect(Path@1))/binary>>/binary,
                                " from "/utf8>>/binary,
                            (gleam@string:inspect(First_file))/binary>>/binary,
                        " and "/utf8>>/binary,
                    (gleam@string:inspect(Second_file))/binary>>/binary,
                "."/utf8>>;

        {duplicate_constructor, Constructor@1, First_file@1, Second_file@1} ->
            <<<<<<<<<<<<"Duplicate route constructor "/utf8,
                                    (gleam@string:inspect(Constructor@1))/binary>>/binary,
                                " from "/utf8>>/binary,
                            (gleam@string:inspect(First_file@1))/binary>>/binary,
                        " and "/utf8>>/binary,
                    (gleam@string:inspect(Second_file@1))/binary>>/binary,
                "."/utf8>>;

        {duplicate_helper, Helper, First_file@2, Second_file@2} ->
            <<<<<<<<<<<<"Duplicate route helper "/utf8,
                                    (gleam@string:inspect(Helper))/binary>>/binary,
                                " from "/utf8>>/binary,
                            (gleam@string:inspect(First_file@2))/binary>>/binary,
                        " and "/utf8>>/binary,
                    (gleam@string:inspect(Second_file@2))/binary>>/binary,
                "."/utf8>>
    end.

-file("src/proute/discover.gleam", 476).
-spec drop_prefix(binary(), binary()) -> binary().
drop_prefix(Value, Prefix) ->
    case gleam_stdlib:string_starts_with(Value, Prefix) of
        true ->
            gleam@string:drop_start(Value, string:length(Prefix));

        false ->
            Value
    end.

-file("src/proute/discover.gleam", 152).
-spec page_module_path(binary()) -> binary().
page_module_path(Source_file) ->
    _pipe = proute@names:module_path_from_file(Source_file),
    gleam@result:unwrap(
        _pipe,
        begin
            _pipe@1 = Source_file,
            _pipe@2 = drop_suffix(_pipe@1, <<".gleam"/utf8>>),
            _pipe@3 = gleam@string:replace(_pipe@2, <<"\\"/utf8>>, <<"/"/utf8>>),
            drop_prefix(_pipe@3, <<"src/"/utf8>>)
        end
    ).