htmless 0.4
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/MIT-LICENSE +19 -0
- data/README.md +7 -0
- data/README_FULL.md +585 -0
- data/lib/copy_to_doc.rb +43 -0
- data/lib/htmless/abstract/abstract_double_tag.rb +144 -0
- data/lib/htmless/abstract/abstract_single_tag.rb +18 -0
- data/lib/htmless/abstract/abstract_tag.rb +246 -0
- data/lib/htmless/abstract.rb +252 -0
- data/lib/htmless/data/html5.rb +179 -0
- data/lib/htmless/data.rb +11 -0
- data/lib/htmless/doc.rb +394 -0
- data/lib/htmless/dynamic_classes.rb +216 -0
- data/lib/htmless/formatted.rb +43 -0
- data/lib/htmless/helper.rb +24 -0
- data/lib/htmless/pool.rb +72 -0
- data/lib/htmless/rails.rb +40 -0
- data/lib/htmless/standard.rb +48 -0
- data/lib/htmless/strings_injector.rb +43 -0
- data/lib/htmless.rb +3 -0
- data/lib/js.rb +176 -0
- data/spec/htmless_spec.rb +290 -0
- metadata +279 -0
@@ -0,0 +1,179 @@
|
|
1
|
+
module Htmless
|
2
|
+
module Data
|
3
|
+
|
4
|
+
single_tags = %w[area base br col embed hr img input link meta param]
|
5
|
+
|
6
|
+
double_tags = %w[
|
7
|
+
a abbr article aside audio address b bdo blockquote body button canvas caption cite code colgroup command
|
8
|
+
datalist dd del details dfn div dl dt em fieldset figure footer form h1 h2 h3 h4 h5 h6 head header hgroup html
|
9
|
+
i iframe ins keygen kbd label legend li map mark meter nav noscript object ol optgroup option p pre progress
|
10
|
+
q ruby rt rp s samp script section select small source span strong style sub sup table tbody td textarea tfoot
|
11
|
+
th thead time title tr u ul var video]
|
12
|
+
|
13
|
+
global_attributes = %w[
|
14
|
+
accesskey class contenteditable contextmenu dir draggable dropzone hidden id lang
|
15
|
+
spellcheck style tabindex title onabort onblur oncanplay oncanplaythrough onchange
|
16
|
+
onclick oncontextmenu oncuechange ondblclick ondrag ondragend ondragenter ondragleave
|
17
|
+
ondragover ondragstart ondrop ondurationchange onemptied onended onerror onfocus oninput
|
18
|
+
oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart
|
19
|
+
onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay
|
20
|
+
onplaying onprogress onratechange onreadystatechange onreset onscroll onseeked onseeking
|
21
|
+
onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange onwaiting ]
|
22
|
+
|
23
|
+
tag_attributes = {
|
24
|
+
:a => %w[href target ping rel media hreflang type],
|
25
|
+
:abbr => %w[], :address => %w[],
|
26
|
+
:area => %w[alt coords shape href target ping rel media hreflang type],
|
27
|
+
:article => %w[], :aside => %w[],
|
28
|
+
:audio => %w[src preload autoplay mediagroup loop controls],
|
29
|
+
:b => %w[],
|
30
|
+
:base => %w[href target],
|
31
|
+
:bdi => %w[], :bdo => %w[],
|
32
|
+
:blockquote => %w[cite],
|
33
|
+
:body => %w[onafterprint onbeforeprint onbeforeunload onblur onerror onfocus onhashchange onload
|
34
|
+
onmessage onoffline ononline onpagehide onpageshow onpopstate onredo onresize onscroll
|
35
|
+
onstorage onundo onunload],
|
36
|
+
:br => %w[],
|
37
|
+
:button => %w[autofocus disabled form formaction formenctype formmethod formnovalidate formtarget name
|
38
|
+
type value],
|
39
|
+
:canvas => %w[width height],
|
40
|
+
:caption => %w[], :cite => %w[], :code => %w[], :col => %w[span],
|
41
|
+
:colgroup => %w[span],
|
42
|
+
:command => %w[type label icon disabled checked radiogroup],
|
43
|
+
:datalist => %w[option],
|
44
|
+
:dd => %w[],
|
45
|
+
:del => %w[cite datetime],
|
46
|
+
:details => %w[open],
|
47
|
+
:dfn => %w[], :div => %w[], :dl => %w[], :dt => %w[], :em => %w[],
|
48
|
+
:embed => %w[src type width height],
|
49
|
+
:fieldset => %w[disabled form name],
|
50
|
+
:figcaption => %w[], :figure => %w[], :footer => %w[],
|
51
|
+
:form => %w[action autocomplete enctype method name novalidate target accept_charset],
|
52
|
+
:h1 => %w[], :h2 => %w[], :h3 => %w[], :h4 => %w[], :h5 => %w[], :h6 => %w[], :head => %w[],
|
53
|
+
:header => %w[], :hgroup => %w[], :hr => %w[],
|
54
|
+
:html => %w[manifest],
|
55
|
+
:i => %w[],
|
56
|
+
:iframe => %w[src srcdoc name sandbox seamless width height],
|
57
|
+
:img => %w[alt src usemap ismap width height],
|
58
|
+
:input => %w[accept alt autocomplete autofocus checked dirname disabled form formaction formenctype
|
59
|
+
formmethod formnovalidate formtarget height list max maxlength min multiple name pattern
|
60
|
+
placeholder readonly required size src step type value width],
|
61
|
+
:ins => %w[cite datetime],
|
62
|
+
:kbd => %w[],
|
63
|
+
:keygen => %w[autofocus challenge disabled form keytype name],
|
64
|
+
:label => %w[form for],
|
65
|
+
:legend => %w[],
|
66
|
+
:li => %w[value],
|
67
|
+
:link => %w[href rel media hreflang type sizes],
|
68
|
+
:map => %w[name],
|
69
|
+
:mark => %w[],
|
70
|
+
:menu => %w[type label],
|
71
|
+
:meta => %w[name content charset http_equiv],
|
72
|
+
:meter => %w[value min max low high optimum form],
|
73
|
+
:nav => %w[], :noscript => %w[],
|
74
|
+
:object => %w[data type name usemap form width height],
|
75
|
+
:ol => %w[reversed start],
|
76
|
+
:optgroup => %w[disabled label],
|
77
|
+
:option => %w[disabled label selected value],
|
78
|
+
:output => %w[for form name],
|
79
|
+
:p => %w[],
|
80
|
+
:param => %w[name value],
|
81
|
+
:pre => %w[],
|
82
|
+
:progress => %w[value max form],
|
83
|
+
:q => %w[cite],
|
84
|
+
:rp => %w[], :rt => %w[], :ruby => %w[], :s => %w[], :samp => %w[],
|
85
|
+
:script => %w[src async defer type charset],
|
86
|
+
:section => %w[],
|
87
|
+
:select => %w[autofocus disabled form multiple name required size],
|
88
|
+
:small => %w[],
|
89
|
+
:source => %w[src type media],
|
90
|
+
:span => %w[], :strong => %w[],
|
91
|
+
:style => %w[media type scoped],
|
92
|
+
:sub => %w[], :summary => %w[], :sup => %w[],
|
93
|
+
:table => %w[border],
|
94
|
+
:tbody => %w[],
|
95
|
+
:td => %w[colspan rowspan headers],
|
96
|
+
:textarea => %w[autofocus cols disabled form maxlength name placeholder readonly required rows wrap],
|
97
|
+
:tfoot => %w[],
|
98
|
+
:th => %w[colspan rowspan headers scope],
|
99
|
+
:thead => %w[],
|
100
|
+
:time => %w[datetime pubdate],
|
101
|
+
:title => %w[], :tr => %w[],
|
102
|
+
:track => %w[default kind label src srclang],
|
103
|
+
:u => %w[], :ul => %w[], :var => %w[],
|
104
|
+
:video => %w[src poster preload autoplay mediagroup loop controls width height],
|
105
|
+
:wbr => %w[]
|
106
|
+
}
|
107
|
+
|
108
|
+
boolean_attributes = {
|
109
|
+
:all => %w{hidden draggable iscontenteditable spellcheck},
|
110
|
+
:style => %w{scoped},
|
111
|
+
:script => %w{async defer},
|
112
|
+
:ol => %w{reversed},
|
113
|
+
:time => %w{pubdate},
|
114
|
+
:img => %w{ismap},
|
115
|
+
:iframe => %w{seamless},
|
116
|
+
:track => %w{default},
|
117
|
+
:audio => %w{autoplay loop controls},
|
118
|
+
:video => %w{autoplay loop controls},
|
119
|
+
:form => %w{novalidate},
|
120
|
+
:fieldset => %w{disabled},
|
121
|
+
:input => %w{autofocus checked disabled formnovalidate multiple readonly
|
122
|
+
required},
|
123
|
+
:button => %w{autofocus disabled formnovalidate},
|
124
|
+
:select => %w{autofocus disabled multiple required},
|
125
|
+
:optgroup => %w{disabled},
|
126
|
+
:option => %w{disabled selected},
|
127
|
+
:textarea => %w{autofocus disabled readonly required},
|
128
|
+
:keygen => %w{autofocus disabled},
|
129
|
+
:details => %w{open},
|
130
|
+
:command => %w{disabled checked}
|
131
|
+
}
|
132
|
+
|
133
|
+
enumerable_attributes = {
|
134
|
+
'all' => [{ 'dir' => %w{ltr rtl auto} }],
|
135
|
+
'meta' => [{ 'http-equiv' => %w{content-language content-type default-style refresh set-cookie} }],
|
136
|
+
'track' => [{ 'kind' => %w{subtitles captions descriptions chapters metadata} }],
|
137
|
+
'video' => [{ 'preload' => %w{none metadata auto} }],
|
138
|
+
'area' => [{ 'shape' => %w{circle default poly rect} }],
|
139
|
+
'th' => [{ 'scope' => %w{row col rowgroup colgroup auto} }],
|
140
|
+
'form' => [{ 'autocomplete' => %w{on off} }], # ala boolean
|
141
|
+
'input' => [{ 'type' => %w{hidden text search tel url email password datetime date month week time
|
142
|
+
datetime-local number range color checkbox radio file submit image reset button} },
|
143
|
+
'autocomplete' => %w{on off}], # ommited for default?,
|
144
|
+
'button' => [{ 'type' => %w{submit reset button} }],
|
145
|
+
'textarea' => [{ 'wrap' => %w{soft hard} }]
|
146
|
+
# TODO
|
147
|
+
}
|
148
|
+
|
149
|
+
attribute_type = lambda do |tag, attr|
|
150
|
+
if boolean_attributes[:all].include?(attr) || (boolean_attributes[tag] && boolean_attributes[tag].include?(attr))
|
151
|
+
:boolean
|
152
|
+
else
|
153
|
+
:string
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
HTML5 = OpenStruct.new(
|
158
|
+
:abstract_attributes => global_attributes.map { |attr| Attribute.new(attr.to_sym, attribute_type[name, attr]) },
|
159
|
+
|
160
|
+
:single_tags => single_tags.map(&:to_sym).map do |name|
|
161
|
+
Tag.new(name,
|
162
|
+
tag_attributes[name].map do |attr|
|
163
|
+
Attribute.new(attr.to_sym, attribute_type[name, attr])
|
164
|
+
end)
|
165
|
+
end,
|
166
|
+
|
167
|
+
:double_tags => double_tags.map(&:to_sym).map do |name|
|
168
|
+
Tag.new(name,
|
169
|
+
tag_attributes[name].map do |attr|
|
170
|
+
Attribute.new(attr.to_sym, attribute_type[name, attr])
|
171
|
+
end)
|
172
|
+
end)
|
173
|
+
|
174
|
+
#require 'pp'
|
175
|
+
#pp HTML5
|
176
|
+
#pp HTML5.abstract_attributes
|
177
|
+
#pp HTML5.simple_tags
|
178
|
+
end
|
179
|
+
end
|
data/lib/htmless/data.rb
ADDED
data/lib/htmless/doc.rb
ADDED
@@ -0,0 +1,394 @@
|
|
1
|
+
module Htmless
|
2
|
+
module StubBuilderForDocumentation
|
3
|
+
class AbstractTag
|
4
|
+
|
5
|
+
|
6
|
+
def self.strings_injector
|
7
|
+
dynamic_class_base.strings_injector
|
8
|
+
end
|
9
|
+
|
10
|
+
def self._attributes=(_attributes)
|
11
|
+
@_attributes = _attributes
|
12
|
+
end
|
13
|
+
|
14
|
+
def self._attributes
|
15
|
+
@_attributes or (superclass._attributes if superclass.respond_to? :_attributes)
|
16
|
+
end
|
17
|
+
|
18
|
+
self._attributes = []
|
19
|
+
|
20
|
+
# @return [Array<String>] array of available attributes for the tag
|
21
|
+
def self.attributes
|
22
|
+
_attributes
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [String] tag's name
|
26
|
+
def self.tag_name
|
27
|
+
@tag || superclass.tag_name
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
# sets the tag's name
|
33
|
+
# @api private
|
34
|
+
def self.set_tag(tag)
|
35
|
+
@tag = tag.to_s.freeze
|
36
|
+
end
|
37
|
+
|
38
|
+
set_tag 'abstract'
|
39
|
+
|
40
|
+
# defines dynamically methods for attributes
|
41
|
+
# @api private
|
42
|
+
def self.define_attribute_methods
|
43
|
+
attributes.each { |attr| define_attribute_method(attr) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.inherited(base)
|
47
|
+
base.define_attribute_methods
|
48
|
+
end
|
49
|
+
|
50
|
+
# defines dynamically method for +attribute+
|
51
|
+
# @param [Data::Attribute] attribute
|
52
|
+
# @api private
|
53
|
+
def self.define_attribute_method(attribute)
|
54
|
+
return if instance_methods.include?(attribute.name)
|
55
|
+
name = attribute.name.to_s
|
56
|
+
content_rendering = attribute_content_rendering(attribute)
|
57
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
58
|
+
def #{name}(content#{' = true' if attribute.type == :boolean})
|
59
|
+
#{content_rendering}
|
60
|
+
self
|
61
|
+
end
|
62
|
+
RUBY
|
63
|
+
end
|
64
|
+
|
65
|
+
# @api private
|
66
|
+
# @param [Data::Attribute] attribute
|
67
|
+
# @returns Ruby code as string
|
68
|
+
def self.attribute_content_rendering(attribute)
|
69
|
+
name = attribute.name.to_s
|
70
|
+
case attribute.type
|
71
|
+
when :string
|
72
|
+
strings_injector.add "attr_#{name}", " #{name.gsub('_', '-')}=#{strings_injector[:quote]}"
|
73
|
+
"@output << @_str_attr_#{name} << CGI.escapeHTML(content.to_s) << @_str_quote"
|
74
|
+
when :boolean
|
75
|
+
strings_injector.add(
|
76
|
+
"attr_#{name}",
|
77
|
+
" #{name.gsub('_', '-')}=#{strings_injector[:quote]}#{name}#{strings_injector[:quote]}")
|
78
|
+
"@output << @_str_attr_#{name} if content"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# adds attribute to class, triggers dynamical creation of needed instance methods etc.
|
83
|
+
# @param [Array<Data::Attribute>] attributes
|
84
|
+
# @api private
|
85
|
+
def self.add_attributes(attributes)
|
86
|
+
attributes = [attributes] unless attributes.is_a? Array
|
87
|
+
raise ArgumentError, attributes.inspect unless attributes.all? { |v| v.is_a? Data::Attribute }
|
88
|
+
self.send :_attributes=, _attributes + attributes
|
89
|
+
define_attribute_methods
|
90
|
+
end
|
91
|
+
|
92
|
+
public
|
93
|
+
|
94
|
+
attr_reader :builder
|
95
|
+
|
96
|
+
# @api private
|
97
|
+
def initialize(builder)
|
98
|
+
@builder = builder
|
99
|
+
@output = builder.instance_eval { @_output }
|
100
|
+
@stack = builder.instance_eval { @_stack }
|
101
|
+
@classes = []
|
102
|
+
@tag_name = self.rclass.tag_name
|
103
|
+
|
104
|
+
self.rclass.strings_injector.inject_to self
|
105
|
+
end
|
106
|
+
|
107
|
+
# @api private
|
108
|
+
def open(attributes = nil)
|
109
|
+
@output << @_str_lt << @tag_name
|
110
|
+
@builder.current = self
|
111
|
+
attributes(attributes)
|
112
|
+
default
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
# it renders attribute using defined attribute method or by rendering attribute directly
|
117
|
+
# @param [String, Symbol] name
|
118
|
+
# @param [#to_s] value
|
119
|
+
def attribute(name, value)
|
120
|
+
return __send__(name, value) if respond_to?(name)
|
121
|
+
@output << @_str_space << name.to_s << @_str_eql_quote << CGI.escapeHTML(value.to_s) << @_str_quote
|
122
|
+
self
|
123
|
+
end
|
124
|
+
|
125
|
+
# @example
|
126
|
+
# div.attributes :id => 'id' # => <div id="id"></div>
|
127
|
+
# div :id => 'id', :class => %w{left right} # => <div id="id" class="left right"></div>
|
128
|
+
# img :src => 'path' # => <img src="path"></div>
|
129
|
+
# attribute`s methods are called on background (in this case #id is called)
|
130
|
+
def attributes(attrs)
|
131
|
+
return self unless attrs
|
132
|
+
attrs.each do |attr, value|
|
133
|
+
if value.kind_of?(Array)
|
134
|
+
__send__(attr, *value)
|
135
|
+
else
|
136
|
+
__send__(attr, value)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
self
|
140
|
+
end
|
141
|
+
|
142
|
+
# original Ruby method for class, class is used for html classes
|
143
|
+
alias_method(:rclass, :class)
|
144
|
+
|
145
|
+
id_class = /^([\w]+)(!|)$/
|
146
|
+
data_attribute = /^data_([a-z_]+)$/
|
147
|
+
METHOD_MISSING_REGEXP = /#{data_attribute}|#{id_class}/ unless defined? METHOD_MISSING_REGEXP
|
148
|
+
|
149
|
+
# allows data-* attributes and id, classes by method_missing
|
150
|
+
def method_missing(method, *args, &block)
|
151
|
+
method = method.to_s
|
152
|
+
if method =~ METHOD_MISSING_REGEXP
|
153
|
+
if $1
|
154
|
+
self.rclass.add_attributes Data::Attribute.new(method, :string)
|
155
|
+
self.send method, *args
|
156
|
+
else
|
157
|
+
self.__send__($3 == '!' ? :id : :class, $2.gsub(@_str_underscore, @_str_dash))
|
158
|
+
self.attributes args.first
|
159
|
+
end
|
160
|
+
else
|
161
|
+
super(method, *args, &block)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
#def respond_to?(symbol, include_private = false)
|
166
|
+
# symbol.to_s =~ METHOD_MISSING_REGEXP || super(symbol, include_private)
|
167
|
+
#end
|
168
|
+
|
169
|
+
strings_injector.add "attr_class", " class=#{strings_injector[:quote]}"
|
170
|
+
# adds classes to the tag by joining +classes+ with ' ' and skipping non-true classes
|
171
|
+
# @param [Array<#to_s>] classes
|
172
|
+
# @example
|
173
|
+
# class(!visible? && 'hidden', 'left') #=> class="hidden left" or class="left"
|
174
|
+
def class(*classes)
|
175
|
+
@classes.push(*classes.select { |c| c })
|
176
|
+
self
|
177
|
+
end
|
178
|
+
|
179
|
+
strings_injector.add "attr_id", " id=#{strings_injector[:quote]}"
|
180
|
+
# adds id to the tag by joining +values+ with '_'
|
181
|
+
# @param [Array<#to_s>] values
|
182
|
+
# @example
|
183
|
+
# id('user', 12) #=> id="user-15"
|
184
|
+
def id(*values)
|
185
|
+
@output << @_str_attr_id << CGI.escapeHTML(values.select { |v| v }.join(@_str_dash)) << @_str_quote
|
186
|
+
self
|
187
|
+
end
|
188
|
+
|
189
|
+
# adds id and class to a tag by an object
|
190
|
+
# @param [Object] obj
|
191
|
+
# To determine the class it looks for .htmless_ref or
|
192
|
+
# it uses class.to_s.underscore.tr('/', '-').
|
193
|
+
# To determine id it looks for #htmless_ref or it takes class and #id or #object_id.
|
194
|
+
# @example
|
195
|
+
# div[AUser.new].with { text 'a' } # => <div id="a_user_1" class="a_user">a</div>
|
196
|
+
def mimic(obj)
|
197
|
+
klass = if obj.class.respond_to? :htmless_ref
|
198
|
+
obj.class.htmless_ref
|
199
|
+
else
|
200
|
+
obj.class.to_s.scan(/[A-Z][a-z\d]*/).join('_').downcase.gsub('::', '-')
|
201
|
+
end
|
202
|
+
|
203
|
+
id = case
|
204
|
+
when obj.respond_to?(:htmless_ref)
|
205
|
+
obj.htmless_ref
|
206
|
+
when obj.respond_to?(:id)
|
207
|
+
[klass, obj.id]
|
208
|
+
else
|
209
|
+
[klass, obj.object_id]
|
210
|
+
end
|
211
|
+
#noinspection RubyArgCount
|
212
|
+
self.class(klass).id(id)
|
213
|
+
end
|
214
|
+
|
215
|
+
alias_method :[], :mimic
|
216
|
+
|
217
|
+
# renders data-* attributes by +hash+
|
218
|
+
# @param [Hash] hash
|
219
|
+
# @example
|
220
|
+
# div.data(:remote => true, :id => 'an_id') # => <div data-remote="true" data-id="an_id"></div>
|
221
|
+
def data(hash)
|
222
|
+
hash.each { |k, v| __send__ "data_#{k}", v }
|
223
|
+
self
|
224
|
+
end
|
225
|
+
|
226
|
+
protected
|
227
|
+
|
228
|
+
# this method is called on each tag opening, useful for default attributes
|
229
|
+
# @example html tag uses this to add xmlns attr.
|
230
|
+
# html # => <html xmlns="http://www.w3.org/1999/xhtml"></html>
|
231
|
+
def default
|
232
|
+
end
|
233
|
+
|
234
|
+
# flushes classes to output
|
235
|
+
# @api private
|
236
|
+
def flush_classes
|
237
|
+
unless @classes.empty?
|
238
|
+
@output << @_str_attr_class << CGI.escapeHTML(@classes.join(@_str_space)) << @_str_quote
|
239
|
+
@classes.clear
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
class AbstractSingleTag < AbstractTag
|
244
|
+
|
245
|
+
nil
|
246
|
+
|
247
|
+
# @api private
|
248
|
+
# closes the tag
|
249
|
+
def flush
|
250
|
+
flush_classes
|
251
|
+
@output << @_str_slash_gt
|
252
|
+
nil
|
253
|
+
end
|
254
|
+
end
|
255
|
+
class AbstractDoubleTag < AbstractTag
|
256
|
+
|
257
|
+
nil
|
258
|
+
|
259
|
+
# @api private
|
260
|
+
def initialize(builder)
|
261
|
+
super
|
262
|
+
@content = nil
|
263
|
+
end
|
264
|
+
|
265
|
+
# allows data-* attributes and id, classes by method_missing
|
266
|
+
def method_missing(method, *args, &block)
|
267
|
+
method = method.to_s
|
268
|
+
if method =~ METHOD_MISSING_REGEXP
|
269
|
+
if $1
|
270
|
+
self.rclass.add_attributes Data::Attribute.new(method.to_sym, :string)
|
271
|
+
self.send method, *args, &block
|
272
|
+
else
|
273
|
+
attributes(if args.last.is_a?(Hash)
|
274
|
+
args.pop
|
275
|
+
end)
|
276
|
+
content args.first
|
277
|
+
self.__send__($3 == '!' ? :id : :class, $2.gsub(@_str_underscore, @_str_dash), &block)
|
278
|
+
end
|
279
|
+
else
|
280
|
+
super(method, *args, &block)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# @api private
|
285
|
+
def open(*args, &block)
|
286
|
+
attributes = if args.last.is_a?(Hash)
|
287
|
+
args.pop
|
288
|
+
end
|
289
|
+
content args[0]
|
290
|
+
super attributes
|
291
|
+
@stack << @tag_name
|
292
|
+
if block
|
293
|
+
with &block
|
294
|
+
else
|
295
|
+
self
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# @api private
|
300
|
+
# closes the tag
|
301
|
+
def flush
|
302
|
+
flush_classes
|
303
|
+
@output << @_str_gt
|
304
|
+
@output << CGI.escapeHTML(@content) if @content
|
305
|
+
@output << @_str_slash_lt << @stack.pop << @_str_gt
|
306
|
+
@content = nil
|
307
|
+
end
|
308
|
+
|
309
|
+
# sets content of the double tag
|
310
|
+
# @example
|
311
|
+
# div 'content' # => <div>content</div>
|
312
|
+
# div.content 'content' # => <div>content</div>
|
313
|
+
# div :content => 'content' # => <div>content</div>
|
314
|
+
def content(content)
|
315
|
+
@content = content.to_s
|
316
|
+
self
|
317
|
+
end
|
318
|
+
|
319
|
+
# renders content of the double tag with block
|
320
|
+
# @yield content of the tag
|
321
|
+
# @example
|
322
|
+
# div { text 'content' } # => <div>content</div>
|
323
|
+
# div :id => 'id' do
|
324
|
+
# text 'content'
|
325
|
+
# end # => <div id="id">content</div>
|
326
|
+
def with
|
327
|
+
flush_classes
|
328
|
+
@output << @_str_gt
|
329
|
+
@content = nil
|
330
|
+
@builder.current = nil
|
331
|
+
yield
|
332
|
+
#if (content = yield).is_a?(String)
|
333
|
+
# @output << EscapeUtils.escape_html(content)
|
334
|
+
#end
|
335
|
+
@builder.flush
|
336
|
+
@output << @_str_slash_lt << @stack.pop << @_str_gt
|
337
|
+
nil
|
338
|
+
end
|
339
|
+
|
340
|
+
alias_method :w, :with
|
341
|
+
|
342
|
+
def mimic(obj, &block)
|
343
|
+
super(obj, &nil)
|
344
|
+
return with(&block) if block
|
345
|
+
self
|
346
|
+
end
|
347
|
+
|
348
|
+
def data(hash, &block)
|
349
|
+
super(hash, &nil)
|
350
|
+
return with(&block) if block
|
351
|
+
self
|
352
|
+
end
|
353
|
+
|
354
|
+
def attribute(name, value, &block)
|
355
|
+
super(name, value, &nil)
|
356
|
+
return with(&block) if block
|
357
|
+
self
|
358
|
+
end
|
359
|
+
|
360
|
+
def attributes(attrs, &block)
|
361
|
+
super(attrs, &nil)
|
362
|
+
return with(&block) if block
|
363
|
+
self
|
364
|
+
end
|
365
|
+
|
366
|
+
protected
|
367
|
+
|
368
|
+
# @api private
|
369
|
+
def self.define_attribute_method(attribute)
|
370
|
+
return if instance_methods(false).include?(attribute.name)
|
371
|
+
name = attribute.name.to_s
|
372
|
+
|
373
|
+
if instance_methods.include?(attribute.name)
|
374
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
375
|
+
def #{name}(*args, &block)
|
376
|
+
super(*args, &nil)
|
377
|
+
return with(&block) if block
|
378
|
+
self
|
379
|
+
end
|
380
|
+
RUBY
|
381
|
+
else
|
382
|
+
content_rendering = attribute_content_rendering(attribute)
|
383
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
384
|
+
def #{name}(content#{' = true' if attribute.type == :boolean}, &block)
|
385
|
+
#{content_rendering}
|
386
|
+
return with(&block) if block
|
387
|
+
self
|
388
|
+
end
|
389
|
+
RUBY
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|