Initial commit: Requirements to Gherkin CLI Converter

This commit is contained in:
Bot
2026-02-02 12:15:36 +00:00
commit ec3ea3da33
29 changed files with 2803 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
"""Exporters module for BDD framework output."""
from nl2gherkin.exporters.base import BaseExporter
from nl2gherkin.exporters.behave import BehaveExporter
from nl2gherkin.exporters.cucumber import CucumberExporter
from nl2gherkin.exporters.pytest_bdd import PytestBDDExporter
__all__ = [
"BaseExporter",
"CucumberExporter",
"BehaveExporter",
"PytestBDDExporter",
]

View File

@@ -0,0 +1,59 @@
"""Base exporter class for BDD frameworks."""
from abc import ABC, abstractmethod
from typing import Dict, List
class BaseExporter(ABC):
"""Base class for BDD framework exporters."""
@abstractmethod
def export(self, features: List[str]) -> str:
"""Export features to the target framework format.
Args:
features: List of Gherkin feature strings.
Returns:
Exported content string.
"""
pass
@abstractmethod
def get_step_definitions_template(self) -> str:
"""Get step definitions template for this framework.
Returns:
Step definitions template string.
"""
pass
@abstractmethod
def get_configuration_template(self) -> Dict[str, str]:
"""Get configuration files for this framework.
Returns:
Dictionary mapping filenames to content templates.
"""
pass
def _extract_scenarios(self, feature: str) -> List[str]:
"""Extract individual scenarios from a feature string."""
scenarios: List[str] = []
current_scenario: List[str] = []
in_scenario = False
for line in feature.split("\n"):
stripped = line.strip()
if stripped.startswith("Scenario:") or stripped.startswith("Scenario Outline:"):
if current_scenario:
scenarios.append("\n".join(current_scenario))
current_scenario = [line]
in_scenario = True
elif in_scenario:
current_scenario.append(line)
if current_scenario:
scenarios.append("\n".join(current_scenario))
return scenarios

View File

@@ -0,0 +1,118 @@
"""Behave exporter for Python BDD projects."""
from typing import Dict, List
from nl2gherkin.exporters.base import BaseExporter
class BehaveExporter(BaseExporter):
"""Exporter for Behave (Python)."""
def __init__(self) -> None:
"""Initialize the Behave exporter."""
pass
def export(self, features: List[str]) -> str:
"""Export features to Behave format.
Args:
features: List of Gherkin feature strings.
Returns:
Behave-compatible feature file content.
"""
combined = "\n\n".join(features)
return combined
def get_step_definitions_template(self) -> str:
"""Get Behave step definitions template.
Returns:
Step definitions template string.
"""
return '''"""Behave step definitions."""
from behave import given, when, then
@given("a setup condition")
def step_given_setup(context):
"""Given step implementation."""
pass
@when("an action occurs")
def step_when_action(context):
"""When step implementation."""
pass
@then("an expected result")
def step_then_result(context):
"""Then step implementation."""
pass
'''
def get_configuration_template(self) -> Dict[str, str]:
"""Get Behave configuration files.
Returns:
Dictionary mapping filenames to content.
"""
return {
"behave.ini": '''[behave]
format = progress
outfiles = behave-report.txt
''',
"features/environment.py": '''"""Behave environment configuration."""
def before_scenario(context, scenario):
"""Run before each scenario."""
pass
def after_scenario(context, scenario):
"""Run after each scenario."""
pass
''',
}
def generate_step_definitions(self, scenarios: List[str]) -> str:
"""Generate step definitions for given scenarios.
Args:
scenarios: List of scenario texts.
Returns:
Step definitions Python code.
"""
step_defs = []
for scenario in scenarios:
lines = scenario.split("\n")
for line in lines:
stripped = line.strip()
if stripped.startswith(("Given ", "When ", "Then ", "And ")):
step_text = " ".join(stripped.split()[1:])
step_def = stripped.split()[0].lower()
params = self._extract_parameters(step_text)
step_def_line = f'@given("{step_text}")'
if params:
extra_params = ", " + ", ".join(f'"{p}"' for p in params)
else:
extra_params = ""
step_impl = f'def step_impl(context{extra_params}):\n """{stripped.split()[0]} step implementation."""\n pass\n'
step_defs.append(step_def_line)
step_defs.append(step_impl)
return "\n".join(step_defs)
def _extract_parameters(self, step_text: str) -> List[str]:
"""Extract parameters from a step text."""
import re
return re.findall(r"<([^>]+)>", step_text)

View File

