nice-ffi 0.1 → 0.2

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,109 @@
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
+ # OpaqueStruct represents a Struct with an unknown layout.
32
+ # This is meant to be used when the C library designer has
33
+ # intentionally hidden the layout (e.g. to prevent user access).
34
+ #
35
+ # Because the size of an OpaqueStruct is unknown, you should
36
+ # only use methods provided by the C library to allocate, modify,
37
+ # or free the struct's memory.
38
+ #
39
+ # OpaqueStruct supports the same memory autorelease system as
40
+ # NiceStruct. Define MyClass.release( pointer ) to call the library
41
+ # function to free the struct, and pass an FFI::Pointer to #new. You
42
+ # can disable autorelease for an individual instance by providing
43
+ # {:autorelease => false} as an option to #new
44
+ #
45
+ class NiceFFI::OpaqueStruct
46
+ include NiceFFI::AutoRelease
47
+
48
+
49
+ # Returns a NiceFFI::TypedPointer instance for this class.
50
+ def self.typed_pointer
51
+ @typed_pointer or (@typed_pointer = NiceFFI::TypedPointer.new(self))
52
+ end
53
+
54
+
55
+ # Create a new instance of the class, wrapping (not copying!) a
56
+ # FFI::Pointer. You can pass another instance of this class to
57
+ # create a new instance wrapping the same pointer.
58
+ #
59
+ # If val is an instance of FFI::Pointer and you have defined
60
+ # MyClass.release, the pointer will be passed to MyClass.release
61
+ # when the memory is no longer being used. Use MyClass.release to
62
+ # free the memory for the struct, as appropriate for your class. To
63
+ # disable autorelease for this instance, set {:autorelease => false}
64
+ # in +options+.
65
+ #
66
+ # (Note: FFI::MemoryPointer and FFI::Buffer have built-in memory
67
+ # management, so MyClass.release is never called for them.)
68
+ #
69
+ def initialize( val, options={} )
70
+ options = {:autorelease => true}.merge!( options )
71
+
72
+ case val
73
+
74
+ when self.class
75
+ initialize( val.pointer, options )
76
+
77
+ when FFI::AutoPointer
78
+ @pointer = val
79
+
80
+ when FFI::Pointer
81
+ if val.is_a? FFI::MemoryPointer or val.is_a? FFI::Buffer
82
+ raise TypeError, "unsupported pointer type #{val.class.name}"
83
+ elsif val.null?
84
+ @pointer = val
85
+ else
86
+ @pointer = _make_autopointer( val, options[:autorelease] )
87
+ end
88
+
89
+ else
90
+ raise TypeError, "cannot create new #{self.class} from #{val.inspect}"
91
+
92
+ end
93
+ end
94
+
95
+
96
+ attr_reader :pointer
97
+
98
+ def to_ptr
99
+ @pointer
100
+ end
101
+
102
+
103
+ def to_s
104
+ "#<%s:%#.x>"%[self.class.name, self.object_id]
105
+ end
106
+
107
+ alias :inspect :to_s
108
+
109
+ end
@@ -28,45 +28,102 @@
28
28
  #++
29
29
 
30
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.
31
+ # PathSet is a collection of directory paths and file name templates,
32
+ # used to help NiceFFI find library files. It allows per-operating
33
+ # system paths and file name templates, using regular expressions to
34
+ # match the OS name.
34
35
  #
