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
@@ -46,37 +46,46 @@ module Liquid
46
46
  class For < Block
47
47
  Syntax = /\A(#{VariableSegment}+)\s+in\s+(#{QuotedFragment}+)\s*(reversed)?/o
48
48
 
49
- def initialize(tag_name, markup, tokens, options)
50
- parse_with_selected_parser(markup)
51
- @nodelist = @for_block = []
49
+ def initialize(tag_name, markup, options)
52
50
  super
51
+ parse_with_selected_parser(markup)
52
+ @for_block = BlockBody.new
53
+ end
54
+
55
+ def parse(tokens)
56
+ if more = parse_body(@for_block, tokens)
57
+ parse_body(@else_block, tokens)
58
+ end
59
+ end
60
+
61
+ def nodelist
62
+ @else_block ? [@for_block, @else_block] : [@for_block]
53
63
  end
54
64
 
55
65
  def unknown_tag(tag, markup, tokens)
56
- return super unless tag == 'else'
57
- @nodelist = @else_block = []
66
+ return super unless tag == 'else'.freeze
67
+ @else_block = BlockBody.new
58
68
  end
59
69
 
60
70
  def render(context)
61
71
  context.registers[:for] ||= Hash.new(0)
62
72
 
63
- collection = context[@collection_name]
73
+ collection = context.evaluate(@collection_name)
64
74
  collection = collection.to_a if collection.is_a?(Range)
65
75
 
66
76
  # Maintains Ruby 1.8.7 String#each behaviour on 1.9
67
77
  return render_else(context) unless iterable?(collection)
68
78
 
69
- from = if @attributes['offset'] == 'continue'
79
+ from = if @from == :continue
70
80
  context.registers[:for][@name].to_i
71
81
  else
72
- context[@attributes['offset']].to_i
82
+ context.evaluate(@from).to_i
73
83
  end
74
84
 
75
- limit = context[@attributes['limit']]
85
+ limit = context.evaluate(@limit)
76
86
  to = limit ? limit.to_i + from : nil
77
87
 
78
-
79
- segment = Utils.slice_collection_using_each(collection, from, to)
88
+ segment = Utils.slice_collection(collection, from, to)
80
89
 
81
90
  return render_else(context) if segment.empty?
82
91
 
@@ -92,17 +101,18 @@ module Liquid
92
101
  context.stack do
93
102
  segment.each_with_index do |item, index|
94
103
  context[@variable_name] = item
95
- context['forloop'] = {
96
- 'name' => @name,
97
- 'length' => length,
98
- 'index' => index + 1,
99
- 'index0' => index,
100
- 'rindex' => length - index,
101
- 'rindex0' => length - index - 1,
102
- 'first' => (index == 0),
103
- 'last' => (index == length - 1) }
104
-
105
- result << render_all(@for_block, context)
104
+ context['forloop'.freeze] = {
105
+ 'name'.freeze => @name,
106
+ 'length'.freeze => length,
107
+ 'index'.freeze => index + 1,
108
+ 'index0'.freeze => index,
109
+ 'rindex'.freeze => length - index,
110
+ 'rindex0'.freeze => length - index - 1,
111
+ 'first'.freeze => (index == 0),
112
+ 'last'.freeze => (index == length - 1)
113
+ }
114
+
115
+ result << @for_block.render(context)
106
116
 
107
117
  # Handle any interrupts if they exist.
108
118
  if context.has_interrupt?
@@ -120,48 +130,60 @@ module Liquid
120
130
  def lax_parse(markup)
121
131
  if markup =~ Syntax
122
132
  @variable_name = $1
123
- @collection_name = $2
124
- @name = "#{$1}-#{$2}"
133
+ collection_name = $2
125
134
  @reversed = $3
126
- @attributes = {}
135
+ @name = "#{@variable_name}-#{collection_name}"
136
+ @collection_name = Expression.parse(collection_name)
127
137
  markup.scan(TagAttributes) do |key, value|
128
- @attributes[key] = value
138
+ set_attribute(key, value)
129
139
  end
130
140
  else
131
- raise SyntaxError.new(options[:locale].t("errors.syntax.for"), line)
141
+ raise SyntaxError.new(options[:locale].t("errors.syntax.for".freeze))
132
142
  end
133
143
  end
134
144
 
135
145
  def strict_parse(markup)
136
146
  p = Parser.new(markup)
137
147
  @variable_name = p.consume(:id)
138
- raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in"), line) unless p.id?('in')
139
- @collection_name = p.expression
140
- @name = "#{@variable_name}-#{@collection_name}"
141
- @reversed = p.id?('reversed')
148
+ raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_in".freeze)) unless p.id?('in'.freeze)
149
+ collection_name = p.expression
150
+ @name = "#{@variable_name}-#{collection_name}"
151
+ @collection_name = Expression.parse(collection_name)
152
+ @reversed = p.id?('reversed'.freeze)
142
153
 
143
- @attributes = {}
144
154
  while p.look(:id) && p.look(:colon, 1)
145
- unless attribute = p.id?('limit') || p.id?('offset')
146
- raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute"), line)
155
+ unless attribute = p.id?('limit'.freeze) || p.id?('offset'.freeze)
156
+ raise SyntaxError.new(options[:locale].t("errors.syntax.for_invalid_attribute".freeze))
147
157
  end
148
158
  p.consume
149
- val = p.expression
150
- @attributes[attribute] = val
159
+ set_attribute(attribute, p.expression)
151
160
  end
152
161
  p.consume(:end_of_string)
153
162
  end
154
163
 
155
164
  private
156
165
 
157
- def render_else(context)
158
- return @else_block ? [render_all(@else_block, context)] : ''
166
+ def set_attribute(key, expr)
167
+ case key
168
+ when 'offset'.freeze
169
+ @from = if expr == 'continue'.freeze
170
+ :continue
171
+ else
172
+ Expression.parse(expr)
173
+ end
174
+ when 'limit'.freeze
175
+ @limit = Expression.parse(expr)
159
176
  end
177
+ end
160
178
 
161
- def iterable?(collection)
162
- collection.respond_to?(:each) || Utils.non_blank_string?(collection)
163
- end
179
+ def render_else(context)
180
+ @else_block ? @else_block.render(context) : ''.freeze
181
+ end
182
+
183
+ def iterable?(collection)
184
+ collection.respond_to?(:each) || Utils.non_blank_string?(collection)
185
+ end
164
186
  end
165
187
 
166
- Template.register_tag('for', For)
188
+ Template.register_tag('for'.freeze, For)
167
189
  end
@@ -12,15 +12,25 @@ module Liquid
12
12
  class If < Block
13
13
  Syntax = /(#{QuotedFragment})\s*([=!<>a-z_]+)?\s*(#{QuotedFragment})?/o
14
14
  ExpressionsAndOperators = /(?:\b(?:\s?and\s?|\s?or\s?)\b|(?:\s*(?!\b(?:\s?and\s?|\s?or\s?)\b)(?:#{QuotedFragment}|\S+)\s*)+)/o
15
+ BOOLEAN_OPERATORS = %w(and or)
15
16
 
16
- def initialize(tag_name, markup, tokens, options)
17
- @blocks = []
18
- push_block('if', markup)
17
+ def initialize(tag_name, markup, options)
19
18
  super
19
+ @blocks = []
20
+ push_block('if'.freeze, markup)
21
+ end
22
+
23
+ def parse(tokens)
24
+ while more = parse_body(@blocks.last.attachment, tokens)
25
+ end
26
+ end
27
+
28
+ def nodelist
29
+ @blocks.map(&:attachment)
20
30
  end
21
31
 
22
32
  def unknown_tag(tag, markup, tokens)
23
- if ['elsif', 'else'].include?(tag)
33
+ if ['elsif'.freeze, 'else'.freeze].include?(tag)
24
34
  push_block(tag, markup)
25
35
  else
26
36
  super
@@ -31,39 +41,40 @@ module Liquid
31
41
  context.stack do
32
42
  @blocks.each do |block|
33
43
  if block.evaluate(context)
34
- return render_all(block.attachment, context)
44
+ return block.attachment.render(context)
35
45
  end
36
46
  end
37
- ''
47
+ ''.freeze
38
48
  end
39
49
  end
40
50
 
41
51
  private
42
52
 
43
53
  def push_block(tag, markup)
44
- block = if tag == 'else'
54
+ block = if tag == 'else'.freeze
45
55
  ElseCondition.new
46
56
  else
47
57
  parse_with_selected_parser(markup)
48
58
  end
49
59
 
50
60
  @blocks.push(block)
51
- @nodelist = block.attach(Array.new)
61
+ block.attach(BlockBody.new)
52
62
  end
53
63
 
54
64
  def lax_parse(markup)
55
- expressions = markup.scan(ExpressionsAndOperators).reverse
56
- raise(SyntaxError.new(options[:locale].t("errors.syntax.if")), line) unless expressions.shift =~ Syntax
65
+ expressions = markup.scan(ExpressionsAndOperators)
66
+ raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop =~ Syntax
57
67
 
58
- condition = Condition.new($1, $2, $3)
68
+ condition = Condition.new(Expression.parse($1), $2, Expression.parse($3))
59
69
 
60
70
  while not expressions.empty?
61
- operator = (expressions.shift).to_s.strip
71
+ operator = expressions.pop.to_s.strip
62
72
 
63
- raise(SyntaxError.new(options[:locale].t("errors.syntax.if")), line) unless expressions.shift.to_s =~ Syntax
73
+ raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless expressions.pop.to_s =~ Syntax
64
74
 
65
- new_condition = Condition.new($1, $2, $3)
66
- new_condition.send(operator.to_sym, condition)
75
+ new_condition = Condition.new(Expression.parse($1), $2, Expression.parse($3))
76
+ raise(SyntaxError.new(options[:locale].t("errors.syntax.if".freeze))) unless BOOLEAN_OPERATORS.include?(operator)
77
+ new_condition.send(operator, condition)
67
78
  condition = new_condition
68
79
  end
69
80
 
@@ -75,7 +86,7 @@ module Liquid
75
86
 
76
87
  condition = parse_comparison(p)
77
88
 
78
- while op = (p.id?('and') || p.id?('or'))
89
+ while op = (p.id?('and'.freeze) || p.id?('or'.freeze))
79
90
  new_cond = parse_comparison(p)
80
91
  new_cond.send(op, condition)
81
92
  condition = new_cond
@@ -86,9 +97,9 @@ module Liquid
86
97
  end
87
98
 
88
99
  def parse_comparison(p)
89
- a = p.expression
100
+ a = Expression.parse(p.expression)
90
101
  if op = p.consume?(:comparison)
91
- b = p.expression
102
+ b = Expression.parse(p.expression)
92
103
  Condition.new(a, op, b)
93
104
  else
94
105
  Condition.new(a)
@@ -96,5 +107,5 @@ module Liquid
96
107
  end
97
108
  end
98
109
 
99
- Template.register_tag('if', If)
110
+ Template.register_tag('if'.freeze, If)
100
111
  end
@@ -4,17 +4,17 @@ module Liquid
4
4
  def render(context)
5
5
  context.stack do
6
6
 
7
- output = render_all(@nodelist, context)
7
+ output = super
8
8
 
9
9
  if output != context.registers[:ifchanged]
10
10
  context.registers[:ifchanged] = output
11
11
  output
12
12
  else
13
- ''
13
+ ''.freeze
14
14
  end
15
15
  end
16
16
  end
17
17
  end
18
18
 
19
- Template.register_tag('ifchanged', Ifchanged)
20
- end
19
+ Template.register_tag('ifchanged'.freeze, Ifchanged)
20
+ end
@@ -17,47 +17,47 @@ module Liquid
17
17
  class Include < Tag
18
18
  Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
19
19
 
20
- def initialize(tag_name, markup, tokens, options)
20
+ def initialize(tag_name, markup, options)
21
+ super
22
+
21
23
  if markup =~ Syntax
22
24
 
23
- @template_name = $1
24
- @variable_name = $3
25
+ template_name = $1
26
+ variable_name = $3
27
+
28
+ @variable_name = Expression.parse(variable_name || template_name[1..-2])
29
+ @context_variable_name = template_name[1..-2].split('/'.freeze).last
30
+ @template_name = Expression.parse(template_name)
25
31
  @attributes = {}
26
32
 
27
33
  markup.scan(TagAttributes) do |key, value|
28
- @attributes[key] = value
34
+ @attributes[key] = Expression.parse(value)
29
35
  end
30
36
 
31
37
  else
32
- raise SyntaxError.new(options[:locale].t("errors.syntax.include"), options[:line])
38
+ raise SyntaxError.new(options[:locale].t("errors.syntax.include".freeze))
33
39
  end
34
-
35
- super
36
40
  end
37
41
 
38
42
  def parse(tokens)
39
43
  end
40
44
 
41
- def blank?
42
- false
43
- end
44
-
45
45
  def render(context)
46
46
  partial = load_cached_partial(context)
47
- variable = context[@variable_name || @template_name[1..-2]]
47
+ variable = context.evaluate(@variable_name)
48
48
 
49
49
  context.stack do
50
50
  @attributes.each do |key, value|
51
- context[key] = context[value]
51
+ context[key] = context.evaluate(value)
52
52
  end
53
53
 
54
54
  if variable.is_a?(Array)
55
55
  variable.collect do |var|
56
- context[@template_name[1..-2]] = var
56
+ context[@context_variable_name] = var
57
57
  partial.render(context)
58
58
  end
59
59
  else
60
- context[@template_name[1..-2]] = variable
60
+ context[@context_variable_name] = variable
61
61
  partial.render(context)
62
62
  end
63
63
  end
@@ -66,13 +66,13 @@ module Liquid
66
66
  private
67
67
  def load_cached_partial(context)
68
68
  cached_partials = context.registers[:cached_partials] || {}
69
- template_name = context[@template_name]
69
+ template_name = context.evaluate(@template_name)
70
70
 
71
71
  if cached = cached_partials[template_name]
72
72
  return cached
73
73
  end
74
74
  source = read_template_from_file_system(context)
75
- partial = Liquid::Template.parse(source)
75
+ partial = Liquid::Template.parse(source, pass_options)
76
76
  cached_partials[template_name] = partial
77
77
  context.registers[:cached_partials] = cached_partials
78
78
  partial
@@ -84,14 +84,24 @@ module Liquid
84
84
  # make read_template_file call backwards-compatible.
85
85
  case file_system.method(:read_template_file).arity
86
86
  when 1
87
- file_system.read_template_file(context[@template_name])
87
+ file_system.read_template_file(context.evaluate(@template_name))
88
88
  when 2
89
- file_system.read_template_file(context[@template_name], context)
89
+ file_system.read_template_file(context.evaluate(@template_name), context)
90
90
  else
91
91
  raise ArgumentError, "file_system.read_template_file expects two parameters: (template_name, context)"
92
92
  end
93
93
  end
94
+
95
+ def pass_options
96
+ dont_pass = @options[:include_options_blacklist]
97
+ return {locale: @options[:locale]} if dont_pass == true
98
+ opts = @options.merge(included: true, include_options_blacklist: false)
99
+ if dont_pass.is_a?(Array)
100
+ dont_pass.each {|o| opts.delete(o)}
101
+ end
102
+ opts
103
+ end
94
104
  end
95
105
 
96
- Template.register_tag('include', Include)
106
+ Template.register_tag('include'.freeze, Include)
97
107
  end
@@ -15,9 +15,9 @@ module Liquid
15
15
  # Hello: 2
16
16
  #
17
17
  class Increment < Tag
18
- def initialize(tag_name, markup, tokens, options)
19
- @variable = markup.strip
18
+ def initialize(tag_name, markup, options)
20
19
  super
20
+ @variable = markup.strip
21
21
  end
22
22
 
23
23
  def render(context)
@@ -25,12 +25,7 @@ module Liquid
25
25
  context.environments.first[@variable] = value + 1
26
26
  value.to_s
27
27
  end
28
-
29
-
30
- def blank?
31
- false
32
- end
33
28
  end
34
29
 
35
- Template.register_tag('increment', Increment)
30
+ Template.register_tag('increment'.freeze, Increment)
36
31
  end
@@ -7,95 +7,93 @@ module Liquid
7
7
  # {% block content }Hello world{% endblock %}
8
8
  #
9
9
  class InheritedBlock < Block
10
- Syntax = /(#{QuotedFragment}+)/
10
+ Syntax = /(#{QuotedFragment}+)/o
11
11
 
12
- attr_accessor :parent
13
12
  attr_reader :name
14
13
 
15
- def initialize(tag_name, markup, tokens, options)
14
+ # linked chain of inherited blocks included
15
+ # in different templates if multiple extends
16
+ attr_accessor :parent, :descendant
17
+
18
+ def initialize(tag_name, markup, options)
19
+ super
20
+
16
21
  if markup =~ Syntax
17
22
  @name = $1.gsub(/["']/o, '').strip
18
23
  else
19
24
  raise(SyntaxError.new(options[:locale].t("errors.syntax.block")), options[:line])
20
25
  end
21
26
 
22
- self.set_full_name!(options)
23
-
24
- (options[:block_stack] ||= []).push(self)
25
- options[:current_block] = self
26
-
27
- super if tokens
27
+ prepare_for_inheritance
28
28
  end
29
29
 
30
- def render(context)
31
- context.stack do
32
- context['block'] = InheritedBlockDrop.new(self)
33
- render_all(@nodelist, context)
30
+ def prepare_for_inheritance
31
+ # give a different name if this is a nested block
32
+ if block = options[:inherited_blocks][:nested].last
33
+ @name = "#{block.name}/#{@name}"
34
34
  end
35
- end
36
35
 
37
- def end_tag
38
- self.register_current_block
36
+ # append this block to the stack in order to
37
+ # get a name for the other nested inherited blocks
38
+ options[:inherited_blocks][:nested].push(self)
39
39
 
40
- options[:block_stack].pop
41
- options[:current_block] = options[:block_stack].last
42
- end
40
+ # build the linked chain of inherited blocks
41
+ # make a link with the descendant and the parent (chained list)
42
+ if descendant = options[:inherited_blocks][:all][@name]
43
+ self.descendant = descendant
44
+ descendant.parent = self
43
45
 
44
- def call_super(context)
45
- if parent
46
- parent.render(context)
47
- else
48
- ''
46
+ # get the value of the blank property from the descendant
47
+ @blank = descendant.blank? #false
49
48
  end
50
- end
51
49
 
52
- def self.clone_block(block)
53
- new_block = self.new(block.send(:instance_variable_get, :"@tag_name"), block.name, nil, {})
54
- new_block.parent = block.parent
55
- new_block.nodelist = block.nodelist
56
- new_block
50
+ # become the descendant of the inherited block from the parent template
51
+ options[:inherited_blocks][:all][@name] = self
57
52
  end
58
53
 
59
- protected
54
+ def parse(tokens)
55
+ super
60
56
 
61
- def set_full_name!(options)
62
- if options[:current_block]
63
- @name = options[:current_block].name + '/' + @name
64
- end
57
+ # when the parsing of the block is done, we can then remove it from the stack
58
+ options[:inherited_blocks][:nested].pop
65
59
  end
66
60
 
67
- def register_current_block
68
- options[:blocks] ||= {}
61
+ alias_method :render_without_inheritance, :render
69
62
 
70
- block = options[:blocks][@name]
63
+ def render(context)
64
+ context.stack do
65
+ # look for the very first descendant
66
+ block = self_or_first_descendant
71
67
 
72
- if block
73
- # copy the existing block in order to make it a parent of the parsed block
74
- new_block = self.class.clone_block(block)
68
+ if block != self
69
+ # the block drop is in charge of rendering "{{ block.super }}"
70
+ context['block'] = InheritedBlockDrop.new(block)
71
+ end
75
72
 
76
- # replace the up-to-date version of the block in the parent template
77
- block.parent = new_block
78
- block.nodelist = @nodelist
73
+ block.render_without_inheritance(context)
79
74
  end
80
75
  end
81
76
 
82
- end
83
-
84
- class InheritedBlockDrop < Drop
85
-
86
- def initialize(block)
87
- @block = block
77
+ # when we render an inherited block, we need the version of the
78
+ # very first descendant.
79
+ def self_or_first_descendant
80
+ block = self
81
+ while block.descendant; block = block.descendant; end
82
+ block
88
83
  end
89
84
 
90
- def name
91
- @block.name
92
- end
85
+ def call_super(context)
86
+ if parent
87
+ # remove the block from the linked chain
88
+ parent.descendant = nil
93
89
 
94
- def super
95
- @block.call_super(@context)
90
+ parent.render(context)
91
+ else
92
+ ''
93
+ end
96
94
  end
97
95
 
98
96
  end
99
97
 
100
98
  Template.register_tag('block', InheritedBlock)
101
- end
99
+ end