summaryrefslogtreecommitdiff
path: root/lib/rdoc/ri/driver.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rdoc/ri/driver.rb')
-rw-r--r--lib/rdoc/ri/driver.rb522
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
-