35
- # * os_regex is a regular expression that matches FFI::Platform::OS
36
- # for the operating system(s) that the path templates are for.
36
+ # Each PathSet holds two hashes, @paths and @files.
37
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".
38
+ # * The keys for both @paths and @files are regexps that match
39
+ # FFI::Platform::OS for the operating system(s) that the paths or
40
+ # file templates apply to.
42
41
  #
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.
42
+ # * The values of @paths are Arrays of one or more strings describing
43
+ # a directory for where a library might be found on this OS. So for
44
+ # example, one pair in @paths might be { /linux|bsd/ =>
45
+ # ["/usr/local/lib/", "/usr/lib/"] }, which means: "For operating
46
+ # systems that match the regular expression /linux|bsd/ (e.g.
47
+ # 'linux', 'freebsd', and 'openbsd'), look for libraries first in
48
+ # the directory '/usr/local/lib/', then in '/usr/lib/'."
49
+ #
50
+ # * The value of @files are Arrays of one or more strings describing
51
+ # the possible formats of library names for that operating system.
52
+ # These are templates -- they should include string "[NAME]",
53
+ # which will be replaced with the library name. For example,
54
+ # "lib[NAME].so" would become "libSDL_ttf.so" when searching for the
55
+ # "SDL_ttf" library.
56
+ #
57
+ # There are several methods to modify @paths and/or @files. See
58
+ # #append, #prepend, #replace, #remove, and #delete.
59
+ #
60
+ # Once @paths and @files are set up, use #find to look for a file with
61
+ # a matching name.
62
+ #
63
+ # NiceFFI::PathSet::DEFAULT is a pre-made PathSet with paths and file
64
+ # name templates for Linux/BSD, Mac (Darwin), and Windows. It is the
65
+ # default PathSet used by NiceFFI::Library.load_library, and you can
66
+ # also use it as a base for custom PathSets.
46
67
  #
47
68
  class NiceFFI::PathSet
48
69
 
49
70
 
50
- def initialize( rules={} )
51
- @rules = rules
71
+ def initialize( paths={}, files={} )
72
+ @paths = {}
73
+ @files = {}
74
+ append!( :paths, paths )
75
+ append!( :files, files )
52
76
  end
53
77
 
54
- attr_reader :rules
78
+ attr_reader :paths, :files
55
79
 
56
80
  def dup
57
- self.class.new( @rules.dup )
81
+ self.class.new( @paths.dup, @files.dup )
58
82
  end
59
83
 
60
84
 
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.
85
+ # call-seq:
86
+ # append( *entries )
87
+ # append( option, *entries )
88
+ #
89
+ # Create a copy of this PathSet and append the new paths and/or
90
+ # files. If the copy already has entries for a given regexp, the
91
+ # new entries will be added after the current entries.
92
+ #
93
+ # option:: You can optionally give either :paths or :files as the
94
+ # first argument to this method. If :paths, only @paths
95
+ # will be modified, @files will never be modified. If
96
+ # :files, only @files will be modified, @paths will never
97
+ # be modified.
64
98
  #
65
- # The given rules can be Hashes or existing PathSets; or
66
- # Arrays to append the given rules to every existing regex.
99
+ # entries:: One or more PathSets, Hashes, Arrays, or Strings,
100
+ # or any assortment of these types.
67
101
  #
102
+ # * If given a PathSet, its @paths and @files are appended to the
103
+ # copy's @paths and @files (respectively). If option is :paths,
104
+ # only @paths is modified. If option is :files, only @files is
105
+ # modified.
68
106
  #
69
- # Example:
107
+ # * If given a Hash, it is appended to the copy's @paths, but
108
+ # @files is not affected. If option is :files, @files is modified
109
+ # instead of @paths.
110
+ #
111
+ # * If given an Array (which should contain only Strings), the array
112
+ # contents are appended to the copy's @paths. If option is
113
+ # :files, @files is modified instead of @paths.
114
+ #
115
+ # * If given a String, the string is appended to the copy's
116
+ # @paths. If option is :files, @files is modified instead of
117
+ # @paths.
118
+ #
119
+ # * If given multiple objects, they are handled in order according to
120
+ # the above rules.
121
+ #
122
+ # See also #append! for a version of this method which modifies self
123
+ # instead of making a copy.
124
+ #
125
+ #--
126
+ # Example (out of date):
70
127
  #
71
128
  # ps = PathSet.new( /a/ => ["liba"],
72
129
  # /b/ => ["libb"] )
@@ -74,38 +131,70 @@ class NiceFFI::PathSet
74
131
  # ps.append!( /a/ => ["newliba"],
75
132
  # /c/ => ["libc"] )
76
133
  #
77
- # ps.rules
134
+ # ps.paths
78
135
  # # => { /a/ => ["liba",
79
136
  # # "newliba"], # added in back
