docs: Add comprehensive documentation to recipe generator
Add detailed docstrings and inline comments to all recipe generator modules explaining classes, functions, and complex logic in plain English. Files updated: - recipe-generator/recipe_generator.py: Module, class, and method docs - recipe-generator/cli.py: CLI modes, arguments, and workflow docs - recipe-generator/validators.py: Three-layer validation architecture docs - recipe-generator/manifest.py: Manifest processing and variant expansion docs - recipe-generator/generate_recipe.py: Entry point documentation
This commit is contained in:
+132
-12
@@ -1,4 +1,32 @@
|
||||
"""Manifest loading and variant expansion."""
|
||||
"""Manifest loading and variant expansion.
|
||||
|
||||
This module provides functionality for processing manifest YAML files, which
|
||||
are used in batch recipe generation mode. It has two main responsibilities:
|
||||
|
||||
1. Loading and Parsing Manifests
|
||||
- Reads YAML files containing recipe configurations
|
||||
- Validates basic structure of the manifest
|
||||
|
||||
2. Variant Expansion
|
||||
- Converts compact manifest entries with list values into individual variants
|
||||
- Uses cartesian product to generate all combinations
|
||||
- Enables generating many recipes from a single manifest entry
|
||||
|
||||
Manifest Example:
|
||||
recipes:
|
||||
- name: virtual-desktop
|
||||
variants:
|
||||
- version: 43
|
||||
desktop: gnome
|
||||
- version: ["43", "rawhide"]
|
||||
storage: ["standard", "encrypted"]
|
||||
|
||||
The above would generate 4 recipes (2 versions × 2 storage types).
|
||||
|
||||
The key insight is that list values in variants represent "multiple values for
|
||||
this field", and we want to generate one recipe for each combination. This is
|
||||
the classic cartesian product problem.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -10,27 +38,79 @@ import yaml
|
||||
|
||||
|
||||
class ManifestProcessor:
|
||||
"""Load and process recipe manifests."""
|
||||
"""Load and process recipe manifests.
|
||||
|
||||
This class handles the loading and processing of manifest YAML files.
|
||||
A manifest is a YAML file that defines which recipes to generate and
|
||||
what variations (variants) of each recipe to create.
|
||||
|
||||
Key features:
|
||||
- Loads and parses YAML manifest files
|
||||
- Validates manifest structure (schema-level checks)
|
||||
- Expands variants with list values into all combinations
|
||||
|
||||
The variant expansion is particularly useful for generating multiple
|
||||
versions (e.g., both Fedora 43 and rawhide) or multiple configurations
|
||||
(e.g., both standard and encrypted storage) in a compact format.
|
||||
"""
|
||||
|
||||
def __init__(self, project_root: Path):
|
||||
"""Initialize the processor with project root path.
|
||||
|
||||
Args:
|
||||
project_root: Path to the phyllomeos project root directory.
|
||||
Not currently used but available for future enhancements.
|
||||
"""
|
||||
self.project_root = project_root
|
||||
|
||||
def load(self, path: Path) -> Dict:
|
||||
"""Load and parse manifest YAML file."""
|
||||
"""Load and parse manifest YAML file.
|
||||
|
||||
This method reads a YAML file from disk and parses it into a Python
|
||||
dictionary. It's a simple wrapper around yaml.safe_load that provides
|
||||
error handling and a consistent interface.
|
||||
|
||||
Args:
|
||||
path: Path to the manifest YAML file
|
||||
|
||||
Returns:
|
||||
Parsed manifest as a dictionary
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If the file doesn't exist
|
||||
yaml.YAMLError: If the file contains invalid YAML
|
||||
"""
|
||||
with open(path, encoding='utf-8') as f:
|
||||
return yaml.safe_load(f)
|
||||
|
||||
def validate(self, manifest: Dict) -> List[str]:
|
||||
"""Validate manifest structure.
|
||||
|
||||
Returns a list of validation warnings (not errors).
|
||||
|
||||
This method performs schema-level validation of a manifest dictionary.
|
||||
It checks that:
|
||||
1. The top-level 'recipes' key exists
|
||||
2. Each recipe configuration has a 'name' field
|
||||
3. Each recipe configuration has a 'variants' field
|
||||
4. Each variant has a 'version' field
|
||||
|
||||
This is a basic structural check that catches obvious errors before
|
||||
trying to process the manifest. It doesn't validate the content of
|
||||
individual recipes (that's done by RecipeGenerator and validators).
|
||||
|
||||
Args:
|
||||
manifest: The manifest dictionary to validate
|
||||
|
||||
Returns:
|
||||
List of validation error strings (empty if valid)
|
||||
"""
|
||||
errors = []
|
||||
|
||||
# Check for required top-level key
|
||||
if 'recipes' not in manifest:
|
||||
errors.append("Manifest missing 'recipes' key")
|
||||
return errors
|
||||
return errors # Can't proceed without recipes
|
||||
|
||||
# Validate each recipe configuration
|
||||
for recipe_config in manifest.get('recipes', []):
|
||||
if 'name' not in recipe_config:
|
||||
errors.append("Recipe config missing 'name' key")
|
||||
@@ -42,16 +122,53 @@ class ManifestProcessor:
|
||||
|
||||
def expand_variants(self, variants: List[Dict]) -> List[Dict]:
|
||||
"""Expand variants with list values into individual variants.
|
||||
|
||||
Converts variants like:
|
||||
- version: ["43", "rawhide"]
|
||||
- storage: ["standard", "encrypted"]
|
||||
|
||||
Into cartesian product of all combinations.
|
||||
|
||||
This is the core functionality of the manifest processor. It converts
|
||||
a compact representation with list values into a complete list of
|
||||
individual variants by generating the cartesian product.
|
||||
|
||||
Example Input:
|
||||
[
|
||||
{
|
||||
'version': ['43', 'rawhide'],
|
||||
'storage': ['standard', 'encrypted'],
|
||||
'desktop': 'gnome'
|
||||
},
|
||||
{
|
||||
'version': '43',
|
||||
'security': 'devel'
|
||||
}
|
||||
]
|
||||
|
||||
Example Output:
|
||||
[
|
||||
{'version': '43', 'storage': 'standard', 'desktop': 'gnome'},
|
||||
{'version': '43', 'storage': 'encrypted', 'desktop': 'gnome'},
|
||||
{'version': 'rawhide', 'storage': 'standard', 'desktop': 'gnome'},
|
||||
{'version': 'rawhide', 'storage': 'encrypted', 'desktop': 'gnome'},
|
||||
{'version': '43', 'security': 'devel'}
|
||||
]
|
||||
|
||||
How it works:
|
||||
1. For each variant dictionary, separate list-valued keys from scalar keys
|
||||
2. If there are no lists, keep the variant as-is
|
||||
3. If there are lists, generate the cartesian product of all list values
|
||||
4. For each combination, create a new variant with scalar values preserved
|
||||
5. Return the complete list of expanded variants
|
||||
|
||||
This enables generating many recipes from a single compact manifest entry,
|
||||
making it easy to generate variations across multiple dimensions.
|
||||
|
||||
Args:
|
||||
variants: List of variant dictionaries, where some values may be lists
|
||||
|
||||
Returns:
|
||||
Expanded list where each variant has only scalar (non-list) values
|
||||
"""
|
||||
expanded = []
|
||||
|
||||
for variant in variants:
|
||||
# Separate list-valued keys from scalar keys
|
||||
list_keys = {}
|
||||
scalar_keys = {}
|
||||
|
||||
@@ -62,12 +179,15 @@ class ManifestProcessor:
|
||||
scalar_keys[key] = value
|
||||
|
||||
if not list_keys:
|
||||
# No lists to expand, keep variant as-is
|
||||
expanded.append(variant)
|
||||
continue
|
||||
|
||||
# Get the cartesian product of all list values
|
||||
keys = list(list_keys.keys())
|
||||
values_product = product(*[list_keys[k] for k in keys])
|
||||
|
||||
# Create a new variant for each combination
|
||||
for combo in values_product:
|
||||
new_variant = scalar_keys.copy()
|
||||
for i, key in enumerate(keys):
|
||||
|
||||
Reference in New Issue
Block a user