-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.