expressir 2.1.30 → 2.1.31

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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docs.yml +98 -0
  3. data/.github/workflows/links.yml +100 -0
  4. data/.github/workflows/rake.yml +4 -0
  5. data/.github/workflows/release.yml +5 -0
  6. data/.github/workflows/validate_schemas.yml +1 -1
  7. data/.gitignore +3 -0
  8. data/.rubocop.yml +1 -1
  9. data/.rubocop_todo.yml +244 -39
  10. data/Gemfile +2 -1
  11. data/README.adoc +621 -54
  12. data/docs/Gemfile +12 -0
  13. data/docs/_config.yml +141 -0
  14. data/docs/_guides/changes/changes-format.adoc +778 -0
  15. data/docs/_guides/changes/importing-eengine.adoc +898 -0
  16. data/docs/_guides/changes/index.adoc +396 -0
  17. data/docs/_guides/changes/programmatic-usage.adoc +1038 -0
  18. data/docs/_guides/changes/validating-changes.adoc +681 -0
  19. data/docs/_guides/cli/benchmark-performance.adoc +834 -0
  20. data/docs/_guides/cli/coverage-analysis.adoc +921 -0
  21. data/docs/_guides/cli/format-schemas.adoc +547 -0
  22. data/docs/_guides/cli/index.adoc +8 -0
  23. data/docs/_guides/cli/managing-changes.adoc +927 -0
  24. data/docs/_guides/cli/validate-ascii.adoc +645 -0
  25. data/docs/_guides/cli/validate-schemas.adoc +534 -0
  26. data/docs/_guides/index.adoc +165 -0
  27. data/docs/_guides/ler/creating-packages.adoc +664 -0
  28. data/docs/_guides/ler/index.adoc +305 -0
  29. data/docs/_guides/ler/loading-packages.adoc +707 -0
  30. data/docs/_guides/ler/package-formats.adoc +748 -0
  31. data/docs/_guides/ler/querying-packages.adoc +826 -0
  32. data/docs/_guides/ler/validating-packages.adoc +750 -0
  33. data/docs/_guides/liquid/basic-templates.adoc +813 -0
  34. data/docs/_guides/liquid/documentation-generation.adoc +1042 -0
  35. data/docs/_guides/liquid/drops-reference.adoc +829 -0
  36. data/docs/_guides/liquid/filters-and-tags.adoc +912 -0
  37. data/docs/_guides/liquid/index.adoc +468 -0
  38. data/docs/_guides/manifests/creating-manifests.adoc +483 -0
  39. data/docs/_guides/manifests/index.adoc +307 -0
  40. data/docs/_guides/manifests/resolving-manifests.adoc +557 -0
  41. data/docs/_guides/manifests/validating-manifests.adoc +713 -0
  42. data/docs/_guides/ruby-api/formatting-schemas.adoc +605 -0
  43. data/docs/_guides/ruby-api/index.adoc +257 -0
  44. data/docs/_guides/ruby-api/parsing-files.adoc +421 -0
  45. data/docs/_guides/ruby-api/search-engine.adoc +609 -0
  46. data/docs/_guides/ruby-api/working-with-repository.adoc +577 -0
  47. data/docs/_pages/data-model.adoc +665 -0
  48. data/docs/_pages/express-language.adoc +506 -0
  49. data/docs/_pages/getting-started.adoc +414 -0
  50. data/docs/_pages/index.adoc +116 -0
  51. data/docs/_pages/introduction.adoc +256 -0
  52. data/docs/_pages/ler-packages.adoc +837 -0
  53. data/docs/_pages/parsers.adoc +683 -0
  54. data/docs/_pages/schema-manifests.adoc +431 -0
  55. data/docs/_references/index.adoc +228 -0
  56. data/docs/_tutorials/creating-ler-package.adoc +735 -0
  57. data/docs/_tutorials/documentation-coverage.adoc +795 -0
  58. data/docs/_tutorials/index.adoc +221 -0
  59. data/docs/_tutorials/liquid-templates.adoc +806 -0
  60. data/docs/_tutorials/parsing-your-first-schema.adoc +522 -0
  61. data/docs/_tutorials/querying-schemas.adoc +751 -0
  62. data/docs/_tutorials/working-with-multiple-schemas.adoc +676 -0
  63. data/docs/index.adoc +242 -0
  64. data/docs/lychee.toml +84 -0
  65. data/examples/demo_ler_usage.sh +86 -0
  66. data/examples/ler/README.md +111 -0
  67. data/examples/ler/simple_example.ler +0 -0
  68. data/examples/ler/simple_schema.exp +33 -0
  69. data/examples/ler_build.rb +75 -0
  70. data/examples/ler_cli.rb +79 -0
  71. data/examples/ler_demo_complete.rb +276 -0
  72. data/examples/ler_query.rb +91 -0
  73. data/examples/ler_query_examples.rb +305 -0
  74. data/examples/ler_stats.rb +81 -0
  75. data/examples/phase3_demo.rb +159 -0
  76. data/examples/query_demo_simple.rb +131 -0
  77. data/expressir.gemspec +2 -0
  78. data/lib/expressir/cli.rb +12 -4
  79. data/lib/expressir/commands/manifest.rb +427 -0
  80. data/lib/expressir/commands/package.rb +1274 -0
  81. data/lib/expressir/commands/validate.rb +70 -37
  82. data/lib/expressir/commands/validate_ascii.rb +607 -0
  83. data/lib/expressir/commands/validate_load.rb +88 -0
  84. data/lib/expressir/express/formatter.rb +5 -1
  85. data/lib/expressir/express/formatters/remark_item_formatter.rb +25 -0
  86. data/lib/expressir/express/parser.rb +33 -0
  87. data/lib/expressir/manifest/resolver.rb +213 -0
  88. data/lib/expressir/manifest/validator.rb +195 -0
  89. data/lib/expressir/model/declarations/entity.rb +6 -0
  90. data/lib/expressir/model/dependency_resolver.rb +270 -0
  91. data/lib/expressir/model/indexes/entity_index.rb +103 -0
  92. data/lib/expressir/model/indexes/reference_index.rb +148 -0
  93. data/lib/expressir/model/indexes/type_index.rb +149 -0
  94. data/lib/expressir/model/interface_validator.rb +384 -0
  95. data/lib/expressir/model/repository.rb +400 -5
  96. data/lib/expressir/model/repository_validator.rb +295 -0
  97. data/lib/expressir/model/search_engine.rb +525 -0
  98. data/lib/expressir/model.rb +4 -94
  99. data/lib/expressir/package/builder.rb +200 -0
  100. data/lib/expressir/package/metadata.rb +81 -0
  101. data/lib/expressir/package/reader.rb +165 -0
  102. data/lib/expressir/schema_manifest.rb +11 -1
  103. data/lib/expressir/version.rb +1 -1
  104. data/lib/expressir.rb +15 -2
  105. metadata +114 -4
  106. data/docs/benchmarking.adoc +0 -107
  107. data/docs/liquid_drops.adoc +0 -1547