80
137
  # # /b/ => ["libb"], # not affected
81
138
  # # /c/ => ["libc"] } # added
82
- #
83
- def append!( *ruleses )
84
- ruleses.each do |rules|
85
- _modify( rules ) { |a,b| a + b }
86
- end
87
- self
139
+ #++
140
+ def append( *entries )
141
+ self.dup.append!( *entries )
88
142
  end
89
143
 
90
- # Like #append!, but returns a copy instead of modifying the original.
91
- def append( *ruleses )
92
- self.dup.append!( *ruleses )
144
+ # call-seq:
145
+ # append!( *entries )
146
+ # append!( option, *entries )
147
+ #
148
+ # Like #append, but modifies self instead of making a copy.
149
+ def append!( *entries )
150
+ _modify( *entries ) { |a,b| a + b }
93
151
  end
94
152
 
95
153
  alias :+ :append
96
- alias :<< :append
97
154
 
98
155
 
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.
156
+ # call-seq:
157
+ # prepend( *entries )
158
+ # prepend( option, *entries )
159
+ #
160
+ # Creates a copy of this PathSet and prepends the new paths and/or
161
+ # files. If the copy already has entries for a given regexp, the
162
+ # new entries will be added before the current entries.
163
+ #
164
+ # option:: You can optionally give either :paths or :files as the
165
+ # first argument to this method. If :paths, only @paths
166
+ # will be modified, @files will never be modified. If
167
+ # :files, only @files will be modified, @paths will never
168
+ # be modified.
103
169
  #
104
- # The given rules can be Hashes or existing PathSets; or
105
- # Arrays to prepend the given rules to every existing regex.
170
+ # entries:: One or more PathSets, Hashes, Arrays, or Strings,
171
+ # or any assortment of these types.
106
172
  #
173
+ # * If given a PathSet, its @paths and @files are prepended to this
174
+ # PathSet's @paths and @files (respectively). If option is :paths,
175
+ # only @paths is modified. If option is :files, only @files is
176
+ # modified.
177
+ #
178
+ # * If given a Hash, it is prepended to the copy's @paths, but
179
+ # @files is not affected. If option is :files, @files is modified
180
+ # instead of @paths.
107
181
  #
108
- # Example:
182
+ # * If given an Array (which should contain only Strings), the array
183
+ # contents are prepended to the copy's @paths. If option is
184
+ # :files, @files is modified instead of @paths.
185
+ #
186
+ # * If given a String, the string is prepended to the copy's
187
+ # @paths. If option is :files, @files is modified instead of
188
+ # @paths.
189
+ #
190
+ # * If given multiple objects, they are handled in order according to
191
+ # the above rules.
192
+ #
193
+ # See also #prepend! for a version of this method which modifies self
194
+ # instead of making a copy.
195
+ #
196
+ #--
197
+ # Example (out of date):
109
198
  #
110
199
  # ps = PathSet.new( /a/ => ["liba"],
111
200
  # /b/ => ["libb"] )
@@ -113,38 +202,74 @@ class NiceFFI::PathSet
113
202
  # ps.prepend!( /a/ => ["newliba"],
114
203
  # /c/ => ["libc"] )
115
204
  #
116
- # ps.rules
205
+ # ps.paths
117
206
  # # => { /a/ => ["newliba", # added in front
118
207
  # # "liba"],
119
208
  # # /b/ => ["libb"], # not affected
120
209
  # # /c/ => ["libc"] } # added
121
- #
122
- def prepend!( *ruleses )
123
- ruleses.each do |rules|
124
- _modify( rules ) { |a,b| b + a }
125
- end
126
- self
210
+ #++
211
+ def prepend( *entries )
212
+ self.dup.prepend!( *entries )
127
213
  end
128
214
 
129
- # Like #prepend!, but returns a copy instead of modifying the original.
130
- def prepend( *ruleses )
131
- self.dup.prepend!( *ruleses )
215
+ # call-seq:
216
+ # prepend!( *entries )
217
+ # prepend!( option, *entries )
218
+ #
219
+ # Like #prepend, but modifies self instead of making a copy.
220
+ def prepend!( *entries )
221
+ _modify( *entries ) { |a,b| b + a }
132
222
  end
