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.
@@ -1,16 +1,18 @@
1
1
  require 'active_support/core_ext/object/try'
2
2
 
3
- # When extended into a class it enables easy defining and extending classes in extended class.
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
- # dc do
8
- # define :A do
9
+ # dynamic_classes do
10
+ # def_class :A do
9
11
  # def to_s
10
12
  # 'a'
11
13
  # end
12
14
  # end
13
- # define :B, :A do
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
- # dc do
28
- # extend :A do
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
- # 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
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
- module ClassMethods
54
- # sets +description+
55
- # @param [String] description
56
- def _description=(description)
57
- @_description = description
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
- super.gsub(/>$/, "(#{@_description})>")
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
- def to_s
66
- klass = respond_to?(:rclass) ? self.rclass : self.class
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
- ClassDefinition = Struct.new(:name, :base, :superclass_or_name, :definition)
76
- ClassExtension = Struct.new(:name, :base, :definition)
77
+ ClassDefinition = Struct.new(:name, :base, :superclass_or_name, :definition)
78
+ ClassExtension = Struct.new(:name, :base, :definition)
77
79
 
78
- class Classes
79
- attr_reader :base, :class_definitions, :classes, :class_extensions
80
+ class Classes
81
+ attr_reader :base, :class_definitions, :classes, :class_extensions
80
82
 
81
- def initialize(base)
82
- raise unless base.is_a? Class
83
- @base = base
84
- @class_definitions = {}
85
- @class_extensions = {}
86
- @classes = {}
87
- end
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
- # 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"
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
- # 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
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
- # triggers loading of all defined classes
117
- def load!
118
- class_names.each {|name| self[name] }
119
- end
118
+ # triggers loading of all defined classes
119
+ def load!
120
+ class_names.each { |name| self[name] }
121
+ end
120
122
 
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
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
- class_extensions(name).each do |klass_extension|
139
- klass = Class.new(klass, &klass_extension.definition)
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
- @classes[name] = klass
144
- end
155
+ private
145
156
 
146
- private
157
+ def class_definition(name)
158
+ @class_definitions[name] || ancestor.try(:class_definition, name)
159
+ end
147
160
 
148
- def class_names
149
- ancestors.map(&:class_definitions).map(&:keys).flatten
150
- end
161
+ def class_extensions(name)
162
+ ([*ancestor.try(:class_extensions, name)] + [@class_extensions[name]]).compact
163
+ end
151
164
 
152
- def class_definition(name)
153
- @class_definitions[name] || ancestor.try(:class_definition, name)
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
- def class_extensions(name)
157
- ( [*ancestor.try(:class_extensions, name)] + [@class_extensions[name]] ).compact
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
- def ancestors
161
- ( [self] + [*ancestor.try(:ancestors)] ).compact
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
- def ancestor
165
- @base.superclass.dynamic_classes if @base.superclass.kind_of?(DynamicClasses)
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
- # hook to create Classes instance
170
- def self.extended(base)
171
- base.send :create_dynamic_classes
172
- super
173
- end
201
+ alias_method :dc, :dynamic_classes
174
202
 
175
- # hook to create Classes instance in descendants
176
- def inherited(base)
177
- base.send :create_dynamic_classes
178
- super
179
- end
203
+ private
180
204
 
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
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