In Files

  • rdoc/ri/driver.rb

Files

Class/Module Index [+]

Quicksearch

RDoc::RI::Driver

Public Class Methods

default_options() click to toggle source
 
               # File rdoc/ri/driver.rb, line 80
def self.default_options
  options = {}
  options[:use_stdout] = !$stdout.tty?
  options[:width] = 72
  options[:formatter] = RDoc::RI::Formatter.for 'plain'
  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
            
new(initial_options={}) click to toggle source
 
               # File rdoc/ri/driver.rb, line 303
def initialize(initial_options={})
  options = self.class.default_options.update(initial_options)

  @names = options[:names]
  @class_cache_name = 'classes'

  @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_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])
end
            
process_args(argv) click to toggle source
 
               # File rdoc/ri/driver.rb, line 98
  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 = [
        RDoc::RI::Paths::SYSDIR,
        RDoc::RI::Paths::SITEDIR,
        RDoc::RI::Paths::HOMEDIR
      ]

      if RDoc::RI::Paths::GEMDIRS then
        Gem.path.each do |dir|
          directories << "#{dir}/doc/*/ri"
        end
      end

      opt.banner = <<-EOT
Usage: #{opt.program_name} [options] [names...]

Where name can be:

  Class | Class::method | Class#method | Class.method | method

All class names may be abbreviated to their minimum unambiguous form. If a name
is ambiguous, all valid options will be listed.

The form '.' method matches either class or instance methods, while #method
matches only instance and ::method matches only class methods.

For example:

    #{opt.program_name} Fil
    #{opt.program_name} File
    #{opt.program_name} File.new
    #{opt.program_name} zip

Note that shell quoting may be required for method names containing
punctuation:

    #{opt.program_name} 'Array.[]'
    #{opt.program_name} compact\\!

By default ri searches for documentation in the following directories:

    #{directories.join "\n    "}

Specifying the --system, --site, --home, --gems or --doc-dir options will
limit ri to searching only the specified directories.

Options may also be set in the 'RI' environment variable.
      EOT

      opt.separator nil
      opt.separator "Options:"
      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
      end

      opt.separator nil

      opt.on("--doc-dir=DIRNAME", "-d", Array,
             "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

      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("--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

      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-]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-]gems",
             "Include documentation from RubyGems.",
             "Defaults to true.") do |value|
        options[:use_gems] = value
      end

      opt.separator nil

      opt.on("--[no-]home",
             "Include documentation stored in ~/.rdoc.",
             "Defaults to true.") do |value|
        options[:use_home] = value
      end

      opt.separator nil

      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-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

      opt.on("--width=WIDTH", "-w", OptionParser::DecimalInteger,
             "Set the width of the output.") do |value|
        options[:width] = value
      end
    end

    argv = ENV['RI'].to_s.split.concat argv

    opts.parse! argv

    options[:names] = argv

    options[:formatter] ||= RDoc::RI::Formatter.for('plain')
    options[:use_stdout] ||= !$stdout.tty?
    options[:use_stdout] ||= options[:interactive]
    options[:width] ||= 72

    options

  rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e
    puts opts
    puts
    puts e
    exit 1
  end
            
run(argv = ARGV) click to toggle source
 
               # File rdoc/ri/driver.rb, line 297
def self.run(argv = ARGV)
  options = process_args argv
  ri = new options
  ri.run
end
            

Public Instance Methods

cache_file_for(klassname) click to toggle source
 
               # File rdoc/ri/driver.rb, line 427
def cache_file_for(klassname)
  File.join cache_file_path, klassname.gsub(/:+/, "-")
end
            
cache_file_path() click to toggle source
 
               # File rdoc/ri/driver.rb, line 431
def cache_file_path
  File.join @homepath, 'cache'
end
            
class_cache() click to toggle source
 
               # File rdoc/ri/driver.rb, line 332
def class_cache
  return @class_cache if @class_cache

  # 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) 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_file_path() click to toggle source
 
               # File rdoc/ri/driver.rb, line 423
def class_cache_file_path
  File.join cache_file_path, @class_cache_name
end
            
create_cache_for(klassname, path) click to toggle source
 
               # File rdoc/ri/driver.rb, line 475
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 =~ %rgems/[\d.]+/doc/([^/]+)%) then
          ext_path = "gem #{$1}"
        else
          ext_path = f
        end

        method["source_path"] = ext_path
      end

      name = method["full_name"]
      cache[name] = method
    end
  end

  write_cache cache, path
