Skip to content

Commit

Permalink
rewrite to support rules
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Hicks committed Feb 12, 2019
1 parent 074d7a4 commit e838907
Show file tree
Hide file tree
Showing 26 changed files with 477 additions and 509 deletions.
13 changes: 13 additions & 0 deletions lib/gherkin/elements/feature.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule Gherkin.Elements.Feature do
@moduledoc """
Representation of an entire feature. Contains rules and/or scenarios which are the primary focus of the feature.
"""
defstruct name: "",
description: "",
tags: [],
background_steps: [],
rules: [],
scenarios: [],
line: 0,
file: nil
end
5 changes: 5 additions & 0 deletions lib/gherkin/elements/line.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule Gherkin.Elements.Line do
@moduledoc false

defstruct raw_text: "", text: "", line_number: nil
end
11 changes: 11 additions & 0 deletions lib/gherkin/elements/rule.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Gherkin.Elements.Rule do
@moduledoc """
Represents a single rule within a feature. Contains scenarios which demonstrate the rule.
"""
defstruct name: "",
description: "",
background_steps: [],
scenarios: [],
line: 0,
tags: []
end
10 changes: 10 additions & 0 deletions lib/gherkin/elements/scenario.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
defmodule Gherkin.Elements.Scenario do
@moduledoc """
Represents a single scenario within a feature. Contains steps which are the primary focus of the scenario.
"""
defstruct name: "",
description: "",
tags: [],
steps: [],
line: 0
end
11 changes: 11 additions & 0 deletions lib/gherkin/elements/scenario_outline.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Gherkin.Elements.ScenarioOutline do
@moduledoc """
Represents an outline of a single scenario.
"""
defstruct name: "",
description: "",
tags: [],
steps: [],
examples: [],
line: 0
end
6 changes: 6 additions & 0 deletions lib/gherkin/elements/step.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
defmodule Gherkin.Elements.Step do
@moduledoc """
Represents an action to be executed as part of a scenario or background.
"""
defstruct(keyword: "", text: "", table_data: [], doc_string: "", line: 0)
end
53 changes: 3 additions & 50 deletions lib/gherkin/gherkin.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,53 +25,6 @@ defmodule Gherkin do
|> Gherkin.Parser.parse_feature(file_name)
end

defmodule Elements do
@moduledoc false
defmodule Feature do
@moduledoc """
Representation of an entire feature. Contains scenarios which are the primary focus of the feature.
"""
defstruct name: "",
description: "",
tags: [],
role: nil,
background_steps: [],
scenarios: [],
line: 0,
file: nil
end

defmodule Scenario do
@moduledoc """
Represents a single scenario within a feature. Contains steps which are the primary focus of the scenario.
"""
defstruct name: "",
tags: [],
steps: [],
line: 0
end

defmodule ScenarioOutline do
@moduledoc """
Represents an outline of a single scenario.
"""
defstruct name: "",
tags: [],
steps: [],
examples: [],
line: 0
end

defmodule Steps do
@moduledoc false
defmodule(Given, do: defstruct(text: "", table_data: [], doc_string: "", line: 0))
defmodule(When, do: defstruct(text: "", table_data: [], doc_string: "", line: 0))
defmodule(Then, do: defstruct(text: "", table_data: [], doc_string: "", line: 0))
defmodule(And, do: defstruct(text: "", table_data: [], doc_string: "", line: 0))
defmodule(But, do: defstruct(text: "", table_data: [], doc_string: "", line: 0))
end
end

