sinatra-tags 0.1.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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+ doc
21
+
22
+ ## PROJECT::SPECIFIC
data/CHANGES ADDED
@@ -0,0 +1,5 @@
1
+
2
+ rel 0.1.0 (2010-03-02)
3
+
4
+ * intial release of gem / code
5
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 kematzy
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,284 @@
1
+ = Sinatra::Tags
2
+
3
+ A Sinatra Extension that provides easy creation of flexible HTML tags.
4
+
5
+ == Why was this gem created ?
6
+
7
+ To enable the Sinatra community to quickly and easily add this functionality to any
8
+ app / extension they wish to create. ie: preventing time waste or the 're-invention of the wheel'.
9
+
10
+
11
+
12
+ == Installation
13
+
14
+ # Add RubyGems.org (former Gemcutter) to your RubyGems sources
15
+ $ gem sources -a http://rubygems.org
16
+
17
+ $ (sudo)? gem install sinatra-tags
18
+
19
+ == Dependencies
20
+
21
+ This Gem depends upon the following:
22
+
23
+ === Runtime:
24
+
25
+ * sinatra ( >= 1.0.a )
26
+ * sinatra-outputbuffer[http://github.com/kematzy/sinatra-outputbuffer] (>= 0.1.0)
27
+
28
+ Optionals:
29
+
30
+ * sinatra-settings[http://github.com/kematzy/sinatra-settings] (>= 0.1.1) # to view default settings in a browser display.
31
+
32
+
33
+ === Development & Tests:
34
+
35
+ * sinatra-tests (>= 0.1.6)
36
+ * rspec (>= 1.3.0 )
37
+ * rack-test (>= 0.5.3)
38
+ * rspec_hpricot_matchers (>= 0.1.0)
39
+
40
+ == Getting Started
41
+
42
+ To start using Sinatra::Tags, just require and register the extension
43
+
44
+ ...in your App...
45
+
46
+ require 'sinatra/tags'
47
+
48
+ class YourApp < Sinatra::Base
49
+ register(Sinatra::Tags)
50
+
51
+ <snip...>
52
+
53
+ end
54
+
55
+ ...or your Extension...
56
+
57
+ require 'sinatra/tags'
58
+
59
+ module Sinatra
60
+ module YourExtension
61
+
62
+ <snip...>
63
+
64
+ def self.registered(app)
65
+ app.register Sinatra::Tags
66
+ <snip...>
67
+ end
68
+
69
+ end
70
+ end
71
+
72
+
73
+ == Usage
74
+
75
+ Sinatra::Tags has only <b>one public method</b>, with this <b>dynamic</b> syntax:
76
+
77
+ tag(name)
78
+ tag(name, &block)
79
+
80
+ tag(name, content)
81
+ tag(name, content, attributes)
82
+ tag(name, content, attributes, &block)
83
+
84
+ tag(name, attributes)
85
+ tag(name, attributes, &block)
86
+
87
+
88
+ This dynamic syntax provides a very flexible method as you can see in the examples below:
89
+
90
+
91
+ <b>Self closing tags:</b>
92
+
93
+ tag(:br) # =>
94
+
95
+ <br> or <br/> if XHTML
96
+
97
+ tag(:hr, :class => "space") # =>
98
+
99
+ <hr class="space">
100
+
101
+ <b>Multi line tags:</b>
102
+
103
+ tag(:div) # =>
104
+
105
+ <div></div>
106
+
107
+ tag(:div, 'content') # =>
108
+
109
+ <div>
110
+ content
111
+ </div>
112
+
113
+ tag(:div, 'content', :id => 'comment') # =>
114
+
115
+ <div id="comment">
116
+ content
117
+ </div>
118
+
119
+ # NB! no content
120
+ tag(:div, :id => 'comment') # =>
121
+
122
+ <div id="comment"></div>
123
+
124
+
125
+ <b>Single line tags:</b>
126
+
127
+ tag(:h1,'Header') # =>
128
+
129
+ <h1>Header</h1>
130
+
131
+ tag(:abbr, 'WHO', :title => "World Health Organization") # =>
132
+
133
+ <abbr title="World Health Organization">WHO</abbr>
134
+
135
+
136
+ <b>Working with blocks</b>
137
+
138
+ tag(:div) do
139
+ tag(:p, 'Hello World')
140
+ end
141
+ # =>
142
+
143
+ <div>
144
+ <p>Hello World</p>
145
+ </div>
146
+
147
+ <% tag(:ul) do %>
148
+ <li>item 1</li>
149
+ <%= tag(:li, 'item 2') %>
150
+ <li>item 3</li>
151
+ <% end %>
152
+ # =>
153
+
154
+ <ul>
155
+ <li>item 1</li>
156
+ <li>item 2</li>
157
+ <li>item 3</li>
158
+ </ul>
159
+
160
+
161
+ # NB! ignored tag contents if given a block
162
+ <% tag(:div, 'ignored tag-content') do %>
163
+ <%= tag(:label, 'Comments:', :for => :comments) %>
164
+ <%= tag(:textarea,'textarea contents', :id => :comments) %>
165
+ <% end %>
166
+ # =>
167
+
168
+ <div>
169
+ <label for="comments">Comments:</label>
170
+ <textarea id="comments">
171
+ textarea contents
172
+ </textarea>
173
+ </div>
174
+
175
+
176
+
177
+ <b>Boolean attributes:</b>
178
+
179
+ tag(:input, :type => :checkbox, :checked => true)
180
+ # =>
181
+
182
+ <input type="checkbox" checked="checked" />
183
+
184
+
185
+ tag(:option, 'Sinatra', :value => "1" :selected => true)
186
+ # =>
187
+
188
+ <option value="1">Sinatra</option>
189
+
190
+
191
+ tag(:option, 'PHP', :value => "0" :selected => false)
192
+ # =>
193
+
194
+ <option value="0">PHP</option>
195
+
196
+
197
+ That's more or less it. Try it out and you'll see what it can do for you.
198
+
199
+
200
+ == Configuration Settings
201
+
202
+ The default settings should help you get moving quickly, and are fairly common sense based.
203
+
204
+
205
+ ==== <tt>:tags_output_format_is_xhtml</tt>
206
+
207
+ Sets the HTML output format, toggling between HTML vs XHTML.
208
+ Default is: <tt>false</tt>
209
+
210
+ I prefer to output in HTML 4.0.1 Strict, but you can easily switch to XHTML
211
+ by setting the value in your App or Extension:
212
+
213
+ set :tags_output_format_is_xhtml, true
214
+
215
+ ...or on the fly like this
216
+
217
+ YourApp.tags_output_format_is_xhtml = true / false
218
+
219
+ self.class.tags_output_format_is_xhtml = true / false
220
+
221
+ settings.tags_output_format_is_xhtml = true / false
222
+
223
+
224
+ ==== <tt>:tags_add_newlines_after_tags</tt>
225
+
226
+ Sets the formatting of the HTML output, whether it should be more compact in nature
227
+ or slightly better layed out.
228
+ Default is: <tt>true</tt>
229
+
230
+
231
+ == RTFM
232
+
233
+ If the above is not clear enough, please check the Specs for a better understanding.
234
+
235
+
236
+ == Errors / Bugs
237
+
238
+ If something is not behaving intuitively, it is a bug, and should be reported.
239
+ Report it here: http://github.com/kematzy/sinatra-tags/issues
240
+
241
+
242
+ == TODOs
243
+
244
+ * Keep it up to date with any changes in Sinatra.
245
+
246
+ * Decide on if it's worth it to do validity checks on all attributes passed to tags
247
+ ie: reject attributes based upon what is allowed for the tag.
248
+
249
+ tag(:base, :href => 'url', :target => '_self', :id => 'is-ignored')
250
+ # =>
251
+
252
+ <base href="url", target="_self">
253
+
254
+ * Decide on whether to add a number of convenience tags (methods), such as:
255
+
256
+ - meta(name, contents)
257
+
258
+ - img(src, attrs)
259
+
260
+
261
+ * Any other improvements I or You can think of.
262
+
263
+
264
+ == Note on Patches/Pull Requests
265
+
266
+ * Fork the project.
267
+ * Make your feature addition or bug fix.
268
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
269
+ * Commit, do not mess with rakefile, version, or history.
270
+ * (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
271
+ * Send me a pull request. Bonus points for topic branches.
272
+
273
+ == Copyright
274
+
275
+ Copyright (c) 2010 Kematzy
276
+
277
+ Released under the MIT License.
278
+
279
+ See LICENSE for further details.
280
+
281
+ === Code Inspirations:
282
+
283
+ * The ActiveSupport gem by DHH & Rails Core Team
284
+
data/Rakefile ADDED
@@ -0,0 +1,91 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "sinatra-tags"
8
+ gem.summary = %Q{A Sinatra Extension that provides easy creation of flexible HTML tags.}
9
+ gem.description = %Q{A Sinatra Extension that provides easy creation of flexible HTML tags.}
10
+ gem.email = "kematzy@gmail.com"
11
+ gem.homepage = "http://github.com/kematzy/sinatra-tags"
12
+ gem.authors = ["kematzy"]
13
+ gem.add_dependency('sinatra', '>=1.0.a')
14
+ gem.add_dependency('sinatra-outputbuffer', '>=0.1.0')
15
+ gem.add_development_dependency "sinatra-tests", ">= 0.1.6"
16
+ gem.add_development_dependency "rspec", ">= 1.3.0"
17
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
+ end
19
+ Jeweler::GemcutterTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
22
+ end
23
+
24
+ require 'spec/rake/spectask'
25
+ Spec::Rake::SpecTask.new(:spec) do |spec|
26
+ spec.libs << 'lib' << 'spec'
27
+ spec.spec_opts = ["--color", "--format", "specdoc", "--require", "spec/spec_helper.rb"]
28
+ spec.spec_files = FileList['spec/**/*_spec.rb']
29
+ end
30
+
31
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
32
+ spec.libs << 'lib' << 'spec'
33
+ spec.spec_opts = ["--color", "--format", "specdoc", "--require", "spec/spec_helper.rb"]
34
+ spec.pattern = 'spec/**/*_spec.rb'
35
+ spec.rcov = true
36
+ end
37
+
38
+ namespace :spec do
39
+
40
+ desc "Run all specifications quietly"
41
+ Spec::Rake::SpecTask.new(:quiet) do |t|
42
+ t.libs << "lib"
43
+ t.spec_opts = ["--color", "--require", "spec/spec_helper.rb"]
44
+ end
45
+
46
+ desc "Run specific spec verbosely (SPEC=/path/2/file)"
47
+ Spec::Rake::SpecTask.new(:select) do |t|
48
+ t.libs << "lib"
49
+ t.spec_files = [ENV["SPEC"]]
50
+ t.spec_opts = ["--color", "--format", "specdoc", "--require", "spec/spec_helper.rb"]
51
+ end
52
+
53
+ end
54
+
55
+ task :spec => :check_dependencies
56
+
57
+ task :default => :spec
58
+
59
+ require 'rake/rdoctask'
60
+ Rake::RDocTask.new do |rdoc|
61
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
62
+
63
+ rdoc.rdoc_dir = 'rdoc'
64
+ rdoc.title = "Sinatra::Tags #{version}"
65
+ rdoc.rdoc_files.include('README*')
66
+ rdoc.rdoc_files.include('lib/**/*.rb')
67
+ end
68
+
69
+ desc 'Build the rdoc HTML Files'
70
+ task :docs do
71
+ version = File.exist?('VERSION') ? IO.read('VERSION').chomp : "[Unknown]"
72
+
73
+ sh "sdoc -N --title 'Sinatra::Tags v#{version}' lib/ README.rdoc"
74
+ end
75
+
76
+ namespace :docs do
77
+
78
+ desc 'Remove rdoc products'
79
+ task :remove => [:clobber_rdoc]
80
+
81
+ desc 'Force a rebuild of the RDOC files'
82
+ task :rebuild => [:rerdoc]
83
+
84
+ desc 'Build docs, and open in browser for viewing (specify BROWSER)'
85
+ task :open => [:docs] do
86
+ browser = ENV["BROWSER"] || "safari"
87
+ sh "open -a #{browser} doc/index.html"
88
+ end
89
+
90
+ end
91
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,587 @@
1
+
2
+ require 'sinatra/base'
3
+ require 'sinatra/outputbuffer'
4
+
5
+ # :stopdoc:
6
+ class Hash
7
+
8
+ # remove any other version of #to_html_attributes
9
+ undef_method :to_html_attributes if method_defined?(:to_html_attributes)
10
+
11
+ def to_html_attributes(include_empties=nil)
12
+ hash = self.dup
13
+ hash.reject! {|k,v| v.blank? } unless include_empties.nil?
14
+ out = ''
15
+ hash.keys.each do |key|
16
+ val = hash[key].is_a?(Array) ? hash[key].join('_') : hash[key].to_s
17
+ out << "#{key.to_s}=\"#{val}\" "
18
+ end
19
+ out.strip
20
+ end
21
+
22
+ end
23
+ # :startdoc:
24
+
25
+ module Sinatra
26
+
27
+ # = Sinatra::Tags
28
+ #
29
+ # A Sinatra Extension that provides easy creation of flexible HTML tags.
30
+ #
31
+ # == Installation
32
+ #
33
+ # # Add RubyGems.org (former Gemcutter) to your RubyGems sources
34
+ # $ gem sources -a http://rubygems.org
35
+ #
36
+ # $ (sudo)? gem install sinatra-tags
37
+ #
38
+ # == Dependencies
39
+ #
40
+ # This Gem depends upon the following:
41
+ #
42
+ # === Runtime:
43
+ #
44
+ # * sinatra ( >= 1.0.a )
45
+ # * sinatra-outputbuffer[http://github.com/kematzy/sinatra-outputbuffer] (>= 0.1.0)
46
+ #
47
+ # Optionals:
48
+ #
49
+ # * sinatra-settings[http://github.com/kematzy/sinatra-settings] (>= 0.1.1) # to view default settings in a browser display.
50
+ #
51
+ #
52
+ # === Development & Tests:
53
+ #
54
+ # * sinatra-tests (>= 0.1.6)
55
+ # * rspec (>= 1.3.0 )
56
+ # * rack-test (>= 0.5.3)
57
+ # * rspec_hpricot_matchers (>= 0.1.0)
58
+ #
59
+ # == Getting Started
60
+ #
61
+ # To start using Sinatra::Tags, just require and register the extension
62
+ #
63
+ # ...in your App...
64
+ #
65
+ # require 'sinatra/tags'
66
+ #
67
+ # class YourApp < Sinatra::Base
68
+ # register(Sinatra::Tags)
69
+ #
70
+ # <snip...>
71
+ #
72
+ # end
73
+ #
74
+ # ...or your Extension...
75
+ #
76
+ # require 'sinatra/tags'
77
+ #
78
+ # module Sinatra
79
+ # module YourExtension
80
+ #
81
+ # <snip...>
82
+ #
83
+ # def self.registered(app)
84
+ # app.register Sinatra::Tags
85
+ # <snip...>
86
+ # end
87
+ #
88
+ # end
89
+ # end
90
+ #
91
+ #
92
+ # == Usage
93
+ #
94
+ # Sinatra::Tags has only <b>one public method</b>, with this <b>dynamic</b> syntax:
95
+ #
96
+ # tag(name)
97
+ # tag(name, &block)
98
+ #
99
+ # tag(name, content)
100
+ # tag(name, content, attributes)
101
+ # tag(name, content, attributes, &block)
102
+ #
103
+ # tag(name, attributes)
104
+ # tag(name, attributes, &block)
105
+ #
106
+ #
107
+ # This dynamic syntax provides a very flexible method as you can see in the examples below:
108
+ #
109
+ #
110
+ # <b>Self closing tags:</b>
111
+ #
112
+ # tag(:br) # =>
113
+ #
114
+ # <br> or <br/> if XHTML
115
+ #
116
+ # tag(:hr, :class => "space") # =>
117
+ #
118
+ # <hr class="space">
119
+ #
120
+ # <b>Multi line tags:</b>
121
+ #
122
+ # tag(:div) # =>
123
+ #
124
+ # <div></div>
125
+ #
126
+ # tag(:div, 'content') # =>
127
+ #
128
+ # <div>
129
+ # content
130
+ # </div>
131
+ #
132
+ # tag(:div, 'content', :id => 'comment') # =>
133
+ #
134
+ # <div id="comment">
135
+ # content
136
+ # </div>
137
+ #
138
+ # # NB! no content
139
+ # tag(:div, :id => 'comment') # =>
140
+ #
141
+ # <div id="comment"></div>
142
+ #
143
+ #
144
+ # <b>Single line tags:</b>
145
+ #
146
+ # tag(:h1,'Header') # =>
147
+ #
148
+ # <h1>Header</h1>
149
+ #
150
+ # tag(:abbr, 'WHO', :title => "World Health Organization") # =>
151
+ #
152
+ # <abbr title="World Health Organization">WHO</abbr>
153
+ #
154
+ #
155
+ # <b>Working with blocks</b>
156
+ #
157
+ # tag(:div) do
158
+ # tag(:p, 'Hello World')
159
+ # end
160
+ # # =>
161
+ #
162
+ # <div>
163
+ # <p>Hello World</p>
164
+ # </div>
165
+ #
166
+ # <% tag(:ul) do %>
167
+ # <li>item 1</li>
168
+ # <%= tag(:li, 'item 2') %>
169
+ # <li>item 3</li>
170
+ # <% end %>
171
+ # # =>
172
+ #
173
+ # <ul>
174
+ # <li>item 1</li>
175
+ # <li>item 2</li>
176
+ # <li>item 3</li>
177
+ # </ul>
178
+ #
179
+ #
180
+ # # NB! ignored tag contents if given a block
181
+ # <% tag(:div, 'ignored tag-content') do %>
182
+ # <%= tag(:label, 'Comments:', :for => :comments) %>
183
+ # <%= tag(:textarea,'textarea contents', :id => :comments) %>
184
+ # <% end %>
185
+ # # =>
186
+ #
187
+ # <div>
188
+ # <label for="comments">Comments:</label>
189
+ # <textarea id="comments">
190
+ # textarea contents
191
+ # </textarea>
192
+ # </div>
193
+ #
194
+ #
195
+ #
196
+ # <b>Boolean attributes:</b>
197
+ #
198
+ # tag(:input, :type => :checkbox, :checked => true)
199
+ # # =>
200
+ #
201
+ # <input type="checkbox" checked="checked" />
202
+ #
203
+ #
204
+ # tag(:option, 'Sinatra', :value => "1" :selected => true)
205
+ # # =>
206
+ #
207
+ # <option value="1">Sinatra</option>
208
+ #
209
+ #
210
+ # tag(:option, 'PHP', :value => "0" :selected => false)
211
+ # # =>
212
+ #
213
+ # <option value="0">PHP</option>
214
+ #
215
+ #
216
+ # That's more or less it. Try it out and you'll see what it can do for you.
217
+ #
218
+ #
219
+ # == Configuration Settings
220
+ #
221
+ # The default settings should help you get moving quickly, and are fairly common sense based.
222
+ #
223
+ #
224
+ # ==== <tt>:tags_output_format_is_xhtml</tt>
225
+ #
226
+ # Sets the HTML output format, toggling between HTML vs XHTML.
227
+ # Default is: <tt>false</tt>
228
+ #
229
+ # I prefer to output in HTML 4.0.1 Strict, but you can easily switch to XHTML
230
+ # by setting the value in your App or Extension:
231
+ #
232
+ # set :tags_output_format_is_xhtml, true
233
+ #
234
+ # ...or on the fly like this
235
+ #
236
+ # YourApp.tags_output_format_is_xhtml = true / false
237
+ #
238
+ # self.class.tags_output_format_is_xhtml = true / false
239
+ #
240
+ # settings.tags_output_format_is_xhtml = true / false
241
+ #
242
+ #
243
+ # ==== <tt>:tags_add_newlines_after_tags</tt>
244
+ #
245
+ # Sets the formatting of the HTML output, whether it should be more compact in nature
246
+ # or slightly better layed out.
247
+ # Default is: <tt>true</tt>
248
+ #
249
+ #
250
+ # == Copyright
251
+ #
252
+ # Copyright (c) 2010 Kematzy
253
+ #
254
+ # Released under the MIT License.
255
+ #
256
+ # See LICENSE for further details.
257
+ #
258
+ # === Code Inspirations:
259
+ #
260
+ # * The ActiveSupport gem by DHH & Rails Core Team
261
+ #
262
+ module Tags
263
+
264
+ VERSION = '0.1.0' unless const_defined?(:VERSION)
265
+
266
+ ##
267
+ # Returns the version string for this extension
268
+ #
269
+ # ==== Examples
270
+ #
271
+ # Sinatra::Tags.version => 'Sinatra::Tags v0.1.0'
272
+ #
273
+ def self.version; "Sinatra::Tags v#{VERSION}"; end
274
+
275
+
276
+ module Helpers
277
+
278
+ ##
279
+ # Tags that should be rendered in multiple lines, like...
280
+ #
281
+ # <body>
282
+ # <snip...>
283
+ # </body>
284
+ #
285
+ MULTI_LINE_TAGS = %w(
286
+ a address applet bdo big blockquote body button caption center
287
+ colgroup dd dir div dl dt fieldset form frameset head html iframe
288
+ map noframes noscript object ol optgroup pre script select small
289
+ style table tbody td textarea tfoot th thead title tr tt ul
290
+ )
291
+
292
+ ##
293
+ # Self closing tags, like...
294
+ #
295
+ # <hr> or <hr />
296
+ #
297
+ SELF_CLOSING_TAGS = %w( area base br col frame hr img input link meta param )
298
+
299
+ ##
300
+ # Tags that should be rendered in a single line, like...
301
+ #
302
+ # <h1>Header</h1>
303
+ #
304
+ SINGLE_LINE_TAGS = %w(
305
+ abbr acronym b cite code del dfn em h1 h2 h3 h4 h5 h6 i kbd
306
+ label legend li option p q samp span strong sub sup var
307
+ )
308
+
309
+ ##
310
+ # Boolean attributes, ie: attributes like...
311
+ #
312
+ # <input type="text" selected="selected"...>
313
+ #
314
+ BOOLEAN_ATTRIBUTES = %w( selected checked disabled readonly multiple )
315
+
316
+ ##
317
+ # Returns markup for tag _name_.
318
+ #
319
+ # Optionally _contents_ may be passed, which is literal content for
320
+ # spanning tags such as <tt>textarea</tt>, etc.
321
+ #
322
+ # A hash of _attrs_ may be passed as the *second* or *third* argument.
323
+ #
324
+ # Self closing tags such as <tt><br/></tt>, <tt><input/></tt>, etc
325
+ # are automatically closed depending on output format, HTML vs XHTML.
326
+ #
327
+ # Boolean attributes like "<tt>selected</tt>", "<tt>checked</tt>" etc,
328
+ # are mirrored or removed when <tt>true</tt> or <tt>false</tt>.
329
+ #
330
+ # ==== Examples
331
+ #
332
+ # Self closing tags:
333
+ #
334
+ # tag(:br)
335
+ # # => <br> or <br/> if XHTML
336
+ #
337
+ # tag(:hr, :class => "space")
338
+ # # => <hr class="space">
339
+ #
340
+ # Multi line tags:
341
+ #
342
+ # tag(:div)
343
+ # # => <div></div>
344
+ #
345
+ # tag(:div, 'content')
346
+ # # => <div>content</div>
347
+ #
348
+ # tag(:div, 'content', :id => 'comment')
349
+ # # => <div id="comment">content</div>
350
+ #
351
+ # tag(:div, :id => 'comment') # NB! no content
352
+ # # => <div id="comment"></div>
353
+ #
354
+ # Single line tags:
355
+ #
356
+ # tag(:h1,'Header')
357
+ # # => <h1>Header</h1>
358
+ #
359
+ # tag(:abbr, 'WHO', :title => "World Health Organization")
360
+ # # => <abbr title="World Health Organization">WHO</abbr>
361
+ #
362
+ # Working with blocks
363
+ #
364
+ # tag(:div) do
365
+ # tag(:p, 'Hello World')
366
+ # end
367
+ # # => <div><p>Hello World</p></div>
368
+ #
369
+ # <% tag(:div) do %>
370
+ # <p>Paragraph 1</p>
371
+ # <%= tag(:p, 'Paragraph 2') %>
372
+ # <p>Paragraph 3</p>
373
+ # <% end %>
374
+ # # =>
375
+ # <div>
376
+ # <p>Paragraph 1</p>
377
+ # <p>Paragraph 2</p>
378
+ # <p>Paragraph 3</p>
379
+ # </div>
380
+ #
381
+ #
382
+ # # NB! ignored tag contents if given a block
383
+ # <% tag(:div, 'ignored tag-content') do %>
384
+ # <%= tag(:label, 'Comments:', :for => :comments) %>
385
+ # <%= tag(:textarea,'textarea contents', :id => :comments) %>
386
+ # <% end %>
387
+ # # =>
388
+ # <div>
389
+ # <label for="comments">Comments:</label>
390
+ # <textarea id="comments">
391
+ # textarea contents
392
+ # </textarea>
393
+ # </div>
394
+ #
395
+ #
396
+ #
397
+ # Boolean attributes:
398
+ #
399
+ # tag(:input, :type => :checkbox, :checked => true)
400
+ # # => <input type="checkbox" checked="checked" />
401
+ #
402
+ # tag(:option, 'Sinatra', :value => "1" :selected => true)
403
+ # # => <option value="1">Sinatra</option>
404
+ #
405
+ # tag(:option, 'PHP', :value => "0" :selected => false)
406
+ # # => <option value="0">PHP</option>
407
+ #
408
+ #
409
+ # @api public
410
+ def tag(*args, &block)
411
+ name = args.first
412
+ attrs = args.last.is_a?(::Hash) ? args.pop : {}
413
+ newline = attrs[:newline] # save before it gets tainted
414
+
415
+ tag_content = block_given? ? capture_html(&block) : args[1] # content
416
+
417
+ if self_closing_tag?(name)
418
+ tag_html = self_closing_tag(name, attrs)
419
+ else
420
+ tag_html = open_tag(name, attrs) + tag_contents_for(name, tag_content, newline) + closing_tag(name)
421
+ end
422
+ block_is_template?(block) ? concat_content(tag_html) : tag_html
423
+ end
424
+
425
+
426
+ private
427
+
428
+ ##
429
+ # Return an opening tag of _name_, with _attrs_.
430
+ #
431
+ # @api private
432
+ def open_tag(name, attrs = {})
433
+ "<#{name}#{normalize_html_attributes(attrs)}>"
434
+ end
435
+
436
+ ##
437
+ # Return closing tag of _name_.
438
+ #
439
+ # @api private
440
+ def closing_tag(name)
441
+ "</#{name}>#{add_newline?}"
442
+ end
443
+
444
+ ##
445
+ # Creates a self closing tag. Like <br/> or <img src="..."/>
446
+ #
447
+ # ==== Options
448
+ # +name+ : the name of the tag to create
449
+ # +attrs+ : a hash where all members will be mapped to key="value"
450
+ #
451
+ # @api private
452
+ def self_closing_tag(name, attrs = {})
453
+ newline = (attrs[:newline].nil?) ? nil : attrs.delete(:newline)
454
+ "<#{name}#{normalize_html_attributes(attrs)}#{is_xhtml?}#{add_newline?(newline)}"
455
+ end
456
+
457
+ ##
458
+ # Based upon the context, wraps the tag content in '\n' (newlines)
459
+ #
460
+ # ==== Examples
461
+ #
462
+ # tag_contents_for(:div, 'content', nil)
463
+ # # => <div>\ncontent\n</div>
464
+ #
465
+ # tag_contents_for(:div, 'content', false)
466
+ # # => <div>content</div>
467
+ #
468
+ # Single line tag
469
+ # tag_contents_for(:option, 'content', true)
470
+ # # => <option...>\ncontent\n</option>
471
+ #
472
+ # @api private
473
+ def tag_contents_for(name, content, newline = nil )
474
+ if multi_line_tag?(name)
475
+ "#{add_newline?(newline)}#{content.to_s}#{add_newline?(newline)}"
476
+ elsif single_line_tag?(name) && newline === true
477
+ "#{add_newline?(newline)}#{content.to_s}#{add_newline?(newline)}"
478
+ else
479
+ content.to_s
480
+ end
481
+ end
482
+
483
+ ##
484
+ # Normalize _attrs_, replacing boolean keys
485
+ # with their mirrored values.
486
+ #
487
+ # @api private
488
+ def normalize_html_attributes(attrs = {})
489
+ return if attrs.blank?
490
+ attrs.delete(:newline) # remove newline from attributes
491
+ attrs.each do |name, value|
492
+ if boolean_attribute?(name)
493
+ value === true ? attrs[name] = name : attrs.delete(name)
494
+ end
495
+ end
496
+ return attrs.empty? ? '' : ' ' + attrs.to_html_attributes
497
+ end
498
+
499
+ ##
500
+ # Check if _name_ is a boolean attribute.
501
+ #
502
+ # @api private
503
+ def boolean_attribute?(name)
504
+ BOOLEAN_ATTRIBUTES.include?(name.to_s)
505
+ end
506
+
507
+ ##
508
+ # Check if tag _name_ is a self-closing tag.
509
+ #
510
+ # @api private
511
+ def self_closing_tag?(name)
512
+ SELF_CLOSING_TAGS.include?(name.to_s)
513
+ end
514
+
515
+ ##
516
+ # Check if tag _name_ is a single line tag.
517
+ #
518
+ # @api private
519
+ def single_line_tag?(name)
520
+ SINGLE_LINE_TAGS.include?(name.to_s)
521
+ end
522
+
523
+ ##
524
+ # Check if tag _name_ is a multi line tag.
525
+ #
526
+ # @api private
527
+ def multi_line_tag?(name)
528
+ MULTI_LINE_TAGS.include?(name.to_s)
529
+ end
530
+
531
+ ##
532
+ # Returns '>' or ' />' based on the output format used,
533
+ # ie: HTML vs XHTML
534
+ #
535
+ # @api private
536
+ def is_xhtml?
537
+ settings.tags_output_format_is_xhtml ? " />" : ">"
538
+ end
539
+
540
+ ##
541
+ #
542
+ #
543
+ # @api private
544
+ def add_newline?(add_override=nil)
545
+ add = (add_override.nil?) ? settings.tags_add_newlines_after_tags : add_override
546
+ add === true ? "\n" : ''
547
+ end
548
+
549
+ end #/ Helpers
550
+
551
+ ##
552
+ # Registers these Extensions:
553
+ #
554
+ # * Sinatra::OutputBuffer
555
+ #
556
+ # Default Settings:
557
+ #
558
+ # * +:tags_output_format_is_xhtml+ => sets the HTML output format. Default is: +false+
559
+ #
560
+ # * +:tags_add_newlines_after_tags+ => sets whether to add new lines to the HTML tags. Default is: +true+
561
+ #
562
+ #
563
+ # @api public
564
+ def self.registered(app)
565
+ app.register(Sinatra::OutputBuffer)
566
+ app.helpers Sinatra::Tags::Helpers
567
+
568
+ # set the HTML output formats
569
+ app.set :tags_output_format_is_xhtml, false
570
+ # set the HTML output formats
571
+ app.set :tags_add_newlines_after_tags, true
572
+
573
+ ## add the extension specific options to those inspectable by :settings_inspect method
574
+ # provided by the Sinatra::Settings extension
575
+ if app.respond_to?(:sinatra_settings_for_inspection)
576
+ %w( tags_add_newlines_after_tags tags_output_format_is_xhtml ).each do |m|
577
+ app.sinatra_settings_for_inspection << m
578
+ end
579
+ end
580
+
581
+ end #/ self.registered
582
+
583
+ end #/ Tags
584
+
585
+ register(Sinatra::Tags)
586
+
587
+ end #/ Sinatra