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.
- 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,430 @@
|
|
1
|
+
# Copyright (C) 2006 Mauricio Fernandez <mfp@acm.org>
|
2
|
+
#
|
3
|
+
# Inspired by ri-emacs.rb by Kristof Bastiaensen <kristof@vleeuwen.org>
|
4
|
+
|
5
|
+
require 'rdoc/ri/paths'
|
6
|
+
require 'rdoc/ri/util'
|
7
|
+
require 'rdoc/ri/formatter'
|
8
|
+
require 'rdoc/ri/display'
|
9
|
+
|
10
|
+
require 'fastri/ri_index.rb'
|
11
|
+
require 'fastri/name_descriptor'
|
12
|
+
|
13
|
+
|
14
|
+
module FastRI
|
15
|
+
|
16
|
+
class RiError < Exception; end
|
17
|
+
|
18
|
+
class ::DefaultDisplay
|
19
|
+
def full_params(method)
|
20
|
+
method.params.split(/\n/).each do |p|
|
21
|
+
p.sub!(/^#{method.name}\(/o,'(')
|
22
|
+
unless p =~ /\b\.\b/
|
23
|
+
p = method.full_name + p
|
24
|
+
end
|
25
|
+
@formatter.wrap(p)
|
26
|
+
@formatter.break_to_newline
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class StringRedirectedDisplay < ::RDoc::RI::DefaultDisplay
|
32
|
+
attr_reader :stringio, :formatter
|
33
|
+
def initialize(*args)
|
34
|
+
super(*args)
|
35
|
+
reset_stringio
|
36
|
+
end
|
37
|
+
|
38
|
+
def puts(*a)
|
39
|
+
@stringio.puts(*a)
|
40
|
+
end
|
41
|
+
|
42
|
+
def print(*a)
|
43
|
+
@stringio.print(*a)
|
44
|
+
end
|
45
|
+
|
46
|
+
def reset_stringio
|
47
|
+
@stringio = StringIO.new("")
|
48
|
+
@formatter.stringio = @stringio
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class ::RDoc::RI::TextFormatter
|
53
|
+
def puts(*a); @stringio.puts(*a) end
|
54
|
+
def print(*a); @stringio.print(*a) end
|
55
|
+
end
|
56
|
+
|
57
|
+
module FormatterRedirection
|
58
|
+
attr_accessor :stringio
|
59
|
+
def initialize(*options)
|
60
|
+
@stringio = StringIO.new("")
|
61
|
+
super
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class RedirectedAnsiFormatter < ::RDoc::RI::AnsiFormatter
|
66
|
+
include FormatterRedirection
|
67
|
+
end
|
68
|
+
|
69
|
+
class RedirectedTextFormatter < ::RDoc::RI::TextFormatter
|
70
|
+
include FormatterRedirection
|
71
|
+
end
|
72
|
+
|
73
|
+
class RiService
|
74
|
+
|
75
|
+
class MatchFinder
|
76
|
+
def self.new
|
77
|
+
ret = super
|
78
|
+
yield ret if block_given?
|
79
|
+
ret
|
80
|
+
end
|
81
|
+
|
82
|
+
def initialize
|
83
|
+
@matchers = {}
|
84
|
+
end
|
85
|
+
|
86
|
+
def add_matcher(name, &block)
|
87
|
+
@matchers[name] = block
|
88
|
+
end
|
89
|
+
|
90
|
+
def get_matches(methods)
|
91
|
+
catch(:MatchFinder_return) do
|
92
|
+
methods.each do |name|
|
93
|
+
matcher = @matchers[name]
|
94
|
+
matcher.call(self) if matcher
|
95
|
+
end
|
96
|
+
[]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def yield(matches)
|
101
|
+
case matches
|
102
|
+
when nil, []; nil
|
103
|
+
when Array
|
104
|
+
throw :MatchFinder_return, matches
|
105
|
+
else
|
106
|
+
throw :MatchFinder_return, [matches]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end # MatchFinder
|
110
|
+
|
111
|
+
|
112
|
+
Options = Struct.new(:formatter, :use_stdout, :width)
|
113
|
+
|
114
|
+
def initialize(ri_reader)
|
115
|
+
@ri_reader = ri_reader
|
116
|
+
end
|
117
|
+
|
118
|
+
DEFAULT_OBTAIN_ENTRIES_OPTIONS = {
|
119
|
+
:lookup_order => [
|
120
|
+
:exact, :exact_ci, :nested, :nested_ci, :partial, :partial_ci,
|
121
|
+
:nested_partial, :nested_partial_ci,
|
122
|
+
],
|
123
|
+
}
|
124
|
+
def obtain_entries(descriptor, options = {})
|
125
|
+
options = DEFAULT_OBTAIN_ENTRIES_OPTIONS.merge(options)
|
126
|
+
if descriptor.class_names.empty?
|
127
|
+
seps = separators(descriptor.is_class_method)
|
128
|
+
return obtain_unqualified_method_entries(descriptor.method_name, seps,
|
129
|
+
options[:lookup_order])
|
130
|
+
end
|
131
|
+
|
132
|
+
# if we're here, some namespace was given
|
133
|
+
full_ns_name = descriptor.class_names.join("::")
|
134
|
+
if descriptor.method_name == nil
|
135
|
+
return obtain_namespace_entries(full_ns_name, options[:lookup_order])
|
136
|
+
else # both namespace and method
|
137
|
+
seps = separators(descriptor.is_class_method)
|
138
|
+
return obtain_qualified_method_entries(full_ns_name, descriptor.method_name,
|
139
|
+
seps, options[:lookup_order])
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def completion_list(keyw)
|
144
|
+
return @ri_reader.full_class_names if keyw == ""
|
145
|
+
|
146
|
+
descriptor = NameDescriptor.new(keyw)
|
147
|
+
|
148
|
+
if descriptor.class_names.empty?
|
149
|
+
# try partial matches
|
150
|
+
meths = @ri_reader.methods_under_matching("", /(#|\.)#{descriptor.method_name}/, true)
|
151
|
+
ret = meths.map{|x| x.name}.uniq.sort
|
152
|
+
return ret.empty? ? nil : ret
|
153
|
+
end
|
154
|
+
|
155
|
+
# if we're here, some namespace was given
|
156
|
+
full_ns_name = descriptor.class_names.join("::")
|
157
|
+
if descriptor.method_name == nil && ! [?#, ?:, ?.].include?(keyw[-1])
|
158
|
+
# partial match
|
159
|
+
namespaces = @ri_reader.namespaces_under_matching("", /^#{full_ns_name}/, false)
|
160
|
+
ret = namespaces.map{|x| x.full_name}.uniq.sort
|
161
|
+
return ret.empty? ? nil : ret
|
162
|
+
else
|
163
|
+
if [?#, ?:, ?.].include?(keyw[-1])
|
164
|
+
seps = case keyw[-1]
|
165
|
+
when ?#; %w[#]
|
166
|
+
when ?:; %w[.]
|
167
|
+
when ?.; %w[. #]
|
168
|
+
end
|
169
|
+
else # both namespace and method
|
170
|
+
seps = separators(descriptor.is_class_method)
|
171
|
+
end
|
172
|
+
sep_re = "(" + seps.map{|x| Regexp.escape(x)}.join("|") + ")"
|
173
|
+
# partial
|
174
|
+
methods = @ri_reader.methods_under_matching(full_ns_name, /#{sep_re}#{descriptor.method_name}/, false)
|
175
|
+
ret = methods.map{|x| x.full_name}.uniq.sort
|
176
|
+
return ret.empty? ? nil : ret
|
177
|
+
end
|
178
|
+
rescue RiError
|
179
|
+
return nil
|
180
|
+
end
|
181
|
+
|
182
|
+
DEFAULT_INFO_OPTIONS = {
|
183
|
+
:formatter => :ansi,
|
184
|
+
:width => 72,
|
185
|
+
:extended => false,
|
186
|
+
:expand_choices => false,
|
187
|
+
}
|
188
|
+
|
189
|
+
def matches(keyword, options = {})
|
190
|
+
options = DEFAULT_INFO_OPTIONS.merge(options)
|
191
|
+
return nil if keyword.strip.empty?
|
192
|
+
descriptor = NameDescriptor.new(keyword)
|
193
|
+
ret = obtain_entries(descriptor, options).map{|x| x.full_name}
|
194
|
+
ret ? ret : nil
|
195
|
+
rescue RiError
|
196
|
+
return nil
|
197
|
+
end
|
198
|
+
|
199
|
+
def info(keyw, options = {})
|
200
|
+
options = DEFAULT_INFO_OPTIONS.merge(options)
|
201
|
+
return nil if keyw.strip.empty?
|
202
|
+
descriptor = NameDescriptor.new(keyw)
|
203
|
+
entries = obtain_entries(descriptor, options)
|
204
|
+
|
205
|
+
case entries.size
|
206
|
+
when 0; nil
|
207
|
+
when 1
|
208
|
+
case entries[0].type
|
209
|
+
when :namespace
|
210
|
+
capture_stdout(display(options)) do |display|
|
211
|
+
#display.display_class_info(@ri_reader.get_class(entries[0]), @ri_reader)
|
212
|
+
display.display_class_info(@ri_reader.get_class(entries[0]))
|
213
|
+
if options[:extended]
|
214
|
+
methods = @ri_reader.methods_under(entries[0], true)
|
215
|
+
methods.each do |meth_entry|
|
216
|
+
display.display_method_info(@ri_reader.get_method(meth_entry))
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
when :method
|
221
|
+
capture_stdout(display(options)) do |display|
|
222
|
+
display.display_method_info(@ri_reader.get_method(entries[0]))
|
223
|
+
end
|
224
|
+
end
|
225
|
+
else
|
226
|
+
capture_stdout(display(options)) do |display|
|
227
|
+
formatter = display.formatter
|
228
|
+
formatter.draw_line("Multiple choices:")
|
229
|
+
formatter.blankline
|
230
|
+
formatter.wrap(entries.map{|x| x.full_name}.join(", "))
|
231
|
+
entries.each do |entry|
|
232
|
+
display.display_method_info(@ri_reader.get_method(entry)) if entry.type == :method
|
233
|
+
end if options[:expand_choices]
|
234
|
+
end
|
235
|
+
end
|
236
|
+
rescue RiError
|
237
|
+
return nil
|
238
|
+
end
|
239
|
+
|
240
|
+
def args(keyword, options = {})
|
241
|
+
options = DEFAULT_INFO_OPTIONS.merge(options)
|
242
|
+
return nil if keyword.strip.empty?
|
243
|
+
descriptor = NameDescriptor.new(keyword)
|
244
|
+
entries = obtain_entries(descriptor, options)
|
245
|
+
return nil if entries.empty? || RiIndex::ClassEntry === entries[0]
|
246
|
+
|
247
|
+
params_text = ""
|
248
|
+
entries.each do |entry|
|
249
|
+
desc = @ri_reader.get_method(entry)
|
250
|
+
params_text << capture_stdout(display(options)) do |display|
|
251
|
+
display.full_params(desc)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
params_text
|
255
|
+
rescue RiError
|
256
|
+
return nil
|
257
|
+
end
|
258
|
+
|
259
|
+
# Returns a list with the names of the modules/classes that define the given
|
260
|
+
# method, or +nil+.
|
261
|
+
def class_list(keyword)
|
262
|
+
_class_list(keyword, '\1')
|
263
|
+
end
|
264
|
+
|
265
|
+
# Returns a list with the names of the modules/classes that define the given
|
266
|
+
# method, followed by a flag (#|::), or +nil+.
|
267
|
+
# e.g. ["Array#", "IO#", "IO::", ... ]
|
268
|
+
def class_list_with_flag(keyword)
|
269
|
+
r = _class_list(keyword, '\1\2')
|
270
|
+
r ? r.map{|x| x.gsub(/\./, "::")} : nil
|
271
|
+
end
|
272
|
+
|
273
|
+
# Return array of strings with the names of all known methods.
|
274
|
+
def all_methods
|
275
|
+
@ri_reader.full_method_names
|
276
|
+
end
|
277
|
+
|
278
|
+
# Return array of strings with the names of all known classes.
|
279
|
+
def all_classes
|
280
|
+
@ri_reader.full_class_names
|
281
|
+
end
|
282
|
+
|
283
|
+
private
|
284
|
+
|
285
|
+
def obtain_unqualified_method_entries(name, separators, order)
|
286
|
+
name = Regexp.escape(name)
|
287
|
+
sep_re = "(" + separators.map{|x| Regexp.escape(x)}.join("|") + ")"
|
288
|
+
matcher = MatchFinder.new do |m|
|
289
|
+
m.add_matcher(:exact) do
|
290
|
+
m.yield @ri_reader.methods_under_matching("", /#{sep_re}#{name}$/, true)
|
291
|
+
end
|
292
|
+
m.add_matcher(:exact_ci) do
|
293
|
+
m.yield @ri_reader.methods_under_matching("", /#{sep_re}#{name}$/i, true)
|
294
|
+
end
|
295
|
+
m.add_matcher(:partial) do
|
296
|
+
m.yield @ri_reader.methods_under_matching("", /#{sep_re}#{name}/, true)
|
297
|
+
end
|
298
|
+
m.add_matcher(:partial_ci) do
|
299
|
+
m.yield @ri_reader.methods_under_matching("", /#{sep_re}#{name}/i, true)
|
300
|
+
end
|
301
|
+
m.add_matcher(:anywhere) do
|
302
|
+
m.yield @ri_reader.methods_under_matching("", /#{sep_re}.*#{name}/, true)
|
303
|
+
end
|
304
|
+
m.add_matcher(:anywhere_ci) do
|
305
|
+
m.yield @ri_reader.methods_under_matching("", /#{sep_re}.*#{name}/i, true)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
matcher.get_matches(order)
|
309
|
+
end
|
310
|
+
|
311
|
+
def obtain_qualified_method_entries(namespace, method, separators, order)
|
312
|
+
namespace, unescaped_namespace = Regexp.escape(namespace), namespace
|
313
|
+
method = Regexp.escape(method)
|
314
|
+
matcher = MatchFinder.new do |m|
|
315
|
+
m.add_matcher(:exact) do
|
316
|
+
separators.each do |sep|
|
317
|
+
m.yield @ri_reader.get_method_entry("#{namespace}#{sep}#{method}")
|
318
|
+
end
|
319
|
+
end
|
320
|
+
sep_re = "(" + separators.map{|x| Regexp.escape(x)}.join("|") + ")"
|
321
|
+
m.add_matcher(:exact_ci) do
|
322
|
+
m.yield @ri_reader.methods_under_matching("", /^#{namespace}#{sep_re}#{method}$/i, true)
|
323
|
+
end
|
324
|
+
m.add_matcher(:nested) do
|
325
|
+
m.yield @ri_reader.methods_under_matching("", /::#{namespace}#{sep_re}#{method}$/, true)
|
326
|
+
end
|
327
|
+
m.add_matcher(:nested_ci) do
|
328
|
+
m.yield @ri_reader.methods_under_matching("", /::#{namespace}#{sep_re}#{method}$/i, true)
|
329
|
+
end
|
330
|
+
m.add_matcher(:partial) do
|
331
|
+
m.yield @ri_reader.methods_under_matching(unescaped_namespace, /#{sep_re}#{method}/, false)
|
332
|
+
end
|
333
|
+
m.add_matcher(:partial_ci) do
|
334
|
+
m.yield @ri_reader.methods_under_matching("", /^#{namespace}#{sep_re}#{method}/i, true)
|
335
|
+
end
|
336
|
+
m.add_matcher(:nested_partial) do
|
337
|
+
m.yield @ri_reader.methods_under_matching("", /::#{namespace}#{sep_re}#{method}/, true)
|
338
|
+
end
|
339
|
+
m.add_matcher(:nested_partial_ci) do
|
340
|
+
m.yield @ri_reader.methods_under_matching("", /::#{namespace}#{sep_re}#{method}/i, true)
|
341
|
+
end
|
342
|
+
m.add_matcher(:namespace_partial) do
|
343
|
+
m.yield @ri_reader.methods_under_matching("", /^#{namespace}[^:]*#{sep_re}#{method}$/, true)
|
344
|
+
end
|
345
|
+
m.add_matcher(:namespace_partial_ci) do
|
346
|
+
m.yield @ri_reader.methods_under_matching("", /^#{namespace}[^:]*#{sep_re}#{method}$/i, true)
|
347
|
+
end
|
348
|
+
m.add_matcher(:full_partial) do
|
349
|
+
m.yield @ri_reader.methods_under_matching("", /^#{namespace}[^:]*#{sep_re}#{method}/, true)
|
350
|
+
end
|
351
|
+
m.add_matcher(:full_partial_ci) do
|
352
|
+
m.yield @ri_reader.methods_under_matching("", /^#{namespace}[^:]*#{sep_re}#{method}/i, true)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
matcher.get_matches(order)
|
356
|
+
end
|
357
|
+
|
358
|
+
def obtain_namespace_entries(name, order)
|
359
|
+
name = Regexp.escape(name)
|
360
|
+
matcher = MatchFinder.new do |m|
|
361
|
+
m.add_matcher(:exact){ m.yield @ri_reader.get_class_entry(name) }
|
362
|
+
m.add_matcher(:exact_ci) do
|
363
|
+
m.yield @ri_reader.namespaces_under_matching("", /^#{name}$/i, true)
|
364
|
+
end
|
365
|
+
m.add_matcher(:nested) do
|
366
|
+
m.yield @ri_reader.namespaces_under_matching("", /::#{name}$/, true)
|
367
|
+
end
|
368
|
+
m.add_matcher(:nested_ci) do
|
369
|
+
m.yield @ri_reader.namespaces_under_matching("", /::#{name}$/i, true)
|
370
|
+
end
|
371
|
+
m.add_matcher(:partial) do
|
372
|
+
m.yield @ri_reader.namespaces_under_matching("", /^#{name}/, true)
|
373
|
+
end
|
374
|
+
m.add_matcher(:partial_ci) do
|
375
|
+
m.yield @ri_reader.namespaces_under_matching("", /^#{name}/i, true)
|
376
|
+
end
|
377
|
+
m.add_matcher(:nested_partial) do
|
378
|
+
m.yield @ri_reader.namespaces_under_matching("", /::#{name}[^:]*$/, true)
|
379
|
+
end
|
380
|
+
m.add_matcher(:nested_partial_ci) do
|
381
|
+
m.yield @ri_reader.namespaces_under_matching("", /::#{name}[^:]*$/i, true)
|
382
|
+
end
|
383
|
+
end
|
384
|
+
matcher.get_matches(order)
|
385
|
+
end
|
386
|
+
|
387
|
+
def _class_list(keyword, rep)
|
388
|
+
return nil if keyword.strip.empty?
|
389
|
+
entries = @ri_reader.methods_under_matching("", /#{keyword}$/, true)
|
390
|
+
return nil if entries.empty?
|
391
|
+
|
392
|
+
entries.map{|entry| entry.full_name.sub(/(.*)(#|\.).*/, rep) }.uniq
|
393
|
+
rescue RiError
|
394
|
+
return nil
|
395
|
+
end
|
396
|
+
|
397
|
+
|
398
|
+
def separators(is_class_method)
|
399
|
+
case is_class_method
|
400
|
+
when true; ["."]
|
401
|
+
when false; ["#"]
|
402
|
+
when nil; [".","#"]
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
DEFAULT_DISPLAY_OPTIONS = {
|
407
|
+
:formatter => :ansi,
|
408
|
+
:width => 72,
|
409
|
+
}
|
410
|
+
def display(opt = {})
|
411
|
+
opt = DEFAULT_DISPLAY_OPTIONS.merge(opt)
|
412
|
+
options = Options.new
|
413
|
+
options.use_stdout = true
|
414
|
+
case opt[:formatter].to_sym
|
415
|
+
when :ansi
|
416
|
+
options.formatter = RedirectedAnsiFormatter
|
417
|
+
else
|
418
|
+
options.formatter = RedirectedTextFormatter
|
419
|
+
end
|
420
|
+
options.width = opt[:width]
|
421
|
+
StringRedirectedDisplay.new(options.formatter, options.width, options.use_stdout)
|
422
|
+
end
|
423
|
+
|
424
|
+
def capture_stdout(display)
|
425
|
+
yield display
|
426
|
+
display.stringio.string
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
end # module FastRI
|
data/lib/fastri/util.rb
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
# Copyright (C) 2006 Mauricio Fernandez <mfp@acm.org>
|
2
|
+
|
3
|
+
# emulate rubygems.rb and define Gem.path if not loaded
|
4
|
+
# This is much faster than requiring rubygems.rb, which loads way too much
|
5
|
+
# stuff.
|
6
|
+
unless defined? ::Gem
|
7
|
+
require 'rbconfig'
|
8
|
+
module Gem
|
9
|
+
def self.path
|
10
|
+
[ENV['GEM_HOME'], ENV['GEM_PATH'], default_dir].compact.flatten
|
11
|
+
end
|
12
|
+
def self.default_dir
|
13
|
+
if defined? RUBY_FRAMEWORK_VERSION
|
14
|
+
paths = []
|
15
|
+
paths << APPLE_GEM_HOME if defined? APPLE_GEM_HOME
|
16
|
+
path = File.join(File.dirname(Config::CONFIG["sitedir"]), "Gems")
|
17
|
+
newpath = File.join(path, Config::CONFIG['ruby_version'])
|
18
|
+
# RubyGems post r1498 appends the ruby version to the path. This
|
19
|
+
# modification was included in the RubyGems shipped with 10.5.0.
|
20
|
+
if File.directory?(newpath)
|
21
|
+
# try new path first, user might have upgraded RubyGems and left old
|
22
|
+
# installation behind
|
23
|
+
paths + [ newpath ]
|
24
|
+
else
|
25
|
+
# pre-10.5.0 or older RubyGems
|
26
|
+
paths + [ path ]
|
27
|
+
end
|
28
|
+
else
|
29
|
+
[ File.join(Config::CONFIG['libdir'], 'ruby', 'gems',
|
30
|
+
Config::CONFIG['ruby_version']) ]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
# don't let rdoc/ri/ri_paths load rubygems.rb, that takes ~100ms !
|
36
|
+
emulation = $".all?{|x| /rubygems\.rb$/ !~ x} # 1.9 compatibility
|
37
|
+
$".unshift "rubygems.rb" if emulation
|
38
|
+
require 'rdoc/ri/paths'
|
39
|
+
$".delete "rubygems.rb" if emulation
|
40
|
+
require 'rdoc/ri/writer'
|
41
|
+
|
42
|
+
module FastRI
|
43
|
+
module Util
|
44
|
+
# Return an array of <tt>[name, version, path]</tt> arrays corresponding to
|
45
|
+
# the last version of each installed gem. +path+ is the base path of the RI
|
46
|
+
# documentation from the gem. If the version cannot be determined, it will
|
47
|
+
# be +nil+, and the corresponding gem might be repeated in the output array
|
48
|
+
# (once per version).
|
49
|
+
def gem_directories_unique
|
50
|
+
return [] unless defined? Gem
|
51
|
+
gemdirs = Gem.path.map{|p| Dir["#{p}/doc/*/ri"]}.flatten
|
52
|
+
gems = Hash.new{|h,k| h[k] = []}
|
53
|
+
gemdirs.each do |path|
|
54
|
+
gemname, version = %r{/([^/]+)-([^-]*)/ri$}.match(path).captures
|
55
|
+
if gemname.nil? # doesn't follow any conventions :(
|
56
|
+
gems[path[%r{/([^/]+)/ri$}, 1]] << [nil, path]
|
57
|
+
else
|
58
|
+
gems[gemname] << [version, path]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
gems.sort_by{|name, _| name}.map do |name, versions|
|
62
|
+
version, path = versions.sort.last
|
63
|
+
[name, version, File.expand_path(path)]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
module_function :gem_directories_unique
|
67
|
+
|
68
|
+
# Return the <tt>[name, version, path]</tt> array for the gem owning the RI
|
69
|
+
# information stored in +path+, or +nil+.
|
70
|
+
def gem_info_for_path(path, gem_dir_info = FastRI::Util.gem_directories_unique)
|
71
|
+
path = File.expand_path(path)
|
72
|
+
matches = gem_dir_info.select{|name, version, gem_path| path.index(gem_path) == 0}
|
73
|
+
matches.sort_by{|name, version, gem_path| [gem_path.size, version, name]}.last
|
74
|
+
end
|
75
|
+
module_function :gem_info_for_path
|
76
|
+
|
77
|
+
# Return the +full_name+ (in ClassEntry or MethodEntry's sense) given a path
|
78
|
+
# to a .yaml file relative to a "base RI DB path".
|
79
|
+
def gem_relpath_to_full_name(relpath)
|
80
|
+
case relpath
|
81
|
+
when %r{^(.*)/cdesc-([^/]*)\.yaml$}
|
82
|
+
path, name = $~.captures
|
83
|
+
(path.split(%r{/})[0..-2] << name).join("::")
|
84
|
+
when %r{^(.*)/([^/]*)-(i|c)\.yaml$}
|
85
|
+
path, escaped_name, type = $~.captures
|
86
|
+
name = RI::RiWriter.external_to_internal(escaped_name)
|
87
|
+
sep = ( type == 'c' ) ? "." : "#"
|
88
|
+
path.gsub("/", "::") + sep + name
|
89
|
+
end
|
90
|
+
end
|
91
|
+
module_function :gem_relpath_to_full_name
|
92
|
+
|
93
|
+
# Returns the home directory (win32-aware).
|
94
|
+
def find_home
|
95
|
+
# stolen from RubyGems
|
96
|
+
['HOME', 'USERPROFILE'].each do |homekey|
|
97
|
+
return ENV[homekey] if ENV[homekey]
|
98
|
+
end
|
99
|
+
if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
|
100
|
+
return "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}"
|
101
|
+
end
|
102
|
+
begin
|
103
|
+
File.expand_path("~")
|
104
|
+
rescue StandardError => ex
|
105
|
+
if File::ALT_SEPARATOR
|
106
|
+
"C:/"
|
107
|
+
else
|
108
|
+
"/"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
module_function :find_home
|
113
|
+
|
114
|
+
def change_query_method_type(query)
|
115
|
+
if md = /\A(.*)(#|\.|::)([^#.:]+)\z/.match(query)
|
116
|
+
namespace, sep, meth = md.captures
|
117
|
+
case sep
|
118
|
+
when /::/ then "#{namespace}##{meth}"
|
119
|
+
when /#/ then "#{namespace}::#{meth}"
|
120
|
+
else
|
121
|
+
query
|
122
|
+
end
|
123
|
+
else
|
124
|
+
query
|
125
|
+
end
|
126
|
+
end
|
127
|
+
module_function :change_query_method_type
|
128
|
+
|
129
|
+
|
130
|
+
module MagicHelp
|
131
|
+
def help_method_extract(m) # :nodoc:
|
132
|
+
unless m.inspect =~ %r[\A#<(?:Unbound)?Method: (.*?)>\Z]
|
133
|
+
raise "Cannot parse result of #{m.class}#inspect: #{m.inspect}"
|
134
|
+
end
|
135
|
+
$1.sub(/\A.*?\((.*?)\)(.*)\Z/){ "#{$1}#{$2}" }.sub(/\./, "::").sub(/#<Class:(.*?)>#/) { "#{$1}::" }
|
136
|
+
end
|
137
|
+
|
138
|
+
def magic_help(query)
|
139
|
+
if query =~ /\A(.*?)(#|::|\.)([^:#.]+)\Z/
|
140
|
+
c, k, m = $1, $2, $3
|
141
|
+
mid = m
|
142
|
+
begin
|
143
|
+
c = c.split(/::/).inject(Object){|s,x| s.const_get(x)}
|
144
|
+
m = case k
|
145
|
+
when "#"
|
146
|
+
c.instance_method(m)
|
147
|
+
when "::"
|
148
|
+
c.method(m)
|
149
|
+
when "."
|
150
|
+
begin
|
151
|
+
# if it's a private_instance_method, assume it was created
|
152
|
+
# with module_function
|
153
|
+
if c.private_instance_methods.include?(m)
|
154
|
+
c.instance_method(m)
|
155
|
+
else
|
156
|
+
c.method(m)
|
157
|
+
end
|
158
|
+
rescue NameError
|
159
|
+
c.instance_method(m)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
ret = help_method_extract(m)
|
164
|
+
if ret == 'Class#new' and
|
165
|
+
c.private_method_defined?(:initialize)
|
166
|
+
return c.name + "::new"
|
167
|
+
elsif ret =~ /^Kernel#/ and
|
168
|
+
Kernel.instance_methods(false).include? mid
|
169
|
+
return "Object##{mid}"
|
170
|
+
end
|
171
|
+
ret
|
172
|
+
rescue Exception
|
173
|
+
query
|
174
|
+
end
|
175
|
+
else
|
176
|
+
query
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
end # module Util
|
183
|
+
end # module FastRI
|