zafu 0.5.0 → 0.6.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.
data/lib/zafu/parser.rb CHANGED
@@ -9,8 +9,12 @@ module Zafu
9
9
  end
10
10
 
11
11
  class Parser
12
+ TEXT_CALLBACKS = %w{before_parse after_parse before_wrap wrap after_wrap}
13
+ PROCESS_CALLBACKS = %w{before_process expander process_unknown after_process}
14
+ CALLBACKS = TEXT_CALLBACKS + PROCESS_CALLBACKS
15
+
12
16
  @@callbacks = {}
13
- attr_accessor :text, :method, :pass, :options, :blocks, :ids, :defined_ids, :parent
17
+ attr_accessor :text, :method, :pass, :options, :blocks, :ids, :defined_ids, :parent, :errors
14
18
  # Method parameters "<r:show attr='name'/>" (params contains {'attr' => 'name'}).
15
19
  attr_accessor :params
16
20
 
@@ -18,7 +22,7 @@ module Zafu
18
22
  def new_with_url(path, opts={})
19
23
  helper = opts[:helper] || Zafu::MockHelper.new
20
24
  text, fullpath, base_path = self.get_template_text(path, helper)
21
- self.new(text, :helper => helper, :base_path => base_path, :included_history => [fullpath])
25
+ self.new(text, :helper => helper, :base_path => base_path, :included_history => [fullpath], :root => path)
22
26
  end
23
27
 
24
28
  # Retrieve the template text in the current folder or as an absolute path.
@@ -26,29 +30,103 @@ module Zafu
26
30
  def get_template_text(path, helper, base_path=nil)
27
31
  res = helper.send(:get_template_text, path, base_path)
28
32
  return [parser_error("template '#{path}' not found", 'include'), nil, nil] unless res
29
- text, fullpath, base_path = *res
30
- return res
33
+ res
31
34
  end
32
35
 
33
36
  def parser_error(message, method)
34
- "<span class='parser_error'><span class='method'>#{method}</span> #{message}</span>"
37
+ "<span class='parser_error'><span class='method'>#{method}: <span class='message'>#{message}</span></span>"
35
38
  end
36
39
 
37
- attr_accessor :before_process_callbacks
40
+ CALLBACKS.each do |clbk|
41
+ eval %Q{
42
+ attr_accessor :#{clbk}_callbacks
43
+
44
+ def #{clbk}_callbacks
45
+ @#{clbk}_callbacks ||= superclass.respond_to?(:#{clbk}_callbacks) ? superclass.#{clbk}_callbacks : []
46
+ end
38
47
 
39
- def before_process_callbacks
40
- @before_process_callbacks ||= []
48
+ def #{clbk}(*args)
49
+ self.#{clbk}_callbacks += args
50
+ end
51
+ }
41
52
  end
53
+ end # class << self
42
54
 
43
- def before_process(*args)
44
- self.before_process_callbacks += args
55
+ PROCESS_CALLBACKS.each do |clbk|
56
+ eval %Q{
57
+ def #{clbk}
58
+ self.class.#{clbk}_callbacks.each do |callback|
59
+ send(callback)
60
+ end
61
+ end
62
+ }
63
+ end
64
+
65
+ def expander
66
+ self.class.expander_callbacks.reverse_each do |callback|
67
+ if res = send(callback)
68
+ if res.kind_of?(String)
69
+ @result << res
70
+ end
71
+ return @result
72
+ end
45
73
  end
