OSDN Git Service

ruby-1.9.1-rc1
[splhack/AndroidRuby.git] / lib / ruby-1.9.1-rc1 / lib / rdoc / ri / driver.rb
1 require 'optparse'
2 require 'yaml'
3
4 require 'rdoc/ri'
5 require 'rdoc/ri/paths'
6 require 'rdoc/ri/formatter'
7 require 'rdoc/ri/display'
8 require 'fileutils'
9 require 'rdoc/markup'
10 require 'rdoc/markup/to_flow'
11
12 class RDoc::RI::Driver
13
14   #
15   # This class offers both Hash and OpenStruct functionality.
16   # We convert from the Core Hash to this before calling any of
17   # the display methods, in order to give the display methods
18   # a cleaner API for accessing the data.
19   #
20   class OpenStructHash < Hash
21     #
22     # This method converts from a Hash to an OpenStructHash.
23     #
24     def self.convert(object)
25       case object
26       when Hash then
27         new_hash = new # Convert Hash -> OpenStructHash
28
29         object.each do |key, value|
30           new_hash[key] = convert(value)
31         end
32
33         new_hash
34       when Array then
35         object.map do |element|
36           convert(element)
37         end
38       else
39         object
40       end
41     end
42
43     def merge_enums(other)
44       other.each do |k, v|
45         if self[k] then
46           case v
47           when Array then
48             # HACK dunno
49             if String === self[k] and self[k].empty? then
50               self[k] = v
51             else
52               self[k] += v
53             end
54           when Hash then
55             self[k].update v
56           else
57             # do nothing
58           end
59         else
60           self[k] = v
61         end
62       end
63     end
64
65     def method_missing method, *args
66       self[method.to_s]
67     end
68   end
69
70   class Error < RDoc::RI::Error; end
71
72   class NotFoundError < Error
73     def message
74       "Nothing known about #{super}"
75     end
76   end
77
78   attr_accessor :homepath # :nodoc:
79
80   def self.default_options
81     options = {}
82     options[:use_stdout] = !$stdout.tty?
83     options[:width] = 72
84     options[:formatter] = RDoc::RI::Formatter.for 'plain'
85     options[:interactive] = false
86     options[:use_cache] = true
87
88     # By default all standard paths are used.
89     options[:use_system] = true
90     options[:use_site] = true
91     options[:use_home] = true
92     options[:use_gems] = true
93     options[:extra_doc_dirs] = []
94
95     return options
96   end
97
98   def self.process_args(argv)
99     options = default_options
100
101     opts = OptionParser.new do |opt|
102       opt.program_name = File.basename $0
103       opt.version = RDoc::VERSION
104       opt.release = nil
105       opt.summary_indent = ' ' * 4
106
107       directories = [
108         RDoc::RI::Paths::SYSDIR,
109         RDoc::RI::Paths::SITEDIR,
110         RDoc::RI::Paths::HOMEDIR
111       ]
112
113       if RDoc::RI::Paths::GEMDIRS then
114         Gem.path.each do |dir|
115           directories << "#{dir}/doc/*/ri"
116         end
117       end
118
119       opt.banner = <<-EOT
120 Usage: #{opt.program_name} [options] [names...]
121
122 Where name can be:
123
124   Class | Class::method | Class#method | Class.method | method
125
126 All class names may be abbreviated to their minimum unambiguous form. If a name
127 is ambiguous, all valid options will be listed.
128
129 The form '.' method matches either class or instance methods, while #method
130 matches only instance and ::method matches only class methods.
131
132 For example:
133
134     #{opt.program_name} Fil
135     #{opt.program_name} File
136     #{opt.program_name} File.new
137     #{opt.program_name} zip
138
139 Note that shell quoting may be required for method names containing
140 punctuation:
141
142     #{opt.program_name} 'Array.[]'
143     #{opt.program_name} compact\\!
144
145 By default ri searches for documentation in the following directories:
146
147     #{directories.join "\n    "}
148
149 Specifying the --system, --site, --home, --gems or --doc-dir options will
150 limit ri to searching only the specified directories.
151
152 Options may also be set in the 'RI' environment variable.
153       EOT
154
155       opt.separator nil
156       opt.separator "Options:"
157       opt.separator nil
158
159       opt.on("--fmt=FORMAT", "--format=FORMAT", "-f",
160              RDoc::RI::Formatter::FORMATTERS.keys,
161              "Format to use when displaying output:",
162              "   #{RDoc::RI::Formatter.list}",
163              "Use 'bs' (backspace) with most pager",
164              "programs. To use ANSI, either disable the",
165              "pager or tell the pager to allow control",
166              "characters.") do |value|
167         options[:formatter] = RDoc::RI::Formatter.for value
168       end
169
170       opt.separator nil
171
172       opt.on("--doc-dir=DIRNAME", "-d", Array,
173              "List of directories from which to source",
174              "documentation in addition to the standard",
175              "directories.  May be repeated.") do |value|
176         value.each do |dir|
177           unless File.directory? dir then
178             raise OptionParser::InvalidArgument, "#{dir} is not a directory"
179           end
180
181           options[:extra_doc_dirs] << File.expand_path(dir)
182         end
183       end
184
185       opt.separator nil
186
187       opt.on("--[no-]use-cache",
188              "Whether or not to use ri's cache.",
189              "True by default.") do |value|
190         options[:use_cache] = value
191       end
192
193       opt.separator nil
194
195       opt.on("--no-standard-docs",
196              "Do not include documentation from",
197              "the Ruby standard library, site_lib,",
198              "installed gems, or ~/.rdoc.",
199              "Equivalent to specifying",
200              "the options --no-system, --no-site, --no-gems,",
201              "and --no-home") do
202         options[:use_system] = false
203         options[:use_site] = false
204         options[:use_gems] = false
205         options[:use_home] = false
206       end
207
208       opt.separator nil
209
210       opt.on("--[no-]system",
211              "Include documentation from Ruby's standard",
212              "library.  Defaults to true.") do |value|
213         options[:use_system] = value
214       end
215
216       opt.separator nil
217
218       opt.on("--[no-]site",
219              "Include documentation from libraries",
220              "installed in site_lib.",
221              "Defaults to true.") do |value|
222         options[:use_site] = value
223       end
224
225       opt.separator nil
226
227       opt.on("--[no-]gems",
228              "Include documentation from RubyGems.",
229              "Defaults to true.") do |value|
230         options[:use_gems] = value
231       end
232
233       opt.separator nil
234
235       opt.on("--[no-]home",
236              "Include documentation stored in ~/.rdoc.",
237              "Defaults to true.") do |value|
238         options[:use_home] = value
239       end
240
241       opt.separator nil
242
243       opt.on("--list-doc-dirs",
244              "List the directories from which ri will",
245              "source documentation on stdout and exit.") do
246         options[:list_doc_dirs] = true
247       end
248
249       opt.separator nil
250
251       opt.on("--no-pager", "-T",
252              "Send output directly to stdout,",
253              "rather than to a pager.") do
254         options[:use_stdout] = true
255       end
256
257       opt.on("--interactive", "-i",
258              "This makes ri go into interactive mode.",
259              "When ri is in interactive mode it will",
260              "allow the user to disambiguate lists of",
261              "methods in case multiple methods match",
262              "against a method search string.  It also",
263              "will allow the user to enter in a method",
264              "name (with auto-completion, if readline",
265              "is supported) when viewing a class.") do
266         options[:interactive] = true
267       end
268
269       opt.separator nil
270
271       opt.on("--width=WIDTH", "-w", OptionParser::DecimalInteger,
272              "Set the width of the output.") do |value|
273         options[:width] = value
274       end
275     end
276
277     argv = ENV['RI'].to_s.split.concat argv
278
279     opts.parse! argv
280
281     options[:names] = argv
282
283     options[:formatter] ||= RDoc::RI::Formatter.for('plain')
284     options[:use_stdout] ||= !$stdout.tty?
285     options[:use_stdout] ||= options[:interactive]
286     options[:width] ||= 72
287
288     options
289
290   rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e
291     puts opts
292     puts
293     puts e
294     exit 1
295   end
296
297   def self.run(argv = ARGV)
298     options = process_args argv
299     ri = new options
300     ri.run
301   end
302
303   def initialize(initial_options={})
304     options = self.class.default_options.update(initial_options)
305
306     @names = options[:names]
307     @class_cache_name = 'classes'
308
309     @doc_dirs = RDoc::RI::Paths.path(options[:use_system],
310                                      options[:use_site],
311                                      options[:use_home],
312                                      options[:use_gems],
313                                      options[:extra_doc_dirs])
314
315     @homepath = RDoc::RI::Paths.raw_path(false, false, true, false).first
316     @homepath = @homepath.sub(/\.rdoc/, '.ri')
317     @sys_dir = RDoc::RI::Paths.raw_path(true, false, false, false).first
318     @list_doc_dirs = options[:list_doc_dirs]
319
320     FileUtils.mkdir_p cache_file_path unless File.directory? cache_file_path
321     @cache_doc_dirs_path = File.join cache_file_path, ".doc_dirs"
322
323     @use_cache = options[:use_cache]
324     @class_cache = nil
325
326     @interactive = options[:interactive]
327     @display = RDoc::RI::DefaultDisplay.new(options[:formatter],
328                                             options[:width],
329                                             options[:use_stdout])
330   end
331
332   def class_cache
333     return @class_cache if @class_cache
334
335     # Get the documentation directories used to make the cache in order to see
336     # whether the cache is valid for the current ri instantiation.
337     if(File.readable?(@cache_doc_dirs_path))
338       cache_doc_dirs = IO.read(@cache_doc_dirs_path).split("\n")
339     else
340       cache_doc_dirs = []
341     end
342
343     newest = map_dirs('created.rid') do |f|
344       File.mtime f if test ?f, f
345     end.max
346
347     # An up to date cache file must have been created more recently than
348     # the last modification of any of the documentation directories.  It also
349     # must have been created with the same documentation directories
350     # as those from which ri currently is sourcing documentation.
351     up_to_date = (File.exist?(class_cache_file_path) and
352                   newest and newest < File.mtime(class_cache_file_path) and
353                   (cache_doc_dirs == @doc_dirs))
354
355     if up_to_date and @use_cache then
356       open class_cache_file_path, 'rb' do |fp|
357         begin
358           @class_cache = Marshal.load fp.read
359         rescue
360           #
361           # This shouldn't be necessary, since the up_to_date logic above
362           # should force the cache to be recreated when a new version of
363           # rdoc is installed.  This seems like a worthwhile enhancement
364           # to ri's robustness, however.
365           #
366           $stderr.puts "Error reading the class cache; recreating the class cache!"
367           @class_cache = create_class_cache
368         end
369       end
370     else
371       @class_cache = create_class_cache
372     end
373
374     @class_cache
375   end
376
377   def create_class_cache
378     class_cache = OpenStructHash.new
379
380     if(@use_cache)
381       # Dump the documentation directories to a file in the cache, so that
382       # we only will use the cache for future instantiations with identical
383       # documentation directories.
384       File.open @cache_doc_dirs_path, "wb" do |fp|
385         fp << @doc_dirs.join("\n")
386       end
387     end
388
389     classes = map_dirs('**/cdesc*.yaml') { |f| Dir[f] }
390     warn "Updating class cache with #{classes.size} classes..."
391     populate_class_cache class_cache, classes
392
393     write_cache class_cache, class_cache_file_path
394
395     class_cache
396   end
397
398   def populate_class_cache(class_cache, classes, extension = false)
399     classes.each do |cdesc|
400       desc = read_yaml cdesc
401       klassname = desc["full_name"]
402
403       unless class_cache.has_key? klassname then
404         desc["display_name"] = "Class"
405         desc["sources"] = [cdesc]
406         desc["instance_method_extensions"] = []
407         desc["class_method_extensions"] = []
408         class_cache[klassname] = desc
409       else
410         klass = class_cache[klassname]
411
412         if extension then
413           desc["instance_method_extensions"] = desc.delete "instance_methods"
414           desc["class_method_extensions"] = desc.delete "class_methods"
415         end
416
417         klass.merge_enums desc
418         klass["sources"] << cdesc
419       end
420     end
421   end
422
423   def class_cache_file_path
424     File.join cache_file_path, @class_cache_name
425   end
426
427   def cache_file_for(klassname)
428     File.join cache_file_path, klassname.gsub(/:+/, "-")
429   end
430
431   def cache_file_path
432     File.join @homepath, 'cache'
433   end
434
435   def display_class(name)
436     klass = class_cache[name]
437     @display.display_class_info klass
438   end
439
440   def display_method(method)
441     @display.display_method_info method
442   end
443
444   def get_info_for(arg)
445     @names = [arg]
446     run
447   end
448
449   def load_cache_for(klassname)
450     path = cache_file_for klassname
451
452     cache = nil
453
454     if File.exist? path and
455        File.mtime(path) >= File.mtime(class_cache_file_path) and
456        @use_cache then
457       open path, 'rb' do |fp|
458         begin
459           cache = Marshal.load fp.read
460         rescue
461           #
462           # The cache somehow is bad.  Recreate the cache.
463           #
464           $stderr.puts "Error reading the cache for #{klassname}; recreating the cache!"
465           cache = create_cache_for klassname, path
466         end
467       end
468     else
469       cache = create_cache_for klassname, path
470     end
471
472     cache
473   end
474
475   def create_cache_for(klassname, path)
476     klass = class_cache[klassname]
477     return nil unless klass
478
479     method_files = klass["sources"]
480     cache = OpenStructHash.new
481
482     method_files.each do |f|
483       system_file = f.index(@sys_dir) == 0
484       Dir[File.join(File.dirname(f), "*")].each do |yaml|
485         next unless yaml =~ /yaml$/
486         next if yaml =~ /cdesc-[^\/]+yaml$/
487
488         method = read_yaml yaml
489
490         if system_file then
491           method["source_path"] = "Ruby #{RDoc::RI::Paths::VERSION}"
492         else
493           if(f =~ %r%gems/[\d.]+/doc/([^/]+)%) then
494             ext_path = "gem #{$1}"
495           else
496             ext_path = f
497           end
498
499           method["source_path"] = ext_path
500         end
501
502         name = method["full_name"]
503         cache[name] = method
504       end
505     end
506
507     write_cache cache, path
508   end
509
510   ##
511   # Finds the next ancestor of +orig_klass+ after +klass+.
512
513   def lookup_ancestor(klass, orig_klass)
514     # This is a bit hacky, but ri will go into an infinite
515     # loop otherwise, since Object has an Object ancestor
516     # for some reason.  Depending on the documentation state, I've seen
517     # Kernel as an ancestor of Object and not as an ancestor of Object.
518     if ((orig_klass == "Object") &&
519         ((klass == "Kernel") || (klass == "Object")))
520       return nil
521     end
522
523     cache = class_cache[orig_klass]
524
525     return nil unless cache
526
527     ancestors = [orig_klass]
528     ancestors.push(*cache.includes.map { |inc| inc['name'] })
529     ancestors << cache.superclass
530     
531     ancestor_index = ancestors.index(klass)
532
533     if ancestor_index
534       ancestor = ancestors[ancestors.index(klass) + 1]
535       return ancestor if ancestor
536     end
537
538     lookup_ancestor klass, cache.superclass
539   end
540
541   ##
542   # Finds the method
543
544   def lookup_method(name, klass)
545     cache = load_cache_for klass
546     return nil unless cache
547
548     method = cache[name.gsub('.', '#')]
549     method = cache[name.gsub('.', '::')] unless method
550     method
551   end
552
553   def map_dirs(file_name)
554     @doc_dirs.map { |dir| yield File.join(dir, file_name) }.flatten.compact
555   end
556
557   ##
558   # Extract the class and method name parts from +name+ like Foo::Bar#baz
559
560   def parse_name(name)
561     parts = name.split(/(::|\#|\.)/)
562
563     if parts[-2] != '::' or parts.last !~ /^[A-Z]/ then
564       meth = parts.pop
565       parts.pop
566     end
567
568     klass = parts.join
569
570     [klass, meth]
571   end
572
573   def read_yaml(path)
574     data = File.read path
575
576     # Necessary to be backward-compatible with documentation generated
577     # by earliar RDoc versions.
578     data = data.gsub(/ \!ruby\/(object|struct):(RDoc::RI|RI).*/, '')
579     data = data.gsub(/ \!ruby\/(object|struct):SM::(\S+)/,
580                      ' !ruby/\1:RDoc::Markup::\2')
581     OpenStructHash.convert(YAML.load(data))
582   end
583
584   def run
585     if(@list_doc_dirs)
586       puts @doc_dirs.join("\n")
587     elsif @names.empty? then
588       @display.list_known_classes class_cache.keys.sort
589     else
590       @names.each do |name|
591         if class_cache.key? name then
592           method_map = display_class name
593           if(@interactive)
594             method_name = @display.get_class_method_choice(method_map)
595
596             if(method_name != nil)
597               method = lookup_method "#{name}#{method_name}", name
598               display_method method
599             end
600           end
601         elsif name =~ /::|\#|\./ then
602           klass, = parse_name name
603
604           orig_klass = klass
605           orig_name = name
606
607           loop do
608             method = lookup_method name, klass
609
610             break method if method
611
612             ancestor = lookup_ancestor klass, orig_klass
613
614             break unless ancestor
615
616             name = name.sub klass, ancestor
617             klass = ancestor
618           end
619
620           raise NotFoundError, orig_name unless method
621
622           display_method method
623         else
624           methods = select_methods(/#{name}/)
625
626           if methods.size == 0
627             raise NotFoundError, name
628           elsif methods.size == 1
629             display_method methods[0]
630           else
631             if(@interactive)
632               @display.display_method_list_choice methods
633             else
634               @display.display_method_list methods
635             end
636           end
637         end
638       end
639     end
640   rescue NotFoundError => e
641     abort e.message
642   end
643
644   def select_methods(pattern)
645     methods = []
646     class_cache.keys.sort.each do |klass|
647       class_cache[klass]["instance_methods"].map{|h|h["name"]}.grep(pattern) do |name|
648         method = load_cache_for(klass)[klass+'#'+name]
649         methods << method if method
650       end
651       class_cache[klass]["class_methods"].map{|h|h["name"]}.grep(pattern) do |name|
652         method = load_cache_for(klass)[klass+'::'+name]
653         methods << method if method
654       end
655     end
656     methods
657   end
658
659   def write_cache(cache, path)
660     if(@use_cache)
661       File.open path, "wb" do |cache_file|
662         Marshal.dump cache, cache_file
663       end
664     end
665
666     cache
667   end
668
669 end