tiny 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,63 @@
1
+ require 'erubis'
2
+ require 'tilt/erb'
3
+
4
+ module Tiny
5
+ # Support for emitting explicitly the result of calls to methods that
6
+ # take blocks. Based on the Rails ERB hack.
7
+ #
8
+ # <%= my_method do %>
9
+ # ...
10
+ # <% end %>
11
+ #
12
+ # It overrides Tilt default Template classes for Erubis.
13
+ #
14
+ module Erubis
15
+ # @see Erubis
16
+ class ::String
17
+ def append= obj
18
+ self << obj.to_s
19
+ end
20
+
21
+ def append_escaped= obj
22
+ self << Tiny::Helpers.sanitize(obj.to_s)
23
+ end
24
+ end
25
+
26
+ # @see Erubis
27
+ module ErubyExtensions
28
+ def add_expr_literal src, code
29
+ src << "_buf.append= #{code};"
30
+ end
31
+
32
+ def add_expr_escaped src, code
33
+ src << "_buf.append_escaped= #{code};"
34
+ end
35
+ end
36
+
37
+ # @see Erubis
38
+ class Eruby < ::Erubis::Eruby
39
+ include ErubyExtensions
40
+ end
41
+
42
+ # @see Erubis
43
+ class EscapedEruby < ::Erubis::EscapedEruby
44
+ include ErubyExtensions
45
+ end
46
+
47
+ # @see Erubis
48
+ class ErubisTemplate < ::Tilt::ErubisTemplate
49
+ def prepare
50
+ engine_class = options.delete(:engine_class) || Eruby
51
+ engine_class = EscapedEruby if options.delete(:escape_html)
52
+ options.merge!(:engine_class => engine_class)
53
+ super
54
+ end
55
+
56
+ def precompiled_preamble locals
57
+ [super, "__in_erb_template=true"].join("\n")
58
+ end
59
+ end
60
+
61
+ Tilt.register ErubisTemplate, 'erb', 'rhtml', 'erubis'
62
+ end
63
+ end
data/lib/tiny/html.rb ADDED
@@ -0,0 +1,177 @@
1
+ module Tiny
2
+ module HTML
3
+ @content_tags = []
4
+ @void_tags = []
5
+
6
+ class << self
7
+ # Void tag names.
8
+ # Tags that should have no content.
9
+ # @return [Array]
10
+ def content_tags
11
+ @content_tags
12
+ end
13
+
14
+ # Content tag names.
15
+ # Tags that can have content.
16
+ # @return [Array]
17
+ def void_tags
18
+ @void_tags
19
+ end
20
+
21
+ private
22
+ # @macro tag_def
23
+ # @method $1(attrs_or_content = {}, attrs = nil, &block)
24
+ # Shortcut for {Markup#html_tag html_tag}(:$1)
25
+ #
26
+ # @param attrs_or_content [Hash, String] Tag's attributes or content.
27
+ # @param attrs [Hash] Tag's attributes if content string passed.
28
+ # @yield Content block.
29
+ # @return [String] HTML markup
30
+ #
31
+ # @see Markup#html_tag
32
+ #
33
+ def tag_def tag_name, void_tag = false
34
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
35
+ def #{tag_name} *args, &block
36
+ html_tag "#{tag_name}", *args, &block
37
+ end
38
+ RUBY
39
+ void_tag ? @void_tags.push(tag_name) : @content_tags.push(tag_name)
40
+ end
41
+ end
42
+
43
+ tag_def 'area', :void
44
+ tag_def 'base', :void
45
+ tag_def 'br', :void
46
+ tag_def 'col', :void
47
+ tag_def 'hr', :void
48
+ tag_def 'img', :void
49
+ tag_def 'input', :void
50
+ tag_def 'link', :void
51
+ tag_def 'meta', :void
52
+ tag_def 'param', :void
53
+
54
+ # html 5 tags
55
+ tag_def 'embed', :void
56
+ tag_def 'article'
57
+ tag_def 'aside'
58
+ tag_def 'audio'
59
+ tag_def 'bdi'
60
+ tag_def 'canvas'
61
+ tag_def 'command'
62
+ tag_def 'datalist'
63
+ tag_def 'details'
64
+ tag_def 'figcaption'
65
+ tag_def 'figure'
66
+ tag_def 'header'
67
+ tag_def 'hgroup'
68
+ tag_def 'keygen'
69
+ tag_def 'mark'
70
+ tag_def 'meter'
71
+ tag_def 'nav'
72
+ tag_def 'output'
73
+ tag_def 'progress'
74
+ tag_def 'section'
75
+ tag_def 'source'
76
+ tag_def 'summary'
77
+ tag_def 'track'
78
+ tag_def 'video'
79
+ tag_def 'wbr'
80
+
81
+ # common tags
82
+ tag_def 'a'
83
+ tag_def 'abbr'
84
+ tag_def 'address'
85
+
86
+ tag_def 'b'
87
+ tag_def 'bdo'
88
+ tag_def 'big'
89
+ tag_def 'blockquote'
90
+ tag_def 'body'
91
+ tag_def 'button'
92
+
93
+ tag_def 'caption'
94
+ tag_def 'cite'
95
+ tag_def 'code'
96
+ tag_def 'colgroup'
97
+
98
+ tag_def 'dd'
99
+ tag_def 'del'
100
+ tag_def 'dfn'
101
+ tag_def 'div'
102
+ tag_def 'dl'
103
+ tag_def 'dt'
104
+
105
+ tag_def 'em'
106
+
107
+ tag_def 'fieldset'
108
+ tag_def 'footer'
109
+ tag_def 'form'
110
+
111
+ tag_def 'h1'
112
+ tag_def 'h2'
113
+ tag_def 'h3'
114
+ tag_def 'h4'
115
+ tag_def 'h5'
116
+ tag_def 'h6'
117
+ tag_def 'head'
118
+ tag_def 'html'
119
+
120
+ tag_def 'i'
121
+ tag_def 'iframe'
122
+ tag_def 'ins'
123
+
124
+ tag_def 'kbd'
125
+
126
+ tag_def 'label'
127
+ tag_def 'legend'
128
+ tag_def 'li'
129
+
130
+ tag_def 'map'
131
+
132
+ tag_def 'noscript'
133
+
134
+ tag_def 'object'
135
+ tag_def 'ol'
136
+ tag_def 'optgroup'
137
+ tag_def 'option'
138
+
139
+ tag_def 'p'
140
+ tag_def 'pre'
141
+
142
+ tag_def 'q'
143
+
144
+ tag_def 'rp'
145
+ tag_def 'rt'
146
+ tag_def 'ruby'
147
+
148
+ tag_def 's'
149
+ tag_def 'samp'
150
+ tag_def 'script'
151
+ tag_def 'select'
152
+ tag_def 'small'
153
+ tag_def 'span'
154
+ tag_def 'strike'
155
+ tag_def 'strong'
156
+ tag_def 'style'
157
+ tag_def 'sub'
158
+ tag_def 'sup'
159
+
160
+ tag_def 'table'
161
+ tag_def 'tbody'
162
+ tag_def 'td'
163
+ tag_def 'textarea'
164
+ tag_def 'tfoot'
165
+ tag_def 'th'
166
+ tag_def 'thead'
167
+ tag_def 'time'
168
+ tag_def 'title'
169
+ tag_def 'tr'
170
+ tag_def 'tt'
171
+
172
+ tag_def 'u'
173
+ tag_def 'ul'
174
+
175
+ tag_def 'var'
176
+ end
177
+ end
@@ -0,0 +1,16 @@
1
+ module Tiny
2
+ # A {SafeString} will not be HTML-escaped when appended to {Tag} or
3
+ # {Widget} content, whereas a String will be.
4
+ # @see Buffering#raw
5
+ # @see Buffering#concat!
6
+ # @see Buffering#concat
7
+ #
8
+ class SafeString < String
9
+ def html_safe?; true end
10
+
11
+ def concat string
12
+ return super unless String === string
13
+ super Helpers.sanitize string
14
+ end
15
+ end
16
+ end
data/lib/tiny/tag.rb ADDED
@@ -0,0 +1,47 @@
1
+ module Tiny
2
+ # @see Markup#html_tag
3
+ class Tag
4
+ attr_reader :tag_name, :attrs
5
+ def initialize tag_name, aoc = {}, attrs = nil
6
+ @attrs, @content =
7
+ Hash === aoc && attrs.nil?? [aoc] : [attrs || {}, aoc]
8
+ @content = Helpers.sanitize(@content) if @content
9
+ @tag_name = tag_name
10
+ end
11
+
12
+ def tag_attributes
13
+ tag_attrs = attrs.map do |name, val|
14
+ next if val.nil? || val == []
15
+ next name if val == true
16
+
17
+ vals = [*val].map do |value|
18
+ EscapeUtils.escape_html value.to_s, false
19
+ end
20
+
21
+ %{#{name}="#{vals.join(' ')}"}
22
+ end.compact.join(' ')
23
+
24
+ " #{tag_attrs}" unless tag_attrs.empty?
25
+ end
26
+
27
+ def render &block
28
+ if void_tag?
29
+ "<#{tag_name}#{tag_attributes} />"
30
+ else
31
+ content = @content
32
+ if block_given?
33
+ context = eval('self', block.binding)
34
+ content = context.with_buffer(&block)
35
+ content.gsub!(/^(?!\s*$)/, " ")
36
+ content.gsub!(/\A(?!$)|(?<!^|\n)\z/, "\n")
37
+ end
38
+
39
+ "<#{tag_name}#{tag_attributes}>#{content}</#{tag_name}>"
40
+ end
41
+ end
42
+
43
+ def void_tag?
44
+ HTML.void_tags.include? tag_name
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,3 @@
1
+ module Tiny
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'markup helpers' do
4
+ it "should register tiny erubis templates for '.erubis' files" do
5
+ Tilt['erubis'].should == Tiny::Erubis::ErubisTemplate
6
+ Tilt['erb'].should == Tiny::Erubis::ErubisTemplate
7
+ end
8
+
9
+ it 'should default to tiny erubis engine' do
10
+ engine = Tilt['erb'].new{}.instance_variable_get(:@engine)
11
+ engine.should be_a Tiny::Erubis::Eruby
12
+ end
13
+
14
+ it 'should use tiny escaped erubis engine for escaping html' do
15
+ engine = Tilt['erb'].new(nil, :escape_html => true){}.instance_variable_get(:@engine)
16
+ engine.should be_a Tiny::Erubis::EscapedEruby
17
+ end
18
+
19
+ it 'should escape html when passing :escape_html => true option' do
20
+ template = Tilt['erb'].new(nil, :escape_html => true) { %(<%= "<p>Hello World!</p>" %>) }
21
+ template.render.should == "&lt;p&gt;Hello World!&lt;&#47;p&gt;"
22
+ end
23
+
24
+ it 'should not escape htmle when passing :escape_html => false option' do
25
+ template = Tilt['erb'].new(nil, :escape_html => false) { %(<%= "<p>Hello World!</p>" %>) }
26
+ template.render.should == "<p>Hello World!</p>"
27
+ end
28
+
29
+ it 'should allow block with explicit output' do
30
+ template = Tilt['erb'].new do
31
+ <<-ERB
32
+ <%= [1,2].each do |i| %>
33
+ <% end %>
34
+ ERB
35
+ end
36
+ template.render.should include "[1, 2]"
37
+ end
38
+
39
+ it 'should allow block with explicit output' do
40
+ template = Tilt['erb'].new nil, :escape_html => true do
41
+ <<-ERB
42
+ <%= [1,2].each do |i| %>
43
+ <% end %>
44
+ ERB
45
+ end
46
+ template.render.should include "[1, 2]"
47
+ end
48
+ end
@@ -0,0 +1,20 @@
1
+ <%= html_tag(:ul) do %>
2
+ <li>
3
+ <%= html_tag(:a) do %>
4
+ A
5
+ <span>1</span>
6
+ <% end %>
7
+ </li>
8
+ <%= html_tag(:li) do %>
9
+ <%= html_tag(:a) do %>
10
+ B
11
+ <span>2</span>
12
+ <% end %>
13
+ <% end %>
14
+ <%= html_tag(:li) do %>
15
+ <%= html_tag(:a) do %>
16
+ C
17
+ <span>3</span>
18
+ <% end %>
19
+ <% end %>
20
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <%= list do %>
2
+ <%= item_content(1) %>
3
+ <%= item_content(2) %>
4
+ <%= item_content(3) %>
5
+ <% end %>
@@ -0,0 +1,16 @@
1
+ = html_tag(:ul) do
2
+ = html_tag(:li) do
3
+ = html_tag(:a) do
4
+ A
5
+ = html_tag(:span) do
6
+ 1
7
+ = html_tag(:li) do
8
+ = html_tag(:a) do
9
+ B
10
+ = html_tag(:span) do
11
+ 2
12
+ = html_tag(:li) do
13
+ = html_tag(:a) do
14
+ C
15
+ = html_tag(:span) do
16
+ 3
@@ -0,0 +1,4 @@
1
+ = list do |element|
2
+ = item_content(1)
3
+ = item_content(2)
4
+ = item_content(3)
@@ -0,0 +1,303 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe 'markup helpers' do
5
+ include Tiny::Helpers
6
+
7
+ let(:output) do
8
+ Capybara::Node::Simple.new(@output)
9
+ end
10
+
11
+ describe 'tag' do
12
+ describe 'basic' do
13
+ describe 'attributes and content' do
14
+ before do
15
+ @output = tag(:li, :class => 'item', :id => 'hello') { text 'Hello' }
16
+ end
17
+ it { output.should have_css 'li', :count => 1 }
18
+ it { output.should have_css 'li', :text => "Hello" }
19
+ it { output.should have_css 'li.item' }
20
+ it { output.should have_css 'li#hello' }
21
+ end
22
+
23
+ it 'should not use blank attribute values' do
24
+ output = tag(:li, :class => [], :id => nil)
25
+ output.should == "<li></li>"
26
+ end
27
+
28
+ it 'should not emit value for an atribute value of true' do
29
+ output = tag(:li, 'data-something' => true)
30
+ output.should == "<li data-something></li>"
31
+ end
32
+
33
+ it 'should not allow passing text without #text' do
34
+ output = tag(:li) { 'Hello' }
35
+ output.should == '<li></li>'
36
+ end
37
+
38
+ it 'should output multiple classes passing an array' do
39
+ output = tag(:li, :class => %w(item in-stock))
40
+ output.should == '<li class="item in-stock"></li>'
41
+ end
42
+
43
+ it 'should allow passing content as string' do
44
+ tag(:h1, "Hello").should == "<h1>Hello</h1>"
45
+ tag(:h1, "Hello", :class => 'main').should == %{<h1 class="main">Hello</h1>}
46
+ end
47
+
48
+ it 'should escape attribute html' do
49
+ tag(:a, :href => '<script>').should == '<a href="&lt;script&gt;"></a>'
50
+ tag(:a, :href => 'art&copy').should == '<a href="art&amp;copy"></a>'
51
+ end
52
+ end
53
+
54
+ describe 'blocks' do
55
+ describe 'shallow blocks' do
56
+ before do
57
+ @output = tag(:div) { tag(:a) { text 'Hello' } }
58
+ end
59
+ it { output.should have_css 'div', :count => 1 }
60
+ it { output.should have_css 'a', :count => 1 }
61
+ it { output.should have_css 'div > a', :text => 'Hello' }
62
+ end
63
+
64
+ describe 'deeper blocks' do
65
+ before do
66
+ @output = tag(:div) do
67
+ tag(:a) do
68
+ text 'Hello'
69
+ tag(:img)
70
+ end
71
+ end
72
+ end
73
+ it { output.should have_css 'div', :count => 1 }
74
+ it { output.should have_css 'a', :count => 1 }
75
+ it { output.should have_css 'div > a' }
76
+ it { output.should have_css 'div > a', :text => 'Hello' }
77
+ it { output.should have_css 'img', :count => 1 }
78
+ it { output.should have_css 'div > a > img' }
79
+ end
80
+ end
81
+
82
+ describe 'buffering' do
83
+ describe 'tag concatenation' do
84
+ before do
85
+ @output = tag(:ul) do
86
+ tag(:li)
87
+ tag(:li)
88
+ tag(:li)
89
+ end
90
+ end
91
+
92
+ it { output.should have_css 'ul', :count => 1 }
93
+ it { output.should have_css 'li', :count => 3 }
94
+ it { output.should have_css 'ul > li', :count => 3 }
95
+ end
96
+
97
+ describe 'concatenation with text' do
98
+ before do
99
+ @output = tag(:ul) do
100
+ tag(:li) { text 'One' }
101
+ tag(:li) { text 'Two' }
102
+ tag(:li) { text 'Three' }
103
+ end
104
+ end
105
+
106
+ it { output.should have_css 'ul', :count => 1 }
107
+ it { output.should have_css 'li', :count => 3 }
108
+ it { output.should have_css 'ul > li', :count => 3 }
109
+ it { output.should have_css 'ul > li', :text => 'One' }
110
+ it { output.should have_css 'ul > li', :text => 'Two' }
111
+ it { output.should have_css 'ul > li', :text => 'Three' }
112
+ end
113
+
114
+ describe 'nested' do
115
+ before do
116
+ @output = tag(:ul) do
117
+ tag(:li) { tag(:a) { text 'One' } }
118
+ tag(:li) { tag(:a) { text 'Two' } }
119
+ tag(:li) { tag(:a) { text 'Three' } }
120
+ end
121
+ end
122
+
123
+ it { output.should have_css 'ul', :count => 1 }
124
+ it { output.should have_css 'li', :count => 3 }
125
+ it { output.should have_css 'ul > li', :count => 3 }
126
+ it { output.should have_css 'a', :count => 3 }
127
+ it { output.should have_css 'ul > li > a', :text => 'One' }
128
+ it { output.should have_css 'ul > li > a', :text => 'Two' }
129
+ it { output.should have_css 'ul > li > a', :text => 'Three' }
130
+ end
131
+
132
+ describe 'outside content block' do
133
+ it 'should not buffer contiguous tags' do
134
+ tag(:span)
135
+ tag(:a).should == '<a></a>'
136
+ end
137
+ end
138
+ end
139
+
140
+ describe 'text' do
141
+ it 'should escape text' do
142
+ @output = tag(:li){ text '&<>' }
143
+ @output.should =~ /&amp;&lt;&gt;/
144
+ end
145
+
146
+ it 'should allow not scaped text' do
147
+ @output = tag(:li){ append! '&<>' }
148
+ @output.should =~ /&<>/
149
+ end
150
+ end
151
+
152
+ describe 'formatting' do
153
+ it 'should buffer with newlines and indentation' do
154
+ output = tag(:ul) do
155
+ tag :li
156
+ tag :li
157
+ end
158
+ output.should == "<ul>\n <li></li>\n <li></li>\n</ul>"
159
+ end
160
+
161
+ it 'should buffer with newlines after text' do
162
+ output = tag(:ul) do
163
+ tag (:li) do
164
+ text 'Hi'
165
+ append! 'Hi'
166
+ end
167
+ end
168
+ output.should == "<ul>\n <li>\n Hi\n Hi\n </li>\n</ul>"
169
+ end
170
+ end
171
+ end
172
+
173
+ describe 'special nodes' do
174
+ describe 'comments' do
175
+ it 'should emit comment' do
176
+ comment('Hello').should == "<!-- Hello -->"
177
+ comment('Hello -- world').should == "<!-- Hello - - world -->"
178
+ comment('Hello -- -- world').should == "<!-- Hello - - - - world -->"
179
+ end
180
+
181
+ it 'should buffer comments' do
182
+ tag(:div) do
183
+ comment 'foo'
184
+ comment 'bar'
185
+ end.should == "<div>\n <!-- foo -->\n <!-- bar -->\n</div>"
186
+ end
187
+ end
188
+
189
+ describe 'cdata' do
190
+ it 'should emit cdata' do
191
+ cdata('Hello').should == "<![CDATA[Hello]]>"
192
+ end
193
+
194
+ it 'should buffer cdata' do
195
+ tag(:div) do
196
+ cdata('foo')
197
+ cdata('bar')
198
+ end.should == "<div>\n <![CDATA[foo]]>\n <![CDATA[bar]]>\n</div>"
199
+ end
200
+
201
+ it 'should not "escape" cdata terminator' do
202
+ cdata(']]>').should == "<![CDATA[]]]]><![CDATA[>]]>"
203
+ end
204
+ end
205
+
206
+ describe 'doctype' do
207
+ it 'should emit html5 doctype' do
208
+ doctype.should == '<!DOCTYPE html>'
209
+ end
210
+
211
+ it 'should buffer doctype' do
212
+ output = with_buffer{ doctype and tag(:html) }
213
+ output.should == "<!DOCTYPE html>\n<html></html>\n"
214
+
215
+ end
216
+ end
217
+ end
218
+
219
+ describe 'tag closing' do
220
+ describe 'void tags' do
221
+ it 'should define void tags' do
222
+ Tiny::HTML.void_tags.should == %w(area base br col hr img input link meta param embed)
223
+ end
224
+
225
+ Tiny::HTML.void_tags.each do |tag_name|
226
+ describe tag_name do
227
+ it 'sould autoclose' do
228
+ tag(tag_name).should == "<#{tag_name} />"
229
+ end
230
+
231
+ it 'should omit content' do
232
+ tag(tag_name){ text 'hi' }.should == "<#{tag_name} />"
233
+ end
234
+ end
235
+ end
236
+ end
237
+
238
+ describe 'content tags' do
239
+ it 'should define content tags' do
240
+ tags = %w(
241
+ article aside audio bdi canvas command datalist details
242
+ figcaption figure header hgroup keygen mark meter nav output progress
243
+ section source summary track video wbr a abbr address b bdo big
244
+ blockquote body button caption cite code colgroup dd del dfn div dl dt
245
+ em fieldset footer form h1 h2 h3 h4 h5 h6 head html i iframe ins kbd
246
+ label legend li map noscript object ol optgroup option p pre q rp rt
247
+ ruby s samp script select small span strike strong style sub sup table
248
+ tbody td textarea tfoot th thead time title tr tt u ul var
249
+ )
250
+ Tiny::HTML.content_tags.should == tags
251
+ end
252
+
253
+ Tiny::HTML.content_tags.each do |tag_name|
254
+ it "should not autoclose #{tag_name} if not empty" do
255
+ tag(tag_name).should == "<#{tag_name}></#{tag_name}>"
256
+ end
257
+ end
258
+ end
259
+ end
260
+
261
+ describe 'with_buffer' do
262
+ before do
263
+ @output = with_buffer do
264
+ tag(:head) { tag(:title, "Tiny Page!") }
265
+ tag(:body) { tag(:h1, "Hello Tiny!") }
266
+ end
267
+ end
268
+
269
+ it { output.should have_css 'head', :count => 1 }
270
+ it { output.should have_css 'head > title', :text => "Tiny Page!", :count => 1 }
271
+ it { output.should have_css 'body', :count => 1 }
272
+ it { output.should have_css 'body > h1', :text => "Hello Tiny!", :count => 1 }
273
+ end
274
+
275
+ describe 'dsl' do
276
+ include Tiny::HTML
277
+
278
+ describe 'void tags' do
279
+ Tiny::HTML.void_tags.each do |tag_name|
280
+ it "should render '#{tag_name}'" do
281
+ self.send(tag_name).should == "<#{tag_name} />"
282
+ end
283
+ end
284
+
285
+ it "should render attributes" do
286
+ link(:href => "some.css").should == '<link href="some.css" />'
287
+ end
288
+ end
289
+
290
+ describe 'content tags' do
291
+ Tiny::HTML.content_tags.each do |tag_name|
292
+ it "should render '#{tag_name}'" do
293
+ self.send(tag_name).should == "<#{tag_name}></#{tag_name}>"
294
+ end
295
+ end
296
+
297
+ it "should render content and attributes" do
298
+ h1(:class => 'main') { text "Hello" }.should == %{<h1 class="main">\n Hello\n</h1>}
299
+ h1("Hello", :class => 'main').should == %{<h1 class="main">Hello</h1>}
300
+ end
301
+ end
302
+ end
303
+ end