hammer_builder 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +4 -0
- data/README.md +24 -41
- data/lib/hammer_builder.rb +3 -699
- data/lib/hammer_builder/abstract.rb +205 -0
- data/lib/hammer_builder/abstract/abstract_double_tag.rb +148 -0
- data/lib/hammer_builder/abstract/abstract_single_tag.rb +18 -0
- data/lib/hammer_builder/abstract/abstract_tag.rb +233 -0
- data/lib/hammer_builder/data.rb +11 -0
- data/lib/hammer_builder/data/html5.rb +179 -0
- data/lib/hammer_builder/doc.rb +385 -0
- data/lib/hammer_builder/dynamic_classes.rb +136 -130
- data/lib/hammer_builder/formatted.rb +43 -0
- data/lib/hammer_builder/helper.rb +24 -0
- data/lib/hammer_builder/pool.rb +72 -0
- data/lib/hammer_builder/rails.rb +40 -0
- data/lib/hammer_builder/standard.rb +48 -0
- data/lib/hammer_builder/strings.rb +29 -0
- data/spec/hammer_builder_spec.rb +239 -146
- data/spec/spec_helper.rb +3 -1
- metadata +264 -96
@@ -0,0 +1,179 @@
|
|
1
|
+
module HammerBuilder
|
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
|
@@ -0,0 +1,385 @@
|
|
1
|
+
module HammerBuilder
|
2
|
+
module StubBuilderForDocumentation
|
3
|
+
class AbstractTag
|
4
|
+
|
5
|
+
|
6
|
+
class_attribute :_attributes, :instance_writer => false, :instance_reader => false
|
7
|
+
self._attributes = []
|
8
|
+
|
9
|
+
# @return [Array<String>] array of available attributes for the tag
|
10
|
+
def self.attributes
|
11
|
+
_attributes
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [String] tag's name
|
15
|
+
def self.tag_name
|
16
|
+
@tag || superclass.tag_name
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
# sets the tag's name
|
22
|
+
# @api private
|
23
|
+
def self.set_tag(tag)
|
24
|
+
@tag = tag.to_s.freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
set_tag 'abstract'
|
28
|
+
|
29
|
+
# defines dynamically methods for attributes
|
30
|
+
# @api private
|
31
|
+
def self.define_attribute_methods
|
32
|
+
attributes.each { |attr| define_attribute_method(attr) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.inherited(base)
|
36
|
+
base.define_attribute_methods
|
37
|
+
end
|
38
|
+
|
39
|
+
# defines dynamically method for +attribute+
|
40
|
+
# @param [Data::Attribute] attribute
|
41
|
+
# @api private
|
42
|
+
def self.define_attribute_method(attribute)
|
43
|
+
return if instance_methods.include?(attribute.name)
|
44
|
+
name = attribute.name.to_s
|
45
|
+
content_rendering = attribute_content_rendering(attribute)
|
46
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
47
|
+
def #{name}(content#{' = true' if attribute.type == :boolean})
|
48
|
+
#{content_rendering}
|
49
|
+
self
|
50
|
+
end
|
51
|
+
RUBY
|
52
|
+
end
|
53
|
+
|
54
|
+
# @api private
|
55
|
+
# @param [Data::Attribute] attribute
|
56
|
+
# @returns Ruby code as string
|
57
|
+
def self.attribute_content_rendering(attribute)
|
58
|
+
name = attribute.name.to_s
|
59
|
+
case attribute.type
|
60
|
+
when :string
|
61
|
+
Strings.add "attr_#{name}", " #{name.gsub('_', '-')}=\""
|
62
|
+
"@output << Strings::ATTR_#{name.upcase} << CGI.escapeHTML(content.to_s) << Strings::QUOTE"
|
63
|
+
when :boolean
|
64
|
+
Strings.add "attr_#{name}", " #{name.gsub('_', '-')}=\"#{name}\""
|
65
|
+
"@output << Strings::ATTR_#{name.upcase} if content"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# adds attribute to class, triggers dynamical creation of needed instance methods etc.
|
70
|
+
# @param [Array<Data::Attribute>] attributes
|
71
|
+
# @api private
|
72
|
+
def self.add_attributes(attributes)
|
73
|
+
attributes = [attributes] unless attributes.is_a? Array
|
74
|
+
raise ArgumentError, attributes.inspect unless attributes.all? { |v| v.is_a? Data::Attribute }
|
75
|
+
self.send :_attributes=, _attributes + attributes
|
76
|
+
define_attribute_methods
|
77
|
+
end
|
78
|
+
|
79
|
+
public
|
80
|
+
|
81
|
+
attr_reader :builder
|
82
|
+
|
83
|
+
# @api private
|
84
|
+
def initialize(builder)
|
85
|
+
@builder = builder
|
86
|
+
@output = builder.instance_eval { @_output }
|
87
|
+
@stack = builder.instance_eval { @_stack }
|
88
|
+
@classes = []
|
89
|
+
@tag_name = self.rclass.tag_name
|
90
|
+
end
|
91
|
+
|
92
|
+
# @api private
|
93
|
+
def open(attributes = nil)
|
94
|
+
@output << Strings::LT << @tag_name
|
95
|
+
@builder.current = self
|
96
|
+
attributes(attributes)
|
97
|
+
default
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
101
|
+
# it renders attribute using defined attribute method or by rendering attribute directly
|
102
|
+
# @param [String, Symbol] name
|
103
|
+
# @param [#to_s] value
|
104
|
+
def attribute(name, value)
|
105
|
+
return __send__(name, value) if respond_to?(name)
|
106
|
+
@output << Strings::SPACE << name.to_s << Strings::EQL_QUOTE << CGI.escapeHTML(value.to_s) << Strings::QUOTE
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
# @example
|
111
|
+
# div.attributes :id => 'id' # => <div id="id"></div>
|
112
|
+
# div :id => 'id', :class => %w{left right} # => <div id="id" class="left right"></div>
|
113
|
+
# img :src => 'path' # => <img src="path"></div>
|
114
|
+
# attribute`s methods are called on background (in this case #id is called)
|
115
|
+
def attributes(attrs)
|
116
|
+
return self unless attrs
|
117
|
+
attrs.each do |attr, value|
|
118
|
+
if value.kind_of?(Array)
|
119
|
+
__send__(attr, *value)
|
120
|
+
else
|
121
|
+
__send__(attr, value)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
self
|
125
|
+
end
|
126
|
+
|
127
|
+
# original Ruby method for class, class is used for html classes
|
128
|
+
alias_method(:rclass, :class)
|
129
|
+
|
130
|
+
id_class = /^([\w]+)(!|)$/
|
131
|
+
data_attribute = /^data_([a-z_]+)$/
|
132
|
+
METHOD_MISSING_REGEXP = /#{data_attribute}|#{id_class}/ unless defined? METHOD_MISSING_REGEXP
|
133
|
+
|
134
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
135
|
+
# allows data-* attributes and id, classes by method_missing
|
136
|
+
def method_missing(method, *args, &block)
|
137
|
+
method = method.to_s
|
138
|
+
if method =~ METHOD_MISSING_REGEXP
|
139
|
+
if $1
|
140
|
+
self.rclass.add_attributes Data::Attribute.new(method, :string)
|
141
|
+
self.send method, *args
|
142
|
+
else
|
143
|
+
self.__send__($3 == '!' ? :id : :class, $2)
|
144
|
+
end
|
145
|
+
else
|
146
|
+
super(method, *args, &block)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
#def respond_to?(symbol, include_private = false)
|
151
|
+
# symbol.to_s =~ METHOD_MISSING_REGEXP || super(symbol, include_private)
|
152
|
+
#end
|
153
|
+
RUBY
|
154
|
+
|
155
|
+
Strings.add "attr_class", " class=\""
|
156
|
+
# adds classes to the tag by joining +classes+ with ' ' and skipping non-true classes
|
157
|
+
# @param [Array<#to_s>] classes
|
158
|
+
# @example
|
159
|
+
# class(!visible? && 'hidden', 'left') #=> class="hidden left" or class="left"
|
160
|
+
def class(*classes)
|
161
|
+
@classes.push(*classes.select { |c| c })
|
162
|
+
self
|
163
|
+
end
|
164
|
+
|
165
|
+
Strings.add "attr_id", " id=\""
|
166
|
+
# adds id to the tag by joining +values+ with '_'
|
167
|
+
# @param [Array<#to_s>] values
|
168
|
+
# @example
|
169
|
+
# id('user', 12) #=> id="user_15"
|
170
|
+
def id(*values)
|
171
|
+
@output << Strings::ATTR_ID << CGI.escapeHTML(values.select { |v| v }.join(Strings::UNDERSCORE)) <<
|
172
|
+
Strings::QUOTE
|
173
|
+
self
|
174
|
+
end
|
175
|
+
|
176
|
+
# adds id and class to a tag by an object
|
177
|
+
# @param [Object] obj
|
178
|
+
# To determine the class it looks for .hammer_builder_ref or
|
179
|
+
# it uses class.to_s.underscore.tr('/', '-').
|
180
|
+
# To determine id it looks for #hammer_builder_ref or it takes class and #id or #object_id.
|
181
|
+
# @example
|
182
|
+
# div[AUser.new].with { text 'a' } # => <div id="a_user_1" class="a_user">a</div>
|
183
|
+
def mimic(obj)
|
184
|
+
klass = if obj.class.respond_to? :hammer_builder_ref
|
185
|
+
obj.class.hammer_builder_ref
|
186
|
+
else
|
187
|
+
ActiveSupport::Inflector.underscore(obj.class.to_s).tr('/', '-')
|
188
|
+
end
|
189
|
+
|
190
|
+
id = case
|
191
|
+
when obj.respond_to?(:hammer_builder_ref)
|
192
|
+
obj.hammer_builder_ref
|
193
|
+
when obj.respond_to?(:id)
|
194
|
+
[klass, obj.id]
|
195
|
+
else
|
196
|
+
[klass, obj.object_id]
|
197
|
+
end
|
198
|
+
#noinspection RubyArgCount
|
199
|
+
self.class(klass).id(id)
|
200
|
+
end
|
201
|
+
|
202
|
+
alias_method :[], :mimic
|
203
|
+
|
204
|
+
# renders data-* attributes by +hash+
|
205
|
+
# @param [Hash] hash
|
206
|
+
# @example
|
207
|
+
# div.data(:remote => true, :id => 'an_id') # => <div data-remote="true" data-id="an_id"></div>
|
208
|
+
def data(hash)
|
209
|
+
hash.each { |k, v| __send__ "data_#{k}", v }
|
210
|
+
self
|
211
|
+
end
|
212
|
+
|
213
|
+
protected
|
214
|
+
|
215
|
+
# this method is called on each tag opening, useful for default attributes
|
216
|
+
# @example html tag uses this to add xmlns attr.
|
217
|
+
# html # => <html xmlns="http://www.w3.org/1999/xhtml"></html>
|
218
|
+
def default
|
219
|
+
end
|
220
|
+
|
221
|
+
# flushes classes to output
|
222
|
+
# @api private
|
223
|
+
def flush_classes
|
224
|
+
unless @classes.empty?
|
225
|
+
@output << Strings::ATTR_CLASS << CGI.escapeHTML(@classes.join(Strings::SPACE)) << Strings::QUOTE
|
226
|
+
@classes.clear
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
class AbstractSingleTag < AbstractTag
|
231
|
+
|
232
|
+
nil
|
233
|
+
|
234
|
+
# @api private
|
235
|
+
# closes the tag
|
236
|
+
def flush
|
237
|
+
flush_classes
|
238
|
+
@output << Strings::SLASH_GT
|
239
|
+
nil
|
240
|
+
end
|
241
|
+
end
|
242
|
+
class AbstractDoubleTag < AbstractTag
|
243
|
+
|
244
|
+
nil
|
245
|
+
|
246
|
+
# defined by class_eval because there is a error cased by super
|
247
|
+
# super from singleton method that is defined to multiple classes is not supported;
|
248
|
+
# this will be fixed in 1.9.3 or later (NotImplementedError)
|
249
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
250
|
+
# @api private
|
251
|
+
def initialize(builder)
|
252
|
+
super
|
253
|
+
@content = nil
|
254
|
+
end
|
255
|
+
|
256
|
+
# allows data-* attributes and id, classes by method_missing
|
257
|
+
def method_missing(method, *args, &block)
|
258
|
+
method = method.to_s
|
259
|
+
if method =~ METHOD_MISSING_REGEXP
|
260
|
+
if $1
|
261
|
+
self.rclass.add_attributes Data::Attribute.new(method.to_sym, :string)
|
262
|
+
self.send method, *args, &block
|
263
|
+
else
|
264
|
+
self.content(args[0]) if args[0]
|
265
|
+
self.__send__($3 == '!' ? :id : :class, $2, &block)
|
266
|
+
end
|
267
|
+
else
|
268
|
+
super(method, *args, &block)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# @api private
|
273
|
+
def open(*args, &block)
|
274
|
+
attributes = if args.last.is_a?(Hash)
|
275
|
+
args.pop
|
276
|
+
end
|
277
|
+
content args[0]
|
278
|
+
super attributes
|
279
|
+
@stack << @tag_name
|
280
|
+
if block
|
281
|
+
with &block
|
282
|
+
else
|
283
|
+
self
|
284
|
+
end
|
285
|
+
end
|
286
|
+
RUBY
|
287
|
+
|
288
|
+
# @api private
|
289
|
+
# closes the tag
|
290
|
+
def flush
|
291
|
+
flush_classes
|
292
|
+
@output << Strings::GT
|
293
|
+
@output << CGI.escapeHTML(@content) if @content
|
294
|
+
@output << Strings::SLASH_LT << @stack.pop << Strings::GT
|
295
|
+
@content = nil
|
296
|
+
end
|
297
|
+
|
298
|
+
# sets content of the double tag
|
299
|
+
# @example
|
300
|
+
# div 'content' # => <div>content</div>
|
301
|
+
# div.content 'content' # => <div>content</div>
|
302
|
+
# div :content => 'content' # => <div>content</div>
|
303
|
+
def content(content)
|
304
|
+
@content = content.to_s
|
305
|
+
self
|
306
|
+
end
|
307
|
+
|
308
|
+
# renders content of the double tag with block
|
309
|
+
# @yield content of the tag
|
310
|
+
# @example
|
311
|
+
# div { text 'content' } # => <div>content</div>
|
312
|
+
# div :id => 'id' do
|
313
|
+
# text 'content'
|
314
|
+
# end # => <div id="id">content</div>
|
315
|
+
def with
|
316
|
+
flush_classes
|
317
|
+
@output << Strings::GT
|
318
|
+
@content = nil
|
319
|
+
@builder.current = nil
|
320
|
+
yield
|
321
|
+
#if (content = yield).is_a?(String)
|
322
|
+
# @output << EscapeUtils.escape_html(content)
|
323
|
+
#end
|
324
|
+
@builder.flush
|
325
|
+
@output << Strings::SLASH_LT << @stack.pop << Strings::GT
|
326
|
+
nil
|
327
|
+
end
|
328
|
+
|
329
|
+
alias_method :w, :with
|
330
|
+
|
331
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
332
|
+
def mimic(obj, &block)
|
333
|
+
super(obj, &nil)
|
334
|
+
return with(&block) if block
|
335
|
+
self
|
336
|
+
end
|
337
|
+
|
338
|
+
def data(hash, &block)
|
339
|
+
super(hash, &nil)
|
340
|
+
return with(&block) if block
|
341
|
+
self
|
342
|
+
end
|
343
|
+
|
344
|
+
def attribute(name, value, &block)
|
345
|
+
super(name, value, &nil)
|
346
|
+
return with(&block) if block
|
347
|
+
self
|
348
|
+
end
|
349
|
+
|
350
|
+
def attributes(attrs, &block)
|
351
|
+
super(attrs, &nil)
|
352
|
+
return with(&block) if block
|
353
|
+
self
|
354
|
+
end
|
355
|
+
RUBY
|
356
|
+
|
357
|
+
protected
|
358
|
+
|
359
|
+
# @api private
|
360
|
+
def self.define_attribute_method(attribute)
|
361
|
+
return if instance_methods(false).include?(attribute.name)
|
362
|
+
name = attribute.name.to_s
|
363
|
+
|
364
|
+
if instance_methods.include?(attribute.name)
|
365
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
366
|
+
def #{name}(*args, &block)
|
367
|
+
super(*args, &nil)
|
368
|
+
return with(&block) if block
|
369
|
+
self
|
370
|
+
end
|
371
|
+
RUBY
|
372
|
+
else
|
373
|
+
content_rendering = attribute_content_rendering(attribute)
|
374
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
375
|
+
def #{name}(content#{' = true' if attribute.type == :boolean}, &block)
|
376
|
+
#{content_rendering}
|
377
|
+
return with(&block) if block
|
378
|
+
self
|
379
|
+
end
|
380
|
+
RUBY
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|