poml 0.0.5 → 0.0.7

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/docs/tutorial/advanced/performance.md +695 -0
  3. data/docs/tutorial/advanced/tool-registration.md +776 -0
  4. data/docs/tutorial/basic-usage.md +351 -0
  5. data/docs/tutorial/components/chat-components.md +552 -0
  6. data/docs/tutorial/components/formatting.md +623 -0
  7. data/docs/tutorial/components/index.md +366 -0
  8. data/docs/tutorial/components/media-components.md +259 -0
  9. data/docs/tutorial/components/schema-components.md +668 -0
  10. data/docs/tutorial/index.md +184 -0
  11. data/docs/tutorial/output-formats.md +688 -0
  12. data/docs/tutorial/quickstart.md +30 -0
  13. data/docs/tutorial/template-engine.md +540 -0
  14. data/examples/303_new_component_syntax.poml +45 -0
  15. data/lib/poml/components/base.rb +150 -3
  16. data/lib/poml/components/content.rb +10 -3
  17. data/lib/poml/components/data.rb +539 -19
  18. data/lib/poml/components/examples.rb +235 -1
  19. data/lib/poml/components/formatting.rb +184 -18
  20. data/lib/poml/components/layout.rb +7 -2
  21. data/lib/poml/components/lists.rb +69 -35
  22. data/lib/poml/components/meta.rb +191 -6
  23. data/lib/poml/components/output_schema.rb +103 -0
  24. data/lib/poml/components/template.rb +72 -61
  25. data/lib/poml/components/text.rb +30 -1
  26. data/lib/poml/components/tool.rb +81 -0
  27. data/lib/poml/components/tool_definition.rb +427 -0
  28. data/lib/poml/components/tools.rb +14 -0
  29. data/lib/poml/components/utilities.rb +34 -18
  30. data/lib/poml/components.rb +29 -0
  31. data/lib/poml/context.rb +19 -4
  32. data/lib/poml/parser.rb +90 -64
  33. data/lib/poml/renderer.rb +191 -9
  34. data/lib/poml/template_engine.rb +138 -13
  35. data/lib/poml/version.rb +1 -1
  36. data/lib/poml.rb +16 -1
  37. data/readme.md +154 -27
  38. metadata +34 -4
  39. data/TUTORIAL.md +0 -987
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5ba78c3aed937b9021b7d60fa5615fce4e75067ba607226fcb1c98674e720cf
4
- data.tar.gz: 366238332f24e1f4309c64ff8da6fe52822747727e411d3925e92902b4b0ab2c
3
+ metadata.gz: 2b093fb2077a5a12052a817a3b71038b81c97892bd8ba888165bbbed56cf21d4
4
+ data.tar.gz: 46cb25b278be930f116c97b46263dead78057289f200274daee79320045b8984
5
5
  SHA512:
