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/History.txt +11 -0
- data/Rakefile +2 -1
- data/lib/zafu/all.rb +6 -4
- data/lib/zafu/controller_methods.rb +27 -18
- data/lib/zafu/info.rb +1 -1
- data/lib/zafu/markup.rb +87 -25
- data/lib/zafu/node_context.rb +48 -4
- data/lib/zafu/ordered_hash.rb +47 -0
- data/lib/zafu/parser.rb +140 -55
- data/lib/zafu/parsing_rules.rb +49 -17
- data/lib/zafu/process/ajax.rb +344 -66
- data/lib/zafu/process/conditional.rb +36 -25
- data/lib/zafu/process/context.rb +101 -4
- data/lib/zafu/process/forms.rb +124 -0
- data/lib/zafu/process/html.rb +78 -83
- data/lib/zafu/process/ruby_less_processing.rb +248 -0
- data/lib/zafu/test_helper.rb +1 -1
- data/lib/zafu/view_methods.rb +6 -0
- data/test/markup_test.rb +150 -8
- data/test/mock/classes.rb +24 -0
- data/test/mock/core_ext.rb +9 -0
- data/test/mock/params.rb +4 -10
- data/test/mock/process.rb +7 -0
- data/test/mock/test_compiler.rb +9 -0
- data/test/node_context_test.rb +135 -46
- data/test/ordered_hash_test.rb +69 -0
- data/test/ruby_less_test.rb +29 -19
- data/test/test_helper.rb +4 -3
- data/test/zafu/ajax.yml +7 -0
- data/test/zafu/asset.yml +3 -0
- data/test/zafu/basic.yml +3 -0
- data/test/zafu/markup.yml +4 -0
- data/test/zafu/meta.yml +8 -0
- data/test/zafu/security.yml +19 -0
- data/test/zafu_test.rb +29 -12
- data/zafu.gemspec +28 -6
- metadata +41 -8
- data/lib/zafu/process/ruby_less.rb +0 -145
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
|
-
|
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}
|
37
|
+
"<span class='parser_error'><span class='method'>#{method}: <span class='message'>#{message}</span></span>"
|
35
38
|
end
|
36
39
|
|
37
|
-
|
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
|
-
|
40
|
-
|
48
|
+
def #{clbk}(*args)
|
49
|
+
self.#{clbk}_callbacks += args
|
50
|
+
end
|
51
|
+
}
|
41
52
|
end
|
53
|
+
end # class << self
|
42
54
|
|
43
|
-
|
44
|
-
|
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
|
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
|
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
|
125
|
-
|
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
|
-
|
231
|
+
do_method("r_#{@method}".to_sym)
|
129
232
|
else
|
130
|
-
|
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
|
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
|
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
|
189
|
-
|
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
|
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
|
-
|
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
|
-
|
346
|
+
parser_error("'#{b.params[:part]}' not found in template '#{@params[:template]}'", 'with')
|
262
347
|
end
|
263
348
|
end
|
264
|
-
@blocks = included_blocks
|
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
|
-
|
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
|
-
|
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
|
data/lib/zafu/parsing_rules.rb
CHANGED
@@ -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)
|
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
|
-
|
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 =
|
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
|
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('<%', '<%').gsub('%>', '%>')
|
82
96
|
end
|
83
97
|
|
98
|
+
def unescape_ruby
|
99
|
+
@params.each do |k,v|
|
100
|
+
v.gsub!('>', '>')
|
101
|
+
v.gsub!('<', '<')
|
102
|
+
end
|
103
|
+
@method.gsub!('>', '>')
|
104
|
+
@method.gsub!('<', '<')
|
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
|
-
|
114
|
-
|
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:#{
|
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
|
-
|
267
|
+
eat $&
|
268
|
+
store $1
|
237
269
|
leave(:asset)
|
238
270
|
else
|
239
271
|
# never ending asset
|