liquid 2.6.3 → 5.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +272 -26
  3. data/README.md +67 -3
  4. data/lib/liquid/block.rb +62 -94
  5. data/lib/liquid/block_body.rb +255 -0
  6. data/lib/liquid/condition.rb +96 -38
  7. data/lib/liquid/context.rb +172 -154
  8. data/lib/liquid/document.rb +57 -9
  9. data/lib/liquid/drop.rb +33 -14
  10. data/lib/liquid/errors.rb +56 -10
  11. data/lib/liquid/expression.rb +45 -0
  12. data/lib/liquid/extensions.rb +21 -7
  13. data/lib/liquid/file_system.rb +27 -14
  14. data/lib/liquid/forloop_drop.rb +92 -0
  15. data/lib/liquid/i18n.rb +41 -0
  16. data/lib/liquid/interrupts.rb +3 -2
  17. data/lib/liquid/lexer.rb +62 -0
  18. data/lib/liquid/locales/en.yml +29 -0
  19. data/lib/liquid/parse_context.rb +54 -0
  20. data/lib/liquid/parse_tree_visitor.rb +42 -0
  21. data/lib/liquid/parser.rb +102 -0
  22. data/lib/liquid/parser_switching.rb +45 -0
  23. data/lib/liquid/partial_cache.rb +24 -0
  24. data/lib/liquid/profiler/hooks.rb +35 -0
  25. data/lib/liquid/profiler.rb +139 -0
  26. data/lib/liquid/range_lookup.rb +47 -0
  27. data/lib/liquid/registers.rb +51 -0
  28. data/lib/liquid/resource_limits.rb +62 -0
  29. data/lib/liquid/standardfilters.rb +789 -118
  30. data/lib/liquid/strainer_factory.rb +41 -0
  31. data/lib/liquid/strainer_template.rb +62 -0
  32. data/lib/liquid/tablerowloop_drop.rb +121 -0
  33. data/lib/liquid/tag/disableable.rb +22 -0
  34. data/lib/liquid/tag/disabler.rb +21 -0
  35. data/lib/liquid/tag.rb +49 -10
  36. data/lib/liquid/tags/assign.rb +61 -19
  37. data/lib/liquid/tags/break.rb +14 -4
  38. data/lib/liquid/tags/capture.rb +29 -21
  39. data/lib/liquid/tags/case.rb +80 -31
  40. data/lib/liquid/tags/comment.rb +24 -2
  41. data/lib/liquid/tags/continue.rb +14 -13
  42. data/lib/liquid/tags/cycle.rb +50 -32
  43. data/lib/liquid/tags/decrement.rb +24 -26
  44. data/lib/liquid/tags/echo.rb +41 -0
  45. data/lib/liquid/tags/for.rb +164 -100
  46. data/lib/liquid/tags/if.rb +105 -44
  47. data/lib/liquid/tags/ifchanged.rb +10 -11
  48. data/lib/liquid/tags/include.rb +85 -65
  49. data/lib/liquid/tags/increment.rb +24 -22
  50. data/lib/liquid/tags/inline_comment.rb +43 -0
  51. data/lib/liquid/tags/raw.rb +50 -11
  52. data/lib/liquid/tags/render.rb +109 -0
  53. data/lib/liquid/tags/table_row.rb +88 -0
  54. data/lib/liquid/tags/unless.rb +37 -21
  55. data/lib/liquid/template.rb +124 -46
  56. data/lib/liquid/template_factory.rb +9 -0
  57. data/lib/liquid/tokenizer.rb +39 -0
  58. data/lib/liquid/usage.rb +8 -0
  59. data/lib/liquid/utils.rb +68 -5
  60. data/lib/liquid/variable.rb +128 -32
  61. data/lib/liquid/variable_lookup.rb +96 -0
  62. data/lib/liquid/version.rb +3 -1
  63. data/lib/liquid.rb +36 -13
  64. metadata +69 -77
  65. data/lib/extras/liquid_view.rb +0 -51
  66. data/lib/liquid/htmltags.rb +0 -73
  67. data/lib/liquid/module_ex.rb +0 -62
  68. data/lib/liquid/strainer.rb +0 -53
  69. data/test/liquid/assign_test.rb +0 -21
  70. data/test/liquid/block_test.rb +0 -58
  71. data/test/liquid/capture_test.rb +0 -40
  72. data/test/liquid/condition_test.rb +0 -127
  73. data/test/liquid/context_test.rb +0 -478
  74. data/test/liquid/drop_test.rb +0 -180
  75. data/test/liquid/error_handling_test.rb +0 -81
  76. data/test/liquid/file_system_test.rb +0 -29
  77. data/test/liquid/filter_test.rb +0 -125
  78. data/test/liquid/hash_ordering_test.rb +0 -25
  79. data/test/liquid/module_ex_test.rb +0 -87
  80. data/test/liquid/output_test.rb +0 -116
  81. data/test/liquid/parsing_quirks_test.rb +0 -52
  82. data/test/liquid/regexp_test.rb +0 -44
  83. data/test/liquid/security_test.rb +0 -64
  84. data/test/liquid/standard_filter_test.rb +0 -263
  85. data/test/liquid/strainer_test.rb +0 -52
  86. data/test/liquid/tags/break_tag_test.rb +0 -16
  87. data/test/liquid/tags/continue_tag_test.rb +0 -16
  88. data/test/liquid/tags/for_tag_test.rb +0 -297
  89. data/test/liquid/tags/html_tag_test.rb +0 -63
  90. data/test/liquid/tags/if_else_tag_test.rb +0 -166
  91. data/test/liquid/tags/include_tag_test.rb +0 -166
  92. data/test/liquid/tags/increment_tag_test.rb +0 -24
  93. data/test/liquid/tags/raw_tag_test.rb +0 -24
  94. data/test/liquid/tags/standard_tag_test.rb +0 -295
  95. data/test/liquid/tags/statements_test.rb +0 -134
  96. data/test/liquid/tags/unless_else_tag_test.rb +0 -26
  97. data/test/liquid/template_test.rb +0 -146
  98. data/test/liquid/variable_test.rb +0 -186
  99. data/test/test_helper.rb +0 -29
  100. /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ # @liquid_public_docs
