diff options
Diffstat (limited to 'lib/rdoc/ri/driver.rb')
-rw-r--r-- | lib/rdoc/ri/driver.rb | 522 |
1 files changed, 320 insertions, 202 deletions
diff --git a/lib/rdoc/ri/driver.rb b/lib/rdoc/ri/driver.rb index dfc5f2f98a..0c91232b70 100644 --- a/lib/rdoc/ri/driver.rb +++ b/lib/rdoc/ri/driver.rb @@ -11,29 +11,33 @@ require 'rdoc/markup/to_flow' class RDoc::RI::Driver - class Hash < ::Hash - def self.convert(hash) - hash = new.update hash - - hash.each do |key, value| - hash[key] = case value - when ::Hash then - convert value - when Array then - value = value.map do |v| - ::Hash === v ? convert(v) : v - end - value - else - value - end - end - - hash - end + # + # This class offers both Hash and OpenStruct functionality. + # We convert from the Core Hash to this before calling any of + # the display methods, in order to give the display methods + # a cleaner API for accessing the data. + # + class OpenStructHash < Hash + # + # This method converts from a Hash to an OpenStructHash. + # + def self.convert(object) + case object + when Hash then + new_hash = new # Convert Hash -> OpenStructHash + + object.each do |key, value| + new_hash[key] = convert(value) + end - def method_missing method, *args - self[method.to_s] + new_hash + when Array then + object.map do |element| + convert(element) + end + else + object + end end def merge_enums(other) @@ -57,6 +61,10 @@ class RDoc::RI::Driver end end end + + def method_missing method, *args + self[method.to_s] + end end class Error < RDoc::RI::Error; end @@ -69,25 +77,31 @@ class RDoc::RI::Driver attr_accessor :homepath # :nodoc: - def self.process_args(argv) + def self.default_options options = {} options[:use_stdout] = !$stdout.tty? options[:width] = 72 options[:formatter] = RDoc::RI::Formatter.for 'plain' - options[:list_classes] = false - options[:list_names] = false + options[:interactive] = false + options[:use_cache] = true + + # By default all standard paths are used. + options[:use_system] = true + options[:use_site] = true + options[:use_home] = true + options[:use_gems] = true + options[:extra_doc_dirs] = [] + + return options + end - # By default all paths are used. If any of these are true, only those - # directories are used. - use_system = false - use_site = false - use_home = false - use_gems = false - doc_dirs = [] + def self.process_args(argv) + options = default_options opts = OptionParser.new do |opt| opt.program_name = File.basename $0 opt.version = RDoc::VERSION + opt.release = nil opt.summary_indent = ' ' * 4 directories = [ @@ -142,86 +156,114 @@ Options may also be set in the 'RI' environment variable. opt.separator "Options:" opt.separator nil - opt.on("--classes", "-c", - "Display the names of classes and modules we", - "know about.") do |value| - options[:list_classes] = value + opt.on("--fmt=FORMAT", "--format=FORMAT", "-f", + RDoc::RI::Formatter::FORMATTERS.keys, + "Format to use when displaying output:", + " #{RDoc::RI::Formatter.list}", + "Use 'bs' (backspace) with most pager", + "programs. To use ANSI, either disable the", + "pager or tell the pager to allow control", + "characters.") do |value| + options[:formatter] = RDoc::RI::Formatter.for value end opt.separator nil opt.on("--doc-dir=DIRNAME", "-d", Array, - "List of directories to search for", - "documentation. If not specified, we search", - "the standard rdoc/ri directories. May be", - "repeated.") do |value| + "List of directories from which to source", + "documentation in addition to the standard", + "directories. May be repeated.") do |value| value.each do |dir| unless File.directory? dir then raise OptionParser::InvalidArgument, "#{dir} is not a directory" end + + options[:extra_doc_dirs] << File.expand_path(dir) end + end + + opt.separator nil - doc_dirs.concat value + opt.on("--[no-]use-cache", + "Whether or not to use ri's cache.", + "True by default.") do |value| + options[:use_cache] = value end opt.separator nil - opt.on("--fmt=FORMAT", "--format=FORMAT", "-f", - RDoc::RI::Formatter::FORMATTERS.keys, - "Format to use when displaying output:", - " #{RDoc::RI::Formatter.list}", - "Use 'bs' (backspace) with most pager", - "programs. To use ANSI, either disable the", - "pager or tell the pager to allow control", - "characters.") do |value| - options[:formatter] = RDoc::RI::Formatter.for value + opt.on("--no-standard-docs", + "Do not include documentation from", + "the Ruby standard library, site_lib,", + "installed gems, or ~/.rdoc.", + "Equivalent to specifying", + "the options --no-system, --no-site, --no-gems,", + "and --no-home") do + options[:use_system] = false + options[:use_site] = false + options[:use_gems] = false + options[:use_home] = false end opt.separator nil - unless RDoc::RI::Paths::GEMDIRS.empty? then - opt.on("--[no-]gems", - "Include documentation from RubyGems.") do |value| - use_gems = value - end + opt.on("--[no-]system", + "Include documentation from Ruby's standard", + "library. Defaults to true.") do |value| + options[:use_system] = value end opt.separator nil - opt.on("--[no-]home", - "Include documentation stored in ~/.rdoc.") do |value| - use_home = value + opt.on("--[no-]site", + "Include documentation from libraries", + "installed in site_lib.", + "Defaults to true.") do |value| + options[:use_site] = value end opt.separator nil - opt.on("--[no-]list-names", "-l", - "List all the names known to RDoc, one per", - "line.") do |value| - options[:list_names] = value + opt.on("--[no-]gems", + "Include documentation from RubyGems.", + "Defaults to true.") do |value| + options[:use_gems] = value end opt.separator nil - opt.on("--no-pager", "-T", - "Send output directly to stdout.") do |value| - options[:use_stdout] = !value + opt.on("--[no-]home", + "Include documentation stored in ~/.rdoc.", + "Defaults to true.") do |value| + options[:use_home] = value end opt.separator nil - opt.on("--[no-]site", - "Include documentation from libraries", - "installed in site_lib.") do |value| - use_site = value + opt.on("--list-doc-dirs", + "List the directories from which ri will", + "source documentation on stdout and exit.") do + options[:list_doc_dirs] = true end opt.separator nil - opt.on("--[no-]system", - "Include documentation from Ruby's standard", - "library.") do |value| - use_system = value + opt.on("--no-pager", "-T", + "Send output directly to stdout,", + "rather than to a pager.") do + options[:use_stdout] = true + end + + opt.on("--interactive", "-i", + "This makes ri go into interactive mode.", + "When ri is in interactive mode it will", + "allow the user to disambiguate lists of", + "methods in case multiple methods match", + "against a method search string. It also", + "will allow the user to enter in a method", + "name (with auto-completion, if readline", + "is supported) when viewing a class.") do + options[:interactive] = true end opt.separator nil @@ -238,10 +280,10 @@ Options may also be set in the 'RI' environment variable. options[:names] = argv - options[:path] = RDoc::RI::Paths.path(use_system, use_site, use_home, - use_gems, *doc_dirs) - options[:raw_path] = RDoc::RI::Paths.raw_path(use_system, use_site, - use_home, use_gems, *doc_dirs) + options[:formatter] ||= RDoc::RI::Formatter.for('plain') + options[:use_stdout] ||= !$stdout.tty? + options[:use_stdout] ||= options[:interactive] + options[:width] ||= 72 options @@ -258,22 +300,30 @@ Options may also be set in the 'RI' environment variable. ri.run end - def initialize(options={}) - options[:formatter] ||= RDoc::RI::Formatter.for('plain') - options[:use_stdout] ||= !$stdout.tty? - options[:width] ||= 72 - @names = options[:names] + def initialize(initial_options={}) + options = self.class.default_options.update(initial_options) + @names = options[:names] @class_cache_name = 'classes' - @all_dirs = RDoc::RI::Paths.path(true, true, true, true) + + @doc_dirs = RDoc::RI::Paths.path(options[:use_system], + options[:use_site], + options[:use_home], + options[:use_gems], + options[:extra_doc_dirs]) + @homepath = RDoc::RI::Paths.raw_path(false, false, true, false).first @homepath = @homepath.sub(/\.rdoc/, '.ri') - @sys_dirs = RDoc::RI::Paths.raw_path(true, false, false, false) + @sys_dir = RDoc::RI::Paths.raw_path(true, false, false, false).first + @list_doc_dirs = options[:list_doc_dirs] FileUtils.mkdir_p cache_file_path unless File.directory? cache_file_path + @cache_doc_dirs_path = File.join cache_file_path, ".doc_dirs" + @use_cache = options[:use_cache] @class_cache = nil + @interactive = options[:interactive] @display = RDoc::RI::DefaultDisplay.new(options[:formatter], options[:width], options[:use_stdout]) @@ -282,30 +332,92 @@ Options may also be set in the 'RI' environment variable. def class_cache return @class_cache if @class_cache - newest = map_dirs('created.rid', :all) do |f| + # Get the documentation directories used to make the cache in order to see + # whether the cache is valid for the current ri instantiation. + if(File.readable?(@cache_doc_dirs_path)) + cache_doc_dirs = IO.read(@cache_doc_dirs_path).split("\n") + else + cache_doc_dirs = [] + end + + newest = map_dirs('created.rid') do |f| File.mtime f if test ?f, f end.max + # An up to date cache file must have been created more recently than + # the last modification of any of the documentation directories. It also + # must have been created with the same documentation directories + # as those from which ri currently is sourcing documentation. up_to_date = (File.exist?(class_cache_file_path) and - newest and newest < File.mtime(class_cache_file_path)) + newest and newest < File.mtime(class_cache_file_path) and + (cache_doc_dirs == @doc_dirs)) + + if up_to_date and @use_cache then + open class_cache_file_path, 'rb' do |fp| + begin + @class_cache = Marshal.load fp.read + rescue + # + # This shouldn't be necessary, since the up_to_date logic above + # should force the cache to be recreated when a new version of + # rdoc is installed. This seems like a worthwhile enhancement + # to ri's robustness, however. + # + $stderr.puts "Error reading the class cache; recreating the class cache!" + @class_cache = create_class_cache + end + end + else + @class_cache = create_class_cache + end + + @class_cache + end - @class_cache = if up_to_date then - load_cache_for @class_cache_name - else - class_cache = RDoc::RI::Driver::Hash.new + def create_class_cache + class_cache = OpenStructHash.new - classes = map_dirs('**/cdesc*.yaml', :sys) { |f| Dir[f] } - populate_class_cache class_cache, classes + if(@use_cache) + # Dump the documentation directories to a file in the cache, so that + # we only will use the cache for future instantiations with identical + # documentation directories. + File.open @cache_doc_dirs_path, "wb" do |fp| + fp << @doc_dirs.join("\n") + end + end - classes = map_dirs('**/cdesc*.yaml') { |f| Dir[f] } - warn "Updating class cache with #{classes.size} classes..." + classes = map_dirs('**/cdesc*.yaml') { |f| Dir[f] } + warn "Updating class cache with #{classes.size} classes..." + populate_class_cache class_cache, classes - populate_class_cache class_cache, classes, true - write_cache class_cache, class_cache_file_path - end + write_cache class_cache, class_cache_file_path - @class_cache = RDoc::RI::Driver::Hash.convert @class_cache - @class_cache + class_cache + end + + def populate_class_cache(class_cache, classes, extension = false) + classes.each do |cdesc| + desc = read_yaml cdesc + klassname = desc["full_name"] + + unless class_cache.has_key? klassname then + desc["display_name"] = "Class" + desc["sources"] = [cdesc] + desc["instance_method_extensions"] = [] + desc["class_method_extensions"] = [] + class_cache[klassname] = desc + else + klass = class_cache[klassname] + + if extension then + desc["instance_method_extensions"] = desc.delete "instance_methods" + desc["class_method_extensions"] = desc.delete "class_methods" + end + + klass.merge_enums desc + klass["sources"] << cdesc + end + end end def class_cache_file_path @@ -322,8 +434,11 @@ Options may also be set in the 'RI' environment variable. def display_class(name) klass = class_cache[name] - klass = RDoc::RI::Driver::Hash.convert klass - @display.display_class_info klass, class_cache + @display.display_class_info klass + end + + def display_method(method) + @display.display_method_info method end def get_info_for(arg) @@ -337,48 +452,74 @@ Options may also be set in the 'RI' environment variable. cache = nil if File.exist? path and - File.mtime(path) >= File.mtime(class_cache_file_path) then + File.mtime(path) >= File.mtime(class_cache_file_path) and + @use_cache then open path, 'rb' do |fp| - cache = Marshal.load fp.read + begin + cache = Marshal.load fp.read + rescue + # + # The cache somehow is bad. Recreate the cache. + # + $stderr.puts "Error reading the cache for #{klassname}; recreating the cache!" + cache = create_cache_for klassname, path + end end else - class_cache = nil + cache = create_cache_for klassname, path + end - open class_cache_file_path, 'rb' do |fp| - class_cache = Marshal.load fp.read - end + cache + end - klass = class_cache[klassname] - return nil unless klass - - method_files = klass["sources"] - cache = RDoc::RI::Driver::Hash.new - - sys_dir = @sys_dirs.first - method_files.each do |f| - system_file = f.index(sys_dir) == 0 - Dir[File.join(File.dirname(f), "*")].each do |yaml| - next unless yaml =~ /yaml$/ - next if yaml =~ /cdesc-[^\/]+yaml$/ - method = read_yaml yaml - name = method["full_name"] - ext_path = f - ext_path = "gem #{$1}" if f =~ %r%gems/[\d.]+/doc/([^/]+)% - method["source_path"] = ext_path unless system_file - cache[name] = RDoc::RI::Driver::Hash.convert method + def create_cache_for(klassname, path) + klass = class_cache[klassname] + return nil unless klass + + method_files = klass["sources"] + cache = OpenStructHash.new + + method_files.each do |f| + system_file = f.index(@sys_dir) == 0 + Dir[File.join(File.dirname(f), "*")].each do |yaml| + next unless yaml =~ /yaml$/ + next if yaml =~ /cdesc-[^\/]+yaml$/ + + method = read_yaml yaml + + if system_file then + method["source_path"] = "Ruby #{RDoc::RI::Paths::VERSION}" + else + if(f =~ %r%gems/[\d.]+/doc/([^/]+)%) then + ext_path = "gem #{$1}" + else + ext_path = f + end + + method["source_path"] = ext_path end - end - write_cache cache, path + name = method["full_name"] + cache[name] = method + end end - RDoc::RI::Driver::Hash.convert cache + write_cache cache, path end ## # Finds the next ancestor of +orig_klass+ after +klass+. def lookup_ancestor(klass, orig_klass) + # This is a bit hacky, but ri will go into an infinite + # loop otherwise, since Object has an Object ancestor + # for some reason. Depending on the documentation state, I've seen + # Kernel as an ancestor of Object and not as an ancestor of Object. + if ((orig_klass == "Object") && + ((klass == "Kernel") || (klass == "Object"))) + return nil + end + cache = class_cache[orig_klass] return nil unless cache @@ -386,10 +527,13 @@ Options may also be set in the 'RI' environment variable. ancestors = [orig_klass] ancestors.push(*cache.includes.map { |inc| inc['name'] }) ancestors << cache.superclass + + ancestor_index = ancestors.index(klass) - ancestor = ancestors[ancestors.index(klass) + 1] - - return ancestor if ancestor + if ancestor_index + ancestor = ancestors[ancestors.index(klass) + 1] + return ancestor if ancestor + end lookup_ancestor klass, cache.superclass end @@ -406,18 +550,8 @@ Options may also be set in the 'RI' environment variable. method end - def map_dirs(file_name, system=false) - dirs = if system == :all then - @all_dirs - else - if system then - @sys_dirs - else - @all_dirs - @sys_dirs - end - end - - dirs.map { |dir| yield File.join(dir, file_name) }.flatten.compact + def map_dirs(file_name) + @doc_dirs.map { |dir| yield File.join(dir, file_name) }.flatten.compact end ## @@ -436,83 +570,66 @@ Options may also be set in the 'RI' environment variable. [klass, meth] end - def populate_class_cache(class_cache, classes, extension = false) - classes.each do |cdesc| - desc = read_yaml cdesc - klassname = desc["full_name"] - - unless class_cache.has_key? klassname then - desc["display_name"] = "Class" - desc["sources"] = [cdesc] - desc["instance_method_extensions"] = [] - desc["class_method_extensions"] = [] - class_cache[klassname] = desc - else - klass = class_cache[klassname] - - if extension then - desc["instance_method_extensions"] = desc.delete "instance_methods" - desc["class_method_extensions"] = desc.delete "class_methods" - end - - klass = RDoc::RI::Driver::Hash.convert klass - - klass.merge_enums desc - klass["sources"] << cdesc - end - end - end - def read_yaml(path) data = File.read path + + # Necessary to be backward-compatible with documentation generated + # by earliar RDoc versions. data = data.gsub(/ \!ruby\/(object|struct):(RDoc::RI|RI).*/, '') data = data.gsub(/ \!ruby\/(object|struct):SM::(\S+)/, ' !ruby/\1:RDoc::Markup::\2') - YAML.load data + OpenStructHash.convert(YAML.load(data)) end def run - if @names.empty? then + if(@list_doc_dirs) + puts @doc_dirs.join("\n") + elsif @names.empty? then @display.list_known_classes class_cache.keys.sort else @names.each do |name| - case name - when /::|\#|\./ then - if class_cache.key? name then - display_class name - else - klass, = parse_name name + if class_cache.key? name then + method_map = display_class name + if(@interactive) + method_name = @display.get_class_method_choice(method_map) + + if(method_name != nil) + method = lookup_method "#{name}#{method_name}", name + display_method method + end + end + elsif name =~ /::|\#|\./ then + klass, = parse_name name - orig_klass = klass - orig_name = name + orig_klass = klass + orig_name = name - until klass == 'Kernel' do - method = lookup_method name, klass + loop do + method = lookup_method name, klass - break method if method + break method if method - ancestor = lookup_ancestor klass, orig_klass + ancestor = lookup_ancestor klass, orig_klass - break unless ancestor + break unless ancestor - name = name.sub klass, ancestor - klass = ancestor - end + name = name.sub klass, ancestor + klass = ancestor + end - raise NotFoundError, orig_name unless method + raise NotFoundError, orig_name unless method - @display.display_method_info method - end + display_method method else - if class_cache.key? name then - display_class name - else - methods = select_methods(/^#{name}/) + methods = select_methods(/#{name}/) - if methods.size == 0 - raise NotFoundError, name - elsif methods.size == 1 - @display.display_method_info methods.first + if methods.size == 0 + raise NotFoundError, name + elsif methods.size == 1 + display_method methods[0] + else + if(@interactive) + @display.display_method_list_choice methods else @display.display_method_list methods end @@ -540,12 +657,13 @@ Options may also be set in the 'RI' environment variable. end def write_cache(cache, path) - File.open path, "wb" do |cache_file| - Marshal.dump cache, cache_file + if(@use_cache) + File.open path, "wb" do |cache_file| + Marshal.dump cache, cache_file + end end cache end end - |