Current section
Files
Jump to
Current section
Files
src/dream_test@gherkin@steps.erl
-module(dream_test@gherkin@steps).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/dream_test/gherkin/steps.gleam").
-export([new/0, given/3, when_/3, then_/3, step/3, find_step/3, capture_count/1, get_int/2, get_float/2, get_string/2, get_word/2]).
-export_type([step_context/0, step_registry/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(
" Step definition registry for Gherkin scenarios.\n"
"\n"
" Use this module to:\n"
"\n"
" - Build a `StepRegistry` by registering patterns and handlers.\n"
" - Match step text against registered patterns (including typed placeholders).\n"
" - Extract typed captures (`{int}`, `{float}`, `{string}`, `{word}`, `{}`).\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let steps =\n"
" new()\n"
" |> step(\"I have {int} items in my cart\", step_have_items)\n"
" |> step(\"I add {int} more items\", step_add_items)\n"
" |> step(\"I should have {int} items total\", step_should_have)\n"
" ```\n"
"\n"
" ## Placeholder types\n"
"\n"
" Step patterns use typed placeholders:\n"
"\n"
" - `{int}`: integers (e.g. `42`, `-5`)\n"
" - `{float}`: decimals (e.g. `3.14`, `-0.5`)\n"
" - `{string}`: quoted strings (e.g. `\"hello world\"`)\n"
" - `{word}`: a single unquoted word (e.g. `alice`)\n"
" - `{}`: any single token\n"
"\n"
" Placeholders can include literal prefixes/suffixes. For example, `${float}`\n"
" matches `$19.99` but captures `19.99` as a `Float`.\n"
).
-type step_context() :: {step_context,
list(dream_test@gherkin@step_trie:captured_value()),
gleam@option:option(list(list(binary()))),
gleam@option:option(binary()),
dream_test@gherkin@world:world()}.
-opaque step_registry() :: {step_registry,
dream_test@gherkin@step_trie:step_trie(fun((step_context()) -> {ok,
dream_test@types:assertion_result()} |
{error, binary()}))}.
-file("src/dream_test/gherkin/steps.gleam", 118).
?DOC(
" Create an empty step registry.\n"
"\n"
" Start with this and chain `given`, `when_`, `then_` calls to add steps.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let steps =\n"
" new()\n"
" |> step(\"I have {int} items\", step_int)\n"
" |> step(\"the price is ${float}\", step_float)\n"
" |> step(\"the message is {string}\", step_string)\n"
" |> step(\"the user is {word}\", step_word)\n"
" |> step(\"everything works\", step_pass)\n"
" ```\n"
).
-spec new() -> step_registry().
new() ->
{step_registry, dream_test@gherkin@step_trie:new()}.
-file("src/dream_test/gherkin/steps.gleam", 142).
?DOC(
" Register a Given step definition.\n"
"\n"
" Given steps describe initial context or preconditions.\n"
"\n"
" ## Parameters\n"
"\n"
" - `registry`: The registry to add to\n"
" - `pattern`: Step pattern with placeholders\n"
" - `handler`: Handler function to execute\n"
"\n"
" ## Returns\n"
"\n"
" A new registry containing the added step.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let registry = new() |> given(\"I have {int} items\", step_pass)\n"
" ```\n"
).
-spec given(
step_registry(),
binary(),
fun((step_context()) -> {ok, dream_test@types:assertion_result()} |
{error, binary()})
) -> step_registry().
given(Registry, Pattern, Handler) ->
Updated = dream_test@gherkin@step_trie:insert(
erlang:element(2, Registry),
<<"Given"/utf8>>,
Pattern,
Handler
),
{step_registry, Updated}.
-file("src/dream_test/gherkin/steps.gleam", 173).
?DOC(
" Register a When step definition.\n"
"\n"
" When steps describe an action or event.\n"
"\n"
" Note: Named `when_` with trailing underscore because `when` is reserved.\n"
"\n"
" ## Parameters\n"
"\n"
" - `registry`: The registry to add to\n"
" - `pattern`: Step pattern with placeholders\n"
" - `handler`: Handler function to execute\n"
"\n"
" ## Returns\n"
"\n"
" A new registry containing the added step.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let registry = new() |> when_(\"I add {int} items\", step_pass)\n"
" ```\n"
).
-spec when_(
step_registry(),
binary(),
fun((step_context()) -> {ok, dream_test@types:assertion_result()} |
{error, binary()})
) -> step_registry().
when_(Registry, Pattern, Handler) ->
Updated = dream_test@gherkin@step_trie:insert(
erlang:element(2, Registry),
<<"When"/utf8>>,
Pattern,
Handler
),
{step_registry, Updated}.
-file("src/dream_test/gherkin/steps.gleam", 204).
?DOC(
" Register a Then step definition.\n"
"\n"
" Then steps describe expected outcomes or assertions.\n"
"\n"
" Note: Named `then_` with trailing underscore because `then` is reserved.\n"
"\n"
" ## Parameters\n"
"\n"
" - `registry`: The registry to add to\n"
" - `pattern`: Step pattern with placeholders\n"
" - `handler`: Handler function to execute\n"
"\n"
" ## Returns\n"
"\n"
" A new registry containing the added step.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let registry = new() |> then_(\"I should have {int} items\", step_pass)\n"
" ```\n"
).
-spec then_(
step_registry(),
binary(),
fun((step_context()) -> {ok, dream_test@types:assertion_result()} |
{error, binary()})
) -> step_registry().
then_(Registry, Pattern, Handler) ->
Updated = dream_test@gherkin@step_trie:insert(
erlang:element(2, Registry),
<<"Then"/utf8>>,
Pattern,
Handler
),
{step_registry, Updated}.
-file("src/dream_test/gherkin/steps.gleam", 237).
?DOC(
" Register a step that matches any keyword.\n"
"\n"
" Use this for steps that work regardless of Given/When/Then context.\n"
"\n"
" ## Parameters\n"
"\n"
" - `registry`: The registry to add to\n"
" - `pattern`: Step pattern with placeholders\n"
" - `handler`: Handler function to execute\n"
"\n"
" ## Returns\n"
"\n"
" A new registry containing the added step.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let steps =\n"
" new()\n"
" |> step(\"I have {int} items in my cart\", step_have_items)\n"
" |> step(\"I add {int} more items\", step_add_items)\n"
" |> step(\"I should have {int} items total\", step_should_have)\n"
" ```\n"
).
-spec step(
step_registry(),
binary(),
fun((step_context()) -> {ok, dream_test@types:assertion_result()} |
{error, binary()})
) -> step_registry().
step(Registry, Pattern, Handler) ->
Updated = dream_test@gherkin@step_trie:insert(
erlang:element(2, Registry),
<<"*"/utf8>>,
Pattern,
Handler
),
{step_registry, Updated}.
-file("src/dream_test/gherkin/steps.gleam", 312).
-spec keyword_to_string(dream_test@gherkin@types:step_keyword()) -> binary().
keyword_to_string(Keyword) ->
case Keyword of
given ->
<<"Given"/utf8>>;
'when' ->
<<"When"/utf8>>;
then ->
<<"Then"/utf8>>;
'and' ->
<<"And"/utf8>>;
but ->
<<"But"/utf8>>
end.
-file("src/dream_test/gherkin/steps.gleam", 300).
?DOC(
" Find a matching step definition.\n"
"\n"
" Searches the registry for a handler matching the given keyword and text.\n"
" Returns the handler and captured values on success, or an error message.\n"
"\n"
" ## Performance\n"
"\n"
" Lookup cost scales with the number of **tokens** in the step text after\n"
" tokenization (the same tokenization used by the step trie).\n"
"\n"
" Concretely: this is the **total number of tokens** in the input step text,\n"
" not the number of *unique* words.\n"
"\n"
" In practice:\n"
" - Tokens are usually “words” separated by spaces.\n"
" - Quoted strings are treated as a single token.\n"
" - Some punctuation/number boundaries are split so patterns like `{int}%` or\n"
" `${float}USD` can match predictably.\n"
"\n"
" Examples:\n"
" - `\"I add 2 items\"` → 4 tokens (`[\"I\", \"add\", \"2\", \"items\"]`)\n"
" - `\"the message is \\\"hello world\\\"\"` → 4 tokens (`[\"the\", \"message\", \"is\", \"\\\"hello world\\\"\"]`)\n"
"\n"
" This does **not** scale with the number of registered steps: you can register\n"
" hundreds of steps and lookup still stays proportional to the tokenized input.\n"
"\n"
" ## Parameters\n"
"\n"
" - `registry`: The registry to search\n"
" - `keyword`: Step keyword (Given, When, Then, And, But)\n"
" - `text`: Step text to match\n"
"\n"
" ## Returns\n"
"\n"
" - `Ok(StepMatch)`: Contains matched handler and captured values\n"
" - `Error(String)`: Error message if no match found\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let registry = new() |> given(\"I have {int} items\", step_pass)\n"
"\n"
" use matched <- result.try(find_step(registry, Given, \"I have 3 items\"))\n"
"\n"
" capture_count(matched.captures)\n"
" |> should\n"
" |> be_equal(1)\n"
" |> or_fail_with(\"expected exactly one capture\")\n"
" ```\n"
).
-spec find_step(
step_registry(),
dream_test@gherkin@types:step_keyword(),
binary()
) -> {ok,
dream_test@gherkin@step_trie:step_match(fun((step_context()) -> {ok,
dream_test@types:assertion_result()} |
{error, binary()}))} |
{error, binary()}.
find_step(Registry, Keyword, Text) ->
Keyword_str = keyword_to_string(Keyword),
case dream_test@gherkin@step_trie:lookup(
erlang:element(2, Registry),
Keyword_str,
Text
) of
{some, Match} ->
{ok, Match};
none ->
{error,
<<<<<<"Undefined step: "/utf8, Keyword_str/binary>>/binary,
" "/utf8>>/binary,
Text/binary>>}
end.
-file("src/dream_test/gherkin/steps.gleam", 469).
?DOC(
" Get the number of captures.\n"
"\n"
" Returns the total number of values captured from placeholders.\n"
"\n"
" ## Parameters\n"
"\n"
" - `captures`: List of captured values from StepContext\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" capture_count(matched.captures)\n"
" |> should\n"
" |> be_equal(1)\n"
" |> or_fail_with(\"expected exactly one capture\")\n"
" ```\n"
).
-spec capture_count(list(dream_test@gherkin@step_trie:captured_value())) -> integer().
capture_count(Captures) ->
erlang:length(Captures).
-file("src/dream_test/gherkin/steps.gleam", 481).
-spec list_at_loop(list(GLB), integer(), integer()) -> gleam@option:option(GLB).
list_at_loop(Items, Target, Current) ->
case Items of
[] ->
none;
[Head | Tail] ->
case Current =:= Target of
true ->
{some, Head};
false ->
list_at_loop(Tail, Target, Current + 1)
end
end.
-file("src/dream_test/gherkin/steps.gleam", 477).
-spec list_at(list(GKY), integer()) -> gleam@option:option(GKY).
list_at(Items, Index) ->
list_at_loop(Items, Index, 0).
-file("src/dream_test/gherkin/steps.gleam", 346).
?DOC(
" Get an Int capture by index (0-based).\n"
"\n"
" Returns the captured integer value at the given index, or an error\n"
" if the index is out of bounds or the value isn't an integer.\n"
"\n"
" ## Parameters\n"
"\n"
" - `captures`: List of captured values from StepContext\n"
" - `index`: 0-based index of the capture to retrieve\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" fn step_int(context: StepContext) {\n"
" let value = get_int(context.captures, 0) |> result.unwrap(0)\n"
" put(context.world, \"int\", value)\n"
" Ok(succeed())\n"
" }\n"
" ```\n"
).
-spec get_int(list(dream_test@gherkin@step_trie:captured_value()), integer()) -> {ok,
integer()} |
{error, binary()}.
get_int(Captures, Index) ->
case list_at(Captures, Index) of
{some, {captured_int, Value}} ->
{ok, Value};
{some, _} ->
{error, <<"Capture at index is not an integer"/utf8>>};
none ->
{error, <<"No capture at index"/utf8>>}
end.
-file("src/dream_test/gherkin/steps.gleam", 377).
?DOC(
" Get a Float capture by index (0-based).\n"
"\n"
" Returns the captured float value at the given index, or an error\n"
" if the index is out of bounds or the value isn't a float.\n"
"\n"
" ## Parameters\n"
"\n"
" - `captures`: List of captured values from StepContext\n"
" - `index`: 0-based index of the capture to retrieve\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" fn step_float(context: StepContext) {\n"
" let value = get_float(context.captures, 0) |> result.unwrap(0.0)\n"
" put(context.world, \"float\", value)\n"
" Ok(succeed())\n"
" }\n"
" ```\n"
).
-spec get_float(list(dream_test@gherkin@step_trie:captured_value()), integer()) -> {ok,
float()} |
{error, binary()}.
get_float(Captures, Index) ->
case list_at(Captures, Index) of
{some, {captured_float, Value}} ->
{ok, Value};
{some, _} ->
{error, <<"Capture at index is not a float"/utf8>>};
none ->
{error, <<"No capture at index"/utf8>>}
end.
-file("src/dream_test/gherkin/steps.gleam", 408).
?DOC(
" Get a String capture by index (0-based).\n"
"\n"
" Returns the captured string value at the given index. Works for\n"
" both {string} (quoted) and {word} captures.\n"
"\n"
" ## Parameters\n"
"\n"
" - `captures`: List of captured values from StepContext\n"
" - `index`: 0-based index of the capture to retrieve\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" fn step_string(context: StepContext) {\n"
" let value = get_string(context.captures, 0) |> result.unwrap(\"\")\n"
" put(context.world, \"string\", value)\n"
" Ok(succeed())\n"
" }\n"
" ```\n"
).
-spec get_string(list(dream_test@gherkin@step_trie:captured_value()), integer()) -> {ok,
binary()} |
{error, binary()}.
get_string(Captures, Index) ->
case list_at(Captures, Index) of
{some, {captured_string, Value}} ->
{ok, Value};
{some, {captured_word, Value@1}} ->
{ok, Value@1};
{some, _} ->
{error, <<"Capture at index is not a string"/utf8>>};
none ->
{error, <<"No capture at index"/utf8>>}
end.
-file("src/dream_test/gherkin/steps.gleam", 440).
?DOC(
" Get a word capture by index (0-based).\n"
"\n"
" Returns the captured word value at the given index. Works for\n"
" both {word} and {} (anonymous) captures.\n"
"\n"
" ## Parameters\n"
"\n"
" - `captures`: List of captured values from StepContext\n"
" - `index`: 0-based index of the capture to retrieve\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" fn step_word(context: StepContext) {\n"
" let value = get_word(context.captures, 0) |> result.unwrap(\"\")\n"
" put(context.world, \"word\", value)\n"
" Ok(succeed())\n"
" }\n"
" ```\n"
).
-spec get_word(list(dream_test@gherkin@step_trie:captured_value()), integer()) -> {ok,
binary()} |
{error, binary()}.
get_word(Captures, Index) ->
case list_at(Captures, Index) of
{some, {captured_word, Value}} ->
{ok, Value};
{some, {captured_string, Value@1}} ->
{ok, Value@1};
{some, _} ->
{error, <<"Capture at index is not a word"/utf8>>};
none ->
{error, <<"No capture at index"/utf8>>}
end.