locomotivecms-liquid 2.6.0 → 4.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +62 -5
  3. data/README.md +4 -4
  4. data/lib/liquid.rb +16 -12
  5. data/lib/liquid/block.rb +37 -118
  6. data/lib/liquid/block_body.rb +131 -0
  7. data/lib/liquid/condition.rb +28 -17
  8. data/lib/liquid/context.rb +94 -146
  9. data/lib/liquid/document.rb +16 -10
  10. data/lib/liquid/drop.rb +8 -5
  11. data/lib/liquid/drops/inherited_block_drop.rb +24 -0
  12. data/lib/liquid/errors.rb +44 -5
  13. data/lib/liquid/expression.rb +33 -0
  14. data/lib/liquid/file_system.rb +17 -6
  15. data/lib/liquid/i18n.rb +2 -2
  16. data/lib/liquid/interrupts.rb +1 -1
  17. data/lib/liquid/lexer.rb +11 -9
  18. data/lib/liquid/locales/en.yml +2 -4
  19. data/lib/liquid/parser.rb +2 -1
  20. data/lib/liquid/parser_switching.rb +31 -0
  21. data/lib/liquid/profiler.rb +162 -0
  22. data/lib/liquid/profiler/hooks.rb +23 -0
  23. data/lib/liquid/range_lookup.rb +22 -0
  24. data/lib/liquid/resource_limits.rb +23 -0
  25. data/lib/liquid/standardfilters.rb +142 -67
  26. data/lib/liquid/strainer.rb +14 -4
  27. data/lib/liquid/tag.rb +22 -41
  28. data/lib/liquid/tags/assign.rb +15 -10
  29. data/lib/liquid/tags/break.rb +1 -1
  30. data/lib/liquid/tags/capture.rb +7 -9
  31. data/lib/liquid/tags/case.rb +28 -19
  32. data/lib/liquid/tags/comment.rb +2 -2
  33. data/lib/liquid/tags/continue.rb +1 -4
  34. data/lib/liquid/tags/cycle.rb +10 -14
  35. data/lib/liquid/tags/decrement.rb +3 -4
  36. data/lib/liquid/tags/extends.rb +28 -44
  37. data/lib/liquid/tags/for.rb +64 -42
  38. data/lib/liquid/tags/if.rb +30 -19
  39. data/lib/liquid/tags/ifchanged.rb +4 -4
  40. data/lib/liquid/tags/include.rb +30 -20
  41. data/lib/liquid/tags/increment.rb +3 -8
  42. data/lib/liquid/tags/inherited_block.rb +54 -56
  43. data/lib/liquid/tags/raw.rb +18 -10
  44. data/lib/liquid/tags/table_row.rb +72 -0
  45. data/lib/liquid/tags/unless.rb +5 -7
  46. data/lib/liquid/template.rb +113 -53
  47. data/lib/liquid/token.rb +18 -0
  48. data/lib/liquid/utils.rb +13 -4
  49. data/lib/liquid/variable.rb +68 -50
  50. data/lib/liquid/variable_lookup.rb +78 -0
  51. data/lib/liquid/version.rb +1 -1
  52. data/test/fixtures/en_locale.yml +9 -0
  53. data/test/integration/assign_test.rb +48 -0
  54. data/test/integration/blank_test.rb +106 -0
  55. data/test/integration/capture_test.rb +50 -0
  56. data/test/integration/context_test.rb +32 -0
  57. data/test/integration/document_test.rb +19 -0
  58. data/test/integration/drop_test.rb +271 -0
  59. data/test/integration/error_handling_test.rb +207 -0
  60. data/test/integration/filter_test.rb +125 -0
  61. data/test/integration/hash_ordering_test.rb +23 -0
  62. data/test/integration/output_test.rb +116 -0
  63. data/test/integration/parsing_quirks_test.rb +119 -0
  64. data/test/integration/render_profiling_test.rb +154 -0
  65. data/test/integration/security_test.rb +64 -0
  66. data/test/integration/standard_filter_test.rb +379 -0
  67. data/test/integration/tags/break_tag_test.rb +16 -0
  68. data/test/integration/tags/continue_tag_test.rb +16 -0
  69. data/test/integration/tags/extends_tag_test.rb +104 -0
  70. data/test/integration/tags/for_tag_test.rb +375 -0
  71. data/test/integration/tags/if_else_tag_test.rb +169 -0
  72. data/test/integration/tags/include_tag_test.rb +234 -0
  73. data/test/integration/tags/increment_tag_test.rb +24 -0
  74. data/test/integration/tags/raw_tag_test.rb +25 -0
  75. data/test/integration/tags/standard_tag_test.rb +297 -0
  76. data/test/integration/tags/statements_test.rb +113 -0
  77. data/test/integration/tags/table_row_test.rb +63 -0
  78. data/test/integration/tags/unless_else_tag_test.rb +26 -0
  79. data/test/integration/template_test.rb +216 -0
  80. data/test/integration/variable_test.rb +82 -0
  81. data/test/test_helper.rb +83 -0
  82. data/test/unit/block_unit_test.rb +55 -0
  83. data/test/unit/condition_unit_test.rb +149 -0
  84. data/test/unit/context_unit_test.rb +482 -0
  85. data/test/unit/file_system_unit_test.rb +35 -0
  86. data/test/unit/i18n_unit_test.rb +37 -0
  87. data/test/unit/lexer_unit_test.rb +51 -0
  88. data/test/unit/module_ex_unit_test.rb +87 -0
  89. data/test/unit/parser_unit_test.rb +82 -0
  90. data/test/unit/regexp_unit_test.rb +44 -0
  91. data/test/unit/strainer_unit_test.rb +71 -0
  92. data/test/unit/tag_unit_test.rb +16 -0
  93. data/test/unit/tags/case_tag_unit_test.rb +10 -0
  94. data/test/unit/tags/for_tag_unit_test.rb +13 -0
  95. data/test/unit/tags/if_tag_unit_test.rb +8 -0
  96. data/test/unit/template_unit_test.rb +70 -0
  97. data/test/unit/tokenizer_unit_test.rb +38 -0
  98. data/test/unit/variable_unit_test.rb +150 -0
  99. metadata +144 -15
  100. data/lib/extras/liquid_view.rb +0 -51
  101. data/lib/liquid/htmltags.rb +0 -74
  102. data/lib/liquid/tags/default_content.rb +0 -21
  103. data/lib/locomotivecms-liquid.rb +0 -1
