bufferaffects 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
+ # encoding: utf-8
2
+ #
3
+ # Easy and automatic resetting of buffers when certain methods are called.
4
+ #
5
+ # Author:: Paweł Wilk (mailto:pw@gnu.org)
6
+ # Copyright:: Copyright (c) 2009 Paweł Wilk
7
+ # License:: LGPL
8
+
9
+ require 'bufferaffects/attr_inheritable'
10
+ require 'bufferaffects/bufferaffects'
11
+
@@ -0,0 +1,48 @@
1
+
2
+ module AttrInheritable
3
+
4
+ def self.included(base) #:nodoc:
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+
10
+ def attr_inheritable(*variables)
11
+ variables.each do |v|
12
+ module_eval %{
13
+ def #{v}
14
+ @#{v} = superclass.#{v} if !instance_variable_defined?(:@#{v}) && superclass.respond_to?(:#{v})
15
+ @#{v} ||= nil
16
+ return @#{v}
17
+ end
18
+ }
19
+ end
20
+ end
21
+
22
+ def attr_inheritable_hash(*variables)
23
+ variables.each do |v|
24
+ module_eval %{
25
+ def #{v}
26
+ @#{v} = superclass.#{v} if !instance_variable_defined?(:@#{v}) && superclass.respond_to?(:#{v})
27
+ @#{v} ||= {}
28
+ return @#{v}
29
+ end
30
+ }
31
+ end
32
+ end
33
+
34
+ def attr_inheritable_array(*variables)
35
+ variables.each do |v|
36
+ module_eval %{
37
+ def #{v}
38
+ @#{v} = superclass.#{v} if !instance_variable_defined?(:@#{v}) && superclass.respond_to?(:#{v})
39
+ if @#{v} ||= []
40
+ return @#{v}
41
+ end
42
+ }
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,254 @@
1
+ # = bufferaffects/bufferaffects
2
+ #
3
+ # Author:: Paweł Wilk (mailto:pw@gnu.org)
4
+ # Copyright:: Copyright (c) 2009 Paweł Wilk
5
+ # License:: LGPL
6
+ #
7
+
8
+ # This module is intended to be used as extension
9
+ # (class level mixin) for classes using some buffers
10
+ # that may be altered by calling certain methods.
11
+ #
12
+ # It automates resetting of buffers by installing
13
+ # wrappers for invasive methods you choose. It rewrites
14
+ # selected methods by adding to them code that calls
15
+ # buffer(s) flushing method created by you.
16
+ #
17
+ # === Markers
18
+ #
19
+ # To select which methods are invasive for your buffer(s)
20
+ # you should use markers which in usage are similar to
21
+ # accessors, e.g:
22
+ #
23
+ # attr_affects_buffers :domain
24
+ #
25
+ # Markers may be placed anywhere in the class. Wrapping
26
+ # routine will wait for methods to be defined if you
27
+ # mark them too early in your code.
28
+ #
29
+ # ==== Marking methods
30
+ #
31
+ # To mark methods which should trigger reset operation
32
+ # when called use method_affects_buffers which takes
33
+ # comma-separated list of symbols describing names
34
+ # of these methods.
35
+ #
36
+ # ==== Marking attributes (setters)
37
+ #
38
+ # The marker attr_affects_buffers is similar but it takes
39
+ # instance members not methods as arguments. It just installs
40
+ # hooks for corresponding setters.
41
+ #
42
+ # === Buffers flushing method
43
+ #
44
+ # Default instance method called to reset buffers should be
45
+ # defined under name +reset_buffers+
46
+ # You may also want to set up your own name by calling
47
+ # buffers_reset_method class method.
48
+ #
49
+ # Buffers flushing method may take none or exactly one argument.
50
+ # If your method will take an argument then a name of calling
51
+ # method will be passed to it as symbol.
52
+ #
53
+ # The name of your
54
+ # buffers flushing method is passed to subclasses but
55
+ # each subclass may redefine it.
56
+ #
57
+ # Be aware that if you have a class that is subclass of
58
+ # a class using BufferAffects then by setting new buffers_reset_method
59
+ # you may experience two methods being called. That may happen
60
+ # when:
61
+ # * your base class has different resetting method assigned
62
+ # than your derivative class
63
+ # * you mark some method or attribute again using attr_affects_buffers or method_affects_buffers
64
+ # * you will not redefine that method in subclass (despite two facts above)
65
+ # That's because we assume that resetting method assigned to a method in superclass
66
+ # may be needed there and it shouldn't be taken away.
67
+ #
68
+ # === Inherited classes
69
+ #
70
+ # This module tries to be inheritance-safe but you will have to
71
+ # mark methods and members in subclasses if you are going
72
+ # to redefine them. That will install triggers again which is needed
73
+ # since redefining creates new code for method which overrides the code
74
+ # altered by BufferAffects.
75
+ #
76
+ # The smooth way is of course to use +super+
77
+ # in overloaded methods so it will also do the job.
78
+ #
79
+ # To be sure that everything will work fine try to place
80
+ # buffers_reset_method clause before any other markers.
81
+ #
82
+ # === Caution
83
+ #
84
+ # This code uses method_added hook. If you're going
85
+ # to redefine that special method in your class while still using
86
+ # this module then remember to call original version or explicitly invoke
87
+ # ba_check_method in your version of method_added:
88
+ #
89
+ # ba_check_method(name)
90
+ #
91
+ # === Example
92
+ #
93
+ # class Main
94
+ #
95
+ # include BufferAffects
96
+ #
97
+ # buffers_reset_method :reset_path_buffer
98
+ # attr_affects_buffers :subpart
99
+ # attr_accessor :subpart, :otherpart
100
+ #
101
+ # def reset_path_buffer(name)
102
+ # @path = nil
103
+ # p "reset called for #{name}"
104
+ # end
105
+ #
106
+ # def path
107
+ # @path ||= @subpart.to_s + @otherpart.to_s
108
+ # end
109
+ #
110
+ # end
111
+ #
112
+ # obj = Main.new
113
+ # obj.subpart = 'test'
114
+ # p obj.path
115
+ # obj.subpart = '1234'
116
+ # p obj.path
117
+
118
+ module BufferAffects
119
+
120
+ def self.included(base) #:nodoc:
121
+ base.extend(ClassMethods)
122
+ end
123
+
124
+ module ClassMethods
125
+
126
+ include AttrInheritable
127
+
128
+ attr_inheritable_hash :__ba_wrapped__
129
+ attr_inheritable :__ba_reset_method__
130
+
131
+ # This method sets name of method that will be used to reset buffers.
132
+
133
+ def buffers_reset_method(name) # :doc:
134
+ name = name.to_s.strip
135
+ raise ArgumentError.new('method name cannot be empty') if name.empty?
136
+ @__ba_reset_method__ = name.to_sym
137
+ end
138
+ private :buffers_reset_method
139
+
140
+ # This method sets the marker for hook to be installed.
141
+ # It ignores methods for which wrapper already exists.
142
+
143
+ def method_affects_buffers(*names) # :doc:
144
+ @__ba_methods__ ||= {}
145
+ names.uniq!
146
+ names.collect! { |name| name.to_sym }
147
+ names.delete_if { |name| @__ba_methods__.has_key?(name) }
148
+ ba_methods_wrap(*names)
149
+ end
150
+ private :method_affects_buffers
151
+
152
+ # This method searches for setter methods for given
153
+ # member names and tries to wrap them into buffers
154
+ # resetting hooks usting method_affects_buffers
155
+
156
+ def attr_affects_buffers(*names) # :doc:
157
+ names.collect! { |name| :"#{name}=" }
158
+ method_affects_buffers(*names)
159
+ end
160
+ private :attr_affects_buffers
161
+
162
+ # This method installs hook for given methods or puts their names
163
+ # on the queue if methods haven't been defined yet. The queue is
164
+ # tested each time ba_check_hook is called.
165
+ #
166
+ # Each processed method can be in one of 2 states:
167
+ # * false - method is not processed now
168
+ # * true - method is now processed
169
+ #
170
+ # After successful wrapping method name (key) and object ID (value) pairs
171
+ # are added two containers: @__ba_wrapped__ and @__ba_methods__
172
+
173
+ def ba_methods_wrap(*names)
174
+ names.delete_if { |name| @__ba_methods__[name] == true } # don't handle methods being processed
175
+ kmethods = public_instance_methods +
176
+ private_instance_methods +
177
+ protected_instance_methods
178
+ install_now = names.select { |name| kmethods.include?(name) } # select methods for immediate wrapping
179
+ install_now.delete_if do |name| # but don't wrap already wrapped, means:
180
+ self.__ba_wrapped__.has_key?(name) && # - wrapped by our class or other class and
181
+ !@__ba_methods__.has_key?(name) && # - not wrapped by our class
182
+ self.__ba_wrapped__[name] == __ba_reset_method__ # - uses the same resetting method
183
+ end
184
+
185
+ install_later = names - install_now # collect undefined and wrapped methods
186
+ install_later.each { |name| @__ba_methods__[name] = false } # and add them to the waiting queue
187
+
188
+ install_now.each { |name| @__ba_methods__[name] = true } # mark methods as currently processed
189
+ installed = ba_install_hook(*install_now) # and install hooks for them
190
+ install_now.each { |name| @__ba_methods__[name] = false } # mark methods as not processed again
191
+ installed.each_pair do |name,id| # and note the reset method assigned to wrapped methods
192
+ @__ba_wrapped__[name] = self.__ba_reset_method__ # inherited container
193
+ @__ba_methods__[name] = self.__ba_reset_method__ # this class's container
194
+ end
195
+ end
196
+ private :ba_methods_wrap
197
+
198
+ # This method checks whether method which name is given
199
+ # is now available and should be installed. In most cases you won't
200
+ # need to use it directly.
201
+
202
+ def ba_check_method(name) # :doc:
203
+ name = name.to_sym
204
+ @__ba_methods__ ||= {}
205
+ if @__ba_methods__.has_key?(name)
206
+ ba_methods_wrap(name)
207
+ end
208
+ end
209
+ private :ba_check_method
210
+
211
+ # This method installs hook which alters given methods by wrapping
212
+ # them into method that invokes buffers resetting routine. It will
213
+ # not install hook for methods beginning with __ba, which signalizes
214
+ # that they are wrappers for other methods.
215
+
216
+ def ba_install_hook(*names)
217
+ @__ba_reset_method__ = 'reset_buffers' if self.__ba_reset_method__.nil?
218
+ installed = {}
219
+ names.uniq.each do |name|
220
+ new_method = name.to_s
221
+ next if new_method[0..3] == '__ba'
222
+ orig_id = instance_method(name.to_sym).object_id
223
+ orig_method = '__ba' + orig_id.to_s + '__'
224
+ reset_method = self.__ba_reset_method__.to_s
225
+ module_eval %{
226
+ alias_method :#{orig_method}, :#{new_method}
227
+ private :#{orig_method}
228
+ def #{new_method}(*args, &block)
229
+ if method(:#{reset_method}).arity == 1
230
+ #{reset_method}(:#{new_method})
231
+ else
232
+ #{reset_method}
233
+ end
234
+ return #{orig_method}(*args, &block)
235
+ end
236
+ }
237
+ installed[name] = orig_id
238
+ end
239
+ return installed
240
+ end
241
+ private :ba_install_hook
242
+
243
+ # Hook that intercepts added methods. It simply calls ba_check_method
244
+ # passing it a name of added method. In most cases there is no need to call it
245
+ # directly.
246
+
247
+ def method_added(name)
248
+ ba_check_method(name)
249
+ end
250
+
251
+ end
252
+
253
+ end
254
+
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bufferaffects
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - "Pawe\xC5\x82 Wilk"
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-22 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: BufferAffects makes it easy to set up automatic resetting of buffers when certain methods are called
17
+ email: pw@gnu.org
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - lib/bufferaffects.rb
26
+ - lib/bufferaffects/bufferaffects.rb
27
+ - lib/bufferaffects/attr_inheritable.rb
28
+ has_rdoc: true
29
+ homepage: http://randomseed.pl/bufferaffects
30
+ post_install_message:
31
+ rdoc_options: []
32
+
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: "0"
40
+ version:
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ requirements: []
48
+
49
+ rubyforge_project:
50
+ rubygems_version: 1.3.1
51
+ signing_key:
52
+ specification_version: 2
53
+ summary: BufferAffects makes it easy to set up automatic resetting of buffers when certain methods are called
54
+ test_files: []
55
+