liquid 5.5.1 → 5.6.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0598b29e6fcedc82da9cda31e60387a4365e688fc23bee62ebdc5b57830453fd'
4
- data.tar.gz: e3f0a880eafbb93c5a06ee6a5f82691fe0486b259c6407258382647aaf717b09
3
+ metadata.gz: e610d96aad5d08594f89119400b89c2667825d9a4e69936a315f8d5ae4f460ae
4
+ data.tar.gz: dadd1ed95abb4eab1c4a1186a251b5598698bc93ce21415fb8894a5f3a188382
5
5
  SHA512:
6
- metadata.gz: bee97a129194c9309265d15c6a076b2f3ff71732d5478901c218589eceec5c55779b2dad99de0a7061419d991974a253d3a8e095e7b98687f4de89e2bda52391
7
- data.tar.gz: d28b48a963d1b5ebb753353a6c71e5c0226b5def5c7b46799b9019a80506c67fe7006ed296ba56ec117e7e33bc7af16f8d0ad1c60518de559d57f821a809ca85
6
+ metadata.gz: cd5f73dd9cf3d9d6c49d96712eb413152bc61b935c496135374c1b0970d7ac6912adaa18d99d3ad6a0eb1fd87bfc9f57d2907dec68237f2bd46c5a1e69a6184e
7
+ data.tar.gz: 3cfd49dc70c4ee2456569bda9fe7f5e700d88f471bed88045da6d4e1eef396a9050c6e045d0c2e715ffb509653d06ff507240e158aa4f34dc15d42c1cec23a05
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::Template.error_mode = :strict # Raises a SyntaxError when invalid syntax is used
66
- Liquid::Template.error_mode = :warn # Adds strict errors to template.errors but continues as normal
67
- Liquid::Template.error_mode = :lax # The default mode, accepts almost anything.
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`:
@@ -52,7 +52,7 @@ module Liquid
52
52
  next parse_liquid_tag(markup, parse_context)
53
53
  end
54
54
 
55
- unless (tag = registered_tags[tag_name])
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 = registered_tags[tag_name])
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 =~ ContentOfVariable
250
- markup = Regexp.last_match(1)
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
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ module Const
5
+ EMPTY_HASH = {}.freeze
6
+ EMPTY_ARRAY = [].freeze
7
+ end
8
+ end
@@ -15,14 +15,15 @@ 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
 
@@ -32,7 +33,7 @@ module Liquid
32
33
  @errors = []
33
34
  @partial = false
34
35
  @strict_variables = false
35
- @resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
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] ||= Liquid::Template.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 = Template.default_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 ||= StrainerFactory.create(self, @filters)
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 Lexer
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
@@ -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] || Template.error_mode
39
+ @error_mode = @options[:error_mode] || @environment.error_mode
39
40
  end
40
41
 
41
42
  def partial_options
@@ -36,7 +36,7 @@ module Liquid
36
36
  protected
37
37
 
38
38
  def children
39
- @node.respond_to?(:nodelist) ? Array(@node.nodelist) : []
39
+ @node.respond_to?(:nodelist) ? Array(@node.nodelist) : Const::EMPTY_ARRAY
40
40
  end
41
41
  end
42
42
  end
data/lib/liquid/parser.rb CHANGED
@@ -53,7 +53,7 @@ module Liquid
53
53
  str = consume
54
54
  str << variable_lookups
55
55
  when :open_square
56
- str = consume
56
+ str = consume.dup
57
57
  str << expression
58
58
  str << consume(:close_square)
59
59
  str << variable_lookups
@@ -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
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'liquid/tag/disabler'
4
+ require 'liquid/tag/disableable'
5
+
3
6
  module Liquid
4
7
  class Tag
5
8
  attr_reader :nodelist, :tag_name, :line_number, :parse_context
@@ -72,6 +72,4 @@ module Liquid
72
72
  end
73
73
  end
74
74
  end
75
-
76
- Template.register_tag('assign', Assign)
77
75
  end
@@ -26,6 +26,4 @@ module Liquid
26
26
  output
27
27
  end
28
28
  end
29
-
30
- Template.register_tag('break', Break)
31
29
  end
@@ -39,6 +39,4 @@ module Liquid
39
39
  true
40
40
  end
41
41
  end
42
-
43
- Template.register_tag('capture', Capture)
44
42
  end
@@ -123,6 +123,4 @@ module Liquid
123
123
  end
124
124
  end
125
125
  end
126
-
127
- Template.register_tag('case', Case)
128
126
  end
@@ -85,6 +85,4 @@ module Liquid
85
85
  raise_tag_never_closed("raw")
86
86
  end
87
87
  end
88
-
89
- Template.register_tag('comment', Comment)
90
88
  end
@@ -17,6 +17,4 @@ module Liquid
17
17
  output
18
18
  end
19
19
  end
20
-
21
- Template.register_tag('continue', Continue)
22
20
  end
@@ -78,6 +78,4 @@ module Liquid
78
78
  end
79
79
  end
80
80
  end
81
-
82
- Template.register_tag('cycle', Cycle)
83
81
  end
@@ -35,6 +35,4 @@ module Liquid
35
35
  output
36
36
  end
37
37
  end
38
-
39
- Template.register_tag('decrement', Decrement)
40
38
  end
@@ -36,6 +36,4 @@ module Liquid
36
36
  end
37
37
  end
38
38
  end
39
-
40
- Template.register_tag('echo', Echo)
41
39
  end
@@ -201,6 +201,4 @@ module Liquid
201
201
  end
202
202
  end
203
203
  end
204
-
205
- Template.register_tag('for', For)
206
204
  end
@@ -135,6 +135,4 @@ module Liquid
135
135
  end
136
136
  end
137
137
  end
138
-
139
- Template.register_tag('if', If)
140
138
  end
@@ -14,6 +14,4 @@ module Liquid
14
14
  output
15
15
  end
16
16
  end
17
-
18
- Template.register_tag('ifchanged', Ifchanged)
19
17
  end
@@ -110,6 +110,4 @@ module Liquid
110
110
  end
111
111
  end
112
112
  end
113
-
114
- Template.register_tag('include', Include)
115
113
  end
@@ -35,6 +35,4 @@ module Liquid
35
35
  output
36
36
  end
37
37
  end
38
-
39
- Template.register_tag('increment', Increment)
40
38
  end
@@ -25,6 +25,4 @@ module Liquid
25
25
  true
26
26
  end
27
27
  end
28
-
29
- Template.register_tag('#', InlineComment)
30
28
  end
@@ -56,6 +56,4 @@ module Liquid
56
56
  end
57
57
  end
58
58
  end
59
-
60
- Template.register_tag('raw', Raw)
61
59
  end
@@ -108,6 +108,4 @@ module Liquid
108
108
  end
109
109
  end
110
110
  end
111
-
112
- Template.register_tag('render', Render)
113
111
  end
@@ -65,6 +65,12 @@ module Liquid
65
65
  super
66
66
  output << '</td>'
67
67
 
68
+ # Handle any interrupts if they exist.
69
+ if context.interrupt?
70
+ interrupt = context.pop_interrupt
71
+ break if interrupt.is_a?(BreakInterrupt)
72
+ end
73
+
68
74
  if tablerowloop.col_last && !tablerowloop.last
69
75
  output << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
70
76
  end
@@ -91,6 +97,4 @@ module Liquid
91
97
  raise Liquid::ArgumentError, "invalid integer"
92
98
  end
93
99
  end
94
-
95
- Template.register_tag('tablerow', TableRow)
96
100
  end
@@ -44,6 +44,4 @@ module Liquid
44
44
  output
45
45
  end
46
46
  end
47
-
48
- Template.register_tag('unless', Unless)
49
47
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "tags/table_row"
4
+ require_relative "tags/echo"
5
+ require_relative "tags/if"
6
+ require_relative "tags/break"
7
+ require_relative "tags/inline_comment"
8
+ require_relative "tags/for"
9
+ require_relative "tags/assign"
10
+ require_relative "tags/ifchanged"
11
+ require_relative "tags/case"
12
+ require_relative "tags/include"
13
+ require_relative "tags/continue"
14
+ require_relative "tags/capture"
15
+ require_relative "tags/decrement"
16
+ require_relative "tags/unless"
17
+ require_relative "tags/increment"
18
+ require_relative "tags/comment"
19
+ require_relative "tags/raw"
20
+ require_relative "tags/render"
21
+ require_relative "tags/cycle"
22
+
23
+ module Liquid
24
+ module Tags
25
+ STANDARD_TAGS = {
26
+ 'cycle' => Cycle,
27
+ 'render' => Render,
28
+ 'raw' => Raw,
29
+ 'comment' => Comment,
30
+ 'increment' => Increment,
31
+ 'unless' => Unless,
32
+ 'decrement' => Decrement,
33
+ 'capture' => Capture,
34
+ 'continue' => Continue,
35
+ 'include' => Include,
36
+ 'case' => Case,
37
+ 'ifchanged' => Ifchanged,
38
+ 'assign' => Assign,
39
+ 'for' => For,
40
+ '#' => InlineComment,
41
+ 'break' => Break,
42
+ 'if' => If,
43
+ 'echo' => Echo,
44
+ 'tablerow' => TableRow,
45
+ }.freeze
46
+ end
47
+ end
@@ -18,89 +18,78 @@ module Liquid
18
18
  attr_accessor :root, :name
19
19
  attr_reader :resource_limits, :warnings
20
20
 
21
- class TagRegistry
22
- include Enumerable
21
+ attr_reader :profiler
23
22
 
24
- def initialize
25
- @tags = {}
26
- @cache = {}
23
+ class << self
24
+ # Sets how strict the parser should be.
25
+ # :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
26
+ # :warn is the default and will give deprecation warnings when invalid syntax is used.
27
+ # :strict will enforce correct syntax.
28
+ def error_mode=(mode)
29
+ Deprecations.warn("Template.error_mode=", "Environment#error_mode=")
30
+ Environment.default.error_mode = mode
27
31
  end
28
32
 
29
- def [](tag_name)
30
- return nil unless @tags.key?(tag_name)
31
- return @cache[tag_name] if Liquid.cache_classes
32
-
33
- lookup_class(@tags[tag_name]).tap { |o| @cache[tag_name] = o }
33
+ def error_mode
34
+ Environment.default.error_mode
34
35
  end
35
36
 
36
- def []=(tag_name, klass)
37
- @tags[tag_name] = klass.name
38
- @cache[tag_name] = klass
37
+ def default_exception_renderer=(renderer)
38
+ Deprecations.warn("Template.default_exception_renderer=", "Environment#exception_renderer=")
39
+ Environment.default.exception_renderer = renderer
39
40
  end
40
41
 
41
- def delete(tag_name)
42
- @tags.delete(tag_name)
43
- @cache.delete(tag_name)
42
+ def default_exception_renderer
43
+ Environment.default.exception_renderer
44
44
  end
45
45
 
46
- def each(&block)
47
- @tags.each(&block)
46
+ def file_system=(file_system)
47
+ Deprecations.warn("Template.file_system=", "Environment#file_system=")
48
+ Environment.default.file_system = file_system
48
49
  end
49
50
 
50
- private
51
-
52
- def lookup_class(name)
53
- Object.const_get(name)
51
+ def file_system
52
+ Environment.default.file_system
54
53
  end
55
- end
56
-
57
- attr_reader :profiler
58
54
 
59
- class << self
60
- # Sets how strict the parser should be.
61
- # :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
62
- # :warn is the default and will give deprecation warnings when invalid syntax is used.
63
- # :strict will enforce correct syntax.
64
- attr_accessor :error_mode
65
- Template.error_mode = :lax
66
-
67
- attr_accessor :default_exception_renderer
68
- Template.default_exception_renderer = lambda do |exception|
69
- exception
55
+ def tags
56
+ Environment.default.tags
70
57
  end
71
58
 
72
- attr_accessor :file_system
73
- Template.file_system = BlankFileSystem.new
74
-
75
- attr_accessor :tags
76
- Template.tags = TagRegistry.new
77
- private :tags=
78
-
79
59
  def register_tag(name, klass)
80
- tags[name.to_s] = klass
60
+ Deprecations.warn("Template.register_tag", "Environment#register_tag")
61
+ Environment.default.register_tag(name, klass)
81
62
  end
82
63
 
83
64
  # Pass a module with filter methods which should be available
84
65
  # to all liquid views. Good for registering the standard library
85
66
  def register_filter(mod)
86
- StrainerFactory.add_global_filter(mod)
67
+ Deprecations.warn("Template.register_filter", "Environment#register_filter")
68
+ Environment.default.register_filter(mod)
87
69
  end
88
70
 
89
- attr_accessor :default_resource_limits
90
- Template.default_resource_limits = {}
91
- private :default_resource_limits=
71
+ private def default_resource_limits=(limits)
72
+ Deprecations.warn("Template.default_resource_limits=", "Environment#default_resource_limits=")
73
+ Environment.default.default_resource_limits = limits
74
+ end
75
+
76
+ def default_resource_limits
77
+ Environment.default.default_resource_limits
78
+ end
92
79
 
93
80
  # creates a new <tt>Template</tt> object from liquid source code
94
81
  # To enable profiling, pass in <tt>profile: true</tt> as an option.
95
82
  # See Liquid::Profiler for more information
96
83
  def parse(source, options = {})
97
- new.parse(source, options)
84
+ environment = options[:environment] || Environment.default
85
+ new(environment: environment).parse(source, options)
98
86
  end
99
87
  end
100
88
 
101
- def initialize
89
+ def initialize(environment: Environment.default)
90
+ @environment = environment
102
91
  @rethrow_errors = false
103
- @resource_limits = ResourceLimits.new(Template.default_resource_limits)
92
+ @resource_limits = ResourceLimits.new(environment.default_resource_limits)
104
93
  end
105
94
 
106
95
  # Parse source code.
@@ -162,11 +151,11 @@ module Liquid
162
151
  c
163
152
  when Liquid::Drop
164
153
  drop = args.shift
165
- drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
154
+ drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
166
155
  when Hash
167
- Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
156
+ Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
168
157
  when nil
169
- Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
158
+ Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
170
159
  else
171
160
  raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
172
161
  end
@@ -226,8 +215,14 @@ module Liquid
226
215
  @options = options
227
216
  @profiling = profiling
228
217
  @line_numbers = options[:line_numbers] || @profiling
229
- parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
230
- @warnings = parse_context.warnings
218
+ parse_context = if options.is_a?(ParseContext)
219
+ options
220
+ else
221
+ opts = options.key?(:environment) ? options : options.merge(environment: @environment)
222
+ ParseContext.new(opts)
223
+ end
224
+
225
+ @warnings = parse_context.warnings
231
226
  parse_context
232
227
  end
233
228
 
@@ -68,7 +68,7 @@ module Liquid
68
68
  @name = parse_context.parse_expression(p.expression)
69
69
  while p.consume?(:pipe)
70
70
  filtername = p.consume(:id)
71
- filterargs = p.consume?(:colon) ? parse_filterargs(p) : []
71
+ filterargs = p.consume?(:colon) ? parse_filterargs(p) : Const::EMPTY_ARRAY
72
72
  @filters << parse_filter_expressions(filtername, filterargs)
73
73
  end
74
74
  p.consume(:end_of_string)
@@ -95,15 +95,21 @@ module Liquid
95
95
 
96
96
  def render_to_output_buffer(context, output)
97
97
  obj = render(context)
98
+ render_obj_to_output(obj, output)
99
+ output
100
+ end
98
101
 
99
- if obj.is_a?(Array)
100
- output << obj.join
101
- elsif obj.nil?
102
- else
102
+ def render_obj_to_output(obj, output)
103
+ case obj
104
+ when NilClass
105
+ # Do nothing
106
+ when Array
107
+ obj.each do |o|
108
+ render_obj_to_output(o, output)
109
+ end
110
+ when
103
111
  output << obj.to_s
104
112
  end
105
-
106
- output
107
113
  end
108
114
 
109
115
  def disabled?(_context)
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Liquid
5
- VERSION = "5.5.1"
5
+ VERSION = "5.6.0.rc2"
6
6
  end
data/lib/liquid.rb CHANGED
@@ -44,13 +44,20 @@ module Liquid
44
44
  VariableParser = /\[(?>[^\[\]]+|\g<0>)*\]|#{VariableSegment}+\??/o
45
45
 
46
46
  RAISE_EXCEPTION_LAMBDA = ->(_e) { raise }
47
-
48
- singleton_class.send(:attr_accessor, :cache_classes)
49
- self.cache_classes = true
50
47
  end
51
48
 
52
49
  require "liquid/version"
50
+ require "liquid/deprecations"
51
+ require "liquid/const"
52
+ require 'liquid/standardfilters'
53
+ require 'liquid/file_system'
54
+ require 'liquid/parser_switching'
55
+ require 'liquid/tag'
56
+ require 'liquid/block'
53
57
  require 'liquid/parse_tree_visitor'
58
+ require 'liquid/interrupts'
59
+ require 'liquid/tags'
60
+ require "liquid/environment"
54
61
  require 'liquid/lexer'
55
62
  require 'liquid/parser'
56
63
  require 'liquid/i18n'
@@ -61,23 +68,16 @@ require 'liquid/extensions'
61
68
  require 'liquid/errors'
62
69
  require 'liquid/interrupts'
63
70
  require 'liquid/strainer_template'
64
- require 'liquid/strainer_factory'
65
71
  require 'liquid/expression'
66
72
  require 'liquid/context'
67
- require 'liquid/parser_switching'
68
73
  require 'liquid/tag'
69
- require 'liquid/tag/disabler'
70
- require 'liquid/tag/disableable'
71
- require 'liquid/block'
72
74
  require 'liquid/block_body'
73
75
  require 'liquid/document'
74
76
  require 'liquid/variable'
75
77
  require 'liquid/variable_lookup'
76
78
  require 'liquid/range_lookup'
77
- require 'liquid/file_system'
78
79
  require 'liquid/resource_limits'
79
80
  require 'liquid/template'
80
- require 'liquid/standardfilters'
81
81
  require 'liquid/condition'
82
82
  require 'liquid/utils'
83
83
  require 'liquid/tokenizer'
@@ -86,7 +86,3 @@ require 'liquid/partial_cache'
86
86
  require 'liquid/usage'
87
87
  require 'liquid/registers'
88
88
  require 'liquid/template_factory'
89
-
90
- # Load all the tags of the standard library
91
- #
92
- Dir["#{__dir__}/liquid/tags/*.rb"].each { |f| require f }
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: liquid
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.5.1
4
+ version: 5.6.0.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Lütke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-23 00:00:00.000000000 Z
11
+ date: 2024-12-11 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: strscan
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bigdecimal
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: rake
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -54,9 +82,12 @@ files:
54
82
  - lib/liquid/block.rb
55
83
  - lib/liquid/block_body.rb
56
84
  - lib/liquid/condition.rb
85
+ - lib/liquid/const.rb
57
86
  - lib/liquid/context.rb
87
+ - lib/liquid/deprecations.rb
58
88
  - lib/liquid/document.rb
59
89
  - lib/liquid/drop.rb
90
+ - lib/liquid/environment.rb
60
91
  - lib/liquid/errors.rb
61
92
  - lib/liquid/expression.rb
62
93
  - lib/liquid/extensions.rb
@@ -77,12 +108,12 @@ files:
77
108
  - lib/liquid/registers.rb
78
109
  - lib/liquid/resource_limits.rb
79
110
  - lib/liquid/standardfilters.rb
80
- - lib/liquid/strainer_factory.rb
81
111
  - lib/liquid/strainer_template.rb
82
112
  - lib/liquid/tablerowloop_drop.rb
83
113
  - lib/liquid/tag.rb
84
114
  - lib/liquid/tag/disableable.rb
85
115
  - lib/liquid/tag/disabler.rb
116
+ - lib/liquid/tags.rb
86
117
  - lib/liquid/tags/assign.rb
87
118
  - lib/liquid/tags/break.rb
88
119
  - lib/liquid/tags/capture.rb
@@ -110,7 +141,7 @@ files:
110
141
  - lib/liquid/variable.rb
111
142
  - lib/liquid/variable_lookup.rb
112
143
  - lib/liquid/version.rb
113
- homepage: http://www.liquidmarkup.org
144
+ homepage: https://shopify.github.io/liquid/
114
145
  licenses:
115
146
  - MIT
116
147
  metadata:
@@ -123,14 +154,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
123
154
  requirements:
124
155
  - - ">="
125
156
  - !ruby/object:Gem::Version
126
- version: 2.7.0
157
+ version: 3.0.0
127
158
  required_rubygems_version: !ruby/object:Gem::Requirement
128
159
  requirements:
129
160
  - - ">="
130
161
  - !ruby/object:Gem::Version
131
162
  version: 1.3.7
132
163
  requirements: []
133
- rubygems_version: 3.5.16
164
+ rubygems_version: 3.5.23
134
165
  signing_key:
135
166
  specification_version: 4
136
167
  summary: A secure, non-evaling end user template engine with aesthetic markup.
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Liquid
4
- # StrainerFactory is the factory for the filters system.
5
- module StrainerFactory
6
- extend self
7
-
8
- def add_global_filter(filter)
9
- strainer_class_cache.clear
10
- GlobalCache.add_filter(filter)
11
- end
12
-
13
- def create(context, filters = [])
14
- strainer_from_cache(filters).new(context)
15
- end
16
-
17
- def global_filter_names
18
- GlobalCache.filter_method_names
19
- end
20
-
21
- GlobalCache = Class.new(StrainerTemplate)
22
-
23
- private
24
-
25
- def strainer_from_cache(filters)
26
- if filters.empty?
27
- GlobalCache
28
- else
29
- strainer_class_cache[filters] ||= begin
30
- klass = Class.new(GlobalCache)
31
- filters.each { |f| klass.add_filter(f) }
32
- klass
33
- end
34
- end
35
- end
36
-
37
- def strainer_class_cache
38
- @strainer_class_cache ||= {}
39
- end
40
- end
41
- end