# Path: pytest_bdd/parser.py
# class Feature:
#     """Feature."""
# 
#     def __init__(
#         self,
#         scenarios: OrderedDict,
#         filename: str,
#         rel_filename: str,
#         name: str | None,
#         tags: set,
#         background: Background | None,
#         line_number: int,
#         description: str,
#     ) -> None:
#         self.scenarios: dict[str, ScenarioTemplate] = scenarios
#         self.rel_filename: str = rel_filename
#         self.filename: str = filename
#         self.tags: set = tags
#         self.name: str | None = name
#         self.line_number: int = line_number
#         self.description: str = description
#         self.background: Background | None = background
# 
# def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> Feature:
#     """Parse the feature file.
# 
#     :param str basedir: Feature files base directory.
#     :param str filename: Relative path to the feature file.
#     :param str encoding: Feature file encoding (utf-8 by default).
#     """
#     abs_filename = os.path.abspath(os.path.join(basedir, filename))
#     rel_filename = os.path.join(os.path.basename(basedir), filename)
#     feature = Feature(
#         scenarios=OrderedDict(),
#         filename=abs_filename,
#         rel_filename=rel_filename,
#         line_number=1,
#         name=None,
#         tags=set(),
#         background=None,
#         description="",
#     )
#     scenario: ScenarioTemplate | None = None
#     mode: str | None = None
#     prev_mode = None
#     description: list[str] = []
#     step = None
#     multiline_step = False
#     prev_line = None
# 
#     with open(abs_filename, encoding=encoding) as f:
#         content = f.read()
# 
#     for line_number, line in enumerate(content.splitlines(), start=1):
#         unindented_line = line.lstrip()
#         line_indent = len(line) - len(unindented_line)
#         if step and (step.indent < line_indent or ((not unindented_line) and multiline_step)):
#             multiline_step = True
#             # multiline step, so just add line and continue
#             step.add_line(line)
#             continue
#         else:
#             step = None
#             multiline_step = False
#         stripped_line = line.strip()
#         clean_line = strip_comments(line)
#         if not clean_line and (not prev_mode or prev_mode not in types.FEATURE):
#             continue
#         mode = get_step_type(clean_line) or mode
# 
#         allowed_prev_mode = (types.BACKGROUND, types.GIVEN, types.WHEN)
# 
#         if not scenario and prev_mode not in allowed_prev_mode and mode in types.STEP_TYPES:
#             raise exceptions.FeatureError(
#                 "Step definition outside of a Scenario or a Background", line_number, clean_line, filename
#             )
# 
#         if mode == types.FEATURE:
#             if prev_mode is None or prev_mode == types.TAG:
#                 _, feature.name = parse_line(clean_line)
#                 feature.line_number = line_number
#                 feature.tags = get_tags(prev_line)
#             elif prev_mode == types.FEATURE:
#                 description.append(clean_line)
#             else:
#                 raise exceptions.FeatureError(
#                     "Multiple features are not allowed in a single feature file",
#                     line_number,
#                     clean_line,
#                     filename,
#                 )
# 
#         prev_mode = mode
# 
#         # Remove Feature, Given, When, Then, And
#         keyword, parsed_line = parse_line(clean_line)
#         if mode in [types.SCENARIO, types.SCENARIO_OUTLINE]:
#             tags = get_tags(prev_line)
#             feature.scenarios[parsed_line] = scenario = ScenarioTemplate(
#                 feature=feature, name=parsed_line, line_number=line_number, tags=tags
#             )
#         elif mode == types.BACKGROUND:
#             feature.background = Background(feature=feature, line_number=line_number)
#         elif mode == types.EXAMPLES:
#             mode = types.EXAMPLES_HEADERS
#             scenario.examples.line_number = line_number
#         elif mode == types.EXAMPLES_HEADERS:
#             scenario.examples.set_param_names([l for l in split_line(parsed_line) if l])
#             mode = types.EXAMPLE_LINE
#         elif mode == types.EXAMPLE_LINE:
#             scenario.examples.add_example([l for l in split_line(stripped_line)])
#         elif mode and mode not in (types.FEATURE, types.TAG):
#             step = Step(name=parsed_line, type=mode, indent=line_indent, line_number=line_number, keyword=keyword)
#             if feature.background and not scenario:
#                 feature.background.add_step(step)
#             else:
#                 scenario = cast(ScenarioTemplate, scenario)
#                 scenario.add_step(step)
#         prev_line = clean_line
# 
#     feature.description = "\n".join(description).strip()
#     return feature


import os.path
import glob2
from .parser import Feature, parse_feature

The way of describing the behavior is based on Gherkin language, but a very
limited version. It doesn't support any parameter tables.
If the parametrization is needed to generate more test cases it can be done
on the fixture level of the pytest.
The <variable> syntax can be used here to make a connection between steps and
it will also validate the parameters mentioned in the steps with ones
provided in the pytest parametrization table.

Syntax example:

    Feature: Articles
        Scenario: Publishing the article
            Given I'm an author user
            And I have an article
            When I go to the article page
            And I press the publish button
            Then I should not see the error message
            And the article should be published  # Note: will query the database