data/lib/liquid/tag.rb CHANGED
@@ -1,61 +1,42 @@
1
1
  module Liquid
2
2
  class Tag
3
- attr_accessor :nodelist, :options, :line
4
- attr_reader :warnings
5
-
6
- def self.new_with_options(tag_name, markup, tokens, options)
7
- # Forgive me Matz for I have sinned. I know this code is weird
8
- # but it was necessary to maintain API compatibility.
9
- new_tag = self.allocate
10
- new_tag.options = options
11
- new_tag.send(:initialize, tag_name, markup, tokens, options)
12
- new_tag
3
+ attr_accessor :options, :line_number
4
+ attr_reader :nodelist, :warnings
5
+ include ParserSwitching
6
+
7
+ class << self
8
+ def parse(tag_name, markup, tokens, options)
9
+ tag = new(tag_name, markup, options)
10
+ tag.parse(tokens)
11
+ tag
12
+ end
13
+
14
+ private :new
13
15
  end
14
16
 
15
- def initialize(tag_name, markup, tokens, options)
17
+ def initialize(tag_name, markup, options)
16
18
  @tag_name = tag_name
17
19
  @markup = markup
18
- @options = options || {}
19
- @line = @options[:line] || 1
20
- parse(tokens)
20
+ @options = options
21
21
  end
