Coverage for fastblocks/adapters/templates/syntax_demo.py: 0%
106 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-29 00:51 -0700
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-29 00:51 -0700
1"""Demo script showcasing FastBlocks syntax support and autocomplete features."""
3import asyncio
4from contextlib import suppress
5from pathlib import Path
6from uuid import UUID
8from acb.config import Settings
9from acb.depends import depends
11from .language_server import FastBlocksLanguageClient
12from .syntax_support import FastBlocksSyntaxSupport
15class SyntaxDemoSettings(Settings):
16 """Settings for syntax demo."""
18 # Required ACB 0.19.0+ metadata
19 MODULE_ID: UUID = UUID("01937d87-3456-789a-bcde-123456789def")
20 MODULE_STATUS: str = "stable"
22 # Demo settings
23 demo_templates_dir: str = "templates/demo"
24 generate_sample_templates: bool = True
25 run_interactive_demo: bool = False
28async def create_sample_templates(demo_dir: Path) -> dict[str, str]:
29 """Create sample FastBlocks templates for demonstration."""
30 demo_dir.mkdir(parents=True, exist_ok=True)
32 templates = {
33 "basic.fb.html": """[# FastBlocks Basic Template Demo #]
34[% extends "base.html" %]
36[% block title %]Welcome to FastBlocks[% endblock %]
38[% block content %]
39 <div class="hero">
40 <h1>[[ title | default("Welcome") ]]</h1>
41 <p>[[ user.name if user else "Guest" ]]</p>
43 [# User navigation with icons #]
44 [% if user %]
45 <nav class="user-nav">
46 [[ ph_icon("user-circle", variant="fill", size="lg") | safe ]]
47 <span>[[ user.email ]]</span>
48 [[ ph_interactive("sign-out", action="logout()") | safe ]]
49 </nav>
50 [% else %]
51 <a href="/login" class="login-btn">
52 [[ ph_button_icon("sign-in", "Sign In", variant="bold") | safe ]]
53 </a>
54 [% endif %]
55 </div>
57 [# Dynamic content section #]
58 <section class="content">
59 [% for item in items %]
60 <article class="item">
61 <h2>[[ item.title | title ]]</h2>
62 <p>[[ item.description | truncate(150) ]]</p>
64 [# Component rendering #]
65 [[ render_component("user_card", {
66 "name": item.author.name,
67 "email": item.author.email,
68 "avatar_url": item.author.avatar | cloudflare_img(width=64, height=64)
69 }) | safe ]]
71 <div class="actions">
72 [[ ph_icon("heart", interactive=True, color="danger") | safe ]]
73 [[ ph_icon("bookmark", interactive=True) | safe ]]
74 [[ ph_icon("share", interactive=True) | safe ]]
75 </div>
76 </article>
77 [% endfor %]
79 [% if not items %]
80 <div class="empty-state">
81 [[ ph_icon("inbox", size="3x", color="muted") | safe ]]
82 <p>No items found</p>
83 </div>
84 [% endif %]
85 </section>
86[% endblock %]""",
88 "advanced.fb.html": """[# Advanced FastBlocks Features Demo #]
89[% extends "layouts/dashboard.html" %]
91[% block head %]
92 [[ phosphor_stylesheet_links() | safe ]]
93 [[ webawesome_stylesheet_links() | safe ]]
94[% endblock %]
96[% block dashboard_content %]
97 [# Error handling with syntax validation #]
98 [% set user_status = get_config("user.status") | default("active") %]
100 <div class="dashboard-grid">
101 [# Widget with duotone icons #]
102 <div class="widget stats">
103 <h3>
104 [[ ph_duotone("chart-bar", primary_color="#007bff", secondary_color="#e3f2fd") | safe ]]
105 Analytics
106 </h3>
108 <div class="metrics">
109 [% for metric in dashboard_metrics %]
110 <div class="metric">
111 <span class="value">[[ metric.value | number_format ]]</span>
112 <span class="label">[[ metric.label ]]</span>
113 [% if metric.trend == "up" %]
114 [[ ph_icon("trend-up", color="success", size="sm") | safe ]]
115 [% elif metric.trend == "down" %]
116 [[ ph_icon("trend-down", color="danger", size="sm") | safe ]]
117 [% else %]
118 [[ ph_icon("minus", color="muted", size="sm") | safe ]]
119 [% endif %]
120 </div>
121 [% endfor %]
122 </div>
123 </div>
125 [# Interactive notifications widget #]
126 <div class="widget notifications">
127 <h3>
128 [[ ph_interactive("bell", variant="fill",
129 action="toggleNotifications()",
130 class="notification-toggle") | safe ]]
131 Notifications ([[ notifications | length ]])
132 </h3>
134 [% for notification in notifications[:5] %]
135 <div class="notification [[ 'unread' if not notification.read else '' ]]">
136 [[ ph_icon(notification.icon, variant="regular", size="sm") | safe ]]
137 <div class="content">
138 <p>[[ notification.message ]]</p>
139 <small>[[ notification.created_at | timeago ]]</small>
140 </div>
141 [% if not notification.read %]
142 [[ ph_interactive("check", size="xs",
143 action="markAsRead(" ~ notification.id ~ ")") | safe ]]
144 [% endif %]
145 </div>
146 [% endfor %]
147 </div>
149 [# File upload with progress #]
150 <div class="widget upload">
151 <h3>
152 [[ ph_icon("upload-simple", variant="bold") | safe ]]
153 File Upload
154 </h3>
156 <div id="upload-zone" class="upload-zone">
157 [[ ph_icon("cloud-arrow-up", size="2x", color="primary") | safe ]]
158 <p>Drag files here or click to browse</p>
160 [% if upload_progress %]
161 <div class="progress-bar">
162 <div class="progress" style="width: [[ upload_progress ]]%"></div>
163 </div>
164 <small>[[ upload_progress ]]% complete</small>
165 [% endif %]
166 </div>
167 </div>
169 [# User management table #]
170 <div class="widget users">
171 <h3>
172 [[ ph_icon("users-three", variant="fill") | safe ]]
173 Recent Users
174 </h3>
176 <table class="users-table">
177 <thead>
178 <tr>
179 <th>User</th>
180 <th>Status</th>
181 <th>Last Active</th>
182 <th>Actions</th>
183 </tr>
184 </thead>
185 <tbody>
186 [% for user in recent_users %]
187 <tr>
188 <td>
189 <div class="user-info">
190 <img src="[[ user.avatar | cloudflare_img(width=32, height=32) ]]"
191 alt="[[ user.name ]]" class="avatar">
192 <div>
193 <strong>[[ user.name ]]</strong>
194 <small>[[ user.email ]]</small>
195 </div>
196 </div>
197 </td>
198 <td>
199 <span class="status [[ user.status ]]">
200 [% if user.status == "online" %]
201 [[ ph_icon("circle", variant="fill", color="success", size="xs") | safe ]]
202 [% elif user.status == "away" %]
203 [[ ph_icon("circle", variant="fill", color="warning", size="xs") | safe ]]
204 [% else %]
205 [[ ph_icon("circle", color="muted", size="xs") | safe ]]
206 [% endif %]
207 [[ user.status | title ]]
208 </span>
209 </td>
210 <td>[[ user.last_active | timeago ]]</td>
211 <td>
212 <div class="actions">
213 [[ ph_interactive("envelope", size="sm",
214 action="sendMessage('" ~ user.id ~ "')") | safe ]]
215 [[ ph_interactive("gear", size="sm",
216 action="editUser('" ~ user.id ~ "')") | safe ]]
217 [% if current_user.is_admin %]
218 [[ ph_interactive("trash", size="sm", color="danger",
219 action="confirmDelete('" ~ user.id ~ "')") | safe ]]
220 [% endif %]
221 </div>
222 </td>
223 </tr>
224 [% endfor %]
225 </tbody>
226 </table>
227 </div>
228 </div>
230 [# Custom component example #]
231 <div class="custom-components">
232 [[ render_component("chart", {
233 "type": "line",
234 "data": chart_data,
235 "options": {"responsive": True, "animation": True}
236 }) | safe ]]
238 [[ render_component("data_table", {
239 "columns": table_columns,
240 "data": table_data,
241 "searchable": True,
242 "sortable": True,
243 "pagination": True
244 }) | safe ]]
245 </div>
246[% endblock %]""",
248 "error_examples.fb.html": """[# Template with intentional errors for syntax checking demo #]
249[% extends "base.html" %]
251[% block content %]
252 [# Missing closing delimiter #]
253 [[ user.name
255 [# Unknown filter #]
256 [[ content | unknown_filter ]]
258 [# Unbalanced delimiters #]
259 [[ items | length ]] items found ]]
261 [# Unknown function #]
262 [[ missing_function("test") ]]
264 [# Correct syntax examples #]
265 [[ user.name | default("Anonymous") ]]
266 [[ ph_icon("check", variant="fill", color="success") | safe ]]
267 [[ render_component("user_card", {"name": user.name}) | safe ]]
268[% endblock %]""",
269 }
271 for filename, content in templates.items():
272 (demo_dir / filename).write_text(content)
274 return templates
277async def demonstrate_syntax_checking():
278 """Demonstrate syntax checking capabilities."""
279 print("🔍 FastBlocks Syntax Checking Demo")
280 print("=" * 50)
282 syntax_support = FastBlocksSyntaxSupport()
284 # Test template with errors
285 error_template = """[[ user.name
286[[ content | unknown_filter ]]
287[[ items | length ]] items ]]
288[% if user %]
289 [[ missing_function() ]]
290[% endblock %]"""
292 print("\n📝 Checking template with errors:")
293 print(error_template)
294 print("\n❌ Syntax errors found:")
296 errors = syntax_support.check_syntax(error_template)
297 for error in errors:
298 print(f" Line {error.line + 1}, Col {error.column + 1}: {error.message}")
299 if error.fix_suggestion:
300 print(f" 💡 Fix: {error.fix_suggestion}")
302 # Test valid template
303 valid_template = """[[ user.name | default("Guest") ]]
304[% if user %]
305 [[ ph_icon("user", variant="fill") | safe ]]
306[% endif %]"""
308 print(f"\n📝 Checking valid template:")
309 print(valid_template)
311 errors = syntax_support.check_syntax(valid_template)
312 if not errors:
313 print("\n✅ No syntax errors found!")
316async def demonstrate_autocomplete():
317 """Demonstrate autocomplete capabilities."""
318 print("\n🔮 FastBlocks Autocomplete Demo")
319 print("=" * 50)
321 syntax_support = FastBlocksSyntaxSupport()
323 test_cases = [
324 ("[[|", "variable context"),
325 ("[%|", "block context"),
326 ("{{ user.name ||", "filter context"),
327 ("{{ render_|", "function context"),
328 ]
330 for content, context_name in test_cases:
331 cursor_pos = content.find("|")
332 test_content = content.replace("|", "")
334 print(f"\n📍 Context: {context_name}")
335 print(f"Content: '{test_content}' (cursor at position {cursor_pos})")
337 completions = syntax_support.get_completions(test_content, 0, cursor_pos)
339 print("💭 Completions:")
340 for completion in completions[:5]: # Show top 5
341 print(f" {completion.kind}: {completion.label}")
342 if completion.detail:
343 print(f" {completion.detail}")
346async def demonstrate_language_server():
347 """Demonstrate language server functionality."""
348 print("\n🖥️ FastBlocks Language Server Demo")
349 print("=" * 50)
351 client = FastBlocksLanguageClient()
353 # Test document
354 test_uri = "file:///demo/test.fb.html"
355 test_content = """[[ user.name | default("Guest") ]]
356[% if user %]
357 [[ ph_icon("user", variant="fill") | safe ]]
358[% endif %]"""
360 print("\n📄 Opening test document:")
361 print(test_content)
363 await client.open_document(test_uri, test_content)
365 # Test completions
366 print("\n💭 Getting completions at line 1, character 5:")
367 completions = await client.get_completions(test_uri, 1, 5)
368 for completion in completions[:3]:
369 print(f" {completion.get('label', '')} ({completion.get('kind', '')})")
371 # Test hover
372 print("\n🖱️ Getting hover info for 'ph_icon':")
373 hover = await client.get_hover(test_uri, 2, 8)
374 if hover and hover.get("contents"):
375 print(f" {hover['contents'].get('value', '')}")
377 # Test diagnostics
378 print("\n🔍 Current diagnostics:")
379 diagnostics = client.get_diagnostics(test_uri)
380 if diagnostics:
381 for diag in diagnostics:
382 print(f" {diag.get('severity', '')}: {diag.get('message', '')}")
383 else:
384 print(" No diagnostics found")
387async def demonstrate_formatting():
388 """Demonstrate template formatting."""
389 print("\n📐 FastBlocks Template Formatting Demo")
390 print("=" * 50)
392 syntax_support = FastBlocksSyntaxSupport()
394 unformatted = """[[ user.name ]]
395[% if user %]
396[[ user.email ]]
397[% for item in items %]
398<div>[[ item.name ]]</div>
399[% endfor %]
400[% endif %]"""
402 print("\n📝 Unformatted template:")
403 print(unformatted)
405 formatted = syntax_support.format_template(unformatted)
407 print("\n✨ Formatted template:")
408 print(formatted)
411async def run_comprehensive_demo():
412 """Run comprehensive FastBlocks syntax support demo."""
413 print("🚀 FastBlocks Syntax Support Comprehensive Demo")
414 print("=" * 60)
416 # Initialize components
417 settings = SyntaxDemoSettings()
419 if settings.generate_sample_templates:
420 demo_dir = Path(settings.demo_templates_dir)
421 templates = await create_sample_templates(demo_dir)
422 print(f"📁 Created {len(templates)} sample templates in {demo_dir}")
424 # Run demonstrations
425 await demonstrate_syntax_checking()
426 await demonstrate_autocomplete()
427 await demonstrate_language_server()
428 await demonstrate_formatting()
430 print("\n🎉 Demo completed!")
431 print("\n📚 Available CLI commands:")
432 print(" python -m fastblocks syntax-check <file>")
433 print(" python -m fastblocks format-template <file>")
434 print(" python -m fastblocks generate-ide-config")
435 print(" python -m fastblocks start-language-server")
438if __name__ == "__main__":
439 with suppress(Exception):
440 depends.set(SyntaxDemoSettings())
442 asyncio.run(run_comprehensive_demo())