liquid 3.0.6 → 4.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +89 -58
  3. data/{MIT-LICENSE → LICENSE} +0 -0
  4. data/lib/liquid.rb +7 -6
  5. data/lib/liquid/block.rb +31 -124
  6. data/lib/liquid/block_body.rb +54 -57
  7. data/lib/liquid/condition.rb +23 -22
  8. data/lib/liquid/context.rb +50 -42
  9. data/lib/liquid/document.rb +19 -9
  10. data/lib/liquid/drop.rb +12 -13
  11. data/lib/liquid/errors.rb +16 -17
  12. data/lib/liquid/expression.rb +15 -3
  13. data/lib/liquid/extensions.rb +7 -7
  14. data/lib/liquid/file_system.rb +3 -3
  15. data/lib/liquid/forloop_drop.rb +42 -0
  16. data/lib/liquid/i18n.rb +5 -5
  17. data/lib/liquid/interrupts.rb +1 -2
  18. data/lib/liquid/lexer.rb +6 -4
  19. data/lib/liquid/locales/en.yml +3 -1
  20. data/lib/liquid/parse_context.rb +37 -0
  21. data/lib/liquid/parser_switching.rb +4 -4
  22. data/lib/liquid/profiler.rb +18 -19
  23. data/lib/liquid/profiler/hooks.rb +7 -7
  24. data/lib/liquid/range_lookup.rb +16 -1
  25. data/lib/liquid/resource_limits.rb +23 -0
  26. data/lib/liquid/standardfilters.rb +101 -56
  27. data/lib/liquid/strainer.rb +4 -5
  28. data/lib/liquid/tablerowloop_drop.rb +62 -0
  29. data/lib/liquid/tag.rb +9 -8
  30. data/lib/liquid/tags/assign.rb +5 -4
  31. data/lib/liquid/tags/break.rb +0 -3
  32. data/lib/liquid/tags/capture.rb +1 -1
  33. data/lib/liquid/tags/case.rb +19 -12
  34. data/lib/liquid/tags/comment.rb +2 -2
  35. data/lib/liquid/tags/cycle.rb +6 -6
  36. data/lib/liquid/tags/decrement.rb +1 -4
  37. data/lib/liquid/tags/for.rb +93 -75
  38. data/lib/liquid/tags/if.rb +49 -44
  39. data/lib/liquid/tags/ifchanged.rb +0 -2
  40. data/lib/liquid/tags/include.rb +60 -52
  41. data/lib/liquid/tags/raw.rb +26 -4
  42. data/lib/liquid/tags/table_row.rb +12 -30
  43. data/lib/liquid/tags/unless.rb +3 -4
  44. data/lib/liquid/template.rb +23 -50
  45. data/lib/liquid/tokenizer.rb +31 -0
  46. data/lib/liquid/utils.rb +48 -8
  47. data/lib/liquid/variable.rb +46 -45
  48. data/lib/liquid/variable_lookup.rb +3 -3
  49. data/lib/liquid/version.rb +1 -1
  50. data/test/integration/assign_test.rb +8 -8
  51. data/test/integration/blank_test.rb +14 -14
  52. data/test/integration/context_test.rb +2 -2
  53. data/test/integration/document_test.rb +19 -0
  54. data/test/integration/drop_test.rb +42 -40
  55. data/test/integration/error_handling_test.rb +64 -45
  56. data/test/integration/filter_test.rb +60 -20
  57. data/test/integration/output_test.rb +26 -27
  58. data/test/integration/parsing_quirks_test.rb +15 -13
  59. data/test/integration/render_profiling_test.rb +20 -20
  60. data/test/integration/security_test.rb +5 -7
  61. data/test/integration/standard_filter_test.rb +119 -37
  62. data/test/integration/tags/break_tag_test.rb +1 -2
  63. data/test/integration/tags/continue_tag_test.rb +0 -1
  64. data/test/integration/tags/for_tag_test.rb +133 -98
  65. data/test/integration/tags/if_else_tag_test.rb +75 -77
  66. data/test/integration/tags/include_tag_test.rb +23 -30
  67. data/test/integration/tags/increment_tag_test.rb +10 -11
  68. data/test/integration/tags/raw_tag_test.rb +7 -1
  69. data/test/integration/tags/standard_tag_test.rb +121 -122
  70. data/test/integration/tags/statements_test.rb +3 -5
  71. data/test/integration/tags/table_row_test.rb +20 -19
  72. data/test/integration/tags/unless_else_tag_test.rb +6 -6
  73. data/test/integration/template_test.rb +91 -45
  74. data/test/integration/variable_test.rb +23 -13
  75. data/test/test_helper.rb +33 -5
  76. data/test/unit/block_unit_test.rb +6 -5
  77. data/test/unit/condition_unit_test.rb +82 -77
  78. data/test/unit/context_unit_test.rb +48 -57
  79. data/test/unit/file_system_unit_test.rb +3 -3
  80. data/test/unit/i18n_unit_test.rb +2 -2
  81. data/test/unit/lexer_unit_test.rb +11 -8
  82. data/test/unit/parser_unit_test.rb +2 -2
  83. data/test/unit/regexp_unit_test.rb +1 -1
  84. data/test/unit/strainer_unit_test.rb +13 -2
  85. data/test/unit/tag_unit_test.rb +7 -2
  86. data/test/unit/tags/case_tag_unit_test.rb +1 -1
  87. data/test/unit/tags/for_tag_unit_test.rb +2 -2
  88. data/test/unit/tags/if_tag_unit_test.rb +1 -1
  89. data/test/unit/template_unit_test.rb +6 -5
  90. data/test/unit/tokenizer_unit_test.rb +24 -7
  91. data/test/unit/variable_unit_test.rb +60 -43
  92. metadata +44 -41
  93. data/lib/liquid/module_ex.rb +0 -62
  94. data/lib/liquid/token.rb +0 -18
  95. data/test/unit/module_ex_unit_test.rb +0 -87
