expressir 2.1.29 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/docs.yml +98 -0
- data/.github/workflows/links.yml +100 -0
- data/.github/workflows/rake.yml +4 -0
- data/.github/workflows/release.yml +5 -0
- data/.github/workflows/validate_schemas.yml +1 -1
- data/.gitignore +3 -0
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +209 -55
- data/Gemfile +2 -1
- data/README.adoc +650 -83
- data/docs/Gemfile +12 -0
- data/docs/_config.yml +141 -0
- data/docs/_guides/changes/changes-format.adoc +778 -0
- data/docs/_guides/changes/importing-eengine.adoc +898 -0
- data/docs/_guides/changes/index.adoc +396 -0
- data/docs/_guides/changes/programmatic-usage.adoc +1038 -0
- data/docs/_guides/changes/validating-changes.adoc +681 -0
- data/docs/_guides/cli/benchmark-performance.adoc +834 -0
- data/docs/_guides/cli/coverage-analysis.adoc +921 -0
- data/docs/_guides/cli/format-schemas.adoc +547 -0
- data/docs/_guides/cli/index.adoc +8 -0
- data/docs/_guides/cli/managing-changes.adoc +927 -0
- data/docs/_guides/cli/validate-ascii.adoc +645 -0
- data/docs/_guides/cli/validate-schemas.adoc +534 -0
- data/docs/_guides/index.adoc +165 -0
- data/docs/_guides/ler/creating-packages.adoc +664 -0
- data/docs/_guides/ler/index.adoc +305 -0
- data/docs/_guides/ler/loading-packages.adoc +707 -0
- data/docs/_guides/ler/package-formats.adoc +748 -0
- data/docs/_guides/ler/querying-packages.adoc +826 -0
- data/docs/_guides/ler/validating-packages.adoc +750 -0
- data/docs/_guides/liquid/basic-templates.adoc +813 -0
- data/docs/_guides/liquid/documentation-generation.adoc +1042 -0
- data/docs/_guides/liquid/drops-reference.adoc +829 -0
- data/docs/_guides/liquid/filters-and-tags.adoc +912 -0
- data/docs/_guides/liquid/index.adoc +468 -0
- data/docs/_guides/manifests/creating-manifests.adoc +483 -0
- data/docs/_guides/manifests/index.adoc +307 -0
- data/docs/_guides/manifests/resolving-manifests.adoc +557 -0
- data/docs/_guides/manifests/validating-manifests.adoc +713 -0
- data/docs/_guides/ruby-api/formatting-schemas.adoc +605 -0
- data/docs/_guides/ruby-api/index.adoc +257 -0
- data/docs/_guides/ruby-api/parsing-files.adoc +421 -0
- data/docs/_guides/ruby-api/search-engine.adoc +609 -0
- data/docs/_guides/ruby-api/working-with-repository.adoc +577 -0
- data/docs/_pages/data-model.adoc +665 -0
- data/docs/_pages/express-language.adoc +506 -0
- data/docs/_pages/getting-started.adoc +414 -0
- data/docs/_pages/index.adoc +116 -0
- data/docs/_pages/introduction.adoc +256 -0
- data/docs/_pages/ler-packages.adoc +837 -0
- data/docs/_pages/parsers.adoc +683 -0
- data/docs/_pages/schema-manifests.adoc +431 -0
- data/docs/_references/index.adoc +228 -0
- data/docs/_tutorials/creating-ler-package.adoc +735 -0
- data/docs/_tutorials/documentation-coverage.adoc +795 -0
- data/docs/_tutorials/index.adoc +221 -0
- data/docs/_tutorials/liquid-templates.adoc +806 -0
- data/docs/_tutorials/parsing-your-first-schema.adoc +522 -0
- data/docs/_tutorials/querying-schemas.adoc +751 -0
- data/docs/_tutorials/working-with-multiple-schemas.adoc +676 -0
- data/docs/index.adoc +242 -0
- data/docs/lychee.toml +84 -0
- data/examples/demo_ler_usage.sh +86 -0
- data/examples/ler/README.md +111 -0
- data/examples/ler/simple_example.ler +0 -0
- data/examples/ler/simple_schema.exp +33 -0
- data/examples/ler_build.rb +75 -0
- data/examples/ler_cli.rb +79 -0
- data/examples/ler_demo_complete.rb +276 -0
- data/examples/ler_query.rb +91 -0
- data/examples/ler_query_examples.rb +305 -0
- data/examples/ler_stats.rb +81 -0
- data/examples/phase3_demo.rb +159 -0
- data/examples/query_demo_simple.rb +131 -0
- data/expressir.gemspec +2 -0
- data/lib/expressir/changes/schema_change.rb +32 -22
- data/lib/expressir/changes/{edition_change.rb → version_change.rb} +3 -3
- data/lib/expressir/cli.rb +12 -4
- data/lib/expressir/commands/changes_import_eengine.rb +2 -2
- data/lib/expressir/commands/changes_validate.rb +1 -1
- data/lib/expressir/commands/manifest.rb +427 -0
- data/lib/expressir/commands/package.rb +1274 -0
- data/lib/expressir/commands/validate.rb +70 -37
- data/lib/expressir/commands/validate_ascii.rb +607 -0
- data/lib/expressir/commands/validate_load.rb +88 -0
- data/lib/expressir/express/formatter.rb +5 -1
- data/lib/expressir/express/formatters/remark_item_formatter.rb +25 -0
- data/lib/expressir/express/parser.rb +33 -0
- data/lib/expressir/manifest/resolver.rb +213 -0
- data/lib/expressir/manifest/validator.rb +195 -0
- data/lib/expressir/model/declarations/entity.rb +6 -0
- data/lib/expressir/model/dependency_resolver.rb +270 -0
- data/lib/expressir/model/indexes/entity_index.rb +103 -0
- data/lib/expressir/model/indexes/reference_index.rb +148 -0
- data/lib/expressir/model/indexes/type_index.rb +149 -0
- data/lib/expressir/model/interface_validator.rb +384 -0
- data/lib/expressir/model/repository.rb +400 -5
- data/lib/expressir/model/repository_validator.rb +295 -0
- data/lib/expressir/model/search_engine.rb +525 -0
- data/lib/expressir/model.rb +4 -94
- data/lib/expressir/package/builder.rb +200 -0
- data/lib/expressir/package/metadata.rb +81 -0
- data/lib/expressir/package/reader.rb +165 -0
- data/lib/expressir/schema_manifest.rb +11 -1
- data/lib/expressir/version.rb +1 -1
- data/lib/expressir.rb +16 -3
- metadata +115 -5
- data/docs/benchmarking.adoc +0 -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: <script>alert('xss')</script>{% endcomment %}
|
|
547
|
+
|
|
548
|
+
{% comment %}escape_once won't double-escape{% endcomment %}
|
|
549
|
+
{{ "<tag>" | escape_once }}
|
|
550
|
+
{% comment %}Output: <tag> (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.
|