liquid 4.0.3 → 5.0.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 +33 -0
- data/README.md +6 -0
- data/lib/liquid.rb +17 -5
- data/lib/liquid/block.rb +31 -14
- data/lib/liquid/block_body.rb +164 -54
- data/lib/liquid/condition.rb +39 -18
- data/lib/liquid/context.rb +106 -51
- data/lib/liquid/document.rb +47 -9
- data/lib/liquid/drop.rb +4 -2
- data/lib/liquid/errors.rb +20 -18
- data/lib/liquid/expression.rb +29 -34
- data/lib/liquid/extensions.rb +2 -0
- data/lib/liquid/file_system.rb +6 -4
- data/lib/liquid/forloop_drop.rb +11 -4
- data/lib/liquid/i18n.rb +5 -3
- data/lib/liquid/interrupts.rb +3 -1
- data/lib/liquid/lexer.rb +30 -23
- data/lib/liquid/locales/en.yml +3 -1
- data/lib/liquid/parse_context.rb +16 -4
- data/lib/liquid/parse_tree_visitor.rb +2 -2
- data/lib/liquid/parser.rb +30 -18
- data/lib/liquid/parser_switching.rb +17 -3
- data/lib/liquid/partial_cache.rb +24 -0
- data/lib/liquid/profiler.rb +67 -86
- data/lib/liquid/profiler/hooks.rb +26 -14
- data/lib/liquid/range_lookup.rb +5 -3
- data/lib/liquid/register.rb +6 -0
- data/lib/liquid/resource_limits.rb +47 -8
- data/lib/liquid/standardfilters.rb +63 -44
- data/lib/liquid/static_registers.rb +44 -0
- data/lib/liquid/strainer_factory.rb +36 -0
- data/lib/liquid/strainer_template.rb +53 -0
- data/lib/liquid/tablerowloop_drop.rb +6 -4
- data/lib/liquid/tag.rb +28 -6
- data/lib/liquid/tag/disableable.rb +22 -0
- data/lib/liquid/tag/disabler.rb +21 -0
- data/lib/liquid/tags/assign.rb +24 -10
- data/lib/liquid/tags/break.rb +8 -3
- data/lib/liquid/tags/capture.rb +11 -8
- data/lib/liquid/tags/case.rb +33 -27
- data/lib/liquid/tags/comment.rb +5 -3
- data/lib/liquid/tags/continue.rb +8 -3
- data/lib/liquid/tags/cycle.rb +25 -14
- data/lib/liquid/tags/decrement.rb +6 -3
- data/lib/liquid/tags/echo.rb +26 -0
- data/lib/liquid/tags/for.rb +68 -44
- data/lib/liquid/tags/if.rb +35 -23
- data/lib/liquid/tags/ifchanged.rb +11 -10
- data/lib/liquid/tags/include.rb +34 -47
- data/lib/liquid/tags/increment.rb +7 -3
- data/lib/liquid/tags/raw.rb +14 -11
- data/lib/liquid/tags/render.rb +84 -0
- data/lib/liquid/tags/table_row.rb +23 -19
- data/lib/liquid/tags/unless.rb +15 -15
- data/lib/liquid/template.rb +55 -71
- data/lib/liquid/template_factory.rb +9 -0
- data/lib/liquid/tokenizer.rb +17 -9
- data/lib/liquid/usage.rb +8 -0
- data/lib/liquid/utils.rb +5 -3
- data/lib/liquid/variable.rb +46 -41
- data/lib/liquid/variable_lookup.rb +8 -6
- data/lib/liquid/version.rb +2 -1
- data/test/integration/assign_test.rb +74 -5
- data/test/integration/blank_test.rb +11 -8
- data/test/integration/block_test.rb +47 -1
- data/test/integration/capture_test.rb +18 -10
- data/test/integration/context_test.rb +608 -5
- data/test/integration/document_test.rb +4 -2
- data/test/integration/drop_test.rb +67 -83
- data/test/integration/error_handling_test.rb +73 -61
- data/test/integration/expression_test.rb +46 -0
- data/test/integration/filter_test.rb +53 -42
- data/test/integration/hash_ordering_test.rb +5 -3
- data/test/integration/output_test.rb +26 -24
- data/test/integration/parsing_quirks_test.rb +19 -7
- data/test/integration/{render_profiling_test.rb → profiler_test.rb} +84 -25
- data/test/integration/security_test.rb +30 -21
- data/test/integration/standard_filter_test.rb +339 -281
- data/test/integration/tag/disableable_test.rb +59 -0
- data/test/integration/tag_test.rb +45 -0
- data/test/integration/tags/break_tag_test.rb +4 -2
- data/test/integration/tags/continue_tag_test.rb +4 -2
- data/test/integration/tags/echo_test.rb +13 -0
- data/test/integration/tags/for_tag_test.rb +107 -51
- data/test/integration/tags/if_else_tag_test.rb +5 -3
- data/test/integration/tags/include_tag_test.rb +70 -54
- data/test/integration/tags/increment_tag_test.rb +4 -2
- data/test/integration/tags/liquid_tag_test.rb +116 -0
- data/test/integration/tags/raw_tag_test.rb +14 -11
- data/test/integration/tags/render_tag_test.rb +213 -0
- data/test/integration/tags/standard_tag_test.rb +38 -31
- data/test/integration/tags/statements_test.rb +23 -21
- data/test/integration/tags/table_row_test.rb +2 -0
- data/test/integration/tags/unless_else_tag_test.rb +4 -2
- data/test/integration/template_test.rb +118 -124
- data/test/integration/trim_mode_test.rb +78 -44
- data/test/integration/variable_test.rb +43 -32
- data/test/test_helper.rb +75 -22
- data/test/unit/block_unit_test.rb +19 -24
- data/test/unit/condition_unit_test.rb +79 -77
- data/test/unit/file_system_unit_test.rb +6 -4
- data/test/unit/i18n_unit_test.rb +7 -5
- data/test/unit/lexer_unit_test.rb +11 -9
- data/test/{integration → unit}/parse_tree_visitor_test.rb +2 -2
- data/test/unit/parser_unit_test.rb +37 -35
- data/test/unit/partial_cache_unit_test.rb +128 -0
- data/test/unit/regexp_unit_test.rb +17 -15
- data/test/unit/static_registers_unit_test.rb +156 -0
- data/test/unit/strainer_factory_unit_test.rb +100 -0
- data/test/unit/strainer_template_unit_test.rb +82 -0
- data/test/unit/tag_unit_test.rb +5 -3
- data/test/unit/tags/case_tag_unit_test.rb +3 -1
- data/test/unit/tags/for_tag_unit_test.rb +4 -2
- data/test/unit/tags/if_tag_unit_test.rb +3 -1
- data/test/unit/template_factory_unit_test.rb +12 -0
- data/test/unit/template_unit_test.rb +19 -10
- data/test/unit/tokenizer_unit_test.rb +19 -17
- data/test/unit/variable_unit_test.rb +51 -49
- metadata +73 -47
- data/lib/liquid/strainer.rb +0 -66
- data/lib/liquid/truffle.rb +0 -5
- data/test/truffle/truffle_test.rb +0 -9
- data/test/unit/context_unit_test.rb +0 -489
- data/test/unit/strainer_unit_test.rb +0 -164
@@ -1,15 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
module ParserSwitching
|
5
|
+
def strict_parse_with_error_mode_fallback(markup)
|
6
|
+
strict_parse_with_error_context(markup)
|
7
|
+
rescue SyntaxError => e
|
8
|
+
case parse_context.error_mode
|
9
|
+
when :strict
|
10
|
+
raise
|
11
|
+
when :warn
|
12
|
+
parse_context.warnings << e
|
13
|
+
end
|
14
|
+
lax_parse(markup)
|
15
|
+
end
|
16
|
+
|
3
17
|
def parse_with_selected_parser(markup)
|
4
18
|
case parse_context.error_mode
|
5
19
|
when :strict then strict_parse_with_error_context(markup)
|
6
20
|
when :lax then lax_parse(markup)
|
7
21
|
when :warn
|
8
22
|
begin
|
9
|
-
|
23
|
+
strict_parse_with_error_context(markup)
|
10
24
|
rescue SyntaxError => e
|
11
25
|
parse_context.warnings << e
|
12
|
-
|
26
|
+
lax_parse(markup)
|
13
27
|
end
|
14
28
|
end
|
15
29
|
end
|
@@ -19,7 +33,7 @@ module Liquid
|
|
19
33
|
def strict_parse_with_error_context(markup)
|
20
34
|
strict_parse(markup)
|
21
35
|
rescue SyntaxError => e
|
22
|
-
e.line_number
|
36
|
+
e.line_number = line_number
|
23
37
|
e.markup_context = markup_context(markup)
|
24
38
|
raise e
|
25
39
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Liquid
|
4
|
+
class PartialCache
|
5
|
+
def self.load(template_name, context:, parse_context:)
|
6
|
+
cached_partials = (context.registers[:cached_partials] ||= {})
|
7
|
+
cached = cached_partials[template_name]
|
8
|
+
return cached if cached
|
9
|
+
|
10
|
+
file_system = (context.registers[:file_system] ||= Liquid::Template.file_system)
|
11
|
+
source = file_system.read_template_file(template_name)
|
12
|
+
|
13
|
+
parse_context.partial = true
|
14
|
+
|
15
|
+
template_factory = (context.registers[:template_factory] ||= Liquid::TemplateFactory.new)
|
16
|
+
template = template_factory.for(template_name)
|
17
|
+
|
18
|
+
partial = template.parse(source, parse_context)
|
19
|
+
cached_partials[template_name] = partial
|
20
|
+
ensure
|
21
|
+
parse_context.partial = false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/liquid/profiler.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'liquid/profiler/hooks'
|
2
4
|
|
3
5
|
module Liquid
|
@@ -23,7 +25,7 @@ module Liquid
|
|
23
25
|
# node.code
|
24
26
|
#
|
25
27
|
# # Which template and line number of this node.
|
26
|
-
# #
|
28
|
+
# # The top-level template name is `nil` by default, but can be set in the Liquid::Context before rendering.
|
27
29
|
# node.partial
|
28
30
|
# node.line_number
|
29
31
|
#
|
@@ -44,115 +46,94 @@ module Liquid
|
|
44
46
|
include Enumerable
|
45
47
|
|
46
48
|
class Timing
|
47
|
-
attr_reader :code, :
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
@
|
49
|
+
attr_reader :code, :template_name, :line_number, :children
|
50
|
+
attr_accessor :total_time
|
51
|
+
alias_method :render_time, :total_time
|
52
|
+
alias_method :partial, :template_name
|
53
|
+
|
54
|
+
def initialize(code: nil, template_name: nil, line_number: nil)
|
55
|
+
@code = code
|
56
|
+
@template_name = template_name
|
57
|
+
@line_number = line_number
|
58
|
+
@children = []
|
54
59
|
end
|
55
60
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
def finish
|
65
|
-
@end_time = Time.now
|
66
|
-
end
|
67
|
-
|
68
|
-
def render_time
|
69
|
-
@end_time - @start_time
|
61
|
+
def self_time
|
62
|
+
@self_time ||= begin
|
63
|
+
total_children_time = 0.0
|
64
|
+
@children.each do |child|
|
65
|
+
total_children_time += child.total_time
|
66
|
+
end
|
67
|
+
@total_time - total_children_time
|
68
|
+
end
|
70
69
|
end
|
71
70
|
end
|
72
71
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
72
|
+
attr_reader :total_time
|
73
|
+
alias_method :total_render_time, :total_time
|
74
|
+
|
75
|
+
def initialize
|
76
|
+
@root_children = []
|
77
|
+
@current_children = nil
|
78
|
+
@total_time = 0.0
|
79
|
+
end
|
80
|
+
|
81
|
+
def profile(template_name, &block)
|
82
|
+
# nested renders are done from a tag that already has a timing node
|
83
|
+
return yield if @current_children
|
84
|
+
|
85
|
+
root_children = @root_children
|
86
|
+
render_idx = root_children.length
|
87
|
+
begin
|
88
|
+
@current_children = root_children
|
89
|
+
profile_node(template_name, &block)
|
90
|
+
ensure
|
91
|
+
@current_children = nil
|
92
|
+
if (timing = root_children[render_idx])
|
93
|
+
@total_time += timing.total_time
|
94
|
+
end
|
81
95
|
end
|
82
96
|
end
|
83
97
|
|
84
|
-
def
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
Profiler.current_profile.pop_partial
|
89
|
-
output
|
98
|
+
def children
|
99
|
+
children = @root_children
|
100
|
+
if children.length == 1
|
101
|
+
children.first.children
|
90
102
|
else
|
91
|
-
|
103
|
+
children
|
92
104
|
end
|
93
105
|
end
|
94
106
|
|
95
|
-
def self.current_profile
|
96
|
-
Thread.current[:liquid_profiler]
|
97
|
-
end
|
98
|
-
|
99
|
-
def initialize
|
100
|
-
@partial_stack = ["<root>"]
|
101
|
-
|
102
|
-
@root_timing = Timing.new("", current_partial)
|
103
|
-
@timing_stack = [@root_timing]
|
104
|
-
|
105
|
-
@render_start_at = Time.now
|
106
|
-
@render_end_at = @render_start_at
|
107
|
-
end
|
108
|
-
|
109
|
-
def start
|
110
|
-
Thread.current[:liquid_profiler] = self
|
111
|
-
@render_start_at = Time.now
|
112
|
-
end
|
113
|
-
|
114
|
-
def stop
|
115
|
-
Thread.current[:liquid_profiler] = nil
|
116
|
-
@render_end_at = Time.now
|
117
|
-
end
|
118
|
-
|
119
|
-
def total_render_time
|
120
|
-
@render_end_at - @render_start_at
|
121
|
-
end
|
122
|
-
|
123
107
|
def each(&block)
|
124
|
-
|
108
|
+
children.each(&block)
|
125
109
|
end
|
126
110
|
|
127
111
|
def [](idx)
|
128
|
-
|
112
|
+
children[idx]
|
129
113
|
end
|
130
114
|
|
131
115
|
def length
|
132
|
-
|
116
|
+
children.length
|
133
117
|
end
|
134
118
|
|
135
|
-
def
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
@partial_stack.last
|
119
|
+
def profile_node(template_name, code: nil, line_number: nil)
|
120
|
+
timing = Timing.new(code: code, template_name: template_name, line_number: line_number)
|
121
|
+
parent_children = @current_children
|
122
|
+
start_time = monotonic_time
|
123
|
+
begin
|
124
|
+
@current_children = timing.children
|
125
|
+
yield
|
126
|
+
ensure
|
127
|
+
@current_children = parent_children
|
128
|
+
timing.total_time = monotonic_time - start_time
|
129
|
+
parent_children << timing
|
130
|
+
end
|
148
131
|
end
|
149
132
|
|
150
|
-
|
151
|
-
@partial_stack.push(partial_name)
|
152
|
-
end
|
133
|
+
private
|
153
134
|
|
154
|
-
def
|
155
|
-
|
135
|
+
def monotonic_time
|
136
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
156
137
|
end
|
157
138
|
end
|
158
139
|
end
|
@@ -1,23 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
|
-
|
3
|
-
def
|
4
|
-
|
5
|
-
|
4
|
+
module BlockBodyProfilingHook
|
5
|
+
def render_node(context, output, node)
|
6
|
+
if (profiler = context.profiler)
|
7
|
+
profiler.profile_node(context.template_name, code: node.raw, line_number: node.line_number) do
|
8
|
+
super
|
9
|
+
end
|
10
|
+
else
|
11
|
+
super
|
6
12
|
end
|
7
13
|
end
|
8
|
-
|
9
|
-
alias_method :render_node_without_profiling, :render_node_to_output
|
10
|
-
alias_method :render_node_to_output, :render_node_with_profiling
|
11
14
|
end
|
15
|
+
BlockBody.prepend(BlockBodyProfilingHook)
|
12
16
|
|
13
|
-
|
14
|
-
def
|
15
|
-
|
16
|
-
|
17
|
-
end
|
17
|
+
module DocumentProfilingHook
|
18
|
+
def render_to_output_buffer(context, output)
|
19
|
+
return super unless context.profiler
|
20
|
+
context.profiler.profile(context.template_name) { super }
|
18
21
|
end
|
22
|
+
end
|
23
|
+
Document.prepend(DocumentProfilingHook)
|
24
|
+
|
25
|
+
module ContextProfilingHook
|
26
|
+
attr_accessor :profiler
|
19
27
|
|
20
|
-
|
21
|
-
|
28
|
+
def new_isolated_subcontext
|
29
|
+
new_context = super
|
30
|
+
new_context.profiler = profiler
|
31
|
+
new_context
|
32
|
+
end
|
22
33
|
end
|
34
|
+
Context.prepend(ContextProfilingHook)
|
23
35
|
end
|
data/lib/liquid/range_lookup.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
class RangeLookup
|
3
5
|
def self.parse(start_markup, end_markup)
|
4
6
|
start_obj = Expression.parse(start_markup)
|
5
|
-
end_obj
|
7
|
+
end_obj = Expression.parse(end_markup)
|
6
8
|
if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)
|
7
9
|
new(start_obj, end_obj)
|
8
10
|
else
|
@@ -12,12 +14,12 @@ module Liquid
|
|
12
14
|
|
13
15
|
def initialize(start_obj, end_obj)
|
14
16
|
@start_obj = start_obj
|
15
|
-
@end_obj
|
17
|
+
@end_obj = end_obj
|
16
18
|
end
|
17
19
|
|
18
20
|
def evaluate(context)
|
19
21
|
start_int = to_integer(context.evaluate(@start_obj))
|
20
|
-
end_int
|
22
|
+
end_int = to_integer(context.evaluate(@end_obj))
|
21
23
|
start_int..end_int
|
22
24
|
end
|
23
25
|
|
@@ -1,23 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
class ResourceLimits
|
3
|
-
attr_accessor :
|
4
|
-
|
5
|
+
attr_accessor :render_length_limit, :render_score_limit, :assign_score_limit
|
6
|
+
attr_reader :render_score, :assign_score
|
5
7
|
|
6
8
|
def initialize(limits)
|
7
9
|
@render_length_limit = limits[:render_length_limit]
|
8
|
-
@render_score_limit
|
9
|
-
@assign_score_limit
|
10
|
+
@render_score_limit = limits[:render_score_limit]
|
11
|
+
@assign_score_limit = limits[:assign_score_limit]
|
10
12
|
reset
|
11
13
|
end
|
12
14
|
|
15
|
+
def increment_render_score(amount)
|
16
|
+
@render_score += amount
|
17
|
+
raise_limits_reached if @render_score_limit && @render_score > @render_score_limit
|
18
|
+
end
|
19
|
+
|
20
|
+
def increment_assign_score(amount)
|
21
|
+
@assign_score += amount
|
22
|
+
raise_limits_reached if @assign_score_limit && @assign_score > @assign_score_limit
|
23
|
+
end
|
24
|
+
|
25
|
+
# update either render_length or assign_score based on whether or not the writes are captured
|
26
|
+
def increment_write_score(output)
|
27
|
+
if (last_captured = @last_capture_length)
|
28
|
+
captured = output.bytesize
|
29
|
+
increment = captured - last_captured
|
30
|
+
@last_capture_length = captured
|
31
|
+
increment_assign_score(increment)
|
32
|
+
elsif @render_length_limit && output.bytesize > @render_length_limit
|
33
|
+
raise_limits_reached
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def raise_limits_reached
|
38
|
+
@reached_limit = true
|
39
|
+
raise MemoryError, "Memory limits exceeded"
|
40
|
+
end
|
41
|
+
|
13
42
|
def reached?
|
14
|
-
|
15
|
-
(@render_score_limit && @render_score > @render_score_limit) ||
|
16
|
-
(@assign_score_limit && @assign_score > @assign_score_limit)
|
43
|
+
@reached_limit
|
17
44
|
end
|
18
45
|
|
19
46
|
def reset
|
20
|
-
@
|
47
|
+
@reached_limit = false
|
48
|
+
@last_capture_length = nil
|
49
|
+
@render_score = @assign_score = 0
|
50
|
+
end
|
51
|
+
|
52
|
+
def with_capture
|
53
|
+
old_capture_length = @last_capture_length
|
54
|
+
begin
|
55
|
+
@last_capture_length = 0
|
56
|
+
yield
|
57
|
+
ensure
|
58
|
+
@last_capture_length = old_capture_length
|
59
|
+
end
|
21
60
|
end
|
22
61
|
end
|
23
62
|
end
|
@@ -1,20 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'cgi'
|
2
4
|
require 'bigdecimal'
|
3
5
|
|
4
6
|
module Liquid
|
5
7
|
module StandardFilters
|
6
8
|
HTML_ESCAPE = {
|
7
|
-
'&'
|
8
|
-
'>'
|
9
|
-
'<'
|
10
|
-
'"'
|
11
|
-
"'"
|
9
|
+
'&' => '&',
|
10
|
+
'>' => '>',
|
11
|
+
'<' => '<',
|
12
|
+
'"' => '"',
|
13
|
+
"'" => ''',
|
12
14
|
}.freeze
|
13
15
|
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
|
14
|
-
STRIP_HTML_BLOCKS
|
15
|
-
|
16
|
+
STRIP_HTML_BLOCKS = Regexp.union(
|
17
|
+
%r{<script.*?</script>}m,
|
16
18
|
/<!--.*?-->/m,
|
17
|
-
|
19
|
+
%r{<style.*?</style>}m
|
18
20
|
)
|
19
21
|
STRIP_HTML_TAGS = /<.*?>/m
|
20
22
|
|
@@ -39,7 +41,7 @@ module Liquid
|
|
39
41
|
end
|
40
42
|
|
41
43
|
def escape(input)
|
42
|
-
CGI.escapeHTML(input.to_s)
|
44
|
+
CGI.escapeHTML(input.to_s) unless input.nil?
|
43
45
|
end
|
44
46
|
alias_method :h, :escape
|
45
47
|
|
@@ -72,23 +74,28 @@ module Liquid
|
|
72
74
|
end
|
73
75
|
|
74
76
|
# Truncate a string down to x characters
|
75
|
-
def truncate(input, length = 50, truncate_string = "..."
|
77
|
+
def truncate(input, length = 50, truncate_string = "...")
|
76
78
|
return if input.nil?
|
77
79
|
input_str = input.to_s
|
78
|
-
length
|
80
|
+
length = Utils.to_integer(length)
|
81
|
+
|
79
82
|
truncate_string_str = truncate_string.to_s
|
83
|
+
|
80
84
|
l = length - truncate_string_str.length
|
81
85
|
l = 0 if l < 0
|
82
|
-
|
86
|
+
|
87
|
+
input_str.length > length ? input_str[0...l].concat(truncate_string_str) : input_str
|
83
88
|
end
|
84
89
|
|
85
|
-
def truncatewords(input, words = 15, truncate_string = "..."
|
90
|
+
def truncatewords(input, words = 15, truncate_string = "...")
|
86
91
|
return if input.nil?
|
87
92
|
wordlist = input.to_s.split
|
88
|
-
words
|
93
|
+
words = Utils.to_integer(words)
|
94
|
+
|
89
95
|
l = words - 1
|
90
96
|
l = 0 if l < 0
|
91
|
-
|
97
|
+
|
98
|
+
wordlist.length > l ? wordlist[0..l].join(" ").concat(truncate_string.to_s) : input
|
92
99
|
end
|
93
100
|
|
94
101
|
# Split input string into an array of substrings separated by given pattern.
|
@@ -113,7 +120,7 @@ module Liquid
|
|
113
120
|
end
|
114
121
|
|
115
122
|
def strip_html(input)
|
116
|
-
empty
|
123
|
+
empty = ''
|
117
124
|
result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
|
118
125
|
result.gsub!(STRIP_HTML_TAGS, empty)
|
119
126
|
result
|
@@ -121,18 +128,18 @@ module Liquid
|
|
121
128
|
|
122
129
|
# Remove all newlines from the string
|
123
130
|
def strip_newlines(input)
|
124
|
-
input.to_s.gsub(/\r?\n/, ''
|
131
|
+
input.to_s.gsub(/\r?\n/, '')
|
125
132
|
end
|
126
133
|
|
127
134
|
# Join elements of the array with certain character between them
|
128
|
-
def join(input, glue = ' '
|
129
|
-
InputIterator.new(input).join(glue)
|
135
|
+
def join(input, glue = ' ')
|
136
|
+
InputIterator.new(input, context).join(glue)
|
130
137
|
end
|
131
138
|
|
132
139
|
# Sort elements of the array
|
133
140
|
# provide optional property with which to sort an array of hashes or drops
|
134
141
|
def sort(input, property = nil)
|
135
|
-
ary = InputIterator.new(input)
|
142
|
+
ary = InputIterator.new(input, context)
|
136
143
|
|
137
144
|
return [] if ary.empty?
|
138
145
|
|
@@ -152,7 +159,7 @@ module Liquid
|
|
152
159
|
# Sort elements of an array ignoring case if strings
|
153
160
|
# provide optional property with which to sort an array of hashes or drops
|
154
161
|
def sort_natural(input, property = nil)
|
155
|
-
ary = InputIterator.new(input)
|
162
|
+
ary = InputIterator.new(input, context)
|
156
163
|
|
157
164
|
return [] if ary.empty?
|
158
165
|
|
@@ -172,7 +179,7 @@ module Liquid
|
|
172
179
|
# Filter the elements of an array to those with a certain property value.
|
173
180
|
# By default the target is any truthy value.
|
174
181
|
def where(input, property, target_value = nil)
|
175
|
-
ary = InputIterator.new(input)
|
182
|
+
ary = InputIterator.new(input, context)
|
176
183
|
|
177
184
|
if ary.empty?
|
178
185
|
[]
|
@@ -194,7 +201,7 @@ module Liquid
|
|
194
201
|
# Remove duplicate elements from an array
|
195
202
|
# provide optional property with which to determine uniqueness
|
196
203
|
def uniq(input, property = nil)
|
197
|
-
ary = InputIterator.new(input)
|
204
|
+
ary = InputIterator.new(input, context)
|
198
205
|
|
199
206
|
if property.nil?
|
200
207
|
ary.uniq
|
@@ -211,16 +218,16 @@ module Liquid
|
|
211
218
|
|
212
219
|
# Reverse the elements of an array
|
213
220
|
def reverse(input)
|
214
|
-
ary = InputIterator.new(input)
|
221
|
+
ary = InputIterator.new(input, context)
|
215
222
|
ary.reverse
|
216
223
|
end
|
217
224
|
|
218
225
|
# map/collect on a given property
|
219
226
|
def map(input, property)
|
220
|
-
InputIterator.new(input).map do |e|
|
227
|
+
InputIterator.new(input, context).map do |e|
|
221
228
|
e = e.call if e.is_a?(Proc)
|
222
229
|
|
223
|
-
if property == "to_liquid"
|
230
|
+
if property == "to_liquid"
|
224
231
|
e
|
225
232
|
elsif e.respond_to?(:[])
|
226
233
|
r = e[property]
|
@@ -234,7 +241,7 @@ module Liquid
|
|
234
241
|
# Remove nils within an array
|
235
242
|
# provide optional property with which to check for nil
|
236
243
|
def compact(input, property = nil)
|
237
|
-
ary = InputIterator.new(input)
|
244
|
+
ary = InputIterator.new(input, context)
|
238
245
|
|
239
246
|
if property.nil?
|
240
247
|
ary.compact
|
@@ -250,23 +257,23 @@ module Liquid
|
|
250
257
|
end
|
251
258
|
|
252
259
|
# Replace occurrences of a string with another
|
253
|
-
def replace(input, string, replacement = ''
|
260
|
+
def replace(input, string, replacement = '')
|
254
261
|
input.to_s.gsub(string.to_s, replacement.to_s)
|
255
262
|
end
|
256
263
|
|
257
264
|
# Replace the first occurrences of a string with another
|
258
|
-
def replace_first(input, string, replacement = ''
|
265
|
+
def replace_first(input, string, replacement = '')
|
259
266
|
input.to_s.sub(string.to_s, replacement.to_s)
|
260
267
|
end
|
261
268
|
|
262
269
|
# remove a substring
|
263
270
|
def remove(input, string)
|
264
|
-
input.to_s.gsub(string.to_s, ''
|
271
|
+
input.to_s.gsub(string.to_s, '')
|
265
272
|
end
|
266
273
|
|
267
274
|
# remove the first occurrences of a substring
|
268
275
|
def remove_first(input, string)
|
269
|
-
input.to_s.sub(string.to_s, ''
|
276
|
+
input.to_s.sub(string.to_s, '')
|
270
277
|
end
|
271
278
|
|
272
279
|
# add one string to another
|
@@ -276,9 +283,9 @@ module Liquid
|
|
276
283
|
|
277
284
|
def concat(input, array)
|
278
285
|
unless array.respond_to?(:to_ary)
|
279
|
-
raise ArgumentError
|
286
|
+
raise ArgumentError, "concat filter requires an array argument"
|
280
287
|
end
|
281
|
-
InputIterator.new(input).concat(array)
|
288
|
+
InputIterator.new(input, context).concat(array)
|
282
289
|
end
|
283
290
|
|
284
291
|
# prepend a string to another
|
@@ -288,7 +295,7 @@ module Liquid
|
|
288
295
|
|
289
296
|
# Add <br /> tags in front of all newlines in input string
|
290
297
|
def newline_to_br(input)
|
291
|
-
input.to_s.gsub(/\n/, "<br />\n"
|
298
|
+
input.to_s.gsub(/\n/, "<br />\n")
|
292
299
|
end
|
293
300
|
|
294
301
|
# Reformat a date using Ruby's core Time#strftime( string ) -> string
|
@@ -325,7 +332,7 @@ module Liquid
|
|
325
332
|
def date(input, format)
|
326
333
|
return input if format.to_s.empty?
|
327
334
|
|
328
|
-
return input unless date = Utils.to_date(input)
|
335
|
+
return input unless (date = Utils.to_date(input))
|
329
336
|
|
330
337
|
date.strftime(format.to_s)
|
331
338
|
end
|
@@ -419,18 +426,28 @@ module Liquid
|
|
419
426
|
result.is_a?(BigDecimal) ? result.to_f : result
|
420
427
|
end
|
421
428
|
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
429
|
+
# Set a default value when the input is nil, false or empty
|
430
|
+
#
|
431
|
+
# Example:
|
432
|
+
# {{ product.title | default: "No Title" }}
|
433
|
+
#
|
434
|
+
# Use `allow_false` when an input should only be tested against nil or empty and not false.
|
435
|
+
#
|
436
|
+
# Example:
|
437
|
+
# {{ product.title | default: "No Title", allow_false: true }}
|
438
|
+
#
|
439
|
+
def default(input, default_value = '', options = {})
|
440
|
+
options = {} unless options.is_a?(Hash)
|
441
|
+
false_check = options['allow_false'] ? input.nil? : !input
|
442
|
+
false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
|
428
443
|
end
|
429
444
|
|
430
445
|
private
|
431
446
|
|
447
|
+
attr_reader :context
|
448
|
+
|
432
449
|
def raise_property_error(property)
|
433
|
-
raise Liquid::ArgumentError
|
450
|
+
raise Liquid::ArgumentError, "cannot select the property '#{property}'"
|
434
451
|
end
|
435
452
|
|
436
453
|
def apply_operation(input, operand, operation)
|
@@ -457,8 +474,9 @@ module Liquid
|
|
457
474
|
class InputIterator
|
458
475
|
include Enumerable
|
459
476
|
|
460
|
-
def initialize(input)
|
461
|
-
@
|
477
|
+
def initialize(input, context)
|
478
|
+
@context = context
|
479
|
+
@input = if input.is_a?(Array)
|
462
480
|
input.flatten
|
463
481
|
elsif input.is_a?(Hash)
|
464
482
|
[input]
|
@@ -496,6 +514,7 @@ module Liquid
|
|
496
514
|
|
497
515
|
def each
|
498
516
|
@input.each do |e|
|
517
|
+
e.context = @context if e.respond_to?(:context=)
|
499
518
|
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
|
500
519
|
end
|
501
520
|
end
|