5
+ # @liquid_type tag
6
+ # @liquid_category iteration
7
+ # @liquid_name tablerow
8
+ # @liquid_summary
9
+ # Generates HTML table rows for every item in an array.
10
+ # @liquid_description
11
+ # The `tablerow` tag must be wrapped in HTML `<table>` and `</table>` tags.
12
+ #
13
+ # > Tip:
14
+ # > Every `tablerow` loop has an associated [`tablerowloop` object](/api/liquid/objects#tablerowloop) with information about the loop.
15
+ # @liquid_syntax
16
+ # {% tablerow variable in array %}
17
+ # expression
18
+ # {% endtablerow %}
19
+ # @liquid_syntax_keyword variable The current item in the array.
20
+ # @liquid_syntax_keyword array The array to iterate over.
21
+ # @liquid_syntax_keyword expression The expression to render.
22
+ # @liquid_optional_param cols [number] The number of columns that the table should have.
23
+ # @liquid_optional_param limit [number] The number of iterations to perform.
24
+ # @liquid_optional_param offset [number] The 1-based index to start iterating at.
25
+ # @liquid_optional_param range [untyped] A custom numeric range to iterate over.
26
+ class TableRow < Block
27
+ Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
28
+
29
+ attr_reader :variable_name, :collection_name, :attributes
30
+
31
+ def initialize(tag_name, markup, options)
32
+ super
33
+ if markup =~ Syntax
34
+ @variable_name = Regexp.last_match(1)
35
+ @collection_name = parse_expression(Regexp.last_match(2))
36
+ @attributes = {}
37
+ markup.scan(TagAttributes) do |key, value|
38
+ @attributes[key] = parse_expression(value)
39
+ end
40
+ else
41
+ raise SyntaxError, options[:locale].t("errors.syntax.table_row")
42
+ end
43
+ end
44
+
45
+ def render_to_output_buffer(context, output)
46
+ (collection = context.evaluate(@collection_name)) || (return '')
47
+
48
+ from = @attributes.key?('offset') ? context.evaluate(@attributes['offset']).to_i : 0
49
+ to = @attributes.key?('limit') ? from + context.evaluate(@attributes['limit']).to_i : nil
50
+
51
+ collection = Utils.slice_collection(collection, from, to)
52
+ length = collection.length
53
+
54
+ cols = context.evaluate(@attributes['cols']).to_i
55
+
56
+ output << "<tr class=\"row1\">\n"
57
+ context.stack do
58
+ tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
59
+ context['tablerowloop'] = tablerowloop
60
+
61
+ collection.each do |item|
62
+ context[@variable_name] = item
63
+
64
+ output << "<td class=\"col#{tablerowloop.col}\">"
65
+ super
66
+ output << '</td>'
67
+
68
+ if tablerowloop.col_last && !tablerowloop.last
69
+ output << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
70
+ end
71
+
72
+ tablerowloop.send(:increment!)
73
+ end
74
+ end
75
+
76
+ output << "</tr>\n"
77
+ output
78
+ end
79
+
80
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
81
+ def children
82
+ super + @node.attributes.values + [@node.collection_name]
83
+ end
84
+ end
85
+ end
86
+
87
+ Template.register_tag('tablerow', TableRow)
88
+ end
@@ -1,33 +1,49 @@
1
- require File.dirname(__FILE__) + '/if'
1
+ # frozen_string_literal: true
2
2
 