133
223
 
134
- alias :>> :prepend
135
224
 
136
225
 
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.
226
+ # call-seq:
227
+ # replace( *entries )
228
+ # replace( option, *entries )
229
+ #
230
+ # Creates a copy of this PathSet and overrides existing entries with
231
+ # the new entries. If the copy already has entries for a regexp
232
+ # in the new entries, the old entries will be discarded and the new
233
+ # entries used instead.
234
+ #
235
+ # option:: You can optionally give either :paths or :files as the
236
+ # first argument to this method. If :paths, only @paths
237
+ # will be modified, @files will never be modified. If
238
+ # :files, only @files will be modified, @paths will never
239
+ # be modified.
240
+ #
241
+ # entries:: One or more PathSets, Hashes, Arrays, or Strings,
242
+ # or any assortment of these types.
243
+ #
244
+ # * If given a PathSet, the copy's @paths and @files with the
245
+ # other PathSet's @paths and @files (respectively). Old entries in
246
+ # the copy are kept if their regexp doesn't appear in the given
247
+ # PathSet. If option is :paths, only @paths is modified. If option
248
+ # is :files, only @files is modified.
249
+ #
250
+ # * If given a Hash, entries in the copy's @paths are replaced
251
+ # with the new entries, but @files is not affected. Old entries in
252
+ # the copy are kept if their regexp doesn't appear in the given
253
+ # PathSet. If option is :files, @files is modified instead of
254
+ # @paths.
255
+ #
256
+ # * If given an Array (which should contain only Strings), entries
257
+ # for every regexp in the copy's @paths are replaced with the
258
+ # array contents. If option is :files, @files is modified instead
259
+ # of @paths.
260
+ #
261
+ # * If given a String, all entries for every regexp in the copy's
262
+ # @paths are replaced with the string. If option is :files, @files
263
+ # is modified instead of @paths.
142
264
  #
143
- # The given rules can be Hashes or existing PathSets; or
144
- # Arrays to replace the given rules for every existing regex.
265
+ # * If given multiple objects, they are handled in order according to
266
+ # the above rules.
145
267
  #
268
+ # See also #replace! for a version of this method which modifies self
269
+ # instead of making a copy.
146
270
  #
147
- # Example:
271
+ #--
272
+ # Example (out of date):
148
273
  #
149
274
  # ps = PathSet.new( /a/ => ["liba"],
150
275
  # /b/ => ["libb"] )
@@ -152,36 +277,70 @@ class NiceFFI::PathSet
152
277
  # ps.replace!( /a/ => ["newliba"],
153
278
  # /c/ => ["libc"] )
154
279
  #
155
- # ps.rules
280
+ # ps.paths
156
281
  # # => { /a/ => ["newliba"], # replaced
157
282
  # # /b/ => ["libb"], # not affected
158
283
  # # /c/ => ["libc"] } # added
159
- #
160
- def replace!( *ruleses )
161
- ruleses.each do |rules|
162
- _modify( rules ) { |a,b| b }
163
- end
164
- self
284
+ #++
285
+ def replace( *entries )
286
+ self.dup.replace!( *entries )
165
287
  end
166
288
 
167
- # Like #replace!, but returns a copy instead of modifying the original.
168
- def replace( *ruleses )
169
- self.dup.replace!( *ruleses )
289
+ # call-seq:
290
+ # replace!( *entries )
291
+ # replace!( option, *entries )
292
+ #
293
+ # Like #replace, but modifies self instead of making a copy.
294
+ def replace!( *entries )
295
+ _modify( *entries ) { |a,b| b }
170
296
  end
171
297
 
172
298
 
173
299
 
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.
300
+ # call-seq:
301
+ # remove( *entries )
302
+ # remove( option, *entries )
303
+ #
304
+ # Creates a copy of this PathSet and removes the given entries from
305
+ # the copy, if it has them. This only removes the entries that are
306
+ # given, other entries for the same regexp are kept. Regexps with no
307
+ # entries left afterwards are removed from the PathSet.
308
+ #
309
+ # option:: You can optionally give either :paths or :files as the
310
+ # first argument to this method. If :paths, only @paths
311
+ # will be modified, @files will never be modified. If
312
+ # :files, only @files will be modified, @paths will never
313
+ # be modified.
177
314
  #