74
+ nil
75
+ end
76
+
77
+ TEXT_CALLBACKS.each do |clbk|
78
+ eval %Q{
79
+ def #{clbk}(text)
80
+ self.class.#{clbk}_callbacks.each do |callback|
81
+ text = send(callback, text)
82
+ end
83
+ text
84
+ end
85
+ }
86
+ end
87
+
88
+ # This method is called at the very beginning of the processing chain and is
89
+ # used to store state to make 'process' reintrant...
90
+ def save_state
91
+ {
92
+ :@context => @context, # <== we need this when rendering twice the same part
93
+ :@result => @result,
94
+ :@out_post => @out_post,
95
+ :@params => @params.dup,
96
+ :@method => @method,
97
+ }
98
+ end
99
+
100
+ # Restore state from a hash
101
+ def restore_state(saved)
102
+ saved.each do |key, value|
103
+ instance_variable_set(key, value)
104
+ end
105
+ end
106
+
107
+ def parser_error(message, method = @method)
108
+ @errors << "<span class='parser_error'><span class='method'>#{method}</span> <span class='message'>#{message}</span></span>"
109
+ nil
110
+ end
111
+
112
+ def process_unknown
113
+ self.class.process_unknown_callbacks.each do |callback|
114
+ if res = send(callback)
115
+ return res
116
+ end
117
+ end
118
+ @errors.empty? ? default_unknown : show_errors
119
+ end
120
+
121
+ def show_errors
122
+ @errors.join(' ')
46
123
  end
47
124
 
48
125
  def initialize(text, opts={})
49
126
  @stack = []
50
127
  @ok = true
51
128
  @blocks = []
129
+ @errors = []
52
130
 
53
131
  @options = {:mode=>:void, :method=>'void'}.merge(opts)
54
132
  @params = @options.delete(:params) || {}
@@ -65,16 +143,16 @@ module Zafu
65
143
  @text = before_parse(text)
66
144
  end
67
145
 
68
-
69
146
  start(mode)
70
147
 
71
148
  # set name
72
- @name ||= @options[:name] || @params[:id]
149
+ @name ||= extract_name
73
150
  @options[:ids][@name] = self if @name
74
151
 
75
152
  unless opts[:sub]
76
153
  @text = after_parse(@text)
77
154
  end
155
+
78
156
  @ids.keys.each do |k|
79
157
  if original_ids[k] != @ids[k]
80
158
  @defined_ids[k] = @ids[k]
@@ -83,6 +161,10 @@ module Zafu
83
161
  @ok
84
162
  end
85
163
 
164
+ def extract_name
165
+ @options[:name] || @params[:id]
166
+ end
167
+
86
168
  def to_erb(context)
87
169
  context[:helper] ||= @options[:helper]
88
170
  process(context)
@@ -96,7 +178,7 @@ module Zafu
96
178
  # This replaces the current object 'self' which is in the original included template, with the custom version 'obj'.
97
179
  def replace_with(obj)
98
180
  # keep @method (obj's method is always 'with')
99
- @blocks = obj.blocks.empty? ? @blocks : obj.blocks
181
+ @blocks = obj.blocks.empty? ? @blocks : obj.blocks
100
182
  @params.merge!(obj.params)
101
183
  end
102
184
 
@@ -110,6 +192,10 @@ module Zafu
110
192
  end
111
193
 
112
194
  def process(context={})
195
+ return '' if @method == 'ignore' || @method.blank?
196
+
197
+ @saved = save_state
198
+
113
199
  if @name
114
200
  # we pass the name as 'context' in the children tags
115
201
  @context = context.merge(:name => @name)
@@ -121,17 +207,31 @@ module Zafu
121
207
 
122
208
  before_process
123
209
 
124
- @pass = {} # used to pass information to the parent
125
- res = nil
210
+ @pass = {} # used to pass information to the parent (is this used ?)
211
+
212
+ res = expander || default_expander
213
+
214
+ res = before_wrap(res)
215
+ res = wrap(res + @out_post)
216
+
217
+ # @text contains unparsed data (empty space)
218
+ res = after_wrap(res) + @text
219
+
220
+ after_process
221
+
222
+ # restore state
223
+ restore_state(@saved)
224
+
225
+ res
226
+ end
126
227
 
228
+ # Default processing
229
+ def default_expander
127
230
  if respond_to?("r_#{@method}".to_sym)
