diff --git a/app/services/recipe_service.py b/app/services/recipe_service.py index 93d0717..5dc6333 100644 --- a/app/services/recipe_service.py +++ b/app/services/recipe_service.py @@ -32,6 +32,8 @@ class RecipeService: async def get_recipe(self, recipe_id: str) -> Optional[Recipe]: """Get a recipe by ID (content hash).""" + import yaml + # Get from cache (content-addressed storage) path = self.cache.get_by_cid(recipe_id) if not path or not path.exists(): @@ -40,13 +42,34 @@ class RecipeService: with open(path) as f: content = f.read() - # Parse S-expression - try: - compiled = compile_string(content) - recipe_data = compiled.to_dict() - recipe_data["sexp"] = content - except (ParseError, CompileError) as e: - return {"error": str(e), "recipe_id": recipe_id} + # Detect format - check if it starts with ( after skipping comments + def is_sexp_format(text): + for line in text.split('\n'): + stripped = line.strip() + if not stripped or stripped.startswith(';'): + continue + return stripped.startswith('(') + return False + + if is_sexp_format(content): + # Parse S-expression + try: + compiled = compile_string(content) + recipe_data = compiled.to_dict() + recipe_data["sexp"] = content + recipe_data["format"] = "sexp" + except (ParseError, CompileError) as e: + return {"error": str(e), "recipe_id": recipe_id} + else: + # Parse YAML + try: + recipe_data = yaml.safe_load(content) + if not isinstance(recipe_data, dict): + return {"error": "Invalid YAML: expected dictionary", "recipe_id": recipe_id} + recipe_data["yaml"] = content + recipe_data["format"] = "yaml" + except yaml.YAMLError as e: + return {"error": f"YAML parse error: {e}", "recipe_id": recipe_id} # Add the recipe_id to the data for convenience recipe_data["recipe_id"] = recipe_id @@ -56,8 +79,12 @@ class RecipeService: if ipfs_cid: recipe_data["ipfs_cid"] = ipfs_cid - # Compute step_count from nodes - nodes = recipe_data.get("dag", {}).get("nodes", []) + # Compute step_count from nodes (handle both formats) + if recipe_data.get("format") == "sexp": + nodes = recipe_data.get("dag", {}).get("nodes", []) + else: + # YAML format: nodes might be at top level or under dag + nodes = recipe_data.get("nodes", recipe_data.get("dag", {}).get("nodes", [])) recipe_data["step_count"] = len(nodes) if isinstance(nodes, (list, dict)) else 0 return recipe_data