3
- module Liquid
3
+ require_relative 'if'
4
4
 
5
- # Unless is a conditional just like 'if' but works on the inverse logic.
6
- #
7
- # {% unless x < 0 %} x is greater than zero {% end %}
8
- #
5
+ module Liquid
6
+ # @liquid_public_docs
7
+ # @liquid_type tag
8
+ # @liquid_category conditional
9
+ # @liquid_name unless
10
+ # @liquid_summary
11
+ # Renders an expression unless a specific condition is `true`.
12
+ # @liquid_description
13
+ # > Tip:
14
+ # > Similar to the [`if` tag](/api/liquid/tags#if), you can use `elsif` to add more conditions to an `unless` tag.
15
+ # @liquid_syntax
16
+ # {% unless condition %}
17
+ # expression
18
+ # {% endunless %}
19
+ # @liquid_syntax_keyword condition The condition to evaluate.
20
+ # @liquid_syntax_keyword expression The expression to render unless the condition is met.
9
21
  class Unless < If
10
- def render(context)
11
- context.stack do
22
+ def render_to_output_buffer(context, output)
23
+ # First condition is interpreted backwards ( if not )
24
+ first_block = @blocks.first
25
+ result = Liquid::Utils.to_liquid_value(
26
+ first_block.evaluate(context)
27
+ )
12
28
 
13
- # First condition is interpreted backwards ( if not )
14
- first_block = @blocks.first
15
- unless first_block.evaluate(context)
16
- return render_all(first_block.attachment, context)
17
- end
29
+ unless result
30
+ return first_block.attachment.render_to_output_buffer(context, output)
31
+ end
18
32
 
19
- # After the first condition unless works just like if
20
- @blocks[1..-1].each do |block|
21
- if block.evaluate(context)
22
- return render_all(block.attachment, context)
23
- end
24
- end
33
+ # After the first condition unless works just like if
34
+ @blocks[1..-1].each do |block|
35
+ result = Liquid::Utils.to_liquid_value(
36
+ block.evaluate(context)
37
+ )
25
38
 
