zafu 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,145 @@
1
+ require 'rubyless'
2
+
3
+ module Zafu
4
+ module Process
5
+ module RubyLess
6
+ include ::RubyLess::SafeClass
7
+ # Actual method resolution. The lookup first starts in the current helper. If nothing is found there, it
8
+ # searches inside a 'helpers' module and finally looks into the current node_context.
9
+ # If nothing is found at this stage, we prepend the method with the current node and start over again.
10
+ def safe_method_type(signature)
11
+ get_method_type(signature, false)
12
+ end
13
+
14
+ # Resolve unknown methods by using RubyLess in the current compilation context (the
15
+ # translate method in RubyLess will call 'safe_method_type' in this module).
16
+ def r_unknown
17
+ rubyless_render(@method, @params)
18
+ rescue ::RubyLess::NoMethodError => err
19
+ parser_error("#{err.error_message} <span class='type'>#{err.method_with_arguments}</span>", err.receiver_with_class)
20
+ rescue ::RubyLess::Error => err
21
+ parser_error(err.message)
22
+ end
23
+
24
+ # Print documentation on the current node type.
25
+ def r_m
26
+ if @params[:helper] == 'true'
27
+ klass = helper.class
28
+ else
29
+ klass = node.klass
30
+ end
31
+
32
+ out "<div class='rubyless-m'><h3>Documentation for <b>#{klass}</b></h3>"
33
+ out "<ul>"
34
+ ::RubyLess::SafeClass.safe_methods_for(klass).each do |signature, opts|
35
+ opts = opts.dup
36
+ opts.delete(:method)
37
+ if opts.keys == [:class]
38
+ opts = opts[:class]
39
+ end
40
+ out "<li>#{signature.inspect} => #{opts.inspect}</li>"
41
+ end
42
+ out "</ul></div>"
43
+ end
44
+
45
+ # TEMPORARY METHOD DURING HACKING...
46
+ def r_erb
47
+ "<pre><%= @erb.gsub('<','&lt;').gsub('>','&gt;') %></pre>"
48
+ end
49
+
50
+ def rubyless_render(method, params)
51
+ rubyless_expand(::RubyLess.translate(method_with_arguments(method, params), self))
52
+ end
53
+
54
+ def rubyless_attr(val)
55
+ ::RubyLess.translate_string(val, self)
56
+ end
57
+
58
+ private
59
+ def get_method_type(signature, added_options = false)
60
+ if type = node_context_from_signature(signature)
61
+ # Resolve @page, @node
62
+ type
63
+ elsif type = safe_method_from(helper, signature)
64
+ # Resolve template helper methods
65
+ type
66
+ elsif helper.respond_to?(:helpers) && type = safe_method_from(helper.helpers, signature)
67
+ # Resolve by looking at the included helpers
68
+ type
69
+ elsif node && node.klass.kind_of?(Class) && type = safe_method_from(node.klass, signature)
70
+ # Resolve node context methods (xxx.foo, xxx.bar)
71
+ type.merge(:method => "#{node.name}.#{type[:method]}")
72
+ elsif node && !added_options
73
+ # Try prepending current node before arguments: link("foo") becomse link(var1, "foo")
74
+ signature_with_node = signature.dup
75
+ signature_with_node.insert(1, node.klass)
76
+ if type = get_method_type(signature_with_node, added_options = true)
77
+ type = type.merge(:prepend_args => ::RubyLess::TypedString.new(node.name, :class => node.klass))
78
+ type
79
+ else
80
+ nil
81
+ end
82
+ else
83
+ nil
84
+ end
85
+ end
86
+
87
+ def method_with_arguments(method, params)
88
+ hash_arguments = {}
89
+ arguments = []
90
+ keys = params.keys.map {|k| k.to_s}
91
+ keys.sort.each do |k|
92
+ if k.to_s =~ /\A_/
93
+ arguments << params[k.to_sym]
94
+ else
95
+ hash_arguments[k.to_s] = params[k.to_sym]
96
+ end
97
+ end
98
+
99
+ arguments += [hash_arguments] if hash_arguments != {}
100
+ if arguments != [] && method[-1..-1] =~ /\w/
101
+ "#{method}(#{arguments.inspect[1..-2]})"
102
+ else
103
+ method
104
+ end
105
+ end
106
+
107
+ def rubyless_expand(res)
108
+ if res.klass == String && @blocks.map {|b| b.kind_of?(String) ? nil : b.method}.compact.empty?
109
+ out "<%= #{res} %>"
110
+ elsif res.could_be_nil?
111
+ out "<% if #{var} = #{res} -%>"
112
+ out @markup.wrap(expand_with_node(var, res.klass))
113
+ out "<% end -%>"
114
+ else
115
+ out "<% #{var} = #{res} -%>"
116
+ out @markup.wrap(expand_with_node(var, res.klass))
117
+ end
118
+ end
119
+
120
+ # This is used to resolve '@node' as NodeContext with class Node, '@page' as first NodeContext
121
+ # of type Page, etc.
122
+ def node_context_from_signature(signature)
123
+ return nil unless signature.size == 1
124
+ ivar = signature.first
125
+ return nil unless ivar[0..0] == '@'
126
+ begin
127
+ klass = Module.const_get(ivar[1..-1].capitalize)
128
+ context = node(klass)
129
+ rescue NameError
130
+ return nil
131
+ end
132
+ {:class => context.klass, :method => context.name}
133
+ end
134
+
135
+ def safe_method_from(context, signature)
136
+ if context.respond_to?(:safe_method_type)
137
+ context.safe_method_type(signature)
138
+ else
139
+ ::RubyLess::SafeClass.safe_method_type_for(context, signature)
140
+ end
141
+ end
142
+
143
+ end # RubyLess
144
+ end # Process
145
+ end # Zafu
@@ -0,0 +1,25 @@
1
+ require 'zafu/compiler'
2
+
3
+ module Zafu
4
+ class Template
5
+ def initialize(template, src_helper = nil, compiler = Zafu::Compiler)
6
+ if template.kind_of?(String)
7
+ @ast = compiler.new(template)
8
+ else
9
+ @ast = compiler.new_with_url(template.path, :helper => src_helper)
10
+ end
11
+ end
12
+
13
+ def to_erb(context = {})
14
+ @ast.to_erb(context)
15
+ end
16
+
17
+ def to_ruby(context = {})
18
+ src = ::ERB.new("<% __in_erb_template=true %>#{to_erb(context)}", nil, '-').src
19
+
20
+ # Ruby 1.9 prepends an encoding to the source. However this is
21
+ # useless because you can only set an encoding on the first line
22
+ RUBY_VERSION >= '1.9' ? src.sub(/\A#coding:.*\n/, '') : src
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ require 'zafu/template'
2
+
3
+ module Zafu
4
+ module TestHelper
5
+ include ::RubyLess::SafeClass
6
+
7
+ def zafu_erb(source, src_helper = self, compiler = Zafu::Compiler)
8
+ Zafu::Template.new(source, src_helper, compiler).to_erb(compilation_context)
9
+ end
10
+
11
+ def zafu_render(source, src_helper = self, compiler = Zafu::Compiler)
12
+ eval Zafu::Template.new(source, src_helper, compiler).to_ruby(compilation_context)
13
+ end
14
+
15
+ def compilation_context
16
+ {:node => @node_context, :helper => self}
17
+ end
18
+ end
19
+ end
data/lib/zafu.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'zafu/all'
2
+
3
+ if defined?(ActionView)
4
+ require 'zafu/handler'
5
+ ActionView::Template.register_template_handler(:zafu, Zafu::Handler)
6
+ ActionView::Template.register_template_handler(:html, Zafu::Handler)
7
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'zafu' #File.join(File.dirname(__FILE__), '..', 'lib', 'zafu')
data/script/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/zafu.rb'}"
9
+ puts "Loading zafu gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,232 @@
1
+ require 'test_helper'
2
+
3
+ class String
4
+ def blank?
5
+ self == ''
6
+ end
7
+ end
8
+
9
+ class NodeContextTest < Test::Unit::TestCase
10
+ include RubyLess::SafeClass
11
+ safe_method :day => {:class => String, :method => %q{Time.now.strftime('%A')}}
12
+ Markup = Zafu::Markup
13
+
14
+ context 'Parsing parameters' do
15
+ should 'retrieve values escaped with single quotes' do
16
+ h = {:class => 'worker', :style => 'tired'}
17
+ assert_equal h, Markup.parse_params("class='worker' style='tired'")
18
+ end
19
+
20
+ should 'retrieve values escaped with double quotes' do
21
+ h = {:class => 'worker', :style => 'tired'}
22
+ assert_equal h, Markup.parse_params('class="worker" style="tired"')
23
+ end
24
+
25
+ should 'retrieve values escaped with mixed quotes' do
26
+ h = {:class => 'worker', :style => 'tired'}
27
+ assert_equal h, Markup.parse_params('class=\'worker\' style="tired"')
28
+ end
29
+
30
+ should 'properly handle escaped single quotes' do
31
+ h = {:class => "that's nice", :style => 'tired'}
32
+ assert_equal h, Markup.parse_params("class='that\\\'s nice' style='tired'")
33
+ end
34
+
35
+ should 'properly handle escaped double quotes' do
36
+ h = {:class => '30"', :style => 'tired'}
37
+ assert_equal h, Markup.parse_params('class="30\\"" style="tired"')
38
+ end
39
+ end
40
+
41
+ context 'Setting parameters' do
42
+ setup do
43
+ @markup = Markup.new('p')
44
+ end
45
+
46
+ should 'parse params if the parameters are provided as a string' do
47
+ @markup.params = "class='shiny' id='slogan'"
48
+ h = {:class => 'shiny', :id => 'slogan'}
49
+ assert_equal h, @markup.params
50
+ end
51
+
52
+ should 'set params if the parameters are provided as a hash' do
53
+ @markup.params = {:class => 'shiny', :style => 'good'}
54
+ h = {:class => 'shiny', :style => 'good'}
55
+ assert_equal h, @markup.params
56
+ end
57
+ end
58
+
59
+ context 'Stealing html params' do
60
+ setup do
61
+ @markup = Markup.new('p')
62
+ end
63
+
64
+ should 'remove common html params' do
65
+ base = {:class => 'blue', :name => 'sprout', :id => 'front_cover', :style => 'padding:5px;', :attr => 'title'}
66
+ @markup.steal_html_params_from(base)
67
+ new_base = {:name => 'sprout', :attr => 'title'}
68
+ markup_params = {:class => 'blue', :id => 'front_cover', :style => 'padding:5px;'}
69
+ assert_equal new_base, base
70
+ assert_equal markup_params, @markup.params
71
+ end
72
+ end
73
+
74
+ context 'Defining the dom id' do
75
+ setup do
76
+ @markup = Markup.new('p')
77
+ @markup.params[:id] = 'foobar'
78
+ @markup.dyn_params[:id] = 'foobar'
79
+ @markup.set_id('<%= @node.zip %>')
80
+ end
81
+
82
+ should 'remove any predifined id' do
83
+ assert_nil @markup.params[:id]
84
+ end
85
+
86
+ should 'write id in the dynamic params' do
87
+ assert_equal '<%= @node.zip %>', @markup.dyn_params[:id]
88
+ end
89
+ end
90
+
91
+ context 'Setting a dynamic param' do
92
+ setup do
93
+ @markup = Markup.new('p')
94
+ @markup.params[:foo] = 'one'
95
+ @markup.set_dyn_params(:foo => '<%= @node.two %>')
96
+ end
97
+
98
+ should 'clear a static param with same key' do
99
+ assert_nil @markup.params[:foo]
100
+ assert_equal '<%= @node.two %>', @markup.dyn_params[:foo]
101
+ end
102
+ end
103
+
104
+ context 'Setting a static param' do
105
+ setup do
106
+ @markup = Markup.new('p')
107
+ @markup.dyn_params[:foo] = '<%= @node.two %>'
108
+ @markup.set_params(:foo => 'one')
109
+ end
110
+
111
+ should 'clear a dynamic param with same key' do
112
+ assert_nil @markup.dyn_params[:foo]
113
+ assert_equal 'one', @markup.params[:foo]
114
+ end
115
+ end
116
+
117
+
118
+ context 'Appending a static param' do
119
+ context 'on a static param' do
120
+ setup do
121
+ @markup = Markup.new('p')
122
+ @markup.set_params(:class => 'simple')
123
+ end
124
+
125
+ should 'append param in the static params' do
126
+ @markup.append_param(:class, 'mind')
127
+ assert_equal 'simple mind', @markup.params[:class]
128
+ end
129
+ end
130
+
131
+ context 'on a dynamic param' do
132
+ setup do
133
+ @markup = Markup.new('p')
134
+ @markup.set_dyn_params(:class => '<%= @foo %>')
135
+ end
136
+
137
+ should 'append param in the dynamic params' do
138
+ @markup.append_param(:class, 'bar')
139
+ assert_equal '<%= @foo %> bar', @markup.dyn_params[:class]
140
+ end
141
+ end
142
+
143
+ context 'on an empty param' do
144
+ setup do
145
+ @markup = Markup.new('p')
146
+ end
147
+
148
+ should 'set param in the static params' do
149
+ @markup.append_param(:class, 'bar')
150
+ assert_equal 'bar', @markup.params[:class]
151
+ end
152
+ end
153
+ end
154
+
155
+ context 'Appending a dynamic param' do
156
+ context 'on a static param' do
157
+ setup do
158
+ @markup = Markup.new('p')
159
+ @markup.set_params(:class => 'simple')
160
+ end
161
+
162
+ should 'copy the static param in the dynamic params' do
163
+ @markup.append_dyn_param(:class, '<%= @mind %>')
164
+ assert_equal 'simple <%= @mind %>', @markup.dyn_params[:class]
165
+ end
166
+ end
167
+
168
+ context 'on a dynamic param' do
169
+ setup do
170
+ @markup = Markup.new('p')
171
+ @markup.set_dyn_params(:class => '<%= @foo %>')
172
+ end
173
+
174
+ should 'append param in the dynamic params' do
175
+ @markup.append_dyn_param(:class, '<%= @bar %>')
176
+ assert_equal '<%= @foo %> <%= @bar %>', @markup.dyn_params[:class]
177
+ end
178
+ end
179
+
180
+ context 'on an empty param' do
181
+ setup do
182
+ @markup = Markup.new('p')
183
+ end
184
+
185
+ should 'set param in the dynamic params' do
186
+ @markup.append_dyn_param(:class, '<%= @bar %>')
187
+ assert_equal '<%= @bar %>', @markup.dyn_params[:class]
188
+ end
189
+ end
190
+ end
191
+
192
+ context 'Wrapping some text' do
193
+ setup do
194
+ @text = 'Alice: It would be so nice if something made sense for a change.'
195
+ @markup = Markup.new('p')
196
+ @markup.params = {:class => 'quote', :style => 'padding:3px; border:1px solid red;'}
197
+ end
198
+
199
+ should 'add the markup tag around the text' do
200
+ assert_equal "<p class='quote' style='padding:3px; border:1px solid red;'>#{@text}</p>", @markup.wrap(@text)
201
+ end
202
+
203
+ should 'not wrap twice if called twice' do
204
+ assert_equal "<p class='quote' style='padding:3px; border:1px solid red;'>#{@text}</p>", @markup.wrap(@markup.wrap(@text))
205
+ end
206
+ end
207
+
208
+ context 'Compiling params' do
209
+ setup do
210
+ @markup = Markup.new('p')
211
+ @markup.params = %q{class='one #{day}' id='foobar' name='#{day}'}
212
+ end
213
+
214
+ context 'with compile_params' do
215
+ setup do
216
+ @markup.compile_params(self)
217
+ end
218
+
219
+ should 'translate dynamic params into ERB by using RubyLess' do
220
+ assert_equal %q{<%= "one #{Time.now.strftime('%A')}" %>}, @markup.dyn_params[:class]
221
+ end
222
+
223
+ should 'translate without string on single dynamic content' do
224
+ assert_equal %q{<%= Time.now.strftime('%A') %>}, @markup.dyn_params[:name]
225
+ end
226
+ end
227
+ end
228
+ end
229
+
230
+
231
+
232
+
@@ -0,0 +1,19 @@
1
+ module Mock
2
+ module Params
3
+ def self.included(base)
4
+ base.before_process :filter_params
5
+ end
6
+
7
+ def filter_params
8
+ if klass = @params.delete(:class)
9
+ if klass =~ /#\{/
10
+ res = RubyLess.translate("\"#{klass}\"", self)
11
+ @markup.append_dyn_param(:class, "<%= #{res} %>")
12
+ else
13
+ @markup.append_param(:class, klass)
14
+ end
15
+ end
16
+ true
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,190 @@
1
+ require 'test_helper'
2
+
3
+ class NodeContextTest < Test::Unit::TestCase
4
+ class Page;end
5
+ class SubPage < Page; end
6
+ class Comment;end
7
+ NodeContext = Zafu::NodeContext
8
+
9
+ context 'In a blank context' do
10
+ setup do
11
+ @context = NodeContext.new('@node', Page)
12
+ end
13
+
14
+ should 'return the current name' do
15
+ assert_equal '@node', @context.name
16
+ end
17
+
18
+ should 'return the current class' do
19
+ assert_equal Page, @context.klass
20
+ end
21
+
22
+ should 'return true on will_be with the same class' do
23
+ assert @context.will_be?(Page)
24
+ end
25
+
26
+ should 'return false on will_be with a sub class' do
27
+ assert !@context.will_be?(SubPage)
28
+ end
29
+
30
+ should 'return false on will_be with a different class' do
31
+ assert !@context.will_be?(String)
32
+ end
33
+
34
+ should 'return self on a get for the same class' do
35
+ assert_equal @context.object_id, @context.get(Page).object_id
36
+ end
37
+
38
+ should 'return nil on a get for another class' do
39
+ assert_nil @context.get(Comment)
40
+ end
41
+
42
+ context 'with a sub-class' do
43
+ setup do
44
+ @context = NodeContext.new('@node', SubPage)
45
+ end
46
+
47
+ should 'return true on will_be with the same class' do
48
+ assert @context.will_be?(SubPage)
49
+ end
50
+
51
+ should 'return true on will_be with a super class' do
52
+ assert @context.will_be?(Page)
53
+ end
54
+
55
+ should 'return false on will_be with a different class' do
56
+ assert !@context.will_be?(String)
57
+ end
58
+ end
59
+ end
60
+
61
+ context 'In a sub-context' do
62
+ setup do
63
+ @parent = NodeContext.new('@node', Page)
64
+ @context = @parent.move_to('comment1', Comment)
65
+ end
66
+
67
+ should 'return the current name' do
68
+ assert_equal 'comment1', @context.name
69
+ end
70
+
71
+ should 'return the current class' do
72
+ assert_equal Comment, @context.klass
73
+ end
74
+
75
+ should 'return self on a get for the same class' do
76
+ assert_equal @context.object_id, @context.get(Comment).object_id
77
+ end
78
+
79
+ should 'return the parent on a get for the class of the parent' do
80
+ assert_equal @parent.object_id, @context.get(Page).object_id
81
+ end
82
+ end
83
+
84
+ context 'In a deeply nested context' do
85
+ setup do
86
+ @grandgrandma = NodeContext.new('@comment', Comment)
87
+ @grandma = @grandgrandma.move_to('page', Page)
88
+ @mother = @grandma.move_to('comment1', Comment)
89
+ @context = @mother.move_to('var1', String)
90
+ end
91
+
92
+ should 'return the current name' do
93
+ assert_equal 'var1', @context.name
94
+ end
95
+
96
+ should 'return the current class' do
97
+ assert_equal String, @context.klass
98
+ end
99
+
100
+ should 'return the first ancestor matching class on get' do
101
+ assert_equal @mother.object_id, @context.get(Comment).object_id
102
+ end
103
+
104
+ should 'return nil if no ancestor matches class on get' do
105
+ assert_nil @context.get(Fixnum)
106
+ end
107
+ end
108
+
109
+ context 'In a sub-classes context' do
110
+ setup do
111
+ @context = NodeContext.new('super', SubPage)
112
+ end
113
+
114
+ should 'find the current context required class is an ancestor' do
115
+ assert_equal @context.object_id, @context.get(Page).object_id
116
+ end
117
+ end
118
+
119
+ context 'In a list context' do
120
+ setup do
121
+ @context = NodeContext.new('list', [Page])
122
+ end
123
+
124
+ should 'find the context and resolve with first' do
125
+ assert context = @context.get(Page)
126
+ assert_equal 'list.first', context.name
127
+ assert_equal Page, context.klass
128
+ end
129
+ end
130
+
131
+ context 'Generating a dom id' do
132
+ context 'in a blank context' do
133
+ setup do
134
+ @context = NodeContext.new('@foo', Page)
135
+ end
136
+
137
+ should 'return the node name in DOM id' do
138
+ assert_equal '<%= @foo.zip %>', @context.dom_id
139
+ end
140
+ end
141
+
142
+ context 'in a hierarchy of contexts' do
143
+ setup do
144
+ @a = NodeContext.new('@node', Page)
145
+ @b = NodeContext.new('var1', [Page], @a)
146
+ @c = NodeContext.new('var2', Page, @b)
147
+ @context = NodeContext.new('var3', Page, @c)
148
+ end
149
+
150
+ context 'with parents as dom_scopes' do
151
+ setup do
152
+ @b.dom_scope!
153
+ @c.dom_scope!
154
+ end
155
+
156
+ should 'use dom_scopes' do
157
+ assert_equal '<%= var1.zip %>_<%= var2.zip %>_<%= var3.zip %>', @context.dom_id
158
+ end
159
+ end
160
+
161
+ context 'with ancestors and self as dom_scopes' do
162
+ setup do
163
+ @a.dom_scope!
164
+ @context.dom_scope!
165
+ end
166
+
167
+ should 'not use self twice' do
168
+ assert_equal '<%= @node.zip %>_<%= var3.zip %>', @context.dom_id
169
+ end
170
+ end
171
+
172
+ context 'with a parent defining a dom_prefix' do
173
+ setup do
174
+ @b.dom_prefix = 'cart'
175
+ end
176
+
177
+ should 'use dom_prefix' do
178
+ assert_equal 'cart_<%= var3.zip %>', @context.dom_id
179
+ end
180
+ end
181
+
182
+ end
183
+ end
184
+ end
185
+
186
+
187
+
188
+
189
+
190
+