pytest_beehave.hatch

Hatch command — generate bee-themed example features directory.

  1"""Hatch command — generate bee-themed example features directory."""
  2
  3from __future__ import annotations
  4
  5import secrets
  6from dataclasses import dataclass
  7from pathlib import Path
  8
  9_FEATURE_NAMES = [
 10    "The Forager's Journey",
 11    "Queen's Decree",
 12    "Drone Assembly Protocol",
 13    "Worker Bee Orientation",
 14    "Nectar Collection Workflow",
 15    "Hive Temperature Regulation",
 16    "Pollen Scout Dispatch",
 17    "Royal Jelly Production",
 18    "Swarm Formation Ritual",
 19    "Honeycomb Architecture Review",
 20]
 21
 22_BEES = [
 23    "Beatrice",
 24    "Boris",
 25    "Belinda",
 26    "Bruno",
 27    "Blossom",
 28    "Barnaby",
 29    "Bridget",
 30    "Bertram",
 31]
 32_HIVES = ["the Golden Hive", "the Amber Hive", "the Crystal Hive", "the Obsidian Hive"]
 33
 34_BACKLOG_CONTENT = """\
 35Feature: {feature_name}
 36
 37  As {bee}, a worker bee in {hive}
 38  I want to complete my assigned foraging route
 39  So that the colony has enough nectar for the season
 40
 41  Rule: Forager readiness
 42
 43    @id:hatch001
 44    Example: Forager departs when pollen reserve is below threshold
 45      Given the pollen reserve is below 30 percent
 46      When the forager sensor detects the shortage
 47      Then {bee} departs for the meadow within one waggle cycle
 48
 49    Example: Untagged scenario triggers auto-ID assignment
 50      Given the hive registers a new forager
 51      When the forager completes orientation
 52      Then the forager is assigned a unique scout ID
 53
 54    @deprecated
 55    Example: Legacy hive-entry handshake (deprecated)
 56      Given an older forager approaches the hive entrance
 57      When the guard bee checks the legacy handshake
 58      Then the handshake is accepted but logged as deprecated
 59
 60  Rule: Nectar quality control
 61
 62    @id:hatch002
 63    Example: Low-quality nectar is rejected at the gate
 64      Given a forager returns with nectar of quality below 0.4 brix
 65      When the gate inspector evaluates the sample
 66      Then the nectar is rejected and the forager is sent to a higher-quality source
 67"""
 68
 69_IN_PROGRESS_CONTENT = """\
 70# language: en
 71Feature: Waggle Dance Communication
 72
 73  Background:
 74    Given the hive is in active foraging mode
 75    And the dance floor is clear of obstacles
 76
 77  Rule: Direction encoding
 78
 79    @id:hatch003
 80    Example: Scout encodes flower direction in waggle run angle
 81      Given a scout has located flowers 200 metres to the north-east
 82      When the scout performs the waggle dance
 83      Then the waggle run angle matches the sun-relative bearing to the flowers
 84
 85  Rule: Distance encoding
 86
 87    @id:hatch004
 88    Scenario Outline: Scout encodes distance via waggle run duration
 89      Given a scout has located flowers at <distance> metres
 90      When the scout performs the waggle dance
 91      Then the waggle run lasts approximately <duration> milliseconds
 92
 93      Examples:
 94        | distance | duration |
 95        | 100      | 250      |
 96        | 500      | 875      |
 97        | 1000     | 1500     |
 98
 99    @id:hatch005
100    Example: Scout provides a data table of visited flower patches
101      Given the scout returns from a multi-patch forage
102      When the scout performs the waggle dance
103      Then the flower patch register contains the following entries:
104        | patch_id | species       | quality |
105        | P-001    | Lavender      | 0.92    |
106        | P-002    | Clover        | 0.85    |
107        | P-003    | Sunflower     | 0.78    |
108"""
109
110_COMPLETED_CONTENT = """\
111Feature: Colony Winter Preparation
112
113  As {bee}, the winter logistics coordinator in {hive}
114  I want to ensure honey stores are sufficient before the first frost
115  So that the colony survives the winter without starvation
116
117  Rule: Honey reserve verification
118
119    @id:hatch006
120    Example: Winter preparation passes when honey reserve exceeds minimum
121      Given the honey reserve is at 85 percent capacity
122      When the winter readiness check is performed
123      Then the colony status is set to WINTER-READY
124
125    @id:hatch007
126    Example: Winter preparation fails when honey reserve is insufficient
127      Given the honey reserve is below 60 percent capacity
128      When the winter readiness check is performed
129      Then the colony status is set to AT-RISK and an alert is raised for {bee}
130"""
131
132
133@dataclass(frozen=True, slots=True)
134class HatchFile:
135    """A single generated .feature file to be written.
136
137    Attributes:
138        relative_path: Path relative to the features root (e.g. ``backlog/x.feature``).
139        content: The full Gherkin text to write.
140    """
141
142    relative_path: str
143    content: str
144
145
146def generate_hatch_files() -> list[HatchFile]:
147    """Generate bee-themed example .feature files using stdlib randomisation.
148
149    Returns:
150        A list of HatchFile objects ready to be written to disk.
151    """
152    feature_name = secrets.choice(_FEATURE_NAMES)
153    bee = secrets.choice(_BEES)
154    hive = secrets.choice(_HIVES)
155
156    return [
157        HatchFile(
158            relative_path="backlog/forager-journey.feature",
159            content=_BACKLOG_CONTENT.format(
160                feature_name=feature_name, bee=bee, hive=hive
161            ),
162        ),
163        HatchFile(
164            relative_path="in-progress/waggle-dance.feature",
165            content=_IN_PROGRESS_CONTENT,
166        ),
167        HatchFile(
168            relative_path="completed/winter-preparation.feature",
169            content=_COMPLETED_CONTENT.format(bee=bee, hive=hive),
170        ),
171    ]
172
173
174def write_hatch(features_root: Path, files: list[HatchFile]) -> list[str]:
175    """Write HatchFile objects to disk under features_root.
176
177    Args:
178        features_root: The root features directory to write into.
179        files: The list of HatchFile objects to write.
180
181    Returns:
182        List of written file paths as strings (relative to features_root).
183    """
184    written: list[str] = []
185    for hatch_file in files:
186        dest = features_root / hatch_file.relative_path
187        dest.parent.mkdir(parents=True, exist_ok=True)
188        dest.write_text(hatch_file.content, encoding="utf-8")
189        written.append(hatch_file.relative_path)
190    return written
191
192
193def run_hatch(features_root: Path, force: bool) -> list[str]:
194    """Run the hatch command: check for conflicts, generate, and write files.
195
196    Args:
197        features_root: The root features directory to populate.
198        force: If True, overwrite existing content without error.
199
200    Returns:
201        List of written file paths as strings.
202
203    Raises:
204        SystemExit: If the directory contains .feature files and force is False.
205    """
206    existing = list(features_root.rglob("*.feature")) if features_root.exists() else []
207    if existing and not force:
208        conflict = existing[0]
209        raise SystemExit(
210            f"[beehave] hatch aborted: existing .feature files found at {conflict}. "
211            "Use --beehave-hatch-force to overwrite."
212        )
213    for old_file in existing:
214        old_file.unlink()
215    return write_hatch(features_root, generate_hatch_files())
@dataclass(frozen=True, slots=True)
class HatchFile:
134@dataclass(frozen=True, slots=True)
135class HatchFile:
136    """A single generated .feature file to be written.
137
138    Attributes:
139        relative_path: Path relative to the features root (e.g. ``backlog/x.feature``).
140        content: The full Gherkin text to write.
141    """
142
143    relative_path: str
144    content: str

