dakrone-fastri 0.3.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,100 @@
1
+ # Copyright (C) 2006 Mauricio Fernandez <mfp@acm.org>
2
+ #
3
+
4
+ require 'fastri/version'
5
+
6
+ module FastRI
7
+
8
+ class FullTextIndexer
9
+ WORD_RE = /[A-Za-z0-9_]+/
10
+ NONWORD_RE = /[^A-Za-z0-9_]+/
11
+ MAGIC = "FastRI full-text index #{FASTRI_FT_INDEX_FORMAT}\0"
12
+
13
+ def initialize(max_querysize)
14
+ @documents = []
15
+ @doc_hash = {}
16
+ @max_wordsize = max_querysize
17
+ end
18
+
19
+ def add_document(name, data, metadata = {})
20
+ @doc_hash[name] = [data, metadata.merge(:size => data.size)]
21
+ @documents << name
22
+ end
23
+
24
+ def data(name)
25
+ @doc_hash[name][0]
26
+ end
27
+
28
+ def documents
29
+ @documents = @documents.uniq
30
+ end
31
+
32
+ def preprocess(str)
33
+ str.gsub(/\0/,"")
34
+ end
35
+
36
+ require 'strscan'
37
+ def find_suffixes(text, offset)
38
+ find_suffixes_simple(text, WORD_RE, NONWORD_RE, offset)
39
+ end
40
+
41
+ def find_suffixes_simple(string, word_re, nonword_re, offset)
42
+ suffixes = []
43
+ sc = StringScanner.new(string)
44
+ until sc.eos?
45
+ sc.skip(nonword_re)
46
+ len = string.size
47
+ loop do
48
+ break if sc.pos == len
49
+ suffixes << offset + sc.pos
50
+ skipped_word = sc.skip(word_re)
51
+ break unless skipped_word
52
+ loop do
53
+ skipped_nonword = sc.skip(nonword_re)
54
+ break unless skipped_nonword
55
+ end
56
+ end
57
+ end
58
+ suffixes
59
+ end
60
+
61
+ require 'enumerator'
62
+ def build_index(full_text_IO, suffix_array_IO)
63
+ fulltext = ""
64
+ io = StringIO.new(fulltext)
65
+ io.write MAGIC
66
+ full_text_IO.write MAGIC
67
+ documents.each do |doc|
68
+ data, metadata = @doc_hash[doc]
69
+ io.write(data)
70
+ full_text_IO.write(data)
71
+ meta_txt = Marshal.dump(metadata)
72
+ footer = "\0....#{doc}\0#{meta_txt}\0"
73
+ footer[1,4] = [footer.size - 5].pack("V")
74
+ io.write(footer)
75
+ full_text_IO.write(footer)
76
+ end
77
+
78
+ scanner = StringScanner.new(fulltext)
79
+ scanner.scan(Regexp.new(Regexp.escape(MAGIC)))
80
+
81
+ count = 0
82
+ suffixes = []
83
+ until scanner.eos?
84
+ count += 1
85
+ start = scanner.pos
86
+ text = scanner.scan_until(/\0/)
87
+ suffixes.concat find_suffixes(text[0..-2], start)
88
+ len = scanner.scan(/..../).unpack("V")[0]
89
+ #puts "LEN: #{len} #{scanner.pos} #{scanner.string.size}"
90
+ #puts "#{scanner.string[scanner.pos,20].inspect}"
91
+ scanner.pos += len
92
+ #scanner.terminate if !text
93
+ end
94
+ sorted = suffixes.sort_by{|x| fulltext[x, @max_wordsize]}
95
+ sorted.each_slice(10000){|x| suffix_array_IO.write x.pack("V*")}
96
+ nil
97
+ end
98
+ end # class FullTextIndexer
99
+
100
+ end # module FastRI
@@ -0,0 +1,71 @@
1
+ # Copyright (C) 2006 Mauricio Fernandez <mfp@acm.org>
2
+ #
3
+
4
+ module FastRI
5
+
6
+ # Alternative NameDescriptor implementation which doesn't require class/module
7
+ # names to be properly capitalized.
8
+ #
9
+ # Rules:
10
+ # * <tt>#foo</tt>: instance method +foo+
11
+ # * <tt>.foo</tt>: method +foo+ (either singleton or instance)
12
+ # * <tt>::foo</tt>: singleton method +foo+
13
+ # * <tt>foo::bar#bar<tt>: instance method +bar+ under <tt>foo::bar</tt>
14
+ # * <tt>foo::bar.bar<tt>: either singleton or instance method +bar+ under
15
+ # <tt>foo::bar</tt>
16
+ # * <tt>foo::bar::Baz<tt>: module/class foo:bar::Baz
17
+ # * <tt>foo::bar::baz</tt>: singleton method +baz+ from <tt>foo::bar</tt>
18
+ # * other: raise RiError
19
+ class NameDescriptor
20
+ attr_reader :class_names
21
+ attr_reader :method_name
22
+
23
+ # true and false have the obvious meaning. nil means we don't care
24
+ attr_reader :is_class_method
25
+
26
+ def initialize(arg)
27
+ @class_names = []
28
+ @method_name = nil
29
+ @is_class_method = nil
30
+
31
+ case arg
32
+ when /((?:[^:]*::)*[^:]*)(#|::|\.)(.*)$/
33
+ ns, sep, meth_or_class = $~.captures
34
+ # optimization attempt: try to guess the real capitalization,
35
+ # so we get a direct hit
36
+ @class_names = ns.split(/::/).map{|x| x[0,1] = x[0,1].upcase; x }
37
+ if %w[# .].include? sep
38
+ @method_name = meth_or_class
39
+ @is_class_method =
40
+ case sep
41
+ when "#"; false
42
+ when "."; nil
43
+ end
44
+ else
45
+ if ("A".."Z").include? meth_or_class[0,1] # 1.9 compatibility
46
+ @class_names << meth_or_class
47
+ else
48
+ @method_name = meth_or_class
49
+ @is_class_method = true
50
+ end
51
+ end
52
+ when /^[^#:.]+/
53
+ if ("A".."Z").include? arg[0,1]
54
+ @class_names = [arg]
55
+ else
56
+ @method_name = arg.dup
57
+ @is_class_method = nil
58
+ end
59
+ else
60
+ raise RiError, "Cannot create NameDescriptor from #{arg}"
61
+ end
62
+ end
63
+
64
+ # Return the full class name (with '::' between the components)
65
+ # or "" if there's no class name
66
+ def full_class_name
67
+ @class_names.join("::")
68
+ end
69
+ end
70
+
71
+ end #module FastRI
@@ -0,0 +1,601 @@
1
+ # Copyright (C) 2006 Mauricio Fernandez <mfp@acm.org>
2
+ #
3
+
4
+ require 'rdoc/ri/cache'
5
+ require 'rdoc/ri/reader'
6
+ require 'rdoc/ri/descriptions'
7
+ require 'fastri/version'
8
+
9
+
10
+ # This is taken straight from 1.8.5's rdoc/ri/ri_descriptions.rb.
11
+ # Older releases have a buggy #merge_in that crashes when old.comment is nil.
12
+ if RUBY_RELEASE_DATE < "2006-06-15"
13
+ module ::RI # :nodoc:
14
+ class ModuleDescription # :nodoc:
15
+ remove_method :merge_in
16
+ # merge in another class desscription into this one
17
+ def merge_in(old)
18
+ merge(@class_methods, old.class_methods)
19
+ merge(@instance_methods, old.instance_methods)
20
+ merge(@attributes, old.attributes)
21
+ merge(@constants, old.constants)
22
+ merge(@includes, old.includes)
23
+ if @comment.nil? || @comment.empty?
24
+ @comment = old.comment
25
+ else
26
+ unless old.comment.nil? or old.comment.empty? then
27
+ @comment << SM::Flow::RULE.new
28
+ @comment.concat old.comment
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+
37
+ module FastRI
38
+
39
+ # This class provides the same functionality as RiReader, with some
40
+ # improvements:
41
+ # * lower memory consumption
42
+ # * ability to handle information from different sources separately.
43
+ #
44
+ # Some operations can be restricted to a given "scope", that is, a
45
+ # "RI DB directory". This allows you to e.g. look for all the instance methods
46
+ # in String defined by a package.
47
+ #
48
+ # Such operations take a +scope+ argument, which is either an integer which
49
+ # indexes the source in #paths, or a name identifying the source (either
50
+ # "system" or a package name). If <tt>scope == nil</tt>, the information from
51
+ # all sources is merged.
52
+ class RiIndex
53
+ # Redefine RI::MethodEntry#full_name to use the following notation:
54
+ # Namespace::Foo.singleton_method (instead of ::). RiIndex depends on this to
55
+ # tell singleton methods apart.
56
+ class RDoc::RI::MethodEntry # :nodoc:
57
+ remove_method :full_name
58
+ def full_name
59
+ res = @in_class.full_name
60
+ unless res.empty?
61
+ if @is_class_method
62
+ res << "."
63
+ else
64
+ res << "#"
65
+ end
66
+ end
67
+ res << @name
68
+ end
69
+ end
70
+
71
+ class MethodEntry
72
+ attr_reader :full_name, :name, :index, :source_index
73
+
74
+ def initialize(ri_index, fullname, index, source_index)
75
+ # index is the index in ri_index' array
76
+ # source_index either nil (all scopes) or the integer referencing the
77
+ # path (-> we'll do @ri_index.paths[@source_index])
78
+ @ri_index = ri_index
79
+ @full_name = fullname
80
+ @name = fullname[/[.#](.*)$/, 1]
81
+ @index = index
82
+ @source_index = source_index
83
+ end
84
+
85
+ # Returns the "fully resolved" file name of the yaml containing our
86
+ # description.
87
+ def path_name
88
+ prefix = @full_name.split(/::|[#.]/)[0..-2]
89
+ case @source_index
90
+ when nil
91
+ ## we'd like to do
92
+ #@ri_index.source_paths_for(self).map do |path|
93
+ # File.join(File.join(path, *prefix), RI::RiWriter.internal_to_external(@name))
94
+ #end
95
+ # but RI doesn't support merging at the method-level, so
96
+ path = @ri_index.source_paths_for(self).first
97
+ File.join(File.join(path, *prefix),
98
+ ::RDoc::RI::Writer.internal_to_external(@name) +
99
+ (singleton_method? ? "-c" : "-i" ) + ".yaml")
100
+ else
101
+ path = @ri_index.paths[@source_index]
102
+ File.join(File.join(path, *prefix),
103
+ ::RDoc::RI::Writer.internal_to_external(@name) +
104
+ (singleton_method? ? "-c" : "-i" ) + ".yaml")
105
+ end
106
+ end
107
+
108
+ def singleton_method?
109
+ /\.[^:]+$/ =~ @full_name
110
+ end
111
+
112
+ def instance_method?
113
+ !singleton_method?
114
+ end
115
+
116
+ # Returns the type of this entry (<tt>:method</tt>).
117
+ def type
118
+ :method
119
+ end
120
+ end
121
+
122
+ class ClassEntry
123
+ attr_reader :full_name, :name, :index, :source_index
124
+
125
+ def initialize(ri_index, fullname, index, source_index)
126
+ @ri_index = ri_index
127
+ @full_name = fullname
128
+ @name = fullname.split(/::/).last
129
+ @index = index
130
+ @source_index = source_index
131
+ end
132
+
133
+ # Returns an array of directory names holding the cdesc-Classname.yaml
134
+ # files.
135
+ def path_names
136
+ prefix = @full_name.split(/::/)
137
+ case @source_index
138
+ when nil
139
+ @ri_index.source_paths_for(self).map{|path| File.join(path, *prefix) }
140
+ else
141
+ [File.join(@ri_index.paths[@source_index], *prefix)]
142
+ end
143
+ end
144
+
145
+ # Returns nested classes and modules matching name (non-recursive).
146
+ def contained_modules_matching(name)
147
+ @ri_index.namespaces_under(self, false, @source_index).select do |x|
148
+ x.name[name]
149
+ end
150
+ end
151
+
152
+ # Returns all nested classes and modules (non-recursive).
153
+ def classes_and_modules
154
+ @ri_index.namespaces_under(self, false, @source_index)
155
+ end
156
+
157
+ # Returns nested class or module named exactly +name+ (non-recursive).
158
+ def contained_class_named(name)
159
+ contained_modules_matching(name).find{|x| x.name == name}
160
+ end
161
+
162
+ # Returns instance or singleton methods matching name (non-recursive).
163
+ def methods_matching(name, is_class_method)
164
+ @ri_index.methods_under(self, false, @source_index).select do |meth|
165
+ meth.name[name] &&
166
+ (is_class_method ? meth.singleton_method? : meth.instance_method?)
167
+ end
168
+ end
169
+
170
+ # Returns instance or singleton methods matching name (recursive).
171
+ def recursively_find_methods_matching(name, is_class_method)
172
+ @ri_index.methods_under(self, true, @source_index).select do |meth|
173
+ meth.name[name] &&
174
+ (is_class_method ? meth.singleton_method? : meth.instance_method?)
175
+ end
176
+ end
177
+
178
+ # Returns all methods, both instance and singleton (non-recursive).
179
+ def all_method_names
180
+ @ri_index.methods_under(self, false, @source_index).map{|meth| meth.full_name}
181
+ end
182
+
183
+ # Returns the type of this entry (<tt>:namespace</tt>).
184
+ def type
185
+ :namespace
186
+ end
187
+ end
188
+
189
+ class TopLevelEntry < ClassEntry
190
+ def methods_matching(name, is_class_method)
191
+ recursively_find_methods_matching(name, is_class_method)
192
+ end
193
+
194
+ def module_named(name)
195
+
196
+ end
197
+ end
198
+
199
+ attr_reader :paths
200
+
201
+ class << self; private :new end
202
+
203
+ def self.new_from_paths(paths = nil)
204
+ obj = new
205
+ obj.rebuild_index(paths)
206
+ obj
207
+ end
208
+
209
+ def self.new_from_IO(anIO)
210
+ obj = new
211
+ obj.load(anIO)
212
+ obj
213
+ end
214
+
215
+ def rebuild_index(paths = nil)
216
+ @paths = paths || RI::Paths::PATH
217
+ @gem_names = paths.map do |p|
218
+ fullp = File.expand_path(p)
219
+ gemname = nil
220
+ begin
221
+ require 'rubygems'
222
+ Gem.path.each do |gempath|
223
+ re = %r!^#{Regexp.escape(File.expand_path(gempath))}/doc/!
224
+ if re =~ fullp
225
+ gemname = fullp.gsub(re,"")[%r{^[^/]+}]
226
+ break
227
+ end
228
+ end
229
+ rescue LoadError
230
+ # no RubyGems, no gems installed, skip it
231
+ end
232
+ gemname ? gemname : "system"
233
+ end
234
+ methods = Hash.new{|h,k| h[k] = []}
235
+ namespaces = methods.clone
236
+ @paths.each_with_index do |path, source_index|
237
+ ri_reader = ::RDoc::RI::Reader.new(::RDoc::RI::Cache.new(path.split("\n")))
238
+ obtain_classes(ri_reader.top_level_namespace.first).each{|name| namespaces[name] << source_index }
239
+ obtain_methods(ri_reader.top_level_namespace.first).each{|name| methods[name] << source_index }
240
+ end
241
+ @method_array = methods.sort_by{|h,k| h}.map do |name, sources|
242
+ "#{name} #{sources.map{|x| x.to_s}.join(' ')}"
243
+ end
244
+ @namespace_array = namespaces.sort_by{|h,k| h}.map do |name, sources|
245
+ "#{name} #{sources.map{|x| x.to_s}.join(' ')}"
246
+ end
247
+
248
+ =begin
249
+ puts "@method_array: #{@method_array.size}"
250
+ puts "@namespace_array: #{@namespace_array.size}"
251
+ puts @method_array.inject(0){|s,x| s + x.size}
252
+ puts @namespace_array.inject(0){|s,x| s + x.size}
253
+ =end
254
+ end
255
+
256
+ MAGIC = "FastRI index #{FASTRI_INDEX_FORMAT}"
257
+ # Load the index from the given IO.
258
+ # It must contain a textual representation generated by #dump.
259
+ def load(anIO)
260
+ header = anIO.gets
261
+ raise "Invalid format." unless header.chomp == MAGIC
262
+ anIO.gets # discard "Sources:"
263
+ paths = []
264
+ gem_names = []
265
+ until (line = anIO.gets).index("=" * 80) == 0
266
+ gemname, path = line.strip.split(/\s+/)
267
+ paths << path
268
+ gem_names << gemname
269
+ end
270
+ anIO.gets # discard "Namespaces:"
271
+ namespace_array = []
272
+ until (line = anIO.gets).index("=" * 80) == 0
273
+ namespace_array << line
274
+ end
275
+ anIO.gets # discard "Methods:"
276
+ method_array = []
277
+ until (line = anIO.gets).index("=" * 80) == 0
278
+ method_array << line
279
+ end
280
+ @paths = paths
281
+ @gem_names = gem_names
282
+ @namespace_array = namespace_array
283
+ @method_array = method_array
284
+ end
285
+
286
+ # Serializes index to the given IO.
287
+ def dump(anIO)
288
+ anIO.puts MAGIC
289
+ anIO.puts "Sources:"
290
+ @paths.zip(@gem_names).each{|p,g| anIO.puts "%-30s %s" % [g, p]}
291
+ anIO.puts "=" * 80
292
+ anIO.puts "Namespaces:"
293
+ anIO.puts @namespace_array
294
+ anIO.puts "=" * 80
295
+ anIO.puts "Methods:"
296
+ anIO.puts @method_array
297
+ anIO.puts "=" * 80
298
+ end
299
+ #{{{ RiReader compatibility interface
300
+
301
+ # Returns an array with the top level namespace.
302
+ def top_level_namespace(scope = nil)
303
+ [TopLevelEntry.new(self, "", -1, scope ? scope_to_sindex(scope) : nil)]
304
+ end
305
+
306
+ # Returns an array of ClassEntry objects whose names match +target+, and
307
+ # which correspond to the namespaces contained in +namespaces+.
308
+ # +namespaces+ is an array of ClassEntry objects.
309
+ def lookup_namespace_in(target, namespaces)
310
+ result = []
311
+ namespaces.each do |ns|
312
+ result.concat(ns.contained_modules_matching(target))
313
+ end
314
+ result
315
+ end
316
+
317
+ # Returns the ClassDescription associated to the given +full_name+.
318
+ def find_class_by_name(full_name, scope = nil)
319
+ entry = get_entry(@namespace_array, full_name, ClassEntry, scope)
320
+ return nil unless entry && entry.full_name == full_name
321
+ get_class(entry)
322
+ end
323
+
324
+ # Returns the MethodDescription associated to the given +full_name+.
325
+ # Only the first definition is returned when <tt>scope = nil</tt>.
326
+ def find_method_by_name(full_name, scope = nil)
327
+ entry = get_entry(@method_array, full_name, MethodEntry, scope)
328
+ return nil unless entry && entry.full_name == full_name
329
+ get_method(entry)
330
+ end
331
+
332
+ # Returns an array of MethodEntry objects, corresponding to the methods in
333
+ # the ClassEntry objects in the +namespaces+ array.
334
+ def find_methods(name, is_class_method, namespaces)
335
+ result = []
336
+ namespaces.each do |ns|
337
+ result.concat ns.methods_matching(name, is_class_method)
338
+ end
339
+ result
340
+ end
341
+
342
+ # Return the MethodDescription for a given MethodEntry
343
+ # by deserializing the YAML.
344
+ def get_method(method_entry)
345
+ path = method_entry.path_name
346
+ File.open(path) { |f| ::RDoc::RI::Description.deserialize(f) }
347
+ end
348
+
349
+ # Return a ClassDescription for a given ClassEntry.
350
+ def get_class(class_entry)
351
+ result = nil
352
+ for path in class_entry.path_names
353
+ path = ::RDoc::RI::Writer.class_desc_path(path, class_entry)
354
+ desc = File.open(path) {|f| ::RDoc::RI::Description.deserialize(f) }
355
+ if result
356
+ result.merge_in(desc)
357
+ else
358
+ result = desc
359
+ end
360
+ end
361
+ result
362
+ end
363
+
364
+ # Return the names of all classes and modules.
365
+ def full_class_names(scope = nil)
366
+ all_entries(@namespace_array, scope)
367
+ end
368
+
369
+ # Return the names of all methods.
370
+ def full_method_names(scope = nil)
371
+ all_entries(@method_array, scope)
372
+ end
373
+
374
+ # Return a list of all classes, modules, and methods.
375
+ def all_names(scope = nil)
376
+ full_class_names(scope).concat(full_method_names(scope))
377
+ end
378
+
379
+ #{{{ New (faster) interface
380
+
381
+ # Returns the number of methods in the index.
382
+ def num_methods
383
+ @method_array.size
384
+ end
385
+
386
+ # Returns the number of namespaces in the index.
387
+ def num_namespaces
388
+ @namespace_array.size
389
+ end
390
+
391
+ # Returns the ClassEntry associated to the given +full_name+.
392
+ def get_class_entry(full_name, scope = nil)
393
+ entry = get_entry(@namespace_array, full_name, ClassEntry, scope)
394
+ return nil unless entry && entry.full_name == full_name
395
+ entry
396
+ end
397
+
398
+ # Returns the MethodEntry associated to the given +full_name+.
399
+ def get_method_entry(full_name, scope = nil)
400
+ entry = get_entry(@method_array, full_name, MethodEntry, scope)
401
+ return nil unless entry && entry.full_name == full_name
402
+ entry
403
+ end
404
+
405
+ # Returns array of ClassEntry objects under class_entry_or_name
406
+ # (either String or ClassEntry) in the hierarchy.
407
+ def namespaces_under(class_entry_or_name, recursive, scope = nil)
408
+ namespaces_under_matching(class_entry_or_name, //, recursive, scope)
409
+ end
410
+
411
+ # Returns array of ClassEntry objects under class_entry_or_name (either
412
+ # String or ClassEntry) in the hierarchy whose +full_name+ matches the given
413
+ # regexp.
414
+ def namespaces_under_matching(class_entry_or_name, regexp, recursive, scope = nil)
415
+ case class_entry_or_name
416
+ when ClassEntry
417
+ class_entry = class_entry_or_name
418
+ when ""
419
+ class_entry = top_level_namespace(scope)[0]
420
+ else
421
+ class_entry = get_entry(@namespace_array, class_entry_or_name, ClassEntry, scope)
422
+ end
423
+ return [] unless class_entry
424
+ ret = []
425
+ re1, re2 = matching_regexps_namespace(class_entry.full_name)
426
+ (class_entry.index+1...@namespace_array.size).each do |i|
427
+ entry = @namespace_array[i]
428
+ break unless re1 =~ entry
429
+ next if !recursive && re2 !~ entry
430
+ full_name = entry[/\S+/]
431
+ next unless regexp =~ full_name
432
+ if scope
433
+ sources = namespace_sources(i)
434
+ if sources.include?(sindex = scope_to_sindex(scope))
435
+ ret << ClassEntry.new(self, full_name, i, sindex)
436
+ end
437
+ else
438
+ ret << ClassEntry.new(self, full_name, i, nil)
439
+ end
440
+ end
441
+ ret
442
+ end
443
+
444
+ # Returns array of MethodEntry objects under class_entry_or_name
445
+ # (either String or ClassEntry) in the hierarchy.
446
+ def methods_under(class_entry_or_name, recursive, scope = nil)
447
+ methods_under_matching(class_entry_or_name, //, recursive, scope)
448
+ end
449
+
450
+ # Returns array of MethodEntry objects under class_entry_or_name (either
451
+ # String or ClassEntry) in the hierarchy whose +full_name+ matches the given
452
+ # regexp.
453
+ def methods_under_matching(class_entry_or_name, regexp, recursive, scope = nil)
454
+ case class_entry_or_name
455
+ when ClassEntry
456
+ full_name = class_entry_or_name.full_name
457
+ else
458
+ full_name = class_entry_or_name
459
+ end
460
+ method_entry = get_entry(@method_array, full_name, MethodEntry)
461
+ return [] unless method_entry
462
+ ret = []
463
+ re1, re2 = matching_regexps_method(full_name)
464
+ (method_entry.index...@method_array.size).each do |i|
465
+ entry = @method_array[i]
466
+ break unless re1 =~ entry
467
+ next if !recursive && re2 !~ entry
468
+ full_name = entry[/\S+/]
469
+ next unless regexp =~ full_name
470
+ if scope
471
+ sources = method_sources(i)
472
+ if sources.include?(sindex = scope_to_sindex(scope))
473
+ ret << MethodEntry.new(self, full_name, i, sindex)
474
+ end
475
+ else
476
+ ret << MethodEntry.new(self, full_name, i, nil)
477
+ end
478
+ end
479
+ ret
480
+ end
481
+
482
+ # Returns array of Strings corresponding to the base directories of all the
483
+ # sources fo the given entry_or_name.
484
+ def source_paths_for(entry_or_name)
485
+ case entry_or_name
486
+ when ClassEntry
487
+ namespace_sources(entry_or_name.index).map{|i| @paths[i] }
488
+ when MethodEntry
489
+ method_sources(entry_or_name.index).map{|i| @paths[i]}
490
+ when nil
491
+ []
492
+ else
493
+ case entry_or_name
494
+ when /[#.]\S+/
495
+ method_entry = get_entry(@method_array, entry_or_name, MethodEntry, nil)
496
+ source_paths_for(method_entry)
497
+ when ""
498
+ []
499
+ else
500
+ class_entry = get_entry(@namespace_array, entry_or_name, ClassEntry, nil)
501
+ source_paths_for(class_entry)
502
+ end
503
+ end
504
+ end
505
+
506
+ private
507
+ def namespace_sources(index)
508
+ @namespace_array[index][/\S+ (.*)/,1].split(/\s+/).map{|x| x.to_i}
509
+ end
510
+
511
+ def method_sources(index)
512
+ @method_array[index][/\S+ (.*)/,1].split(/\s+/).map{|x| x.to_i}
513
+ end
514
+
515
+ def all_entries(array, scope)
516
+ if scope
517
+ wanted_sidx = scope_to_sindex(scope)
518
+ chosen = array.select{|x| x[/ (.*$)/, 1].split(/\s+/).map{|x| x.to_i}.include? wanted_sidx }
519
+ else
520
+ chosen = array
521
+ end
522
+ chosen.map{|x| x[/(\S+)/]}
523
+ end
524
+
525
+ def matching_regexps_namespace(prefix)
526
+ if prefix.empty?
527
+ [//, /^[^:]+ /]
528
+ else
529
+ [/^#{Regexp.escape(prefix)}/, /^#{Regexp.escape(prefix)}(::|[#.])[^:]+ / ]
530
+ end
531
+ end
532
+
533
+ def matching_regexps_method(prefix)
534
+ if prefix.empty?
535
+ [//, /^[#.] /] # the second should never match
536
+ else
537
+ [/^#{Regexp.escape(prefix)}([#.]|::)/, /^#{Regexp.escape(prefix)}([#.])\S+ / ]
538
+ end
539
+ end
540
+
541
+ def scope_to_sindex(scope)
542
+ case scope
543
+ when Integer
544
+ scope
545
+ else
546
+ @gem_names.index(scope)
547
+ end
548
+ end
549
+
550
+ def get_entry(array, fullname, klass, scope = nil)
551
+ index = binary_search(array, fullname)
552
+ return nil unless index
553
+ entry = array[index]
554
+ sources = entry[/\S+ (.*)/,1].split(/\s+/).map{|x| x.to_i}
555
+ if scope
556
+ wanted_sidx = scope_to_sindex(scope)
557
+ return nil unless wanted_sidx
558
+ return nil unless sources.include?(wanted_sidx)
559
+ return klass.new(self, entry[/\S+/], index, wanted_sidx)
560
+ end
561
+ klass.new(self, entry[/\S+/], index, nil)
562
+ end
563
+
564
+ def binary_search(array, name, from = 0, to = array.size - 1)
565
+ middle = (from + to) / 2
566
+ pivot = array[middle][/\S+/]
567
+ if from == to
568
+ if pivot.index(name) == 0
569
+ from
570
+ else
571
+ nil
572
+ end
573
+ elsif name <= pivot
574
+ binary_search(array, name, from, middle)
575
+ elsif name > pivot
576
+ binary_search(array, name, middle+1, to)
577
+ end
578
+ end
579
+
580
+ def obtain_classes(namespace, res = [])
581
+ subnamespaces = namespace.classes_and_modules
582
+ subnamespaces.each do |ns|
583
+ res << ns.full_name
584
+ obtain_classes(ns, res)
585
+ end
586
+ res
587
+ end
588
+
589
+ def obtain_methods(namespace, res = [])
590
+ subnamespaces = namespace.classes_and_modules
591
+ subnamespaces.each do |ns|
592
+ res.concat ns.all_method_names
593
+ obtain_methods(ns, res)
594
+ end
595
+ res
596
+ end
597
+ end
598
+
599
+ end #module FastRI
600
+
601
+ # vi: set sw=2 expandtab: