dakrone-fastri 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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