hammer_builder 0.1.0 → 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.
- data/README.md +2 -1
- data/lib/hammer_builder.rb +215 -239
- data/lib/hammer_builder/dynamic_classes.rb +205 -0
- metadata +4 -3
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# HammerBuilder
|
|
2
2
|
|
|
3
3
|
[`HammerBuilder`](https://github.com/ruby-hammer/hammer-builder)
|
|
4
|
-
is a xhtml5 builder written in Ruby 1.9.2. It does not introduce anything special, you just
|
|
4
|
+
is a xhtml5 builder written in and for Ruby 1.9.2. It does not introduce anything special, you just
|
|
5
5
|
use Ruby to get your xhtml. [`HammerBuilder`](https://github.com/ruby-hammer/hammer-builder)
|
|
6
6
|
has been written with three objectives:
|
|
7
7
|
|
|
@@ -16,6 +16,7 @@ has been written with three objectives:
|
|
|
16
16
|
* Yardoc: [http://hammer.pitr.ch/hammer-builder/](http://hammer.pitr.ch/hammer-builder/)
|
|
17
17
|
* Issues: [https://github.com/ruby-hammer/hammer-builder/issues](https://github.com/ruby-hammer/hammer-builder/issues)
|
|
18
18
|
* Changelog: [http://hammer.pitr.ch/hammer-builder/file.CHANGELOG.html](http://hammer.pitr.ch/hammer-builder/file.CHANGELOG.html)
|
|
19
|
+
* Gem: [https://rubygems.org/gems/hammer_builder](https://rubygems.org/gems/hammer_builder)
|
|
19
20
|
|
|
20
21
|
## Syntax
|
|
21
22
|
|
data/lib/hammer_builder.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require 'cgi'
|
|
2
2
|
require 'active_support/core_ext/class/inheritable_attributes'
|
|
3
3
|
require 'active_support/core_ext/string/inflections'
|
|
4
|
+
require 'hammer_builder/dynamic_classes'
|
|
4
5
|
|
|
5
6
|
module HammerBuilder
|
|
6
7
|
EXTRA_ATTRIBUTES = {
|
|
@@ -133,7 +134,7 @@ module HammerBuilder
|
|
|
133
134
|
]
|
|
134
135
|
|
|
135
136
|
DOUBLE_TAGS = [
|
|
136
|
-
'a', 'abbr', 'article', 'aside', 'audio', 'address',
|
|
137
|
+
'a', 'abbr', 'article', 'aside', 'audio', 'address',
|
|
137
138
|
'b', 'bdo', 'blockquote', 'body', 'button',
|
|
138
139
|
'canvas', 'caption', 'cite', 'code', 'colgroup', 'command',
|
|
139
140
|
'datalist', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt',
|
|
@@ -207,64 +208,6 @@ module HammerBuilder
|
|
|
207
208
|
end
|
|
208
209
|
end
|
|
209
210
|
|
|
210
|
-
module RedefinableClassTree
|
|
211
|
-
# defines new class
|
|
212
|
-
# @param [Symbol] class_name
|
|
213
|
-
# @param [Symbol] superclass_name e.g. :AbstractEmptyTag
|
|
214
|
-
# @yield definition block which is evaluated inside the new class, doing so defining the class's methods etc.
|
|
215
|
-
def define_class(class_name, superclass_name = nil, &definition)
|
|
216
|
-
class_name = class_name(class_name)
|
|
217
|
-
superclass_name = class_name(superclass_name) if superclass_name
|
|
218
|
-
|
|
219
|
-
raise "class: '#{class_name}' already defined" if respond_to? method_class(class_name)
|
|
220
|
-
|
|
221
|
-
define_singleton_method method_class(class_name) do |builder|
|
|
222
|
-
builder.instance_variable_get("@#{method_class(class_name)}") || begin
|
|
223
|
-
klass = builder.send(method_class_definition(class_name), builder)
|
|
224
|
-
builder.const_set class_name, klass
|
|
225
|
-
builder.instance_variable_set("@#{method_class(class_name)}", klass)
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
define_singleton_method method_class_definition(class_name) do |builder|
|
|
230
|
-
superclass = if superclass_name
|
|
231
|
-
builder.send method_class(superclass_name), builder
|
|
232
|
-
else
|
|
233
|
-
Object
|
|
234
|
-
end
|
|
235
|
-
Class.new(superclass, &definition)
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
# extends existing class
|
|
240
|
-
# @param [Symbol] class_name
|
|
241
|
-
# @yield definition block which is evaluated inside the new class, doing so extending the class's methods etc.
|
|
242
|
-
def extend_class(class_name, &definition)
|
|
243
|
-
raise "class: '#{class_name}' not defined" unless respond_to? method_class(class_name)
|
|
244
|
-
|
|
245
|
-
define_singleton_method method_class_definition(class_name) do |builder|
|
|
246
|
-
ancestor = super(builder)
|
|
247
|
-
count = 1; count += 1 while builder.const_defined? "#{class_name}Super#{count}"
|
|
248
|
-
builder.const_set "#{class_name}Super#{count}", ancestor
|
|
249
|
-
Class.new(ancestor, &definition)
|
|
250
|
-
end
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
private
|
|
254
|
-
|
|
255
|
-
def class_name(klass)
|
|
256
|
-
klass.to_s.camelize
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
def method_class(klass)
|
|
260
|
-
"#{klass.to_s.underscore}_class"
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
def method_class_definition(klass)
|
|
264
|
-
"#{method_class(klass)}_definition"
|
|
265
|
-
end
|
|
266
|
-
end
|
|
267
|
-
|
|
268
211
|
# Creating builder instances is expensive, therefore you can use Pool to go around that
|
|
269
212
|
module Pool
|
|
270
213
|
def self.included(base)
|
|
@@ -275,7 +218,7 @@ module HammerBuilder
|
|
|
275
218
|
module ClassMethods
|
|
276
219
|
# This the preferred way of getting new Builder. If you forget to release it, it does not matter -
|
|
277
220
|
# builder gets GCed after you lose reference
|
|
278
|
-
# @return [Standard, Formated]
|
|
221
|
+
# @return [Standard, Formated]
|
|
279
222
|
def get
|
|
280
223
|
mutex.synchronize do
|
|
281
224
|
if free_builders.empty?
|
|
@@ -321,7 +264,7 @@ module HammerBuilder
|
|
|
321
264
|
|
|
322
265
|
# Abstract implementation of Builder
|
|
323
266
|
class Abstract
|
|
324
|
-
extend
|
|
267
|
+
extend DynamicClasses
|
|
325
268
|
include Pool
|
|
326
269
|
|
|
327
270
|
# << faster then +
|
|
@@ -330,142 +273,146 @@ module HammerBuilder
|
|
|
330
273
|
# class_eval faster then define_method
|
|
331
274
|
# beware of strings in methods -> creates a lot of garbage
|
|
332
275
|
|
|
333
|
-
|
|
334
|
-
def initialize(builder)
|
|
335
|
-
@builder = builder
|
|
336
|
-
@output = builder.instance_eval { @output }
|
|
337
|
-
@stack = builder.instance_eval { @stack }
|
|
338
|
-
@classes = []
|
|
339
|
-
set_tag
|
|
340
|
-
end
|
|
276
|
+
dc do
|
|
341
277
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
278
|
+
define :AbstractTag do
|
|
279
|
+
def initialize(builder)
|
|
280
|
+
@builder = builder
|
|
281
|
+
@output = builder.instance_eval { @output }
|
|
282
|
+
@stack = builder.instance_eval { @stack }
|
|
283
|
+
@classes = []
|
|
284
|
+
set_tag
|
|
285
|
+
end
|
|
349
286
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
287
|
+
def open(attributes = nil)
|
|
288
|
+
@output << LT << @tag
|
|
289
|
+
@builder.current = self
|
|
290
|
+
attributes(attributes)
|
|
291
|
+
default
|
|
292
|
+
self
|
|
356
293
|
end
|
|
357
|
-
self
|
|
358
|
-
end
|
|
359
294
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
295
|
+
# @example
|
|
296
|
+
# div.attributes :id => 'id' # => <div id="id"></div>
|
|
297
|
+
def attributes(attrs)
|
|
298
|
+
return self unless attrs
|
|
299
|
+
attrs.each do |attr, value|
|
|
300
|
+
__send__(attr, *value)
|
|
301
|
+
end
|
|
302
|
+
self
|
|
303
|
+
end
|
|
365
304
|
|
|
366
|
-
|
|
305
|
+
# @example
|
|
306
|
+
# div.attribute :id, 'id' # => <div id="id"></div>
|
|
307
|
+
# @deprecated Please use {#attributes} instead
|
|
308
|
+
def attribute(attribute, content) # TODO lose the method in 0.2
|
|
309
|
+
warn ("method #attribute is deprecated use #attributes instead, called from:#{caller[0]}" )
|
|
310
|
+
@output << SPACE << attribute.to_s << EQL_QUOTE << CGI.escapeHTML(content.to_s) << QUOTE
|
|
311
|
+
end
|
|
367
312
|
|
|
368
|
-
|
|
313
|
+
alias_method(:rclass, :class)
|
|
369
314
|
|
|
370
|
-
|
|
371
|
-
self._attributes
|
|
372
|
-
end
|
|
315
|
+
class_inheritable_array :_attributes, :instance_writer => false, :instance_reader => false
|
|
373
316
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if method.to_s =~ /data_([a-z_]+)/
|
|
377
|
-
self.rclass.attributes = [method.to_s]
|
|
378
|
-
self.send method, *args, &block
|
|
379
|
-
else
|
|
380
|
-
super
|
|
317
|
+
def self.attributes
|
|
318
|
+
self._attributes
|
|
381
319
|
end
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
protected
|
|
385
320
|
|
|
386
|
-
# sets the right tag in descendants
|
|
387
|
-
def self.set_tag(tag)
|
|
388
321
|
class_eval <<-RUBYCODE, __FILE__, __LINE__ + 1
|
|
389
|
-
|
|
390
|
-
|
|
322
|
+
# allows data-* attributes
|
|
323
|
+
def method_missing(method, *args, &block)
|
|
324
|
+
if method.to_s =~ /data_([a-z_]+)/
|
|
325
|
+
self.rclass.attributes = [method.to_s]
|
|
326
|
+
self.send method, *args, &block
|
|
327
|
+
else
|
|
328
|
+
super
|
|
391
329
|
end
|
|
330
|
+
end
|
|
392
331
|
RUBYCODE
|
|
393
|
-
end
|
|
394
332
|
|
|
395
|
-
|
|
396
|
-
# @example html tag uses this to add xmlns attr.
|
|
397
|
-
# html # => <html xmlns="http://www.w3.org/1999/xhtml"></html>
|
|
398
|
-
def default
|
|
399
|
-
end
|
|
333
|
+
protected
|
|
400
334
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
attributes.each do |attr|
|
|
404
|
-
next if instance_methods.include?(attr.to_sym)
|
|
335
|
+
# sets the right tag in descendants
|
|
336
|
+
def self.set_tag(tag)
|
|
405
337
|
class_eval <<-RUBYCODE, __FILE__, __LINE__ + 1
|
|
338
|
+
def set_tag
|
|
339
|
+
@tag = '#{tag}'.freeze
|
|
340
|
+
end
|
|
341
|
+
RUBYCODE
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
set_tag 'abstract'
|
|
345
|
+
|
|
346
|
+
# this method is called on each tag opening, useful for default attributes
|
|
347
|
+
# @example html tag uses this to add xmlns attr.
|
|
348
|
+
# html # => <html xmlns="http://www.w3.org/1999/xhtml"></html>
|
|
349
|
+
def default
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# defines dynamically methods for attributes
|
|
353
|
+
def self.define_attributes
|
|
354
|
+
attributes.each do |attr|
|
|
355
|
+
next if instance_methods.include?(attr.to_sym)
|
|
356
|
+
class_eval <<-RUBYCODE, __FILE__, __LINE__ + 1
|
|
406
357
|
def #{attr}(content)
|
|
407
358
|
@output << ATTR_#{attr.upcase} << CGI.escapeHTML(content.to_s) << QUOTE
|
|
408
359
|
self
|
|
409
360
|
end
|
|
410
|
-
|
|
361
|
+
RUBYCODE
|
|
362
|
+
end
|
|
363
|
+
define_attribute_constants
|
|
411
364
|
end
|
|
412
|
-
define_attribute_constants
|
|
413
|
-
end
|
|
414
365
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
366
|
+
# defines constant strings not to make garbage
|
|
367
|
+
def self.define_attribute_constants
|
|
368
|
+
attributes.each do |attr|
|
|
369
|
+
const = "attr_#{attr}".upcase
|
|
370
|
+
HammerBuilder.const_set const, " #{attr.gsub('_', '-')}=\"".freeze unless HammerBuilder.const_defined?(const)
|
|
371
|
+
end
|
|
420
372
|
end
|
|
421
|
-
end
|
|
422
|
-
|
|
423
|
-
# adds attribute to class, triggers dynamical creation of needed instance methods etc.
|
|
424
|
-
def self.attributes=(attributes)
|
|
425
|
-
self._attributes = attributes
|
|
426
|
-
define_attributes
|
|
427
|
-
end
|
|
428
373
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
@classes.clear
|
|
374
|
+
# adds attribute to class, triggers dynamical creation of needed instance methods etc.
|
|
375
|
+
def self.attributes=(attributes)
|
|
376
|
+
self._attributes = attributes
|
|
377
|
+
define_attributes
|
|
434
378
|
end
|
|
435
|
-
end
|
|
436
379
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
380
|
+
# flushes classes to output
|
|
381
|
+
def flush_classes
|
|
382
|
+
unless @classes.empty?
|
|
383
|
+
@output << ATTR_CLASS << CGI.escapeHTML(@classes.join(SPACE)) << QUOTE
|
|
384
|
+
@classes.clear
|
|
385
|
+
end
|
|
386
|
+
end
|
|
440
387
|
|
|
441
|
-
|
|
388
|
+
public
|
|
442
389
|
|
|
443
|
-
|
|
444
|
-
|
|
390
|
+
# global HTML5 attributes
|
|
391
|
+
self.attributes = GLOBAL_ATTRIBUTES
|
|
445
392
|
|
|
446
|
-
|
|
393
|
+
alias :[] :id
|
|
447
394
|
|
|
448
|
-
|
|
395
|
+
class_eval <<-RUBYCODE, __FILE__, __LINE__ + 1
|
|
449
396
|
def class(*classes)
|
|
450
397
|
@classes.push(*classes)
|
|
451
398
|
self
|
|
452
399
|
end
|
|
453
|
-
|
|
454
|
-
|
|
400
|
+
RUBYCODE
|
|
401
|
+
end
|
|
455
402
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
403
|
+
define :AbstractEmptyTag, :AbstractTag do
|
|
404
|
+
def flush
|
|
405
|
+
flush_classes
|
|
406
|
+
@output << SLASH_GT
|
|
407
|
+
nil
|
|
408
|
+
end
|
|
461
409
|
end
|
|
462
|
-
end
|
|
463
410
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
411
|
+
define :AbstractDoubleTag, :AbstractTag do
|
|
412
|
+
# defined by class_eval because there is a super calling, causing error:
|
|
413
|
+
# super from singleton method that is defined to multiple classes is not supported;
|
|
414
|
+
# this will be fixed in 1.9.3 or later (NotImplementedError)
|
|
415
|
+
class_eval <<-RUBYCODE, __FILE__, __LINE__ + 1
|
|
469
416
|
def initialize(builder)
|
|
470
417
|
super
|
|
471
418
|
@content = nil
|
|
@@ -484,61 +431,62 @@ module HammerBuilder
|
|
|
484
431
|
self
|
|
485
432
|
end
|
|
486
433
|
end
|
|
487
|
-
|
|
434
|
+
RUBYCODE
|
|
488
435
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
436
|
+
def flush
|
|
437
|
+
flush_classes
|
|
438
|
+
@output << GT
|
|
439
|
+
@output << CGI.escapeHTML(@content) if @content
|
|
440
|
+
@output << SLASH_LT << @stack.pop << GT
|
|
441
|
+
@content = nil
|
|
442
|
+
end
|
|
496
443
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
444
|
+
# sets content of the double tag
|
|
445
|
+
def content(content)
|
|
446
|
+
@content = content.to_s
|
|
447
|
+
self
|
|
448
|
+
end
|
|
502
449
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
450
|
+
# renders content of the double tag with block
|
|
451
|
+
def with
|
|
452
|
+
flush_classes
|
|
453
|
+
@output << GT
|
|
454
|
+
@content = nil
|
|
455
|
+
@builder.current = nil
|
|
456
|
+
yield
|
|
457
|
+
# if (content = yield).is_a?(String)
|
|
458
|
+
# @output << CGI.escapeHTML(content)
|
|
459
|
+
# end
|
|
460
|
+
@builder.flush
|
|
461
|
+
@output << SLASH_LT << @stack.pop << GT
|
|
462
|
+
nil
|
|
463
|
+
end
|
|
517
464
|
|
|
518
|
-
|
|
465
|
+
protected
|
|
519
466
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
467
|
+
def self.define_attributes
|
|
468
|
+
attributes.each do |attr|
|
|
469
|
+
next if instance_methods(false).include?(attr.to_sym)
|
|
470
|
+
if instance_methods.include?(attr.to_sym)
|
|
471
|
+
class_eval <<-RUBYCODE, __FILE__, __LINE__ + 1
|
|
525
472
|
def #{attr}(*args, &block)
|
|
526
473
|
super(*args, &nil)
|
|
527
474
|
return with(&block) if block
|
|
528
475
|
self
|
|
529
476
|
end
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
477
|
+
RUBYCODE
|
|
478
|
+
else
|
|
479
|
+
class_eval <<-RUBYCODE, __FILE__, __LINE__ + 1
|
|
533
480
|
def #{attr}(content, &block)
|
|
534
481
|
@output << ATTR_#{attr.upcase} << CGI.escapeHTML(content.to_s) << QUOTE
|
|
535
482
|
return with(&block) if block
|
|
536
483
|
self
|
|
537
484
|
end
|
|
538
|
-
|
|
485
|
+
RUBYCODE
|
|
486
|
+
end
|
|
539
487
|
end
|
|
488
|
+
define_attribute_constants
|
|
540
489
|
end
|
|
541
|
-
define_attribute_constants
|
|
542
490
|
end
|
|
543
491
|
end
|
|
544
492
|
|
|
@@ -549,6 +497,7 @@ module HammerBuilder
|
|
|
549
497
|
|
|
550
498
|
# defines instance method for +tag+ in builder
|
|
551
499
|
def self.define_tag(tag)
|
|
500
|
+
tag = tag.to_s
|
|
552
501
|
class_eval <<-RUBYCODE, __FILE__, __LINE__ + 1
|
|
553
502
|
def #{tag}(*args, &block)
|
|
554
503
|
flush
|
|
@@ -568,7 +517,7 @@ module HammerBuilder
|
|
|
568
517
|
@current = nil
|
|
569
518
|
# tag classes initialization
|
|
570
519
|
tags.values.each do |klass|
|
|
571
|
-
instance_variable_set(:"@#{klass}", self.class.
|
|
520
|
+
instance_variable_set(:"@#{klass}", self.class.dc[klass.camelize.to_sym].new(self))
|
|
572
521
|
end
|
|
573
522
|
end
|
|
574
523
|
|
|
@@ -631,6 +580,13 @@ module HammerBuilder
|
|
|
631
580
|
self
|
|
632
581
|
end
|
|
633
582
|
|
|
583
|
+
def set_variables(instance_variables)
|
|
584
|
+
instance_variables.each {|name,value| instance_variable_set("@#{name}", value) }
|
|
585
|
+
yield
|
|
586
|
+
instance_variables.each {|name,_| remove_instance_variable("@#{name}") }
|
|
587
|
+
self
|
|
588
|
+
end
|
|
589
|
+
|
|
634
590
|
# @return [String] output
|
|
635
591
|
def to_xhtml()
|
|
636
592
|
flush
|
|
@@ -655,66 +611,86 @@ module HammerBuilder
|
|
|
655
611
|
# Builder implementation without formating (one line)
|
|
656
612
|
class Standard < Abstract
|
|
657
613
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
614
|
+
dc do
|
|
615
|
+
(DOUBLE_TAGS - ['html']).each do |tag|
|
|
616
|
+
define tag.camelize.to_sym , :AbstractDoubleTag do
|
|
617
|
+
set_tag tag
|
|
618
|
+
self.attributes = EXTRA_ATTRIBUTES[tag]
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
base.define_tag(tag)
|
|
662
622
|
end
|
|
663
623
|
|
|
664
|
-
|
|
665
|
-
|
|
624
|
+
define :Html, :AbstractDoubleTag do
|
|
625
|
+
set_tag 'html'
|
|
626
|
+
self.attributes = ['xmlns'] + EXTRA_ATTRIBUTES['html']
|
|
666
627
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
628
|
+
def default
|
|
629
|
+
xmlns('http://www.w3.org/1999/xhtml')
|
|
630
|
+
end
|
|
631
|
+
end
|
|
632
|
+
base.define_tag('html')
|
|
633
|
+
|
|
634
|
+
EMPTY_TAGS.each do |tag|
|
|
635
|
+
define tag.camelize.to_sym, :AbstractEmptyTag do
|
|
636
|
+
set_tag tag
|
|
637
|
+
self.attributes = EXTRA_ATTRIBUTES[tag]
|
|
638
|
+
end
|
|
670
639
|
|
|
671
|
-
|
|
672
|
-
xmlns('http://www.w3.org/1999/xhtml')
|
|
640
|
+
base.define_tag(tag)
|
|
673
641
|
end
|
|
674
642
|
end
|
|
675
643
|
|
|
676
|
-
define_tag('html')
|
|
677
|
-
|
|
678
644
|
def js(js , options = {})
|
|
645
|
+
flush
|
|
679
646
|
script({:type => "text/javascript"}.merge(options)) { cdata js }
|
|
680
647
|
end
|
|
681
648
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
649
|
+
def join(collection, glue, &it)
|
|
650
|
+
flush
|
|
651
|
+
glue_block = if glue.is_a? String
|
|
652
|
+
lambda { text glue }
|
|
653
|
+
else
|
|
654
|
+
glue
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
collection.each_with_index do |obj, i|
|
|
658
|
+
glue_block.call() if i > 0
|
|
659
|
+
it.call(obj)
|
|
686
660
|
end
|
|
687
|
-
|
|
688
|
-
define_tag(tag)
|
|
689
661
|
end
|
|
662
|
+
|
|
690
663
|
end
|
|
691
664
|
|
|
692
665
|
# Builder implementation with formating (indented by ' ')
|
|
693
666
|
# Slow down is less then 1%
|
|
694
667
|
class Formated < Standard
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
668
|
+
|
|
669
|
+
dc do
|
|
670
|
+
extend :AbstractTag do
|
|
671
|
+
def open(attributes = nil)
|
|
672
|
+
@output << NEWLINE << SPACES.fetch(@stack.size, SPACE) << LT << @tag
|
|
673
|
+
@builder.current = self
|
|
674
|
+
attributes(attributes)
|
|
675
|
+
default
|
|
676
|
+
self
|
|
677
|
+
end
|
|
702
678
|
end
|
|
703
|
-
end
|
|
704
679
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
680
|
+
extend :AbstractDoubleTag do
|
|
681
|
+
def with
|
|
682
|
+
flush_classes
|
|
683
|
+
@output << GT
|
|
684
|
+
@content = nil
|
|
685
|
+
@builder.current = nil
|
|
686
|
+
yield
|
|
687
|
+
# if (content = yield).is_a?(String)
|
|
688
|
+
# @output << CGI.escapeHTML(content)
|
|
689
|
+
# end
|
|
690
|
+
@builder.flush
|
|
691
|
+
@output << NEWLINE << SPACES.fetch(@stack.size-1, SPACE) << SLASH_LT << @stack.pop << GT
|
|
692
|
+
nil
|
|
693
|
+
end
|
|
718
694
|
end
|
|
719
695
|
end
|
|
720
696
|
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
require 'active_support/core_ext/object/try'
|
|
2
|
+
|
|
3
|
+
# When extended into a class it enables easy defining and extending classes in extended class.
|
|
4
|
+
#
|
|
5
|
+
# class A
|
|
6
|
+
# extend DynamicClasses
|
|
7
|
+
# dc do
|
|
8
|
+
# define :A do
|
|
9
|
+
# def to_s
|
|
10
|
+
# 'a'
|
|
11
|
+
# end
|
|
12
|
+
# end
|
|
13
|
+
# define :B, :A do
|
|
14
|
+
# class_eval <<-RUBYCODE, __FILE__, __LINE__+1
|
|
15
|
+
# def to_s
|
|
16
|
+
# super + 'b'
|
|
17
|
+
# end
|
|
18
|
+
# RUBYCODE
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# class B < A
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# class C < A
|
|
27
|
+
# dc do
|
|
28
|
+
# extend :A do
|
|
29
|
+
# def to_s
|
|
30
|
+
# 'aa'
|
|
31
|
+
# end
|
|
32
|
+
# end
|
|
33
|
+
# end
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# puts A.dc[:A] # => #<Class:0x00000001d449b8(A.dc[:A])>
|
|
37
|
+
# puts B.dc[:A] # => #<Class:0x00000001d42398(B.dc[:A])>
|
|
38
|
+
# puts B.dc[:A].new # => a
|
|
39
|
+
# puts B.dc[:B].new # => ab
|
|
40
|
+
# puts C.dc[:B].new # => aab
|
|
41
|
+
#
|
|
42
|
+
# Last example is the most interesting. It prints 'aab' not 'ab' because of the extension in class C. Class :B has
|
|
43
|
+
# as ancestor extended class :A from C therefore the two 'a'.
|
|
44
|
+
module DynamicClasses
|
|
45
|
+
|
|
46
|
+
# Adds ability to describe itself when class is defined without constant
|
|
47
|
+
module Describable
|
|
48
|
+
def self.included(base)
|
|
49
|
+
base.singleton_class.send :alias_method, :original_to_s, :to_s
|
|
50
|
+
base.extend ClassMethods
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
module ClassMethods
|
|
54
|
+
# sets +description+
|
|
55
|
+
# @param [String] description
|
|
56
|
+
def _description=(description)
|
|
57
|
+
@_description = description
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def to_s
|
|
61
|
+
super.gsub(/>$/, "(#{@_description})>")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def to_s
|
|
66
|
+
klass = respond_to?(:rclass) ? self.rclass : self.class
|
|
67
|
+
super.gsub(klass.original_to_s, klass.to_s)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class DescribableClass
|
|
72
|
+
include Describable
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
ClassDefinition = Struct.new(:name, :base, :superclass_or_name, :definition)
|
|
76
|
+
ClassExtension = Struct.new(:name, :base, :definition)
|
|
77
|
+
|
|
78
|
+
class Classes
|
|
79
|
+
attr_reader :base, :class_definitions, :classes, :class_extensions
|
|
80
|
+
|
|
81
|
+
def initialize(base)
|
|
82
|
+
raise unless base.is_a? Class
|
|
83
|
+
@base = base
|
|
84
|
+
@class_definitions = {}
|
|
85
|
+
@class_extensions = {}
|
|
86
|
+
@classes = {}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# define a class
|
|
90
|
+
# @param [Symbol] name
|
|
91
|
+
# @param [Symbol, Class, nil] superclass_or_name
|
|
92
|
+
# when Symbol then dynamic class is found
|
|
93
|
+
# when Class then this class is used
|
|
94
|
+
# when nil then Object is used
|
|
95
|
+
# @yield definition block is evaluated inside the class defining it
|
|
96
|
+
def define(name, superclass_or_name = nil, &definition)
|
|
97
|
+
raise ArgumentError, "name is not a Symbol" unless name.is_a?(Symbol)
|
|
98
|
+
unless superclass_or_name.is_a?(Symbol) || superclass_or_name.is_a?(Class) || superclass_or_name.nil?
|
|
99
|
+
raise ArgumentError, "superclass_or_name is not a Symbol, Class or nil"
|
|
100
|
+
end
|
|
101
|
+
raise ArgumentError, "definition is nil" unless definition
|
|
102
|
+
raise ArgumentError, "Class #{name} already defined" if class_definition(name)
|
|
103
|
+
@class_definitions[name] = ClassDefinition.new(name, base, superclass_or_name, definition)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# extends already defined class by adding a child,
|
|
107
|
+
# @param [Symbol] name
|
|
108
|
+
# @yield definition block is evaluated inside the class extending it
|
|
109
|
+
def extend(name, &definition)
|
|
110
|
+
raise ArgumentError, "name is not a Symbol" unless name.is_a?(Symbol)
|
|
111
|
+
raise ArgumentError, "definition is nil" unless definition
|
|
112
|
+
raise ArgumentError, "Class #{name} not defined" unless class_definition(name)
|
|
113
|
+
@class_extensions[name] = ClassExtension.new(name, base, definition)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# triggers loading of all defined classes
|
|
117
|
+
def load!
|
|
118
|
+
class_names.each {|name| self[name] }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @return [Class] defined class
|
|
122
|
+
def [](name)
|
|
123
|
+
return @classes[name] if @classes[name]
|
|
124
|
+
return nil unless klass_definition = class_definition(name)
|
|
125
|
+
|
|
126
|
+
superclass = case klass_definition.superclass_or_name
|
|
127
|
+
when Symbol then self[klass_definition.superclass_or_name]
|
|
128
|
+
when Class then
|
|
129
|
+
klass = Class.new(klass_definition.superclass_or_name)
|
|
130
|
+
klass.send :include, Describable
|
|
131
|
+
klass._description = "Describable#{klass_definition.superclass_or_name}"
|
|
132
|
+
klass
|
|
133
|
+
when nil then DescribableClass
|
|
134
|
+
end
|
|
135
|
+
klass = Class.new(superclass, &klass_definition.definition)
|
|
136
|
+
klass._description = "#{base}.dc[:#{klass_definition.name}]"
|
|
137
|
+
|
|
138
|
+
class_extensions(name).each do |klass_extension|
|
|
139
|
+
klass = Class.new(klass, &klass_extension.definition)
|
|
140
|
+
klass._description = "#{base}.dc[:#{klass_extension.name}]"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
@classes[name] = klass
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
|
|
148
|
+
def class_names
|
|
149
|
+
ancestors.map(&:class_definitions).map(&:keys).flatten
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def class_definition(name)
|
|
153
|
+
@class_definitions[name] || ancestor.try(:class_definition, name)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def class_extensions(name)
|
|
157
|
+
( [*ancestor.try(:class_extensions, name)] + [@class_extensions[name]] ).compact
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def ancestors
|
|
161
|
+
( [self] + [*ancestor.try(:ancestors)] ).compact
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def ancestor
|
|
165
|
+
@base.superclass.dynamic_classes if @base.superclass.kind_of?(DynamicClasses)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# hook to create Classes instance
|
|
170
|
+
def self.extended(base)
|
|
171
|
+
base.send :create_dynamic_classes
|
|
172
|
+
super
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# hook to create Classes instance in descendants
|
|
176
|
+
def inherited(base)
|
|
177
|
+
base.send :create_dynamic_classes
|
|
178
|
+
super
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# call this to get access to Classes instance to define/extend classes inside +definition+
|
|
182
|
+
# calls Classes#load! to preload defined classes
|
|
183
|
+
# @yield [Proc, nil] definition
|
|
184
|
+
# a Proc enables writing class definitions/extensions
|
|
185
|
+
# @return [Classes] when definition is nil
|
|
186
|
+
def dynamic_classes(&definition)
|
|
187
|
+
if definition
|
|
188
|
+
@dynamic_classes.instance_eval &definition
|
|
189
|
+
@dynamic_classes.load!
|
|
190
|
+
nil
|
|
191
|
+
else
|
|
192
|
+
@dynamic_classes
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
alias :dc :dynamic_classes
|
|
197
|
+
|
|
198
|
+
private
|
|
199
|
+
|
|
200
|
+
def create_dynamic_classes
|
|
201
|
+
@dynamic_classes = Classes.new(self)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
|
metadata
CHANGED
|
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
|
5
5
|
segments:
|
|
6
6
|
- 0
|
|
7
7
|
- 1
|
|
8
|
-
-
|
|
9
|
-
version: 0.1.
|
|
8
|
+
- 1
|
|
9
|
+
version: 0.1.1
|
|
10
10
|
platform: ruby
|
|
11
11
|
authors:
|
|
12
12
|
- Petr Chalupa
|
|
@@ -14,7 +14,7 @@ autorequire:
|
|
|
14
14
|
bindir: bin
|
|
15
15
|
cert_chain: []
|
|
16
16
|
|
|
17
|
-
date: 2011-05-
|
|
17
|
+
date: 2011-05-16 00:00:00 +02:00
|
|
18
18
|
default_executable:
|
|
19
19
|
dependencies:
|
|
20
20
|
- !ruby/object:Gem::Dependency
|
|
@@ -102,6 +102,7 @@ extra_rdoc_files:
|
|
|
102
102
|
- README.md
|
|
103
103
|
files:
|
|
104
104
|
- lib/hammer_builder.rb
|
|
105
|
+
- lib/hammer_builder/dynamic_classes.rb
|
|
105
106
|
- LICENSE
|
|
106
107
|
- README.md
|
|
107
108
|
- spec/hammer_builder_spec.rb
|