Skip to content

Commit 88cc787

Browse files
committed
yaml2rst.py: improve generators.
1 parent f70ca2b commit 88cc787

File tree

12 files changed

+781
-421
lines changed

12 files changed

+781
-421
lines changed
Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
"""Package for RST file generation components."""
2+
from .base_generator import BaseGenerator, GeneratorError, ValidationError
23
from .file_generator import FileGenerator, GeneratorConfig
3-
from .base_generator import BaseGenerator
4+
from .common_generators import ListBasedGenerator, SingleFileGenerator
45

5-
__all__ = ['FileGenerator', 'GeneratorConfig', 'BaseGenerator']
6+
__all__ = [
7+
'BaseGenerator',
8+
'GeneratorError',
9+
'ValidationError',
10+
'FileGenerator',
11+
'GeneratorConfig',
12+
'ListBasedGenerator',
13+
'SingleFileGenerator',
14+
]
Lines changed: 125 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,54 @@
1-
"""Base generator class for generating RST content from data."""
1+
"""Base generator module for RST content generation."""
2+
from abc import ABC, abstractmethod
23
from typing import Dict, Any, Iterator, Optional
4+
import logging
5+
from dataclasses import dataclass
6+
from pathlib import Path
37

4-
class BaseGenerator:
8+
class GeneratorError(Exception):
9+
"""Base exception for generator errors."""
10+
pass
11+
12+
class ValidationError(GeneratorError):
13+
"""Raised when data validation fails."""
14+
pass
15+
16+
@dataclass
17+
class GeneratorContext:
18+
"""Context information for generators."""
19+
lang: str
20+
base_dir: Path
21+
22+
class BaseGenerator(ABC):
523
"""Base class for all content generators."""
624

7-
def __init__(self, lang: str):
25+
def __init__(self, lang: str, base_dir: Optional[Path] = None):
826
"""Initialize the generator.
927
1028
Args:
1129
lang: Language code for content generation
30+
base_dir: Base directory for file operations (optional)
1231
"""
13-
self.lang = lang
32+
self.context = GeneratorContext(
33+
lang=lang,
34+
base_dir=Path(base_dir) if base_dir else Path.cwd()
35+
)
36+
self.logger = logging.getLogger(self.__class__.__name__)
1437

38+
@property
39+
def lang(self) -> str:
40+
"""Get language code for backward compatibility."""
41+
return self.context.lang
42+
43+
@abstractmethod
1544
def generate(self) -> Iterator[Dict[str, Any]]:
1645
"""Generate content data.
1746
18-
Returns:
19-
Iterator yielding dictionaries containing template data
47+
Yields:
48+
Dictionary containing template data
49+
50+
Raises:
51+
GeneratorError: On generation failure
2052
"""
2153
raise NotImplementedError("Subclasses must implement generate()")
2254

@@ -27,3 +59,90 @@ def get_dependencies(self) -> list[str]:
2759
List of file paths that this generator depends on
2860
"""
2961
return []
62+
63+
def validate_data(self, data: Dict[str, Any]) -> bool:
64+
"""Validate generated data.
65+
66+
Args:
67+
data: Data to validate
68+
69+
Returns:
70+
True if data is valid, False otherwise
71+
72+
Raises:
73+
ValidationError: If validation fails with specific reason
74+
"""
75+
return True
76+
77+
def preprocess_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
78+
"""Preprocess data before template rendering.
79+
80+
Args:
81+
data: Data to preprocess
82+
83+
Returns:
84+
Preprocessed data
85+
"""
86+
return data
87+
88+
def postprocess_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
89+
"""Postprocess data after template rendering.
90+
91+
Args:
92+
data: Data to postprocess
93+
94+
Returns:
95+
Postprocessed data
96+
"""
97+
return data
98+
99+
def process_single_item(self, item: Any) -> Dict[str, Any]:
100+
"""Process a single item into template data.
101+
102+
Args:
103+
item: Item to process
104+
105+
Returns:
106+
Processed template data
107+
108+
Raises:
109+
GeneratorError: On processing failure
110+
"""
111+
try:
112+
data = self._process_item(item)
113+
if not data:
114+
self.logger.warning(f"No data generated for item: {item}")
115+
return {}
116+
117+
if not self.validate_data(data):
118+
raise ValidationError(f"Data validation failed for item: {item}")
119+
120+
processed_data = self.preprocess_data(data)
121+
return self.postprocess_data(processed_data)
122+
123+
except Exception as e:
124+
self.logger.error(f"Error processing item {item}: {str(e)}")
125+
raise GeneratorError(f"Failed to process item: {str(e)}") from e
126+
127+
def _process_item(self, item: Any) -> Dict[str, Any]:
128+
"""Internal method to process a single item.
129+
130+
Args:
131+
item: Item to process
132+
133+
Returns:
134+
Processed template data
135+
"""
136+
raise NotImplementedError("Implement this method if using process_single_item")
137+
138+
def cleanup(self) -> None:
139+
"""Cleanup any resources after generation."""
140+
pass
141+
142+
def __enter__(self):
143+
"""Context manager entry."""
144+
return self
145+
146+
def __exit__(self, exc_type, exc_val, exc_tb):
147+
"""Context manager exit."""
148+
self.cleanup()
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""Common base classes for specific types of generators."""
2+
from typing import Dict, Any, Iterator, TypeVar, Generic, List
3+
from abc import abstractmethod
4+
5+
from a11y_guidelines import Faq, FaqTag, WcagSc, RelationshipManager
6+
from .base_generator import BaseGenerator
7+
8+
T = TypeVar('T')
9+
10+
class ListBasedGenerator(BaseGenerator, Generic[T]):
11+
"""Base class for generators that process lists of items."""
12+
13+
def generate(self) -> Iterator[Dict[str, Any]]:
14+
"""Generate content from a list of items."""
15+
items = self.get_items()
16+
self.logger.info(f"Processing {len(items)} items")
17+
18+
for item in items:
19+
try:
20+
data = self.process_item(item)
21+
if data and self.validate_data(data):
22+
yield self.postprocess_data(data)
23+
except Exception as e:
24+
self.logger.error(f"Error processing item {item}: {e}")
25+
raise
26+
27+
@abstractmethod
28+
def get_items(self) -> List[T]:
29+
"""Get list of items to process."""
30+
raise NotImplementedError
31+
32+
@abstractmethod
33+
def process_item(self, item: T) -> Dict[str, Any]:
34+
"""Process a single item into template data."""
35+
raise NotImplementedError
36+
37+
class SingleFileGenerator(BaseGenerator):
38+
"""Base class for generators that produce a single file."""
39+
40+
@abstractmethod
41+
def get_template_data(self) -> Dict[str, Any]:
42+
"""Get data for the template."""
43+
raise NotImplementedError
44+
45+
def generate(self) -> Iterator[Dict[str, Any]]:
46+
"""Generate content for a single file."""
47+
try:
48+
data = self.get_template_data()
49+
if data and self.validate_data(data):
50+
yield self.postprocess_data(data)
51+
except Exception as e:
52+
self.logger.error(f"Error generating template data: {e}")
53+
raise

