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,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
@@ -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