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.
@@ -0,0 +1,11 @@
1
+ require 'ostruct'
2
+
3
+ module HammerBuilder
4
+ module Data
5
+
6
+ Attribute = Struct.new(:name, :type)
7
+ Tag = Struct.new(:name, :attributes)
8
+
9
+ end
10
+ end
11
+
@@ -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