Initial commit: Requirements to Gherkin CLI Converter
This commit is contained in:
13
src/nl2gherkin/exporters/__init__.py
Normal file
13
src/nl2gherkin/exporters/__init__.py
Normal 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",
|
||||
]
|
||||
59
src/nl2gherkin/exporters/base.py
Normal file
59
src/nl2gherkin/exporters/base.py
Normal 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
|
||||
118
src/nl2gherkin/exporters/behave.py
Normal file
118
src/nl2gherkin/exporters/behave.py
Normal 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)
|
||||
89
src/nl2gherkin/exporters/cucumber.py
Normal file
89
src/nl2gherkin/exporters/cucumber.py
Normal 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)
|
||||
141
src/nl2gherkin/exporters/pytest_bdd.py
Normal file
141
src/nl2gherkin/exporters/pytest_bdd.py
Normal 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)
|
||||
Reference in New Issue
Block a user