178
- # The given rules can be Hashes or existing PathSets; or
179
- # Arrays to remove the given rules from every existing regex.
315
+ # entries:: One or more PathSets, Hashes, Arrays, or Strings,
316
+ # or any assortment of these types.
180
317
  #
181
- # Regexes with no rules left are pruned.
318
+ # * If given a PathSet, entries from its @paths and @files are
319
+ # removed from the copy's @paths and @files (respectively). If
320
+ # option is :paths, only @paths is modified. If option is :files,
321
+ # only @files is modified.
182
322
  #
323
+ # * If given a Hash, the given entries are removed from this
324
+ # PathSet's @paths, but @files is not affected. If option is
325
+ # :files, @files is modified instead of @paths.
183
326
  #
184
- # Example:
327
+ # * If given an Array (which should contain only Strings), the array
328
+ # contents are removed from the entries for every regexp in this
329
+ # PathSet's @paths. If option is :files, @files is modified
330
+ # instead of @paths.
331
+ #
332
+ # * If given a String, the string is removed from the entries for
333
+ # every regexp in the copy's @paths. If option is :files,
334
+ # @files is modified instead of @paths.
335
+ #
336
+ # * If given multiple objects, they are handled in order according to
337
+ # the above rules.
338
+ #
339
+ # See also #remove! for a version of this method which modifies self
340
+ # instead of making a copy.
341
+ #
342
+ #--
343
+ # Example (out of date):
185
344
  #
186
345
  # ps = PathSet.new( /a/ => ["liba", "badliba"],
187
346
  # /b/ => ["libb"] )
@@ -190,55 +349,88 @@ class NiceFFI::PathSet
190
349
  # /b/ => ["libb"] )
191
350
  # /c/ => ["libc"] )
192
351
  #
193
- # ps.rules
352
+ # ps.paths
194
353
  # # => { /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
354
+ # # # /b/ paths were all removed.
355
+ # # # /c/ not affected because it had no old paths anyway.
356
+ #++
357
+ def remove( *entries )
358
+ self.dup.remove!( *entries )
203
359
  end
204
360
 
205
- # Like #remove!, but returns a copy instead of modifying the original.
206
- def remove( *ruleses )
207
- self.dup.remove!( *ruleses )
361
+ # call-seq:
362
+ # remove!( *entries )
363
+ # remove!( option, *entries )
364
+ #
365
+ # Like #remove, but modifies self instead of making a copy.
366
+ def remove!( *entries )
367
+ _modify( *entries ) { |a,b| a - b }
208
368
  end
209
369
 
210
370
  alias :- :remove
211
371
 
212
372
 
213
373
 
214
- # Remove all rules for the given regex(es). Has no effect on
215
- # regexes that are not given.
374
+ # call-seq:
375
+ # delete( *regexps )
376
+ # delete( option, *regexps )
377
+ #
378
+ # Creates a copy of this PathSet and delete all entries from the
379
+ # copy for the given regexp(s) from @paths and/or @files. Has no
380
+ # effect on entries for regexps that are not given.
381
+ #
382
+ # option:: You can optionally give either :paths or :files as the
383
+ # first argument to this method. If :paths, only @paths
384
+ # will be modified, @files will never be modified. If
385
+ # :files, only @files will be modified, @paths will never
386
+ # be modified.
387
+ #
388
+ # regexps:: One or more Regexps to remove entries for.
216
389
  #
390
+ # See also #delete! for a version of this method which modifies self
391
+ # instead of making a copy.
217
392
  #
218
- # Example:
393
+ #--
394
+ # Example (out of date):
219
395
  #
220
396
  # ps = PathSet.new( /a/ => ["liba"],
221
397
  # /b/ => ["libb"] )
222
398
  #
223
399
  # ps.delete!( /b/, /c/ )