128
- res = do_method("r_#{@method}".to_sym)
231
+ do_method("r_#{@method}".to_sym)
129
232
  else
130
- res = do_method(:r_unknown)
233
+ do_method(:process_unknown)
131
234
  end
132
-
133
- # @text contains unparsed data (empty space)
134
- after_process(res + @text)
135
235
  end
136
236
 
137
237
  def do_method(sym)
@@ -139,18 +239,15 @@ module Zafu
139
239
  if res.kind_of?(String)
140
240
  @result << res
141
241
  elsif @result.blank?
142
- @result << @method
242
+ @result << (@errors.blank? ? @method : show_errors)
143
243
  end
144
- @result + @out_post
244
+ @result
145
245
  end
146
246
 
147
247
  def r_void
148
248
  expand_with
149
249
  end
150
250
 
151
- def r_ignore
152
- end
153
-
154
251
  alias to_s r_void
155
252
 
156
253
  def r_inspect
@@ -161,7 +258,7 @@ module Zafu
161
258
  end
162
259
 
163
260
  # basic rule to display errors
164
- def r_unknown
261
+ def default_unknown
165
262
  sp = ""
166
263
  @params.each do |k,v|
167
264
  sp += " #{k}=#{v.inspect.gsub("'","TMPQUOTE").gsub('"',"'").gsub("TMPQUOTE",'"')}"
@@ -185,22 +282,8 @@ module Zafu
185
282
  expand_with(hash)
186
283
  end
187
284
 
188
- def before_process
189
- self.class.before_process_callbacks.each do |callback|
190
- self.send(callback)
191
- end
192
- end
193
-
194
- def after_process(text)
195
- text
196
- end
197
-
198
- def before_parse(text)
199
- text
200
- end
201
-
202
- def after_parse(text)
203
- text
285
+ def r_ignore
286
+ ''
204
287
  end
205
288
 
206
289
  def include_template
@@ -218,7 +301,7 @@ module Zafu
218
301
 
219
302
  included_text, absolute_url, base_path = self.class.get_template_text(@params[:template], @options[:helper], @options[:base_path])
220
303
 
221
- if absolute_url
304
+ if included_text
222
305
  absolute_url += "::#{@params[:part].gsub('/','_')}" if @params[:part]
223
306
  absolute_url += "??#{@options[:part].gsub('/','_')}" if @options[:part]
224
307
  if @options[:included_history].include?(absolute_url)
@@ -226,8 +309,11 @@ module Zafu
226
309
  else
227
310
  included_history = @options[:included_history] + [absolute_url]
228
311
  end
312
+ else
313
+ return "<span class='parser_error'>[include] template '#{@params[:template]}' not found</span>"
229
314
  end
230
- res = self.class.new(included_text, :helper => @options[:helper], :base_path => base_path, :included_history => included_history, :part => @params[:part]) # we set :part to avoid loop failure when doing self inclusion
315
+
316
+ res = self.class.new(included_text, :helper => @options[:helper], :base_path => base_path, :included_history => included_history, :part => @params[:part], :parent => self) # we set :part to avoid loop failure when doing self inclusion
231
317
 
232
318
  if @params[:part]
233
319
  if iblock = res.ids[@params[:part]]
@@ -245,7 +331,6 @@ module Zafu
245
331
  enter(:void) # normal scan on content
246
332
  # replace 'with'
247
333
 
248
- not_found = []
249
334
  @blocks.each do |b|
250
335
  next if b.kind_of?(String) || b.method != 'with'
251
336
  if target = res.ids[b.params[:part]]
@@ -258,10 +343,10 @@ module Zafu
258
343
  end
259
344
  else
260
345
  # part not found
261
- not_found << parser_error("'#{b.params[:part]}' not found in template '#{@params[:template]}'", 'with')
346
+ parser_error("'#{b.params[:part]}' not found in template '#{@params[:template]}'", 'with')
262
347
  end
