Current section

Files

Jump to
dream src dream@router@parser.erl
Raw

src/dream@router@parser.erl

-module(dream@router@parser).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/dream/router/parser.gleam").
-export([parse_pattern/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.
?MODULEDOC(
" Pattern Parser - Convert string paths to structured segments\n"
"\n"
" Parses route pattern strings (like \"/users/:id/posts/**path\") into\n"
" structured segment lists that the radix trie can use for matching.\n"
"\n"
" ## Pattern Syntax\n"
"\n"
" - **Literal**: `users` - Matches exactly \"users\"\n"
" - **Parameter**: `:id` - Captures one segment as \"id\"\n"
" - **Single wildcard**: `*` or `*name` - Matches one segment\n"
" - **Multi wildcard**: `**` or `**path` - Matches zero or more segments\n"
" - **Extension**: `*.jpg` - Matches filenames ending in .jpg\n"
" - **Multiple extensions**: `*.{jpg,png,gif}` - Matches any of the listed extensions\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" parse_pattern(\"/users/:id\")\n"
" // [Literal(\"users\"), Param(\"id\")]\n"
"\n"
" parse_pattern(\"/files/**path\")\n"
" // [Literal(\"files\"), MultiWildcard(Some(\"path\"))]\n"
"\n"
" parse_pattern(\"/images/*.{jpg,png}\")\n"
" // [Literal(\"images\"), ExtensionPattern([\"jpg\", \"png\"])]\n"
" ```\n"
"\n"
" ## Pattern Precedence\n"
"\n"
" When multiple routes could match the same path, the most specific wins:\n"
" 1. Literal segments (highest priority)\n"
" 2. Parameters\n"
" 3. Single wildcards\n"
" 4. Extension patterns\n"
" 5. Multi-segment wildcards (lowest priority)\n"
).
-file("src/dream/router/parser.gleam", 104).
?DOC(
" Check if a segment is non-empty\n"
"\n"
" Used to filter out empty segments from multiple slashes or leading/trailing slashes.\n"
).
-spec is_non_empty_segment(binary()) -> boolean().
is_non_empty_segment(Seg) ->
Seg /= <<""/utf8>>.
-file("src/dream/router/parser.gleam", 137).
?DOC(
" Parse a segment that might be a literal or a literal with extension pattern\n"
"\n"
" Handles patterns like \"products.{json,xml}\" which should match\n"
" \"products.json\" or \"products.xml\"\n"
).
-spec parse_literal_or_extension(binary()) -> dream@router@trie:segment().
parse_literal_or_extension(Segment) ->
case gleam@string:split_once(Segment, <<".{"/utf8>>) of
{ok, {Base_name, Rest}} ->
case gleam_stdlib:string_ends_with(Rest, <<"}"/utf8>>) of
true ->
Inner = gleam@string:drop_end(Rest, 1),
Extensions = begin
_pipe = Inner,
_pipe@1 = gleam@string:split(_pipe, <<","/utf8>>),
gleam@list:map(_pipe@1, fun gleam@string:trim/1)
end,
{literal_extension, Base_name, Extensions};
false ->
{literal, Segment}
end;
{error, _} ->
{literal, Segment}
end.
-file("src/dream/router/parser.gleam", 179).
?DOC(
" Parse brace-delimited extensions: {jpg,png,gif}\n"
"\n"
" Extracts the comma-separated extensions from inside the braces,\n"
" trimming whitespace from each extension.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" parse_brace_extensions(\"{jpg, png, gif}\")\n"
" // ExtensionPattern([\"jpg\", \"png\", \"gif\"])\n"
" ```\n"
).
-spec parse_brace_extensions(binary()) -> dream@router@trie:segment().
parse_brace_extensions(Brace_pattern) ->
Inner = begin
_pipe = Brace_pattern,
_pipe@1 = gleam@string:drop_start(_pipe, 1),
gleam@string:drop_end(_pipe@1, 1)
end,
Extensions = begin
_pipe@2 = Inner,
_pipe@3 = gleam@string:split(_pipe@2, <<","/utf8>>),
gleam@list:map(_pipe@3, fun gleam@string:trim/1)
end,
{extension_pattern, Extensions}.
-file("src/dream/router/parser.gleam", 161).
?DOC(
" Parse extension pattern from the part after \"*.\"\n"
"\n"
" Handles both single extensions (*.jpg) and multiple extensions (*.{jpg,png}).\n"
).
-spec parse_extension_pattern(binary()) -> dream@router@trie:segment().
parse_extension_pattern(Ext_pattern) ->
case gleam_stdlib:string_starts_with(Ext_pattern, <<"{"/utf8>>) of
true ->
parse_brace_extensions(Ext_pattern);
false ->
{extension_pattern, [Ext_pattern]}
end.
-file("src/dream/router/parser.gleam", 112).
?DOC(
" Parse a single segment string into a Segment type\n"
"\n"
" Determines the segment type based on its prefix and structure.\n"
" Handles all pattern syntaxes: parameters, wildcards, extensions, and literals.\n"
).
-spec parse_segment(binary()) -> dream@router@trie:segment().
parse_segment(Segment) ->
case Segment of
<<":"/utf8, Name/binary>> ->
{param, Name};
<<"**"/utf8>> ->
{multi_wildcard, none};
<<"**"/utf8, Name@1/binary>> ->
{multi_wildcard, {some, Name@1}};
<<"*."/utf8, Ext_pattern/binary>> ->
parse_extension_pattern(Ext_pattern);
<<"*"/utf8>> ->
{single_wildcard, none};
<<"*"/utf8, Name@2/binary>> ->
{single_wildcard, {some, Name@2}};
_ ->
parse_literal_or_extension(Segment)
end.
-file("src/dream/router/parser.gleam", 90).
?DOC(
" Parse a path pattern string into a list of segments\n"
"\n"
" Converts a string path pattern into structured segments that can be\n"
" inserted into the radix trie. Handles all pattern types including\n"
" literals, parameters, wildcards, and extension patterns.\n"
"\n"
" ## Parameters\n"
"\n"
" - `pattern`: Path pattern string (e.g., \"/users/:id\")\n"
"\n"
" ## Returns\n"
"\n"
" List of segments representing the pattern structure\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" // Simple literal path\n"
" parse_pattern(\"/users\")\n"
" // [Literal(\"users\")]\n"
"\n"
" // Parameter capture\n"
" parse_pattern(\"/users/:id\")\n"
" // [Literal(\"users\"), Param(\"id\")]\n"
"\n"
" // Multi-segment wildcard\n"
" parse_pattern(\"/public/**filepath\")\n"
" // [Literal(\"public\"), MultiWildcard(Some(\"filepath\"))]\n"
"\n"
" // Extension matching\n"
" parse_pattern(\"/images/*.{jpg,png}\")\n"
" // [Literal(\"images\"), ExtensionPattern([\"jpg\", \"png\"])]\n"
" ```\n"
"\n"
" ## Pattern Details\n"
"\n"
" - Leading and trailing slashes are ignored\n"
" - Empty segments (from multiple slashes) are filtered out\n"
" - Parameter names start with `:` (e.g., `:id`)\n"
" - Wildcards start with `*` for single-segment or `**` for multi-segment\n"
" - Extensions use `*.ext` or `*.{ext1,ext2,ext3}` syntax\n"
).
-spec parse_pattern(binary()) -> list(dream@router@trie:segment()).
parse_pattern(Pattern) ->
_pipe = Pattern,
_pipe@1 = gleam@string:split(_pipe, <<"/"/utf8>>),
_pipe@2 = gleam@list:filter(_pipe@1, fun is_non_empty_segment/1),
gleam@list:map(_pipe@2, fun parse_segment/1).