:note: The "#" symbol is used for comments.
:note: There are no multiline steps, the description of the step must fit in
one line.
"""
from __future__ import annotations




# Global features dictionary

# Path: pytest_bdd/parser.py
# class Feature:
#     """Feature."""
# 
#     def __init__(
#         self,
#         scenarios: OrderedDict,
#         filename: str,
#         rel_filename: str,
#         name: str | None,
#         tags: set,
#         background: Background | None,
#         line_number: int,
#         description: str,
#     ) -> None:
#         self.scenarios: dict[str, ScenarioTemplate] = scenarios
#         self.rel_filename: str = rel_filename
#         self.filename: str = filename
#         self.tags: set = tags
#         self.name: str | None = name
#         self.line_number: int = line_number
#         self.description: str = description
#         self.background: Background | None = background
# 
# def parse_feature(basedir: str, filename: str, encoding: str = "utf-8") -> Feature:
#     """Parse the feature file.
# 
#     :param str basedir: Feature files base directory.
#     :param str filename: Relative path to the feature file.
#     :param str encoding: Feature file encoding (utf-8 by default).
#     """
#     abs_filename = os.path.abspath(os.path.join(basedir, filename))
#     rel_filename = os.path.join(os.path.basename(basedir), filename)
#     feature = Feature(
#         scenarios=OrderedDict(),
#         filename=abs_filename,
#         rel_filename=rel_filename,
#         line_number=1,
#         name=None,
#         tags=set(),
#         background=None,
#         description="",
#     )
#     scenario: ScenarioTemplate | None = None
#     mode: str | None = None
#     prev_mode = None
#     description: list[str] = []
#     step = None
#     multiline_step = False
#     prev_line = None
# 
#     with open(abs_filename, encoding=encoding) as f:
#         content = f.read()
# 
#     for line_number, line in enumerate(content.splitlines(), start=1):
#         unindented_line = line.lstrip()
#         line_indent = len(line) - len(unindented_line)
#         if step and (step.indent < line_indent or ((not unindented_line) and multiline_step)):
#             multiline_step = True
#             # multiline step, so just add line and continue
#             step.add_line(line)
#             continue
#         else:
#             step = None
#             multiline_step = False
#         stripped_line = line.strip()
#         clean_line = strip_comments(line)
#         if not clean_line and (not prev_mode or prev_mode not in types.FEATURE):
#             continue
#         mode = get_step_type(clean_line) or mode
# 
#         allowed_prev_mode = (types.BACKGROUND, types.GIVEN, types.WHEN)
# 
#         if not scenario and prev_mode not in allowed_prev_mode and mode in types.STEP_TYPES:
#             raise exceptions.FeatureError(
#                 "Step definition outside of a Scenario or a Background", line_number, clean_line, filename
#             )
# 
#         if mode == types.FEATURE:
#             if prev_mode is None or prev_mode == types.TAG:
#                 _, feature.name = parse_line(clean_line)
#                 feature.line_number = line_number
#                 feature.tags = get_tags(prev_line)
#             elif prev_mode == types.FEATURE:
#                 description.append(clean_line)
#             else:
#                 raise exceptions.FeatureError(
#                     "Multiple features are not allowed in a single feature file",
#                     line_number,
#                     clean_line,
#                     filename,
#                 )
# 
#         prev_mode = mode
# 
#         # Remove Feature, Given, When, Then, And
#         keyword, parsed_line = parse_line(clean_line)
#         if mode in [types.SCENARIO, types.SCENARIO_OUTLINE]:
#             tags = get_tags(prev_line)
#             feature.scenarios[parsed_line] = scenario = ScenarioTemplate(
#                 feature=feature, name=parsed_line, line_number=line_number, tags=tags
#             )
#         elif mode == types.BACKGROUND:
#             feature.background = Background(feature=feature, line_number=line_number)
#         elif mode == types.EXAMPLES:
#             mode = types.EXAMPLES_HEADERS
#             scenario.examples.line_number = line_number
#         elif mode == types.EXAMPLES_HEADERS:
#             scenario.examples.set_param_names([l for l in split_line(parsed_line) if l])
#             mode = types.EXAMPLE_LINE
#         elif mode == types.EXAMPLE_LINE:
#             scenario.examples.add_example([l for l in split_line(stripped_line)])
#         elif mode and mode not in (types.FEATURE, types.TAG):
#             step = Step(name=parsed_line, type=mode, indent=line_indent, line_number=line_number, keyword=keyword)
#             if feature.background and not scenario:
#                 feature.background.add_step(step)
#             else:
#                 scenario = cast(ScenarioTemplate, scenario)
#                 scenario.add_step(step)
#         prev_line = clean_line
# 
#     feature.description = "\n".join(description).strip()
#     return feature


# Path: pytest_bdd/feature.py
import os.path
import glob2
from .parser import Feature, parse_feature


The way of describing the behavior is based on Gherkin language, but a very
limited version. It doesn't support any parameter tables.
If the parametrization is needed to generate more test cases it can be done
on the fixture level of the pytest.
The <variable> syntax can be used here to make a connection between steps and
it will also validate the parameters mentioned in the steps with ones
provided in the pytest parametrization table.

Syntax example:

    Feature: Articles
        Scenario: Publishing the article
            Given I'm an author user
            And I have an article
            When I go to the article page
            And I press the publish button
            Then I should not see the error message
            And the article should be published  # Note: will query the database

:note: The "#" symbol is used for comments.
:note: There are no multiline steps, the description of the step must fit in
one line.
"""
from __future__ import annotations




# Global features dictionary

features: dict[str, Feature] = {}