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.
- 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
@@ -1,16 +1,18 @@
|
|
1
1
|
require 'active_support/core_ext/object/try'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
module HammerBuilder
|
4
|
+
|
5
|
+
# When extended into a class it enables easy defining and extending classes in the class.
|
6
|
+
#
|
5
7
|
# class A
|
6
8
|
# extend DynamicClasses
|
7
|
-
#
|
8
|
-
#
|
9
|
+
# dynamic_classes do
|
10
|
+
# def_class :A do
|
9
11
|
# def to_s
|
10
12
|
# 'a'
|
11
13
|
# end
|
12
14
|
# end
|
13
|
-
#
|
15
|
+
# def_class :B, :A do
|
14
16
|
# class_eval <<-RUBYCODE, __FILE__, __LINE__+1
|
15
17
|
# def to_s
|
16
18
|
# super + 'b'
|
@@ -24,8 +26,8 @@ require 'active_support/core_ext/object/try'
|
|
24
26
|
# end
|
25
27
|
#
|
26
28
|
# class C < A
|
27
|
-
#
|
28
|
-
#
|
29
|
+
# dynamic_classes do
|
30
|
+
# extend_class :A do
|
29
31
|
# def to_s
|
30
32
|
# 'aa'
|
31
33
|
# end
|
@@ -41,165 +43,169 @@ require 'active_support/core_ext/object/try'
|
|
41
43
|
#
|
42
44
|
# Last example is the most interesting. It prints 'aab' not 'ab' because of the extension in class C. Class :B has
|
43
45
|
# as ancestor extended class :A from C therefore the two 'a'.
|
44
|
-
module DynamicClasses
|
46
|
+
module DynamicClasses
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
48
|
+
# Adds ability to describe itself when class is defined without constant
|
49
|
+
module Describable
|
50
|
+
def self.included(base)
|
51
|
+
base.singleton_class.send :alias_method, :original_to_s, :to_s
|
52
|
+
base.extend ClassMethods
|
53
|
+
end
|
52
54
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
55
|
+
module ClassMethods
|
56
|
+
# sets +description+
|
57
|
+
# @param [String] description
|
58
|
+
def _description=(description)
|
59
|
+
@_description = description
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_s
|
63
|
+
super.gsub(/>$/, "(#{@_description})>")
|
64
|
+
end
|
58
65
|
end
|
59
66
|
|
60
67
|
def to_s
|
61
|
-
|
68
|
+
klass = respond_to?(:rclass) ? self.rclass : self.class
|
69
|
+
super.gsub(klass.original_to_s, klass.to_s)
|
62
70
|
end
|
63
71
|
end
|
64
72
|
|
65
|
-
|
66
|
-
|
67
|
-
super.gsub(klass.original_to_s, klass.to_s)
|
73
|
+
class DescribableClass
|
74
|
+
include Describable
|
68
75
|
end
|
69
|
-
end
|
70
|
-
|
71
|
-
class DescribableClass
|
72
|
-
include Describable
|
73
|
-
end
|
74
76
|
|
75
|
-
|
76
|
-
|
77
|
+
ClassDefinition = Struct.new(:name, :base, :superclass_or_name, :definition)
|
78
|
+
ClassExtension = Struct.new(:name, :base, :definition)
|
77
79
|
|
78
|
-
|
79
|
-
|
80
|
+
class Classes
|
81
|
+
attr_reader :base, :class_definitions, :classes, :class_extensions
|
80
82
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
83
|
+
def initialize(base)
|
84
|
+
raise unless base.is_a? Class
|
85
|
+
@base = base
|
86
|
+
@class_definitions = { }
|
87
|
+
@class_extensions = { }
|
88
|
+
@classes = { }
|
89
|
+
end
|
88
90
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
91
|
+
# define a class
|
92
|
+
# @param [Symbol] name
|
93
|
+
# @param [Symbol, Class, nil] superclass_or_name
|
94
|
+
# when Symbol then dynamic class is found
|
95
|
+
# when Class then this class is used
|
96
|
+
# when nil then Object is used
|
97
|
+
# @yield definition block is evaluated inside the class defining it
|
98
|
+
def def_class(name, superclass_or_name = nil, &definition)
|
99
|
+
raise ArgumentError, "name is not a Symbol" unless name.is_a?(Symbol)
|
100
|
+
unless superclass_or_name.is_a?(Symbol) || superclass_or_name.is_a?(Class) || superclass_or_name.nil?
|
101
|
+
raise ArgumentError, "superclass_or_name is not a Symbol, Class or nil"
|
102
|
+
end
|
103
|
+
raise ArgumentError, "definition is nil" unless definition
|
104
|
+
raise ArgumentError, "Class #{name} already defined" if class_definition(name)
|
105
|
+
@class_definitions[name] = ClassDefinition.new(name, base, superclass_or_name, definition)
|
100
106
|
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
107
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
108
|
+
# extends already defined class by adding a child,
|
109
|
+
# @param [Symbol] name
|
110
|
+
# @yield definition block is evaluated inside the class extending it
|
111
|
+
def extend_class(name, &definition)
|
112
|
+
raise ArgumentError, "name is not a Symbol" unless name.is_a?(Symbol)
|
113
|
+
raise ArgumentError, "definition is nil" unless definition
|
114
|
+
raise ArgumentError, "Class #{name} not defined" unless class_definition(name)
|
115
|
+
@class_extensions[name] = ClassExtension.new(name, base, definition)
|
116
|
+
end
|
115
117
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
118
|
+
# triggers loading of all defined classes
|
119
|
+
def load!
|
120
|
+
class_names.each { |name| self[name] }
|
121
|
+
end
|
120
122
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
123
|
+
# @return [Class] defined class
|
124
|
+
def [](name)
|
125
|
+
return @classes[name] if @classes[name]
|
126
|
+
return nil unless klass_definition = class_definition(name)
|
127
|
+
|
128
|
+
superclass = case klass_definition.superclass_or_name
|
129
|
+
when Symbol then
|
130
|
+
self[klass_definition.superclass_or_name]
|
131
|
+
when Class then
|
132
|
+
klass = Class.new(klass_definition.superclass_or_name)
|
133
|
+
klass.send :include, Describable
|
134
|
+
klass._description = "Describable#{klass_definition.superclass_or_name}"
|
135
|
+
klass
|
136
|
+
when nil then
|
137
|
+
DescribableClass
|
138
|
+
end
|
139
|
+
|
140
|
+
klass = Class.new(superclass, &klass_definition.definition)
|
141
|
+
klass._description = "#{base}.dc[:#{klass_definition.name}]"
|
142
|
+
|
143
|
+
class_extensions(name).each do |klass_extension|
|
144
|
+
klass = Class.new(klass, &klass_extension.definition)
|
145
|
+
klass._description = "#{base}.dc[:#{klass_extension.name}]"
|
146
|
+
end
|
147
|
+
|
148
|
+
@classes[name] = klass
|
134
149
|
end
|
135
|
-
klass = Class.new(superclass, &klass_definition.definition)
|
136
|
-
klass._description = "#{base}.dc[:#{klass_definition.name}]"
|
137
150
|
|
138
|
-
|
139
|
-
|
140
|
-
klass._description = "#{base}.dc[:#{klass_extension.name}]"
|
151
|
+
def class_names
|
152
|
+
ancestors.map(&:class_definitions).map(&:keys).flatten
|
141
153
|
end
|
142
154
|
|
143
|
-
|
144
|
-
end
|
155
|
+
private
|
145
156
|
|
146
|
-
|
157
|
+
def class_definition(name)
|
158
|
+
@class_definitions[name] || ancestor.try(:class_definition, name)
|
159
|
+
end
|
147
160
|
|
148
|
-
|
149
|
-
|
150
|
-
|
161
|
+
def class_extensions(name)
|
162
|
+
([*ancestor.try(:class_extensions, name)] + [@class_extensions[name]]).compact
|
163
|
+
end
|
151
164
|
|
152
|
-
|
153
|
-
|
165
|
+
def ancestors
|
166
|
+
([self] + [*ancestor.try(:ancestors)]).compact
|
167
|
+
end
|
168
|
+
|
169
|
+
def ancestor
|
170
|
+
@base.superclass.dynamic_classes if @base.superclass.kind_of?(DynamicClasses)
|
171
|
+
end
|
154
172
|
end
|
155
173
|
|
156
|
-
|
157
|
-
|
174
|
+
# hook to create Classes instance
|
175
|
+
def self.extended(base)
|
176
|
+
base.send :create_dynamic_classes
|
177
|
+
super
|
158
178
|
end
|
159
179
|
|
160
|
-
|
161
|
-
|
180
|
+
# hook to create Classes instance in descendants
|
181
|
+
def inherited(base)
|
182
|
+
base.send :create_dynamic_classes
|
183
|
+
super
|
162
184
|
end
|
163
185
|
|
164
|
-
|
165
|
-
|
186
|
+
# call this to get access to Classes instance to define/extend classes inside +definition+
|
187
|
+
# calls Classes#load! to preload defined classes
|
188
|
+
# @yield [Proc, nil] definition
|
189
|
+
# a Proc enables writing class definitions/extensions
|
190
|
+
# @return [Classes] when definition is nil
|
191
|
+
def dynamic_classes(&definition)
|
192
|
+
if definition
|
193
|
+
@dynamic_classes.instance_eval &definition
|
194
|
+
# @dynamic_classes.load!
|
195
|
+
nil
|
196
|
+
else
|
197
|
+
@dynamic_classes
|
198
|
+
end
|
166
199
|
end
|
167
|
-
end
|
168
200
|
|
169
|
-
|
170
|
-
def self.extended(base)
|
171
|
-
base.send :create_dynamic_classes
|
172
|
-
super
|
173
|
-
end
|
201
|
+
alias_method :dc, :dynamic_classes
|
174
202
|
|
175
|
-
|
176
|
-
def inherited(base)
|
177
|
-
base.send :create_dynamic_classes
|
178
|
-
super
|
179
|
-
end
|
203
|
+
private
|
180
204
|
|
181
|
-
|
182
|
-
|
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
|
205
|
+
def create_dynamic_classes
|
206
|
+
@dynamic_classes = Classes.new(self)
|
193
207
|
end
|
194
208
|
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
209
|
end
|
204
210
|
|
205
211
|
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'hammer_builder/standard'
|
2
|
+
|
3
|
+
module HammerBuilder
|
4
|
+
# Builder implementation with formatting (indented by ' ')
|
5
|
+
# Slow down is less then 1%
|
6
|
+
class Formatted < Standard
|
7
|
+
|
8
|
+
dynamic_classes do
|
9
|
+
extend_class :AbstractTag do
|
10
|
+
def open(attributes = nil)
|
11
|
+
@output << Strings::NEWLINE << Strings::SPACES.fetch(@stack.size, Strings::SPACE) << Strings::LT << @tag_name
|
12
|
+
@builder.current = self
|
13
|
+
attributes(attributes)
|
14
|
+
default
|
15
|
+
self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
extend_class :AbstractDoubleTag do
|
20
|
+
def with
|
21
|
+
flush_classes
|
22
|
+
@output << Strings::GT
|
23
|
+
@content = nil
|
24
|
+
@builder.current = nil
|
25
|
+
yield
|
26
|
+
#if (content = yield).is_a?(String)
|
27
|
+
# @output << EscapeUtils.escape_html(content, false)
|
28
|
+
#end
|
29
|
+
@builder.flush
|
30
|
+
@output << Strings::NEWLINE << Strings::SPACES.fetch(@stack.size-1, Strings::SPACE) << Strings::SLASH_LT <<
|
31
|
+
@stack.pop << Strings::GT
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def comment(comment)
|
38
|
+
flush
|
39
|
+
@_output << Strings::NEWLINE << Strings::SPACES.fetch(@_stack.size, Strings::SPACE) << Strings::COMMENT_START <<
|
40
|
+
comment.to_s << Strings::COMMENT_END
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module HammerBuilder
|
2
|
+
module Helper
|
3
|
+
|
4
|
+
# adds instance method to the class. Method accepts any instance of builder and returns it after rendering.
|
5
|
+
# @param [Symbol] method_name
|
6
|
+
# @yield [self] builder_block is evaluated inside builder and accepts instance of a rendered object as parameter
|
7
|
+
# @example
|
8
|
+
# class User
|
9
|
+
# # ...
|
10
|
+
# include HammerBuilder::Helper
|
11
|
+
#
|
12
|
+
# builder :menu do |user|
|
13
|
+
# li user.name
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# User.new.menu(HammerBuilder::Standard.get).to_html! #=> "<li>Name</li>"
|
18
|
+
def builder(method_name, &builder_block)
|
19
|
+
define_method(method_name) do |builder, *args|
|
20
|
+
builder.dive(self, *args, &builder_block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module HammerBuilder
|
2
|
+
|
3
|
+
# Creating builder instances is expensive, therefore you can use Pool to go around that
|
4
|
+
# @example
|
5
|
+
# pool = Pool.new Formatted
|
6
|
+
# pool.get.go_in do
|
7
|
+
# # some rendering
|
8
|
+
# end.to_xhtml! # => output and releases the builder to pool
|
9
|
+
class Pool
|
10
|
+
|
11
|
+
module Helper
|
12
|
+
def release
|
13
|
+
@_origin.release self
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [String] output and releases the builder to pool
|
17
|
+
def to_html!
|
18
|
+
to_html
|
19
|
+
ensure
|
20
|
+
release
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :klass
|
25
|
+
|
26
|
+
def initialize(klass)
|
27
|
+
@klass = klass
|
28
|
+
@pool = []
|
29
|
+
klass.send :include, Helper
|
30
|
+
end
|
31
|
+
|
32
|
+
# This the preferred way of getting new Builder. If you forget to release it, it does not matter -
|
33
|
+
# builder gets GCed after you lose reference
|
34
|
+
# @return [Abstract]
|
35
|
+
def get
|
36
|
+
if @pool.empty?
|
37
|
+
@klass.new.instance_exec(self) { |origin| @_origin = origin; self }
|
38
|
+
else
|
39
|
+
@pool.pop
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# returns +builder+ back into pool *DONT* forget to lose the reference to the +builder+
|
44
|
+
# @param [Abstract]
|
45
|
+
def release(builder)
|
46
|
+
raise TypeError unless builder.is_a? @klass
|
47
|
+
builder.reset
|
48
|
+
@pool.push builder
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def size
|
53
|
+
@pool.size
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class SynchronizedPool < Pool
|
58
|
+
def initialize(klass)
|
59
|
+
super(klass)
|
60
|
+
@mutex = Mutex.new
|
61
|
+
end
|
62
|
+
|
63
|
+
def get
|
64
|
+
@mutex.synchronize { super }
|
65
|
+
end
|
66
|
+
|
67
|
+
def release(builder)
|
68
|
+
@mutex.synchronize { super(builder) }
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|