miki-template 2.2.3 → 2.3.1

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 (66) hide show
  1. package/.github/workflows/docs.yml +3 -1
  2. package/.github/workflows/release.yml +0 -5
  3. package/README.md +17 -5
  4. package/benchmarks/ejs-results.json +6 -6
  5. package/benchmarks/ejs.js +5 -3
  6. package/benchmarks/handlebars-results.json +6 -6
  7. package/benchmarks/handlebars.js +5 -8
  8. package/benchmarks/miki-results.json +6 -6
  9. package/benchmarks/miki.js +6 -3
  10. package/benchmarks/pug-results.json +6 -6
  11. package/benchmarks/pug.js +5 -3
  12. package/docs/api/async-render.md +88 -3
  13. package/docs/api/cache.md +90 -3
  14. package/docs/api/compile.md +131 -3
  15. package/docs/api/context-processors.md +80 -3
  16. package/docs/api/filters.md +223 -3
  17. package/docs/api/finder.md +97 -3
  18. package/docs/api/helpers.md +56 -3
  19. package/docs/api/i18n.md +160 -3
  20. package/docs/api/index.md +82 -28
  21. package/docs/api/libraries.md +210 -3
  22. package/docs/api/render-partial.md +84 -3
  23. package/docs/api/render.md +95 -3
  24. package/docs/api/security.md +148 -3
  25. package/docs/api/setup-express.md +78 -2
  26. package/docs/api/tags.md +138 -4
  27. package/docs/filter.md +0 -0
  28. package/docs/guide/advanced-usage.md +403 -6
  29. package/docs/guide/async-rendering.md +312 -4
  30. package/docs/guide/context-processors.md +261 -4
  31. package/docs/guide/custom-filters.md +315 -4
  32. package/docs/guide/custom-tags.md +275 -4
  33. package/docs/guide/filters.md +675 -3
  34. package/docs/guide/getting-started.md +109 -7
  35. package/docs/guide/installation.md +99 -4
  36. package/docs/guide/partial-templates.md +371 -4
  37. package/docs/guide/quick-start.md +228 -6
  38. package/docs/guide/security.md +348 -3
  39. package/docs/guide/tags.md +789 -6
  40. package/docs/guide/template-discovery.md +174 -4
  41. package/docs/guide/template-inheritance.md +277 -4
  42. package/docs/index.md +24 -42
  43. package/docs/integrations/elysia.md +4 -2
  44. package/docs/integrations/express.md +219 -219
  45. package/docs/integrations/fastify.md +4 -2
  46. package/docs/integrations/hono.md +4 -2
  47. package/docs/integrations/index.md +68 -68
  48. package/docs/integrations/koa.md +4 -2
  49. package/docs/integrations/nestjs.md +4 -2
  50. package/docs/integrations/tsed.md +4 -2
  51. package/docs/performance.md +45 -8
  52. package/ex.mjs +1 -1
  53. package/mkdocs.yml +0 -22
  54. package/overrides/main.html +1 -1
  55. package/package.json +1 -1
  56. package/requirements-docs.txt +2 -1
  57. package/src/codegen.js +905 -0
  58. package/src/context.js +42 -30
  59. package/src/filters.js +16 -0
  60. package/src/index.js +66 -61
  61. package/src/tags/control.js +15 -12
  62. package/src/utils.js +60 -0
  63. package/tests/filters.test.js +9 -0
  64. package/docs/javascripts/extra.js +0 -174
  65. package/docs/stylesheets/extra.css +0 -819
  66. package/overrides/partials/footer.html +0 -9
@@ -1,783 +1,1566 @@
1
- # Tags
1
+ # Tags
2
+
3
+
2
4
 
3
5
  Tags control template logic and structure. They use `{% %}` syntax.
4
6
 
7
+
8
+
5
9
  ## Table of Contents
6
10
 
