Current section
Files
Jump to
Current section
Files
src/dream_test@gherkin@parser.erl
-module(dream_test@gherkin@parser).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/dream_test/gherkin/parser.gleam").
-export([parse_string/1, parse_file/1]).
-export_type([parser_state/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(
" Parse Gherkin `.feature` files into `gherkin/types.Feature`.\n"
"\n"
" Use this when you have Gherkin text (from a file on disk, a fixture, or a\n"
" string literal) and you want a structured representation you can convert\n"
" into runnable tests via `dream_test/gherkin/feature`.\n"
"\n"
" The parser supports common Gherkin syntax:\n"
" Feature / Scenario / Scenario Outline, Background, tags (`@tag`), steps,\n"
" DocStrings (`\"\"\"`), DataTables (`| ... |`), Examples tables, and comments.\n"
).
-type parser_state() :: {parser_state,
gleam@option:option(binary()),
gleam@option:option(binary()),
list(binary()),
gleam@option:option(dream_test@gherkin@types:background()),
list(dream_test@gherkin@types:scenario()),
gleam@option:option(binary()),
list(binary()),
list(dream_test@gherkin@types:step()),
gleam@option:option(dream_test@gherkin@types:examples_table()),
list(binary()),
boolean(),
list(binary()),
gleam@option:option(binary()),
boolean(),
boolean()}.
-file("src/dream_test/gherkin/parser.gleam", 56).
-spec feature_with_source(dream_test@gherkin@types:feature(), binary()) -> dream_test@gherkin@types:feature().
feature_with_source(Feature, Source) ->
{feature, Name, _, Description, Tags, Background, Scenarios} = Feature,
{feature, Name, {some, Source}, Description, Tags, Background, Scenarios}.
-file("src/dream_test/gherkin/parser.gleam", 136).
-spec initial_state() -> parser_state().
initial_state() ->
{parser_state,
none,
none,
[],
none,
[],
none,
[],
[],
none,
[],
false,
[],
none,
false,
false}.
-file("src/dream_test/gherkin/parser.gleam", 196).
-spec close_doc_string(parser_state()) -> {ok, parser_state()} |
{error, binary()}.
close_doc_string(State) ->
Content = gleam@string:join(
lists:reverse(erlang:element(13, State)),
<<"\n"/utf8>>
),
Doc_string = {doc_string, Content, erlang:element(14, State)},
case erlang:element(9, State) of
[{step, Keyword, Text, _} | Rest] ->
Updated_step = {step, Keyword, Text, {some, Doc_string}},
{ok,
{parser_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
[Updated_step | Rest],
erlang:element(10, State),
erlang:element(11, State),
false,
[],
none,
erlang:element(15, State),
erlang:element(16, State)}};
[] ->
{error, <<"DocString without preceding step"/utf8>>}
end.
-file("src/dream_test/gherkin/parser.gleam", 219).
-spec append_doc_string_line(binary(), parser_state()) -> {ok, parser_state()} |
{error, binary()}.
append_doc_string_line(Line, State) ->
{ok,
{parser_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
erlang:element(9, State),
erlang:element(10, State),
erlang:element(11, State),
erlang:element(12, State),
[Line | erlang:element(13, State)],
erlang:element(14, State),
erlang:element(15, State),
erlang:element(16, State)}}.
-file("src/dream_test/gherkin/parser.gleam", 185).
-spec handle_doc_string_line(binary(), binary(), parser_state()) -> {ok,
parser_state()} |
{error, binary()}.
handle_doc_string_line(Trimmed, Original, State) ->
case gleam_stdlib:string_starts_with(Trimmed, <<"\"\"\""/utf8>>) of
true ->
close_doc_string(State);
false ->
append_doc_string_line(Original, State)
end.
-file("src/dream_test/gherkin/parser.gleam", 289).
-spec is_tag(binary()) -> boolean().
is_tag(Word) ->
gleam_stdlib:string_starts_with(Word, <<"@"/utf8>>).
-file("src/dream_test/gherkin/parser.gleam", 293).
-spec extract_tag_name(binary()) -> binary().
extract_tag_name(Tag) ->
gleam@string:drop_start(Tag, 1).
-file("src/dream_test/gherkin/parser.gleam", 282).
-spec extract_tags(binary()) -> list(binary()).
extract_tags(Line) ->
_pipe = Line,
_pipe@1 = gleam@string:split(_pipe, <<" "/utf8>>),
_pipe@2 = gleam@list:filter(_pipe@1, fun is_tag/1),
gleam@list:map(_pipe@2, fun extract_tag_name/1).
-file("src/dream_test/gherkin/parser.gleam", 277).
-spec parse_tags(binary(), parser_state()) -> {ok, parser_state()} |
{error, binary()}.
parse_tags(Line, State) ->
Tags = extract_tags(Line),
{ok,
{parser_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
erlang:element(9, State),
erlang:element(10, State),
lists:append(erlang:element(11, State), Tags),
erlang:element(12, State),
erlang:element(13, State),
erlang:element(14, State),
erlang:element(15, State),
erlang:element(16, State)}}.
-file("src/dream_test/gherkin/parser.gleam", 301).
-spec parse_feature_line(binary(), parser_state()) -> {ok, parser_state()} |
{error, binary()}.
parse_feature_line(Name, State) ->
{ok,
{parser_state,
{some, gleam@string:trim(Name)},
erlang:element(3, State),
erlang:element(11, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
erlang:element(9, State),
erlang:element(10, State),
[],
erlang:element(12, State),
erlang:element(13, State),
erlang:element(14, State),
erlang:element(15, State),
erlang:element(16, State)}}.
-file("src/dream_test/gherkin/parser.gleam", 319).
-spec start_background(parser_state()) -> {ok, parser_state()} |
{error, binary()}.
start_background(State) ->
{ok,
{parser_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
[],
erlang:element(10, State),
erlang:element(11, State),
erlang:element(12, State),
erlang:element(13, State),
erlang:element(14, State),
true,
erlang:element(16, State)}}.
-file("src/dream_test/gherkin/parser.gleam", 376).
-spec finalize_background(parser_state()) -> parser_state().
finalize_background(State) ->
case erlang:element(9, State) of
[] ->
State;
Steps ->
Background = {background, lists:reverse(Steps)},
{parser_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
{some, Background},
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
[],
erlang:element(10, State),
erlang:element(11, State),
erlang:element(12, State),
erlang:element(13, State),
erlang:element(14, State),
false,
erlang:element(16, State)}
end.
-file("src/dream_test/gherkin/parser.gleam", 417).
-spec build_scenario(
binary(),
list(binary()),
list(dream_test@gherkin@types:step()),
gleam@option:option(dream_test@gherkin@types:examples_table()),
boolean()
) -> dream_test@gherkin@types:scenario().
build_scenario(Name, Tags, Steps, Examples, Is_outline) ->
case {Is_outline, Examples} of
{true, {some, Ex}} ->
{scenario_outline, Name, Tags, Steps, Ex};
{true, none} ->
{scenario_outline,
Name,
Tags,
Steps,
dream_test@gherkin@types:empty_examples()};
{false, _} ->
{scenario, Name, Tags, Steps}
end.
-file("src/dream_test/gherkin/parser.gleam", 391).
-spec finalize_scenario(parser_state()) -> parser_state().
finalize_scenario(State) ->
case erlang:element(7, State) of
none ->
State;
{some, Name} ->
Steps = lists:reverse(erlang:element(9, State)),
Scenario = build_scenario(
Name,
erlang:element(8, State),
Steps,
erlang:element(10, State),
erlang:element(16, State)
),
{parser_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
[Scenario | erlang:element(6, State)],
none,
[],
[],
none,
erlang:element(11, State),
erlang:element(12, State),
erlang:element(13, State),
erlang:element(14, State),
erlang:element(15, State),
false}
end.
-file("src/dream_test/gherkin/parser.gleam", 369).
-spec finalize_current_scenario(parser_state()) -> parser_state().
finalize_current_scenario(State) ->
case erlang:element(15, State) of
true ->
finalize_background(State);
false ->
finalize_scenario(State)
end.
-file("src/dream_test/gherkin/parser.gleam", 327).
-spec start_scenario(binary(), parser_state()) -> {ok, parser_state()} |
{error, binary()}.
start_scenario(Name, State) ->
State_with_previous = finalize_current_scenario(State),
{ok,
{parser_state,
erlang:element(2, State_with_previous),
erlang:element(3, State_with_previous),
erlang:element(4, State_with_previous),
erlang:element(5, State_with_previous),
erlang:element(6, State_with_previous),
{some, gleam@string:trim(Name)},
erlang:element(11, State_with_previous),
[],
none,
[],
erlang:element(12, State_with_previous),
erlang:element(13, State_with_previous),
erlang:element(14, State_with_previous),
false,
false}}.
-file("src/dream_test/gherkin/parser.gleam", 348).
-spec start_scenario_outline(binary(), parser_state()) -> {ok, parser_state()} |
{error, binary()}.
start_scenario_outline(Name, State) ->
State_with_previous = finalize_current_scenario(State),
{ok,
{parser_state,
erlang:element(2, State_with_previous),
erlang:element(3, State_with_previous),
erlang:element(4, State_with_previous),
erlang:element(5, State_with_previous),
erlang:element(6, State_with_previous),
{some, gleam@string:trim(Name)},
erlang:element(11, State_with_previous),
[],
none,
[],
erlang:element(12, State_with_previous),
erlang:element(13, State_with_previous),
erlang:element(14, State_with_previous),
false,
true}}.
-file("src/dream_test/gherkin/parser.gleam", 442).
-spec parse_step(
dream_test@gherkin@types:step_keyword(),
binary(),
parser_state()
) -> {ok, parser_state()} | {error, binary()}.
parse_step(Keyword, Text, State) ->
Step = {step, Keyword, gleam@string:trim(Text), none},
{ok,
{parser_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
[Step | erlang:element(9, State)],
erlang:element(10, State),
erlang:element(11, State),
erlang:element(12, State),
erlang:element(13, State),
erlang:element(14, State),
erlang:element(15, State),
erlang:element(16, State)}}.
-file("src/dream_test/gherkin/parser.gleam", 455).
-spec start_doc_string(binary(), parser_state()) -> {ok, parser_state()} |
{error, binary()}.
start_doc_string(Type_hint, State) ->
Content_type = case gleam@string:trim(Type_hint) of
<<""/utf8>> ->
none;
T ->
{some, T}
end,
{ok,
{parser_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
erlang:element(9, State),
erlang:element(10, State),
erlang:element(11, State),
true,
[],
Content_type,
erlang:element(15, State),
erlang:element(16, State)}}.
-file("src/dream_test/gherkin/parser.gleam", 497).
-spec is_non_empty_cell(binary()) -> boolean().
is_non_empty_cell(Cell) ->
gleam@string:trim(Cell) /= <<""/utf8>>.
-file("src/dream_test/gherkin/parser.gleam", 490).
-spec parse_table_cells(binary()) -> list(binary()).
parse_table_cells(Line) ->
_pipe = Line,
_pipe@1 = gleam@string:split(_pipe, <<"|"/utf8>>),
_pipe@2 = gleam@list:filter(_pipe@1, fun is_non_empty_cell/1),
gleam@list:map(_pipe@2, fun gleam@string:trim/1).
-file("src/dream_test/gherkin/parser.gleam", 501).
-spec add_examples_row(
list(binary()),
dream_test@gherkin@types:examples_table(),
parser_state()
) -> {ok, parser_state()} | {error, binary()}.
add_examples_row(Cells, Examples, State) ->
Updated = case erlang:element(2, Examples) of
[] ->
{examples_table, Cells, []};
_ ->
{examples_table,
erlang:element(2, Examples),
lists:append(erlang:element(3, Examples), [Cells])}
end,
{ok,
{parser_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
erlang:element(9, State),
{some, Updated},
erlang:element(11, State),
erlang:element(12, State),
erlang:element(13, State),
erlang:element(14, State),
erlang:element(15, State),
erlang:element(16, State)}}.
-file("src/dream_test/gherkin/parser.gleam", 517).
-spec add_data_table_row(list(binary()), parser_state()) -> {ok, parser_state()} |
{error, binary()}.
add_data_table_row(Cells, State) ->
case erlang:element(9, State) of
[{step, Keyword, Text, Arg} | Rest] ->
Updated_arg = case Arg of
{some, {data_table, Rows}} ->
{data_table, lists:append(Rows, [Cells])};
{some, Other} ->
Other;
none ->
{data_table, [Cells]}
end,
Updated_step = {step, Keyword, Text, {some, Updated_arg}},
{ok,
{parser_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
[Updated_step | Rest],
erlang:element(10, State),
erlang:element(11, State),
erlang:element(12, State),
erlang:element(13, State),
erlang:element(14, State),
erlang:element(15, State),
erlang:element(16, State)}};
[] ->
{error, <<"DataTable without preceding step"/utf8>>}
end.
-file("src/dream_test/gherkin/parser.gleam", 477).
-spec parse_table_row(binary(), parser_state()) -> {ok, parser_state()} |
{error, binary()}.
parse_table_row(Line, State) ->
Cells = parse_table_cells(Line),
case erlang:element(10, State) of
{some, Ex} ->
add_examples_row(Cells, Ex, State);
none ->
add_data_table_row(Cells, State)
end.
-file("src/dream_test/gherkin/parser.gleam", 540).
-spec start_examples(parser_state()) -> {ok, parser_state()} | {error, binary()}.
start_examples(State) ->
{ok,
{parser_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
erlang:element(9, State),
{some, dream_test@gherkin@types:empty_examples()},
erlang:element(11, State),
erlang:element(12, State),
erlang:element(13, State),
erlang:element(14, State),
erlang:element(15, State),
erlang:element(16, State)}}.
-file("src/dream_test/gherkin/parser.gleam", 548).
-spec parse_description_or_unknown(binary(), parser_state()) -> {ok,
parser_state()} |
{error, binary()}.
parse_description_or_unknown(Line, State) ->
case {erlang:element(2, State), erlang:element(7, State)} of
{{some, _}, none} ->
Description = case erlang:element(3, State) of
none ->
Line;
{some, Existing} ->
<<<<Existing/binary, "\n"/utf8>>/binary, Line/binary>>
end,
{ok,
{parser_state,
erlang:element(2, State),
{some, Description},
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State),
erlang:element(8, State),
erlang:element(9, State),
erlang:element(10, State),
erlang:element(11, State),
erlang:element(12, State),
erlang:element(13, State),
erlang:element(14, State),
erlang:element(15, State),
erlang:element(16, State)}};
{_, _} ->
{ok, State}
end.
-file("src/dream_test/gherkin/parser.gleam", 228).
-spec handle_normal_line(binary(), parser_state()) -> {ok, parser_state()} |
{error, binary()}.
handle_normal_line(Trimmed, State) ->
case Trimmed of
<<""/utf8>> ->
{ok, State};
<<"#"/utf8, _/binary>> ->
{ok, State};
<<"@"/utf8, _/binary>> ->
parse_tags(Trimmed, State);
<<"Feature:"/utf8, Rest/binary>> ->
parse_feature_line(Rest, State);
<<"Background:"/utf8>> ->
start_background(State);
<<"Scenario:"/utf8, Rest@1/binary>> ->
start_scenario(Rest@1, State);
<<"Scenario Outline:"/utf8, Rest@2/binary>> ->
start_scenario_outline(Rest@2, State);
<<"Examples:"/utf8>> ->
start_examples(State);
<<"Given "/utf8, Rest@3/binary>> ->
parse_step(given, Rest@3, State);
<<"When "/utf8, Rest@4/binary>> ->
parse_step('when', Rest@4, State);
<<"Then "/utf8, Rest@5/binary>> ->
parse_step(then, Rest@5, State);
<<"And "/utf8, Rest@6/binary>> ->
parse_step('and', Rest@6, State);
<<"But "/utf8, Rest@7/binary>> ->
parse_step(but, Rest@7, State);
<<"\"\"\""/utf8, Type_hint/binary>> ->
start_doc_string(Type_hint, State);
<<"|"/utf8, _/binary>> ->
parse_table_row(Trimmed, State);
_ ->
parse_description_or_unknown(Trimmed, State)
end.
-file("src/dream_test/gherkin/parser.gleam", 175).
-spec parse_line(binary(), parser_state()) -> {ok, parser_state()} |
{error, binary()}.
parse_line(Line, State) ->
Trimmed = gleam@string:trim(Line),
case erlang:element(12, State) of
true ->
handle_doc_string_line(Trimmed, Line, State);
false ->
handle_normal_line(Trimmed, State)
end.
-file("src/dream_test/gherkin/parser.gleam", 570).
-spec finalize_feature(parser_state()) -> {ok,
dream_test@gherkin@types:feature()} |
{error, binary()}.
finalize_feature(State) ->
Final_state = finalize_current_scenario(State),
case erlang:element(2, Final_state) of
none ->
{error, <<"No Feature found in content"/utf8>>};
{some, Name} ->
{ok,
{feature,
Name,
none,
erlang:element(3, Final_state),
erlang:element(4, Final_state),
erlang:element(5, Final_state),
lists:reverse(erlang:element(6, Final_state))}}
end.
-file("src/dream_test/gherkin/parser.gleam", 160).
-spec parse_lines(list(binary()), parser_state()) -> {ok,
dream_test@gherkin@types:feature()} |
{error, binary()}.
parse_lines(Lines, State) ->
case Lines of
[] ->
finalize_feature(State);
[Line | Rest] ->
case parse_line(Line, State) of
{ok, New_state} ->
parse_lines(Rest, New_state);
{error, Msg} ->
{error, Msg}
end
end.
-file("src/dream_test/gherkin/parser.gleam", 106).
?DOC(
" Parse Gherkin content from a string.\n"
"\n"
" Parses the provided string as Gherkin syntax.\n"
"\n"
" ## Parameters\n"
"\n"
" - `content`: Gherkin content as a string\n"
"\n"
" ## Returns\n"
"\n"
" - `Ok(Feature)`: Successfully parsed feature\n"
" - `Error(String)`: Parse error with description\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let content =\n"
" \"@smoke\\n\"\n"
" <> \"Feature: Demo\\n\"\n"
" <> \"\\n\"\n"
" <> \" Scenario: One\\n\"\n"
" <> \" Given a thing\\n\"\n"
"\n"
" use feature <- result.try(parser.parse_string(content))\n"
"\n"
" feature.name\n"
" |> should\n"
" |> be_equal(\"Demo\")\n"
" |> or_fail_with(\"expected feature name Demo\")\n"
" ```\n"
).
-spec parse_string(binary()) -> {ok, dream_test@gherkin@types:feature()} |
{error, binary()}.
parse_string(Content) ->
Lines = gleam@string:split(Content, <<"\n"/utf8>>),
State = initial_state(),
parse_lines(Lines, State).
-file("src/dream_test/gherkin/parser.gleam", 44).
?DOC(
" Parse a `.feature` file from disk.\n"
"\n"
" Reads the file and parses its contents into a Feature.\n"
"\n"
" ## Parameters\n"
"\n"
" - `path`: Path to the `.feature` file\n"
"\n"
" ## Returns\n"
"\n"
" - `Ok(Feature)`: Successfully parsed feature\n"
" - `Error(String)`: Parse error with description\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let assert Ok(feature) = parser.parse_file(\"test/cart.feature\")\n"
" ```\n"
).
-spec parse_file(binary()) -> {ok, dream_test@gherkin@types:feature()} |
{error, binary()}.
parse_file(Path) ->
case dream_test_file_ffi:read_file(Path) of
{ok, Content} ->
case parse_string(Content) of
{ok, Feature} ->
{ok, feature_with_source(Feature, Path)};
{error, Message} ->
{error, Message}
end;
{error, Error} ->
{error,
<<"Failed to read feature file: "/utf8,
(dream_test@file:error_to_string(Error))/binary>>}
end.