luoma 0.1.0

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 (152) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +2 -0
  3. data/.rdoc_options +16 -0
  4. data/CHANGELOG.md +5 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +68 -0
  7. data/Rakefile +69 -0
  8. data/Steepfile +41 -0
  9. data/certs/jgrp.pem +27 -0
  10. data/docs/configuration.md +180 -0
  11. data/docs/custom_filters.md +3 -0
  12. data/docs/custom_tags.md +3 -0
  13. data/docs/expressions.md +3 -0
  14. data/docs/extension_types.md +41 -0
  15. data/docs/filter_reference.md +1702 -0
  16. data/docs/index.md +73 -0
  17. data/docs/luoma_for_template_authors.md +3 -0
  18. data/docs/markdown.md +111 -0
  19. data/docs/predicate_reference.md +3 -0
  20. data/docs/static_analysis.md +3 -0
  21. data/docs/tag_reference.md +352 -0
  22. data/docs/template_loaders.md +177 -0
  23. data/docs/undefined_variables.md +3 -0
  24. data/docs-requirements.txt +11 -0
  25. data/docs_/cycle.md +17 -0
  26. data/docs_/font_rendering_example.md +157 -0
  27. data/docs_/header_block_example.md +51 -0
  28. data/docs_/increment_and_decrement.md +49 -0
  29. data/docs_/logo_style_example.md +40 -0
  30. data/docs_/migration.md +11 -0
  31. data/docs_/notes.md +19 -0
  32. data/lib/luoma/cache.rb +80 -0
  33. data/lib/luoma/chain_hash.rb +54 -0
  34. data/lib/luoma/context.rb +227 -0
  35. data/lib/luoma/drop.rb +84 -0
  36. data/lib/luoma/drops/block.rb +33 -0
  37. data/lib/luoma/drops/expression.rb +29 -0
  38. data/lib/luoma/drops/html_safe.rb +36 -0
  39. data/lib/luoma/drops/range.rb +69 -0
  40. data/lib/luoma/drops/undefined.rb +119 -0
  41. data/lib/luoma/environment.rb +479 -0
  42. data/lib/luoma/errors.rb +79 -0
  43. data/lib/luoma/escape.rb +35 -0
  44. data/lib/luoma/expression.rb +1271 -0
  45. data/lib/luoma/filter.rb +97 -0
  46. data/lib/luoma/filters/array.rb +372 -0
  47. data/lib/luoma/filters/date.rb +19 -0
  48. data/lib/luoma/filters/default.rb +16 -0
  49. data/lib/luoma/filters/json.rb +14 -0
  50. data/lib/luoma/filters/math.rb +84 -0
  51. data/lib/luoma/filters/size.rb +13 -0
  52. data/lib/luoma/filters/slice.rb +52 -0
  53. data/lib/luoma/filters/sort.rb +113 -0
  54. data/lib/luoma/filters/string.rb +186 -0
  55. data/lib/luoma/fnv.rb +15 -0
  56. data/lib/luoma/lexer.rb +106 -0
  57. data/lib/luoma/lexer_legacy.rb +396 -0
  58. data/lib/luoma/lexer_unified.rb +279 -0
  59. data/lib/luoma/loader.rb +50 -0
  60. data/lib/luoma/loaders/choice_loader.rb +20 -0
  61. data/lib/luoma/loaders/file_system_loader.rb +75 -0
  62. data/lib/luoma/loaders/mixins.rb +52 -0
  63. data/lib/luoma/markup.rb +109 -0
  64. data/lib/luoma/parser.rb +252 -0
  65. data/lib/luoma/parser_unified.rb +979 -0
  66. data/lib/luoma/predicates/blank.rb +19 -0
  67. data/lib/luoma/predicates/defined.rb +10 -0
  68. data/lib/luoma/predicates/empty.rb +15 -0
  69. data/lib/luoma/predicates/type.rb +35 -0
  70. data/lib/luoma/static_analysis.rb +427 -0
  71. data/lib/luoma/tags/assign.rb +63 -0
  72. data/lib/luoma/tags/break.rb +23 -0
  73. data/lib/luoma/tags/capture.rb +43 -0
  74. data/lib/luoma/tags/case.rb +137 -0
  75. data/lib/luoma/tags/comment.rb +17 -0
  76. data/lib/luoma/tags/continue.rb +23 -0
  77. data/lib/luoma/tags/define.rb +42 -0
  78. data/lib/luoma/tags/else.rb +30 -0
  79. data/lib/luoma/tags/for.rb +108 -0
  80. data/lib/luoma/tags/if.rb +109 -0
  81. data/lib/luoma/tags/import.rb +91 -0
  82. data/lib/luoma/tags/include.rb +77 -0
  83. data/lib/luoma/tags/output.rb +20 -0
  84. data/lib/luoma/tags/raw.rb +26 -0
  85. data/lib/luoma/tags/render.rb +92 -0
  86. data/lib/luoma/tags/with.rb +55 -0
  87. data/lib/luoma/template.rb +178 -0
  88. data/lib/luoma/token.rb +84 -0
  89. data/lib/luoma/version.rb +5 -0
  90. data/lib/luoma.rb +75 -0
  91. data/sig/luoma/cache.rbs +44 -0
  92. data/sig/luoma/chain_hash.rbs +26 -0
  93. data/sig/luoma/context.rbs +115 -0
  94. data/sig/luoma/drop.rbs +44 -0
  95. data/sig/luoma/drops/block.rbs +10 -0
  96. data/sig/luoma/drops/expression.rbs +10 -0
  97. data/sig/luoma/drops/html_safe.rbs +14 -0
  98. data/sig/luoma/drops/range.rbs +15 -0
  99. data/sig/luoma/drops/undefined.rbs +27 -0
  100. data/sig/luoma/environment.rbs +212 -0
  101. data/sig/luoma/errors.rbs +67 -0
  102. data/sig/luoma/escape.rbs +15 -0
  103. data/sig/luoma/expression.rbs +514 -0
  104. data/sig/luoma/filter.rbs +66 -0
  105. data/sig/luoma/filters/array.rbs +74 -0
  106. data/sig/luoma/filters/date.rbs +9 -0
  107. data/sig/luoma/filters/default.rbs +8 -0
  108. data/sig/luoma/filters/json.rbs +7 -0
  109. data/sig/luoma/filters/math.rbs +37 -0
  110. data/sig/luoma/filters/size.rbs +6 -0
  111. data/sig/luoma/filters/slice.rbs +10 -0
  112. data/sig/luoma/filters/sort.rbs +17 -0
  113. data/sig/luoma/filters/string.rbs +103 -0
  114. data/sig/luoma/fnv.rbs +3 -0
  115. data/sig/luoma/lexer.rbs +42 -0
  116. data/sig/luoma/lexer_legacy.rbs +81 -0
  117. data/sig/luoma/lexer_unified.rbs +49 -0
  118. data/sig/luoma/loader.rbs +40 -0
  119. data/sig/luoma/loaders/choice_loader.rbs +9 -0
  120. data/sig/luoma/loaders/file_system_loader.rbs +19 -0
  121. data/sig/luoma/loaders/mixins.rbs +16 -0
  122. data/sig/luoma/markup.rbs +68 -0
  123. data/sig/luoma/parser.rbs +105 -0
  124. data/sig/luoma/parser_unified.rbs +150 -0
  125. data/sig/luoma/predicates/blank.rbs +6 -0
  126. data/sig/luoma/predicates/defined.rbs +6 -0
  127. data/sig/luoma/predicates/empty.rbs +6 -0
  128. data/sig/luoma/predicates/type.rbs +21 -0
  129. data/sig/luoma/static_analysis.rbs +141 -0
  130. data/sig/luoma/tags/assign.rbs +12 -0
  131. data/sig/luoma/tags/break.rbs +11 -0
  132. data/sig/luoma/tags/capture.rbs +14 -0
  133. data/sig/luoma/tags/case.rbs +36 -0
  134. data/sig/luoma/tags/comment.rbs +11 -0
  135. data/sig/luoma/tags/continue.rbs +11 -0
  136. data/sig/luoma/tags/define.rbs +16 -0
  137. data/sig/luoma/tags/else.rbs +17 -0
  138. data/sig/luoma/tags/for.rbs +26 -0
  139. data/sig/luoma/tags/if.rbs +45 -0
  140. data/sig/luoma/tags/import.rbs +13 -0
  141. data/sig/luoma/tags/include.rbs +13 -0
  142. data/sig/luoma/tags/output.rbs +13 -0
  143. data/sig/luoma/tags/raw.rbs +11 -0
  144. data/sig/luoma/tags/render.rbs +13 -0
  145. data/sig/luoma/tags/with.rbs +15 -0
  146. data/sig/luoma/template.rbs +117 -0
  147. data/sig/luoma/token.rbs +81 -0
  148. data/sig/luoma.rbs +11 -0
  149. data/zensical.toml +363 -0
  150. data.tar.gz.sig +0 -0
  151. metadata +233 -0
  152. metadata.gz.sig +0 -0
