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.
- data/ChangeLog.txt +590 -0
- data/README.rdoc +76 -0
- data/TODO.rdoc +16 -0
- data/docs/usage.rdoc +254 -0
- data/lib/nice-ffi.rb +47 -0
- data/lib/nice-ffi/nicelibrary.rb +172 -0
- data/lib/nice-ffi/nicestruct.rb +414 -0
- data/lib/nice-ffi/pathset.rb +333 -0
- data/lib/nice-ffi/typedpointer.rb +88 -0
- metadata +86 -0
@@ -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
|