224
400
  #
225
- # ps.rules
401
+ # ps.paths
226
402
  # # => { /a/ => ["liba"] } # not affected
227
- # # # /b/ and all rules removed.
228
- # # # /c/ not affected because it had no rules anyway.
403
+ # # # /b/ and all paths removed.
404
+ # # # /c/ not affected because it had no paths anyway.
405
+ #++
406
+ def delete( *regexps )
407
+ self.dup.delete!( *regexps )
408
+ end
409
+
410
+ # call-seq:
411
+ # delete!( *regexps )
412
+ # delete!( option, *regexps )
229
413
  #
230
- def delete!( *regexs )
231
- @rules.delete_if { |regex, paths| regexs.include? regex }
414
+ # Like #delete, but modifies self instead of making a copy.
415
+ def delete!( *regexps )
416
+ case regexps[0]
417
+ when :paths
418
+ @paths.delete_if { |regexp, paths| regexps.include? regexp }
419
+ when :files
420
+ @files.delete_if { |regexp, files| regexps.include? regexp }
421
+ when Symbol
422
+ raise( "Invalid symbol option '#{first.inspect}'. " +
423
+ "Expected :paths or :files." )
424
+ else
425
+ @paths.delete_if { |regexp, paths| regexps.include? regexp }
426
+ @files.delete_if { |regexp, files| regexps.include? regexp }
427
+ end
232
428
  self
233
429
  end
234
430
 
235
- # Like #delete!, but returns a copy instead of modifying the original.
236
- def delete( *regexs )
237
- self.dup.delete!( *regexs )
238
- end
239
431
 
240
432
 
241
- # Try to find a file based on the rules in this PathSet.
433
+ # Try to find a file based on the paths in this PathSet.
242
434
  #
243
435
  # *names:: Strings to try substituting for [NAME] in the paths.
244
436
  #
@@ -248,86 +440,154 @@ class NiceFFI::PathSet
248
440
  # Raises LoadError if the current operating system did not match
249
441
  # any of the regular expressions in the PathSet.
250
442
  #
251
- # Examples:
443
+ #--
444
+ # Examples (out of date):
252
445
  #
253
- # ps = PathSet.new( /linux/ => ["/usr/lib/lib[NAME].so"],
254
- # /win32/ => ["C:\\windows\\system32\\[NAME].dll"] )
446
+ # ps = PathSet.new( /linux/ => ["/usr/lib/lib[NAME].so"],
447
+ # /windows/ => ["C:\\windows\\system32\\[NAME].dll"] )
255
448
  #
256
449
  # ps.find( "SDL" )
257
450
  # ps.find( "foo", "foo_alt_name" )
258
- #
451
+ #++
259
452
  def find( *names )
260
-
261
453
  os = FFI::Platform::OS
262
454
 
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
455
+ # Fetch the paths and files for the matching OSes.
456
+ paths = @paths.collect{ |regexp,ps| regexp =~ os ? ps : [] }.flatten
457
+ files = @files.collect{ |regexp,fs| regexp =~ os ? fs : [] }.flatten
268
458
 
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?
459
+ # Drat, they are using an OS with no matches.
460
+ if paths.empty? and files.empty?
274
461
  raise( LoadError, "Your OS (#{os}) is not supported yet.\n" +
275
462
  "Please report this and help us support more platforms." )
276
463
  end
277
464
 
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
465
+ results = paths.collect do |path|
466
+ files.collect do |file|
467
+ names.collect do |name|
468
+ # Concat path and file, fill in for [NAME], and expand.
469
+ File.expand_path( (path+file).gsub("[NAME]", name) )
470
+ end
471
+ end
294
472
  end
295
473
 
296
- return found
474
+ return results.flatten.select{ |r| File.exist? r }
297
475
  end
298
476
 
299
477
 
300
478
  private
301
479
 
302
480
 
