-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} -> <<<>/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 = <>, 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 ).