@@ -0,0 +1,89 @@
"""Cucumber exporter for JavaScript/TypeScript projects."""
from typing import Dict, List
from nl2gherkin.exporters.base import BaseExporter
class CucumberExporter(BaseExporter):
"""Exporter for Cucumber (JavaScript/TypeScript)."""
def __init__(self) -> None:
"""Initialize the Cucumber exporter."""
self.step_definitions_template = """const {{ Given, When, Then }} = require('@cucumber/cucumber');
{{step_definitions}}
"""
def export(self, features: List[str]) -> str:
"""Export features to Cucumber format.
Args:
features: List of Gherkin feature strings.
Returns:
Cucumber-compatible feature file content.
"""
combined = "\n\n".join(features)
return combined
def get_step_definitions_template(self) -> str:
"""Get Cucumber step definitions template.
Returns:
Step definitions template string.
"""
return self.step_definitions_template
def get_configuration_template(self) -> Dict[str, str]:
"""Get Cucumber configuration files.
Returns:
Dictionary mapping filenames to content.
"""
return {
"cucumber.js": '''module.exports = {
default: '--publish-quiet'
}
''',
".cucumberrc": '''default:
publish-quiet: true
format: ['progress-bar', 'html:cucumber-report.html']
''',
}
def generate_step_definitions(self, scenarios: List[str]) -> str:
"""Generate step definitions for given scenarios.
Args:
scenarios: List of scenario texts.
Returns:
Step definitions JavaScript code.
"""
step_defs = []
for scenario in scenarios:
lines = scenario.split("\n")
for line in lines:
stripped = line.strip()
if stripped.startswith(("Given ", "When ", "Then ", "And ")):
step_text = " ".join(stripped.split()[1:])
step_def = stripped.split()[0].lower()
indent = " " * (1 if stripped.startswith("And") or stripped.startswith("But") else 0)
params = self._extract_parameters(step_text)
param_str = ", ".join(f'"{p}"' for p in params) if params else ""
params_list = ", ".join(p for p in params)
step_def_code = step_def.capitalize() + "(" + param_str + ", async function (" + params_list + ") {\n"
step_def_code += " // TODO: implement step\n"
step_def_code += "});\n"
step_defs.append(step_def_code)
return "\n".join(step_defs)
def _extract_parameters(self, step_text: str) -> List[str]:
"""Extract parameters from a step text."""
import re
return re.findall(r"<([^>]+)>", step_text)

View File

@@ -0,0 +1,141 @@
"""pytest-bdd exporter for pytest projects."""
from typing import Dict, List
from nl2gherkin.exporters.base import BaseExporter
class PytestBDDExporter(BaseExporter):
"""Exporter for pytest-bdd (Python)."""
def __init__(self) -> None:
"""Initialize the pytest-bdd exporter."""
pass
def export(self, features: List[str]) -> str:
"""Export features to pytest-bdd format.
Args:
features: List of Gherkin feature strings.
Returns:
pytest-bdd-compatible feature file content.
"""
combined = "\n\n".join(features)
return combined
def get_step_definitions_template(self) -> str:
"""Get pytest-bdd step definitions template.
Returns:
Step definitions template string.
"""
return '''"""pytest-bdd step definitions."""
import pytest
from pytest_bdd import given, when, then, scenarios
scenarios('features')
@given("a setup condition")
def setup_condition():
"""Given step implementation."""
return {}
@when("an action occurs")
def action_occurs():
"""When step implementation."""
pass
@then("an expected result")
def expected_result():
"""Then step implementation."""
pass
'''
def get_configuration_template(self) -> Dict[str, str]:
"""Get pytest-bdd configuration files.
Returns:
Dictionary mapping filenames to content.
"""
return {
"conftest.py": '''"""pytest configuration and fixtures."""
import pytest
from pytest_bdd import scenarios
scenarios('features')
@pytest.fixture
def context():
"""Shared test context."""
return {}
def pytest_configure(config):
"""Configure pytest."""
pass
''',
"pytest.ini": '''[pytest]
bdd_features_base_dir = features/
''',
}
def generate_step_definitions(self, scenarios: List[str], feature_name: str = "features") -> str:
"""Generate step definitions for given scenarios.
Args:
scenarios: List of scenario texts.
feature_name: Name of the feature file.
Returns:
Step definitions Python code.
"""
step_defs = []
for scenario in scenarios:
lines = scenario.split("\n")
scenario_name = ""
for line in lines:
stripped = line.strip()
if stripped.startswith("Scenario:"):
scenario_name = stripped[9:].strip().replace(" ", "_")
break
for line in lines:
stripped = line.strip()
if stripped.startswith(("Given ", "When ", "Then ", "And ")):
step_text = " ".join(stripped.split()[1:])
step_def = stripped.split()[0].lower()
params = self._extract_parameters(step_text)
param_str = ", ".join(f'"{p}"' for p in params) if params else ""
if params:
step_impl = f'''@pytest.{step_def}("{step_text}")
def {step_def}_{scenario_name}({", ".join(params)}):
"""{stripped.split()[0]} step implementation."""
pass
'''
else:
step_impl = f'''@{step_def}("{step_text}")
def {step_def}_{scenario_name}():
"""{stripped.split()[0]} step implementation."""
pass
'''
step_defs.append(step_impl)
return "\n".join(step_defs)
def _extract_parameters(self, step_text: str) -> List[str]:
"""Extract parameters from a step text."""
import re
return re.findall(r"<([^>]+)>", step_text)