263
348
  end
264
- @blocks = included_blocks + not_found
349
+ @blocks = included_blocks
265
350
  end
266
351
 
267
352
  # Return a hash of all descendants. Find a specific descendant with descendant['form'] for example.
@@ -289,6 +374,10 @@ module Zafu
289
374
  end.compact.flatten]
290
375
  end
291
376
 
377
+ def dynamic_blocks?
378
+ @blocks.detect { |b| !b.kind_of?(String) }
379
+ end
380
+
292
381
  def descendants(key)
293
382
  all_descendants[key] || []
294
383
  end
@@ -354,14 +443,14 @@ module Zafu
354
443
  def out(str)
355
444
  @result << str
356
445
  # Avoid double entry when this is the last call in a render method.
357
- nil
446
+ true
358
447
  end
359
448
 
360
449
  # Output ERB code that will be inserted after @result.
361
450
  def out_post(str)
362
451
  @out_post << str
363
452
  # Avoid double entry when this is the last call in a render method.
364
- nil
453
+ true
365
454
  end
366
455
 
367
456
  # Advance parser.
@@ -396,8 +485,8 @@ module Zafu
396
485
  custom_text = opts.delete(:text)
397
486
  end
398
487
  text = custom_text || @text
399
- opts = @options.merge(opts).merge(:sub=>true, :mode=>mode, :parent => self)
400
- new_obj = self.class.new(text,opts)
488
+ opts = @options.merge(opts).merge(:sub => true, :mode => mode, :parent => self)
489
+ new_obj = self.class.new(text, opts)
401
490
  if new_obj.success?
402
491
  @text = new_obj.text unless custom_text
403
492
  new_obj.text = ""
@@ -556,9 +645,5 @@ module Zafu
556
645
  end
557
646
  result + @text
558
647
  end
559
-
560
- def parser_error(message, method = @method)
561
- self.class.parser_error(message, method)
562
- end
563
648
  end # Parser
564
649
  end # Zafu
@@ -13,16 +13,21 @@ module Zafu
13
13
  # indentation information that should be used when rendered. This context is not inherited.
14
14
  attr_accessor :markup
15
15
 
16
+ # We need this flag to detect cases like <r:with part='list' do='other list finder'/>
17
+ attr_reader :sub_do
18
+
19
+ def self.included(base)
20
+ base.before_parse :remove_erb
21
+ base.before_process :unescape_ruby
22
+ end
23
+
16
24
  # This callback is run just after the block is initialized (Parser#initialize).
17
25
  def start(mode)
18
26
  # tag_context
19
27
  @markup = Markup.new(@options.delete(:html_tag))
20
28
 
21
29
  # html_tag
22
- @markup.params = @options.delete(:html_tag_params) # @html_tag_params
23
-
24
- # have we already wrapped the result with our tag ?
25
- @markup.done = false # @html_tag_done
30
+ @markup.params = @options.delete(:html_tag_params)
26
31
 
27
32
  # end_tag is used to know when to close parsing in sub-do
28
33
  # Example:
@@ -42,11 +47,14 @@ module Zafu
42
47
  # FIXME: what is this ???
43
48
  @options[:form] ||= true if @method == 'form'
44
49
 
