diff --git a/src/mudlib/crafting.py b/src/mudlib/crafting.py new file mode 100644 index 0000000..30b7e37 --- /dev/null +++ b/src/mudlib/crafting.py @@ -0,0 +1,57 @@ +"""Crafting recipe system.""" + +import logging +import tomllib +from dataclasses import dataclass +from pathlib import Path + +logger = logging.getLogger(__name__) + + +@dataclass +class Recipe: + """A crafting recipe definition.""" + + name: str + description: str + ingredients: list[str] + result: str + + +# Module-level registry +recipes: dict[str, Recipe] = {} + + +def load_recipe(path: Path) -> Recipe: + """Load a recipe from TOML. + + Args: + path: Path to the recipe TOML file + + Returns: + Recipe instance + """ + with open(path, "rb") as f: + data = tomllib.load(f) + return Recipe( + name=data["name"], + description=data["description"], + ingredients=data["ingredients"], + result=data["result"], + ) + + +def load_recipes(directory: Path) -> dict[str, Recipe]: + """Load all recipes from a directory. + + Args: + directory: Path to directory containing recipe TOML files + + Returns: + Dict of recipes keyed by name + """ + loaded: dict[str, Recipe] = {} + for path in sorted(directory.glob("*.toml")): + recipe = load_recipe(path) + loaded[recipe.name] = recipe + return loaded diff --git a/tests/test_crafting.py b/tests/test_crafting.py new file mode 100644 index 0000000..e50a77b --- /dev/null +++ b/tests/test_crafting.py @@ -0,0 +1,99 @@ +"""Tests for the crafting recipe system.""" + +import tempfile +from pathlib import Path + +import pytest + +from mudlib.crafting import Recipe, load_recipe, load_recipes, recipes +from mudlib.things import thing_templates +from mudlib.zones import zone_registry + + +@pytest.fixture(autouse=True) +def _clean_registries(): + """Snapshot and restore registries to prevent test leakage.""" + saved_zones = dict(zone_registry) + saved_templates = dict(thing_templates) + saved_recipes = dict(recipes) + zone_registry.clear() + thing_templates.clear() + recipes.clear() + yield + zone_registry.clear() + zone_registry.update(saved_zones) + thing_templates.clear() + thing_templates.update(saved_templates) + recipes.clear() + recipes.update(saved_recipes) + + +def test_load_recipe_from_toml(): + """Parse a recipe TOML file.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f: + f.write(""" +name = "wooden_table" +description = "Craft a sturdy table from planks and nails" +ingredients = ["plank", "plank", "plank", "nail", "nail"] +result = "table" +""") + path = Path(f.name) + + try: + recipe = load_recipe(path) + assert recipe.name == "wooden_table" + assert recipe.description == "Craft a sturdy table from planks and nails" + assert recipe.ingredients == ["plank", "plank", "plank", "nail", "nail"] + assert recipe.result == "table" + finally: + path.unlink() + + +def test_load_recipes_directory(): + """Load all recipes from a directory.""" + with tempfile.TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + + # Create two recipe files + (tmp_path / "table.toml").write_text(""" +name = "wooden_table" +description = "Craft a table" +ingredients = ["plank", "plank"] +result = "table" +""") + (tmp_path / "chair.toml").write_text(""" +name = "wooden_chair" +description = "Craft a chair" +ingredients = ["plank"] +result = "chair" +""") + + loaded = load_recipes(tmp_path) + assert len(loaded) == 2 + assert "wooden_table" in loaded + assert "wooden_chair" in loaded + assert loaded["wooden_table"].result == "table" + assert loaded["wooden_chair"].result == "chair" + + +def test_recipe_fields(): + """Verify all recipe fields are populated correctly.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f: + f.write(""" +name = "test_recipe" +description = "A test recipe" +ingredients = ["item_a", "item_b", "item_c"] +result = "item_result" +""") + path = Path(f.name) + + try: + recipe = load_recipe(path) + assert isinstance(recipe, Recipe) + assert recipe.name == "test_recipe" + assert recipe.description == "A test recipe" + assert recipe.ingredients == ["item_a", "item_b", "item_c"] + assert recipe.result == "item_result" + assert len(recipe.ingredients) == 3 + finally: + path.unlink()