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,5 @@
"""CLI module for NL2Gherkin."""
from nl2gherkin.cli.commands import cli, convert, interactive, validate
__all__ = ["cli", "convert", "interactive", "validate"]

View File

@@ -0,0 +1,170 @@
"""CLI commands for the NL2Gherkin tool."""
import sys
from pathlib import Path
from typing import Optional
import click
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
from nl2gherkin.gherkin.generator import GherkinGenerator
from nl2gherkin.gherkin.parser import GherkinParser
from nl2gherkin.nlp.analyzer import NLPAnalyzer
@click.group()
def cli() -> None:
"""CLI tool for converting natural language requirements to Gherkin format."""
pass
@cli.command()
@click.argument(
"input_file",
type=click.Path(exists=True, readable=True, path_type=Path),
)
@click.option(
"--output",
"-o",
type=click.Path(path_type=Path),
help="Output file for the generated Gherkin.",
)
@click.option(
"--framework",
"-f",
type=click.Choice(["cucumber", "behave", "pytest-bdd"]),
default="cucumber",
help="BDD framework to export for.",
)
@click.option(
"--validate/--no-validate",
default=True,
help="Validate Gherkin syntax after generation.",
)
@click.option(
"--ambiguity-check/--no-ambiguity-check",
default=True,
help="Check for ambiguous language in requirements.",
)
def convert(
input_file: Path,
output: Optional[Path],
framework: str,
validate: bool,
ambiguity_check: bool,
) -> None:
"""Convert a requirements file to Gherkin format.
INPUT_FILE should be a text file containing natural language requirements.
"""
try:
content = input_file.read_text(encoding="utf-8")
analyzer = NLPAnalyzer()
parser = GherkinParser()
generator = GherkinGenerator(parser)
exporter: BaseExporter
if framework == "cucumber":
exporter = CucumberExporter()
elif framework == "behave":
exporter = BehaveExporter()
else:
exporter = PytestBDDExporter()
requirements = content.strip().split("\n\n")
gherkin_features = []
all_ambiguities = []
for req in requirements:
if not req.strip():
continue
if ambiguity_check:
ambiguities = analyzer.analyze_ambiguity(req)
if ambiguities:
all_ambiguities.extend(ambiguities)
click.echo("\n[WARNING] Ambiguities found in requirement:")
for amb in ambiguities:
click.echo(f" - {amb.message}")
if amb.suggestion:
click.echo(f" Suggestion: {amb.suggestion}")
analysis = analyzer.analyze(req)
gherkin = generator.generate(analysis)
gherkin_features.append(gherkin)
output_content = exporter.export(gherkin_features)
if validate:
valid, errors = parser.validate(output_content)
if not valid:
click.echo("\n[ERROR] Validation failed:")
for error in errors:
click.echo(f" {error}")
sys.exit(1)
else:
click.echo("[OK] Gherkin syntax is valid.")
if output:
output.write_text(output_content, encoding="utf-8")
click.echo(f"\nOutput written to: {output}")
else:
click.echo(f"\n{output_content}")
except Exception as e:
click.echo(f"[ERROR] {e}")
sys.exit(1)
@cli.command()
@click.option(
"--framework",
"-f",
type=click.Choice(["cucumber", "behave", "pytest-bdd"]),
default="cucumber",
help="BDD framework to export for.",
)
def interactive(framework: str) -> None:
"""Enter interactive mode for editing requirements."""
from nl2gherkin.cli.interactive import run_interactive_session
exporter: BaseExporter
if framework == "cucumber":
exporter = CucumberExporter()
elif framework == "behave":
exporter = BehaveExporter()
else:
exporter = PytestBDDExporter()
run_interactive_session(exporter)
@cli.command()
@click.argument(
"gherkin_file",
type=click.Path(exists=True, readable=True, path_type=Path),
)
def validate(gherkin_file: Path) -> None:
"""Validate a Gherkin file for syntax correctness."""
try:
content = gherkin_file.read_text(encoding="utf-8")
parser = GherkinParser()
valid, errors = parser.validate(content)
if valid:
click.echo("[OK] Gherkin syntax is valid.")
else:
click.echo("\n[ERROR] Validation failed:")
for error in errors:
click.echo(f" {error}")
sys.exit(1)
except Exception as e:
click.echo(f"[ERROR] {e}")
sys.exit(1)

View File

@@ -0,0 +1,108 @@
"""Interactive mode for the NL2Gherkin CLI."""
from typing import List
import click
from nl2gherkin.exporters.base import BaseExporter
from nl2gherkin.gherkin.generator import GherkinGenerator
from nl2gherkin.gherkin.parser import GherkinParser
from nl2gherkin.nlp.analyzer import NLPAnalyzer
def _colorize(text: str, color: str) -> str:
"""Helper to apply color to text."""
return click.style(text, fg=color)
def run_interactive_session(exporter: BaseExporter) -> None:
"""Run the interactive session for editing requirements."""
analyzer = NLPAnalyzer()
parser = GherkinParser()
generator = GherkinGenerator(parser)
history: List[dict] = []
generated_scenarios: List[str] = []
click.echo("\n[NL2Gherkin Interactive Mode]")
click.echo("Enter your requirements (press Ctrl+C to exit)")
click.echo("Use 'edit' to modify the last generated scenario")
click.echo("Use 'export' to export all scenarios")
click.echo("Use 'clear' to clear all scenarios\n")
while True:
try:
requirement = click.prompt(
"Enter requirement",
type=str,
default="",
show_default=False,
)
if not requirement.strip():
continue
if requirement.lower() == "edit":
if not generated_scenarios:
click.echo(_colorize("No scenarios to edit.", "yellow"))
continue
idx = click.prompt(
"Enter scenario number to edit",
type=int,
default=len(generated_scenarios),
show_default=False,
)
if 1 <= idx <= len(generated_scenarios):
edited_req = click.prompt(
"Enter modified requirement",
type=str,
default=generated_scenarios[idx - 1],
show_default=False,
)
analysis = analyzer.analyze(edited_req)
gherkin = generator.generate(analysis)
generated_scenarios[idx - 1] = gherkin
click.echo(f"\nUpdated scenario {idx}:")
click.echo(gherkin)
else:
click.echo(_colorize("Invalid scenario number.", "yellow"))
continue
if requirement.lower() == "export":
if not generated_scenarios:
click.echo(_colorize("No scenarios to export.", "yellow"))
continue
output = exporter.export(generated_scenarios)
click.echo("\n--- Exported Gherkin ---")
click.echo(output)
continue
if requirement.lower() == "clear":
generated_scenarios = []
click.echo(_colorize("Cleared all scenarios.", "green"))
continue
analysis = analyzer.analyze(requirement)
gherkin = generator.generate(analysis)
generated_scenarios.append(gherkin)
history.append({"requirement": requirement, "gherkin": gherkin})
click.echo("\n--- Generated Scenario ---")
click.echo(gherkin)
ambiguities = analyzer.analyze_ambiguity(requirement)
if ambiguities:
click.echo(_colorize("\n[WARNING] Potential ambiguities:", "yellow"))
for amb in ambiguities:
click.echo(f" - {amb.message}")
if amb.suggestion:
click.echo(f" Suggestion: {amb.suggestion}")
click.echo("")
except KeyboardInterrupt:
click.echo("\n\nExiting interactive mode.")
break