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.
@@ -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('<','&lt;').gsub('>','&gt;') %></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
@@ -2,7 +2,7 @@ require 'zafu/template'
2
2
 
3
3
  module Zafu
4
4
  module TestHelper
5
- include ::RubyLess::SafeClass
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)
@@ -0,0 +1,6 @@
1
+ require 'zafu/controller_methods'
2
+ module Zafu
3
+ module ViewMethods
4
+ include Zafu::ControllerMethods::Common
5
+ end
6
+ end
data/test/markup_test.rb CHANGED
@@ -6,8 +6,9 @@ class String
6
6
  end
7
7
  end
8
8
 
9
- class NodeContextTest < Test::Unit::TestCase
10
- include RubyLess::SafeClass
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
- setup do
61
- @markup = Markup.new('p')
69
+ subject do
70
+ Markup.new('p')
62
71
  end
63
72
 
64
- should 'remove common html params' do
73
+ should 'transfer common html params' do
65
74
  base = {:class => 'blue', :name => 'sprout', :id => 'front_cover', :style => 'padding:5px;', :attr => 'title'}
66
- @markup.steal_html_params_from(base)
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, @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 = {:class => 'quote', :style => 'padding:3px; border:1px solid red;'}
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