end
            
create_class_cache() click to toggle source
 
               # File rdoc/ri/driver.rb, line 377
def create_class_cache
  class_cache = OpenStructHash.new

  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..."
  populate_class_cache class_cache, classes

  write_cache class_cache, class_cache_file_path

  class_cache
end
            
display_class(name) click to toggle source
 
               # File rdoc/ri/driver.rb, line 435
def display_class(name)
  klass = class_cache[name]
  @display.display_class_info klass
end
            
display_method(method) click to toggle source
 
               # File rdoc/ri/driver.rb, line 440
def display_method(method)
  @display.display_method_info method
end
            
get_info_for(arg) click to toggle source
 
               # File rdoc/ri/driver.rb, line 444
def get_info_for(arg)
  @names = [arg]
  run
end
            
load_cache_for(klassname) click to toggle source
 
               # File rdoc/ri/driver.rb, line 449
def load_cache_for(klassname)
  path = cache_file_for klassname

  cache = nil

  if File.exist? path and
     File.mtime(path) >= File.mtime(class_cache_file_path) and
     @use_cache then
    open path, 'rb' do |fp|
      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
    cache = create_cache_for klassname, path
  end

  cache
end
            
lookup_ancestor(klass, orig_klass) click to toggle source

Finds the next ancestor of orig_klass after klass.

 
               # File rdoc/ri/driver.rb, line 513
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

  ancestors = [orig_klass]
  ancestors.push(*cache.includes.map { |inc| inc['name'] })
  ancestors << cache.superclass
  
  ancestor_index = ancestors.index(klass)

  if ancestor_index
    ancestor = ancestors[ancestors.index(klass) + 1]
    return ancestor if ancestor
  end

  lookup_ancestor klass, cache.superclass
end
            
lookup_method(name, klass) click to toggle source

Finds the method

 
               # File rdoc/ri/driver.rb, line 544
def lookup_method(name, klass)
  cache = load_cache_for klass
  return nil unless cache

  method = cache[name.gsub('.', '#')]
  method = cache[name.gsub('.', '::')] unless method
  method
end
            
map_dirs(file_name) click to toggle source
 
               # File rdoc/ri/driver.rb, line 553
def map_dirs(file_name)
  @doc_dirs.map { |dir| yield File.join(dir, file_name) }.flatten.compact
end
            
parse_name(name) click to toggle source

Extract the class and method name parts from name like Foo::Bar#baz

 
               # File rdoc/ri/driver.rb, line 560
def parse_name(name)
  parts = name.split(/(::|\#|\.)/)

  if parts[-2] != '::' or parts.last !~ /^[A-Z]/ then
    meth = parts.pop
    parts.pop
  end

  klass = parts.join

  [klass, meth]
end
            
populate_class_cache(class_cache, classes, extension = false) click to toggle source
 
               # File rdoc/ri/driver.rb, line 398
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
            
read_yaml(path) click to toggle source
 
               # File rdoc/ri/driver.rb, line 573
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')
  OpenStructHash.convert(YAML.load(data))
end
            
run() click to toggle source
 
               # File rdoc/ri/driver.rb, line 584
def run
  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|
      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

        loop do
          method = lookup_method name, klass

          break method if method

          ancestor = lookup_ancestor klass, orig_klass

          break unless ancestor

          name = name.sub klass, ancestor
          klass = ancestor
        end

        raise NotFoundError, orig_name unless method

        display_method method
      else
        methods = select_methods(/#{name}/)

        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
        end
      end
    end
  end
rescue NotFoundError => e
  abort e.message
end
            
select_methods(pattern) click to toggle source
 
               # File rdoc/ri/driver.rb, line 644
def select_methods(pattern)
  methods = []
  class_cache.keys.sort.each do |klass|
    class_cache[klass]["instance_methods"].map{|h|h["name"]}.grep(pattern) do |name|
      method = load_cache_for(klass)[klass+'#'+name]
      methods << method if method
    end
    class_cache[klass]["class_methods"].map{|h|h["name"]}.grep(pattern) do |name|
      method = load_cache_for(klass)[klass+'::'+name]
      methods << method if method
    end
  end
  methods
end
            
write_cache(cache, path) click to toggle source
 
               # File rdoc/ri/driver.rb, line 659
def write_cache(cache, path)
  if(@use_cache)
    File.open path, "wb" do |cache_file|
      Marshal.dump cache, cache_file
    end
  end

  cache
end