26
- ''
39
+ if result
40
+ return block.attachment.render_to_output_buffer(context, output)
41
+ end
27
42
  end
43
+
44
+ output
28
45
  end
29
46
  end
30
47
 
31
-
32
48
  Template.register_tag('unless', Unless)
33
49
  end
@@ -1,5 +1,6 @@
1
- module Liquid
1
+ # frozen_string_literal: true
2
2
 
3
+ module Liquid
3
4
  # Templates are central to liquid.
4
5
  # Interpretating templates is a two step process. First you compile the
5
6
  # source code you got. During compile time some extensive error checking is performed.
@@ -14,49 +15,100 @@ module Liquid
14
15
  # template.render('user_name' => 'bob')
15
16
  #
16
17
  class Template
17
- attr_accessor :root, :resource_limits
18
- @@file_system = BlankFileSystem.new
18
+ attr_accessor :root
19
+ attr_reader :resource_limits, :warnings
19
20
 
20
- class << self
21
- def file_system
22
- @@file_system
21
+ class TagRegistry
22
+ include Enumerable
23
+
24
+ def initialize
25
+ @tags = {}
26
+ @cache = {}
27
+ end
28
+
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 }
23
34
  end
24
35
 
25
- def file_system=(obj)
26
- @@file_system = obj
36
+ def []=(tag_name, klass)
37
+ @tags[tag_name] = klass.name
38
+ @cache[tag_name] = klass
27
39
  end
28
40
 
29
- def register_tag(name, klass)
30
- tags[name.to_s] = klass
41
+ def delete(tag_name)
42
+ @tags.delete(tag_name)
43
+ @cache.delete(tag_name)
44
+ end
45
+
46
+ def each(&block)
47
+ @tags.each(&block)
48
+ end
49
+
50
+ private
51
+
52
+ def lookup_class(name)
53
+ Object.const_get(name)
54
+ end
55
+ end
56
+
57
+ attr_reader :profiler
58
+
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
31
70
  end
32
71
 
33
- def tags
34
- @tags ||= {}
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
+ def register_tag(name, klass)
80
+ tags[name.to_s] = klass
35
81
  end
36
82
 
37
83
  # Pass a module with filter methods which should be available
38
84
  # to all liquid views. Good for registering the standard library
39
85
  def register_filter(mod)
40
- Strainer.global_filter(mod)
86
+ StrainerFactory.add_global_filter(mod)
41
87
  end
42
88
 
89
+ attr_accessor :default_resource_limits
90
+ Template.default_resource_limits = {}
91
+ private :default_resource_limits=
92
+
43
93
  # creates a new <tt>Template</tt> object from liquid source code
44
- def parse(source)
45
- template = Template.new
46
- template.parse(source)
47
- template
94
+ # To enable profiling, pass in <tt>profile: true</tt> as an option.
95
+ # See Liquid::Profiler for more information
96
+ def parse(source, options = {})
97
+ new.parse(source, options)
48
98
  end
49
99
  end
50
100
 
51
- # creates a new <tt>Template</tt> from an array of tokens. Use <tt>Template.parse</tt> instead
52
101
  def initialize
53
- @resource_limits = {}
102
+ @rethrow_errors = false
103
+ @resource_limits = ResourceLimits.new(Template.default_resource_limits)
54
104
  end
55
105
 
56
106
  # Parse source code.
57
107
  # Returns self for easy chaining
58
- def parse(source)
59
- @root = Document.new(tokenize(source))
108
+ def parse(source, options = {})
109
+ parse_context = configure_options(options)
110
+ tokenizer = parse_context.new_tokenizer(source, start_line_number: @line_numbers && 1)
111
+ @root = Document.parse(tokenizer, parse_context)
60
112
  self
61
113
  end
62
114
 
@@ -81,6 +133,9 @@ module Liquid
81
133
  # if you use the same filters over and over again consider registering them globally
82
134
  # with <tt>Template.register_filter</tt>
83
135
  #
