# 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
: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] = {}


def get_feature(base_path: str, filename: str, encoding: str = "utf-8") -> Feature:
    """Get a feature by the filename.

    :param str base_path: Base feature directory.
    :param str filename: Filename of the feature file.
    :param str encoding: Feature file encoding.

    :return: `Feature` instance from the parsed feature cache.

    :note: The features are parsed on the execution of the test and
           stored in the global variable cache to improve the performance
           when multiple scenarios are referencing the same file.
    """

    full_name = os.path.abspath(os.path.join(base_path, filename))
    feature = features.get(full_name)
    if not feature:

# 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

: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] = {}


def get_feature(base_path: str, filename: str, encoding: str = "utf-8") -> Feature:
    """Get a feature by the filename.

    :param str base_path: Base feature directory.
    :param str filename: Filename of the feature file.
    :param str encoding: Feature file encoding.

    :return: `Feature` instance from the parsed feature cache.

    :note: The features are parsed on the execution of the test and
           stored in the global variable cache to improve the performance
           when multiple scenarios are referencing the same file.
    """

    full_name = os.path.abspath(os.path.join(base_path, filename))
    feature = features.get(full_name)
    if not feature:

        feature = parse_feature(base_path, filename, encoding=encoding)