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.
- plating/__init__.py +27 -0
- plating/_version.py +53 -0
- plating/adorner/__init__.py +16 -0
- plating/adorner/adorner.py +115 -0
- plating/adorner/api.py +24 -0
- plating/adorner/finder.py +22 -0
- plating/adorner/templates.py +199 -0
- plating/cli.py +317 -0
- plating/config.py +106 -0
- plating/error_handling.py +125 -0
- plating/errors.py +142 -0
- plating/generator.py +192 -0
- plating/linting.py +235 -0
- plating/models.py +75 -0
- plating/plater.py +487 -0
- plating/plating.py +207 -0
- plating/schema.py +502 -0
- plating/template_filters.py +117 -0
- plating/template_functions.py +216 -0
- plating/templates.py +232 -0
- plating/test_runner.py +1005 -0
- plating/types.py +71 -0
- plating-0.0.0.dev0.dist-info/METADATA +223 -0
- plating-0.0.0.dev0.dist-info/RECORD +28 -0
- plating-0.0.0.dev0.dist-info/WHEEL +5 -0
- plating-0.0.0.dev0.dist-info/entry_points.txt +2 -0
- plating-0.0.0.dev0.dist-info/licenses/LICENSE +201 -0
- plating-0.0.0.dev0.dist-info/top_level.txt +1 -0
|
@@ -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
|
+
# 🍲🥄📄🪄
|