@@ -1,11 +1,24 @@
1
1
  module Liquid
2
2
  class Expression
3
+ class MethodLiteral
4
+ attr_reader :method_name, :to_s
5
+
6
+ def initialize(method_name, to_s)
7
+ @method_name = method_name
8
+ @to_s = to_s
9
+ end
10
+
11
+ def to_liquid
12
+ to_s
13
+ end
14
+ end
15
+
3
16
  LITERALS = {
4
17
  nil => nil, 'nil'.freeze => nil, 'null'.freeze => nil, ''.freeze => nil,
5
18
  'true'.freeze => true,
6
19
  'false'.freeze => false,
7
- 'blank'.freeze => :blank?,
8
- 'empty'.freeze => :empty?
20
+ 'blank'.freeze => MethodLiteral.new(:blank?, '').freeze,
21
+ 'empty'.freeze => MethodLiteral.new(:empty?, '').freeze
9
22
  }
10
23
 
11
24
  def self.parse(markup)
@@ -28,6 +41,5 @@ module Liquid
28
41
  end
29
42
  end
30
43
  end
31
-
32
44
  end
33
45
  end
@@ -7,44 +7,44 @@ class String # :nodoc:
7
7
  end
8
8
  end
9
9
 
10
- class Array # :nodoc:
10
+ class Array # :nodoc:
11
11
  def to_liquid
12
12
  self
13
13
  end
14
14
  end
15
15
 
16
- class Hash # :nodoc:
16
+ class Hash # :nodoc:
17
17
  def to_liquid
18
18
  self
19
19
  end
20
20
  end
21
21
 
22
- class Numeric # :nodoc:
22
+ class Numeric # :nodoc:
23
23
  def to_liquid
24
24
  self
25
25
  end
26
26
  end
27
27
 
28
- class Time # :nodoc:
28
+ class Time # :nodoc:
29
29
  def to_liquid
30
30
  self
31
31
  end
32
32
  end
33
33
 
34
- class DateTime < Date # :nodoc:
34
+ class DateTime < Date # :nodoc:
35
35
  def to_liquid
36
36
  self
37
37
  end
38
38
  end
39
39
 
40
- class Date # :nodoc:
40
+ class Date # :nodoc:
41
41
  def to_liquid
42
42
  self
43
43
  end
44
44
  end
45
45
 
46
46
  class TrueClass
47
- def to_liquid # :nodoc:
47
+ def to_liquid # :nodoc:
48
48
  self
49
49
  end
50
50
  end
@@ -14,7 +14,7 @@ module Liquid
14
14
  # This will parse the template with a LocalFileSystem implementation rooted at 'template_path'.
15
15
  class BlankFileSystem
16
16
  # Called by Liquid to retrieve a template file
17
- def read_template_file(template_path, context)
17
+ def read_template_file(_template_path)
18
18
  raise FileSystemError, "This liquid context does not allow includes."
19
19
  end
20
20
  end
@@ -49,9 +49,9 @@ module Liquid
49
49
  @pattern = pattern
50
50
  end
51
51
 
