soleprint init commit
This commit is contained in:
231
station/tools/tester/gherkin/parser.py
Normal file
231
station/tools/tester/gherkin/parser.py
Normal file
@@ -0,0 +1,231 @@
|
||||
"""
|
||||
Parse Gherkin .feature files.
|
||||
|
||||
Simple parser without external dependencies - parses the subset we need.
|
||||
For full Gherkin support, could use gherkin-python package later.
|
||||
"""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass
|
||||
class GherkinScenario:
|
||||
"""A Gherkin scenario."""
|
||||
name: str
|
||||
description: str
|
||||
tags: list[str] = field(default_factory=list)
|
||||
steps: list[str] = field(default_factory=list)
|
||||
examples: dict = field(default_factory=dict)
|
||||
scenario_type: str = "Scenario" # or "Scenario Outline" / "Esquema del escenario"
|
||||
|
||||
|
||||
@dataclass
|
||||
class GherkinFeature:
|
||||
"""A parsed Gherkin feature file."""
|
||||
name: str
|
||||
description: str
|
||||
file_path: str
|
||||
language: str = "en" # or "es"
|
||||
tags: list[str] = field(default_factory=list)
|
||||
background: Optional[dict] = None
|
||||
scenarios: list[GherkinScenario] = field(default_factory=list)
|
||||
|
||||
|
||||
def parse_feature_file(file_path: Path) -> Optional[GherkinFeature]:
|
||||
"""
|
||||
Parse a Gherkin .feature file.
|
||||
|
||||
Supports both English and Spanish keywords.
|
||||
Extracts: Feature name, scenarios, tags, steps.
|
||||
"""
|
||||
if not file_path.exists():
|
||||
return None
|
||||
|
||||
try:
|
||||
content = file_path.read_text(encoding='utf-8')
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
# Detect language
|
||||
language = "en"
|
||||
if re.search(r"#\s*language:\s*es", content):
|
||||
language = "es"
|
||||
|
||||
# Keywords by language
|
||||
if language == "es":
|
||||
feature_kw = r"Característica"
|
||||
scenario_kw = r"Escenario"
|
||||
outline_kw = r"Esquema del escenario"
|
||||
background_kw = r"Antecedentes"
|
||||
examples_kw = r"Ejemplos"
|
||||
given_kw = r"Dado"
|
||||
when_kw = r"Cuando"
|
||||
then_kw = r"Entonces"
|
||||
and_kw = r"Y"
|
||||
but_kw = r"Pero"
|
||||
else:
|
||||
feature_kw = r"Feature"
|
||||
scenario_kw = r"Scenario"
|
||||
outline_kw = r"Scenario Outline"
|
||||
background_kw = r"Background"
|
||||
examples_kw = r"Examples"
|
||||
given_kw = r"Given"
|
||||
when_kw = r"When"
|
||||
then_kw = r"Then"
|
||||
and_kw = r"And"
|
||||
but_kw = r"But"
|
||||
|
||||
lines = content.split('\n')
|
||||
|
||||
# Extract feature
|
||||
feature_name = None
|
||||
feature_desc = []
|
||||
feature_tags = []
|
||||
scenarios = []
|
||||
current_scenario = None
|
||||
current_tags = []
|
||||
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i].strip()
|
||||
|
||||
# Skip comments and empty lines
|
||||
if not line or line.startswith('#'):
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Tags
|
||||
if line.startswith('@'):
|
||||
tags = re.findall(r'@[\w-]+', line)
|
||||
current_tags.extend(tags)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Feature
|
||||
feature_match = re.match(rf"^{feature_kw}:\s*(.+)", line)
|
||||
if feature_match:
|
||||
feature_name = feature_match.group(1).strip()
|
||||
feature_tags = current_tags.copy()
|
||||
current_tags = []
|
||||
|
||||
# Read feature description
|
||||
i += 1
|
||||
while i < len(lines):
|
||||
line = lines[i].strip()
|
||||
if not line or line.startswith('#'):
|
||||
i += 1
|
||||
continue
|
||||
# Stop at scenario or background
|
||||
if re.match(rf"^({scenario_kw}|{outline_kw}|{background_kw}):", line):
|
||||
break
|
||||
feature_desc.append(line)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Scenario
|
||||
scenario_match = re.match(rf"^({scenario_kw}|{outline_kw}):\s*(.+)", line)
|
||||
if scenario_match:
|
||||
# Save previous scenario
|
||||
if current_scenario:
|
||||
scenarios.append(current_scenario)
|
||||
|
||||
scenario_type = scenario_match.group(1)
|
||||
scenario_name = scenario_match.group(2).strip()
|
||||
|
||||
current_scenario = GherkinScenario(
|
||||
name=scenario_name,
|
||||
description="",
|
||||
tags=current_tags.copy(),
|
||||
steps=[],
|
||||
scenario_type=scenario_type
|
||||
)
|
||||
current_tags = []
|
||||
|
||||
# Read scenario steps
|
||||
i += 1
|
||||
while i < len(lines):
|
||||
line = lines[i].strip()
|
||||
|
||||
# Empty or comment
|
||||
if not line or line.startswith('#'):
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# New scenario or feature-level element
|
||||
if re.match(rf"^({scenario_kw}|{outline_kw}|{examples_kw}):", line):
|
||||
break
|
||||
|
||||
# Tags (start of next scenario)
|
||||
if line.startswith('@'):
|
||||
break
|
||||
|
||||
# Step keywords
|
||||
if re.match(rf"^({given_kw}|{when_kw}|{then_kw}|{and_kw}|{but_kw})\s+", line):
|
||||
current_scenario.steps.append(line)
|
||||
|
||||
i += 1
|
||||
continue
|
||||
|
||||
i += 1
|
||||
|
||||
# Add last scenario
|
||||
if current_scenario:
|
||||
scenarios.append(current_scenario)
|
||||
|
||||
if not feature_name:
|
||||
return None
|
||||
|
||||
return GherkinFeature(
|
||||
name=feature_name,
|
||||
description=" ".join(feature_desc),
|
||||
file_path=str(file_path),
|
||||
language=language,
|
||||
tags=feature_tags,
|
||||
scenarios=scenarios
|
||||
)
|
||||
|
||||
|
||||
def discover_features(features_dir: Path) -> list[GherkinFeature]:
|
||||
"""
|
||||
Discover all .feature files in the features directory.
|
||||
"""
|
||||
if not features_dir.exists():
|
||||
return []
|
||||
|
||||
features = []
|
||||
|
||||
for feature_file in features_dir.rglob("*.feature"):
|
||||
parsed = parse_feature_file(feature_file)
|
||||
if parsed:
|
||||
features.append(parsed)
|
||||
|
||||
return features
|
||||
|
||||
|
||||
def extract_tags_from_features(features: list[GherkinFeature]) -> set[str]:
|
||||
"""Extract all unique tags from features."""
|
||||
tags = set()
|
||||
|
||||
for feature in features:
|
||||
tags.update(feature.tags)
|
||||
for scenario in feature.scenarios:
|
||||
tags.update(scenario.tags)
|
||||
|
||||
return tags
|
||||
|
||||
|
||||
def get_feature_names(features: list[GherkinFeature]) -> list[str]:
|
||||
"""Get list of feature names."""
|
||||
return [f.name for f in features]
|
||||
|
||||
|
||||
def get_scenario_names(features: list[GherkinFeature]) -> list[str]:
|
||||
"""Get list of all scenario names across all features."""
|
||||
scenarios = []
|
||||
for feature in features:
|
||||
for scenario in feature.scenarios:
|
||||
scenarios.append(scenario.name)
|
||||
return scenarios
|
||||
Reference in New Issue
Block a user