soleprint init commit

This commit is contained in:
buenosairesam
2025-12-24 05:38:37 -03:00
commit 329c401ff5
96 changed files with 11564 additions and 0 deletions

View 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