liquid 2.6.3 → 5.4.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 (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