11
+
12
+
7
13
  - [Control Flow](#control-flow)
14
+
8
15
  - [Variable Assignment](#variable-assignment)
16
+
9
17
  - [Change Detection](#change-detection)
18
+
10
19
  - [Date and Time](#date-and-time)
20
+
11
21
  - [Utility Tags](#utility-tags)
22
+
12
23
  - [Security Tags](#security-tags)
24
+
13
25
  - [Comments and Raw Output](#comments-and-raw-output)
26
+
14
27
  - [Autoescape](#autoescape)
28
+
15
29
  - [Library Loading](#library-loading)
30
+
16
31
  - [Template Tags](#template-tags)
32
+
17
33
  - [Inheritance Tags](#inheritance-tags)
34
+
18
35
  - [Partial Tags](#partial-tags)
36
+
19
37
  - [i18n Tags](#i18n-tags)
20
38
 
39
+
40
+
21
41
  ---
22
42
 
43
+
44
+
23
45
  ## Control Flow
24
46
 
47
+
48
+
25
49
  ### if / elif / else / endif
26
50
 
51
+
52
+
27
53
  Conditional rendering with a wide range of operators.
28
54
 
55
+
56
+
29
57
  === "Basic if"
30
58
 
59
+
60
+
31
61
  ```html
62
+
32
63
  {% if user.is_authenticated %}
64
+
33
65
  <p>Welcome back, {{ user.name }}!</p>
66
+
34
67
  {% else %}
68
+
35
69
  <p>Please <a href="/login">log in</a>.</p>
70
+
36
71
  {% endif %}
72
+
37
73
  ```
38
74
 
75
+
76
+
39
77
  === "Multiple conditions with elif"
40
78
 
79
+
80
+
41
81
  ```html
82
+
42
83
  {% if user.role == 'admin' %}
84
+
43
85
  <p>Admin panel</p>
86
+
44
87
  {% elif user.is_staff %}
88
+
45
89
  <p>Staff dashboard</p>
90
+
46
91
  {% else %}
92
+
47
93
  <p>Guest view</p>
94
+
48
95
  {% endif %}
96
+
49
97
  ```
50
98
 
99
+
100
+
51
101
  === "Combined conditions with parentheses"
52
102
 
103
+
104
+
53
105
  ```html
106
+
54
107
  {% if (user.role == 'admin' or user.is_staff) and user.is_active %}
108
+
55
109
  <p>Active staff member</p>
110
+
56
111
  {% endif %}
57
112
 
113
+
114
+
58
115
  {% if item not in cart_items %}
116
+
59
117
  <button>Add to cart</button>
118
+
60
119
  {% endif %}
120
+
61
121
  ```
62
122
 
123
+
124
+
63
125
  **Supported operators:** `==`, `!=`, `<`, `<=`, `>`, `>=`, `in`, `not in`, `and`, `or`, `not`
64
126
 
127
+
128
+
65
129
  **Operator precedence** (highest to lowest): comparison → `and` → `or`
66
130
 
131
+
132
+
67
133
  ### for / empty / endfor
68
134
 
135
+
136
+
69
137
  Loop over arrays and objects. Injects `forloop` meta tracking.
70
138
 
139
+
140
+
71
141
  === "Basic loop"
72
142
 
143
+
144
+
73
145
  ```html
146
+
74
147
  <ul>
148
+
75
149
  {% for item in items %}
150
+
76
151
  <li>{{ forloop.counter }}: {{ item }}</li>
152
+
77
153
  {% empty %}
154
+
78
155
  <li>No items found</li>
156
+
79
157
  {% endfor %}
158
+
80
159
  </ul>
160
+
81
161
  ```
82
162
 
163
+
164
+
83
165
  === "Loop with filters"
84
166
 
167
+
168
+
85
169
  ```html
170
+
86
171
  {% for group in items|regroup:"category" %}
172
+
87
173
  <h3>{{ group.grouper }}</h3>
174
+
88
175
  {% for item in group.list %}- {{ item.name }}
176
+
89
177
  {% endfor %}
178
+
90
179
  {% endfor %}
180
+
91
181
  ```
92
182
 
183
+
184
+
93
185
  === "Dictionary iteration"
94
186
 
187
+
188
+
95
189
  ```html
190
+
96
191
  {% for key, value in config %}
192
+
97
193
  <dt>{{ key }}</dt>
194
+
98
195
  <dd>{{ value }}</dd>
196
+
99
197
  {% endfor %}
198
+
100
199
  ```
101
200
 
201
+
202
+
102
203
  === "Nested loops"
103
204
 
205
+
206
+
104
207
  ```html
208
+
105
209
  {% for department in departments %}
210
+
106
211
  <h2>{{ department.name }}</h2>
212
+
107
213
  {% for employee in department.employees %}
214
+
108
215
  <span>#{{ forloop.parentloop.counter }}.{{ forloop.counter }} {{ employee.name }}</span>
216
+
109
217
  {% endfor %}
218
+
110
219
  {% endfor %}
220
+
111
221
  ```
112
222
 
223
+
224
+
113
225
  #### forloop Metadata
114
226
 
227
+
228
+
115
229
  | Variable | Description |
230
+
116
231
  |----------|-------------|
232
+
117
233
  | `forloop.counter` | 1-based index |
234
+
118
235
  | `forloop.counter0` | 0-based index |
236
+
119
237
  | `forloop.revcounter` | Reverse 1-based index |
238
+
120
239
  | `forloop.revcounter0` | Reverse 0-based index |
240
+
121
241
  | `forloop.first` | `true` on first iteration |
242
+
122
243
  | `forloop.last` | `true` on last iteration |
244
+
123
245
  | `forloop.parentloop` | Parent loop context (nested loops) |
124
246
 
247
+
248
+
125
249
  **Real-world table with alternating row classes:**
126
250
 
251
+
252
+
127
253
  ```html
254
+
128
255
  <table>
256
+
129
257
  {% for row in rows %}
258
+
130
259
  <tr class="{% cycle 'row-odd' 'row-even' %}">
260
+
131
261
  <td>{{ row.name }}</td>
262
+
132
263
  <td>{{ row.value }}</td>
264
+
133
265
  </tr>
266
+
134
267
  {% endfor %}
268
+
135
269
  </table>
270
+
136
271
  ```
137
272
 
273
+
274
+
138
275
  ### with / endwith
139
276
 
277
+
278
+
140
279
  Scope localized variables.
141
280
 
281
+
282
+
142
283
  === "Alias a variable"
143
284
 
285
+
286
+
144
287
  ```html
288
+
145
289
  {% with user.profile.address as addr %}
290
+
146
291
  <p>{{ addr.city }}, {{ addr.zip }}</p>
292
+
147
293
  {% endwith %}
294
+
148
295
  ```
149
296
 
297
+
298
+
150
299
  === "Multiple assignments"
151
300
 
301
+
302
+
152
303
  ```html
304
+
153
305
  {% with greeting="Hello", who="World" %}
306
+
154
307
  <p>{{ greeting }} {{ who }}</p>
308
+
155
309
  {% endwith %}
310
+
156
311
  ```
157
312
 
313
+
314
+
158
315
  === "Combined alias"
159
316
 
317
+
318
+
160
319
  ```html
320
+
161
321
  {% with a=5, b=10 as total %}
322
+
162
323
  <p>Total: {{ total }}</p>
324
+
163
325
  {% endwith %}
326
+
164
327
  ```
165
328
 
329
+
330
+
166
331
  ### cycle
167
332
 
333
+
334
+
168
335
  Cycle through values sequentially.
169
336
 
337
+
338
+
170
339
  === "Alternating CSS classes"
171
340
 
341
+
342
+
172
343
  ```html
344
+
173
345
  {% for row in rows %}
346
+
174
347
  <tr class="{% cycle 'row-odd' 'row-even' %}">...</tr>
348
+
175
349
  {% endfor %}
350
+
176
351
  ```
177
352
 
353
+
354
+
178
355
  === "Store without output (as)"
179
356
 
357
+
358
+
180
359
  ```html
360
+
181
361
  {% cycle 'row-odd' 'row-even' as row_class %}
362
+
182
363
  <tr class="{{ row_class }}">
364
+
183
365
  ```
184
366
 
367
+
368
+
185
369
  === "Named cycle for resumable state"
186
370
 
371
+
372
+
187
373
  ```html
374
+
188
375
  {% for item in items %}
376
+
189
377
  {% cycle 'a' 'b' 'c' as marker silent %}
378
+
190
379
  {% if marker == 'b' %}
380
+
191
381
  <strong>{{ item }}</strong>
382
+
192
383
  {% else %}
384
+
193
385
  {{ item }}
386
+
194
387
  {% endif %}
388
+
195
389
  {% endfor %}
390
+
196
391
  ```
197
392
 
393
+
394
+
198
395
  ### firstof
199
396
 
397
+
398
+
200
399
  Return the first truthy value.
201
400
 
401
+
402
+
202
403
  ```html
404
+
203
405
  {% firstof user.display_name user.username "Anonymous" %}
406
+
204
407
  ```
205
408
 
409
+
410
+
206
411
  ---
207
412
 
413
+
414
+
208
415
  ## Variable Assignment
209
416
 
417
+
418
+
210
419
  ### set
211
420
 
421
+
422
+
212
423
  Assign a value to a variable.
213
424
 
425
+
426
+
214
427
  === "Inline assignment"
215
428
 
429
+
430
+
216
431
  ```html
432
+
217
433
  {% set total = price * quantity %}
434
+
218
435
  <p>Total: ${{ total|floatformat:2 }}</p>
436
+
219
437
  ```
220
438
 
439
+
440
+
221
441
  === "Block form (captures rendered output)"
222
442
 
443
+
444
+
223
445
  ```html
446
+
224
447
  {% set greeting %}
448
+
225
449
  Hello {{ user.name|title }}, welcome to {{ site.name }}!
450
+
226
451
  {% endset %}
227
452
 
453
+
454
+
228
455
  <h1>{{ greeting|safe }}</h1>
456
+
229
457
  ```
230
458
 
459
+
460
+
231
461
  === "Multiple variables"
232
462
 
463
+
464
+
233
465
  ```html
466
+
234
467
  {% set tax_rate = 0.08, tax = subtotal|mult:tax_rate %}
468
+
235
469
  ```
236
470
 
471
+
472
+
237
473
  Variables set with `{% set %}` persist in the current scope and can be used after the tag.
238
474
 
475
+
476
+
239
477
  ---
240
478
 
479
+
480
+
241
481
  ## Change Detection
242
482
 
483
+
484
+
243
485
  ### ifchanged / endifchanged
244
486
 
487
+
488
+
245
489
  Render the body only when a value changes.
246
490
 
491
+
492
+
247
493
  === "Basic (no argument)"
248
494
 
495
+
496
+
249
497
  ```html
498
+
250
499
  {% for item in changelog %}
500
+
251
501
  {% ifchanged item.timestamp %}
502
+
252
503
  <h3>{{ item.timestamp|date:"Y-m-d" }}</h3>
504
+
253
505
  {% endifchanged %}
506
+
254
507
  <p>{{ item.change }}</p>
508
+
255
509
  {% endfor %}
510
+
256
511
  ```
257
512
 
258
- === "With else"
513
+
514
+
515
+ === "With else"
516
+
517
+
259
518
 
260
519
  ```html
520
+
261
521
  {% for item in items %}
522
+
262
523
  {% ifchanged item.category %}
524
+
263
525
  <h2>{{ item.category }}</h2>
526
+
264
527
  {% else %}
528
+
265
529
  <p>Same category as above</p>
530
+
266
531
  {% endifchanged %}
532
+
267
533
  {% endfor %}
534
+
268
535
  ```
269
536
 
537
+
538
+
270
539
  ---
271
540
 
541
+
542
+
272
543
  ## Date and Time
273
544
 
545
+
546
+
274
547
  ### now
275
548
 
549
+
550
+
276
551
  Output the current date/time.
277
552
 
553
+
554
+
278
555
  ```html
556
+
279
557
  <p>Current time: {% now "Y-m-d H:i:s" %}</p>
558
+
280
559
  <p>Pretty date: {% now "F j, Y" %}</p>
560
+
281
561
  ```
282
562
 
563
+
564
+
283
565
  Uses the same format codes as the `date` filter (Django-style tokens like `Y`, `m`, `d`, `H`, `i`, `s`, `F`).
284
566
 
567
+
568
+
285
569
  **Real-world copyright footer:**
286
570
 
571
+
572
+
287
573
  ```html
574
+
288
575
  <footer>
576
+
289
577
  &copy; {{ "now"|date:"Y" }} {{ site.name }}. All rights reserved.
578
+
290
579
  </footer>
580
+
291
581
  ```
292
582
 
583
+
584
+
293
585
  ---
294
586
 
587
+
588
+
295
589
  ## Utility Tags
296
590
 
591
+
592
+
297
593
  ### static
298
594
 
595
+
596
+
299
597
  Generate a static file URL.
300
598
 
599
+
600
+
301
601
  ```html
602
+
302
603
  <link rel="stylesheet" href="{% static "css/main.css" %}">
604
+
303
605
  <script src="{% static "js/app.js" %}"></script>
606
+
304
607
  <img src="{% static "images/logo.svg" }}" alt="{{ site.name }}">
608
+
305
609
  ```
306
610
 
611
+
612
+
307
613
  Configure the prefix at compile time:
308
614
 
615
+
616
+
309
617
  === "CommonJS"
310
618
 
619
+
620
+
311
621
  ```javascript
622
+
312
623
  const { compile } = require('miki-template');
624
+
313
625
  const template = compile(source, { staticUrl: '/assets/' });
626
+
314
627
  ```
315
628
 
629
+
630
+
316
631
  === "ES Modules"
317
632
 
633
+
634
+
318
635
  ```javascript
636
+
319
637
  import { compile } from 'miki-template';
638
+
320
639
  const template = compile(source, { staticUrl: '/assets/' });
640
+
321
641
  ```
322
642
 
643
+
644
+
323
645
  ### url
324
646
 
647
+
648
+
325
649
  Build a URL from a route name.
326
650
 
651
+
652
+
327
653
  === "Basic"
328
654
 
655
+
656
+
329
657
  ```html
658
+
330
659
  <a href="{% url 'user.profile' user.id %}">Profile</a>
660
+
331
661
  ```
332
662
 
663
+
664
+
333
665
  === "With keyword arguments"
334
666
 
667
+
668
+
335
669
  ```html
670
+
336
671
  {% url 'posts.show' post.id tab='comments' %}
672
+
337
673
  ```
338
674
 
675
+
676
+
339
677
  === "Deep routing with dots"
340
678
 
679
+
680
+
341
681
  ```html
682
+
342
683
  {% url 'user.profile.posts.show' user.id post.id %}
684
+
343
685
  ```
344
686
 
687
+
688
+
345
689
  Configure with a custom resolver:
346
690
 
691
+
692
+
347
693
  === "CommonJS"
348
694
 
695
+
696
+
349
697
  ```javascript
698
+
350
699
  const { compile } = require('miki-template');
700
+
351
701
  const template = compile(source, {
702
+
352
703
  urlHelper: (routeName, ...args) => {
704
+
353
705
  // Convert "user.profile" + [42] → "/user/profile/42"
706
+
354
707
  return '/' + routeName.split('.').join('/') + '/' + args.join('/');
708
+
355
709
  }
710
+
356
711
  });
712
+
357
713
  ```
358
714
 
715
+
716
+
359
717
  === "ES Modules"
360
718
 
719
+
720
+
361
721
  ```javascript
722
+
362
723
  import { compile } from 'miki-template';
724
+
363
725
  const template = compile(source, {
726
+
364
727
  urlHelper: (routeName, ...args) => {
728
+
365
729
  return '/' + routeName.split('.').join('/') + '/' + args.join('/');
730
+
366
731
  }
732
+
367
733
  });
734
+
368
735
  ```
369
736
 
737
+
738
+
370
739
  ### regroup
371
740
 
741
+
742
+
372
743
  Group a list by a common attribute.
373
744
 
745
+
746
+
374
747
  ```html
748
+
375
749
  {% regroup people by gender as departments %}
750
+
376
751
  {% for dept in departments %}
752
+
377
753
  <h3>{{ dept.grouper }}</h3>
754
+
378
755
  {% for person in dept.list %}
756
+
379
757
  <p>{{ person.name }}</p>
758
+
380
759
  {% endfor %}
760
+
381
761
  {% endfor %}
762
+
382
763
  ```
383
764
 
765
+
766
+
384
767
  You can also use `regroup` as a filter inside a `{% for %}` loop:
385
768
 
769
+
770
+
386
771
  ```html
772
+
387
773
  {% for group in items|regroup:"category" %}
774
+
388
775
  <h3>{{ group.grouper }}</h3>
776
+
389
777
  {% for item in group.list %}
778
+
390
779
  <p>{{ item.name }}</p>
780
+
391
781
  {% endfor %}
782
+
392
783
  {% endfor %}
784
+
393
785
  ```
394
786
 
787
+
788
+
395
789
  ### spaceless
396
790
 
791
+
792
+
397
793
  Remove whitespace between HTML tags.
398
794
 
795
+
796
+
399
797
  ```html
798
+
400
799
  {% spaceless %}
800
+
401
801
  <div>
802
+
402
803
  <span> hello </span>
804
+
403
805
  </div>
806
+
404
807
  {% endspaceless %}
808
+
405
809
  ```
406
810
 
811
+
812
+
407
813
  Output: `<div><span> hello </span></div>`
408
814
 
815
+
816
+
409
817
  ### widthratio
410
818
 
819
+
820
+
411
821
  Calculate ratios for progress bars or scaling.
412
822
 
823
+
824
+
413
825
  ```html
826
+
414
827
  <!-- Calculate 25 out of 100 scaled to max-width 150 -->
828
+
415
829
  {% widthratio score 100 150 %}
830
+
416
831
  <!-- → 37 (floor of 25/100*150) -->
417
832
 
833
+
834
+
418
835
  <!-- Progress bar width -->
836
+
419
837
  <div class="bar" style="width: {% widthratio value max_value 100 %}px;"></div>
838
+
420
839
  ```
421
840
 
841
+
842
+
422
843
  ### debug
423
844
 
845
+
846
+
424
847
  Dump the current template context for debugging.
425
848
 
849
+
850
+
426
851
  ```html
852
+
427
853
  {% debug %}
854
+
428
855
  ```
429
856
 
857
+
858
+
430
859
  Outputs a `<pre>` block with all context variables.
431
860
 
861
+
862
+
432
863
  ---
433
864
 
865
+
866
+
434
867
  ## Security Tags
435
868
 
869
+
870
+
436
871
  ### csrf_token
437
872
 
873
+
874
+
438
875
  Output a hidden CSRF token input.
439
876
 
877
+
878
+
440
879
  ```html
880
+
441
881
  <form method="post">
882
+
442
883
  {% csrf_token %}
884
+
443
885
  <button type="submit">Submit</button>
886
+
444
887
  </form>
888
+
445
889
  ```
446
890
 
891
+
892
+
447
893
  The token value is HTML-escaped to prevent attribute injection. Requires `csrf_token` to be present in the template context.
448
894
 
895
+
896
+
449
897
  === "CommonJS (Express middleware)"
450
898
 
899
+
900
+
451
901
  ```javascript
902
+
452
903
  app.use((req, res, next) => {
904
+
453
905
  res.locals.csrf_token = req.csrfToken();
906
+
454
907
  next();
908
+
455
909
  });
910
+
456
911
  ```
457
912
 
913
+
914
+
458
915
  === "ES Modules"
459
916
 
917
+
918
+
460
919
  ```javascript
920
+
461
921
  app.use((req, res, next) => {
922
+
462
923
  res.locals.csrf_token = req.csrfToken();
924
+
463
925
  next();
926
+
464
927
  });
928
+
465
929
  ```
466
930
 
931
+
932
+
467
933
  ### csp_nonce_attr
468
934
 
935
+
936
+
469
937
  Output a `nonce` attribute when `csp_nonce` is in the context.
470
938
 
939
+
940
+
471
941
  ```html
942
+
472
943
  <script {% csp_nonce_attr %} src="/js/app.js"></script>
944
+
473
945
  ```
474
946
 
947
+
948
+
475
949
  If `csp_nonce` is present in context, the output is:
476
950
 
951
+
952
+
477
953
  ```html
954
+
478
955
  <script nonce="abc123" src="/js/app.js"></script>
956
+
479
957
  ```
480
958
 
959
+
960
+
481
961
  If `csp_nonce` is not present, the tag outputs nothing.
482
962
 
963
+
964
+
483
965
  ---
484
966
 
967
+
968
+
485
969
  ## Comments and Raw Output
486
970
 
971
+
972
+
487
973
  ### comment / endcomment
488
974
 
975
+
976
+
489
977
  Block comments ignored during parsing.
490
978
 
979
+
980
+
491
981
  ```html
982
+
492
983
  {% comment %}
984
+
493
985
  This is a comment.
986
+
494
987
  It can span multiple lines.
988
+
495
989
  {% endcomment %}
990
+
496
991
  ```
497
992
 
993
+
994
+
498
995
  ### verbatim / endverbatim
499
996
 
997
+
998
+
500
999
  Treat content as raw text — template syntax is not parsed.
501
1000
 
1001
+
1002
+
502
1003
  ```html
1004
+
503
1005
  {% verbatim %}
1006
+
504
1007
  This will NOT be parsed: {{ user.name }}
1008
+
505
1009
  And this won't either: {% if x %}
1010
+
506
1011
  {% endverbatim %}
1012
+
507
1013
  ```
508
1014
 
1015
+
1016
+
509
1017
  You can also name a verbatim block:
510
1018
 
1019
+
1020
+
511
1021
  ```html
1022
+
512
1023
  {% verbatim myscript %}
1024
+
513
1025
  {{ angularExpression }}
1026
+
514
1027
  {% endverbatim %}
1028
+
515
1029
  ```
516
1030
 
1031
+
1032
+
517
1033
  ---
518
1034
 
1035
+
1036
+
519
1037
  ## Autoescape
520
1038
 
1039
+
1040
+
521
1041
  Control HTML escaping for a block.
522
1042
 
1043
+
1044
+
523
1045
  ```html
1046
+
524
1047
  {% autoescape on %}
1048
+
525
1049
  {{ user_input }} {# escaped → &lt;script&gt;... #}
1050
+
526
1051
  {% endautoescape %}
527
1052
 
1053
+
1054
+
528
1055
  {% autoescape off %}
1056
+
529
1057
  {{ trusted_html }} {# not escaped → raw HTML #}
1058
+
530
1059
  {% endautoescape %}
1060
+
531
1061
  ```
532
1062
 
1063
+
1064
+
533
1065
  ---
534
1066
 
1067
+
1068
+
535
1069
  ## Library Loading
536
1070
 
1071
+
1072
+
537
1073
  ### load
538
1074
 
1075
+
1076
+
539
1077
  Activate a template library. Built-in libraries (`humanize`, `cache`, `lorem`) are auto-activated — you only need `{% load %}` for custom libraries you've registered.
540
1078
 
1079
+
1080
+
541
1081
  ```html
1082
+
542
1083
  {% load humanize %}
1084
+
543
1085
  {{ views|intcomma }}
1086
+
544
1087
  {{ count|ordinal }}
1088
+
545
1089
  ```
546
1090
 
1091
+
1092
+
547
1093
  === "CommonJS (registering a library)"
548
1094
 
1095
+
1096
+
549
1097
  ```javascript
1098
+
550
1099
  const { registerLibrary } = require('miki-template');
551
1100
 
1101
+
1102
+
552
1103
  registerLibrary('myutils', {
1104
+
553
1105
  filters: {
1106
+
554
1107
  shout: (val) => String(val).toUpperCase() + '!'
1108
+
555
1109
  }
1110
+
556
1111
  });
1112
+
557
1113
  ```
558
1114
 
1115
+
1116
+
559
1117
  === "ES Modules"
560
1118
 
1119
+
1120
+
561
1121
  ```javascript
1122
+
562
1123
  import { registerLibrary } from 'miki-template';
563
1124
 
1125
+
1126
+
564
1127
  registerLibrary('myutils', {
1128
+
565
1129
  filters: {
1130
+
566
1131
  shout: (val) => String(val).toUpperCase() + '!'
1132
+
567
1133
  }
1134
+
568
1135
  });
1136
+
569
1137
  ```
570
1138
 
1139
+
1140
+
571
1141
  Then use in templates:
572
1142
 
1143
+
1144
+
573
1145
  ```html
1146
+
574
1147
  {% load myutils %}
1148
+
575
1149
  {{ name|shout }}
1150
+
576
1151
  ```
577
1152
 
1153
+
1154
+
578
1155
  **Built-in libraries:**
579
1156
 
1157
+
1158
+
580
1159
  - `i18n` — `{% trans %}`, `{% blocktrans %}`, `{% language %}`
1160
+
581
1161
  - `humanize` — `intcomma`, `intword`, `apnumber`, `ordinal`, `naturalday`
1162
+
582
1163
  - `cache` — `{% cache timeout key %}...{% endcache %}`
1164
+
583
1165
  - `lorem` — `{% lorem %}` tag and `lorem` filter
584
1166
 
1167
+
1168
+
585
1169
  ---
586
1170
 
1171
+
1172
+
587
1173
  ## Template Tags
588
1174
 
1175
+
1176
+
589
1177
  ### templatetag
590
1178
 
1179
+
1180
+
591
1181
  Output literal template tag tokens. Useful when generating documentation or when the template syntax conflicts with another templating layer.
592
1182
 
1183
+
1184
+
593
1185
  ```html
1186
+
594
1187
  {% templatetag openblock %} if user.is_admin {% templatetag closeblock %}
1188
+
595
1189
  <!-- Renders: {% if user.is_admin %} -->
596
1190
 
1191
+
1192
+
597
1193
  {% templatetag openvariable %} name {% templatetag closevariable %}
1194
+
598
1195
  <!-- Renders: {{ name }} -->
1196
+
599
1197
  ```
600
1198
 
1199
+
1200
+
601
1201
  Available tokens:
602
1202
 
1203
+
1204
+
603
1205
  | Token | Output |
1206
+
604
1207
  |-------|--------|
1208
+
605
1209
  | `openblock` | `{%` |
1210
+
606
1211
  | `closeblock` | `%}` |
1212
+
607
1213
  | `openvariable` | `{{` |
1214
+
608
1215
  | `closevariable` | `}}` |
1216
+
609
1217
  | `openbrace` | `{` |
1218
+
610
1219
  | `closebrace` | `}` |
1220
+
611
1221
  | `opencomment` | `{#` |
1222
+
612
1223
  | `closecomment` | `#}` |
613
1224
 
1225
+
1226
+
614
1227
  ---
615
1228
 
1229
+
1230
+
616
1231
  ## Inheritance Tags
617
1232
 
1233
+
1234
+
618
1235
  ### extends
619
1236
 
1237
+
1238
+
620
1239
  Inherit from a parent template.
621
1240
 
1241
+
1242
+
622
1243
  ```html
1244
+
623
1245
  {% extends "base.html" %}
1246
+
624
1247
  ```
625
1248
 
1249
+
1250
+
626
1251
  Can use expressions for dynamic parent selection:
627
1252
 
1253
+
1254
+
628
1255
  ```html
1256
+
629
1257
  {% extends device|default:"desktop/base.html" %}
1258
+
630
1259
  ```
631
1260
 
1261
+
1262
+
632
1263
  **Security:** Path traversal is blocked — `{% extends "../../etc/passwd" %}` is rejected.
633
1264
 
1265
+
1266
+
634
1267
  ### block / endblock
635
1268
 
1269
+
1270
+
636
1271
  Define a block that can be overridden by child templates.
637
1272
 
1273
+
1274
+
638
1275
  ```html
1276
+
639
1277
  <!-- base.html -->
1278
+
640
1279
  <html>
1280
+
641
1281
  <body>
1282
+
642
1283
  {% block content %}Default content{% endblock %}
1284
+
643
1285
  </body>
1286
+
644
1287
  </html>
1288
+
645
1289
  ```
646
1290
 
1291
+
1292
+
647
1293
  ```html
1294
+
648
1295
  <!-- child.html -->
1296
+
649
1297
  {% extends "base.html" %}
1298
+
650
1299
  {% block content %}
1300
+
651
1301
  <h1>Child content</h1>
1302
+
652
1303
  {{ block.super }}
1304
+
653
1305
  {% endblock %}
1306
+
654
1307
  ```
655
1308
 
1309
+
1310
+
656
1311
  ### block.super
657
1312
 
1313
+
1314
+
658
1315
  A special variable (not a tag). When used inside a `{% block %}`, it renders the parent template's version of that block.
659
1316
 
1317
+
1318
+
660
1319
  See [Template Inheritance](template-inheritance.md) for a detailed guide.
661
1320
 
1321
+
1322
+
662
1323
  ### include
663
1324
 
1325
+
1326
+
664
1327
  Include another template's content inline.
665
1328
 
1329
+
1330
+
666
1331
  ```html
1332
+
667
1333
  {% include "header.html" %}
1334
+
668
1335
  {% include "header.html" with title="Hello" %}
1336
+
669
1337
  {% include "header.html#partial_name" %}
1338
+
670
1339
  {% include "header.html" with title="Hello" %}
1340
+
671
1341
  ```
672
1342
 
1343
+
1344
+
673
1345
  **Security:** Path traversal is blocked.
674
1346
 
1347
+
1348
+
675
1349
  ---
676
1350
 
1351
+
1352
+
677
1353
  ## Partial Tags
678
1354
 
1355
+
1356
+
679
1357
  ### partialdef / endpartialdef
680
1358
 
1359
+
1360
+
681
1361
  Define a reusable partial block.
682
1362
 
1363
+
1364
+
683
1365
  ```html
1366
+
684
1367
  {% partialdef card %}
1368
+
685
1369
  <div class="card">
1370
+
686
1371
  <h3>{{ title|default:"Untitled" }}</h3>
1372
+
687
1373
  <p>{{ body|truncatewords:30 }}</p>
1374
+
688
1375
  {% if featured %}<em>Featured</em>{% endif %}
1376
+
689
1377
  </div>
1378
+
690
1379
  {% endpartialdef %}
1380
+
691
1381
  ```
692
1382
 
1383
+
1384
+
693
1385
  **Options:**
694
1386
 
1387
+
1388
+
695
1389
  | Option | Description |
1390
+
696
1391
  |--------|-------------|
1392
+
697
1393
  | `inline` | Renders the definition inline at its location during parse (the body appears in output AND registers for later use). |
698
1394
 
1395
+
1396
+
699
1397
  ```html
1398
+
700
1399
  {% partialdef greeting inline %}
1400
+
701
1401
  Hello {{ name }}!
1402
+
702
1403
  {% endpartialdef %}
1404
+
703
1405
  <!-- Above line ALSO outputs "Hello World!" when rendered -->
1406
+
704
1407
  ```
705
1408
 
1409
+
1410
+
706
1411
  **Programmatic access:**
707
1412
 
1413
+
1414
+
708
1415
  === "CommonJS"
709
1416
 
1417
+
1418
+
710
1419
  ```javascript
1420
+
711
1421
  const { compile } = require('miki-template');
1422
+
712
1423
  const compiled = compile(template);
1424
+
713
1425
  compiled.renderPartial('card', { title: 'Hi', body: 'There' });
1426
+
714
1427
  ```
715
1428
 
1429
+
1430
+
716
1431
  === "ES Modules"
717
1432
 
1433
+
1434
+
718
1435
  ```javascript
1436
+
719
1437
  import { compile } from 'miki-template';
1438
+
720
1439
  const compiled = compile(template);
1440
+
721
1441
  compiled.renderPartial('card', { title: 'Hi', body: 'There' });
1442
+
722
1443
  ```
723
1444
 
1445
+
1446
+
724
1447
  ### partial
725
1448
 
1449
+
1450
+
726
1451
  Render a named partial.
727
1452
 
1453
+
1454
+
728
1455
  ```html
1456
+
729
1457
  {% partial card %}
1458
+
730
1459
  {% partial card with title="Custom" body="World" %}
1460
+
731
1461
  {% partial greeting with name=user.name %}
1462
+
732
1463
  ```
733
1464
 
1465
+
1466
+
734
1467
  See [Partial Templates](partial-templates.md) for a detailed guide.
735
1468
 
1469
+
1470
+
736
1471
  ---
737
1472
 
1473
+
1474
+
738
1475
  ## i18n Tags
739
1476
 
1477
+
1478
+
740
1479
  ### trans
741
1480
 
1481
+
1482
+
742
1483
  Translate a string.
743
1484
 
1485
+
1486
+
744
1487
  ```html
1488
+
745
1489
  {% trans "Hello, world!" %}
1490
+
746
1491
  {% trans "Hello, %s!" name=user.name %}
1492
+
747
1493
  {% trans context "verb" "He runs" %}
1494
+
748
1495
  ```
749
1496
 
1497
+
1498
+
750
1499
  ### blocktrans / endblocktrans
751
1500
 
1501
+
1502
+
752
1503
  Translate a block of text with variable interpolation and pluralization.
753
1504
 
1505
+
1506
+
754
1507
  ```html
1508
+
755
1509
  {% blocktrans with name=user.name %}
1510
+
756
1511
  Hello, {{ name }}!
1512
+
757
1513
  {% endblocktrans %}
758
1514
 
1515
+
1516
+
759
1517
  {% blocktrans count items|length %}
1518
+
760
1519
  {{ count }} item
1520
+
761
1521
  {% plural %}
1522
+
762
1523
  {{ count }} items
1524
+
763
1525
  {% endblocktrans %}
1526
+
764
1527
  ```
765
1528
 
1529
+
1530
+
766
1531
  ### language / endlanguage
767
1532
 
1533
+
1534
+
768
1535
  Switch language temporarily for a block.
769
1536
 
1537
+
1538
+
770
1539
  ```html
1540
+
771
1541
  {% language "fr" %}
1542
+
772
1543
  {% trans "Hello" %} → renders in French
1544
+
773
1545
  {% endlanguage %}
1546
+
774
1547
  ```
775
1548
 
1549
+
1550
+
776
1551
  ---
777
1552
 
1553
+
1554
+
778
1555
  ## Next Steps
779
1556
 
780
- - [Filters](./filters)
781
- - [Template Inheritance](./template-inheritance)
782
- - [Partial Templates](./partial-templates)
783
- - [Custom Tags](./custom-tags)
1557
+
1558
+
1559
+ - [Filters](./filters.md)
1560
+
1561
+ - [Template Inheritance](./template-inheritance.md)
1562
+
1563
+ - [Partial Templates](./partial-templates.md)
1564
+
1565
+ - [Custom Tags](./custom-tags.md)
1566
+