Initial commit: Requirements to Gherkin CLI Converter
This commit is contained in:
5
src/nl2gherkin/cli/__init__.py
Normal file
5
src/nl2gherkin/cli/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""CLI module for NL2Gherkin."""
|
||||
|
||||
from nl2gherkin.cli.commands import cli, convert, interactive, validate
|
||||
|
||||
__all__ = ["cli", "convert", "interactive", "validate"]
|
||||
170
src/nl2gherkin/cli/commands.py
Normal file
170
src/nl2gherkin/cli/commands.py
Normal 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)
|
||||
108
src/nl2gherkin/cli/interactive.py
Normal file
108
src/nl2gherkin/cli/interactive.py
Normal 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
|
||||
Reference in New Issue
Block a user