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 def self.convert(hash)
16 hash = new.update hash
18 hash.each do |key, value|
19 hash[key] = case value
23 value = value.map do |v|
24 ::Hash === v ? convert(v) : v
35 def method_missing method, *args
39 def merge_enums(other)
45 if String === self[k] and self[k].empty? then
62 class Error < RDoc::RI::Error; end
64 class NotFoundError < Error
66 "Nothing known about #{super}"
70 attr_accessor :homepath # :nodoc:
72 def self.process_args(argv)
74 options[:use_stdout] = !$stdout.tty?
76 options[:formatter] = RDoc::RI::Formatter.for 'plain'
77 options[:list_classes] = false
78 options[:list_names] = false
80 # By default all paths are used. If any of these are true, only those
81 # directories are used.
88 opts = OptionParser.new do |opt|
89 opt.program_name = File.basename $0
90 opt.version = RDoc::VERSION
91 opt.summary_indent = ' ' * 4
94 RDoc::RI::Paths::SYSDIR,
95 RDoc::RI::Paths::SITEDIR,
96 RDoc::RI::Paths::HOMEDIR
99 if RDoc::RI::Paths::GEMDIRS then
100 Gem.path.each do |dir|
101 directories << "#{dir}/doc/*/ri"
106 Usage: #{opt.program_name} [options] [names...]
110 Class | Class::method | Class#method | Class.method | method
112 All class names may be abbreviated to their minimum unambiguous form. If a name
113 is ambiguous, all valid options will be listed.
115 The form '.' method matches either class or instance methods, while #method
116 matches only instance and ::method matches only class methods.
120 #{opt.program_name} Fil
121 #{opt.program_name} File
122 #{opt.program_name} File.new
123 #{opt.program_name} zip
125 Note that shell quoting may be required for method names containing
128 #{opt.program_name} 'Array.[]'
129 #{opt.program_name} compact\\!
131 By default ri searches for documentation in the following directories:
133 #{directories.join "\n "}
135 Specifying the --system, --site, --home, --gems or --doc-dir options will
136 limit ri to searching only the specified directories.
138 Options may also be set in the 'RI' environment variable.
142 opt.separator "Options:"
145 opt.on("--classes", "-c",
146 "Display the names of classes and modules we",
147 "know about.") do |value|
148 options[:list_classes] = value
153 opt.on("--doc-dir=DIRNAME", "-d", Array,
154 "List of directories to search for",
155 "documentation. If not specified, we search",
156 "the standard rdoc/ri directories. May be",
157 "repeated.") do |value|
159 unless File.directory? dir then
160 raise OptionParser::InvalidArgument, "#{dir} is not a directory"
164 doc_dirs.concat value
169 opt.on("--fmt=FORMAT", "--format=FORMAT", "-f",
170 RDoc::RI::Formatter::FORMATTERS.keys,
171 "Format to use when displaying output:",
172 " #{RDoc::RI::Formatter.list}",
173 "Use 'bs' (backspace) with most pager",
174 "programs. To use ANSI, either disable the",
175 "pager or tell the pager to allow control",
176 "characters.") do |value|
177 options[:formatter] = RDoc::RI::Formatter.for value
182 unless RDoc::RI::Paths::GEMDIRS.empty? then
183 opt.on("--[no-]gems",
184 "Include documentation from RubyGems.") do |value|
191 opt.on("--[no-]home",
192 "Include documentation stored in ~/.rdoc.") do |value|
198 opt.on("--[no-]list-names", "-l",
199 "List all the names known to RDoc, one per",
201 options[:list_names] = value
206 opt.on("--no-pager", "-T",
207 "Send output directly to stdout.") do |value|
208 options[:use_stdout] = !value
213 opt.on("--[no-]site",
214 "Include documentation from libraries",
215 "installed in site_lib.") do |value|
221 opt.on("--[no-]system",
222 "Include documentation from Ruby's standard",
223 "library.") do |value|
229 opt.on("--width=WIDTH", "-w", OptionParser::DecimalInteger,
230 "Set the width of the output.") do |value|
231 options[:width] = value
235 argv = ENV['RI'].to_s.split.concat argv
239 options[:names] = argv
241 options[:path] = RDoc::RI::Paths.path(use_system, use_site, use_home,
243 options[:raw_path] = RDoc::RI::Paths.raw_path(use_system, use_site,
244 use_home, use_gems, *doc_dirs)
248 rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e
255 def self.run(argv = ARGV)
256 options = process_args argv
261 def initialize(options={})
262 options[:formatter] ||= RDoc::RI::Formatter.for('plain')
263 options[:use_stdout] ||= !$stdout.tty?
264 options[:width] ||= 72
265 @names = options[:names]
267 @class_cache_name = 'classes'
268 @all_dirs = RDoc::RI::Paths.path(true, true, true, true)
269 @homepath = RDoc::RI::Paths.raw_path(false, false, true, false).first
270 @homepath = @homepath.sub(/\.rdoc/, '.ri')
271 @sys_dirs = RDoc::RI::Paths.raw_path(true, false, false, false)
273 FileUtils.mkdir_p cache_file_path unless File.directory? cache_file_path
277 @display = RDoc::RI::DefaultDisplay.new(options[:formatter],
279 options[:use_stdout])
283 return @class_cache if @class_cache
285 newest = map_dirs('created.rid', :all) do |f|
286 File.mtime f if test ?f, f
289 up_to_date = (File.exist?(class_cache_file_path) and
290 newest and newest < File.mtime(class_cache_file_path))
292 @class_cache = if up_to_date then
293 load_cache_for @class_cache_name
295 class_cache = RDoc::RI::Driver::Hash.new
297 classes = map_dirs('**/cdesc*.yaml', :sys) { |f| Dir[f] }
298 populate_class_cache class_cache, classes
300 classes = map_dirs('**/cdesc*.yaml') { |f| Dir[f] }
301 warn "Updating class cache with #{classes.size} classes..."
303 populate_class_cache class_cache, classes, true
304 write_cache class_cache, class_cache_file_path
307 @class_cache = RDoc::RI::Driver::Hash.convert @class_cache
311 def class_cache_file_path
312 File.join cache_file_path, @class_cache_name
315 def cache_file_for(klassname)
316 File.join cache_file_path, klassname.gsub(/:+/, "-")
320 File.join @homepath, 'cache'
323 def display_class(name)
324 klass = class_cache[name]
325 klass = RDoc::RI::Driver::Hash.convert klass
326 @display.display_class_info klass, class_cache
329 def get_info_for(arg)
334 def load_cache_for(klassname)
335 path = cache_file_for klassname
339 if File.exist? path and
340 File.mtime(path) >= File.mtime(class_cache_file_path) then
341 open path, 'rb' do |fp|
342 cache = Marshal.load fp.read
347 open class_cache_file_path, 'rb' do |fp|
348 class_cache = Marshal.load fp.read
351 klass = class_cache[klassname]
352 return nil unless klass
354 method_files = klass["sources"]
355 cache = RDoc::RI::Driver::Hash.new
357 sys_dir = @sys_dirs.first
358 method_files.each do |f|
359 system_file = f.index(sys_dir) == 0
360 Dir[File.join(File.dirname(f), "*")].each do |yaml|
361 next unless yaml =~ /yaml$/
362 next if yaml =~ /cdesc-[^\/]+yaml$/
363 method = read_yaml yaml
364 name = method["full_name"]
366 ext_path = "gem #{$1}" if f =~ %r%gems/[\d.]+/doc/([^/]+)%
367 method["source_path"] = ext_path unless system_file
368 cache[name] = RDoc::RI::Driver::Hash.convert method
372 write_cache cache, path
375 RDoc::RI::Driver::Hash.convert cache
379 # Finds the next ancestor of +orig_klass+ after +klass+.
381 def lookup_ancestor(klass, orig_klass)
382 cache = class_cache[orig_klass]
384 return nil unless cache
386 ancestors = [orig_klass]
387 ancestors.push(*cache.includes.map { |inc| inc['name'] })
388 ancestors << cache.superclass
390 ancestor = ancestors[ancestors.index(klass) + 1]
392 return ancestor if ancestor
394 lookup_ancestor klass, cache.superclass
400 def lookup_method(name, klass)
401 cache = load_cache_for klass
402 return nil unless cache
404 method = cache[name.gsub('.', '#')]
405 method = cache[name.gsub('.', '::')] unless method
409 def map_dirs(file_name, system=false)
410 dirs = if system == :all then
416 @all_dirs - @sys_dirs
420 dirs.map { |dir| yield File.join(dir, file_name) }.flatten.compact
424 # Extract the class and method name parts from +name+ like Foo::Bar#baz
427 parts = name.split(/(::|\#|\.)/)
429 if parts[-2] != '::' or parts.last !~ /^[A-Z]/ then
439 def populate_class_cache(class_cache, classes, extension = false)
440 classes.each do |cdesc|
441 desc = read_yaml cdesc
442 klassname = desc["full_name"]
444 unless class_cache.has_key? klassname then
445 desc["display_name"] = "Class"
446 desc["sources"] = [cdesc]
447 desc["instance_method_extensions"] = []
448 desc["class_method_extensions"] = []
449 class_cache[klassname] = desc
451 klass = class_cache[klassname]
454 desc["instance_method_extensions"] = desc.delete "instance_methods"
455 desc["class_method_extensions"] = desc.delete "class_methods"
458 klass = RDoc::RI::Driver::Hash.convert klass
460 klass.merge_enums desc
461 klass["sources"] << cdesc
467 data = File.read path
468 data = data.gsub(/ \!ruby\/(object|struct):(RDoc::RI|RI).*/, '')
469 data = data.gsub(/ \!ruby\/(object|struct):SM::(\S+)/,
470 ' !ruby/\1:RDoc::Markup::\2')
475 if @names.empty? then
476 @display.list_known_classes class_cache.keys.sort
478 @names.each do |name|
481 if class_cache.key? name then
484 klass, = parse_name name
489 until klass == 'Kernel' do
490 method = lookup_method name, klass
492 break method if method
494 ancestor = lookup_ancestor klass, orig_klass
496 break unless ancestor
498 name = name.sub klass, ancestor
502 raise NotFoundError, orig_name unless method
504 @display.display_method_info method
507 if class_cache.key? name then
510 methods = select_methods(/^#{name}/)
513 raise NotFoundError, name
514 elsif methods.size == 1
515 @display.display_method_info methods.first
517 @display.display_method_list methods
523 rescue NotFoundError => e
527 def select_methods(pattern)
529 class_cache.keys.sort.each do |klass|
530 class_cache[klass]["instance_methods"].map{|h|h["name"]}.grep(pattern) do |name|
531 method = load_cache_for(klass)[klass+'#'+name]
532 methods << method if method
534 class_cache[klass]["class_methods"].map{|h|h["name"]}.grep(pattern) do |name|
535 method = load_cache_for(klass)[klass+'::'+name]
536 methods << method if method
542 def write_cache(cache, path)
543 File.open path, "wb" do |cache_file|
544 Marshal.dump cache, cache_file