roda-tags 0.1.1
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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +24 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +1083 -0
- data/Rakefile +21 -0
- data/lib/core_ext/blank.rb +158 -0
- data/lib/core_ext/hash.rb +47 -0
- data/lib/core_ext/object.rb +31 -0
- data/lib/core_ext/string.rb +330 -0
- data/lib/roda/plugins/tag_helpers.rb +919 -0
- data/lib/roda/plugins/tags.rb +452 -0
- data/lib/roda/tags.rb +3 -0
- data/lib/roda/tags/version.rb +8 -0
- data/roda-tags.gemspec +49 -0
- metadata +237 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
require 'roda'
|
|
2
|
+
require_relative '../../core_ext/hash' unless {}.respond_to?(:to_html_attributes)
|
|
3
|
+
require_relative '../../core_ext/blank' unless Object.new.respond_to?(:blank?)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Roda
|
|
7
|
+
|
|
8
|
+
#
|
|
9
|
+
module RodaPlugins
|
|
10
|
+
|
|
11
|
+
# TODO: Add documentation here
|
|
12
|
+
#
|
|
13
|
+
#
|
|
14
|
+
module RodaTags
|
|
15
|
+
|
|
16
|
+
# default options
|
|
17
|
+
OPTS = {
|
|
18
|
+
# toggle for XHTML formatted output in case of legacy
|
|
19
|
+
tag_output_format_is_xhtml: false,
|
|
20
|
+
# toggle for adding newlines after output
|
|
21
|
+
tag_add_newlines_after_tags: true
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
# Tags that should be rendered in multiple lines, like...
|
|
25
|
+
#
|
|
26
|
+
# <body>
|
|
27
|
+
# <snip...>
|
|
28
|
+
# </body>
|
|
29
|
+
#
|
|
30
|
+
MULTI_LINE_TAGS = %w(
|
|
31
|
+
a address applet bdo big blockquote body button caption center
|
|
32
|
+
colgroup dd dir div dl dt fieldset form frameset head html iframe
|
|
33
|
+
map noframes noscript object ol optgroup pre script section select small
|
|
34
|
+
style table tbody td tfoot th thead title tr tt ul
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Self closing tags, like...
|
|
38
|
+
#
|
|
39
|
+
# <hr> or <hr />
|
|
40
|
+
#
|
|
41
|
+
SELF_CLOSING_TAGS = %w( area base br col frame hr img input link meta param )
|
|
42
|
+
|
|
43
|
+
# Tags that should be rendered in a single line, like...
|
|
44
|
+
#
|
|
45
|
+
# <h1>Header</h1>
|
|
46
|
+
#
|
|
47
|
+
SINGLE_LINE_TAGS = %w(
|
|
48
|
+
abbr acronym b cite code del dfn em h1 h2 h3 h4 h5 h6 i kbd
|
|
49
|
+
label legend li option p q samp span strong sub sup var
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Boolean attributes, ie: attributes like...
|
|
53
|
+
#
|
|
54
|
+
# <option value="a" selected="selected">A</option>
|
|
55
|
+
#
|
|
56
|
+
BOOLEAN_ATTRIBUTES = %w(autofocus checked disabled multiple readonly required selected)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# Depend on the render plugin, since this plugin only makes
|
|
60
|
+
# sense when the render plugin is used.
|
|
61
|
+
def self.load_dependencies(app, opts = OPTS)
|
|
62
|
+
app.plugin :render
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.configure(app, opts = {})
|
|
66
|
+
if app.opts[:tags]
|
|
67
|
+
opts = app.opts[:tags][:orig_opts].merge(opts)
|
|
68
|
+
else
|
|
69
|
+
opts = OPTS.merge(opts)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
app.opts[:tags] = opts.dup
|
|
73
|
+
app.opts[:tags][:orig_opts] = opts
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
#
|
|
77
|
+
module ClassMethods
|
|
78
|
+
|
|
79
|
+
# Return the uitags options for this class.
|
|
80
|
+
def tags_opts
|
|
81
|
+
opts[:tags]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
#
|
|
87
|
+
module InstanceMethods
|
|
88
|
+
|
|
89
|
+
# Returns markup for tag _name_.
|
|
90
|
+
#
|
|
91
|
+
# Optionally _contents_ may be passed, which is literal content for spanning tags such as
|
|
92
|
+
# <tt>textarea</tt>, etc.
|
|
93
|
+
#
|
|
94
|
+
# A hash of _attrs_ may be passed as the *second* or *third* argument.
|
|
95
|
+
#
|
|
96
|
+
# Self closing tags such as <tt><br/></tt>, <tt><input/></tt>, etc are automatically closed
|
|
97
|
+
# depending on output format, HTML vs XHTML.
|
|
98
|
+
#
|
|
99
|
+
# Boolean attributes like "<tt>selected</tt>", "<tt>checked</tt>" etc, are mirrored or
|
|
100
|
+
# removed when <tt>true</tt> or <tt>false</tt>.
|
|
101
|
+
#
|
|
102
|
+
# ==== Examples
|
|
103
|
+
#
|
|
104
|
+
# Self closing tags:
|
|
105
|
+
#
|
|
106
|
+
# tag(:br)
|
|
107
|
+
# # => <br> or <br/> if XHTML
|
|
108
|
+
#
|
|
109
|
+
# tag(:hr, class: "space")
|
|
110
|
+
# # => <hr class="space">
|
|
111
|
+
#
|
|
112
|
+
# Multi line tags:
|
|
113
|
+
#
|
|
114
|
+
# tag(:div)
|
|
115
|
+
# # => <div></div>
|
|
116
|
+
#
|
|
117
|
+
# tag(:div, 'content')
|
|
118
|
+
# # => <div>content</div>
|
|
119
|
+
#
|
|
120
|
+
# tag(:div, 'content', id: 'comment')
|
|
121
|
+
# # => <div id="comment">content</div>
|
|
122
|
+
#
|
|
123
|
+
# tag(:div, id: 'comment') # NB! no content
|
|
124
|
+
# # => <div id="comment"></div>
|
|
125
|
+
#
|
|
126
|
+
# Single line tags:
|
|
127
|
+
#
|
|
128
|
+
# tag(:h1,'Header')
|
|
129
|
+
# # => <h1>Header</h1>
|
|
130
|
+
#
|
|
131
|
+
# tag(:abbr, 'WHO', :title => "World Health Organization")
|
|
132
|
+
# # => <abbr title="World Health Organization">WHO</abbr>
|
|
133
|
+
#
|
|
134
|
+
# Working with blocks
|
|
135
|
+
#
|
|
136
|
+
# tag(:div) do
|
|
137
|
+
# tag(:p, 'Hello World')
|
|
138
|
+
# end
|
|
139
|
+
# # => <div><p>Hello World</p></div>
|
|
140
|
+
#
|
|
141
|
+
# <% tag(:div) do %>
|
|
142
|
+
# <p>Paragraph 1</p>
|
|
143
|
+
# <%= tag(:p, 'Paragraph 2') %>
|
|
144
|
+
# <p>Paragraph 3</p>
|
|
145
|
+
# <% end %>
|
|
146
|
+
# # =>
|
|
147
|
+
# <div>
|
|
148
|
+
# <p>Paragraph 1</p>
|
|
149
|
+
# <p>Paragraph 2</p>
|
|
150
|
+
# <p>Paragraph 3</p>
|
|
151
|
+
# </div>
|
|
152
|
+
#
|
|
153
|
+
#
|
|
154
|
+
# # NB! ignored tag contents if given a block
|
|
155
|
+
# <% tag(:div, 'ignored tag-content') do %>
|
|
156
|
+
# <%= tag(:label, 'Comments:', for: :comments) %>
|
|
157
|
+
# <%= tag(:textarea,'textarea contents', id: :comments) %>
|
|
158
|
+
# <% end %>
|
|
159
|
+
# # =>
|
|
160
|
+
# <div>
|
|
161
|
+
# <label for="comments">Comments:</label>
|
|
162
|
+
# <textarea id="comments">
|
|
163
|
+
# textarea contents
|
|
164
|
+
# </textarea>
|
|
165
|
+
# </div>
|
|
166
|
+
#
|
|
167
|
+
#
|
|
168
|
+
#
|
|
169
|
+
# Boolean attributes:
|
|
170
|
+
#
|
|
171
|
+
# tag(:input, type: :checkbox, checked: true)
|
|
172
|
+
# # => <input type="checkbox" checked="checked">
|
|
173
|
+
#
|
|
174
|
+
# tag(:option, 'Sinatra', value: "1", selected: true)
|
|
175
|
+
# # => <option value="1" selected>Sinatra</option>
|
|
176
|
+
#
|
|
177
|
+
# tag(:option, 'PHP', value: "0", selected: false)
|
|
178
|
+
# # => <option value="0">PHP</option>
|
|
179
|
+
#
|
|
180
|
+
def tag(*args, &block)
|
|
181
|
+
name = args.first
|
|
182
|
+
attrs = args.last.is_a?(Hash) ? args.pop : {}
|
|
183
|
+
newline = attrs[:newline] # save before it gets tainted
|
|
184
|
+
|
|
185
|
+
tag_content = block_given? ? capture_html(&block) : args[1] # content
|
|
186
|
+
|
|
187
|
+
if self_closing_tag?(name)
|
|
188
|
+
tag_html = self_closing_tag(name, attrs)
|
|
189
|
+
else
|
|
190
|
+
tag_html = "#{open_tag(name, attrs)}#{tag_contents_for(name, tag_content, newline)}"
|
|
191
|
+
tag_html << closing_tag(name)
|
|
192
|
+
end
|
|
193
|
+
block_is_template?(block) ? concat_content(tag_html) : tag_html
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Update the +:class+ entry in the +attr+ hash with the given +classes+ and returns +attr+.
|
|
197
|
+
#
|
|
198
|
+
# attr = { class: 'alert', id: :idval }
|
|
199
|
+
#
|
|
200
|
+
# merge_attr_classes(attr, 'alert-info') #=> { class: 'alert alert-info', id: :idval }
|
|
201
|
+
#
|
|
202
|
+
# merge_attr_classes(attr, [:alert, 'alert-info'])
|
|
203
|
+
# #=> { class: 'alert alert-info', id: :idval }
|
|
204
|
+
#
|
|
205
|
+
def merge_attr_classes(attr, *classes)
|
|
206
|
+
attr[:class] = [] if attr[:class].blank?
|
|
207
|
+
attr[:class] = merge_classes(attr[:class], *classes)
|
|
208
|
+
attr[:class] = nil if attr[:class] == '' # set to nil to remove from tag output
|
|
209
|
+
attr
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Return an alphabetized string that includes all given class values.
|
|
213
|
+
#
|
|
214
|
+
# Handles a combination of arrays, strings & symbols being passed in.
|
|
215
|
+
#
|
|
216
|
+
# attr = { class: 'alert', id: :idval }
|
|
217
|
+
#
|
|
218
|
+
# merge_classes(attr[:class], ['alert', 'alert-info']) #=> 'alert alert-info'
|
|
219
|
+
#
|
|
220
|
+
# merge_classes(attr[:class], :text) #=> 'alert text'
|
|
221
|
+
#
|
|
222
|
+
# merge_classes(attr[:class], [:text, :'alert-info']) #=> 'alert alert-info text'
|
|
223
|
+
#
|
|
224
|
+
#
|
|
225
|
+
def merge_classes(*classes)
|
|
226
|
+
klasses = []
|
|
227
|
+
classes.each do |c|
|
|
228
|
+
klasses << c.to_s if c.is_a?(Symbol)
|
|
229
|
+
c.split(/\s+/).each { |x| klasses << x.to_s } if c.is_a?(String)
|
|
230
|
+
c.each { |i| klasses << i.to_s } if c.is_a?(Array)
|
|
231
|
+
end
|
|
232
|
+
klasses.compact.uniq.sort.join(' ').strip
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
## HELPERS
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
#
|
|
240
|
+
def capture(block = '') # :nodoc:
|
|
241
|
+
buf_was = @output
|
|
242
|
+
@output = block.is_a?(Proc) ? (eval('@_out_buf', block.binding) || @output) : block
|
|
243
|
+
yield
|
|
244
|
+
ret = @output
|
|
245
|
+
@output = buf_was
|
|
246
|
+
ret
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Captures the html from a block of template code for erb or haml
|
|
250
|
+
#
|
|
251
|
+
# ==== Examples
|
|
252
|
+
#
|
|
253
|
+
# capture_html(&block) => "...html..."
|
|
254
|
+
#
|
|
255
|
+
def capture_html(*args, &block)
|
|
256
|
+
if self.respond_to?(:is_haml?) && is_haml?
|
|
257
|
+
block_is_haml?(block) ? capture_haml(*args, &block) : block.call
|
|
258
|
+
elsif erb_buffer?
|
|
259
|
+
result_text = capture_block(*args, &block)
|
|
260
|
+
result_text.present? ? result_text : (block_given? && block.call(*args))
|
|
261
|
+
else # theres no template to capture, invoke the block directly
|
|
262
|
+
block.call(*args)
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Outputs the given text to the templates buffer directly.
|
|
267
|
+
#
|
|
268
|
+
# ==== Examples
|
|
269
|
+
#
|
|
270
|
+
# concat_content("This will be output to the template buffer in erb or haml")
|
|
271
|
+
#
|
|
272
|
+
def concat_content(text = '')
|
|
273
|
+
if self.respond_to?(:is_haml?) && is_haml?
|
|
274
|
+
haml_concat(text)
|
|
275
|
+
elsif :erb_buffer?
|
|
276
|
+
buffer_concat(text)
|
|
277
|
+
else # theres no template to concat, return the text directly
|
|
278
|
+
text
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Returns true if the block is from an ERB or HAML template; false otherwise.
|
|
283
|
+
# Used to determine if html should be returned or concatenated to a view.
|
|
284
|
+
#
|
|
285
|
+
# ==== Examples
|
|
286
|
+
#
|
|
287
|
+
# block_is_template?(block)
|
|
288
|
+
#
|
|
289
|
+
def block_is_template?(block)
|
|
290
|
+
block && (erb_block?(block) ||
|
|
291
|
+
(self.respond_to?(:block_is_haml?) && block_is_haml?(block)))
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
#
|
|
295
|
+
def output_is_xhtml?
|
|
296
|
+
opts[:tags][:tag_output_format_is_xhtml]
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
private
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
# Return an opening tag of _name_, with _attrs_.
|
|
304
|
+
def open_tag(name, attrs = {})
|
|
305
|
+
"<#{name}#{normalize_html_attributes(attrs)}>"
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Return closing tag of _name_.
|
|
309
|
+
def closing_tag(name)
|
|
310
|
+
"</#{name}>#{add_newline?}"
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Creates a self closing tag. Like <br/> or <img src="..."/>
|
|
314
|
+
#
|
|
315
|
+
# ==== Options
|
|
316
|
+
# +name+ : the name of the tag to create
|
|
317
|
+
# +attrs+ : a hash where all members will be mapped to key="value"
|
|
318
|
+
#
|
|
319
|
+
def self_closing_tag(name, attrs = {})
|
|
320
|
+
newline = (attrs[:newline].nil?) ? nil : attrs.delete(:newline)
|
|
321
|
+
"<#{name}#{normalize_html_attributes(attrs)}#{is_xhtml?}#{add_newline?(newline)}"
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Based upon the context, wraps the tag content in '\n' (newlines)
|
|
325
|
+
#
|
|
326
|
+
# ==== Examples
|
|
327
|
+
#
|
|
328
|
+
# tag_contents_for(:div, 'content', nil)
|
|
329
|
+
# # => <div>content</div>
|
|
330
|
+
#
|
|
331
|
+
# tag_contents_for(:div, 'content', false)
|
|
332
|
+
# # => <div>content</div>
|
|
333
|
+
#
|
|
334
|
+
# Single line tag
|
|
335
|
+
# tag_contents_for(:option, 'content', true)
|
|
336
|
+
# # => <option...>\ncontent\n</option>
|
|
337
|
+
#
|
|
338
|
+
def tag_contents_for(name, content, newline = nil)
|
|
339
|
+
if multi_line_tag?(name)
|
|
340
|
+
"#{add_newline?(newline)}#{content}#{add_newline?(newline)}".gsub(/\n\n/, "\n")
|
|
341
|
+
elsif single_line_tag?(name) && newline == true
|
|
342
|
+
"#{add_newline?(newline)}#{content}#{add_newline?(newline)}"
|
|
343
|
+
else
|
|
344
|
+
content.to_s
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Normalize _attrs_, replacing boolean keys with their mirrored values.
|
|
349
|
+
def normalize_html_attributes(attrs = {})
|
|
350
|
+
return if attrs.blank?
|
|
351
|
+
attrs.delete(:newline) # remove newline from attributes
|
|
352
|
+
# look for data attrs
|
|
353
|
+
if value = attrs.delete(:data)
|
|
354
|
+
# NB!! convert key to symbol for [].sort
|
|
355
|
+
value.each { |k, v| attrs[:"data-#{k.to_s}"] = v }
|
|
356
|
+
end
|
|
357
|
+
attrs.each do |name, val|
|
|
358
|
+
if boolean_attribute?(name)
|
|
359
|
+
val == true ? attrs[name] = name : attrs.delete(name)
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
return attrs.empty? ? '' : ' ' + attrs.to_html_attributes
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# Check if _name_ is a boolean attribute.
|
|
366
|
+
def boolean_attribute?(name)
|
|
367
|
+
BOOLEAN_ATTRIBUTES.include?(name.to_s)
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Check if tag _name_ is a self-closing tag.
|
|
371
|
+
def self_closing_tag?(name)
|
|
372
|
+
SELF_CLOSING_TAGS.include?(name.to_s)
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# Check if tag _name_ is a single line tag.
|
|
376
|
+
def single_line_tag?(name)
|
|
377
|
+
SINGLE_LINE_TAGS.include?(name.to_s)
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# Check if tag _name_ is a multi line tag.
|
|
381
|
+
def multi_line_tag?(name)
|
|
382
|
+
MULTI_LINE_TAGS.include?(name.to_s)
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
# Returns a '>' or ' />' string based on the output format used, ie: HTML vs XHTML
|
|
386
|
+
def xhtml?
|
|
387
|
+
opts[:tags][:tag_output_format_is_xhtml] ? ' />' : '>'
|
|
388
|
+
end
|
|
389
|
+
alias_method :is_xhtml?, :xhtml?
|
|
390
|
+
|
|
391
|
+
#
|
|
392
|
+
def add_newline?(add_override = nil)
|
|
393
|
+
add = (add_override.nil?) ? opts[:tags][:tag_add_newlines_after_tags] : add_override
|
|
394
|
+
add == true ? "\n" : ''
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# concat contents to the buffer if present
|
|
398
|
+
#
|
|
399
|
+
# ==== Examples
|
|
400
|
+
#
|
|
401
|
+
# buffer_concat("Direct to buffer")
|
|
402
|
+
#
|
|
403
|
+
def buffer_concat(txt)
|
|
404
|
+
@_out_buf << txt if buffer?
|
|
405
|
+
end
|
|
406
|
+
alias_method :erb_concat, :buffer_concat
|
|
407
|
+
|
|
408
|
+
# Used to capture the contents of html/ERB block
|
|
409
|
+
#
|
|
410
|
+
# ==== Examples
|
|
411
|
+
#
|
|
412
|
+
# capture_block(&block) => '...html...'
|
|
413
|
+
#
|
|
414
|
+
def capture_block(*args, &block)
|
|
415
|
+
with_output_buffer { block_given? && block.call(*args) }
|
|
416
|
+
end
|
|
417
|
+
alias_method :capture_erb, :capture_block
|
|
418
|
+
|
|
419
|
+
# Used to direct the buffer for the erb capture
|
|
420
|
+
def with_output_buffer(buf = '')
|
|
421
|
+
@_out_buf, old_buffer = buf, @_out_buf
|
|
422
|
+
yield
|
|
423
|
+
@_out_buf
|
|
424
|
+
ensure
|
|
425
|
+
@_out_buf = old_buffer
|
|
426
|
+
end
|
|
427
|
+
alias_method :erb_with_output_buffer, :with_output_buffer
|
|
428
|
+
|
|
429
|
+
# returns true if the buffer is not empty
|
|
430
|
+
def buffer?
|
|
431
|
+
!@_out_buf.nil?
|
|
432
|
+
end
|
|
433
|
+
alias_method :have_buffer?, :buffer?
|
|
434
|
+
alias_method :erb_buffer?, :buffer?
|
|
435
|
+
|
|
436
|
+
#
|
|
437
|
+
def erb_block?(block)
|
|
438
|
+
have_buffer? || block && eval('defined? __in_erb_template', block.binding)
|
|
439
|
+
end
|
|
440
|
+
alias_method :is_erb_block?, :erb_block?
|
|
441
|
+
alias_method :is_erb_template?, :erb_block?
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
end # /InstanceMethods
|
|
445
|
+
|
|
446
|
+
end # /RodaTags
|
|
447
|
+
|
|
448
|
+
register_plugin(:tags, RodaTags)
|
|
449
|
+
|
|
450
|
+
end # /RodaPlugins
|
|
451
|
+
|
|
452
|
+
end
|
data/lib/roda/tags.rb
ADDED