dakrone-fastri 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: