defmodule Gherkin.Feature do @moduledoc """ Represents a parsed Gherkin feature file (minimal subset). A Feature is the top-level element in a Gherkin file, containing a name, optional description, optional background, scenarios, and rules. It can also have tags that apply to all scenarios in the feature. `tag_lines` records each tag's own source line (parallel to `tags`) and `comments` collects every comment line in the file as `{line, text}` — both for Cucumber Messages locations; the published `tags` shape is unchanged. """ defstruct name: "", description: "", background: nil, scenarios: [], rules: [], tags: [], line: nil, tag_lines: [], comments: [] @type t :: %__MODULE__{ name: String.t(), description: String.t(), background: Gherkin.Background.t() | nil, scenarios: [Gherkin.Scenario.t() | Gherkin.ScenarioOutline.t()], rules: [Gherkin.Rule.t()], tags: [String.t()], line: non_neg_integer() | nil, tag_lines: [non_neg_integer()], comments: [{non_neg_integer(), String.t()}] } end defmodule Gherkin.Rule do @moduledoc """ Represents a Gherkin Rule section. A Rule groups related scenarios under a feature to express a business rule. It can have its own description, tags, and Background; rule-background steps run after the feature-background steps for each scenario in the rule, and rule tags are inherited by those scenarios. """ defstruct name: "", description: "", background: nil, scenarios: [], tags: [], line: nil, tag_lines: [] @type t :: %__MODULE__{ name: String.t(), description: String.t(), background: Gherkin.Background.t() | nil, scenarios: [Gherkin.Scenario.t() | Gherkin.ScenarioOutline.t()], tags: [String.t()], line: non_neg_integer() | nil, tag_lines: [non_neg_integer()] } end defmodule Gherkin.Background do @moduledoc """ Represents a Gherkin Background section. A Background contains steps that are run before each scenario in the feature. It allows you to define common setup steps that apply to all scenarios. """ defstruct name: "", steps: [], description: "", line: nil @type t :: %__MODULE__{ name: String.t(), steps: [Gherkin.Step.t()], description: String.t(), line: non_neg_integer() | nil } end defmodule Gherkin.Scenario do @moduledoc """ Represents a Gherkin Scenario section. A Scenario is a concrete example that illustrates a business rule. It consists of a name, an optional free-form description, a list of steps, optional tags for filtering, and the line number where it appears in the source file. Rule provenance for scenarios defined inside a `Rule` lives on `Gherkin.Pickle.rule_name`. """ defstruct name: "", description: "", steps: [], tags: [], line: nil, keyword: "Scenario", tag_lines: [] @type t :: %__MODULE__{ name: String.t(), description: String.t(), steps: [Gherkin.Step.t()], tags: [String.t()], line: non_neg_integer() | nil, keyword: String.t(), tag_lines: [non_neg_integer()] } end defmodule Gherkin.ScenarioOutline do @moduledoc """ Represents a Gherkin Scenario Outline section. A Scenario Outline is a template that runs multiple times with different data from Examples tables. Placeholders in step text use `` syntax and are substituted with values from each row of the Examples table. """ defstruct name: "", description: "", steps: [], tags: [], examples: [], line: nil, keyword: "Scenario Outline", tag_lines: [] @type t :: %__MODULE__{ name: String.t(), description: String.t(), steps: [Gherkin.Step.t()], tags: [String.t()], examples: [Gherkin.Examples.t()], line: non_neg_integer() | nil, keyword: String.t(), tag_lines: [non_neg_integer()] } end defmodule Gherkin.Examples do @moduledoc """ Represents an Examples block within a Scenario Outline. Each Examples block contains a table of data used to parameterize the outline. The first row contains headers (placeholder names), and subsequent rows contain values to substitute. Examples blocks can have optional names, descriptions, and tags. """ defstruct name: "", description: "", tags: [], table_header: [], table_body: [], line: nil, keyword: "Examples", table_header_line: nil, table_body_lines: nil, tag_lines: [] @type t :: %__MODULE__{ name: String.t(), description: String.t(), tags: [String.t()], table_header: [String.t()], table_body: [[String.t()]], line: non_neg_integer() | nil, keyword: String.t(), table_header_line: non_neg_integer() | nil, table_body_lines: [non_neg_integer()] | nil, tag_lines: [non_neg_integer()] } end defmodule Gherkin.Step do @moduledoc """ Represents a Gherkin step (Given/When/Then/And/But/*). A Step is a single action or assertion in a scenario. It consists of: - keyword: The step type (Given, When, Then, And, But, or *) - text: The step text that matches step definitions - docstring: Optional multi-line text block (delimited by `\"\"\"` or triple backticks) - docstring_media_type: Optional media type annotation on the opening docstring delimiter (e.g. `json` in `\"\"\"json`) - datatable: Optional table data (pipe-delimited) - line: Line number in the source file `docstring_line`, `docstring_delimiter`, and `datatable_lines` record the source line of the docstring's opening delimiter, the delimiter style used (`\"\"\"` or triple backticks), and each datatable row's line (parallel to `datatable`) for Cucumber Messages; the published `docstring` and `datatable` shapes are unchanged. """ defstruct keyword: "", text: "", docstring: nil, docstring_media_type: nil, datatable: nil, line: nil, docstring_line: nil, docstring_delimiter: nil, datatable_lines: nil @type t :: %__MODULE__{ keyword: String.t(), text: String.t(), docstring: String.t() | nil, docstring_media_type: String.t() | nil, datatable: [[String.t()]] | nil, line: non_neg_integer() | nil, docstring_line: non_neg_integer() | nil, docstring_delimiter: String.t() | nil, datatable_lines: [non_neg_integer()] | nil } end defmodule Gherkin.Parser do @moduledoc """ Gherkin parser using NimbleParsec. This module parses Gherkin feature files into Elixir structs, supporting: - Feature with name, description, and tags - Background with steps - Scenarios with steps and tags - Scenario Outlines with Examples - Steps with keywords, text, docstrings, and datatables It implements a subset of the Gherkin language focused on core BDD concepts. """ @doc """ Parses a Gherkin feature file from a string into structured data. This function takes a string containing Gherkin syntax and parses it into a structured `Gherkin.Feature` struct with its associated components. ## Parameters * `gherkin_string` - A string containing Gherkin syntax ## Returns Returns a `%Gherkin.Feature{}` struct containing: * `name` - The feature name * `description` - The feature description * `tags` - List of feature-level tags * `background` - Background steps (if present) * `scenarios` - List of scenarios ## Examples # Parse a string containing Gherkin syntax Gherkin.Parser.parse("Feature: Shopping Cart\\nScenario: Adding an item") # Returns %Gherkin.Feature{} struct with parsed data """ @spec parse(String.t()) :: Gherkin.Feature.t() defdelegate parse(gherkin_string), to: Gherkin.NimbleParser @doc """ Parses feature file content, choosing the parser by the file's path. `.feature.md` files parse as Markdown with Gherkin (see `Gherkin.Markdown`); everything else parses as plain Gherkin, exactly like `parse/1`. ## Examples Gherkin.Parser.parse("# Feature: Cheese", "cheese.feature.md") # Returns %Gherkin.Feature{} struct with parsed data """ @spec parse(String.t(), String.t()) :: Gherkin.Feature.t() def parse(content, path) do if Gherkin.Markdown.markdown_path?(path) do Gherkin.Markdown.parse(content) else Gherkin.NimbleParser.parse(content) end end end