136
+ # if profiling was enabled in <tt>Template#parse</tt> then the resulting profiling information
137
+ # will be available via <tt>Template#profiler</tt>
138
+ #
84
139
  # Following options can be passed:
85
140
  #
86
141
  # * <tt>filters</tt> : array with local filters
@@ -92,41 +147,51 @@ module Liquid
92
147
 
93
148
  context = case args.first
94
149
  when Liquid::Context
95
- args.shift
150
+ c = args.shift
151
+
152
+ if @rethrow_errors
153
+ c.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
154
+ end
155
+
156
+ c
96
157
  when Liquid::Drop
97
- drop = args.shift
158
+ drop = args.shift
98
159
  drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
99
160
  when Hash
100
161
  Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
101
162
  when nil
102
163
  Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
103
164
  else
104
- raise ArgumentError, "Expect Hash or Liquid::Context as parameter"
165
+ raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
105
166
  end
106
167
 
168
+ output = nil
169
+
107
170
  case args.last
108
171
  when Hash
109
172
  options = args.pop
173
+ output = options[:output] if options[:output]
174
+ static_registers = context.registers.static
110
175
 
111
- if options[:registers].is_a?(Hash)
112
- self.registers.merge!(options[:registers])
113
- end
114
-
115
- if options[:filters]
116
- context.add_filters(options[:filters])
176
+ options[:registers]&.each do |key, register|
177
+ static_registers[key] = register
117
178
  end
118
179
 
119
- when Module
120
- context.add_filters(args.pop)
121
- when Array
180
+ apply_options_to_context(context, options)
181
+ when Module, Array
122
182
  context.add_filters(args.pop)
123
183
  end
124
184
 
185
+ # Retrying a render resets resource usage
186
+ context.resource_limits.reset
187
+
188
+ if @profiling && context.profiler.nil?
189
+ @profiler = context.profiler = Liquid::Profiler.new
190
+ end
191
+
125
192
  begin
126
193
  # render the nodelist.
127
- # for performance reasons we get an array back here. join will make a string out of it.
128
- result = @root.render(context)
129
- result.respond_to?(:join) ? result.join : result
194
+ @root.render_to_output_buffer(context, output || +'')
130
195
  rescue Liquid::MemoryError => e
131
196
  context.handle_error(e)
132
197
  ensure
@@ -135,22 +200,35 @@ module Liquid
135
200
  end
136
201
 
137
202
  def render!(*args)
138
- @rethrow_errors = true; render(*args)
203
+ @rethrow_errors = true
204
+ render(*args)
139
205
  end
140
206
 
141
- private
207
+ def render_to_output_buffer(context, output)
208
+ render(context, output: output)
209
+ end
142
210
 
143
- # Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
144
- def tokenize(source)
145
- source = source.source if source.respond_to?(:source)
146
- return [] if source.to_s.empty?
147
- tokens = source.split(TemplateParser)
211
+ private
148
212
 
149
- # removes the rogue empty element at the beginning of the array
150
- tokens.shift if tokens[0] and tokens[0].empty?
213
+ def configure_options(options)
214
+ if (profiling = options[:profile])
215
+ raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
216
+ end
151
217
 
152
- tokens
218
+ @options = options
219
+ @profiling = profiling
220
+ @line_numbers = options[:line_numbers] || @profiling
221
+ parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
222
+ @warnings = parse_context.warnings
223
+ parse_context
153
224
  end
154
225
 
226
+ def apply_options_to_context(context, options)
227
+ context.add_filters(options[:filters]) if options[:filters]
228
+ context.global_filter = options[:global_filter] if options[:global_filter]
229
+ context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
230
+ context.strict_variables = options[:strict_variables] if options[:strict_variables]
231
+ context.strict_filters = options[:strict_filters] if options[:strict_filters]
232
+ end
155
233
  end
