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
@@ -0,0 +1,248 @@
|
|
1
|
+
require 'rubyless'
|
2
|
+
|
3
|
+
module Zafu
|
4
|
+
module Process
|
5
|
+
module RubyLessProcessing
|
6
|
+
include RubyLess
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.process_unknown :rubyless_eval
|
10
|
+
|
11
|
+
base.class_eval do
|
12
|
+
def do_method(sym)
|
13
|
+
super
|
14
|
+
rescue RubyLess::Error => err
|
15
|
+
self.class.parser_error(err.message, @method)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Actual method resolution. The lookup first starts in the current helper. If nothing is found there, it
|
21
|
+
# searches inside a 'helpers' module and finally looks into the current node_context.
|
22
|
+
# If nothing is found at this stage, we prepend the method with the current node and start over again.
|
23
|
+
def safe_method_type(signature)
|
24
|
+
#puts [node.name, node.klass, signature].inspect
|
25
|
+
super || get_method_type(signature, false)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Resolve unknown methods by using RubyLess in the current compilation context (the
|
29
|
+
# translate method in RubyLess will call 'safe_method_type' in this module).
|
30
|
+
def rubyless_eval
|
31
|
+
if @method =~ /^[A-Z]\w+$/
|
32
|
+
return rubyless_class_scope(@method)
|
33
|
+
end
|
34
|
+
|
35
|
+
if code = @method[/^\#\{(.+)\}$/, 1]
|
36
|
+
@params[:eval] = $1
|
37
|
+
r_show
|
38
|
+
else
|
39
|
+
rubyless_render(@method, @params)
|
40
|
+
end
|
41
|
+
rescue RubyLess::NoMethodError => err
|
42
|
+
parser_error("#{err.error_message} <span class='type'>#{err.method_with_arguments}</span> (#{node.class_name} context)")
|
43
|
+
rescue RubyLess::Error => err
|
44
|
+
parser_error(err.message)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Print documentation on the current node type.
|
48
|
+
def r_m
|
49
|
+
if @params[:helper] == 'true'
|
50
|
+
klass = helper.class
|
51
|
+
else
|
52
|
+
klass = node.klass
|
53
|
+
end
|
54
|
+
|
55
|
+
out "<div class='rubyless-m'><h3>Documentation for <b>#{klass}</b></h3>"
|
56
|
+
out "<ul>"
|
57
|
+
RubyLess::SafeClass.safe_methods_for(klass).each do |signature, opts|
|
58
|
+
opts = opts.dup
|
59
|
+
opts.delete(:method)
|
60
|
+
if opts.keys == [:class]
|
61
|
+
opts = opts[:class]
|
62
|
+
end
|
63
|
+
out "<li>#{signature.inspect} => #{opts.inspect}</li>"
|
64
|
+
end
|
65
|
+
out "</ul></div>"
|
66
|
+
end
|
67
|
+
|
68
|
+
# TEMPORARY METHOD DURING HACKING...
|
69
|
+
def r_erb
|
70
|
+
"<pre><%= @erb.gsub('<','<').gsub('>','>') %></pre>"
|
71
|
+
end
|
72
|
+
|
73
|
+
def rubyless_render(method, params)
|
74
|
+
# We need to set this here because we cannot pass options to RubyLess or get them back
|
75
|
+
# when we evaluate the method to see if we can use blocks as arguments.
|
76
|
+
@rendering_block_owner = true
|
77
|
+
code = method_with_arguments(method, params)
|
78
|
+
rubyless_expand RubyLess.translate(code, self)
|
79
|
+
ensure
|
80
|
+
@rendering_block_owner = false
|
81
|
+
end
|
82
|
+
|
83
|
+
def set_markup_attr(markup, key, value)
|
84
|
+
value = value.kind_of?(RubyLess::TypedString) ? value : RubyLess.translate_string(value, self)
|
85
|
+
if value.literal
|
86
|
+
markup.set_param(key, value.literal)
|
87
|
+
else
|
88
|
+
markup.set_dyn_param(key, "<%= #{value} %>")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def append_markup_attr(markup, key, value)
|
93
|
+
value = RubyLess.translate_string(value, self)
|
94
|
+
if value.literal
|
95
|
+
markup.append_param(key, value.literal)
|
96
|
+
else
|
97
|
+
markup.append_dyn_param(key, "<%= #{value} %>")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
# block_owner should be set to true when we are resolving <r:xxx>...</r:xxx> or <div do='xxx'>...</div>
|
103
|
+
def get_method_type(signature, added_options = false)
|
104
|
+
if type = node_context_from_signature(signature)
|
105
|
+
# Resolve self, @page, @node
|
106
|
+
type
|
107
|
+
elsif type = get_var_from_signature(signature)
|
108
|
+
# Resolved stored set_xxx='something' in context.
|
109
|
+
type
|
110
|
+
elsif type = safe_method_from(helper, signature)
|
111
|
+
# Resolve template helper methods
|
112
|
+
type
|
113
|
+
elsif helper.respond_to?(:helpers) && type = safe_method_from(helper.helpers, signature)
|
114
|
+
# Resolve by looking at the included helpers
|
115
|
+
type
|
116
|
+
elsif node && node.klass.kind_of?(Class) && type = safe_method_from(node.klass, signature)
|
117
|
+
# Resolve node context methods: xxx.foo, xxx.bar
|
118
|
+
type.merge(:method => "#{node.name}.#{type[:method]}")
|
119
|
+
elsif node && node.klass.kind_of?(Array) && type = safe_method_from(node.klass.first, signature)
|
120
|
+
type.merge(:method => "#{node.name}.first.#{type[:method]}")
|
121
|
+
elsif @rendering_block_owner && @blocks.first.kind_of?(String) && !added_options
|
122
|
+
# Insert the block content into the method: <r:trans>blah</r:trans> becomes trans("blah")
|
123
|
+
signature_with_block = signature.dup
|
124
|
+
signature_with_block << String
|
125
|
+
if type = get_method_type(signature_with_block, true)
|
126
|
+
type.merge(:prepend_args => RubyLess::TypedString.new(@blocks.first.inspect, :class => String, :literal => @blocks.first))
|
127
|
+
else
|
128
|
+
nil
|
129
|
+
end
|
130
|
+
elsif node && !added_options
|
131
|
+
# Try prepending current node before arguments: link("foo") becomes link(var1, "foo")
|
132
|
+
signature_with_node = signature.dup
|
133
|
+
signature_with_node.insert(1, node.klass)
|
134
|
+
if type = get_method_type(signature_with_node, added_options = true)
|
135
|
+
type.merge(:prepend_args => RubyLess::TypedString.new(node.name, :class => node.klass))
|
136
|
+
else
|
137
|
+
nil
|
138
|
+
end
|
139
|
+
else
|
140
|
+
nil
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def method_with_arguments(method, params)
|
145
|
+
hash_arguments = []
|
146
|
+
arguments = []
|
147
|
+
params.keys.sort {|a,b| a.to_s <=> b.to_s}.each do |k|
|
148
|
+
if k =~ /\A_/
|
149
|
+
arguments << "%Q{#{params[k]}}"
|
150
|
+
else
|
151
|
+
hash_arguments << ":#{k} => %Q{#{params[k]}}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
if hash_arguments != []
|
156
|
+
arguments << hash_arguments.join(', ')
|
157
|
+
end
|
158
|
+
|
159
|
+
if arguments != []
|
160
|
+
if method =~ /^(.*)\((.*)\)$/
|
161
|
+
if $2 == ''
|
162
|
+
"#{$1}(#{arguments.join(', ')})"
|
163
|
+
else
|
164
|
+
"#{$1}(#{$2}, #{arguments.join(', ')})"
|
165
|
+
end
|
166
|
+
else
|
167
|
+
"#{method}(#{arguments.join(', ')})"
|
168
|
+
end
|
169
|
+
else
|
170
|
+
method
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def rubyless_expand(res)
|
175
|
+
if res.klass == String && !@blocks.detect {|b| !b.kind_of?(String)}
|
176
|
+
if lit = res.literal
|
177
|
+
out lit
|
178
|
+
else
|
179
|
+
out "<%= #{res} %>"
|
180
|
+
end
|
181
|
+
elsif @blocks.empty?
|
182
|
+
out "<%= #{res} %>"
|
183
|
+
elsif res.could_be_nil?
|
184
|
+
expand_with_finder(:method => res, :class => res.klass, :nil => true)
|
185
|
+
else
|
186
|
+
expand_with_finder(:method => res, :class => res.klass)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def rubyless_class_scope(class_name)
|
191
|
+
# capital letter ==> class conditional
|
192
|
+
klass = Module.const_get(class_name)
|
193
|
+
if klass.ancestors.include?(node.klass)
|
194
|
+
expand_if("#{node}.kind_of?(#{klass})")
|
195
|
+
else
|
196
|
+
# render nothing: incompatible classes
|
197
|
+
''
|
198
|
+
end
|
199
|
+
rescue
|
200
|
+
parser_error("Invalid class name '#{class_name}'")
|
201
|
+
end
|
202
|
+
|
203
|
+
# Find a class or behavior based on a name. The returned class should implement
|
204
|
+
# 'safe_method_type'.
|
205
|
+
def get_class(class_name)
|
206
|
+
Module.const_get(class_name)
|
207
|
+
rescue
|
208
|
+
nil
|
209
|
+
end
|
210
|
+
|
211
|
+
# This is used to resolve 'this' (current NodeContext), '@node' as NodeContext with class Node,
|
212
|
+
# '@page' as first NodeContext of type Page, etc.
|
213
|
+
def node_context_from_signature(signature)
|
214
|
+
return nil unless signature.size == 1
|
215
|
+
ivar = signature.first
|
216
|
+
return {:class => node.klass, :method => node.name} if ivar == 'this'
|
217
|
+
return nil unless ivar[0..0] == '@'
|
218
|
+
begin
|
219
|
+
klass = Module.const_get(ivar[1..-1].capitalize)
|
220
|
+
context = node(klass)
|
221
|
+
rescue NameError
|
222
|
+
return nil
|
223
|
+
end
|
224
|
+
{:class => context.klass, :method => context.name}
|
225
|
+
end
|
226
|
+
|
227
|
+
# Find stored variables back. Stored elements are set with set_xxx='something to eval'.
|
228
|
+
def get_var_from_signature(signature)
|
229
|
+
return nil unless signature.size == 1
|
230
|
+
if var = get_context_var('set_var', signature.first)
|
231
|
+
{:method => var, :class => var.klass, :nil => var.could_be_nil?}
|
232
|
+
else
|
233
|
+
nil
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def safe_method_from(context, signature)
|
238
|
+
|
239
|
+
if context.respond_to?(:safe_method_type)
|
240
|
+
context.safe_method_type(signature)
|
241
|
+
else
|
242
|
+
RubyLess::SafeClass.safe_method_type_for(context, signature)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
end # RubyLessProcessing
|
247
|
+
end # Process
|
248
|
+
end # Zafu
|
data/lib/zafu/test_helper.rb
CHANGED
@@ -2,7 +2,7 @@ require 'zafu/template'
|
|
2
2
|
|
3
3
|
module Zafu
|
4
4
|
module TestHelper
|
5
|
-
include
|
5
|
+
include RubyLess::SafeClass
|
6
6
|
|
7
7
|
def zafu_erb(source, src_helper = self, compiler = Zafu::Compiler)
|
8
8
|
Zafu::Template.new(source, src_helper, compiler).to_erb(compilation_context)
|
data/test/markup_test.rb
CHANGED
@@ -6,8 +6,9 @@ class String
|
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
|
10
|
+
class MarkupTest < Test::Unit::TestCase
|
11
|
+
include RubyLess
|
11
12
|
safe_method :day => {:class => String, :method => %q{Time.now.strftime('%A')}}
|
12
13
|
Markup = Zafu::Markup
|
13
14
|
|
@@ -54,20 +55,43 @@ class NodeContextTest < Test::Unit::TestCase
|
|
54
55
|
h = {:class => 'shiny', :style => 'good'}
|
55
56
|
assert_equal h, @markup.params
|
56
57
|
end
|
58
|
+
|
59
|
+
should 'respond to has_param' do
|
60
|
+
@markup.params = {:class => 'one', :x => 'y'}
|
61
|
+
@markup.dyn_params = {:y => 'z'}
|
62
|
+
assert @markup.has_param?(:x)
|
63
|
+
assert @markup.has_param?(:y)
|
64
|
+
assert !@markup.has_param?(:z)
|
65
|
+
end
|
57
66
|
end
|
58
67
|
|
59
68
|
context 'Stealing html params' do
|
60
|
-
|
61
|
-
|
69
|
+
subject do
|
70
|
+
Markup.new('p')
|
62
71
|
end
|
63
72
|
|
64
|
-
should '
|
73
|
+
should 'transfer common html params' do
|
65
74
|
base = {:class => 'blue', :name => 'sprout', :id => 'front_cover', :style => 'padding:5px;', :attr => 'title'}
|
66
|
-
|
75
|
+
subject.steal_html_params_from(base)
|
67
76
|
new_base = {:name => 'sprout', :attr => 'title'}
|
68
77
|
markup_params = {:class => 'blue', :id => 'front_cover', :style => 'padding:5px;'}
|
69
78
|
assert_equal new_base, base
|
70
|
-
assert_equal markup_params,
|
79
|
+
assert_equal markup_params, subject.params
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'on a link' do
|
83
|
+
subject do
|
84
|
+
Markup.new('link')
|
85
|
+
end
|
86
|
+
|
87
|
+
should 'transfer common html params' do
|
88
|
+
base = {:rel => 'rel', :type => 'type', :class => 'blue', :name => 'sprout', :id => 'front_cover', :style => 'padding:5px;', :attr => 'title'}
|
89
|
+
subject.steal_html_params_from(base)
|
90
|
+
new_base = {:name => 'sprout', :attr => 'title'}
|
91
|
+
markup_params = {:type=>"type", :rel=>"rel", :id => 'front_cover', :class => 'blue', :style => 'padding:5px;'}
|
92
|
+
assert_equal new_base, base
|
93
|
+
assert_equal markup_params, subject.params
|
94
|
+
end
|
71
95
|
end
|
72
96
|
end
|
73
97
|
|
@@ -152,6 +176,43 @@ class NodeContextTest < Test::Unit::TestCase
|
|
152
176
|
end
|
153
177
|
end
|
154
178
|
|
179
|
+
context 'Prepending a static param' do
|
180
|
+
context 'on a static param' do
|
181
|
+
setup do
|
182
|
+
@markup = Markup.new('p')
|
183
|
+
@markup.set_params(:class => 'simple')
|
184
|
+
end
|
185
|
+
|
186
|
+
should 'prepend param in the static params' do
|
187
|
+
@markup.prepend_param(:class, 'super')
|
188
|
+
assert_equal 'super simple', @markup.params[:class]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context 'on a dynamic param' do
|
193
|
+
setup do
|
194
|
+
@markup = Markup.new('p')
|
195
|
+
@markup.set_dyn_params(:class => '<%= @fu %>')
|
196
|
+
end
|
197
|
+
|
198
|
+
should 'prepend param in the dynamic params' do
|
199
|
+
@markup.prepend_param(:class, 'to')
|
200
|
+
assert_equal 'to <%= @fu %>', @markup.dyn_params[:class]
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
context 'on an empty param' do
|
205
|
+
setup do
|
206
|
+
@markup = Markup.new('p')
|
207
|
+
end
|
208
|
+
|
209
|
+
should 'set param in the static params' do
|
210
|
+
@markup.prepend_param(:class, 'bar')
|
211
|
+
assert_equal 'bar', @markup.params[:class]
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
155
216
|
context 'Appending a dynamic param' do
|
156
217
|
context 'on a static param' do
|
157
218
|
setup do
|
@@ -175,6 +236,11 @@ class NodeContextTest < Test::Unit::TestCase
|
|
175
236
|
@markup.append_dyn_param(:class, '<%= @bar %>')
|
176
237
|
assert_equal '<%= @foo %> <%= @bar %>', @markup.dyn_params[:class]
|
177
238
|
end
|
239
|
+
|
240
|
+
should 'append param without spacer if conditional' do
|
241
|
+
@markup.append_dyn_param(:class, '<%= @bar %>', true)
|
242
|
+
assert_equal '<%= @foo %><%= @bar %>', @markup.dyn_params[:class]
|
243
|
+
end
|
178
244
|
end
|
179
245
|
|
180
246
|
context 'on an empty param' do
|
@@ -189,11 +255,54 @@ class NodeContextTest < Test::Unit::TestCase
|
|
189
255
|
end
|
190
256
|
end
|
191
257
|
|
258
|
+
context 'Prepending a dynamic param' do
|
259
|
+
context 'on a static param' do
|
260
|
+
setup do
|
261
|
+
@markup = Markup.new('p')
|
262
|
+
@markup.set_params(:class => 'fu')
|
263
|
+
end
|
264
|
+
|
265
|
+
should 'copy the static param in the dynamic params' do
|
266
|
+
@markup.prepend_dyn_param(:class, '<%= @to %>')
|
267
|
+
assert_equal '<%= @to %> fu', @markup.dyn_params[:class]
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
context 'on a dynamic param' do
|
272
|
+
setup do
|
273
|
+
@markup = Markup.new('p')
|
274
|
+
@markup.set_dyn_params(:class => '<%= @fu %>')
|
275
|
+
end
|
276
|
+
|
277
|
+
should 'prepend param in the dynamic params' do
|
278
|
+
@markup.prepend_dyn_param(:class, '<%= @to %>')
|
279
|
+
assert_equal '<%= @to %> <%= @fu %>', @markup.dyn_params[:class]
|
280
|
+
end
|
281
|
+
|
282
|
+
should 'prepend param without spacer if conditional' do
|
283
|
+
@markup.prepend_dyn_param(:class, '<%= @to %>', true)
|
284
|
+
assert_equal '<%= @to %><%= @fu %>', @markup.dyn_params[:class]
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
context 'on an empty param' do
|
289
|
+
setup do
|
290
|
+
@markup = Markup.new('p')
|
291
|
+
end
|
292
|
+
|
293
|
+
should 'set param in the dynamic params' do
|
294
|
+
@markup.prepend_dyn_param(:class, '<%= @bar %>')
|
295
|
+
assert_equal '<%= @bar %>', @markup.dyn_params[:class]
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
192
300
|
context 'Wrapping some text' do
|
193
301
|
setup do
|
194
302
|
@text = 'Alice: It would be so nice if something made sense for a change.'
|
195
303
|
@markup = Markup.new('p')
|
196
|
-
@markup.params
|
304
|
+
@markup.params[:class] = 'quote'
|
305
|
+
@markup.params[:style] = 'padding:3px; border:1px solid red;'
|
197
306
|
end
|
198
307
|
|
199
308
|
should 'add the markup tag around the text' do
|
@@ -203,6 +312,12 @@ class NodeContextTest < Test::Unit::TestCase
|
|
203
312
|
should 'not wrap twice if called twice' do
|
204
313
|
assert_equal "<p class='quote' style='padding:3px; border:1px solid red;'>#{@text}</p>", @markup.wrap(@markup.wrap(@text))
|
205
314
|
end
|
315
|
+
|
316
|
+
should 'display static params before dynamic and keep them ordered' do
|
317
|
+
@markup.set_dyn_params(:foo => '<%= @bar %>')
|
318
|
+
@markup.set_params(:baz => 'buzz')
|
319
|
+
assert_equal "<p class='quote' style='padding:3px; border:1px solid red;' baz='buzz' foo='<%= @bar %>'>foo</p>", @markup.wrap('foo')
|
320
|
+
end
|
206
321
|
end
|
207
322
|
|
208
323
|
context 'Compiling params' do
|
@@ -225,6 +340,33 @@ class NodeContextTest < Test::Unit::TestCase
|
|
225
340
|
end
|
226
341
|
end
|
227
342
|
end
|
343
|
+
|
344
|
+
context 'Duplicating a markup' do
|
345
|
+
context 'and changing params' do
|
346
|
+
setup do
|
347
|
+
@markup = Markup.new('p')
|
348
|
+
@markup.params[:class] = 'one'
|
349
|
+
@duplicate = @markup.dup
|
350
|
+
end
|
351
|
+
|
352
|
+
should 'not propagate changes to original' do
|
353
|
+
@duplicate.params[:class] = 'two'
|
354
|
+
assert_equal "<p class='one'>one</p>", @markup.wrap('one')
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
context 'and wrapping' do
|
359
|
+
setup do
|
360
|
+
@markup = Markup.new('p')
|
361
|
+
@duplicate = @markup.dup
|
362
|
+
end
|
363
|
+
|
364
|
+
should 'not propagate done to duplicate' do
|
365
|
+
@markup.wrap('')
|
366
|
+
assert_equal '<p>one</p>', @duplicate.wrap('one')
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
228
370
|
end
|
229
371
|
|
230
372
|
|