22
22
 
23
23
  def parse(tokens)
24
24
  end
25
25
 
26
+ def raw
27
+ "#{@tag_name} #{@markup}"
28
+ end
29
+
26
30
  def name
27
31
  self.class.name.downcase
28
32
  end
29
33
 
30
34
  def render(context)
31
- ''
35
+ ''.freeze
32
36
  end
33
37
 
34
38
  def blank?
35
- @blank || true
36
- end
37
-
38
- def parse_with_selected_parser(markup)
39
- case @options[:error_mode] || Template.error_mode
40
- when :strict then strict_parse_with_error_context(markup)
41
- when :lax then lax_parse(markup)
42
- when :warn
43
- begin
44
- return strict_parse_with_error_context(markup)
45
- rescue SyntaxError => e
46
- @warnings ||= []
47
- @warnings << e
48
- return lax_parse(markup)
49
- end
50
- end
51
- end
52
-
53
- private
54
- def strict_parse_with_error_context(markup)
55
- strict_parse(markup)
56
- rescue SyntaxError => e
57
- e.message << " in \"#{markup.strip}\""
58
- raise e
39
+ false
59
40
  end
60
- end # Tag
61
- end # Liquid
41
+ end
42
+ end
@@ -9,28 +9,33 @@ module Liquid
9
9
  # {{ foo }}
10
10
  #
11
11
  class Assign < Tag
12
- Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/o
12
+ Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
13
13
 
14
-
15
- def initialize(tag_name, markup, tokens, options)
14
+ def initialize(tag_name, markup, options)
15
+ super
16
16
  if markup =~ Syntax
17
17
  @to = $1
18
- @from = Variable.new($2)
18
+ @from = Variable.new($2,options)
19
+ @from.line_number = line_number
19
20
  else
20
- raise SyntaxError.new options[:locale].t("errors.syntax.assign")
21
+ raise SyntaxError.new options[:locale].t("errors.syntax.assign".freeze)
21
22
  end
22
-
23
- super
24
23
  end
25
24
 
26
25
  def render(context)
27
26
  val = @from.render(context)
28
27
  context.scopes.last[@to] = val
29
- context.resource_limits[:assign_score_current] += (val.respond_to?(:length) ? val.length : 1)
30
- ''
28
+
29
+ inc = val.instance_of?(String) || val.instance_of?(Array) || val.instance_of?(Hash) ? val.length : 1
30
+ context.resource_limits.assign_score += inc
31
+
32
+ ''.freeze
31
33
  end
32
34
 
35
+ def blank?
36
+ true
37
+ end
33
38
  end
34
39
 
35
- Template.register_tag('assign', Assign)
40
+ Template.register_tag('assign'.freeze, Assign)
36
41
  end
@@ -17,5 +17,5 @@ module Liquid
17
17
 
18
18
  end
19
19
 
20
- Template.register_tag('break', Break)
20
+ Template.register_tag('break'.freeze, Break)
21
21
  end
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # Capture stores the result of a block into a variable without rendering it inplace.
4
3
  #
5
4
  # {% capture heading %}
@@ -12,23 +11,22 @@ module Liquid
12
11
  # in a sidebar or footer.
13
12
  #
14
13
  class Capture < Block