A single generated .feature file to be written.

Attributes: relative_path: Path relative to the features root (e.g. backlog/x.feature). content: The full Gherkin text to write.

HatchFile(relative_path: str, content: str)
relative_path: str
content: str
def generate_hatch_files() -> list[HatchFile]:
147def generate_hatch_files() -> list[HatchFile]:
148    """Generate bee-themed example .feature files using stdlib randomisation.
149
150    Returns:
151        A list of HatchFile objects ready to be written to disk.
152    """
153    feature_name = secrets.choice(_FEATURE_NAMES)
154    bee = secrets.choice(_BEES)
155    hive = secrets.choice(_HIVES)
156
157    return [
158        HatchFile(
159            relative_path="backlog/forager-journey.feature",
160            content=_BACKLOG_CONTENT.format(
161                feature_name=feature_name, bee=bee, hive=hive
162            ),
163        ),
164        HatchFile(
165            relative_path="in-progress/waggle-dance.feature",
166            content=_IN_PROGRESS_CONTENT,
167        ),
168        HatchFile(
169            relative_path="completed/winter-preparation.feature",
170            content=_COMPLETED_CONTENT.format(bee=bee, hive=hive),
171        ),
172    ]

Generate bee-themed example .feature files using stdlib randomisation.

Returns: A list of HatchFile objects ready to be written to disk.

def write_hatch( features_root: pathlib._local.Path, files: list[HatchFile]) -> list[str]:
175def write_hatch(features_root: Path, files: list[HatchFile]) -> list[str]:
176    """Write HatchFile objects to disk under features_root.
177
178    Args:
179        features_root: The root features directory to write into.
180        files: The list of HatchFile objects to write.
181
182    Returns:
183        List of written file paths as strings (relative to features_root).
184    """
185    written: list[str] = []
186    for hatch_file in files:
187        dest = features_root / hatch_file.relative_path
188        dest.parent.mkdir(parents=True, exist_ok=True)
189        dest.write_text(hatch_file.content, encoding="utf-8")
190        written.append(hatch_file.relative_path)
191    return written

Write HatchFile objects to disk under features_root.

Args: features_root: The root features directory to write into. files: The list of HatchFile objects to write.

Returns: List of written file paths as strings (relative to features_root).

def run_hatch(features_root: pathlib._local.Path, force: bool) -> list[str]:
194def run_hatch(features_root: Path, force: bool) -> list[str]:
195    """Run the hatch command: check for conflicts, generate, and write files.
196
197    Args:
198        features_root: The root features directory to populate.
199        force: If True, overwrite existing content without error.
200
201    Returns:
202        List of written file paths as strings.
203
204    Raises:
205        SystemExit: If the directory contains .feature files and force is False.
206    """
207    existing = list(features_root.rglob("*.feature")) if features_root.exists() else []
208    if existing and not force:
209        conflict = existing[0]
210        raise SystemExit(
211            f"[beehave] hatch aborted: existing .feature files found at {conflict}. "
212            "Use --beehave-hatch-force to overwrite."
213        )
214    for old_file in existing:
215        old_file.unlink()
216    return write_hatch(features_root, generate_hatch_files())

Run the hatch command: check for conflicts, generate, and write files.

Args: features_root: The root features directory to populate. force: If True, overwrite existing content without error.

Returns: List of written file paths as strings.

Raises: SystemExit: If the directory contains .feature files and force is False.