bufferaffects 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/lib/bufferaffects.rb +11 -0
- data/lib/bufferaffects/attr_inheritable.rb +48 -0
- data/lib/bufferaffects/bufferaffects.rb +254 -0
- metadata +55 -0
@@ -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
|
+
|