liquid 4.0.3 → 5.0.1
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 +43 -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 +166 -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 +20 -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 +67 -46
- 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 +34 -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 +53 -72
- 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 +609 -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 +343 -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 +9 -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 +26 -19
- 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,30 @@ 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
|
+
input = input.to_s
|
88
93
|
words = Utils.to_integer(words)
|
89
|
-
|
90
|
-
|
91
|
-
wordlist
|
94
|
+
words = 1 if words <= 0
|
95
|
+
|
96
|
+
wordlist = input.split(" ", words + 1)
|
97
|
+
return input if wordlist.length <= words
|
98
|
+
|
99
|
+
wordlist.pop
|
100
|
+
wordlist.join(" ").concat(truncate_string.to_s)
|
92
101
|
end
|
93
102
|
|
94
103
|
# Split input string into an array of substrings separated by given pattern.
|
@@ -113,7 +122,7 @@ module Liquid
|
|
113
122
|
end
|
114
123
|
|
115
124
|
def strip_html(input)
|
116
|
-
empty
|
125
|
+
empty = ''
|
117
126
|
result = input.to_s.gsub(STRIP_HTML_BLOCKS, empty)
|
118
127
|
result.gsub!(STRIP_HTML_TAGS, empty)
|
119
128
|
result
|
@@ -121,18 +130,18 @@ module Liquid
|
|
121
130
|
|
122
131
|
# Remove all newlines from the string
|
123
132
|
def strip_newlines(input)
|
124
|
-
input.to_s.gsub(/\r?\n/, ''
|
133
|
+
input.to_s.gsub(/\r?\n/, '')
|
125
134
|
end
|
126
135
|
|
127
136
|
# Join elements of the array with certain character between them
|
128
|
-
def join(input, glue = ' '
|
129
|
-
InputIterator.new(input).join(glue)
|
137
|
+
def join(input, glue = ' ')
|
138
|
+
InputIterator.new(input, context).join(glue)
|
130
139
|
end
|
131
140
|
|
132
141
|
# Sort elements of the array
|
133
142
|
# provide optional property with which to sort an array of hashes or drops
|
134
143
|
def sort(input, property = nil)
|
135
|
-
ary = InputIterator.new(input)
|
144
|
+
ary = InputIterator.new(input, context)
|
136
145
|
|
137
146
|
return [] if ary.empty?
|
138
147
|
|
@@ -152,7 +161,7 @@ module Liquid
|
|
152
161
|
# Sort elements of an array ignoring case if strings
|
153
162
|
# provide optional property with which to sort an array of hashes or drops
|
154
163
|
def sort_natural(input, property = nil)
|
155
|
-
ary = InputIterator.new(input)
|
164
|
+
ary = InputIterator.new(input, context)
|
156
165
|
|
157
166
|
return [] if ary.empty?
|
158
167
|
|
@@ -172,7 +181,7 @@ module Liquid
|
|
172
181
|
# Filter the elements of an array to those with a certain property value.
|
173
182
|
# By default the target is any truthy value.
|
174
183
|
def where(input, property, target_value = nil)
|
175
|
-
ary = InputIterator.new(input)
|
184
|
+
ary = InputIterator.new(input, context)
|
176
185
|
|
177
186
|
if ary.empty?
|
178
187
|
[]
|
@@ -194,7 +203,7 @@ module Liquid
|
|
194
203
|
# Remove duplicate elements from an array
|
195
204
|
# provide optional property with which to determine uniqueness
|
196
205
|
def uniq(input, property = nil)
|
197
|
-
ary = InputIterator.new(input)
|
206
|
+
ary = InputIterator.new(input, context)
|
198
207
|
|
199
208
|
if property.nil?
|
200
209
|
ary.uniq
|
@@ -211,16 +220,16 @@ module Liquid
|
|
211
220
|
|
212
221
|
# Reverse the elements of an array
|
213
222
|
def reverse(input)
|
214
|
-
ary = InputIterator.new(input)
|
223
|
+
ary = InputIterator.new(input, context)
|
215
224
|
ary.reverse
|
216
225
|
end
|
217
226
|
|
218
227
|
# map/collect on a given property
|
219
228
|
def map(input, property)
|
220
|
-
InputIterator.new(input).map do |e|
|
229
|
+
InputIterator.new(input, context).map do |e|
|
221
230
|
e = e.call if e.is_a?(Proc)
|
222
231
|
|
223
|
-
if property == "to_liquid"
|
232
|
+
if property == "to_liquid"
|
224
233
|
e
|
225
234
|
elsif e.respond_to?(:[])
|
226
235
|
r = e[property]
|
@@ -234,7 +243,7 @@ module Liquid
|
|
234
243
|
# Remove nils within an array
|
235
244
|
# provide optional property with which to check for nil
|
236
245
|
def compact(input, property = nil)
|
237
|
-
ary = InputIterator.new(input)
|
246
|
+
ary = InputIterator.new(input, context)
|
238
247
|
|
239
248
|
if property.nil?
|
240
249
|
ary.compact
|
@@ -250,23 +259,23 @@ module Liquid
|
|
250
259
|
end
|
251
260
|
|
252
261
|
# Replace occurrences of a string with another
|
253
|
-
def replace(input, string, replacement = ''
|
262
|
+
def replace(input, string, replacement = '')
|
254
263
|
input.to_s.gsub(string.to_s, replacement.to_s)
|
255
264
|
end
|
256
265
|
|
257
266
|
# Replace the first occurrences of a string with another
|
258
|
-
def replace_first(input, string, replacement = ''
|
267
|
+
def replace_first(input, string, replacement = '')
|
259
268
|
input.to_s.sub(string.to_s, replacement.to_s)
|
260
269
|
end
|
261
270
|
|
262
271
|
# remove a substring
|
263
272
|
def remove(input, string)
|
264
|
-
input.to_s.gsub(string.to_s, ''
|
273
|
+
input.to_s.gsub(string.to_s, '')
|
265
274
|
end
|
266
275
|
|
267
276
|
# remove the first occurrences of a substring
|
268
277
|
def remove_first(input, string)
|
269
|
-
input.to_s.sub(string.to_s, ''
|
278
|
+
input.to_s.sub(string.to_s, '')
|
270
279
|
end
|
271
280
|
|
272
281
|
# add one string to another
|
@@ -276,9 +285,9 @@ module Liquid
|
|
276
285
|
|
277
286
|
def concat(input, array)
|
278
287
|
unless array.respond_to?(:to_ary)
|
279
|
-
raise ArgumentError
|
288
|
+
raise ArgumentError, "concat filter requires an array argument"
|
280
289
|
end
|
281
|
-
InputIterator.new(input).concat(array)
|
290
|
+
InputIterator.new(input, context).concat(array)
|
282
291
|
end
|
283
292
|
|
284
293
|
# prepend a string to another
|
@@ -288,7 +297,7 @@ module Liquid
|
|
288
297
|
|
289
298
|
# Add <br /> tags in front of all newlines in input string
|
290
299
|
def newline_to_br(input)
|
291
|
-
input.to_s.gsub(/\n/, "<br />\n"
|
300
|
+
input.to_s.gsub(/\r?\n/, "<br />\n")
|
292
301
|
end
|
293
302
|
|
294
303
|
# Reformat a date using Ruby's core Time#strftime( string ) -> string
|
@@ -325,7 +334,7 @@ module Liquid
|
|
325
334
|
def date(input, format)
|
326
335
|
return input if format.to_s.empty?
|
327
336
|
|
328
|
-
return input unless date = Utils.to_date(input)
|
337
|
+
return input unless (date = Utils.to_date(input))
|
329
338
|
|
330
339
|
date.strftime(format.to_s)
|
331
340
|
end
|
@@ -419,18 +428,28 @@ module Liquid
|
|
419
428
|
result.is_a?(BigDecimal) ? result.to_f : result
|
420
429
|
end
|
421
430
|
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
431
|
+
# Set a default value when the input is nil, false or empty
|
432
|
+
#
|
433
|
+
# Example:
|
434
|
+
# {{ product.title | default: "No Title" }}
|
435
|
+
#
|
436
|
+
# Use `allow_false` when an input should only be tested against nil or empty and not false.
|
437
|
+
#
|
438
|
+
# Example:
|
439
|
+
# {{ product.title | default: "No Title", allow_false: true }}
|
440
|
+
#
|
441
|
+
def default(input, default_value = '', options = {})
|
442
|
+
options = {} unless options.is_a?(Hash)
|
443
|
+
false_check = options['allow_false'] ? input.nil? : !input
|
444
|
+
false_check || (input.respond_to?(:empty?) && input.empty?) ? default_value : input
|
428
445
|
end
|
429
446
|
|
430
447
|
private
|
431
448
|
|
449
|
+
attr_reader :context
|
450
|
+
|
432
451
|
def raise_property_error(property)
|
433
|
-
raise Liquid::ArgumentError
|
452
|
+
raise Liquid::ArgumentError, "cannot select the property '#{property}'"
|
434
453
|
end
|
435
454
|
|
436
455
|
def apply_operation(input, operand, operation)
|
@@ -457,8 +476,9 @@ module Liquid
|
|
457
476
|
class InputIterator
|
458
477
|
include Enumerable
|
459
478
|
|
460
|
-
def initialize(input)
|
461
|
-
@
|
479
|
+
def initialize(input, context)
|
480
|
+
@context = context
|
481
|
+
@input = if input.is_a?(Array)
|
462
482
|
input.flatten
|
463
483
|
elsif input.is_a?(Hash)
|
464
484
|
[input]
|
@@ -496,6 +516,7 @@ module Liquid
|
|
496
516
|
|
497
517
|
def each
|
498
518
|
@input.each do |e|
|
519
|
+
e.context = @context if e.respond_to?(:context=)
|
499
520
|
yield(e.respond_to?(:to_liquid) ? e.to_liquid : e)
|
500
521
|
end
|
501
522
|
end
|