hammer_builder 0.1.2 → 0.2.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.
@@ -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