dakrone-fastri 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +61 -0
- data/COPYING +340 -0
- data/LEGAL +4 -0
- data/LICENSE +56 -0
- data/README.en +102 -0
- data/Rakefile +26 -0
- data/THANKS +36 -0
- data/bin/fastri-server +251 -0
- data/bin/fri +353 -0
- data/bin/ri-emacs +202 -0
- data/fastri.gemspec +64 -0
- data/indexer.rb +135 -0
- data/lib/fastri/full_text_index.rb +245 -0
- data/lib/fastri/full_text_indexer.rb +100 -0
- data/lib/fastri/name_descriptor.rb +71 -0
- data/lib/fastri/ri_index.rb +601 -0
- data/lib/fastri/ri_service.rb +430 -0
- data/lib/fastri/util.rb +183 -0
- data/lib/fastri/version.rb +13 -0
- data/lookup.rb +197 -0
- data/pre-install.rb +11 -0
- data/setup.rb +1585 -0
- data/test/test_full_text_index.rb +182 -0
- data/test/test_full_text_indexer.rb +84 -0
- data/test/test_functional_ri_service.rb +60 -0
- data/test/test_integration_full_text_index.rb +43 -0
- data/test/test_name_descriptor.rb +35 -0
- data/test/test_ri_index.rb +389 -0
- data/test/test_util.rb +91 -0
- metadata +84 -0
@@ -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:
|