52
- def read_template_file(template_path, context)
52
+ def read_template_file(template_path)
53
53
  full_path = full_path(template_path)
54
- raise FileSystemError, "No such template '#{template_path}'" unless File.exists?(full_path)
54
+ raise FileSystemError, "No such template '#{template_path}'" unless File.exist?(full_path)
55
55
 
56
56
  File.read(full_path)
57
57
  end
@@ -0,0 +1,42 @@
1
+ module Liquid
2
+ class ForloopDrop < Drop
3
+ def initialize(name, length, parentloop)
4
+ @name = name
5
+ @length = length
6
+ @parentloop = parentloop
7
+ @index = 0
8
+ end
9
+
10
+ attr_reader :name, :length, :parentloop
11
+
12
+ def index
13
+ @index + 1
14
+ end
15
+
16
+ def index0
17
+ @index
18
+ end
19
+
20
+ def rindex
21
+ @length - @index
22
+ end
23
+
24
+ def rindex0
25
+ @length - @index - 1
26
+ end
27
+
28
+ def first
29
+ @index == 0
30
+ end
31
+
32
+ def last
33
+ @index == @length - 1
34
+ end
35
+
36
+ protected
37
+
38
+ def increment!
39
+ @index += 1
40
+ end
41
+ end
42
+ end
@@ -2,10 +2,9 @@ require 'yaml'
2
2
 
3
3
  module Liquid
4
4
  class I18n
5
- DEFAULT_LOCALE = File.join(File.expand_path(File.dirname(__FILE__)), "locales", "en.yml")
5
+ DEFAULT_LOCALE = File.join(File.expand_path(__dir__), "locales", "en.yml")
6
6
 
7
- class TranslationError < StandardError
8
- end
7
+ TranslationError = Class.new(StandardError)
9
8
 
10
9
  attr_reader :path
11
10
 
@@ -23,11 +22,12 @@ module Liquid
23
22
  end
24
23
 
25
24
  private
25
+
26
26
  def interpolate(name, vars)
27
- name.gsub(/%\{(\w+)\}/) {
27
+ name.gsub(/%\{(\w+)\}/) do
28
28
  # raise TranslationError, "Undefined key #{$1} for interpolation in translation #{name}" unless vars[$1.to_sym]
29
29
  "#{vars[$1.to_sym]}"
30
- }
30
+ end
31
31
  end
32
32
 
33
33
  def deep_fetch_translation(name)
@@ -1,10 +1,9 @@
1
1
  module Liquid
2
-
3
2
  # An interrupt is any command that breaks processing of a block (ex: a for loop).
4
3
  class Interrupt
5
4
  attr_reader :message
6
5
 
7
- def initialize(message=nil)
6
+ def initialize(message = nil)
8
7
  @message = message || "interrupt".freeze
9
8
  end
10
9
  end
@@ -9,9 +9,11 @@ module Liquid
9
9
  '['.freeze => :open_square,
10
10
  ']'.freeze => :close_square,
11
11
  '('.freeze => :open_round,
12
- ')'.freeze => :close_round
12
+ ')'.freeze => :close_round,
13
+ '?'.freeze => :question,
14
+ '-'.freeze => :dash
13
15
  }
14
- IDENTIFIER = /[\w\-?!]+/
16
+ IDENTIFIER = /[a-zA-Z_][\w-]*\??/
15
17
  SINGLE_STRING_LITERAL = /'[^\']*'/
16
18
  DOUBLE_STRING_LITERAL = /"[^\"]*"/
17
19
  NUMBER_LITERAL = /-?\d+(\.\d+)?/
@@ -25,7 +27,7 @@ module Liquid
25
27
  def tokenize
26
28
  @output = []
27
29
 
28
- while !@ss.eos?
30
+ until @ss.eos?
29
31
  @ss.skip(/\s*/)
30
32
  tok = case
31
33
  when t = @ss.scan(COMPARISON_OPERATOR) then [:comparison, t]
@@ -37,7 +39,7 @@ module Liquid
37
39
  else
38
40
  c = @ss.getch
39
41
  if s = SPECIALS[c]
40
- [s,c]
42
+ [s, c]
41
43
  else
42
44
  raise SyntaxError, "Unexpected character #{c}"
43
45
  end
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  errors:
3
3
  syntax:
4
+ tag_unexpected_args: "Syntax Error in '%{tag}' - Valid syntax: %{tag}"
4
5
  assign: "Syntax Error in 'assign' - Valid syntax: assign [var] = [source]"
5
6
  capture: "Syntax Error in 'capture' - Valid syntax: capture [var]"
6
7
  case: "Syntax Error in 'case' - Valid syntax: case [condition]"
@@ -14,7 +15,8 @@
14
15
  include: "Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]"
15
16
  unknown_tag: "Unknown tag '%{tag}'"
16
17
  invalid_delimiter: "'end' is not a valid delimiter for %{block_name} tags. use %{block_delimiter}"
17
- unexpected_else: "%{block_name} tag does not expect else tag"
18
+ unexpected_else: "%{block_name} tag does not expect 'else' tag"
19
+ unexpected_outer_tag: "Unexpected outer '%{tag}' tag"
18
20
  tag_termination: "Tag '%{token}' was not properly terminated with regexp: %{tag_end}"
19
21
  variable_termination: "Variable '%{token}' was not properly terminated with regexp: %{tag_end}"
20
22
  tag_never_closed: "'%{block_name}' tag was never closed"
@@ -0,0 +1,37 @@
1
+ module Liquid
2
+ class ParseContext
3
+ attr_accessor :partial, :locale, :line_number
4
+ attr_reader :warnings, :error_mode
5
+
6
+ def initialize(options = {})
7
+ @template_options = options ? options.dup : {}
8
+ @locale = @template_options[:locale] ||= I18n.new
9
+ @warnings = []
10
+ self.partial = false
11
+ end
12
+
13
+ def [](option_key)
14
+ @options[option_key]
15
+ end
16
+
17
+ def partial=(value)
18
+ @partial = value
19
+ @options = value ? partial_options : @template_options
20
+ @error_mode = @options[:error_mode] || Template.error_mode
21
+ value
22
+ end
23
+
24
+ def partial_options
25
+ @partial_options ||= begin
26
+ dont_pass = @template_options[:include_options_blacklist]
27
+ if dont_pass == true
28
+ { locale: locale }
29
+ elsif dont_pass.is_a?(Array)
30
+ @template_options.reject { |k, v| dont_pass.include?(k) }
31
+ else
32
+ @template_options
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,25 +1,25 @@
1
1
  module Liquid
2
2
  module ParserSwitching
3
3
  def parse_with_selected_parser(markup)
4
- case @options[:error_mode] || Template.error_mode
4
+ case parse_context.error_mode
5
5
  when :strict then strict_parse_with_error_context(markup)
6
6
  when :lax then lax_parse(markup)
7
7
  when :warn
8
8
  begin
9
9
  return strict_parse_with_error_context(markup)
10
10
  rescue SyntaxError => e
11
- e.set_line_number_from_token(markup)
12
- @warnings ||= []
13
- @warnings << e
11
+ parse_context.warnings << e
14
12
  return lax_parse(markup)
15
13
  end
16
14
  end
17
15
  end
18
16
 
19
17
  private
18
+
20
19
  def strict_parse_with_error_context(markup)
21
20
  strict_parse(markup)
22
21
  rescue SyntaxError => e
22
+ e.line_number = line_number
23
23
  e.markup_context = markup_context(markup)
24
24
  raise e
25
25
  end
@@ -1,9 +1,11 @@
1
- module Liquid
1
+ require 'liquid/profiler/hooks'
2
2
 
3
+ module Liquid
3
4
  # Profiler enables support for profiling template rendering to help track down performance issues.
4
5
  #
5
- # To enable profiling, pass the <tt>profile: true</tt> option to <tt>Liquid::Template.parse</tt>. Then, after
6
- # <tt>Liquid::Template#render</tt> is called, the template object makes available an instance of this
6
+ # To enable profiling, first require 'liquid/profiler'.
7
+ # Then, to profile a parse/render cycle, pass the <tt>profile: true</tt> option to <tt>Liquid::Template.parse</tt>.
8
+ # After <tt>Liquid::Template#render</tt> is called, the template object makes available an instance of this
7
9
  # class via the <tt>Liquid::Template#profiler</tt> method.
8
10
  #
9
11
  # template = Liquid::Template.parse(template_content, profile: true)
@@ -17,7 +19,7 @@ module Liquid
17
19
  # inside of <tt>{% include %}</tt> tags.
18
20
  #
19
21
  # profile.each do |node|
20
- # # Access to the token itself
22
+ # # Access to the node itself
21
23
  # node.code
22
24
  #
23
25
  # # Which template and line number of this node.
@@ -44,17 +46,15 @@ module Liquid
44
46
  class Timing
45
47
  attr_reader :code, :partial, :line_number, :children
46
48
 
47
- def initialize(token, partial)
48
- @code = token.respond_to?(:raw) ? token.raw : token
49
+ def initialize(node, partial)
50
+ @code = node.respond_to?(:raw) ? node.raw : node
49
51
  @partial = partial
50
- @line_number = token.respond_to?(:line_number) ? token.line_number : nil
52
+ @line_number = node.respond_to?(:line_number) ? node.line_number : nil
51
53
  @children = []
52
54
  end
53
55
 
54
- def self.start(token, partial)
55
- new(token, partial).tap do |t|
56
- t.start
57
- end
56
+ def self.start(node, partial)
57
+ new(node, partial).tap(&:start)
58
58
  end
59
59
 
60
60
  def start
@@ -70,11 +70,11 @@ module Liquid
70
70
  end
71
71
  end
72
72
 
73
- def self.profile_token_render(token)
74
- if Profiler.current_profile && token.respond_to?(:render)
75
- Profiler.current_profile.start_token(token)
73
+ def self.profile_node_render(node)
74
+ if Profiler.current_profile && node.respond_to?(:render)
75
+ Profiler.current_profile.start_node(node)
76
76
  output = yield
77
- Profiler.current_profile.end_token(token)
77
+ Profiler.current_profile.end_node(node)
78
78
  output
79
79
  else
80
80
  yield
@@ -132,11 +132,11 @@ module Liquid
132
132
  @root_timing.children.length
133
133
  end
134
134
 
135
- def start_token(token)
136
- @timing_stack.push(Timing.start(token, current_partial))
135
+ def start_node(node)
136
+ @timing_stack.push(Timing.start(node, current_partial))
137
137
  end
138
138
 
139
- def end_token(token)
139
+ def end_node(_node)
140
140
  timing = @timing_stack.pop
141
141
  timing.finish
142
142
 
@@ -154,6 +154,5 @@ module Liquid
154
154
  def pop_partial
155
155
  @partial_stack.pop
156
156
  end
157
-
158
157
  end
159
158
  end
@@ -1,18 +1,18 @@
1
1
  module Liquid
2
- class Block < Tag
3
- def render_token_with_profiling(token, context)
4
- Profiler.profile_token_render(token) do
5
- render_token_without_profiling(token, context)
2
+ class BlockBody
3
+ def render_node_with_profiling(node, context)
4
+ Profiler.profile_node_render(node) do
5
+ render_node_without_profiling(node, context)
6
6
  end
7
7
  end
8
8
 
9
- alias_method :render_token_without_profiling, :render_token
10
- alias_method :render_token, :render_token_with_profiling
9
+ alias_method :render_node_without_profiling, :render_node
10
+ alias_method :render_node, :render_node_with_profiling
11
11
  end
12
12
 
13
13
  class Include < Tag
14
14
  def render_with_profiling(context)
15
- Profiler.profile_children(@template_name) do
15
+ Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do
16
16
  render_without_profiling(context)
17
17
  end
18
18
  end
@@ -16,7 +16,22 @@ module Liquid
16
16
  end
17
17
 
18
18
  def evaluate(context)
19
- context.evaluate(@start_obj).to_i..context.evaluate(@end_obj).to_i
19
+ start_int = to_integer(context.evaluate(@start_obj))
20
+ end_int = to_integer(context.evaluate(@end_obj))
21
+ start_int..end_int
22
+ end
23
+
24
+ private
25
+
26
+ def to_integer(input)
27
+ case input
28
+ when Integer
29
+ input
30
+ when NilClass, String
31
+ input.to_i
32
+ else
33
+ Utils.to_integer(input)
34
+ end
20
35
  end
21
36
  end
22
37
  end
@@ -0,0 +1,23 @@
1
+ module Liquid
2
+ class ResourceLimits
3
+ attr_accessor :render_length, :render_score, :assign_score,
4
+ :render_length_limit, :render_score_limit, :assign_score_limit
5
+
6
+ def initialize(limits)
7
+ @render_length_limit = limits[:render_length_limit]
8
+ @render_score_limit = limits[:render_score_limit]
9
+ @assign_score_limit = limits[:assign_score_limit]
10
+ reset
11
+ end
12
+
13
+ def reached?
14
+ (@render_length_limit && @render_length > @render_length_limit) ||
15
+ (@render_score_limit && @render_score > @render_score_limit) ||
16
+ (@assign_score_limit && @assign_score > @assign_score_limit)
17
+ end
18
+
19
+ def reset
20
+ @render_length = @render_score = @assign_score = 0
21
+ end
22
+ end
23
+ end