303
- def _modify( rules, &block ) # :nodoc:
481
+ def _modify( *entries, &block )
482
+ # could be :paths or :files, or perhaps an entry
483
+ part = entries[0]
484
+
485
+ case part
486
+ when :paths, :files, :default
487
+ entries = entries[1..-1]
488
+ when Symbol # other symbols are invalid
489
+ raise( "Invalid symbol option '#{first.inspect}'. " +
490
+ "Expected :paths or :files." )
491
+ else
492
+ part = :default
493
+ end
494
+
495
+ entries.each do |entry|
496
+ if entry.kind_of? self.class
497
+ if part == :default
498
+ _modify_set( :paths, entry.paths, &block )
499
+ _modify_set( :files, entry.files, &block )
500
+ else
501
+ _modify_set( part, entry.send(part), &block )
502
+ end
503
+ else
504
+ if part == :default
505
+ _modify_set( :paths, entry, &block )
506
+ else
507
+ _modify_set( part, entry, &block )
508
+ end
509
+ end
510
+ end
511
+
512
+ return self
513
+ end
514
+
515
+
516
+ def _modify_set( ours, other, &block ) # :nodoc:
304
517
  raise "No block given!" unless block_given?
305
518
 
306
- case rules
307
- when self.class
308
- _modify( rules.rules, &block )
519
+ ours = case ours
520
+ when :paths; @paths
521
+ when :files; @files
522
+ else
523
+ raise( "Invalid symbol option '#{ours.inspect}'. " +
524
+ "Expected :paths or :files." )
525
+ end
526
+
527
+ case other
309
528
  when Hash
310
- rules.each do |regex, paths|
311
- _apply_modifier( regex, (@rules[regex] or []), paths, &block )
529
+ # Apply each of the regexps in `other` to the same regexp in `ours`
530
+ other.each do |regexp, paths|
531
+ _apply_modifier( ours, regexp, (ours[regexp] or []), paths, &block )
312
532
  end
313
533
  when Array
314
- @rules.each { |regex, paths|
315
- _apply_modifier( regex, paths, rules, &block )
534
+ # Apply `other` to each of the regexps in `ours`
535
+ ours.each { |regexp, paths|
536
+ _apply_modifier( ours, regexp, paths, other, &block )
537
+ }
538
+ when String
539
+ # Apply an Array holding `other` to each of the regexps in `ours`
540
+ ours.each { |regexp, paths|
541
+ _apply_modifier( ours, regexp, paths, [other], &block )
316
542
  }
317
543
  end
318
544
  end
319
545
 
320
546
 
321
- def _apply_modifier( regex, a, b, &block ) # :nodoc:
547
+ def _apply_modifier( ours, regexp, a, b, &block ) # :nodoc:
322
548
  raise "No block given!" unless block_given?
323
549
 
324
550
  result = yield( a, b )
325
551
 
326
552
  if result == []
327
- @rules.delete( regex )
553
+ ours.delete( regexp )
328
554
  else
329
- @rules[regex] = result
555
+ ours[regexp] = result
330
556
  end
331
557
  end
332
558
 
333
559
  end
560
+
561
+
562
+
563
+ #--
564
+ # NOTE: If you update these defaults, update doc/usage.rdoc too.
565
+ #++
566
+
567
+ paths = {
568
+ /linux|bsd/ => [ "/usr/local/lib/",
569
+ "/usr/lib/" ],
570
+
571
+ /darwin/ => [ "/usr/local/lib/",
572
+ "/sw/lib/",
573
+ "/opt/local/lib/",
574
+ "~/Library/Frameworks/",
575
+ "/Library/Frameworks/" ],
576
+
577
+ /windows/ => [ "C:\\windows\\system32\\",
578
+ "C:\\windows\\system\\" ]
579
+ }
580
+
581
+ files = {
582
+ /linux|bsd/ => [ "lib[NAME].so" ],
583
+
584
+ /darwin/ => [ "lib[NAME].dylib",
585
+ "[NAME].framework/[NAME]" ],
586
+
587
+ /windows/ => [ "[NAME].dll" ]
588
+ }
589
+
590
+ # The default paths to look for libraries. See PathSet
591
+ # and NiceFFI::Library.load_library.
592
+ #
593
+ NiceFFI::PathSet::DEFAULT = NiceFFI::PathSet.new( paths, files )