15
- Syntax = /(\w+)/
14
+ Syntax = /(#{VariableSignature}+)/o
16
15
 
17
- def initialize(tag_name, markup, tokens, options)
16
+ def initialize(tag_name, markup, options)
17
+ super
18
18
  if markup =~ Syntax
19
19
  @to = $1
20
20
  else
21
- raise SyntaxError.new(options[:locale].t("errors.syntax.capture"), options[:line])
21
+ raise SyntaxError.new(options[:locale].t("errors.syntax.capture"))
22
22
  end
23
-
24
- super
25
23
  end
26
24
 
27
25
  def render(context)
28
26
  output = super
29
27
  context.scopes.last[@to] = output
30
- context.resource_limits[:assign_score_current] += (output.respond_to?(:length) ? output.length : 1)
31
- ''
28
+ context.resource_limits.assign_score += output.length
29
+ ''.freeze
32
30
  end
33
31
 
34
32
  def blank?
@@ -36,5 +34,5 @@ module Liquid
36
34
  end
37
35
  end
38
36
 
39
- Template.register_tag('capture', Capture)
37
+ Template.register_tag('capture'.freeze, Capture)
40
38
  end
@@ -1,27 +1,35 @@
1
1
  module Liquid
2
2
  class Case < Block
3
3
  Syntax = /(#{QuotedFragment})/o
4
- WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/o
4
+ WhenSyntax = /(#{QuotedFragment})(?:(?:\s+or\s+|\s*\,\s*)(#{QuotedFragment}.*))?/om
5
5
 
6
-
7
- def initialize(tag_name, markup, tokens, options)
6
+ def initialize(tag_name, markup, options)
7
+ super
8
8
  @blocks = []
9
9
 
10
10
  if markup =~ Syntax
11
- @left = $1
11
+ @left = Expression.parse($1)
12
12
  else
13
- raise SyntaxError.new(options[:locale].t("errors.syntax.case"), options[:line])
13
+ raise SyntaxError.new(options[:locale].t("errors.syntax.case".freeze))
14
14
  end
15
+ end
15
16
 
16
- super
17
+ def parse(tokens)
18
+ body = BlockBody.new
19
+ while more = parse_body(body, tokens)
20
+ body = @blocks.last.attachment
21
+ end
22
+ end
23
+
24
+ def nodelist
25
+ @blocks.map(&:attachment)
17
26
  end
18
27
 
19
28
  def unknown_tag(tag, markup, tokens)
20
- @nodelist = []
21
29
  case tag
22
- when 'when'
30
+ when 'when'.freeze
23
31
  record_when_condition(markup)
24
- when 'else'
32
+ when 'else'.freeze
25
33
  record_else_condition(markup)
26
34
  else
27
35
  super
@@ -35,10 +43,10 @@ module Liquid
35
43
  output = ''
36
44
  @blocks.each do |block|
37
45
  if block.else?
38
- return render_all(block.attachment, context) if execute_else_block
46
+ return block.attachment.render(context) if execute_else_block
39
47
  elsif block.evaluate(context)
40
48
  execute_else_block = false
41
- output << render_all(block.attachment, context)
49
+ output << block.attachment.render(context)
42
50
  end
43
51
  end
44
52
  output
@@ -48,30 +56,31 @@ module Liquid
48
56
  private
49
57
 
50
58
  def record_when_condition(markup)
59
+ body = BlockBody.new
60
+
51
61
  while markup
52
- # Create a new nodelist and assign it to the new block
53
62
  if not markup =~ WhenSyntax
54
- raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when"), line)
63
+ raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_when".freeze))
55
64
  end
56
65
 
57
66
  markup = $2
58
67
 
59
- block = Condition.new(@left, '==', $1)
60
- block.attach(@nodelist)
61
- @blocks.push(block)
68
+ block = Condition.new(@left, '=='.freeze, Expression.parse($1))
69
+ block.attach(body)
70
+ @blocks << block
62
71
  end
63
72
  end
64
73
 
65
74
  def record_else_condition(markup)
66
75
  if not markup.strip.empty?
67
- raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else"), line)
76
+ raise SyntaxError.new(options[:locale].t("errors.syntax.case_invalid_else".freeze))
68
77
  end
69
78
 
70
79
  block = ElseCondition.new
71
- block.attach(@nodelist)
80
+ block.attach(BlockBody.new)
72
81
  @blocks << block
73
82
  end
74
83
  end
75
84
 
76
- Template.register_tag('case', Case)
85
+ Template.register_tag('case'.freeze, Case)
77
86
  end
@@ -1,7 +1,7 @@
1
1
  module Liquid
2
2
  class Comment < Block
3
3
  def render(context)
4
- ''
4
+ ''.freeze
5
5
  end
6
6
 
7
7
  def unknown_tag(tag, markup, tokens)
@@ -12,5 +12,5 @@ module Liquid
12
12
  end
13
13
  end
14
14
 
15
- Template.register_tag('comment', Comment)
15
+ Template.register_tag('comment'.freeze, Comment)
16
16
  end
@@ -1,5 +1,4 @@
1
1
  module Liquid
2
-
3
2
  # Continue tag to be used to break out of a for loop.
4
3
  #
5
4
  # == Basic Usage:
@@ -10,12 +9,10 @@ module Liquid
10
9
  # {% endfor %}
11
10
  #
12
11
  class Continue < Tag
13
-
14
12
  def interrupt
15
13
  ContinueInterrupt.new
16
14
  end
17
-
18
15
  end
19
16
 
20
- Template.register_tag('continue', Continue)
17
+ Template.register_tag('continue'.freeze, Continue)
21
18
  end
@@ -12,30 +12,30 @@ module Liquid
12
12
  # <div class="green"> Item five</div>
13
13
  #
14
14
  class Cycle < Tag
15
- SimpleSyntax = /^#{QuotedFragment}+/o
16
- NamedSyntax = /^(#{QuotedFragment})\s*\:\s*(.*)/o
15
+ SimpleSyntax = /\A#{QuotedFragment}+/o
16
+ NamedSyntax = /\A(#{QuotedFragment})\s*\:\s*(.*)/om
17
17
 
18
- def initialize(tag_name, markup, tokens, options)
18
+ def initialize(tag_name, markup, options)
19
+ super
19
20
  case markup
20
21
  when NamedSyntax
21
22
  @variables = variables_from_string($2)
22
- @name = $1
23
+ @name = Expression.parse($1)
23
24
  when SimpleSyntax
24
25
  @variables = variables_from_string(markup)
25
- @name = "'#{@variables.to_s}'"
26
+ @name = @variables.to_s
26
27
  else
27
- raise SyntaxError.new(options[:locale].t("errors.syntax.cycle"), options[:line])
28
+ raise SyntaxError.new(options[:locale].t("errors.syntax.cycle".freeze))
28
29
  end
29
- super
30
30
  end
31
31
 
32
32
  def render(context)
33
33
  context.registers[:cycle] ||= Hash.new(0)
34
34
 
35
35
  context.stack do
36
- key = context[@name]
36
+ key = context.evaluate(@name)
37
37
  iteration = context.registers[:cycle][key]
38
- result = context[@variables[iteration]]
38
+ result = context.evaluate(@variables[iteration])
39
39
  iteration += 1
40
40
  iteration = 0 if iteration >= @variables.size
41
41
  context.registers[:cycle][key] = iteration
@@ -43,16 +43,12 @@ module Liquid
43
43
  end
44
44
  end
45
45
 
46
- def blank?
47
- false
48
- end
49
-
50
46
  private
51
47
 
52
48
  def variables_from_string(markup)
53
49
  markup.split(',').collect do |var|
54
50
  var =~ /\s*(#{QuotedFragment})\s*/o
55
- $1 ? $1 : nil
51
+ $1 ? Expression.parse($1) : nil
56
52
  end.compact
57
53
  end
58
54
  end
@@ -19,10 +19,9 @@ module Liquid
19
19
  # Hello: -3
20
20
  #
21
21
  class Decrement < Tag
22
- def initialize(tag_name, markup, tokens, options)
23
- @variable = markup.strip
24
-
22
+ def initialize(tag_name, markup, options)
25
23
  super
24
+ @variable = markup.strip
26
25
  end
27
26
 
28
27
  def render(context)
@@ -35,5 +34,5 @@ module Liquid
35
34
  private
36
35
  end
37
36
 
38
- Template.register_tag('decrement', Decrement)
37
+ Template.register_tag('decrement'.freeze, Decrement)
39
38
  end
@@ -1,40 +1,38 @@
1
1
  module Liquid
2
2
 
3
- # Extends allows designer to use template inheritance
3
+ # Extends allows designer to use template inheritance.
4
4
  #
5
5
  # {% extends home %}
6
6
  # {% block content }Hello world{% endblock %}
7
7
  #
8
8
  class Extends < Block
9
- Syntax = /(#{QuotedFragment}+)/
9
+ Syntax = /(#{QuotedFragment}+)/o
10
+
11
+ def initialize(tag_name, markup, options)
12
+ super
10
13
 
11
- def initialize(tag_name, markup, tokens, options)
12
14
  if markup =~ Syntax
13
15
  @template_name = $1.gsub(/["']/o, '').strip
14
16
  else
15
- raise(SyntaxError.new(options[:locale].t("errors.syntax.extends")), options[:line])
17
+ raise(SyntaxError.new(options[:locale].t("errors.syntax.extends".freeze)))
16
18
  end
17
19
 
18
- @options = options
19
-
20
- @parent_template = parse_parent_template
21
-
22
- prepare_parsing
23
-
24
- super
25
-
26
- end_tag
20
+ # variables needed by the inheritance mechanism during the parsing
21
+ options[:inherited_blocks] ||= {
22
+ nested: [], # used to get the full name of the blocks if nested (stack mechanism)
23
+ all: {} # keep track of the blocks by their full name
24
+ }
27
25
  end
28
26
 
29
- def prepare_parsing
30
- @options.merge!(:blocks => self.find_blocks(@parent_template.root.nodelist))
31
- end
27
+ def parse(tokens)
28
+ super
32
29
 
33
- def end_tag
34
- # replace the nodelist by the new one
35
- @nodelist = @parent_template.root.nodelist.clone
30
+ parent_template = parse_parent_template
36
31
 
37
- @parent_template = nil # no need to keep it
32
+ # replace the nodes of the current template by those from the parent
33
+ # which itself may have have done the same operation if it includes
34
+ # the extends tag.
35
+ nodelist.replace(parent_template.root.nodelist)
38
36
  end
39
37
 
40
38
  def blank?
@@ -43,37 +41,23 @@ module Liquid
43
41
 
44
42
  protected
45
43
 
46
- def find_blocks(nodelist, blocks = {})
47
- if nodelist && nodelist.any?
48
- 0.upto(nodelist.size - 1).each do |index|
49
- node = nodelist[index]
50
-
51
- if node.respond_to?(:call_super) # inherited block !
52
- new_node = node.class.clone_block(node)
53
-
54
- nodelist.insert(index, new_node)
55
- nodelist.delete_at(index + 1)
44
+ def parse_body(body, tokens)
45
+ body.parse(tokens, options) do |end_tag_name, end_tag_params|
46
+ @blank &&= body.blank?
56
47
 
57
- blocks[node.name] = new_node
58
- end
59
- if node.respond_to?(:nodelist)
60
- self.find_blocks(node.nodelist, blocks) # FIXME: find nested blocks too
61
- end
62
- end
48
+ # Note: extends does not require the "end tag".
49
+ return false if end_tag_name.nil?
63
50
  end
64
- blocks
65
- end
66
51
 
67
- private
52
+ true
53
+ end
68
54
 
69
55
  def parse_parent_template
70
- source = Template.file_system.read_template_file(@template_name)
71
- Template.parse(source)
56
+ source = Template.file_system.read_template_file(@template_name, {})
57
+ Template.parse(source, options)
72
58
  end
73
59
 
74
- def assert_missing_delimitation!
75
- end
76
60
  end
77
61
 
78
62
  Template.register_tag('extends', Extends)
79
- end
63
+ end