liquid 5.5.0 → 5.6.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.
- checksums.yaml +4 -4
- data/History.md +7 -0
- data/README.md +45 -3
- data/lib/liquid/block_body.rb +11 -8
- data/lib/liquid/const.rb +8 -0
- data/lib/liquid/context.rb +11 -9
- data/lib/liquid/deprecations.rb +22 -0
- data/lib/liquid/environment.rb +159 -0
- data/lib/liquid/lexer.rb +177 -1
- data/lib/liquid/parse_context.rb +4 -3
- data/lib/liquid/parse_tree_visitor.rb +1 -1
- data/lib/liquid/parser.rb +1 -1
- data/lib/liquid/standardfilters.rb +1 -3
- data/lib/liquid/tag.rb +3 -0
- data/lib/liquid/tags/assign.rb +0 -2
- data/lib/liquid/tags/break.rb +0 -2
- data/lib/liquid/tags/capture.rb +0 -2
- data/lib/liquid/tags/case.rb +0 -2
- data/lib/liquid/tags/comment.rb +0 -2
- data/lib/liquid/tags/continue.rb +0 -2
- data/lib/liquid/tags/cycle.rb +6 -2
- data/lib/liquid/tags/decrement.rb +0 -2
- data/lib/liquid/tags/echo.rb +0 -2
- data/lib/liquid/tags/for.rb +0 -2
- data/lib/liquid/tags/if.rb +1 -3
- data/lib/liquid/tags/ifchanged.rb +0 -2
- data/lib/liquid/tags/include.rb +0 -2
- data/lib/liquid/tags/increment.rb +0 -2
- data/lib/liquid/tags/inline_comment.rb +0 -2
- data/lib/liquid/tags/raw.rb +0 -2
- data/lib/liquid/tags/render.rb +0 -2
- data/lib/liquid/tags/table_row.rb +6 -2
- data/lib/liquid/tags/unless.rb +0 -2
- data/lib/liquid/tags.rb +47 -0
- data/lib/liquid/template.rb +51 -56
- data/lib/liquid/tokenizer.rb +1 -1
- data/lib/liquid/variable.rb +13 -7
- data/lib/liquid/version.rb +1 -1
- data/lib/liquid.rb +10 -14
- metadata +37 -10
- data/lib/liquid/strainer_factory.rb +0 -41
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d1a39a98605d86cc2cf586a59d1a402ed708ad3722061083514eeeea602249d9
|
|
4
|
+
data.tar.gz: d3de02a25cada366198f08959e8d6565b120b961978810b98dda3170acee7b7e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fa084464c4927940f8edc1c0206858bdf4c39ca5bd1b632d2786ab6b11b235f5a5696dad9c9e9bdf79640d897539baaa5c56044230ed6901d1c34f930c0e3f0a
|
|
7
|
+
data.tar.gz: dbe05e17ceb7c73461ed0b4f838a03af7853ec47a454ada51c632763adb31e28cf8e4fd3d89c2ed0c5c2f8d91eea2e3b661c8257ab266d7895c398ef1ac047f5
|
data/History.md
CHANGED
data/README.md
CHANGED
|
@@ -52,6 +52,47 @@ For standard use you can just pass it the content of a file and call render with
|
|
|
52
52
|
@template.render('name' => 'tobi') # => "hi tobi"
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
### Concept of Environments
|
|
56
|
+
|
|
57
|
+
In Liquid, a "Environment" is a scoped environment that encapsulates custom tags, filters, and other configurations. This allows you to define and isolate different sets of functionality for different contexts, avoiding global overrides that can lead to conflicts and unexpected behavior.
|
|
58
|
+
|
|
59
|
+
By using environments, you can:
|
|
60
|
+
|
|
61
|
+
1. **Encapsulate Logic**: Keep the logic for different parts of your application separate.
|
|
62
|
+
2. **Avoid Conflicts**: Prevent custom tags and filters from clashing with each other.
|
|
63
|
+
3. **Improve Maintainability**: Make it easier to manage and understand the scope of customizations.
|
|
64
|
+
4. **Enhance Security**: Limit the availability of certain tags and filters to specific contexts.
|
|
65
|
+
|
|
66
|
+
We encourage the use of Environments over globally overriding things because it promotes better software design principles such as modularity, encapsulation, and separation of concerns.
|
|
67
|
+
|
|
68
|
+
Here's an example of how you can define and use Environments in Liquid:
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
user_environment = Liquid::Environment.build do |environment|
|
|
72
|
+
environment.register_tag("renderobj", RenderObjTag)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
Liquid::Template.parse(<<~LIQUID, environment: user_environment)
|
|
76
|
+
{% renderobj src: "path/to/model.obj" %}
|
|
77
|
+
LIQUID
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
In this example, `RenderObjTag` is a custom tag that is only available within the `user_environment`.
|
|
81
|
+
|
|
82
|
+
Similarly, you can define another environment for a different context, such as email templates:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
email_environment = Liquid::Environment.build do |environment|
|
|
86
|
+
environment.register_tag("unsubscribe_footer", UnsubscribeFooter)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
Liquid::Template.parse(<<~LIQUID, environment: email_environment)
|
|
90
|
+
{% unsubscribe_footer %}
|
|
91
|
+
LIQUID
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
By using Environments, you ensure that custom tags and filters are only available in the contexts where they are needed, making your Liquid templates more robust and easier to manage.
|
|
95
|
+
|
|
55
96
|
### Error Modes
|
|
56
97
|
|
|
57
98
|
Setting the error mode of Liquid lets you specify how strictly you want your templates to be interpreted.
|
|
@@ -62,9 +103,10 @@ Liquid also comes with a stricter parser that can be used when editing templates
|
|
|
62
103
|
when templates are invalid. You can enable this new parser like this:
|
|
63
104
|
|
|
64
105
|
```ruby
|
|
65
|
-
Liquid::
|
|
66
|
-
Liquid::
|
|
67
|
-
Liquid::
|
|
106
|
+
Liquid::Environment.default.error_mode = :strict
|
|
107
|
+
Liquid::Environment.default.error_mode = :strict # Raises a SyntaxError when invalid syntax is used
|
|
108
|
+
Liquid::Environment.default.error_mode = :warn # Adds strict errors to template.errors but continues as normal
|
|
109
|
+
Liquid::Environment.default.error_mode = :lax # The default mode, accepts almost anything.
|
|
68
110
|
```
|
|
69
111
|
|
|
70
112
|
If you want to set the error mode only on specific templates you can pass `:error_mode` as an option to `parse`:
|
data/lib/liquid/block_body.rb
CHANGED
|
@@ -52,7 +52,7 @@ module Liquid
|
|
|
52
52
|
next parse_liquid_tag(markup, parse_context)
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
unless (tag =
|
|
55
|
+
unless (tag = parse_context.environment.tag_for_name(tag_name))
|
|
56
56
|
# end parsing if we reach an unknown tag and let the caller decide
|
|
57
57
|
# determine how to proceed
|
|
58
58
|
return yield tag_name, markup
|
|
@@ -147,7 +147,7 @@ module Liquid
|
|
|
147
147
|
next
|
|
148
148
|
end
|
|
149
149
|
|
|
150
|
-
unless (tag =
|
|
150
|
+
unless (tag = parse_context.environment.tag_for_name(tag_name))
|
|
151
151
|
# end parsing if we reach an unknown tag and let the caller decide
|
|
152
152
|
# determine how to proceed
|
|
153
153
|
return yield tag_name, markup
|
|
@@ -246,10 +246,17 @@ module Liquid
|
|
|
246
246
|
end
|
|
247
247
|
|
|
248
248
|
def create_variable(token, parse_context)
|
|
249
|
-
if token
|
|
250
|
-
|
|
249
|
+
if token.end_with?("}}")
|
|
250
|
+
i = 2
|
|
251
|
+
i = 3 if token[i] == "-"
|
|
252
|
+
parse_end = token.length - 3
|
|
253
|
+
parse_end -= 1 if token[parse_end] == "-"
|
|
254
|
+
markup_end = parse_end - i + 1
|
|
255
|
+
markup = markup_end <= 0 ? "" : token.slice(i, markup_end)
|
|
256
|
+
|
|
251
257
|
return Variable.new(markup, parse_context)
|
|
252
258
|
end
|
|
259
|
+
|
|
253
260
|
BlockBody.raise_missing_variable_terminator(token, parse_context)
|
|
254
261
|
end
|
|
255
262
|
|
|
@@ -262,9 +269,5 @@ module Liquid
|
|
|
262
269
|
def raise_missing_variable_terminator(token, parse_context)
|
|
263
270
|
BlockBody.raise_missing_variable_terminator(token, parse_context)
|
|
264
271
|
end
|
|
265
|
-
|
|
266
|
-
def registered_tags
|
|
267
|
-
Template.tags
|
|
268
|
-
end
|
|
269
272
|
end
|
|
270
273
|
end
|
data/lib/liquid/const.rb
ADDED
data/lib/liquid/context.rb
CHANGED
|
@@ -15,24 +15,25 @@ module Liquid
|
|
|
15
15
|
# context['bob'] #=> nil class Context
|
|
16
16
|
class Context
|
|
17
17
|
attr_reader :scopes, :errors, :registers, :environments, :resource_limits, :static_registers, :static_environments
|
|
18
|
-
attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
|
|
18
|
+
attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters, :environment
|
|
19
19
|
|
|
20
20
|
# rubocop:disable Metrics/ParameterLists
|
|
21
|
-
def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {}, &block)
|
|
22
|
-
new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments, &block)
|
|
21
|
+
def self.build(environment: Environment.default, environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {}, &block)
|
|
22
|
+
new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments, environment, &block)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {})
|
|
25
|
+
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {}, environment = Environment.default)
|
|
26
|
+
@environment = environment
|
|
26
27
|
@environments = [environments]
|
|
27
28
|
@environments.flatten!
|
|
28
29
|
|
|
29
30
|
@static_environments = [static_environments].flatten(1).freeze
|
|
30
|
-
@scopes = [
|
|
31
|
+
@scopes = [outer_scope || {}]
|
|
31
32
|
@registers = registers.is_a?(Registers) ? registers : Registers.new(registers)
|
|
32
33
|
@errors = []
|
|
33
34
|
@partial = false
|
|
34
35
|
@strict_variables = false
|
|
35
|
-
@resource_limits = resource_limits || ResourceLimits.new(
|
|
36
|
+
@resource_limits = resource_limits || ResourceLimits.new(environment.default_resource_limits)
|
|
36
37
|
@base_scope_depth = 0
|
|
37
38
|
@interrupts = []
|
|
38
39
|
@filters = []
|
|
@@ -40,10 +41,10 @@ module Liquid
|
|
|
40
41
|
@disabled_tags = {}
|
|
41
42
|
|
|
42
43
|
@registers.static[:cached_partials] ||= {}
|
|
43
|
-
@registers.static[:file_system] ||=
|
|
44
|
+
@registers.static[:file_system] ||= environment.file_system
|
|
44
45
|
@registers.static[:template_factory] ||= Liquid::TemplateFactory.new
|
|
45
46
|
|
|
46
|
-
self.exception_renderer =
|
|
47
|
+
self.exception_renderer = environment.exception_renderer
|
|
47
48
|
if rethrow_errors
|
|
48
49
|
self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
|
|
49
50
|
end
|
|
@@ -60,7 +61,7 @@ module Liquid
|
|
|
60
61
|
end
|
|
61
62
|
|
|
62
63
|
def strainer
|
|
63
|
-
@strainer ||=
|
|
64
|
+
@strainer ||= @environment.create_strainer(self, @filters)
|
|
64
65
|
end
|
|
65
66
|
|
|
66
67
|
# Adds filters to this context.
|
|
@@ -142,6 +143,7 @@ module Liquid
|
|
|
142
143
|
check_overflow
|
|
143
144
|
|
|
144
145
|
self.class.build(
|
|
146
|
+
environment: @environment,
|
|
145
147
|
resource_limits: resource_limits,
|
|
146
148
|
static_environments: static_environments,
|
|
147
149
|
registers: Registers.new(registers),
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
5
|
+
module Liquid
|
|
6
|
+
class Deprecations
|
|
7
|
+
class << self
|
|
8
|
+
attr_accessor :warned
|
|
9
|
+
|
|
10
|
+
Deprecations.warned = Set.new
|
|
11
|
+
|
|
12
|
+
def warn(name, alternative)
|
|
13
|
+
return if warned.include?(name)
|
|
14
|
+
|
|
15
|
+
warned << name
|
|
16
|
+
|
|
17
|
+
caller_location = caller_locations(2, 1).first
|
|
18
|
+
Warning.warn("[DEPRECATION] #{name} is deprecated. Use #{alternative} instead. Called from #{caller_location}\n")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Liquid
|
|
4
|
+
# The Environment is the container for all configuration options of Liquid, such as
|
|
5
|
+
# the registered tags, filters, and the default error mode.
|
|
6
|
+
class Environment
|
|
7
|
+
# The default error mode for all templates. This can be overridden on a
|
|
8
|
+
# per-template basis.
|
|
9
|
+
attr_accessor :error_mode
|
|
10
|
+
|
|
11
|
+
# The tags that are available to use in the template.
|
|
12
|
+
attr_accessor :tags
|
|
13
|
+
|
|
14
|
+
# The strainer template which is used to store filters that are available to
|
|
15
|
+
# use in templates.
|
|
16
|
+
attr_accessor :strainer_template
|
|
17
|
+
|
|
18
|
+
# The exception renderer that is used to render exceptions that are raised
|
|
19
|
+
# when rendering a template
|
|
20
|
+
attr_accessor :exception_renderer
|
|
21
|
+
|
|
22
|
+
# The default file system that is used to load templates from.
|
|
23
|
+
attr_accessor :file_system
|
|
24
|
+
|
|
25
|
+
# The default resource limits that are used to limit the resources that a
|
|
26
|
+
# template can consume.
|
|
27
|
+
attr_accessor :default_resource_limits
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
# Creates a new environment instance.
|
|
31
|
+
#
|
|
32
|
+
# @param tags [Hash] The tags that are available to use in
|
|
33
|
+
# the template.
|
|
34
|
+
# @param file_system The default file system that is used
|
|
35
|
+
# to load templates from.
|
|
36
|
+
# @param error_mode [Symbol] The default error mode for all templates
|
|
37
|
+
# (either :strict, :warn, or :lax).
|
|
38
|
+
# @param exception_renderer [Proc] The exception renderer that is used to
|
|
39
|
+
# render exceptions.
|
|
40
|
+
# @yieldparam environment [Environment] The environment instance that is being built.
|
|
41
|
+
# @return [Environment] The new environment instance.
|
|
42
|
+
def build(tags: nil, file_system: nil, error_mode: nil, exception_renderer: nil)
|
|
43
|
+
ret = new
|
|
44
|
+
ret.tags = tags if tags
|
|
45
|
+
ret.file_system = file_system if file_system
|
|
46
|
+
ret.error_mode = error_mode if error_mode
|
|
47
|
+
ret.exception_renderer = exception_renderer if exception_renderer
|
|
48
|
+
yield ret if block_given?
|
|
49
|
+
ret.freeze
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Returns the default environment instance.
|
|
53
|
+
#
|
|
54
|
+
# @return [Environment] The default environment instance.
|
|
55
|
+
def default
|
|
56
|
+
@default ||= new
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Sets the default environment instance for the duration of the block
|
|
60
|
+
#
|
|
61
|
+
# @param environment [Environment] The environment instance to use as the default for the
|
|
62
|
+
# duration of the block.
|
|
63
|
+
# @yield
|
|
64
|
+
# @return [Object] The return value of the block.
|
|
65
|
+
def dangerously_override(environment)
|
|
66
|
+
original_default = @default
|
|
67
|
+
@default = environment
|
|
68
|
+
yield
|
|
69
|
+
ensure
|
|
70
|
+
@default = original_default
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Initializes a new environment instance.
|
|
75
|
+
# @api private
|
|
76
|
+
def initialize
|
|
77
|
+
@tags = Tags::STANDARD_TAGS.dup
|
|
78
|
+
@error_mode = :lax
|
|
79
|
+
@strainer_template = Class.new(StrainerTemplate).tap do |klass|
|
|
80
|
+
klass.add_filter(StandardFilters)
|
|
81
|
+
end
|
|
82
|
+
@exception_renderer = ->(exception) { exception }
|
|
83
|
+
@file_system = BlankFileSystem.new
|
|
84
|
+
@default_resource_limits = Const::EMPTY_HASH
|
|
85
|
+
@strainer_template_class_cache = {}
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Registers a new tag with the environment.
|
|
89
|
+
#
|
|
90
|
+
# @param name [String] The name of the tag.
|
|
91
|
+
# @param klass [Liquid::Tag] The class that implements the tag.
|
|
92
|
+
# @return [void]
|
|
93
|
+
def register_tag(name, klass)
|
|
94
|
+
@tags[name] = klass
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Registers a new filter with the environment.
|
|
98
|
+
#
|
|
99
|
+
# @param filter [Module] The module that contains the filter methods.
|
|
100
|
+
# @return [void]
|
|
101
|
+
def register_filter(filter)
|
|
102
|
+
@strainer_template_class_cache.clear
|
|
103
|
+
@strainer_template.add_filter(filter)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Registers multiple filters with this environment.
|
|
107
|
+
#
|
|
108
|
+
# @param filters [Array<Module>] The modules that contain the filter methods.
|
|
109
|
+
# @return [self]
|
|
110
|
+
def register_filters(filters)
|
|
111
|
+
@strainer_template_class_cache.clear
|
|
112
|
+
filters.each { |f| @strainer_template.add_filter(f) }
|
|
113
|
+
self
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Creates a new strainer instance with the given filters, caching the result
|
|
117
|
+
# for faster lookup.
|
|
118
|
+
#
|
|
119
|
+
# @param context [Liquid::Context] The context that the strainer will be
|
|
120
|
+
# used in.
|
|
121
|
+
# @param filters [Array<Module>] The filters that the strainer will have
|
|
122
|
+
# access to.
|
|
123
|
+
# @return [Liquid::Strainer] The new strainer instance.
|
|
124
|
+
def create_strainer(context, filters = Const::EMPTY_ARRAY)
|
|
125
|
+
return @strainer_template.new(context) if filters.empty?
|
|
126
|
+
|
|
127
|
+
strainer_template = @strainer_template_class_cache[filters] ||= begin
|
|
128
|
+
klass = Class.new(@strainer_template)
|
|
129
|
+
filters.each { |f| klass.add_filter(f) }
|
|
130
|
+
klass
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
strainer_template.new(context)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Returns the names of all the filter methods that are available to use in
|
|
137
|
+
# the strainer template.
|
|
138
|
+
#
|
|
139
|
+
# @return [Array<String>] The names of all the filter methods.
|
|
140
|
+
def filter_method_names
|
|
141
|
+
@strainer_template.filter_method_names
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Returns the tag class for the given tag name.
|
|
145
|
+
#
|
|
146
|
+
# @param name [String] The name of the tag.
|
|
147
|
+
# @return [Liquid::Tag] The tag class.
|
|
148
|
+
def tag_for_name(name)
|
|
149
|
+
@tags[name]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def freeze
|
|
153
|
+
@tags.freeze
|
|
154
|
+
# TODO: freeze the tags, currently this is not possible because of liquid-c
|
|
155
|
+
# @strainer_template.freeze
|
|
156
|
+
super
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
data/lib/liquid/lexer.rb
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "strscan"
|
|
4
|
+
|
|
4
5
|
module Liquid
|
|
5
|
-
class
|
|
6
|
+
class Lexer1
|
|
6
7
|
SPECIALS = {
|
|
7
8
|
'|' => :pipe,
|
|
8
9
|
'.' => :dot,
|
|
@@ -58,4 +59,179 @@ module Liquid
|
|
|
58
59
|
@output << [:end_of_string]
|
|
59
60
|
end
|
|
60
61
|
end
|
|
62
|
+
|
|
63
|
+
class Lexer2
|
|
64
|
+
CLOSE_ROUND = [:close_round, ")"].freeze
|
|
65
|
+
CLOSE_SQUARE = [:close_square, "]"].freeze
|
|
66
|
+
COLON = [:colon, ":"].freeze
|
|
67
|
+
COMMA = [:comma, ","].freeze
|
|
68
|
+
COMPARISION_NOT_EQUAL = [:comparison, "!="].freeze
|
|
69
|
+
COMPARISON_CONTAINS = [:comparison, "contains"].freeze
|
|
70
|
+
COMPARISON_EQUAL = [:comparison, "=="].freeze
|
|
71
|
+
COMPARISON_GREATER_THAN = [:comparison, ">"].freeze
|
|
72
|
+
COMPARISON_GREATER_THAN_OR_EQUAL = [:comparison, ">="].freeze
|
|
73
|
+
COMPARISON_LESS_THAN = [:comparison, "<"].freeze
|
|
74
|
+
COMPARISON_LESS_THAN_OR_EQUAL = [:comparison, "<="].freeze
|
|
75
|
+
COMPARISON_NOT_EQUAL_ALT = [:comparison, "<>"].freeze
|
|
76
|
+
DASH = [:dash, "-"].freeze
|
|
77
|
+
DOT = [:dot, "."].freeze
|
|
78
|
+
DOTDOT = [:dotdot, ".."].freeze
|
|
79
|
+
DOT_ORD = ".".ord
|
|
80
|
+
DOUBLE_STRING_LITERAL = /"[^\"]*"/
|
|
81
|
+
EOS = [:end_of_string].freeze
|
|
82
|
+
IDENTIFIER = /[a-zA-Z_][\w-]*\??/
|
|
83
|
+
NUMBER_LITERAL = /-?\d+(\.\d+)?/
|
|
84
|
+
OPEN_ROUND = [:open_round, "("].freeze
|
|
85
|
+
OPEN_SQUARE = [:open_square, "["].freeze
|
|
86
|
+
PIPE = [:pipe, "|"].freeze
|
|
87
|
+
QUESTION = [:question, "?"].freeze
|
|
88
|
+
RUBY_WHITESPACE = [" ", "\t", "\r", "\n", "\f"].freeze
|
|
89
|
+
SINGLE_STRING_LITERAL = /'[^\']*'/
|
|
90
|
+
WHITESPACE_OR_NOTHING = /\s*/
|
|
91
|
+
|
|
92
|
+
SINGLE_COMPARISON_TOKENS = [].tap do |table|
|
|
93
|
+
table["<".ord] = COMPARISON_LESS_THAN
|
|
94
|
+
table[">".ord] = COMPARISON_GREATER_THAN
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
TWO_CHARS_COMPARISON_JUMP_TABLE = [].tap do |table|
|
|
98
|
+
table["=".ord] = [].tap do |sub_table|
|
|
99
|
+
sub_table["=".ord] = COMPARISON_EQUAL
|
|
100
|
+
sub_table.freeze
|
|
101
|
+
end
|
|
102
|
+
table["!".ord] = [].tap do |sub_table|
|
|
103
|
+
sub_table["=".ord] = COMPARISION_NOT_EQUAL
|
|
104
|
+
sub_table.freeze
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
COMPARISON_JUMP_TABLE = [].tap do |table|
|
|
109
|
+
table["<".ord] = [].tap do |sub_table|
|
|
110
|
+
sub_table["=".ord] = COMPARISON_LESS_THAN_OR_EQUAL
|
|
111
|
+
sub_table[">".ord] = COMPARISON_NOT_EQUAL_ALT
|
|
112
|
+
RUBY_WHITESPACE.each { |c| sub_table[c.ord] = COMPARISON_LESS_THAN }
|
|
113
|
+
sub_table.freeze
|
|
114
|
+
end
|
|
115
|
+
table[">".ord] = [].tap do |sub_table|
|
|
116
|
+
sub_table["=".ord] = COMPARISON_GREATER_THAN_OR_EQUAL
|
|
117
|
+
RUBY_WHITESPACE.each { |c| sub_table[c.ord] = COMPARISON_GREATER_THAN }
|
|
118
|
+
sub_table.freeze
|
|
119
|
+
end
|
|
120
|
+
table.freeze
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
NEXT_MATCHER_JUMP_TABLE = [].tap do |table|
|
|
124
|
+
"a".upto("z") do |c|
|
|
125
|
+
table[c.ord] = [:id, IDENTIFIER].freeze
|
|
126
|
+
table[c.upcase.ord] = [:id, IDENTIFIER].freeze
|
|
127
|
+
end
|
|
128
|
+
table["_".ord] = [:id, IDENTIFIER].freeze
|
|
129
|
+
|
|
130
|
+
"0".upto("9") do |c|
|
|
131
|
+
table[c.ord] = [:number, NUMBER_LITERAL].freeze
|
|
132
|
+
end
|
|
133
|
+
table["-".ord] = [:number, NUMBER_LITERAL].freeze
|
|
134
|
+
|
|
135
|
+
table["'".ord] = [:string, SINGLE_STRING_LITERAL].freeze
|
|
136
|
+
table["\"".ord] = [:string, DOUBLE_STRING_LITERAL].freeze
|
|
137
|
+
table.freeze
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
SPECIAL_TABLE = [].tap do |table|
|
|
141
|
+
table["|".ord] = PIPE
|
|
142
|
+
table[".".ord] = DOT
|
|
143
|
+
table[":".ord] = COLON
|
|
144
|
+
table[",".ord] = COMMA
|
|
145
|
+
table["[".ord] = OPEN_SQUARE
|
|
146
|
+
table["]".ord] = CLOSE_SQUARE
|
|
147
|
+
table["(".ord] = OPEN_ROUND
|
|
148
|
+
table[")".ord] = CLOSE_ROUND
|
|
149
|
+
table["?".ord] = QUESTION
|
|
150
|
+
table["-".ord] = DASH
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
NUMBER_TABLE = [].tap do |table|
|
|
154
|
+
"0".upto("9") do |c|
|
|
155
|
+
table[c.ord] = true
|
|
156
|
+
end
|
|
157
|
+
table.freeze
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def initialize(input)
|
|
161
|
+
@ss = StringScanner.new(input)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# rubocop:disable Metrics/BlockNesting
|
|
165
|
+
def tokenize
|
|
166
|
+
@output = []
|
|
167
|
+
|
|
168
|
+
until @ss.eos?
|
|
169
|
+
@ss.skip(WHITESPACE_OR_NOTHING)
|
|
170
|
+
|
|
171
|
+
break if @ss.eos?
|
|
172
|
+
|
|
173
|
+
start_pos = @ss.pos
|
|
174
|
+
peeked = @ss.peek_byte
|
|
175
|
+
|
|
176
|
+
if (special = SPECIAL_TABLE[peeked])
|
|
177
|
+
@ss.scan_byte
|
|
178
|
+
# Special case for ".."
|
|
179
|
+
if special == DOT && @ss.peek_byte == DOT_ORD
|
|
180
|
+
@ss.scan_byte
|
|
181
|
+
@output << DOTDOT
|
|
182
|
+
elsif special == DASH
|
|
183
|
+
# Special case for negative numbers
|
|
184
|
+
if (peeked_byte = @ss.peek_byte) && NUMBER_TABLE[peeked_byte]
|
|
185
|
+
@ss.pos -= 1
|
|
186
|
+
@output << [:number, @ss.scan(NUMBER_LITERAL)]
|
|
187
|
+
else
|
|
188
|
+
@output << special
|
|
189
|
+
end
|
|
190
|
+
else
|
|
191
|
+
@output << special
|
|
192
|
+
end
|
|
193
|
+
elsif (sub_table = TWO_CHARS_COMPARISON_JUMP_TABLE[peeked])
|
|
194
|
+
@ss.scan_byte
|
|
195
|
+
if (peeked_byte = @ss.peek_byte) && (found = sub_table[peeked_byte])
|
|
196
|
+
@output << found
|
|
197
|
+
@ss.scan_byte
|
|
198
|
+
else
|
|
199
|
+
raise_syntax_error(start_pos)
|
|
200
|
+
end
|
|
201
|
+
elsif (sub_table = COMPARISON_JUMP_TABLE[peeked])
|
|
202
|
+
@ss.scan_byte
|
|
203
|
+
if (peeked_byte = @ss.peek_byte) && (found = sub_table[peeked_byte])
|
|
204
|
+
@output << found
|
|
205
|
+
@ss.scan_byte
|
|
206
|
+
else
|
|
207
|
+
@output << SINGLE_COMPARISON_TOKENS[peeked]
|
|
208
|
+
end
|
|
209
|
+
else
|
|
210
|
+
type, pattern = NEXT_MATCHER_JUMP_TABLE[peeked]
|
|
211
|
+
|
|
212
|
+
if type && (t = @ss.scan(pattern))
|
|
213
|
+
# Special case for "contains"
|
|
214
|
+
@output << if type == :id && t == "contains" && @output.last&.first != :dot
|
|
215
|
+
COMPARISON_CONTAINS
|
|
216
|
+
else
|
|
217
|
+
[type, t]
|
|
218
|
+
end
|
|
219
|
+
else
|
|
220
|
+
raise_syntax_error(start_pos)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
# rubocop:enable Metrics/BlockNesting
|
|
225
|
+
|
|
226
|
+
@output << EOS
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def raise_syntax_error(start_pos)
|
|
230
|
+
@ss.pos = start_pos
|
|
231
|
+
# the character could be a UTF-8 character, use getch to get all the bytes
|
|
232
|
+
raise SyntaxError, "Unexpected character #{@ss.getch}"
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
Lexer = StringScanner.instance_methods.include?(:scan_byte) ? Lexer2 : Lexer1
|
|
61
237
|
end
|
data/lib/liquid/parse_context.rb
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
module Liquid
|
|
4
4
|
class ParseContext
|
|
5
5
|
attr_accessor :locale, :line_number, :trim_whitespace, :depth
|
|
6
|
-
attr_reader :partial, :warnings, :error_mode
|
|
6
|
+
attr_reader :partial, :warnings, :error_mode, :environment
|
|
7
7
|
|
|
8
|
-
def initialize(options =
|
|
8
|
+
def initialize(options = Const::EMPTY_HASH)
|
|
9
|
+
@environment = options.fetch(:environment, Environment.default)
|
|
9
10
|
@template_options = options ? options.dup : {}
|
|
10
11
|
|
|
11
12
|
@locale = @template_options[:locale] ||= I18n.new
|
|
@@ -35,7 +36,7 @@ module Liquid
|
|
|
35
36
|
@partial = value
|
|
36
37
|
@options = value ? partial_options : @template_options
|
|
37
38
|
|
|
38
|
-
@error_mode = @options[:error_mode] ||
|
|
39
|
+
@error_mode = @options[:error_mode] || @environment.error_mode
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
def partial_options
|
data/lib/liquid/parser.rb
CHANGED
|
@@ -877,7 +877,7 @@ module Liquid
|
|
|
877
877
|
# - [`nil`](/docs/api/liquid/basics#nil)
|
|
878
878
|
# @liquid_syntax variable | default: variable
|
|
879
879
|
# @liquid_return [untyped]
|
|
880
|
-
# @liquid_optional_param allow_false [boolean] Whether to use false values instead of the default.
|
|
880
|
+
# @liquid_optional_param allow_false: [boolean] Whether to use false values instead of the default.
|
|
881
881
|
def default(input, default_value = '', options = {})
|
|
882
882
|
options = {} unless options.is_a?(Hash)
|
|
883
883
|
false_check = options['allow_false'] ? input.nil? : !Liquid::Utils.to_liquid_value(input)
|
|
@@ -1001,6 +1001,4 @@ module Liquid
|
|
|
1001
1001
|
end
|
|
1002
1002
|
end
|
|
1003
1003
|
end
|
|
1004
|
-
|
|
1005
|
-
Template.register_filter(StandardFilters)
|
|
1006
1004
|
end
|
data/lib/liquid/tag.rb
CHANGED
data/lib/liquid/tags/assign.rb
CHANGED
data/lib/liquid/tags/break.rb
CHANGED
data/lib/liquid/tags/capture.rb
CHANGED