data/docs/index.md ADDED
@@ -0,0 +1,73 @@
1
+ # Luoma - Ruby
2
+
3
+ Luoma is a modern template engine with a well-defined, composable, implementation agnostic expression language.
4
+
5
+ Luoma markup will be familiar to anyone who's used [Liquid](https://github.com/Shopify/liquid), [Jinja](https://jinja.palletsprojects.com/en/stable/) or [Django's template language](https://docs.djangoproject.com/en/6.0/topics/templates/#the-django-template-language), but with a strictly immutable data model, first-class blocks and expressions, and functional primitives for when data transformation is necessary.
6
+
7
+ If you're a template author, start with [Luoma for template authors](./luoma_for_template_authors.md). The rest of this documentation covers how to install, configure, use and extend Luoma if you're an application developer.
8
+
9
+ ## Install
10
+
11
+ Add `'luoma'` to your Gemfile:
12
+
13
+ ```ruby
14
+ gem 'luoma', '~> 0.1.0'
15
+ ```
16
+
17
+ Or:
18
+
19
+ ```sh
20
+ gem install luoma
21
+ ```
22
+
23
+ Or:
24
+
25
+ ```sh
26
+ bundle add luoma
27
+ ```
28
+
29
+ ## Quick start
30
+
31
+ ### Render
32
+
33
+ Render a template by passing a string to `Luoma.render(source, data = nil)`. If _data_ is given it should be a hash mapping strings to objects. Hash values will be available as template variables bound to their associated keys.
34
+
35
+ ```ruby
36
+ require "luoma"
37
+
38
+ puts luoma.render("Hello, {{ you }}!", "you" => "World") # Hello, World!
39
+ ```
40
+
41
+ `Luoma.render` is a convenience method equivalent to `Luoma::DEFAULT_ENVIRONMENT.parse(source).render(data)`.
42
+
43
+ ### Parse
44
+
45
+ Often you'll want to render the same template multiple times with different variables. We can parse source text without immediately rendering it using `Luoma.parse(source, globals: nil)`. `Luoma.parse` returns an instance of `Luoma::Template` with a `render(data)`method.
46
+
47
+ ```ruby
48
+ require "luoma"
49
+
50
+ template = Luoma.parse("Hello, {{ you }}!")
51
+ puts template.render("you" => "World") # Hello, World!
52
+ puts template.render("you" => "Luoma") # Hello, Luoma!
53
+ ```
54
+
55
+ If _globals_ is given, data from _globals_ is pined to the resulting template and merged into data from `Luoma::Template#render` every time the template is rendered, with `render` arguments taking priority over pinned data.
56
+
57
+ `Luoma.parse` is a convenience method equivalent to `Luoma::DEFAULT_ENVIRONMENT.parse(source)` or `Luoma::Environment.new.parse(source)`.
58
+
59
+ ### Configure
60
+
61
+ Both `Luoma.parse` and `Luoma.render` are convenience functions that use the default Luoma environment. For all but the simplest of cases you'll want to configure your own instance of `Luoma::Environment`, then load and render templates from that.
62
+
63
+ ```ruby
64
+ require "luoma"
65
+
66
+ env = Luoma::Environment.new(
67
+ loader: Luoma::CachingFileSystemLoader.new("templates/")
68
+ )
69
+
70
+ template = env.get_template("index.luoma")
71
+ another_template = env.parse("{% render 'index.luoma' %}")
72
+ # ...
73
+ ```
@@ -0,0 +1,3 @@
1
+ # Luoma for template authors
2
+
3
+ TODO:
data/docs/markdown.md ADDED
@@ -0,0 +1,111 @@
1
+ ---
2
+ icon: simple/markdown
3
+ ---
4
+
5
+ # Markdown in 5min
6
+
7
+ ## Headers
8
+
9
+ ```
10
+ # H1 Header
11
+ ## H2 Header
12
+ ### H3 Header
13
+ #### H4 Header
14
+ ##### H5 Header
15
+ ###### H6 Header
16
+ ```
17
+
18
+ ## Text formatting
19
+
20
+ ```
21
+ **bold text**
22
+ *italic text*
23
+ ***bold and italic***
24
+ ~~strikethrough~~
25
+ `inline code`
26
+ ```
27
+
28
+ ## Links and images
29
+
30
+ ```
31
+ [Link text](https://example.com)
32
+ [Link with title](https://example.com "Hover title")
33
+ ![Alt text](image.jpg)
34
+ ![Image with title](image.jpg "Image title")
35
+ ```
36
+
37
+ ## Lists
38
+
39
+ ```
40
+ Unordered:
41
+
42
+ - Item 1
43
+ - Item 2
44
+ - Nested item
45
+
46
+ Ordered:
47
+
48
+ 1. First item
49
+ 2. Second item
50
+ 3. Third item
51
+ ```
52
+
53
+ ## Blockquotes
54
+
55
+ ```
56
+ > This is a blockquote
57
+ > Multiple lines
58
+ >> Nested quote
59
+ ```
60
+
61
+ ## Code blocks
62
+
63
+ ````
64
+ ```javascript
65
+ function hello() {
66
+ console.log("Hello, world!");
67
+ }
68
+ ```
69
+ ````
70
+
71
+ ## Tables
72
+
73
+ ```
74
+ | Header 1 | Header 2 | Header 3 |
75
+ |----------|----------|----------|
76
+ | Row 1 | Data | Data |
77
+ | Row 2 | Data | Data |
78
+ ```
79
+
80
+ ## Horizontal rule
81
+
82
+ ```
83
+ ---
84
+ or
85
+ ***
86
+ or
87
+ ___
88
+ ```
89
+
90
+ ## Task lists
91
+
92
+ ```
93
+ - [x] Completed task
94
+ - [ ] Incomplete task
95
+ - [ ] Another task
96
+ ```
97
+
98
+ ## Escaping characters
99
+
100
+ ```
101
+ Use backslash to escape: \* \_ \# \`
102
+ ```
103
+
104
+ ## Line breaks
105
+
106
+ ```
107
+ End a line with two spaces
108
+ to create a line break.
109
+
110
+ Or use a blank line for a new paragraph.
111
+ ```
@@ -0,0 +1,3 @@
1
+ # Built-in predicates
2
+
3
+ TODO
@@ -0,0 +1,3 @@
1
+ # Static analysis
2
+
3
+ TODO:
@@ -0,0 +1,352 @@
1
+ # Built-in tags
2
+
3
+ ## Comments
4
+
5
+ ```title="Syntax"
6
+ {# <comment text> #}
7
+ ```
8
+
9
+ Comments can be used to add documentation to your templates or "comment out" chunks of markup so that it wont be rendered.
10
+
11
+ ```liquid2
12
+ {# This is a comment #}
13
+ {#
14
+ Comments can
15
+ span
16
+ multiple lines
17
+ #}
18
+ ```
19
+
20
+ You can safely comment out markup containing comments by adding hashes, as long as the number of hashes is balanced.
21
+
22
+ ```liquid2
23
+ {##
24
+ {# existing comment #}
25
+ {{ something }}
26
+ ##}
27
+ ```
28
+
29
+ By convention, comments that start with a single hash on every line are considered doc comments to be parsed by a documentation generator.
30
+
31
+ ```liquid2
32
+ {##
33
+ # @param font
34
+ # @returns CSS
35
+ ##}
36
+ ```
37
+
38
+ ## Output
39
+
40
+ ```title="Syntax"
41
+ {{ <expression> }}
42
+ ```
43
+
44
+ An expression surrounded by double curly braces (`{{` and `}}`) is an _output statement_. When rendered, the expression will be evaluated and the result written to the output stream.
45
+
46
+ In this example the expression is the variable `you` - which will be resolved to a value and the value's string representation will be written to the output stream - but output statements can contain any expression.
47
+
48
+ ```liquid2
49
+ Hello, {{ you }}!
50
+ ```
51
+
52
+ ## assign
53
+
54
+ ```title="Syntax"
55
+ {% assign <identifier> = <expression> [, <identifier> = <expression> ...] %}
56
+ ```
57
+
58
+ The `assign` tag binds the result of evaluating an expression to a name, creating or updating a variable.
59
+
60
+ ```liquid2
61
+ {% assign foo = "bar" %}
62
+ {% assign foo = 42 %}
63
+ ```
64
+
65
+ A single `assign` tag can define multiple variables, separated by commas. Earlier bindings are immediately available in later expressions.
66
+
67
+ ```liquid2
68
+ {% assign
69
+ a = 1,
70
+ b = a + 1
71
+ %}
72
+ ```
73
+
74
+ ## capture
75
+
76
+ ```title="Syntax"
77
+ {% capture <identifier> %} <markup> {% endcapture %}
78
+ ```
79
+
80
+ The `capture` tag renders a block of markup to a string variable instead of writing it directly to the output stream.
81
+
82
+ ```liquid2
83
+ {% assign first_name = "Sam" %}
84
+ {% assign points = 120 %}
85
+
86
+ {% capture message %}
87
+ Hello {{ first_name }}!
88
+ You have {{ points }} reward points.
89
+ {% endcapture %}
90
+
91
+ <p>{{ message }}</p>
92
+ ```
93
+
94
+ ```title="Output"
95
+ <p>Hello Sam!
96
+ You have 120 reward points.</p>
97
+ ```
98
+
99
+ See also the [`define`](#define) tag.
100
+
101
+ ## case
102
+
103
+ ```title="Syntax"
104
+ {% case <expression> %}
105
+ [ {% when <expression | predicate> [, <expression | predicate>] %} <markup> ] ...
106
+ [ {% else %} <markup> ]
107
+ {% endcase %}
108
+ ```
109
+
110
+ The `case` tag provides a multi-way conditional. It compares the result of a single expression against the result of one or more candidate expressions, and renders the block following the first matching `{% when %}` tag. If no branch matches, the optional `{% else %}` branch is rendered.
111
+
112
+ ```liquid2
113
+ {% assign day = "Monday" %}
114
+
115
+ {% case day %}
116
+ {% when "Monday" %}
117
+ Start of the work week!
118
+ {% when "Friday" %}
119
+ It's almost the weekend!
120
+ {% when "Saturday" or "Sunday" %}
121
+ Enjoy your weekend!
122
+ {% else %}
123
+ Just another weekday.
124
+ {% endcase %}
125
+ ```
126
+
127
+ `when` tags also understand _bare predicates_, like `.defined?` and `.empty?` in place of a standard expression.
128
+
129
+ ```liquid2
130
+ {% case items %}
131
+ {% when empty? %}
132
+ No items.
133
+ {% else %}
134
+ {{ items | size }} items.
135
+ {% endcase %}
136
+ ```
137
+
138
+ ## define
139
+
140
+ ```title="Syntax"
141
+ {% define <identifier> [ visibility: ("public" | "private") ] %} <markup> {% enddefine %}
142
+ ```
143
+
144
+ The `define` tag captures a block of markup for later rendering. `define` captures nothing about the scope in which it rendered. Defined blocks are rendered in the scope in which they are output or coerced to a string.
145
+
146
+ ```liquid2
147
+ {% assign name = "Alice" %}
148
+
149
+ {% define greeting %}
150
+ Hello {{ name }}!
151
+ {% enddefine %}
152
+
153
+ {% assign name = "Bob" %}
154
+
155
+ {{ greeting }}
156
+ {{ greeting | upcase }}
157
+ ```
158
+
159
+ ```title="Output"
160
+ Hello Bob!
161
+ HELLO BOB!
162
+ ```
163
+
164
+ `define` can be though of as a deferred [`capture`](#capture). See also [`import`](#import).
165
+
166
+ ## for
167
+
168
+ ```title="Syntax"
169
+ {% for <identifier> [, identifier [, identifier]] in <expression> %}
170
+ <markup>
171
+ [ {% else %} <markup> ]
172
+ {% endfor %}
173
+ ```
174
+
175
+ The `for` tag renders its block once for each item in an iterable object. If the iterable is empty and an `else` block given, it will be rendered instead.
176
+
177
+ ```liquid2
178
+ {% for product in collection %}
179
+ - {{ product.title }}
180
+ {% else %}
181
+ No products available
182
+ {% endfor %}
183
+ ```
184
+
185
+ Optionally, the current zero-based iteration index can be bound to a name.
186
+
187
+ ```liquid2
188
+ {% for product, index in collection %}
189
+ - {{ product.title }} at index {{ index }}
190
+ {% endfor %}
191
+ ```
192
+
193
+ And the optional third identifier binds the array being iterated to a name.
194
+
195
+ ```liquid2
196
+ {% for product, index, array in collection %}
197
+ {%- assign last = index == (array | size) - 1 -%}
198
+ ...
199
+ {% endfor %}
200
+ ```
201
+
202
+ ### break
203
+
204
+ You can exit a loop early using the `break` tag.
205
+
206
+ ```liquid2
207
+ {% for product in collection.products %}
208
+ {% if product.title == "Shirt" %}
209
+ {% break %}
210
+ {% endif %}
211
+
212
+ - {{ product.title }}
213
+ {% endfor %}
214
+ ```
215
+
216
+ ### continue
217
+
218
+ You can skip all or part of a loop iteration with the `continue` tag.
219
+
220
+ ```liquid2
221
+ {% for product in collection.products %}
222
+ {% if product.title == "Shirt" %}
223
+ {% continue %}
224
+ {% endif %}
225
+
226
+ - {{ product.title }}
227
+ {% endfor %}
228
+ ```
229
+
230
+ ## if
231
+
232
+ ```
233
+ {% if <expression> %}
234
+ <markup>
235
+ [ {% elsif <expression> %} <markup> [ {% elsif <expression> %} ... ]]
236
+ [ {% else %} <markup> ... ]
237
+ {% endif %}
238
+ ```
239
+
240
+ The `if` tag conditionally renders its block if its expression evaluates to be truthy. Any number of `elsif` blocks can be given to add alternative conditions, and an `else` block is used as a default if no preceding conditions were truthy.
241
+
242
+ ```liquid2
243
+ {% if product.title == "OK Hat" %}
244
+ This hat is OK.
245
+ {% elsif product.title == "Rubbish Tie" %}
246
+ This tie is rubbish.
247
+ {% else %}
248
+ Not sure what this is.
249
+ {% endif %}
250
+ ```
251
+
252
+ ## import
253
+
254
+ ```title="Syntax"
255
+ {% import <string> [ as <identifier>] %}
256
+ ```
257
+
258
+ The `import` tag loads and renders a named template for its side effects, discarding its output. `import` is used to bring variables (including blocks defined with the [`define` tag](#define)) into scope.
259
+
260
+ ```liquid2
261
+ {% import button_utils as buttons %}
262
+ ```
263
+
264
+ ## include
265
+
266
+ ```title="Syntax"
267
+ {% include <template name>
268
+ [[,] <identifier>: <expression> [, [<identifier>: <expression> ... ]]]
269
+ %}
270
+ ```
271
+
272
+ The `include` tag loads and renders a named template, inserting the resulting text in its place. The name of the template to include can be a string literal or a variable resolving to a string. When rendered, the included template will share the same scope as the "calling" template.
273
+
274
+ ```liquid2
275
+ {% include "snippets/header.html" %}
276
+ ```
277
+
278
+ ### Keyword arguments
279
+
280
+ Additional keyword arguments given to the `include` tag will be added to the included template's scope, then go out of scope after the included template has been rendered.
281
+
282
+ ```liquid2
283
+ {% include "partial_template" greeting: "Hello", num: 3, skip: 2 %}
284
+ ```
285
+
286
+ ## raw
287
+
288
+ ```
289
+ {% raw %} <text> {% endraw %}
290
+ ```
291
+
292
+ Any text between `{% raw %}` and `{% endraw %}` will not be interpreted as markup, but output as plain text instead.
293
+
294
+ ```liquid2
295
+ {% raw %}
296
+ This will be rendered {{verbatim}}, with the curly brackets.
297
+ {% endraw %}
298
+ ```
299
+
300
+ ## render
301
+
302
+ ```title="Syntax"
303
+ {% render <string>
304
+ [[,] <identifier>: <expression> [, [<identifier>: <expression> ... ]]]
305
+ %}
306
+ ```
307
+
308
+ The `render` tag loads and renders a named template, inserting the resulting text in its place. The name of the template to include **must** be a string literal. When rendered, the included template will have its onw scope, without variables define in the calling template.
309
+
310
+ ```liquid2
311
+ {% render "snippets/header.html" %}
312
+ ```
313
+
314
+ ### Keyword arguments
315
+
316
+ Additional keyword arguments given to the `render` tag will be added to the rendered template's scope, then go out of scope after the it has been rendered.
317
+
318
+ ```liquid2
319
+ {% render "partial_template" greeting: "Hello", num: 3, skip: 2 %}
320
+ ```
321
+
322
+ ## with
323
+
324
+ ```title="Syntax"
325
+ {% with <identifier> = <expression> [, <identifier> = <expression> ... ] %}
326
+ <markup>
327
+ {% endwith %}
328
+ ```
329
+
330
+ The `with` tag introduces block-scoped variables. These variables have the potential to shadow global variables or variables assigned with `{% assign %}`, `{% capture %}` and `{% define %}`.
331
+
332
+ ```liquid2
333
+ {% with p = collection.products.first %}
334
+ {{ p.title }}
335
+ {% endwith %}
336
+
337
+ {% with a = 1, b = 3.4 %}
338
+ {{ a }} + {{ b }} = {{ a + b }}
339
+ {% endwith %}
340
+ ```
341
+
342
+ When multiple bindings are given, earlier variables are in scope for later expressions.
343
+
344
+ ```liquid2
345
+ {% with
346
+ a = 1,
347
+ b = 3.4,
348
+ c = a + b,
349
+ %}
350
+ {{ a }} + {{ b }} = {{ c }}
351
+ {% endwith %}
352
+ ```