5 require 'rdoc/ri/paths'
6 require 'rdoc/ri/formatter'
7 require 'rdoc/ri/display'
10 require 'rdoc/markup/to_flow'
12 class RDoc::RI::Driver
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.
20 class OpenStructHash < Hash
22 # This method converts from a Hash to an OpenStructHash.
24 def self.convert(object)
27 new_hash = new # Convert Hash -> OpenStructHash
29 object.each do |key, value|
30 new_hash[key] = convert(value)
35 object.map do |element|
43 def merge_enums(other)
49 if String === self[k] and self[k].empty? then
65 def method_missing method, *args
70 class Error < RDoc::RI::Error; end
72 class NotFoundError < Error
74 "Nothing known about #{super}"
78 attr_accessor :homepath # :nodoc:
80 def self.default_options
82 options[:use_stdout] = !$stdout.tty?
84 options[:formatter] = RDoc::RI::Formatter.for 'plain'
85 options[:interactive] = false
86 options[:use_cache] = true
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] = []
98 def self.process_args(argv)
99 options = default_options
101 opts = OptionParser.new do |opt|
102 opt.program_name = File.basename $0
103 opt.version = RDoc::VERSION
105 opt.summary_indent = ' ' * 4
108 RDoc::RI::Paths::SYSDIR,
109 RDoc::RI::Paths::SITEDIR,
110 RDoc::RI::Paths::HOMEDIR
113 if RDoc::RI::Paths::GEMDIRS then
114 Gem.path.each do |dir|
115 directories << "#{dir}/doc/*/ri"
120 Usage: #{opt.program_name} [options] [names...]
124 Class | Class::method | Class#method | Class.method | method
126 All class names may be abbreviated to their minimum unambiguous form. If a name
127 is ambiguous, all valid options will be listed.
129 The form '.' method matches either class or instance methods, while #method
130 matches only instance and ::method matches only class methods.
134 #{opt.program_name} Fil
135 #{opt.program_name} File
136 #{opt.program_name} File.new
137 #{opt.program_name} zip
139 Note that shell quoting may be required for method names containing
142 #{opt.program_name} 'Array.[]'
143 #{opt.program_name} compact\\!
145 By default ri searches for documentation in the following directories:
147 #{directories.join "\n "}
149 Specifying the --system, --site, --home, --gems or --doc-dir options will
150 limit ri to searching only the specified directories.
152 Options may also be set in the 'RI' environment variable.
156 opt.separator "Options:"
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
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|
177 unless File.directory? dir then
178 raise OptionParser::InvalidArgument, "#{dir} is not a directory"
181 options[:extra_doc_dirs] << File.expand_path(dir)
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
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,",
202 options[:use_system] = false
203 options[:use_site] = false
204 options[:use_gems] = false
205 options[:use_home] = false
210 opt.on("--[no-]system",
211 "Include documentation from Ruby's standard",
212 "library. Defaults to true.") do |value|
213 options[:use_system] = value
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
227 opt.on("--[no-]gems",
228 "Include documentation from RubyGems.",
229 "Defaults to true.") do |value|
230 options[:use_gems] = value
235 opt.on("--[no-]home",
236 "Include documentation stored in ~/.rdoc.",
237 "Defaults to true.") do |value|
238 options[:use_home] = value
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
251 opt.on("--no-pager", "-T",
252 "Send output directly to stdout,",
253 "rather than to a pager.") do
254 options[:use_stdout] = true
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
271 opt.on("--width=WIDTH", "-w", OptionParser::DecimalInteger,
272 "Set the width of the output.") do |value|
273 options[:width] = value
277 argv = ENV['RI'].to_s.split.concat argv
281 options[:names] = argv
283 options[:formatter] ||= RDoc::RI::Formatter.for('plain')
284 options[:use_stdout] ||= !$stdout.tty?
285 options[:use_stdout] ||= options[:interactive]
286 options[:width] ||= 72
290 rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e
297 def self.run(argv = ARGV)
298 options = process_args argv
303 def initialize(initial_options={})
304 options = self.class.default_options.update(initial_options)
306 @names = options[:names]
307 @class_cache_name = 'classes'
309 @doc_dirs = RDoc::RI::Paths.path(options[:use_system],
313 options[:extra_doc_dirs])
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]
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"
323 @use_cache = options[:use_cache]
326 @interactive = options[:interactive]
327 @display = RDoc::RI::DefaultDisplay.new(options[:formatter],
329 options[:use_stdout])
333 return @class_cache if @class_cache
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")
343 newest = map_dirs('created.rid') do |f|
344 File.mtime f if test ?f, f
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))
355 if up_to_date and @use_cache then
356 open class_cache_file_path, 'rb' do |fp|
358 @class_cache = Marshal.load fp.read
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.
366 $stderr.puts "Error reading the class cache; recreating the class cache!"
367 @class_cache = create_class_cache
371 @class_cache = create_class_cache
377 def create_class_cache
378 class_cache = OpenStructHash.new
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")
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
393 write_cache class_cache, class_cache_file_path
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"]
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
410 klass = class_cache[klassname]
413 desc["instance_method_extensions"] = desc.delete "instance_methods"
414 desc["class_method_extensions"] = desc.delete "class_methods"
417 klass.merge_enums desc
418 klass["sources"] << cdesc
423 def class_cache_file_path
424 File.join cache_file_path, @class_cache_name
427 def cache_file_for(klassname)
428 File.join cache_file_path, klassname.gsub(/:+/, "-")
432 File.join @homepath, 'cache'
435 def display_class(name)
436 klass = class_cache[name]
437 @display.display_class_info klass
440 def display_method(method)
441 @display.display_method_info method
444 def get_info_for(arg)
449 def load_cache_for(klassname)
450 path = cache_file_for klassname
454 if File.exist? path and
455 File.mtime(path) >= File.mtime(class_cache_file_path) and
457 open path, 'rb' do |fp|
459 cache = Marshal.load fp.read
462 # The cache somehow is bad. Recreate the cache.
464 $stderr.puts "Error reading the cache for #{klassname}; recreating the cache!"
465 cache = create_cache_for klassname, path
469 cache = create_cache_for klassname, path
475 def create_cache_for(klassname, path)
476 klass = class_cache[klassname]
477 return nil unless klass
479 method_files = klass["sources"]
480 cache = OpenStructHash.new
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$/
488 method = read_yaml yaml
491 method["source_path"] = "Ruby #{RDoc::RI::Paths::VERSION}"
493 if(f =~ %r%gems/[\d.]+/doc/([^/]+)%) then
494 ext_path = "gem #{$1}"
499 method["source_path"] = ext_path
502 name = method["full_name"]
507 write_cache cache, path
511 # Finds the next ancestor of +orig_klass+ after +klass+.
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")))
523 cache = class_cache[orig_klass]
525 return nil unless cache
527 ancestors = [orig_klass]
528 ancestors.push(*cache.includes.map { |inc| inc['name'] })
529 ancestors << cache.superclass
531 ancestor_index = ancestors.index(klass)
534 ancestor = ancestors[ancestors.index(klass) + 1]
535 return ancestor if ancestor
538 lookup_ancestor klass, cache.superclass
544 def lookup_method(name, klass)
545 cache = load_cache_for klass
546 return nil unless cache
548 method = cache[name.gsub('.', '#')]
549 method = cache[name.gsub('.', '::')] unless method
553 def map_dirs(file_name)
554 @doc_dirs.map { |dir| yield File.join(dir, file_name) }.flatten.compact
558 # Extract the class and method name parts from +name+ like Foo::Bar#baz
561 parts = name.split(/(::|\#|\.)/)
563 if parts[-2] != '::' or parts.last !~ /^[A-Z]/ then
574 data = File.read path
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))
586 puts @doc_dirs.join("\n")
587 elsif @names.empty? then
588 @display.list_known_classes class_cache.keys.sort
590 @names.each do |name|
591 if class_cache.key? name then
592 method_map = display_class name
594 method_name = @display.get_class_method_choice(method_map)
596 if(method_name != nil)
597 method = lookup_method "#{name}#{method_name}", name
598 display_method method
601 elsif name =~ /::|\#|\./ then
602 klass, = parse_name name
608 method = lookup_method name, klass
610 break method if method
612 ancestor = lookup_ancestor klass, orig_klass
614 break unless ancestor
616 name = name.sub klass, ancestor
620 raise NotFoundError, orig_name unless method
622 display_method method
624 methods = select_methods(/#{name}/)
627 raise NotFoundError, name
628 elsif methods.size == 1
629 display_method methods[0]
632 @display.display_method_list_choice methods
634 @display.display_method_list methods
640 rescue NotFoundError => e
644 def select_methods(pattern)
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
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
659 def write_cache(cache, path)
661 File.open path, "wb" do |cache_file|
662 Marshal.dump cache, cache_file