6
- metadata.gz: f54b42cd18875cb6200eda77ea3f5be6ef21bbb5a8adcc35e3d349c4b7b6c8e8194f9fccc7c618bfe04c44163bcd8363816f04658f8b5ad279958aa6b9a21e64
7
- data.tar.gz: caab1eb1555492e0bcc7342baa2b7c5a48c1a5606a172dee3d4983b617572940ce04d5cfda2b1d966da073cec0bc35b71c060435a5354126d265b3e43148fc5f
6
+ metadata.gz: 66b01f839ae216447b86d45c8cb49d016e569e3d42fc91bd82e777abdb4fab466644d4610756f4cedb1bdd4e6171f6e2c177b3773afd2ad5a08c4b9328e9c44f
7
+ data.tar.gz: 2f107a45b2ee752c6303b6cc64d385bb3c77aa345c9697eeb0942a4221c1984054d4bd256658358b6732d37b44e0ff70112e6d9b84c8b39a6c4392bb93c80938
@@ -0,0 +1,695 @@
1
+ # Performance Optimization
2
+
3
+ Learn how to optimize POML processing for speed, memory usage, and scalability in production environments.
4
+
5
+ ## Performance Fundamentals
6
+
7
+ ### Component Performance Characteristics
8
+
9
+ | Component Type | Processing Speed | Memory Usage | I/O Impact |
10
+ |---------------|------------------|--------------|------------|
11
+ | Text (`<p>`, `<b>`, `<i>`) | ⚡⚡⚡⚡⚡ | Very Low | None |
12
+ | Lists (`<list>`, `<item>`) | ⚡⚡⚡⚡ | Low | None |
13
+ | Templates (`{{}}`, `<if>`, `<for>`) | ⚡⚡⚡ | Medium | None |
14
+ | File Reading (`<file>`, `<img>`) | ⚡⚡ | High | High |
15
+ | External Data (`<table>` with URLs) | ⚡ | High | Very High |
16
+
17
+ ### Optimization Principles
18
+
19
+ 1. **Minimize I/O Operations** - Cache file reads and external data
20
+ 2. **Limit Loop Iterations** - Use conditions to restrict processing
21
+ 3. **Optimize Template Variables** - Pre-compute complex expressions
22
+ 4. **Choose Appropriate Output Formats** - Match format to use case
23
+ 5. **Enable Caching** - Reuse processed templates
24
+
25
+ ## Template Engine Optimization
26
+
27
+ ### Variable Pre-computation
28
+
29
+ ```ruby
30
+ # Slow: Complex computation in template
31
+ <poml>
32
+ <for variable="item" items="{{items}}">
33
+ <p>{{item.complex_calculation.expensive_method}}</p>
34
+ </for>
35
+ </poml>
36
+
37
+ # Fast: Pre-compute values
38
+ context = {
39
+ 'items' => items.map do |item|
40
+ item.merge('display_value' => item.complex_calculation.expensive_method)
41
+ end
42
+ }
43
+
44
+ <poml>
45
+ <for variable="item" items="{{items}}">
46
+ <p>{{item.display_value}}</p>
47
+ </for>
48
+ </poml>
49
+ ```
50
+
51
+ ### Conditional Optimization
52
+
53
+ ```ruby
54
+ # Slow: Check condition inside loop
55
+ <poml>
56
+ <for variable="item" items="{{all_items}}">
57
+ <if condition="{{item.visible}}">
58
+ <p>{{item.name}}</p>
59
+ </if>
60
+ </for>
61
+ </poml>
62
+
63
+ # Fast: Filter before processing
64
+ context = {
65
+ 'visible_items' => all_items.select(&:visible)
66
+ }
67
+
68
+ <poml>
69
+ <for variable="item" items="{{visible_items}}">
70
+ <p>{{item.name}}</p>
71
+ </for>
72
+ </poml>
73
+ ```
74
+
75
+ ### Loop Limiting
76
+
77
+ ```ruby
78
+ # Potentially slow: Unlimited iteration
79
+ <poml>
80
+ <for variable="item" items="{{large_dataset}}">
81
+ <p>{{item.description}}</p>
82
+ </for>
83
+ </poml>
84
+
85
+ # Optimized: Limit iteration count
86
+ <poml>
87
+ <for variable="item" items="{{large_dataset}}" limit="100">
88
+ <p>{{item.description}}</p>
89
+ </for>
90
+ </poml>
91
+
92
+ # Or pre-limit in context
93
+ context = {
94
+ 'limited_items' => large_dataset.first(100)
95
+ }
96
+ ```
97
+
98
+ ## File Operation Optimization
99
+
100
+ ### Caching File Reads
101
+
102
+ ```ruby
103
+ class CachedPomlProcessor
104
+ def initialize
105
+ @file_cache = {}
106
+ @poml = POML.new
107
+ end
108
+
109
+ def process_with_cache(template_path, context = {})
110
+ # Cache file content
111
+ cached_files = {}
112
+
113
+ # Pre-read files referenced in context
114
+ context.each do |key, value|
115
+ if key.end_with?('_file') && File.exist?(value)
116
+ cached_files[value] = File.read(value) unless @file_cache[value]
117
+ @file_cache[value] ||= cached_files[value]
118
+ end
119
+ end
120
+
121
+ # Process with cached context
122
+ enhanced_context = context.merge(cached_files)
123
+ @poml.process_file(template_path, variables: enhanced_context)
124
+ end
125
+ end
126
+ ```
127
+
128
+ ### Lazy File Loading
129
+
130
+ ```ruby
131
+ # Template using lazy loading
132
+ <poml>
133
+ <if condition="{{show_details}}">
134
+ <file src="{{details_file}}" />
135
+ </if>
136
+
137
+ <if condition="{{include_logs}}">
138
+ <file src="{{log_file}}" maxLines="50" />
139
+ </if>
140
+ </poml>
141
+
142
+ # Context controls loading
143
+ context = {
144
+ 'show_details' => user_requested_details,
145
+ 'include_logs' => debug_mode_enabled,
146
+ 'details_file' => 'path/to/details.txt',
147
+ 'log_file' => 'path/to/debug.log'
148
+ }
149
+ ```
150
+
151
+ ### Streaming Large Files
152
+
153
+ ```ruby
154
+ class StreamingFileProcessor
155
+ def process_large_file(file_path, max_size: 1_000_000)
156
+ if File.size(file_path) > max_size
157
+ # Stream first portion
158
+ content = File.open(file_path) do |file|
159
+ file.read(max_size) + "\n... (truncated)"
160
+ end
161
+ else
162
+ content = File.read(file_path)
163
+ end
164
+
165
+ content
166
+ end
167
+ end
168
+ ```
169
+
170
+ ## Memory Management
171
+
172
+ ### Efficient Data Structures
173
+
174
+ ```ruby
175
+ # Memory-heavy: Full object loading
176
+ context = {
177
+ 'users' => User.all.map(&:to_hash) # Loads all data
178
+ }
179
+
180
+ # Memory-efficient: Lazy loading
181
+ context = {
182
+ 'users' => User.limit(50).pluck(:id, :name, :email) # Minimal data
183
+ }
184
+
185
+ # For templates
186
+ <poml>
187
+ <for variable="user" items="{{users}}">
188
+ <p>{{user[1]}} ({{user[2]}})</p> <!-- name, email -->
189
+ </for>
190
+ </poml>
191
+ ```
192
+
193
+ ### Garbage Collection Optimization
194
+
195
+ ```ruby
196
+ class OptimizedProcessor
197
+ def process_batch(templates, contexts)
198
+ results = []
199
+
200
+ templates.each_with_index do |template, index|
201
+ result = POML.new.process(
202
+ markup: template,
203
+ variables: contexts[index]
204
+ )
205
+ results << result
206
+
207
+ # Force GC every 100 items for large batches
208
+ GC.start if (index + 1) % 100 == 0
209
+ end
210
+
211
+ results
212
+ end
213
+ end
214
+ ```
215
+
216
+ ### Memory Pooling
217
+
218
+ ```ruby
219
+ class PomlPool
220
+ def initialize(pool_size: 5)
221
+ @pool = Array.new(pool_size) { POML.new }
222
+ @available = @pool.dup
223
+ @mutex = Mutex.new
224
+ end
225
+
226
+ def process(template, context = {})
227
+ processor = acquire_processor
228
+ begin
229
+ processor.process(markup: template, variables: context)
230
+ ensure
231
+ release_processor(processor)
232
+ end
233
+ end
234
+
235
+ private
236
+
237
+ def acquire_processor
238
+ @mutex.synchronize do
239
+ @available.pop || POML.new
240
+ end
241
+ end
242
+
243
+ def release_processor(processor)
244
+ @mutex.synchronize do
245
+ @available.push(processor) if @available.size < @pool.size
246
+ end
247
+ end
248
+ end
249
+ ```
250
+
251
+ ## Caching Strategies
252
+
253
+ ### Template Caching
254
+
255
+ ```ruby
256
+ class TemplateCache
257
+ def initialize(max_size: 100, ttl: 3600)
258
+ @cache = {}
259
+ @access_times = {}
260
+ @max_size = max_size
261
+ @ttl = ttl
262
+ end
263
+
264
+ def get_or_process(template_path, context = {})
265
+ cache_key = generate_key(template_path, context)
266
+
267
+ if cached = get_cached(cache_key)
268
+ return cached
269
+ end
270
+
271
+ result = POML.new.process_file(template_path, variables: context)
272
+ store_cached(cache_key, result)
273
+ result
274
+ end
275
+
276
+ private
277
+
278
+ def generate_key(template_path, context)
279
+ "#{template_path}:#{context.hash}"
280
+ end
281
+
282
+ def get_cached(key)
283
+ return nil unless @cache[key]
284
+ return nil if expired?(key)
285
+
286
+ @access_times[key] = Time.now
287
+ @cache[key]
288
+ end
289
+
290
+ def store_cached(key, value)
291
+ evict_if_needed
292
+ @cache[key] = value
293
+ @access_times[key] = Time.now
294
+ end
295
+
296
+ def expired?(key)
297
+ Time.now - @access_times[key] > @ttl
298
+ end
299
+
300
+ def evict_if_needed
301
+ return if @cache.size < @max_size
302
+
303
+ # Remove least recently used
304
+ lru_key = @access_times.min_by { |k, v| v }.first
305
+ @cache.delete(lru_key)
306
+ @access_times.delete(lru_key)
307
+ end
308
+ end
309
+ ```
310
+
311
+ ### Context Caching
312
+
313
+ ```ruby
314
+ class ContextCache
315
+ def initialize
316
+ @computed_values = {}
317
+ end
318
+
319
+ def enhanced_context(base_context)
320
+ base_context.merge(computed_values(base_context))
321
+ end
322
+
323
+ private
324
+
325
+ def computed_values(context)
326
+ cache_key = context.hash
327
+
328
+ @computed_values[cache_key] ||= {
329
+ 'formatted_date' => format_date(context['date']),
330
+ 'user_summary' => summarize_user(context['user']),
331
+ 'processed_items' => process_items(context['items'])
332
+ }
333
+ end
334
+
335
+ def format_date(date)
336
+ return '' unless date
337
+ Date.parse(date.to_s).strftime('%B %d, %Y')
338
+ end
339
+
340
+ def summarize_user(user)
341
+ return {} unless user
342
+ {
343
+ 'display_name' => "#{user['first_name']} #{user['last_name']}",
344
+ 'initials' => "#{user['first_name'][0]}#{user['last_name'][0]}"
345
+ }
346
+ end
347
+
348
+ def process_items(items)
349
+ return [] unless items
350
+ items.map.with_index do |item, index|
351
+ item.merge(
352
+ 'index' => index + 1,
353
+ 'is_last' => index == items.length - 1
354
+ )
355
+ end
356
+ end
357
+ end
358
+ ```
359
+
360
+ ## Profiling and Monitoring
361
+
362
+ ### Performance Measurement
363
+
364
+ ```ruby
365
+ require 'benchmark'
366
+
367
+ class PomlProfiler
368
+ def self.profile_processing(template, context = {})
369
+ results = {}
370
+
371
+ # Measure overall processing
372
+ results[:total] = Benchmark.measure do
373
+ POML.new.process(markup: template, variables: context)
374
+ end
375
+
376
+ # Measure template parsing
377
+ results[:parsing] = Benchmark.measure do
378
+ POML.new.send(:parse_template, template)
379
+ end
380
+
381
+ # Measure variable substitution
382
+ results[:substitution] = Benchmark.measure do
383
+ POML.new.send(:substitute_variables, template, context)
384
+ end
385
+
386
+ results
387
+ end
388
+
389
+ def self.profile_components(template)
390
+ component_counts = Hash.new(0)
391
+
392
+ # Count component usage
393
+ template.scan(/<(\w+)/).each do |match|
394
+ component_counts[match[0]] += 1
395
+ end
396
+
397
+ component_counts
398
+ end
399
+ end
400
+
401
+ # Usage
402
+ template = File.read('complex_template.poml')
403
+ context = { 'items' => (1..1000).to_a }
404
+
405
+ profile = PomlProfiler.profile_processing(template, context)
406
+ puts "Total time: #{profile[:total].real}s"
407
+ puts "Parsing time: #{profile[:parsing].real}s"
408
+
409
+ components = PomlProfiler.profile_components(template)
410
+ puts "Component usage: #{components}"
411
+ ```
412
+
413
+ ### Memory Profiling
414
+
415
+ ```ruby
416
+ require 'memory_profiler'
417
+
418
+ def profile_memory(template, context)
419
+ report = MemoryProfiler.report do
420
+ POML.new.process(markup: template, variables: context)
421
+ end
422
+
423
+ puts "Total allocated: #{report.total_allocated_memsize} bytes"
424
+ puts "Total retained: #{report.total_retained_memsize} bytes"
425
+
426
+ # Top allocations
427
+ report.allocated_memory_by_class.first(5).each do |allocation|
428
+ puts "#{allocation[:data]}: #{allocation[:count]} objects"
429
+ end
430
+ end
431
+ ```
432
+
433
+ ### Performance Monitoring
434
+
435
+ ```ruby
436
+ class PerformanceMonitor
437
+ def initialize
438
+ @metrics = {
439
+ processing_times: [],
440
+ memory_usage: [],
441
+ cache_hits: 0,
442
+ cache_misses: 0
443
+ }
444
+ end
445
+
446
+ def track_processing(template_name, &block)
447
+ start_time = Time.now
448
+ start_memory = memory_usage
449
+
450
+ result = yield
451
+
452
+ end_time = Time.now
453
+ end_memory = memory_usage
454
+
455
+ @metrics[:processing_times] << {
456
+ template: template_name,
457
+ duration: end_time - start_time,
458
+ memory_delta: end_memory - start_memory,
459
+ timestamp: start_time
460
+ }
461
+
462
+ result
463
+ end
464
+
465
+ def cache_hit!
466
+ @metrics[:cache_hits] += 1
467
+ end
468
+
469
+ def cache_miss!
470
+ @metrics[:cache_misses] += 1
471
+ end
472
+
473
+ def statistics
474
+ times = @metrics[:processing_times].map { |m| m[:duration] }
475
+
476
+ {
477
+ avg_processing_time: times.sum / times.length,
478
+ max_processing_time: times.max,
479
+ min_processing_time: times.min,
480
+ cache_hit_rate: cache_hit_rate,
481
+ total_processes: times.length
482
+ }
483
+ end
484
+
485
+ private
486
+
487
+ def memory_usage
488
+ `ps -o rss= -p #{Process.pid}`.to_i * 1024
489
+ end
490
+
491
+ def cache_hit_rate
492
+ total = @metrics[:cache_hits] + @metrics[:cache_misses]
493
+ return 0 if total == 0
494
+ (@metrics[:cache_hits].to_f / total * 100).round(2)
495
+ end
496
+ end
497
+ ```
498
+
499
+ ## Production Optimization
500
+
501
+ ### Asynchronous Processing
502
+
503
+ ```ruby
504
+ require 'concurrent-ruby'
505
+
506
+ class AsyncPomlProcessor
507
+ def initialize(pool_size: 10)
508
+ @executor = Concurrent::ThreadPoolExecutor.new(
509
+ min_threads: 2,
510
+ max_threads: pool_size,
511
+ max_queue: 100
512
+ )
513
+ end
514
+
515
+ def process_async(templates_and_contexts)
516
+ futures = templates_and_contexts.map do |template, context|
517
+ Concurrent::Future.execute(executor: @executor) do
518
+ POML.new.process(markup: template, variables: context)
519
+ end
520
+ end
521
+
522
+ # Wait for all to complete
523
+ futures.map(&:value)
524
+ end
525
+
526
+ def shutdown
527
+ @executor.shutdown
528
+ @executor.wait_for_termination(30)
529
+ end
530
+ end
531
+ ```
532
+
533
+ ### Background Job Integration
534
+
535
+ ```ruby
536
+ # Sidekiq job example
537
+ class PomlProcessingJob
538
+ include Sidekiq::Job
539
+
540
+ sidekiq_options queue: 'poml_processing', retry: 3
541
+
542
+ def perform(template_path, context, user_id)
543
+ result = OptimizedPomlProcessor.process_with_cache(template_path, context)
544
+
545
+ # Store result
546
+ ProcessingResult.create!(
547
+ user_id: user_id,
548
+ template_path: template_path,
549
+ result: result,
550
+ processed_at: Time.current
551
+ )
552
+ rescue StandardError => e
553
+ Rails.logger.error "POML processing failed: #{e.message}"
554
+ raise
555
+ end
556
+ end
557
+
558
+ # Usage
559
+ PomlProcessingJob.perform_async(
560
+ 'templates/report.poml',
561
+ { 'user_id' => user.id, 'data' => report_data },
562
+ user.id
563
+ )
564
+ ```
565
+
566
+ ### Load Balancing
567
+
568
+ ```ruby
569
+ class DistributedPomlProcessor
570
+ def initialize(worker_urls)
571
+ @workers = worker_urls.map { |url| Worker.new(url) }
572
+ @current_worker = 0
573
+ end
574
+
575
+ def process(template, context)
576
+ worker = next_worker
577
+ worker.process_remote(template, context)
578
+ rescue WorkerError => e
579
+ # Failover to next worker
580
+ retry_with_next_worker(template, context, e)
581
+ end
582
+
583
+ private
584
+
585
+ def next_worker
586
+ worker = @workers[@current_worker]
587
+ @current_worker = (@current_worker + 1) % @workers.length
588
+ worker
589
+ end
590
+
591
+ def retry_with_next_worker(template, context, original_error)
592
+ @workers.each do |worker|
593
+ return worker.process_remote(template, context)
594
+ rescue WorkerError
595
+ next
596
+ end
597
+
598
+ raise original_error
599
+ end
600
+ end
601
+ ```
602
+
603
+ ## Performance Best Practices
604
+
605
+ ### Template Design
606
+
607
+ ```ruby
608
+ # ✅ Good: Efficient template structure
609
+ <poml>
610
+ <!-- Pre-filter data -->
611
+ <for variable="item" items="{{priority_items}}">
612
+ <p><b>{{item.title}}</b></p>
613
+ <if condition="{{item.details}}">
614
+ <p>{{item.details}}</p>
615
+ </if>
616
+ </for>
617
+ </poml>
618
+
619
+ # ❌ Avoid: Inefficient nested conditions
620
+ <poml>
621
+ <for variable="item" items="{{all_items}}">
622
+ <if condition="{{item.category}} == 'important'">
623
+ <if condition="{{item.status}} == 'active'">
624
+ <if condition="{{item.user_can_view}}">
625
+ <p>{{item.title}}</p>
626
+ </if>
627
+ </if>
628
+ </if>
629
+ </for>
630
+ </poml>
631
+ ```
632
+
633
+ ### Context Preparation
634
+
635
+ ```ruby
636
+ # ✅ Good: Optimized context
637
+ def prepare_context(user, items)
638
+ {
639
+ 'user_name' => user.full_name,
640
+ 'priority_items' => items
641
+ .select(&:important?)
642
+ .select(&:active?)
643
+ .select { |item| user.can_view?(item) }
644
+ .map { |item| item.to_hash.slice(:title, :details, :created_at) }
645
+ }
646
+ end
647
+
648
+ # ❌ Avoid: Heavy context objects
649
+ def heavy_context(user, items)
650
+ {
651
+ 'user' => user, # Full ActiveRecord object
652
+ 'all_items' => items, # All items, not filtered
653
+ 'computed_data' => expensive_computation() # Computed every time
654
+ }
655
+ end
656
+ ```
657
+
658
+ ### Monitoring Setup
659
+
660
+ ```ruby
661
+ # Application monitoring
662
+ class PomlMetrics
663
+ def self.setup_monitoring
664
+ ActiveSupport::Notifications.subscribe('poml.process') do |name, start, finish, id, payload|
665
+ duration = finish - start
666
+ template_name = payload[:template_name]
667
+
668
+ # Send to monitoring service
669
+ StatsD.histogram('poml.processing_time', duration, tags: ["template:#{template_name}"])
670
+
671
+ # Log slow processing
672
+ if duration > 1.0
673
+ Rails.logger.warn "Slow POML processing: #{template_name} took #{duration}s"
674
+ end
675
+ end
676
+ end
677
+ end
678
+
679
+ # Usage in initializer
680
+ PomlMetrics.setup_monitoring
681
+ ```
682
+
683
+ ## Next Steps
684
+
685
+ 1. **Identify Bottlenecks** - Profile your specific use cases
686
+ 2. **Implement Caching** - Start with template and context caching
687
+ 3. **Monitor Performance** - Set up metrics and alerting
688
+ 4. **Optimize Iteratively** - Make incremental improvements
689
+ 5. **Scale Appropriately** - Add async processing for heavy workloads
690
+
691
+ For related topics, see:
692
+
693
+ - [Error Handling](error-handling.md) for robust error management
694
+ - [Integration Patterns](../integration/) for production deployment
695
+ - [Template Engine](../template-engine.md) for optimization techniques