plating 0.0.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,216 @@
1
+ #
2
+ # plating/template_functions.py
3
+ #
4
+ """Custom Jinja2 template functions for .plating rendering."""
5
+
6
+ from typing import Any
7
+
8
+ from jinja2 import BaseLoader, Environment, select_autoescape
9
+
10
+
11
+ class SchemaRenderer:
12
+ """Renders Pyvider schemas to markdown tables."""
13
+
14
+ def render_schema(self, schema) -> str:
15
+ """Render schema attributes and blocks to markdown table."""
16
+ if not schema:
17
+ return "No arguments available."
18
+
19
+ markdown_parts = []
20
+
21
+ # Render main attributes
22
+ if hasattr(schema, "attributes") and schema.attributes:
23
+ markdown_parts.append(self._render_attributes_table(schema.attributes))
24
+
25
+ # Render nested blocks
26
+ if hasattr(schema, "blocks") and schema.blocks:
27
+ for block_name, block_schema in schema.blocks.items():
28
+ markdown_parts.append(f"\n### {block_name}\n")
29
+ if hasattr(block_schema, "attributes") and block_schema.attributes:
30
+ markdown_parts.append(
31
+ self._render_attributes_table(block_schema.attributes)
32
+ )
33
+
34
+ return (
35
+ "\n".join(markdown_parts) if markdown_parts else "No arguments available."
36
+ )
37
+
38
+ def _render_attributes_table(self, attributes: dict[str, Any]) -> str:
39
+ """Render attributes dictionary to markdown table."""
40
+ if not attributes:
41
+ return "No arguments available."
42
+
43
+ lines = [
44
+ "| Argument | Type | Required | Description |",
45
+ "|----------|------|----------|-------------|",
46
+ ]
47
+
48
+ for attr_name, attr_def in attributes.items():
49
+ # Extract attribute properties
50
+ attr_type = self._format_type(getattr(attr_def, "type", "String"))
51
+ required = self._format_required(attr_def)
52
+ description = getattr(attr_def, "description", "No description available")
53
+
54
+ lines.append(
55
+ f"| `{attr_name}` | {attr_type} | {required} | {description} |"
56
+ )
57
+
58
+ return "\n".join(lines)
59
+
60
+ def _format_type(self, type_info) -> str:
61
+ """Format type information for display."""
62
+ if isinstance(type_info, str):
63
+ return type_info.title()
64
+ elif isinstance(type_info, list) and len(type_info) == 2:
65
+ # Handle complex types like ["list", "string"]
66
+ container, element = type_info
67
+ return f"{container.title()} of {element.title()}"
68
+ else:
69
+ return str(type_info).title()
70
+
71
+ def _format_required(self, attr_def) -> str:
72
+ """Format required status for display."""
73
+ if getattr(attr_def, "required", False):
74
+ return "**Yes**"
75
+ elif getattr(attr_def, "computed", False):
76
+ return "No (Computed)"
77
+ else:
78
+ return "No"
79
+
80
+
81
+ class TemplateEngine:
82
+ """Jinja2 template engine with custom functions for .plating rendering."""
83
+
84
+ def __init__(self):
85
+ self.schema_renderer = SchemaRenderer()
86
+ self.env = Environment(
87
+ loader=BaseLoader(),
88
+ autoescape=select_autoescape(["html", "xml"]),
89
+ trim_blocks=True,
90
+ lstrip_blocks=True,
91
+ )
92
+
93
+ # Register custom functions
94
+ self.env.globals.update(
95
+ {
96
+ "schema": self._schema_function,
97
+ "example": self._example_function,
98
+ "include": self._include_function,
99
+ "render": self._render_function,
100
+ }
101
+ )
102
+
103
+ def render_template(self, template_content: str, context: dict[str, Any]) -> str:
104
+ """Render template with context."""
105
+ # Store context for custom functions
106
+ self._current_context = context
107
+
108
+ try:
109
+ template = self.env.from_string(template_content)
110
+ return template.render(**context)
111
+ finally:
112
+ self._current_context = None
113
+
114
+ def _schema_function(self) -> str:
115
+ """{{ schema() }} - Render the component schema as markdown table."""
116
+ if not hasattr(self, "_current_context") or not self._current_context:
117
+ return "<!-- Schema not available -->"
118
+
119
+ schema = self._current_context.get("schema")
120
+ if not schema:
121
+ return "<!-- Schema not available -->"
122
+
123
+ return self.schema_renderer.render_schema(schema)
124
+
125
+ def _example_function(self, example_name: str) -> str:
126
+ """{{ example('name') }} - Render named example in terraform code block."""
127
+ if not hasattr(self, "_current_context") or not self._current_context:
128
+ return f"<!-- Example '{example_name}' not found -->"
129
+
130
+ examples = self._current_context.get("examples", {})
131
+ if example_name not in examples:
132
+ return f"<!-- Example '{example_name}' not found -->"
133
+
134
+ example_content = examples[example_name]
135
+ return f"```terraform\n{example_content}\n```"
136
+
137
+ def _include_function(self, filename: str) -> str:
138
+ """{{ include('filename') }} - Include static partial file."""
139
+ if not hasattr(self, "_current_context") or not self._current_context:
140
+ return f"<!-- Partial '{filename}' not found -->"
141
+
142
+ partials = self._current_context.get("partials", {})
143
+ if filename not in partials:
144
+ return f"<!-- Partial '{filename}' not found -->"
145
+
146
+ return partials[filename]
147
+
148
+ def _render_function(self, filename: str) -> str:
149
+ """{{ render('filename') }} - Render dynamic template partial."""
150
+ if not hasattr(self, "_current_context") or not self._current_context:
151
+ return f"<!-- Partial '{filename}' not found -->"
152
+
153
+ partials = self._current_context.get("partials", {})
154
+ if filename not in partials:
155
+ return f"<!-- Partial '{filename}' not found -->"
156
+
157
+ # Render the partial as a template with current context
158
+ partial_content = partials[filename]
159
+ try:
160
+ partial_template = self.env.from_string(partial_content)
161
+ return partial_template.render(**self._current_context)
162
+ except Exception as e:
163
+ return f"<!-- Error rendering partial '{filename}': {e} -->"
164
+
165
+
166
+ def create_template_context(component, bundle) -> dict[str, Any]:
167
+ """Create template rendering context from component and bundle."""
168
+ context = {
169
+ "name": bundle.name,
170
+ "type": _format_component_type(bundle.component_type),
171
+ "examples": bundle.load_examples(),
172
+ "partials": bundle.load_partials(),
173
+ }
174
+
175
+ # Add schema if available
176
+ try:
177
+ if hasattr(component, "get_schema"):
178
+ context["schema"] = component.get_schema()
179
+ else:
180
+ context["schema"] = None
181
+ except Exception:
182
+ context["schema"] = None
183
+
184
+ # Add component-specific context
185
+ if bundle.component_type == "function":
186
+ context.update(_create_function_context(context["schema"]))
187
+
188
+ return context
189
+
190
+
191
+ def _format_component_type(component_type: str) -> str:
192
+ """Format component type for display."""
193
+ type_mapping = {
194
+ "resource": "Resource",
195
+ "data_source": "Data Source",
196
+ "function": "Function",
197
+ }
198
+ return type_mapping.get(component_type, component_type.title())
199
+
200
+
201
+ def _create_function_context(schema) -> dict[str, Any]:
202
+ """Create additional context for function components."""
203
+ context = {}
204
+
205
+ if schema and hasattr(schema, "parameters"):
206
+ # Add function-specific fields
207
+ context["has_parameters"] = len(schema.parameters) > 0
208
+ context["parameter_count"] = len(schema.parameters)
209
+
210
+ if hasattr(schema, "return_type"):
211
+ context["return_type"] = schema.return_type
212
+
213
+ return context
214
+
215
+
216
+ # 🍲🥄📄🪄
plating/templates.py ADDED
@@ -0,0 +1,232 @@
1
+ #
2
+ # plating/templates.py
3
+ #
4
+ """Template processing and rendering for documentation generation."""
5
+
6
+ from typing import TYPE_CHECKING
7
+
8
+ from jinja2 import DictLoader, Environment, select_autoescape
9
+ from provide.foundation import pout
10
+
11
+ if TYPE_CHECKING:
12
+ from .plating import PlatingBundle
13
+ from .generator import DocsGenerator
14
+
15
+
16
+ class TemplateProcessor:
17
+ """Handles template generation and rendering."""
18
+
19
+ def __init__(self, generator: "DocsGenerator"):
20
+ self.generator = generator
21
+
22
+ def generate_missing_templates(self):
23
+ """
24
+ Generate missing template files - now a no-op since we use .plating bundles.
25
+
26
+ This method is kept for compatibility but .plating directories should contain
27
+ all necessary templates and are discovered automatically.
28
+ """
29
+ pout("📄 Using .plating bundles for templates (no template generation needed)")
30
+
31
+ def render_templates(self):
32
+ """Render all templates using plating bundles to generate documentation."""
33
+ # Ensure output directory exists
34
+ self.generator.output_dir.mkdir(parents=True, exist_ok=True)
35
+
36
+ # Discover all plating bundles
37
+ bundles = self.generator.plating_discovery.discover_bundles()
38
+
39
+ # Render provider index using built-in template
40
+ self._render_provider_index()
41
+
42
+ # Render each component with its plating bundle
43
+ for bundle in bundles:
44
+ self._render_component_from_bundle(bundle)
45
+
46
+ def _render_provider_index(self):
47
+ """Render the provider index page using built-in template."""
48
+ if not self.generator.provider_info:
49
+ return
50
+
51
+ # Built-in index template
52
+ index_template = """---
53
+ page_title: "{{ provider.short_name }} Provider"
54
+ description: |-
55
+ {{ provider.description }}
56
+ ---
57
+
58
+ # {{ provider.rendered_name }} Provider
59
+
60
+ {{ provider.description }}
61
+
62
+ ## Example Usage
63
+
64
+ ```terraform
65
+ provider "{{ provider.short_name }}" {
66
+ # Configuration options
67
+ }
68
+ ```
69
+
70
+ ## Schema
71
+
72
+ {{ provider_schema }}
73
+ """
74
+
75
+ # Set up Jinja2 environment with built-in template
76
+ env = Environment(
77
+ loader=DictLoader({"index.md.tmpl": index_template}),
78
+ autoescape=select_autoescape(["html", "xml"]),
79
+ )
80
+
81
+ template = env.get_template("index.md.tmpl")
82
+
83
+ # Get provider schema if available
84
+ provider_schema = ""
85
+ if (
86
+ hasattr(self.generator, "schema_processor")
87
+ and self.generator.schema_processor
88
+ ):
89
+ try:
90
+ provider_schema = self.generator.schema_processor.get_provider_schema()
91
+ if not provider_schema:
92
+ provider_schema = "No provider configuration required"
93
+ except Exception:
94
+ provider_schema = "Provider configuration documentation not available"
95
+ else:
96
+ provider_schema = "Provider configuration documentation not available"
97
+
98
+ rendered = template.render(
99
+ provider=self.generator.provider_info,
100
+ provider_schema=provider_schema,
101
+ )
102
+
103
+ (self.generator.output_dir / "index.md").write_text(rendered)
104
+
105
+ def _render_component_from_bundle(self, bundle: "PlatingBundle"):
106
+ """Render a single component using its plating bundle."""
107
+ # Load template and assets from bundle
108
+ template_content = bundle.load_main_template()
109
+ if not template_content:
110
+ pout(f"⚠️ No main template found for {bundle.name}")
111
+ return
112
+
113
+ examples = bundle.load_examples()
114
+ partials = bundle.load_partials()
115
+
116
+ # Get component info from generator
117
+ component_info = self._get_component_info(bundle)
118
+ if not component_info:
119
+ pout(f"⚠️ No component info found for {bundle.name}")
120
+ return
121
+
122
+ # Set up Jinja2 environment with custom functions
123
+ env = Environment(
124
+ loader=DictLoader(
125
+ {
126
+ "main.tmpl.md": template_content,
127
+ **partials, # Include all partials as available templates
128
+ }
129
+ ),
130
+ autoescape=select_autoescape(["html", "xml"]),
131
+ )
132
+
133
+ # Add custom template functions
134
+ env.globals["schema"] = lambda: component_info.get("schema_markdown", "")
135
+ env.globals["example"] = (
136
+ lambda name: f"```terraform\n{examples.get(name, '')}\n```"
137
+ )
138
+ env.globals["include"] = lambda filename: partials.get(filename, "")
139
+ env.globals["render"] = lambda filename: self._render_partial(
140
+ env, filename, component_info, examples, partials
141
+ )
142
+
143
+ # Render the template
144
+ template = env.get_template("main.tmpl.md")
145
+
146
+ # Create render context, excluding keys that conflict with template globals
147
+ render_context = {
148
+ k: v for k, v in component_info.items() if k not in ["schema"]
149
+ }
150
+ render_context.update(
151
+ {
152
+ "bundle_name": bundle.name,
153
+ "bundle_type": bundle.component_type.replace("_", " ").title(),
154
+ "examples": examples,
155
+ "partials": partials,
156
+ }
157
+ )
158
+
159
+ rendered = template.render(**render_context)
160
+
161
+ # Write to output directory
162
+ output_dir = self.generator.output_dir / f"{bundle.component_type}s"
163
+ output_dir.mkdir(parents=True, exist_ok=True)
164
+ (output_dir / f"{bundle.name}.md").write_text(rendered)
165
+
166
+ def _render_partial(
167
+ self,
168
+ env: Environment,
169
+ filename: str,
170
+ component_info: dict,
171
+ examples: dict,
172
+ partials: dict,
173
+ ) -> str:
174
+ """Render a partial template with full context."""
175
+ try:
176
+ partial_template = env.get_template(filename)
177
+ return partial_template.render(
178
+ name=component_info.get("name", ""),
179
+ type=component_info.get("type", ""),
180
+ schema=component_info.get("schema", {}),
181
+ examples=examples,
182
+ partials=partials,
183
+ **component_info,
184
+ )
185
+ except Exception as e:
186
+ return f"<!-- Error rendering partial {filename}: {e} -->"
187
+
188
+ def _get_component_info(self, bundle: "PlatingBundle") -> dict:
189
+ """Get component information based on bundle type and name."""
190
+ # Try both the bundle name as-is and with the pyvider_ prefix
191
+ possible_names = [bundle.name, f"pyvider_{bundle.name}"]
192
+
193
+ if bundle.component_type == "resource":
194
+ for name in possible_names:
195
+ resource_info = self.generator.resources.get(name)
196
+ if resource_info:
197
+ return {
198
+ "name": resource_info.name,
199
+ "type": resource_info.type,
200
+ "description": resource_info.description,
201
+ "schema": resource_info.schema,
202
+ "schema_markdown": resource_info.schema_markdown,
203
+ }
204
+ elif bundle.component_type == "data_source":
205
+ for name in possible_names:
206
+ ds_info = self.generator.data_sources.get(name)
207
+ if ds_info:
208
+ return {
209
+ "name": ds_info.name,
210
+ "type": ds_info.type,
211
+ "description": ds_info.description,
212
+ "schema": ds_info.schema,
213
+ "schema_markdown": ds_info.schema_markdown,
214
+ }
215
+ elif bundle.component_type == "function":
216
+ for name in possible_names:
217
+ func_info = self.generator.functions.get(name)
218
+ if func_info:
219
+ return {
220
+ "name": func_info.name,
221
+ "description": func_info.description,
222
+ "summary": func_info.summary,
223
+ "signature_markdown": func_info.signature_markdown,
224
+ "arguments_markdown": func_info.arguments_markdown,
225
+ "variadic_argument_markdown": func_info.variadic_argument_markdown,
226
+ "has_variadic": func_info.has_variadic,
227
+ }
228
+
229
+ return {}
230
+
231
+
232
+ # 🍲🥄📄🪄