@@ -0,0 +1,912 @@
1
+ ---
2
+ title: Filters and Tags
3
+ parent: Liquid
4
+ grand_parent: Guides
5
+ nav_order: 3
6
+ ---
7
+
8
+ = Liquid filters and tags
9
+
10
+ == Purpose
11
+
12
+ This guide covers Liquid's built-in filters and tags for transforming
13
+ and manipulating data in templates. Learn how to format strings, work
14
+ with arrays, apply mathematical operations, and use advanced tags for
15
+ template control.
16
+
17
+ == References
18
+
19
+ * link:basic-templates.html[Basic Templates] - Template fundamentals
20
+ * link:drops-reference.html[Drops Reference] - Available data
21
+ * https://shopify.github.io/liquid/filters/[Liquid Filters Reference] -
22
+ Complete filter list
23
+ * https://shopify.github.io/liquid/tags/[Liquid Tags Reference] -
24
+ Complete tag list
25
+
26
+ == Concepts
27
+
28
+ Filter:: A function that transforms a value, applied with the pipe `|`
29
+ operator.
30
+
31
+ Tag:: A control structure or command using `{%%}` delimiters.
32
+
33
+ Chaining:: Applying multiple filters sequentially to transform data.
34
+
35
+ Input:: The value passed to a filter (left side of `|`).
36
+
37
+ Parameter:: Additional arguments passed to filters.
38
+
39
+ == Filter basics
40
+
41
+ === Filter syntax
42
+
43
+ Filters transform values using the pipe operator:
44
+
45
+ [source,liquid]
46
+ ----
47
+ {{ value | filter }}
48
+ {{ value | filter: parameter }}
49
+ {{ value | filter: param1, param2 }}
50
+ ----
51
+
52
+ === Filter chaining
53
+
54
+ Apply multiple filters in sequence:
55
+
56
+ [source,liquid]
57
+ ----
58
+ {{ entity.id | upcase | replace: "_", " " }}
59
+ {{ description | truncate: 100 | strip }}
60
+ {{ schema.entities | map: "id" | join: ", " | upcase }}
61
+ ----
62
+
63
+ Each filter receives the output of the previous filter.
64
+
65
+ == String filters
66
+
67
+ === capitalize
68
+
69
+ Capitalize first letter of string:
70
+
71
+ [source,liquid]
72
+ ----
73
+ {{ "express schema" | capitalize }}
74
+ {% comment %}Output: Express schema{% endcomment %}
75
+
76
+ {{ entity.id | capitalize }}
77
+ {% comment %}"person" becomes "Person"{% endcomment %}
78
+ ----
79
+
80
+ === upcase
81
+
82
+ Convert to uppercase:
83
+
84
+ [source,liquid]
85
+ ----
86
+ {{ entity.id | upcase }}
87
+ {% comment %}"person" becomes "PERSON"{% endcomment %}
88
+
89
+ {{ schema.id | upcase }}
90
+ {% comment %}"action_schema" becomes "ACTION_SCHEMA"{% endcomment %}
91
+ ----
92
+
93
+ === downcase
94
+
95
+ Convert to lowercase:
96
+
97
+ [source,liquid]
98
+ ----
99
+ {{ "ACTION_SCHEMA" | downcase }}
100
+ {% comment %}Output: action_schema{% endcomment %}
101
+ ----
102
+
103
+ === strip
104
+
105
+ Remove leading and trailing whitespace:
106
+
107
+ [source,liquid]
108
+ ----
109
+ {{ " extra spaces " | strip }}
110
+ {% comment %}Output: "extra spaces"{% endcomment %}
111
+
112
+ {% capture text %}
113
+ {{ entity.remarks | join: " " }}
114
+ {% endcapture %}
115
+ {{ text | strip }}
116
+ ----
117
+
118
+ === lstrip / rstrip
119
+
120
+ Remove whitespace from left or right only:
121
+
122
+ [source,liquid]
123
+ ----
124
+ {{ " left spaces" | lstrip }}
125
+ {% comment %}Output: "left spaces"{% endcomment %}
126
+
127
+ {{ "right spaces " | rstrip }}
128
+ {% comment %}Output: "right spaces"{% endcomment %}
129
+ ----
130
+
131
+ === truncate
132
+
133
+ Shorten string to specified length:
134
+
135
+ [source,liquid]
136
+ ----
137
+ {{ description | truncate: 50 }}
138
+ {% comment %}Truncates to 50 chars, adds "..."{% endcomment %}
139
+
140
+ {{ description | truncate: 50, "…" }}
141
+ {% comment %}Custom ending character{% endcomment %}
142
+
143
+ {{ entity.remarks | join: " " | truncate: 100 }}
144
+ ----
145
+
146
+ === truncatewords
147
+
148
+ Truncate by word count:
149
+
150
+ [source,liquid]
151
+ ----
152
+ {{ description | truncatewords: 10 }}
153
+ {% comment %}First 10 words only{% endcomment %}
154
+
155
+ {{ entity.remarks | join: " " | truncatewords: 20, "..." }}
156
+ ----
157
+
158
+ === replace / replace_first
159
+
160
+ Replace text in string:
161
+
162
+ [source,liquid]
163
+ ----
164
+ {{ entity.id | replace: "_", " " }}
165
+ {% comment %}"person_name" becomes "person name"{% endcomment %}
166
+
167
+ {{ entity.id | replace: "_", "-" }}
168
+ {% comment %}"person_name" becomes "person-name"{% endcomment %}
169
+
170
+ {% comment %}Replace only first occurrence{% endcomment %}
171
+ {{ text | replace_first: "old", "new" }}
172
+ ----
173
+
174
+ === remove / remove_first
175
+
176
+ Remove text from string:
177
+
178
+ [source,liquid]
179
+ ----
180
+ {{ entity.id | remove: "_schema" }}
181
+ {% comment %}"action_schema" becomes "action"{% endcomment %}
182
+
183
+ {{ text | remove_first: "prefix_" }}
184
+ ----
185
+
186
+ === split
187
+
188
+ Split string into array:
189
+
190
+ [source,liquid]
191
+ ----
192
+ {% assign parts = entity.id | split: "_" %}
193
+ {% for part in parts %}
194
+ - {{ part }}
195
+ {% endfor %}
196
+
197
+ {% comment %}Split and join differently{% endcomment %}
198
+ {{ entity.id | split: "_" | join: " " }}
199
+ ----
200
+
201
+ === slice
202
+
203
+ Extract substring:
204
+
205
+ [source,liquid]
206
+ ----
207
+ {{ entity.id | slice: 0, 4 }}
208
+ {% comment %}First 4 characters{% endcomment %}
209
+
210
+ {{ entity.id | slice: 0 }}
211
+ {% comment %}First character only{% endcomment %}
212
+
213
+ {{ entity.id | slice: -5, 5 }}
214
+ {% comment %}Last 5 characters{% endcomment %}
215
+ ----
216
+
217
+ === append / prepend
218
+
219
+ Add text to end or beginning:
220
+
221
+ [source,liquid]
222
+ ----
223
+ {{ entity.id | append: "_entity" }}
224
+ {% comment %}"person" becomes "person_entity"{% endcomment %}
225
+
226
+ {{ entity.id | prepend: "type_" }}
227
+ {% comment %}"person" becomes "type_person"{% endcomment %}
228
+ ----
229
+
230
+ === newline_to_br
231
+
232
+ Convert newlines to HTML breaks:
233
+
234
+ [source,liquid]
235
+ ----
236
+ {{ entity.remarks | join: "\n" | newline_to_br }}
237
+ ----
238
+
239
+ === strip_html
240
+
241
+ Remove HTML tags:
242
+
243
+ [source,liquid]
244
+ ----
245
+ {{ "<p>Text with <strong>tags</strong></p>" | strip_html }}
246
+ {% comment %}Output: Text with tags{% endcomment %}
247
+ ----
248
+
249
+ === strip_newlines
250
+
251
+ Remove newline characters:
252
+
253
+ [source,liquid]
254
+ ----
255
+ {{ multiline_text | strip_newlines }}
256
+ ----
257
+
258
+ === url_encode / url_decode
259
+
260
+ Encode/decode URLs:
261
+
262
+ [source,liquid]
263
+ ----
264
+ {{ "schema name with spaces" | url_encode }}
265
+ {% comment %}Output: schema+name+with+spaces{% endcomment %}
266
+
267
+ {{ entity.id | url_encode }}
268
+ ----
269
+
270
+ == Array filters
271
+
272
+ === size
273
+
274
+ Get array length:
275
+
276
+ [source,liquid]
277
+ ----
278
+ {{ schema.entities.size }}
279
+ {{ entity.attributes.size }}
280
+
281
+ {% if schema.entities.size > 10 %}
282
+ Large schema with many entities.
283
+ {% endif %}
284
+ ----
285
+
286
+ === first / last
287
+
288
+ Get first or last element:
289
+
290
+ [source,liquid]
291
+ ----
292
+ {{ schema.entities.first.id }}
293
+ {{ schema.entities.last.id }}
294
+
295
+ {% assign first_entity = schema.entities.first %}
296
+ {{ first_entity.id }}
297
+ ----
298
+
299
+ === join
300
+
301
+ Combine array elements into string:
302
+
303
+ [source,liquid]
304
+ ----
305
+ {{ schema.entities | map: "id" | join: ", " }}
306
+ {% comment %}Output: person, company, product{% endcomment %}
307
+
308
+ {{ entity.remarks | join: " " }}
309
+ {% comment %}Combine remarks into single string{% endcomment %}
310
+
311
+ {{ entity.attributes | map: "id" | join: "\n" }}
312
+ {% comment %}One per line{% endcomment %}
313
+ ----
314
+
315
+ === map
316
+
317
+ Extract attribute from each element:
318
+
319
+ [source,liquid]
320
+ ----
321
+ {{ schema.entities | map: "id" }}
322
+ {% comment %}Array of entity IDs{% endcomment %}
323
+
324
+ {{ schema.entities | map: "id" | join: ", " }}
325
+ {% comment %}String of IDs: "person, company"{% endcomment %}
326
+
327
+ {% assign entity_names = schema.entities | map: "id" %}
328
+ {% for name in entity_names %}
329
+ - {{ name }}
330
+ {% endfor %}
331
+ ----
332
+
333
+ === sort / sort_natural
334
+
335
+ Sort array alphabetically:
336
+
337
+ [source,liquid]
338
+ ----
339
+ {% assign sorted = schema.entities | sort: "id" %}
340
+ {% for entity in sorted %}
341
+ - {{ entity.id }}
342
+ {% endfor %}
343
+
344
+ {% comment %}Natural sort (handles numbers){% endcomment %}
345
+ {% assign sorted = items | sort_natural: "name" %}
346
+ ----
347
+
348
+ === reverse
349
+
350
+ Reverse array order:
351
+
352
+ [source,liquid]
353
+ ----
354
+ {% assign reversed = schema.entities | reverse %}
355
+ {% for entity in reversed %}
356
+ - {{ entity.id }}
357
+ {% endfor %}
358
+ ----
359
+
360
+ === uniq
361
+
362
+ Remove duplicate values:
363
+
364
+ [source,liquid]
365
+ ----
366
+ {% assign unique_types = all_types | uniq %}
367
+ ----
368
+
369
+ === where
370
+
371
+ Filter array by attribute value:
372
+
373
+ [source,liquid]
374
+ ----
375
+ {% comment %}Filter to abstract entities only{% endcomment %}
376
+ {% assign abstract = schema.entities | where: "abstract", true %}
377
+
378
+ {% for entity in abstract %}
379
+ - {{ entity.id }} (abstract)
380
+ {% endfor %}
381
+
382
+ {% comment %}This may not work as expected with complex objects{% endcomment %}
383
+ {% comment %}Better to use if conditions in loops{% endcomment %}
384
+ ----
385
+
386
+ === concat
387
+
388
+ Combine two arrays:
389
+
390
+ [source,liquid]
391
+ ----
392
+ {% assign all_items = schema.entities | concat: schema.types %}
393
+
394
+ {% for item in all_items %}
395
+ - {{ item.id }}
396
+ {% endfor %}
397
+ ----
398
+
399
+ === compact
400
+
401
+ Remove nil values:
402
+
403
+ [source,liquid]
404
+ ----
405
+ {% assign cleaned = array_with_nils | compact %}
406
+ ----
407
+
408
+ == Math filters
409
+
410
+ === plus / minus
411
+
412
+ Addition and subtraction:
413
+
414
+ [source,liquid]
415
+ ----
416
+ {% assign total = schema.entities.size | plus: schema.types.size %}
417
+ Total items: {{ total }}
418
+
419
+ {% assign remaining = max | minus: current %}
420
+ {{ remaining }} items left
421
+ ----
422
+
423
+ === times / divided_by
424
+
425
+ Multiplication and division:
426
+
427
+ [source,liquid]
428
+ ----
429
+ {% assign doubled = count | times: 2 %}
430
+ {% assign half = count | divided_by: 2 %}
431
+
432
+ {% comment %}Calculate percentage{% endcomment %}
433
+ {% assign percent = documented | times: 100 | divided_by: total %}
434
+ Coverage: {{ percent }}%
435
+ ----
436
+
437
+ === modulo
438
+
439
+ Get remainder:
440
+
441
+ [source,liquid]
442
+ ----
443
+ {% assign remainder = number | modulo: 3 %}
444
+
445
+ {% comment %}Use for alternating rows{% endcomment %}
446
+ {% if forloop.index | modulo: 2 == 0 %}
447
+ Even row
448
+ {% else %}
449
+ Odd row
450
+ {% endif %}
451
+ ----
452
+
453
+ === round / floor / ceil
454
+
455
+ Round numbers:
456
+
457
+ [source,liquid]
458
+ ----
459
+ {{ 4.6 | round }}
460
+ {% comment %}Output: 5{% endcomment %}
461
+
462
+ {{ 4.6 | floor }}
463
+ {% comment %}Output: 4{% endcomment %}
464
+
465
+ {{ 4.2 | ceil }}
466
+ {% comment %}Output: 5{% endcomment %}
467
+
468
+ {{ 4.5678 | round: 2 }}
469
+ {% comment %}Output: 4.57 (2 decimal places){% endcomment %}
470
+ ----
471
+
472
+ === abs
473
+
474
+ Absolute value:
475
+
476
+ [source,liquid]
477
+ ----
478
+ {{ -5 | abs }}
479
+ {% comment %}Output: 5{% endcomment %}
480
+ ----
481
+
482
+ === at_least / at_most
483
+
484
+ Constrain number to minimum or maximum:
485
+
486
+ [source,liquid]
487
+ ----
488
+ {{ value | at_least: 0 }}
489
+ {% comment %}Ensure non-negative{% endcomment %}
490
+
491
+ {{ value | at_most: 100 }}
492
+ {% comment %}Cap at 100{% endcomment %}
493
+ ----
494
+
495
+ == Date filters
496
+
497
+ While Expressir doesn't typically have date fields, these are available
498
+ if needed:
499
+
500
+ === date
501
+
502
+ Format date:
503
+
504
+ [source,liquid]
505
+ ----
506
+ {{ "now" | date: "%Y-%m-%d" }}
507
+ {% comment %}Output: 2024-01-15{% endcomment %}
508
+
509
+ {{ "now" | date: "%B %d, %Y" }}
510
+ {% comment %}Output: January 15, 2024{% endcomment %}
511
+ ----
512
+
513
+ Common format specifiers:
514
+
515
+ * `%Y` - Year (4 digits)
516
+ * `%m` - Month (01-12)
517
+ * `%d` - Day (01-31)
518
+ * `%B` - Month name
519
+ * `%H` - Hour (00-23)
520
+ * `%M` - Minute (00-59)
521
+ * `%S` - Second (00-59)
522
+
523
+ == Default filter
524
+
525
+ Provide fallback for nil/empty values:
526
+
527
+ [source,liquid]
528
+ ----
529
+ {{ entity.remarks | default: "No description available" }}
530
+
531
+ {{ schema.version.value | default: "Unknown version" }}
532
+
533
+ {% comment %}Works with any value{% endcomment %}
534
+ {{ variable | default: "default value" }}
535
+ ----
536
+
537
+ == Escape filters
538
+
539
+ === escape / escape_once
540
+
541
+ HTML escape:
542
+
543
+ [source,liquid]
544
+ ----
545
+ {{ "<script>alert('xss')</script>" | escape }}
546
+ {% comment %}Output: &lt;script&gt;alert('xss')&lt;/script&gt;{% endcomment %}
547
+
548
+ {% comment %}escape_once won't double-escape{% endcomment %}
549
+ {{ "&lt;tag&gt;" | escape_once }}
550
+ {% comment %}Output: &lt;tag&gt; (not re-escaped){% endcomment %}
551
+ ----
552
+
553
+ == Advanced tags
554
+
555
+ === assign
556
+
557
+ Create variables:
558
+
559
+ [source,liquid]
560
+ ----
561
+ {% assign entity_count = schema.entities.size %}
562
+ {% assign has_types = schema.types.size > 0 %}
563
+ {% assign first = schema.entities.first %}
564
+
565
+ Total: {{ entity_count }}
566
+ ----
567
+
568
+ === capture
569
+
570
+ Capture template output:
571
+
572
+ [source,liquid]
573
+ ----
574
+ {% capture entity_list %}
575
+ {% for entity in schema.entities %}
576
+ {{ entity.id }}{% unless forloop.last %}, {% endunless %}
577
+ {% endfor %}
578
+ {% endcapture %}
579
+
580
+ Entities: {{ entity_list }}
581
+ ----
582
+
583
+ Useful for:
584
+
585
+ * Building complex strings
586
+ * Reusing generated content
587
+ * Conditional output assembly
588
+
589
+ [source,liquid]
590
+ ----
591
+ {% capture stats %}
592
+ Entities: {{ schema.entities.size }}
593
+ Types: {{ schema.types.size }}
594
+ Functions: {{ schema.functions.size }}
595
+ {% endcapture %}
596
+
597
+ {{ stats }}
598
+
599
+ {% comment %}Reuse later{% endcomment %}
600
+ {{ stats }}
601
+ ----
602
+
603
+ === increment / decrement
604
+
605
+ Create auto-incrementing counters:
606
+
607
+ [source,liquid]
608
+ ----
609
+ {% increment counter %}
610
+ {% comment %}Output: 0{% endcomment %}
611
+
612
+ {% increment counter %}
613
+ {% comment %}Output: 1{% endcomment %}
614
+
615
+ {% increment counter %}
616
+ {% comment %}Output: 2{% endcomment %}
617
+
618
+ {% decrement counter2 %}
619
+ {% comment %}Output: -1{% endcomment %}
620
+
621
+ {% decrement counter2 %}
622
+ {% comment %}Output: -2{% endcomment %}
623
+ ----
624
+
625
+ Note: Each counter name is independent.
626
+
627
+ === cycle
628
+
629
+ Cycle through values:
630
+
631
+ [source,liquid]
632
+ ----
633
+ {% for entity in schema.entities %}
634
+ <tr class="{% cycle 'row-odd', 'row-even' %}">
635
+ <td>{{ entity.id }}</td>
636
+ </tr>
637
+ {% endfor %}
638
+
639
+ {% comment %}Named cycles{% endcomment %}
640
+ {% cycle 'group1': 'one', 'two', 'three' %}
641
+ ----
642
+
643
+ === tablerow
644
+
645
+ Create HTML table rows:
646
+
647
+ [source,liquid]
648
+ ----
649
+ <table>
650
+ {% tablerow entity in schema.entities cols:3 %}
651
+ {{ entity.id }}
652
+ {% endtablerow %}
653
+ </table>
654
+ ----
655
+
656
+ Generates table with specified column count.
657
+
658
+ === raw
659
+
660
+ Output Liquid code without processing:
661
+
662
+ [source,liquid]
663
+ ----
664
+ {% raw %}
665
+ {{ entity.id }}
666
+ {% for item in items %}
667
+ {% endraw %}
668
+ {% comment %}Displays literally: {{ entity.id }}{% endcomment %}
669
+ ----
670
+
671
+ Useful for showing example code in documentation.
672
+
673
+ === liquid
674
+
675
+ Process Liquid code from variable:
676
+
677
+ [source,liquid]
678
+ ----
679
+ {% liquid
680
+ assign total = 0
681
+ for entity in schema.entities
682
+ assign total = total | plus: entity.attributes.size
683
+ endfor
684
+ echo "Total attributes: "
685
+ echo total
686
+ %}
687
+ ----
688
+
689
+ Allows multiple statements without individual tag pairs.
690
+
691
+ == Combining filters and data
692
+
693
+ === Entity list formatted
694
+
695
+ [source,liquid]
696
+ ----
697
+ {{ schema.entities | map: "id" | sort | join: ", " | upcase }}
698
+ {% comment %}Output: COMPANY, PERSON, PRODUCT{% endcomment %}
699
+ ----
700
+
701
+ === Entity count with formatting
702
+
703
+ [source,liquid]
704
+ ----
705
+ {% assign count = schema.entities.size %}
706
+ {% assign plural = count | times: 0 | plus: count %}
707
+
708
+ {{ count }} entit
709
+ {%- if count == 1 -%}
710
+ y
711
+ {%- else -%}
712
+ ies
713
+ {%- endif %}
714
+ ----
715
+
716
+ === Build hierarchical display
717
+
718
+ [source,liquid]
719
+ ----
720
+ {% for entity in schema.entities | sort: "id" %}
721
+ {{ forloop.index }}. {{ entity.id | upcase | replace: "_", " " }}
722
+ {% if entity.attributes.size > 0 %}
723
+ ({{ entity.attributes.size }} attribute
724
+ {%- if entity.attributes.size != 1 -%}s{%- endif -%})
725
+ {% endif %}
726
+ {% endfor %}
727
+ ----
728
+
729
+ === Documentation coverage calculation
730
+
731
+ [source,liquid]
732
+ ----
733
+ {% assign total_entities = schema.entities.size %}
734
+ {% assign documented = 0 %}
735
+
736
+ {% for entity in schema.entities %}
737
+ {% if entity.remarks.size > 0 %}
738
+ {% assign documented = documented | plus: 1 %}
739
+ {% endif %}
740
+ {% endfor %}
741
+
742
+ {% if total_entities > 0 %}
743
+ {% assign coverage = documented | times: 100 | divided_by: total_entities %}
744
+ Documentation coverage: {{ coverage }}%
745
+ {% endif %}
746
+ ----
747
+
748
+ === Smart truncation with links
749
+
750
+ [source,liquid]
751
+ ----
752
+ {% for entity in schema.entities %}
753
+ ## {{ entity.id }}
754
+
755
+ {% if entity.remarks.size > 0 %}
756
+ {% assign description = entity.remarks | join: " " %}
757
+ {% if description.size > 200 %}
758
+ {{ description | truncate: 200 }}
759
+
760
+ [Read more](#{{ entity.id }}-full)
761
+ {% else %}
762
+ {{ description }}
763
+ {% endif %}
764
+ {% endif %}
765
+ {% endfor %}
766
+ ----
767
+
768
+ == Performance considerations
769
+
770
+ **Cache computed values**::
771
+ [source,liquid]
772
+ ----
773
+ {% comment %}❌ Inefficient - computes multiple times{% endcomment %}
774
+ {% if schema.entities.size > 0 %}
775
+ Total: {{ schema.entities.size }}
776
+ Average: {{ total | divided_by: schema.entities.size }}
777
+ {% endif %}
778
+
779
+ {% comment %}✅ Efficient - compute once{% endcomment %}
780
+ {% assign count = schema.entities.size %}
781
+ {% if count > 0 %}
782
+ Total: {{ count }}
783
+ Average: {{ total | divided_by: count }}
784
+ {% endif %}
785
+ ----
786
+
787
+ **Avoid complex chaining in loops**::
788
+ [source,liquid]
789
+ ----
790
+ {% comment %}❌ Computes for each entity{% endcomment %}
791
+ {% for entity in schema.entities %}
792
+ {% assign sorted_attrs = entity.attributes | sort: "id" | map: "id" | join: ", " %}
793
+ {% endfor %}
794
+
795
+ {% comment %}✅ Compute outside loop if possible{% endcomment %}
796
+ {% assign sorted_entities = schema.entities | sort: "id" %}
797
+ {% for entity in sorted_entities %}
798
+ ...
799
+ {% endfor %}
800
+ ----
801
+
802
+ **Use simple filters when possible**::
803
+ [source,liquid]
804
+ ----
805
+ {% comment %}Simple and fast{% endcomment %}
806
+ {{ entity.id | upcase }}
807
+ {{ items.size }}
808
+
809
+ {% comment %}More complex, slower{% endcomment %}
810
+ {{ text | split: " " | map: "upcase" | join: " " }}
811
+ ----
812
+
813
+ == Common patterns
814
+
815
+ === Build comma-separated list
816
+
817
+ [source,liquid]
818
+ ----
819
+ {% for entity in schema.entities %}
820
+ {{ entity.id }}{% unless forloop.last %}, {% endunless %}
821
+ {% endfor %}
822
+
823
+ {% comment %}Or using filters{% endcomment %}
824
+ {{ schema.entities | map: "id" | join: ", " }}
825
+ ----
826
+
827
+ === Create anchor links
828
+
829
+ [source,liquid]
830
+ ----
831
+ {% for entity in schema.entities %}
832
+ - [{{ entity.id }}](#{{ entity.id | downcase | replace: "_", "-" }})
833
+ {% endfor %}
834
+ ----
835
+
836
+ === Format numbers with commas
837
+
838
+ [source,liquid]
839
+ ----
840
+ {% comment %}Liquid doesn't have built-in number formatting{% endcomment %}
841
+ {% comment %}Use string manipulation for simple cases{% endcomment %}
842
+ {{ count }} entities
843
+ ----
844
+
845
+ === Generate unique IDs
846
+
847
+ [source,liquid]
848
+ ----
849
+ {% for entity in schema.entities %}
850
+ <div id="{{ schema.id }}-{{ entity.id }}-{{ forloop.index }}">
851
+ {{ entity.id }}
852
+ </div>
853
+ {% endfor %}
854
+ ----
855
+
856
+ == Best practices
857
+
858
+ **Always provide defaults**::
859
+ [source,liquid]
860
+ ----
861
+ {{ entity.remarks | default: "No description" }}
862
+ {{ schema.version.value | default: "Unknown" }}
863
+ ----
864
+
865
+ **Validate before filtering**::
866
+ [source,liquid]
867
+ ----
868
+ {% if entity.attributes and entity.attributes.size > 0 %}
869
+ {{ entity.attributes | map: "id" | join: ", " }}
870
+ {% endif %}
871
+ ----
872
+
873
+ **Use meaningful variable names**::
874
+ [source,liquid]
875
+ ----
876
+ {% assign entity_count = schema.entities.size %}
877
+ {% assign documented_count = documented_entities.size %}
878
+ {% assign coverage_percent = documented_count | times: 100 | divided_by: entity_count %}
879
+ ----
880
+
881
+ **Comment complex filter chains**::
882
+ [source,liquid]
883
+ ----
884
+ {% comment %}Extract entity IDs, sort alphabetically, combine with commas{% endcomment %}
885
+ {{ schema.entities | map: "id" | sort | join: ", " }}
886
+ ----
887
+
888
+ == Next steps
889
+
890
+ Apply these filters and tags:
891
+
892
+ * link:documentation-generation.html[Documentation Generation] -
893
+ Complete workflows
894
+ * link:../../_tutorials/liquid-templates.html[Liquid Templates Tutorial]
895
+ - Practice examples
896
+ * https://shopify.github.io/liquid/[Liquid Documentation] - More
897
+ filters and tags
898
+
899
+ == Summary
900
+
901
+ Liquid filters and tags provide:
902
+
903
+ * ✅ String manipulation: case, replace, truncate, strip
904
+ * ✅ Array operations: map, join, sort, filter
905
+ * ✅ Math operations: plus, minus, times, divided_by
906
+ * ✅ Control: assign, capture, increment
907
+ * ✅ Defaults for missing values
908
+ * ✅ Filter chaining for complex transformations
909
+ * ✅ Performance-conscious usage
910
+
911
+ Master filters and tags to create sophisticated EXPRESS schema
912
+ documentation templates.