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

1"""Demo script showcasing FastBlocks syntax support and autocomplete features.""" 

2 

3import asyncio 

4from contextlib import suppress 

5from pathlib import Path 

6from uuid import UUID 

7 

8from acb.config import Settings 

9from acb.depends import depends 

10 

11from .language_server import FastBlocksLanguageClient 

12from .syntax_support import FastBlocksSyntaxSupport 

13 

14 

15class SyntaxDemoSettings(Settings): 

16 """Settings for syntax demo.""" 

17 

18 # Required ACB 0.19.0+ metadata 

19 MODULE_ID: UUID = UUID("01937d87-3456-789a-bcde-123456789def") 

20 MODULE_STATUS: str = "stable" 

21 

22 # Demo settings 

23 demo_templates_dir: str = "templates/demo" 

24 generate_sample_templates: bool = True 

25 run_interactive_demo: bool = False 

26 

27 

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) 

31 

32 templates = { 

33 "basic.fb.html": """[# FastBlocks Basic Template Demo #] 

34[% extends "base.html" %] 

35 

36[% block title %]Welcome to FastBlocks[% endblock %] 

37 

38[% block content %] 

39 <div class="hero"> 

40 <h1>[[ title | default("Welcome") ]]</h1> 

41 <p>[[ user.name if user else "Guest" ]]</p> 

42 

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> 

56 

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> 

63 

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 ]] 

70 

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 %] 

78 

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 %]""", 

87 

88 "advanced.fb.html": """[# Advanced FastBlocks Features Demo #] 

89[% extends "layouts/dashboard.html" %] 

90 

91[% block head %] 

92 [[ phosphor_stylesheet_links() | safe ]] 

93 [[ webawesome_stylesheet_links() | safe ]] 

94[% endblock %] 

95 

96[% block dashboard_content %] 

97 [# Error handling with syntax validation #] 

98 [% set user_status = get_config("user.status") | default("active") %] 

99 

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> 

107 

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> 

124 

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> 

133 

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> 

148 

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> 

155 

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> 

159 

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> 

168 

169 [# User management table #] 

170 <div class="widget users"> 

171 <h3> 

172 [[ ph_icon("users-three", variant="fill") | safe ]] 

173 Recent Users 

174 </h3> 

175 

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> 

229 

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 ]] 

237 

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 %]""", 

247 

248 "error_examples.fb.html": """[# Template with intentional errors for syntax checking demo #] 

249[% extends "base.html" %] 

250 

251[% block content %] 

252 [# Missing closing delimiter #] 

253 [[ user.name 

254 

255 [# Unknown filter #] 

256 [[ content | unknown_filter ]] 

257 

258 [# Unbalanced delimiters #] 

259 [[ items | length ]] items found ]] 

260 

261 [# Unknown function #] 

262 [[ missing_function("test") ]] 

263 

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 } 

270 

271 for filename, content in templates.items(): 

272 (demo_dir / filename).write_text(content) 

273 

274 return templates 

275 

276 

277async def demonstrate_syntax_checking(): 

278 """Demonstrate syntax checking capabilities.""" 

279 print("🔍 FastBlocks Syntax Checking Demo") 

280 print("=" * 50) 

281 

282 syntax_support = FastBlocksSyntaxSupport() 

283 

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 %]""" 

291 

292 print("\n📝 Checking template with errors:") 

293 print(error_template) 

294 print("\n❌ Syntax errors found:") 

295 

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}") 

301 

302 # Test valid template 

303 valid_template = """[[ user.name | default("Guest") ]] 

304[% if user %] 

305 [[ ph_icon("user", variant="fill") | safe ]] 

306[% endif %]""" 

307 

308 print(f"\n📝 Checking valid template:") 

309 print(valid_template) 

310 

311 errors = syntax_support.check_syntax(valid_template) 

312 if not errors: 

313 print("\n✅ No syntax errors found!") 

314 

315 

316async def demonstrate_autocomplete(): 

317 """Demonstrate autocomplete capabilities.""" 

318 print("\n🔮 FastBlocks Autocomplete Demo") 

319 print("=" * 50) 

320 

321 syntax_support = FastBlocksSyntaxSupport() 

322 

323 test_cases = [ 

324 ("[[|", "variable context"), 

325 ("[%|", "block context"), 

326 ("{{ user.name ||", "filter context"), 

327 ("{{ render_|", "function context"), 

328 ] 

329 

330 for content, context_name in test_cases: 

331 cursor_pos = content.find("|") 

332 test_content = content.replace("|", "") 

333 

334 print(f"\n📍 Context: {context_name}") 

335 print(f"Content: '{test_content}' (cursor at position {cursor_pos})") 

336 

337 completions = syntax_support.get_completions(test_content, 0, cursor_pos) 

338 

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}") 

344 

345 

346async def demonstrate_language_server(): 

347 """Demonstrate language server functionality.""" 

348 print("\n🖥️ FastBlocks Language Server Demo") 

349 print("=" * 50) 

350 

351 client = FastBlocksLanguageClient() 

352 

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 %]""" 

359 

360 print("\n📄 Opening test document:") 

361 print(test_content) 

362 

363 await client.open_document(test_uri, test_content) 

364 

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', '')})") 

370 

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', '')}") 

376 

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") 

385 

386 

387async def demonstrate_formatting(): 

388 """Demonstrate template formatting.""" 

389 print("\n📐 FastBlocks Template Formatting Demo") 

390 print("=" * 50) 

391 

392 syntax_support = FastBlocksSyntaxSupport() 

393 

394 unformatted = """[[ user.name ]] 

395[% if user %] 

396[[ user.email ]] 

397[% for item in items %] 

398<div>[[ item.name ]]</div> 

399[% endfor %] 

400[% endif %]""" 

401 

402 print("\n📝 Unformatted template:") 

403 print(unformatted) 

404 

405 formatted = syntax_support.format_template(unformatted) 

406 

407 print("\n✨ Formatted template:") 

408 print(formatted) 

409 

410 

411async def run_comprehensive_demo(): 

412 """Run comprehensive FastBlocks syntax support demo.""" 

413 print("🚀 FastBlocks Syntax Support Comprehensive Demo") 

414 print("=" * 60) 

415 

416 # Initialize components 

417 settings = SyntaxDemoSettings() 

418 

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}") 

423 

424 # Run demonstrations 

425 await demonstrate_syntax_checking() 

426 await demonstrate_autocomplete() 

427 await demonstrate_language_server() 

428 await demonstrate_formatting() 

429 

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") 

436 

437 

438if __name__ == "__main__": 

439 with suppress(Exception): 

440 depends.set(SyntaxDemoSettings()) 

441 

442 asyncio.run(run_comprehensive_demo())