nice-ffi 0.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.
@@ -0,0 +1,414 @@
1
+ #--
2
+ #
3
+ # This file is one part of:
4
+ #
5
+ # Nice-FFI - Convenience layer atop Ruby-FFI
6
+ #
7
+ # Copyright (c) 2009 John Croisant
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining
10
+ # a copy of this software and associated documentation files (the
11
+ # "Software"), to deal in the Software without restriction, including
12
+ # without limitation the rights to use, copy, modify, merge, publish,
13
+ # distribute, sublicense, and/or sell copies of the Software, and to
14
+ # permit persons to whom the Software is furnished to do so, subject to
15
+ # the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be
18
+ # included in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
25
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
26
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+ #
28
+ #++
29
+
30
+
31
+ require 'ffi'
32
+
33
+ need{ 'typedpointer' }
34
+
35
+
36
+ # A class to be used as a baseclass where you would use FFI::Struct.
37
+ # It acts mostly like FFI::Struct, but with nice extra features and
38
+ # conveniences to make life easier:
39
+ #
40
+ # * Automatically defines read and write accessor methods (e.g. #x,
41
+ # #x=) for struct members when you call #layout. (You can use
42
+ # #hidden and #read_only before or after calling #layout to affect
43
+ # which members have accessors.)
44
+ #
45
+ # * Implements "smart" accessors for TypedPointer types, seamlessly
46
+ # wrapping those members so you don't even have to think about the
47
+ # fact they are pointers!
48
+ #
49
+ # * Implements a nicer #new method which allows you to create a new
50
+ # struct and set its data in one shot by passing an Array, Hash, or
51
+ # another instance of the class (to copy data). You can also use it
52
+ # to wrap a FFI::Pointer or FFI::MemoryPointer like FFI::Struct can.
53
+ #
54
+ # * Implements #to_ary and #to_hash to dump the struct data.
55
+ #
56
+ # * Implements #to_s and #inspect for nice debugging output.
57
+ #
58
+ class NiceFFI::Struct < FFI::Struct
59
+
60
+ class << self
61
+
62
+ # Same syntax as FFI::Struct#layout, but also defines nice
63
+ # accessors for the attributes.
64
+ #
65
+ # Example:
66
+ #
67
+ # class Rect < NiceStruct
68
+ # layout( :x, :int16,
69
+ # :y, :int16,
70
+ # :w, :uint16,
71
+ # :h, :uint16 )
72
+ # end
73
+ #
74
+ def layout( *spec )
75
+ @nice_spec = spec
76
+
77
+ # Wrap the members.
78
+ 0.step(spec.size - 1, 2) { |index|
79
+ member, type = spec[index, 2]
80
+ wrap_member( member, type)
81
+ }
82
+
83
+ simple_spec = spec.collect { |a|
84
+ case a
85
+ when NiceFFI::TypedPointer
86
+ :pointer
87
+ else
88
+ a
89
+ end
90
+ }
91
+
92
+ # Normal FFI::Struct behavior
93
+ super( *simple_spec )
94
+ end
95
+
96
+
97
+ # Mark the given members as hidden, i.e. do not create accessors
98
+ # for them in #layout, and do not print them out in #to_s, etc.
99
+ # You can call this before or after calling #layout, and can call
100
+ # it more than once if you like.
101
+ #
102
+ # Note: They can still be read and written via #[] and #[]=,
103
+ # but will not have convenience accessors.
104
+ #
105
+ # Note: This will remove the accessor methods (if they exist) for
106
+ # the members! So if you're defining your own custom accessors, do
107
+ # that *after* you have called this method.
108
+ #
109
+ # Example:
110
+ #
111
+ # class SecretStruct < NiceStruct
112
+ #
113
+ # # You can use it before the layout...
114
+ # hidden( :hidden1 )
115
+ #
116
+ # layout( :visible1, :uint16,
117
+ # :visible2, :int,
118
+ # :hidden1, :uint,
119
+ # :hidden2, :pointer )
120
+ #
121
+ # # ... and/or after it.
122
+ # hidden( :hidden2 )
123
+ #
124
+ # # :hidden1 and :hidden2 are now both hidden.
125
+ # end
126
+ #
127
+ def hidden( *members )
128
+ if defined?(@hidden_members)
129
+ @hidden_members += members
130
+ else
131
+ @hidden_members = members
132
+ end
133
+
134
+ members.each do |member|
135
+ # Remove the accessors if they exist.
136
+ [member, "#{member}=".to_sym].each { |m|
137
+ begin
138
+ remove_method( m )
139
+ rescue NameError
140
+ end
141
+ }
142
+ end
143
+ end
144
+
145
+
146
+ # True if the member has been marked #hidden, false otherwise.
147
+ def hidden?( member )
148
+ return false unless defined?(@hidden_members)
149
+ @hidden_members.include?( member )
150
+ end
151
+
152
+
153
+ # Mark the given members as read-only, so they won't have write
154
+ # accessors.
155
+ #
156
+ # Note: They can still be written via #[]=,
157
+ # but will not have convenience accessors.
158
+ #
159
+ # Note: This will remove the writer method (if it exists) for
160
+ # the members! So if you're defining your own custom writer, do
161
+ # that *after* you have called this method.
162
+ #
163
+ # Example:
164
+ #
165
+ # class SecretStruct < NiceStruct
166
+ #
167
+ # # You can use it before the layout...
168
+ # read_only( :readonly1 )
169
+ #
170
+ # layout( :visible1, :uint16,
171
+ # :visible2, :int,
172
+ # :readonly1, :uint,
173
+ # :readonly2, :pointer )
174
+ #
175
+ # # ... and/or after it.
176
+ # read_only( :readonly2 )
177
+ #
178
+ # # :readonly1 and :readonly2 are now both read-only.
179
+ # end
180
+ #
181
+ def read_only( *members )
182
+ if defined?(@readonly_members)
183
+ @readonly_members += members
184
+ else
185
+ @readonly_members = members
186
+ end
187
+
188
+ members.each do |member|
189
+ # Remove the write accessor if it exists.
190
+ begin
191
+ remove_method( "#{member}=".to_sym )
192
+ rescue NameError
193
+ end
194
+ end
195
+ end
196
+
197
+ # True if the member has been marked #read_only, false otherwise.
198
+ def read_only?( member )
199
+ return false unless defined?(@readonly_members)
200
+ @readonly_members.include?( member )
201
+ end
202
+
203
+
204
+ private
205
+
206
+
207
+ # Defines read and write accessors for the given struct member.
208
+ # This is similar to attr_accessor, except these accessors read
209
+ # and write the struct's members, instead of to instance
210
+ # variables.
211
+ #
212
+ # E.g. `wrap_member(:x, :int16)` defines #x and #x= read and write
213
+ # to the struct's :x member, which is an int16.
214
+ #
215
+ # Normally you don't need to call this method, because #layout
216
+ # does this automatically.
217
+ #
218
+ def wrap_member( member, type )
219
+ @hidden_members = [] unless defined?(@hidden_members)
220
+
221
+ unless hidden?( member )
222
+ _make_reader( member, type )
223
+ unless read_only?( member )
224
+ _make_writer( member, type )
225
+ end
226
+ end
227
+ end
228
+
229
+
230
+ def _make_reader( member, type ) # :nodoc:
231
+ # POINTERS
232
+ if( type.is_a? NiceFFI::TypedPointer )
233
+ self.class_eval do
234
+
235
+ define_method( member ) do
236
+ @member_cache[member] or
237
+ (@member_cache[member] = type.wrap(self[member]))
238
+ end
239
+
240
+ end
241
+
242
+ # STRUCTS
243
+ elsif( type.is_a? Class and type.ancestors.include? FFI::Struct )
244
+ self.class_eval do
245
+
246
+ define_method( member ) do
247
+ @member_cache[member] or
248
+ (@member_cache[member] = self[member])
249
+ end
250
+
251
+ end
252
+
253
+ # OTHER TYPES
254
+ else
255
+ self.class_eval do
256
+ define_method( member ) do
257
+ begin
258
+ self[member]
259
+ rescue FFI::NullPointerError
260
+ nil
261
+ end
262
+ end
263
+ end
264
+ end
265
+ end
266
+
267
+
268
+ def _make_writer( member, type ) # :nodoc:
269
+
270
+ # POINTERS
271
+ if( type.is_a? NiceFFI::TypedPointer )
272
+ self.class_eval do
273
+
274
+ define_method( "#{member}=".to_sym ) do |val|
275
+ unless val.is_a?( type.type )
276
+ raise TypeError, "got #{val.class}, expected #{type.type}"
277
+ end
278
+
279
+ self[member] = type.unwrap(val)
280
+ @member_cache.delete(member)
281
+ end
282
+
283
+ end
284
+
285
+
286
+ # TODO: Make a writer for nested structs (not struct pointers).
287
+ # They can't actually be overwritten, but we could overwrite
288
+ # the values, perhaps. May need to be recursive, since the
289
+ # nested struct might also contain a nested struct.
290
+
291
+
292
+ # OTHER TYPES
293
+ else
294
+ self.class_eval do
295
+
296
+ define_method( "#{member}=".to_sym ) do |val|
297
+ self[member] = val
298
+ end
299
+
300
+ end
301
+ end
302
+ end
303
+
304
+
305
+ end
306
+
307
+
308
+ # Create a new instance of the class, reading data from a Hash or
309
+ # Array of attributes, copying from another instance of the class,
310
+ # or wrapping (not copying!) a FFI::Pointer or FFI::MemoryPointer.
311
+ #
312
+ def initialize( val )
313
+ # Stores certain kinds of member values so that we don't need
314
+ # to create a new object every time they are read.
315
+ @member_cache = {}
316
+
317
+ case val
318
+
319
+ when Hash
320
+ super() # Create empty struct
321
+ init_from_hash( val ) # Read the values from a Hash.
322
+
323
+ # Note: plain "Array" would mean FFI::Struct::Array in this scope.
324
+ when ::Array
325
+ super() # Create empty struct
326
+ init_from_array( val ) # Read the values from an Array.
327
+
328
+ when self.class
329
+ super() # Create empty struct
330
+ init_from_array( val.to_ary ) # Read the values from another instance.
331
+
332
+ when FFI::Pointer, FFI::MemoryPointer
333
+ # Normal FFI::Struct behavior to wrap the pointer.
334
+ super( val )
335
+
336
+ else
337
+ raise TypeError, "cannot create new #{self.class} from #{val.inspect}"
338
+
339
+ end
340
+ end
341
+
342
+
343
+ def init_from_hash( val ) # :nodoc:
344
+ members.each do |member|
345
+ self[ member ] = val[ member ]
346
+ end
347
+ end
348
+ private :init_from_hash
349
+
350
+
351
+ def init_from_array( val ) # :nodoc:
352
+ members.each_with_index do |member, i|
353
+ self[ member ] = val[ i ]
354
+ end
355
+ end
356
+ private :init_from_array
357
+
358
+
359
+
360
+ # Dump this instance as an Array of its struct data.
361
+ # The array contains only the data, not the member names.
362
+ #
363
+ # Note: the order of data in the array always matches the
364
+ # order of members given in #layout.
365
+ #
366
+ # Example:
367
+ #
368
+ # Rect.new( :x=>1, :y=>2, :w=>3, :h=>4 ).to_ary
369
+ # # => [1,2,3,4]
370
+ #
371
+ def to_ary
372
+ members.collect{ |m| self[m] }
373
+ end
374
+
375
+ # Dump this instance as a Hash containing {member => data} pairs
376
+ # for every member in the struct.
377
+ #
378
+ # Example:
379
+ #
380
+ # Rect.new( :x=>1, :y=>2, :w=>3, :h=>4 ).to_ary
381
+ # # => {:h=>4, :w=>3, :x=>1, :y=>2}
382
+ #
383
+ def to_hash
384
+ return {} if members.empty?
385
+ Hash[ *(members.collect{ |m| [m, self[m]] }.flatten!) ]
386
+ end
387
+
388
+
389
+ def to_s
390
+ mems = members.collect{ |m|
391
+ unless self.class.hidden?( m )
392
+ val = self.send(m)
393
+
394
+ # Cleanup/simplify for display
395
+ if val.is_a? FFI::NullPointer or val.nil?
396
+ val = "NULL"
397
+ elsif val.kind_of? FFI::Struct
398
+ val = "#<#{val.class}:%#.x>"%val.object_id
399
+ end
400
+
401
+ "@#{m}=#{val}"
402
+ end
403
+ }.compact.join(", ")
404
+
405
+ if( mems == "" )
406
+ return "#<%s:%#.x>"%[self.class.name, self.object_id]
407
+ else
408
+ return "#<%s:%#.x %s>"%[self.class.name, self.object_id, mems]
409
+ end
410
+ end
411
+
412
+ alias :inspect :to_s
413
+
414
+ end
@@ -0,0 +1,333 @@
1
+ #--
2
+ #
3
+ # This file is one part of:
4
+ #
5
+ # Nice-FFI - Convenience layer atop Ruby-FFI
6
+ #
7
+ # Copyright (c) 2009 John Croisant
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining
10
+ # a copy of this software and associated documentation files (the
11
+ # "Software"), to deal in the Software without restriction, including
12
+ # without limitation the rights to use, copy, modify, merge, publish,
13
+ # distribute, sublicense, and/or sell copies of the Software, and to
14
+ # permit persons to whom the Software is furnished to do so, subject to
15
+ # the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be
18
+ # included in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
25
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
26
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+ #
28
+ #++
29
+
30
+
31
+ # PathSet is essentially a Hash of { os_regex => path_templates } pairs,
32
+ # rules describing where to look for files (libraries) on each
33
+ # operating system.
34
+ #
35
+ # * os_regex is a regular expression that matches FFI::Platform::OS
36
+ # for the operating system(s) that the path templates are for.
37
+ #
38
+ # * path_templates is an Array of one or more strings describing
39
+ # a template for where a library might be found on this OS.
40
+ # The string [NAME] will be replaced with the library name.
41
+ # So "/usr/lib/lib[NAME].so" becomes e.g. "/usr/lib/libSDL_ttf.so".
42
+ #
43
+ # You can use #append!, #prepend!, #replace!, #remove!, and #clear!
44
+ # to modify the rules, and #find to look for a file with a matching
45
+ # name.
46
+ #
47
+ class NiceFFI::PathSet
48
+
49
+
50
+ def initialize( rules={} )
51
+ @rules = rules
52
+ end
53
+
54
+ attr_reader :rules
55
+
56
+ def dup
57
+ self.class.new( @rules.dup )
58
+ end
59
+
60
+
61
+ # Append the new rules to this PathSet. If this PathSet already
62
+ # has rules for a regex in the new rules, the new rules will be
63
+ # added after the current rules.
64
+ #
65
+ # The given rules can be Hashes or existing PathSets; or
66
+ # Arrays to append the given rules to every existing regex.
67
+ #
68
+ #
69
+ # Example:
70
+ #
71
+ # ps = PathSet.new( /a/ => ["liba"],
72
+ # /b/ => ["libb"] )
73
+ #
74
+ # ps.append!( /a/ => ["newliba"],
75
+ # /c/ => ["libc"] )
76
+ #
77
+ # ps.rules
78
+ # # => { /a/ => ["liba",
79
+ # # "newliba"], # added in back
80
+ # # /b/ => ["libb"], # not affected
81
+ # # /c/ => ["libc"] } # added
82
+ #
83
+ def append!( *ruleses )
84
+ ruleses.each do |rules|
85
+ _modify( rules ) { |a,b| a + b }
86
+ end
87
+ self
88
+ end
89
+
90
+ # Like #append!, but returns a copy instead of modifying the original.
91
+ def append( *ruleses )
92
+ self.dup.append!( *ruleses )
93
+ end
94
+
95
+ alias :+ :append
96
+ alias :<< :append
97
+
98
+
99
+
100
+ # Prepend the new rules to this PathSet. If this PathSet already
101
+ # has rules for a regex in the new rules, the new rules will be
102
+ # added before the current rules.
103
+ #
104
+ # The given rules can be Hashes or existing PathSets; or
105
+ # Arrays to prepend the given rules to every existing regex.
106
+ #
107
+ #
108
+ # Example:
109
+ #
110
+ # ps = PathSet.new( /a/ => ["liba"],
111
+ # /b/ => ["libb"] )
112
+ #
113
+ # ps.prepend!( /a/ => ["newliba"],
114
+ # /c/ => ["libc"] )
115
+ #
116
+ # ps.rules
117
+ # # => { /a/ => ["newliba", # added in front
118
+ # # "liba"],
119
+ # # /b/ => ["libb"], # not affected
120
+ # # /c/ => ["libc"] } # added
121
+ #
122
+ def prepend!( *ruleses )
123
+ ruleses.each do |rules|
124
+ _modify( rules ) { |a,b| b + a }
125
+ end
126
+ self
127
+ end
128
+
129
+ # Like #prepend!, but returns a copy instead of modifying the original.
130
+ def prepend( *ruleses )
131
+ self.dup.prepend!( *ruleses )
132
+ end
133
+
134
+ alias :>> :prepend
135
+
136
+
137
+
138
+ # Override existing rules with the new rules to this PathSet.
139
+ # If this PathSet already has rules for a regex in the new rules,
140
+ # the old rules will be discarded and the new rules used instead.
141
+ # Old rules are kept if their regex doesn't appear in the new rules.
142
+ #
143
+ # The given rules can be Hashes or existing PathSets; or
144
+ # Arrays to replace the given rules for every existing regex.
145
+ #
146
+ #
147
+ # Example:
148
+ #
149
+ # ps = PathSet.new( /a/ => ["liba"],
150
+ # /b/ => ["libb"] )
151
+ #
152
+ # ps.replace!( /a/ => ["newliba"],
153
+ # /c/ => ["libc"] )
154
+ #
155
+ # ps.rules
156
+ # # => { /a/ => ["newliba"], # replaced
157
+ # # /b/ => ["libb"], # not affected
158
+ # # /c/ => ["libc"] } # added
159
+ #
160
+ def replace!( *ruleses )
161
+ ruleses.each do |rules|
162
+ _modify( rules ) { |a,b| b }
163
+ end
164
+ self
165
+ end
166
+
167
+ # Like #replace!, but returns a copy instead of modifying the original.
168
+ def replace( *ruleses )
169
+ self.dup.replace!( *ruleses )
170
+ end
171
+
172
+
173
+
174
+ # Remove the given rules from the PathSet, if it has them.
175
+ # This only removes the rules that are given, other rules
176
+ # for the same regex are kept.
177
+ #
178
+ # The given rules can be Hashes or existing PathSets; or
179
+ # Arrays to remove the given rules from every existing regex.
180
+ #
181
+ # Regexes with no rules left are pruned.
182
+ #
183
+ #
184
+ # Example:
185
+ #
186
+ # ps = PathSet.new( /a/ => ["liba", "badliba"],
187
+ # /b/ => ["libb"] )
188
+ #
189
+ # ps.remove!( /a/ => ["badliba"],
190
+ # /b/ => ["libb"] )
191
+ # /c/ => ["libc"] )
192
+ #
193
+ # ps.rules
194
+ # # => { /a/ => ["liba"] } # removed only "badliba".
195
+ # # # /b/ rules were all removed.
196
+ # # # /c/ not affected because it had no old rules anyway.
197
+ #
198
+ def remove!( *ruleses )
199
+ ruleses.each do |rules|
200
+ _modify( rules ) { |a,b| a - b }
201
+ end
202
+ self
203
+ end
204
+
205
+ # Like #remove!, but returns a copy instead of modifying the original.
206
+ def remove( *ruleses )
207
+ self.dup.remove!( *ruleses )
208
+ end
209
+
210
+ alias :- :remove
211
+
212
+
213
+
214
+ # Remove all rules for the given regex(es). Has no effect on
215
+ # regexes that are not given.
216
+ #
217
+ #
218
+ # Example:
219
+ #
220
+ # ps = PathSet.new( /a/ => ["liba"],
221
+ # /b/ => ["libb"] )
222
+ #
223
+ # ps.delete!( /b/, /c/ )
224
+ #
225
+ # ps.rules
226
+ # # => { /a/ => ["liba"] } # not affected
227
+ # # # /b/ and all rules removed.
228
+ # # # /c/ not affected because it had no rules anyway.
229
+ #
230
+ def delete!( *regexs )
231
+ @rules.delete_if { |regex, paths| regexs.include? regex }
232
+ self
233
+ end
234
+
235
+ # Like #delete!, but returns a copy instead of modifying the original.
236
+ def delete( *regexs )
237
+ self.dup.delete!( *regexs )
238
+ end
239
+
240
+
241
+ # Try to find a file based on the rules in this PathSet.
242
+ #
243
+ # *names:: Strings to try substituting for [NAME] in the paths.
244
+ #
245
+ # Returns an Array of the paths of matching files, or [] if
246
+ # there were no matches.
247
+ #
248
+ # Raises LoadError if the current operating system did not match
249
+ # any of the regular expressions in the PathSet.
250
+ #
251
+ # Examples:
252
+ #
253
+ # ps = PathSet.new( /linux/ => ["/usr/lib/lib[NAME].so"],
254
+ # /win32/ => ["C:\\windows\\system32\\[NAME].dll"] )
255
+ #
256
+ # ps.find( "SDL" )
257
+ # ps.find( "foo", "foo_alt_name" )
258
+ #
259
+ def find( *names )
260
+
261
+ os = FFI::Platform::OS
262
+
263
+ # Remember the paths that we found.
264
+ found = []
265
+
266
+ # Remember whether any of the search paths included our OS.
267
+ os_supported = false
268
+
269
+ # Find the regexs that matches our OS.
270
+ os_matches = @rules.keys.find_all{ |regex| regex =~ os }
271
+
272
+ # Drat, they are using an unsupported OS.
273
+ if os_matches.empty?
274
+ raise( LoadError, "Your OS (#{os}) is not supported yet.\n" +
275
+ "Please report this and help us support more platforms." )
276
+ end
277
+
278
+ os_matches.each do |os_match|
279
+ # Fetch the paths for the matching OS.
280
+ paths = @rules[os_match]
281
+
282
+ # Fill in for [NAME] and expand the paths.
283
+ paths = names.collect { |name|
284
+ paths.collect { |path|
285
+ File.expand_path( path.gsub("[NAME]", name) )
286
+ }
287
+ }.flatten!
288
+
289
+ # Delete all the paths that don't exist.
290
+ paths.delete_if { |path| not File.exist?(path) }
291
+
292
+ # Add what's left.
293
+ found += paths
294
+ end
295
+
296
+ return found
297
+ end
298
+
299
+
300
+ private
301
+
302
+
303
+ def _modify( rules, &block ) # :nodoc:
304
+ raise "No block given!" unless block_given?
305
+
306
+ case rules
307
+ when self.class
308
+ _modify( rules.rules, &block )
309
+ when Hash
310
+ rules.each do |regex, paths|
311
+ _apply_modifier( regex, (@rules[regex] or []), paths, &block )
312
+ end
313
+ when Array
314
+ @rules.each { |regex, paths|
315
+ _apply_modifier( regex, paths, rules, &block )
316
+ }
317
+ end
318
+ end
319
+
320
+
321
+ def _apply_modifier( regex, a, b, &block ) # :nodoc:
322
+ raise "No block given!" unless block_given?
323
+
324
+ result = yield( a, b )
325
+
326
+ if result == []
327
+ @rules.delete( regex )
328
+ else
329
+ @rules[regex] = result
330
+ end
331
+ end
332
+
333
+ end