45
- # puts "[#{@markup[:space_before]}(#{@method})#{@markup[:space_after]}]"
46
50
  if @params =~ /\A([^>]*?)do\s*=('|")([^\2]*?[^\\])\2([^>]*)\Z/
51
+ #puts $~.to_a.inspect
47
52
  # we have a sub 'do'
53
+
48
54
  @params = Markup.parse_params($1)
49
- @sub_do = $3 # this is used by replace_with (FIXME)
55
+
56
+ # We need this flag to detect cases cases like <r:with part='list' do='other list finder'/>
57
+ @sub_do = true
50
58
 
51
59
  opts = {:method=>$3, :params=>$4}
52
60
 
@@ -61,14 +69,14 @@ module Zafu
61
69
  end
62
70
 
63
71
  # set name used for include/replace from html_tag if not allready set by superclass
64
- @name = @options[:name] || @params[:name] || @params[:id] || @markup.params[:id]
72
+ @name = extract_name
65
73
 
66
74
  if !@markup.tag && (@markup.tag = @params.delete(:tag))
67
75
  # Extract html tag parameters from @params
68
76
  @markup.steal_html_params_from(@params)
69
77
  end
70
78
 
71
- if @method == 'include'
79
+ if @method == 'include' && @params[:template]
72
80
  include_template
73
81
  elsif mode == :tag && !sub
74
82
  scan_tag
@@ -77,10 +85,36 @@ module Zafu
77
85
  end
78
86
  end
79
87
 
80
- def before_parse(text)
88
+ def extract_name
89
+ super ||
90
+ (%w{input select textarea}.include?(@method) ? nil : @params[:name]) ||
91
+ @markup.params[:id]
92
+ end
93
+
94
+ def remove_erb(text)
81
95
  text.gsub('<%', '&lt;%').gsub('%>', '%&gt;')
82
96
  end
83
97
 
98
+ def unescape_ruby
99
+ @params.each do |k,v|
100
+ v.gsub!('&gt;', '>')
101
+ v.gsub!('&lt;', '<')
102
+ end
103
+ @method.gsub!('&gt;', '>')
104
+ @method.gsub!('&lt;', '<')
105
+ end
106
+
107
+ def single_child_method
108
+ return @single_child_method if defined?(@single_child_method)
109
+ @single_child_method = if @blocks.size == 1
110
+ single_child = @blocks[0]
111
+ return nil if single_child.kind_of?(String)
112
+ single_child.markup.tag ? nil : single_child.method
113
+ else
114
+ nil
115
+ end
116
+ end
117
+
84
118
  # scan rules
85
119
  def scan
86
120
  # puts "SCAN(#{@method}): [#{@text}]"
@@ -110,11 +144,8 @@ module Zafu
110
144
  if $1 == @end_tag
111
145
  @end_tag_count -= 1
112
146
  if @end_tag_count == 0
113
- if @end_tag == 'script'
114
- flush $& # keep closing tag
115
- else
116
- eat $&
117
- end
147
+ eat $&
148
+
118
149
  @markup.space_after = $2
119
150
  leave
120
151
  else
@@ -162,7 +193,7 @@ module Zafu
162
193
  opts.merge!(:text=>'') if $3 != ''
163
194
  make(:void, opts)
164
195
  elsif @text =~ /\A<(\w+)([^>]*?)do\s*=('([^>]*?[^\\]|)'|"([^>]*?[^\\]|)")([^>]*?)(\/?)>/
165
- #puts "DO:#{$~.to_a.inspect}" # do tag
196
+ #puts "DO:#{($4||$5).inspect}" # do tag
166
197
  eat $&
167
198
  opts.merge!(:method=>($4||$5), :html_tag=>$1, :html_tag_params=>$2, :params=>$6)
168
199
  opts.merge!(:text=>'') if $7 != ''
@@ -216,7 +247,7 @@ module Zafu
216
247
  @method = 'rename_asset'
217
248
  @markup.tag = @end_tag = $1
218
249
  closed = ($3 != '')
219
- @params = parse_params($2)
250
+ @params = Markup.parse_params($2)
220
251
  if closed
221
252
  leave(:asset)
222
253
  elsif @markup.tag == 'script'
@@ -233,7 +264,8 @@ module Zafu
233
264
 
234
265
  def scan_inside_asset
235
266
  if @text =~ /\A(.*?)<\/#{@end_tag}>/m
236
- flush $&
267
+ eat $&
268
+ store $1
237
269
  leave(:asset)
238
270
  else
239
271
  # never ending asset