tools/yaml2x/yaml2rst/generators/content_generators/__init__.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,38 @@
88
FaqTagIndexGenerator,
99
FaqArticleIndexGenerator
1010
)
11-
from .reference_generator import (
11+
from .wcag_generator import (
1212
WcagMappingGenerator,
13-
PriorityDiffGenerator,
13+
PriorityDiffGenerator
14+
)
15+
from .reference_generator import (
1416
MiscDefinitionsGenerator,
1517
InfoToGuidelinesGenerator,
1618
InfoToFaqsGenerator,
1719
AxeRulesGenerator
1820
)
19-
from .makefile_generator import MakefileGenerator
21+
from .makefile_generator import (
22+
MakefileGenerator,
23+
MakefileConfig
24+
)
2025

2126
__all__ = [
2227
'CategoryGenerator',
2328
'AllChecksGenerator',
2429
'CheckExampleGenerator',
30+
'FaqGeneratorBase',
2531
'FaqArticleGenerator',
2632
'FaqTagPageGenerator',
2733
'FaqIndexGenerator',
2834
'FaqTagIndexGenerator',
2935
'FaqArticleIndexGenerator',
36+
'WcagGeneratorBase',
3037
'WcagMappingGenerator',
3138
'PriorityDiffGenerator',
3239
'MiscDefinitionsGenerator',
3340
'InfoToGuidelinesGenerator',
3441
'InfoToFaqsGenerator',
3542
'AxeRulesGenerator',
36-
'MakefileGenerator'
43+
'MakefileGenerator',
44+
'MakefileConfig'
3745
]
Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,36 @@
11
"""Generator for category pages."""
2-
from typing import Dict, Any, Iterator
2+
from typing import Dict, Any, Iterator, List
33

44
from a11y_guidelines import RelationshipManager, Category
5-
from ..base_generator import BaseGenerator
5+
from ..common_generators import ListBasedGenerator
66

7-
class CategoryGenerator(BaseGenerator):
7+
class CategoryGenerator(ListBasedGenerator[str]):
88
"""Generates category pages with associated guidelines."""
99

10-
def generate(self) -> Iterator[Dict[str, Any]]:
11-
"""Generate category page data.
12-
13-
Yields:
14-
Dictionary containing category and its guidelines data
15-
"""
16-
rel = RelationshipManager()
17-
for category, guidelines in rel.get_guidelines_to_category().items():
18-
yield {
19-
'filename': category,
20-
'guidelines': [gl.template_data(self.lang) for gl in guidelines]
21-
}
10+
def __init__(self, lang: str):
11+
super().__init__(lang)
12+
self.relationship_manager = RelationshipManager()
13+
self.categories_by_id = {cat.id: cat for cat in Category.list_all()}
14+
15+
def get_items(self) -> List[str]:
16+
"""Get all category IDs to process."""
17+
return list(self.categories_by_id.keys())
18+
19+
def process_item(self, category_id: str) -> Dict[str, Any]:
20+
"""Process a single category."""
21+
category_map = self.relationship_manager.get_guidelines_to_category()
22+
category = self.categories_by_id[category_id]
23+
guidelines = category_map[category_id]
24+
return {
25+
'filename': category_id,
26+
'guidelines': [gl.template_data(self.lang) for gl in guidelines]
27+
}
28+
29+
def validate_data(self, data: Dict[str, Any]) -> bool:
30+
"""Validate category page data."""
31+
required_fields = ['filename', 'guidelines']
32+
return all(field in data for field in required_fields)
2233

2334
def get_dependencies(self) -> list[str]:
24-
"""Get category file dependencies.
25-
26-
Returns:
27-
List of category source file paths
28-
"""
35+
"""Get category file dependencies."""
2936
return [cat.get_dependency() for cat in Category.list_all()]

0 commit comments

Comments
 (0)