@doc """
Changes a `Gherkin.Elements.ScenarioOutline` into multiple `Gherkin.Elements.Scenario`s
so that they may be executed in the same manner.
Expand All @@ -81,17 +34,17 @@ defmodule Gherkin do
outline = %Gherkin.Elements.ScenarioOutline{}
Gherkin.scenarios_for(outline) |> Enum.each(&run_scenario/1)
"""
def scenarios_for(%Elements.ScenarioOutline{
def scenarios_for(%Gherkin.Elements.ScenarioOutline{
name: name,
tags: tags,
steps: steps,
examples: examples,
line: line
}) do
} = scenario) do
examples
|> Enum.with_index(1)
|> Enum.map(fn {example, index} ->
%Elements.Scenario{
%Gherkin.Elements.Scenario{
name: name <> " (Example #{index})",
tags: tags,
line: line,
Expand Down
85 changes: 46 additions & 39 deletions lib/gherkin/parser.ex
Original file line number Diff line number Diff line change
@@ -1,67 +1,74 @@
defmodule Gherkin.Parser do
@moduledoc false
alias Gherkin.Elements.Feature, as: Feature
alias Gherkin.Parser.GenericLine, as: LineParser
alias Gherkin.Parsers.FeatureParser

@doc """
Parses a string that represents a Gherkin feature document into Elixir terms.
"""
def parse_feature(feature_text, file_name \\ nil) do
feature_text
|> process_lines()
|> parse_each_line(file_name)
|> correct_scenario_order()
|> transform_lines()
|> normalize_lines()
|> build_gherkin_document(file_name)
end

defp correct_scenario_order(feature = %{scenarios: scenarios}) do
%{feature | scenarios: Enum.reverse(scenarios)}
defp transform_lines(%File.Stream{line_or_bytes: :line} = file_stream) do
transform_all_lines(file_stream)
end

defp process_lines(%File.Stream{line_or_bytes: :line} = stream) do
{:ok, output} = Enum.reduce(stream, {:ok, []}, &process_line/2)

Enum.reverse(output)
defp transform_lines(string) do
string
|> String.split(~r/\r?\n/)
# If a string, rather than a file, was given strip leading empty lines to give a more intuitive line count.
|> Stream.drop_while(fn line -> line === "" end)
|> transform_all_lines()
end

defp process_lines(string) do
{:ok, output} =
string
|> String.split(~r/\r?\n/)
|> Enum.reduce({:ok, []}, &process_line/2)

Enum.reverse(output)
defp transform_all_lines(stream) do
stream
|> Stream.with_index(1)
|> Stream.map(&transform_line/1)
end

defp process_line(line, {state, lines}) do
process_line(String.trim_leading(line), {state, lines, line})

defp transform_line({raw_line, line_number}) do
%Gherkin.Elements.Line{raw_text: raw_line, line_number: line_number}
end

defp normalize_lines(lines) do
lines
|> Stream.map(&trim/1)
# Drop empty lines and comment lines as nothing will be done with them
|> Stream.filter(fn line -> line.text != "" and not String.starts_with?(line.text, "#") end)
|> Enum.reduce([], &normalize_line/2)
|> Enum.reverse()
end

# Multiline / Doc string processing
defp process_line(line = ~s(""") <> _, {:ok, lines, original_line}) do
indent_length =
String.length(original_line) - String.length(String.trim_leading(original_line))
defp trim(line) do
%{line | text: String.trim_leading(line.raw_text)}
end

{{:multiline, indent_length}, [line | lines]}
# Closing quotes for Doc String
defp normalize_line(%{text: ~s(""") <> _} = line, {{:multiline, _}, lines}) do
[line | lines]
end

defp process_line(line = ~s(""") <> _, {{:multiline, _}, lines, _}) do
{:ok, [line | lines]}
# Opening quotes for Doc String
defp normalize_line(%{text: ~s(""") <> _} = line, lines) do
indent_length = String.length(line.raw_text) - String.length(line.text)

{{:multiline, indent_length}, [line| lines]}
end

defp process_line(_, {{:multiline, indent} = state, lines, original_line}) do
{strippable, doc_string} = String.split_at(original_line, indent)
{state, [String.trim_leading(strippable) <> doc_string | lines]}
# Line between opening/closing quotes for Doc String
defp normalize_line(line, {{:multiline, indent} = multiline_state, lines}) do
{_, doc_string} = String.split_at(line.raw_text, indent)
{multiline_state, [%{line | text: doc_string} | lines]}
end

# Default processing
defp process_line(line, {:ok, lines, _}), do: {:ok, [line | lines]}

defp parse_each_line(lines, file) do
{feature, _end_state} =
lines
|> Enum.with_index(1)
|> Enum.reduce({%Feature{file: file}, :start}, &LineParser.process_line/2)
defp normalize_line(line, lines), do: [line| lines]

feature
defp build_gherkin_document(lines, file) do
FeatureParser.build_feature(%Gherkin.Elements.Feature{file: file}, lines)
end
end
143 changes: 0 additions & 143 deletions lib/gherkin/parser/generic_line.ex

This file was deleted.

0 comments on commit e838907

Please sign in to comment.