tiny 0.2.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,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