-module(dream@router@pattern). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/dream/router/pattern.gleam"). -export([match_segments/2]). -export_type([pattern_type/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. ?MODULEDOC( " Pattern matching utilities for router path patterns\n" "\n" " Supports various pattern types:\n" " - Static segments: \"users\"\n" " - Named parameters: \":id\"\n" " - Single wildcards: \"*\" or \"*name\"\n" " - Multi-segment wildcards: \"**\" or \"**path\"\n" " - Extension patterns: \"*.jpg\" or \"*.{jpg,png,gif}\"\n" "\n" " This is an internal module used by the router for path matching.\n" " Most applications won't need to use this directly - the router\n" " handles pattern matching automatically.\n" "\n" " ## Pattern Examples\n" "\n" " ```gleam\n" " // Static: /users/profile\n" " // Param: /users/:id → extracts id\n" " // Single wildcard: /files/* → matches one segment\n" " // Multi wildcard: /files/** → matches multiple segments\n" " // Extension: /images/*.jpg → matches .jpg files\n" " // Multiple extensions: /images/*.{jpg,png,gif}\n" " ```\n" ). -type pattern_type() :: static_segment | param_segment | single_wildcard | multi_wildcard | extension_pattern. -file("src/dream/router/pattern.gleam", 93). ?DOC(" Classify what type of pattern a segment is\n"). -spec classify_pattern(binary()) -> pattern_type(). classify_pattern(Segment) -> Starts_with_colon = gleam_stdlib:string_starts_with(Segment, <<":"/utf8>>), Starts_with_star = gleam_stdlib:string_starts_with(Segment, <<"*"/utf8>>), Starts_with_double_star = gleam_stdlib:string_starts_with( Segment, <<"**"/utf8>> ), Starts_with_star_dot = gleam_stdlib:string_starts_with( Segment, <<"*."/utf8>> ), case {Starts_with_colon, Starts_with_double_star, Starts_with_star_dot, Starts_with_star} of {true, _, _, _} -> param_segment; {_, true, _, _} -> multi_wildcard; {_, _, true, _} -> extension_pattern; {_, _, _, true} -> single_wildcard; {_, _, _, _} -> static_segment end. -file("src/dream/router/pattern.gleam", 116). ?DOC( " Extract name from pattern segment (for params and named wildcards)\n" " \":id\" → Some(\"id\"), \"*name\" → Some(\"name\"), \"**path\" → Some(\"path\")\n" " \"*\" → None, \"**\" → None, \"static\" → None\n" ). -spec extract_pattern_name(binary(), pattern_type()) -> gleam@option:option(binary()). extract_pattern_name(Segment, Pattern_type) -> case Pattern_type of param_segment -> {some, gleam@string:drop_start(Segment, 1)}; single_wildcard when Segment =:= <<"*"/utf8>> -> none; single_wildcard -> {some, gleam@string:drop_start(Segment, 1)}; multi_wildcard when Segment =:= <<"**"/utf8>> -> none; multi_wildcard -> {some, gleam@string:drop_start(Segment, 2)}; extension_pattern -> none; _ -> none end. -file("src/dream/router/pattern.gleam", 135). ?DOC(" Add parameter to list if name is provided\n"). -spec maybe_add_param( gleam@option:option(binary()), binary(), list({binary(), binary()}) ) -> list({binary(), binary()}). maybe_add_param(Name, Value, Params) -> case Name of {some, N} -> [{N, Value} | Params]; none -> Params end. -file("src/dream/router/pattern.gleam", 159). -spec matches_brace_extensions(binary(), binary()) -> boolean(). matches_brace_extensions(Filename, Brace_pattern) -> Inner = begin _pipe = Brace_pattern, _pipe@1 = gleam@string:drop_start(_pipe, 1), gleam@string:drop_end(_pipe@1, 1) end, Extensions = gleam@string:split(Inner, <<","/utf8>>), gleam@list:any( Extensions, fun(Ext) -> gleam_stdlib:string_ends_with( Filename, <<"."/utf8, (gleam@string:trim(Ext))/binary>> ) end ). -file("src/dream/router/pattern.gleam", 148). ?DOC( " Parse extension pattern and check if filename matches\n" " Supports: *.jpg and *.{jpg,png,gif}\n" ). -spec matches_extension(binary(), binary()) -> boolean(). matches_extension(Filename, Pattern) -> Ext_pattern = gleam@string:drop_start(Pattern, 2), Has_braces = gleam_stdlib:string_starts_with(Ext_pattern, <<"{"/utf8>>) andalso gleam_stdlib:string_ends_with(Ext_pattern, <<"}"/utf8>>), case Has_braces of false -> gleam_stdlib:string_ends_with( Filename, <<"."/utf8, Ext_pattern/binary>> ); true -> matches_brace_extensions(Filename, Ext_pattern) end. -file("src/dream/router/pattern.gleam", 255). ?DOC( " Strip format extension from a path segment\n" " \"products.json\" -> \"products\", \"users.csv\" -> \"users\", \"file\" -> \"file\"\n" ). -spec strip_format_extension(binary()) -> binary(). strip_format_extension(Segment) -> case gleam@string:split(Segment, <<"."/utf8>>) of [Base, _] -> Base; _ -> Segment end. -file("src/dream/router/pattern.gleam", 335). ?DOC(" ** at end of pattern - capture all remaining segments\n"). -spec handle_multi_wildcard_at_end( gleam@option:option(binary()), list(binary()), list({binary(), binary()}) ) -> gleam@option:option(list({binary(), binary()})). handle_multi_wildcard_at_end(Name, Path_segments, Params) -> Captured = gleam@string:join(Path_segments, <<"/"/utf8>>), Updated_params = maybe_add_param(Name, Captured, Params), {some, lists:reverse(Updated_params)}. -file("src/dream/router/pattern.gleam", 217). ?DOC(" Handle :param segment\n"). -spec handle_param_segment( binary(), list(binary()), list(binary()), list({binary(), binary()}) ) -> gleam@option:option(list({binary(), binary()})). handle_param_segment(Pattern_seg, Rest_pattern, Path_segments, Params) -> case Path_segments of [] -> none; [Path_seg | Rest_path] -> Param_name = gleam@string:drop_start(Pattern_seg, 1), Updated_params = [{Param_name, Path_seg} | Params], extract_params(Rest_pattern, Rest_path, Updated_params) end. -file("src/dream/router/pattern.gleam", 169). ?DOC(" Extract path parameters from pattern segments and path segments\n"). -spec extract_params(list(binary()), list(binary()), list({binary(), binary()})) -> gleam@option:option(list({binary(), binary()})). extract_params(Pattern_segments, Path_segments, Accumulated_params) -> case {Pattern_segments, Path_segments} of {[], []} -> {some, lists:reverse(Accumulated_params)}; {[], _} -> none; {[Pattern_seg | Rest_pat], _} -> Pattern_type = classify_pattern(Pattern_seg), extract_params_by_type( Pattern_type, Pattern_seg, Rest_pat, Path_segments, Accumulated_params ) end. -file("src/dream/router/pattern.gleam", 191). ?DOC(" Route to appropriate handler based on pattern type\n"). -spec extract_params_by_type( pattern_type(), binary(), list(binary()), list(binary()), list({binary(), binary()}) ) -> gleam@option:option(list({binary(), binary()})). extract_params_by_type( Pattern_type, Pattern_seg, Rest_pattern, Path_segments, Params ) -> case Pattern_type of multi_wildcard -> handle_multi_wildcard( Pattern_seg, Rest_pattern, Path_segments, Params ); single_wildcard -> handle_single_wildcard( Pattern_seg, Rest_pattern, Path_segments, Params ); extension_pattern -> handle_extension_pattern( Pattern_seg, Rest_pattern, Path_segments, Params ); param_segment -> handle_param_segment( Pattern_seg, Rest_pattern, Path_segments, Params ); static_segment -> handle_static_segment( Pattern_seg, Rest_pattern, Path_segments, Params ) end. -file("src/dream/router/pattern.gleam", 85). ?DOC( " Match a pattern against path segments and extract parameters\n" "\n" " Core pattern matching function that compares a route pattern\n" " (split into segments) against a request path (also split into segments).\n" " Returns extracted parameters if the pattern matches, None if it doesn't.\n" "\n" " ## Parameters\n" "\n" " - `pattern_segments`: Route pattern split by \"/\" (e.g., [\"users\", \":id\"])\n" " - `path_segments`: Request path split by \"/\" (e.g., [\"users\", \"123\"])\n" "\n" " ## Returns\n" "\n" " - `Some(params)`: List of extracted parameters as key-value tuples\n" " - `None`: Pattern doesn't match the path\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " // Named parameter\n" " match_segments([\"users\", \":id\"], [\"users\", \"123\"])\n" " // Some([#(\"id\", \"123\")])\n" "\n" " // Multiple parameters\n" " match_segments([\"users\", \":user_id\", \"posts\", \":id\"], [\"users\", \"1\", \"posts\", \"2\"])\n" " // Some([#(\"user_id\", \"1\"), #(\"id\", \"2\")])\n" "\n" " // Wildcard\n" " match_segments([\"files\", \"**path\"], [\"files\", \"docs\", \"guide.pdf\"])\n" " // Some([#(\"path\", \"docs/guide.pdf\")])\n" "\n" " // No match\n" " match_segments([\"users\", \":id\"], [\"posts\", \"123\"])\n" " // None\n" " ```\n" ). -spec match_segments(list(binary()), list(binary())) -> gleam@option:option(list({binary(), binary()})). match_segments(Pattern_segments, Path_segments) -> extract_params(Pattern_segments, Path_segments, []). -file("src/dream/router/pattern.gleam", 234). ?DOC(" Handle static segment\n"). -spec handle_static_segment( binary(), list(binary()), list(binary()), list({binary(), binary()}) ) -> gleam@option:option(list({binary(), binary()})). handle_static_segment(Pattern_seg, Rest_pattern, Path_segments, Params) -> case Path_segments of [] -> none; [Path_seg | Rest_path] -> Path_without_format = strip_format_extension(Path_seg), case Path_without_format =:= Pattern_seg of true -> extract_params(Rest_pattern, Rest_path, Params); false -> none end end. -file("src/dream/router/pattern.gleam", 263). ?DOC(" Handle * or *name segment\n"). -spec handle_single_wildcard( binary(), list(binary()), list(binary()), list({binary(), binary()}) ) -> gleam@option:option(list({binary(), binary()})). handle_single_wildcard(Pattern_seg, Rest_pattern, Path_segments, Params) -> case Path_segments of [] -> none; [Path_seg | Rest_path] -> Name = extract_pattern_name(Pattern_seg, single_wildcard), Updated_params = maybe_add_param(Name, Path_seg, Params), extract_params(Rest_pattern, Rest_path, Updated_params) end. -file("src/dream/router/pattern.gleam", 299). -spec check_extension_and_continue( binary(), binary(), list(binary()), list(binary()), list({binary(), binary()}) ) -> gleam@option:option(list({binary(), binary()})). check_extension_and_continue( Path_seg, Pattern_seg, Rest_pattern, Rest_path, Params ) -> Matches = matches_extension(Path_seg, Pattern_seg), case Matches of false -> none; true -> Name = extract_pattern_name(Pattern_seg, extension_pattern), Updated_params = maybe_add_param(Name, Path_seg, Params), extract_params(Rest_pattern, Rest_path, Updated_params) end. -file("src/dream/router/pattern.gleam", 280). ?DOC(" Handle *.ext or *.{ext1,ext2} segment\n"). -spec handle_extension_pattern( binary(), list(binary()), list(binary()), list({binary(), binary()}) ) -> gleam@option:option(list({binary(), binary()})). handle_extension_pattern(Pattern_seg, Rest_pattern, Path_segments, Params) -> case Path_segments of [] -> none; [Path_seg | Rest_path] -> check_extension_and_continue( Path_seg, Pattern_seg, Rest_pattern, Rest_path, Params ) end. -file("src/dream/router/pattern.gleam", 383). ?DOC(" Continue trying at next position\n"). -spec try_next_position( gleam@option:option(binary()), list(binary()), list(binary()), list(binary()), list({binary(), binary()}) ) -> gleam@option:option(list({binary(), binary()})). try_next_position( Wildcard_name, Rest_pattern, Remaining_path, Consumed_segments, Params ) -> case Remaining_path of [] -> none; [Seg | Rest] -> try_match_at_positions( Wildcard_name, Rest_pattern, Rest, [Seg | Consumed_segments], Params ) end. -file("src/dream/router/pattern.gleam", 356). ?DOC(" Try matching rest of pattern starting at different positions\n"). -spec try_match_at_positions( gleam@option:option(binary()), list(binary()), list(binary()), list(binary()), list({binary(), binary()}) ) -> gleam@option:option(list({binary(), binary()})). try_match_at_positions( Wildcard_name, Rest_pattern, Remaining_path, Consumed_segments, Params ) -> Captured = gleam@string:join(lists:reverse(Consumed_segments), <<"/"/utf8>>), Updated_params = maybe_add_param(Wildcard_name, Captured, Params), Match_result = extract_params(Rest_pattern, Remaining_path, Updated_params), case Match_result of {some, _} -> Match_result; none -> try_next_position( Wildcard_name, Rest_pattern, Remaining_path, Consumed_segments, Params ) end. -file("src/dream/router/pattern.gleam", 346). ?DOC(" ** in middle - try matching rest of pattern at each position\n"). -spec handle_multi_wildcard_in_middle( gleam@option:option(binary()), list(binary()), list(binary()), list({binary(), binary()}) ) -> gleam@option:option(list({binary(), binary()})). handle_multi_wildcard_in_middle(Name, Rest_pattern, Path_segments, Params) -> try_match_at_positions(Name, Rest_pattern, Path_segments, [], Params). -file("src/dream/router/pattern.gleam", 319). ?DOC(" Handle ** or **name segment\n"). -spec handle_multi_wildcard( binary(), list(binary()), list(binary()), list({binary(), binary()}) ) -> gleam@option:option(list({binary(), binary()})). handle_multi_wildcard(Pattern_seg, Rest_pattern, Path_segments, Params) -> Name = extract_pattern_name(Pattern_seg, multi_wildcard), case Rest_pattern of [] -> handle_multi_wildcard_at_end(Name, Path_segments, Params); _ -> handle_multi_wildcard_in_middle( Name, Rest_pattern, Path_segments, Params ) end.