156
234
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ class TemplateFactory
5
+ def for(_template_name)
6
+ Liquid::Template.new
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ class Tokenizer
5
+ attr_reader :line_number, :for_liquid_tag
6
+
7
+ def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
8
+ @source = source.to_s.to_str
9
+ @line_number = line_number || (line_numbers ? 1 : nil)
10
+ @for_liquid_tag = for_liquid_tag
11
+ @tokens = tokenize
12
+ end
13
+
14
+ def shift
15
+ (token = @tokens.shift) || return
16
+
17
+ if @line_number
18
+ @line_number += @for_liquid_tag ? 1 : token.count("\n")
19
+ end
20
+
21
+ token
22
+ end
23
+
24
+ private
25
+
26
+ def tokenize
27
+ return [] if @source.empty?
28
+
29
+ return @source.split("\n") if @for_liquid_tag
30
+
31
+ tokens = @source.split(TemplateParser)
32
+
33
+ # removes the rogue empty element at the beginning of the array
34
+ tokens.shift if tokens[0]&.empty?
35
+
36
+ tokens
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ module Usage
5
+ def self.increment(name)
6
+ end
7
+ end
8
+ end
data/lib/liquid/utils.rb CHANGED
@@ -1,14 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  module Utils
5
+ def self.slice_collection(collection, from, to)
6
+ if (from != 0 || !to.nil?) && collection.respond_to?(:load_slice)
7
+ collection.load_slice(from, to)
8
+ else
9
+ slice_collection_using_each(collection, from, to)
10
+ end
11
+ end
12
+
3
13
  def self.slice_collection_using_each(collection, from, to)
4
14
  segments = []
5
- index = 0
15
+ index = 0
6
16
 
7
17
  # Maintains Ruby 1.8.7 String#each behaviour on 1.9
8
- return [collection] if non_blank_string?(collection)
18
+ if collection.is_a?(String)
19
+ return collection.empty? ? [] : [collection]
20
+ end
21
+ return [] unless collection.respond_to?(:each)
9
22
 
10
23
  collection.each do |item|
11
-
12
24
  if to && to <= index
13
25
  break
14
26
  end
@@ -23,8 +35,59 @@ module Liquid
23
35
  segments
24
36
  end
25
37
 
26
- def self.non_blank_string?(collection)
27
- collection.is_a?(String) && collection != ''
38
+ def self.to_integer(num)
39
+ return num if num.is_a?(Integer)
40
+ num = num.to_s
41
+ begin
42
+ Integer(num)
43
+ rescue ::ArgumentError
44
+ raise Liquid::ArgumentError, "invalid integer"
45
+ end
46
+ end
47
+
48
+ def self.to_number(obj)
49
+ case obj
50
+ when Float
51
+ BigDecimal(obj.to_s)
52
+ when Numeric
53
+ obj
54
+ when String
55
+ /\A-?\d+\.\d+\z/.match?(obj.strip) ? BigDecimal(obj) : obj.to_i
56
+ else
57
+ if obj.respond_to?(:to_number)
58
+ obj.to_number
59
+ else
60
+ 0
61
+ end
62
+ end
63
+ end
64
+
65
+ def self.to_date(obj)
66
+ return obj if obj.respond_to?(:strftime)
67
+
68
+ if obj.is_a?(String)
69
+ return nil if obj.empty?
70
+ obj = obj.downcase
71
+ end
72
+
73
+ case obj
74
+ when 'now', 'today'
75
+ Time.now
76
+ when /\A\d+\z/, Integer
77
+ Time.at(obj.to_i)
78
+ when String
79
+ Time.parse(obj)
80
+ end
81
+ rescue ::ArgumentError
82
+ nil
83
+ end
84
+
85
+ def self.to_liquid_value(obj)
86
+ # Enable "obj" to represent itself as a primitive value like integer, string, or boolean
87
+ return obj.to_liquid_value if obj.respond_to?(:to_liquid_value)
88
+
89
+ # Otherwise return the object itself
90
+ obj
28
91
  end
29
92
  end
30
93
  end