class RDoc::RI::Driver

def self.default_options

def self.default_options
  options = {}
  options[:interactive] = false
  options[:profile]     = false
  options[:show_all]    = false
  options[:expand_refs] = true
  options[:use_stdout]  = !$stdout.tty?
  options[:width]       = 72
  # 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

def self.dump(data_path)

def self.dump(data_path)
  require 'pp'
  File.open data_path, 'rb' do |io|
    pp Marshal.load(io.read)
  end
end

def self.process_args(argv)

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
    opt.banner = <<-EOT
age: #{opt.program_name} [options] [name ...]
ere name can be:
Class | Module | Module::Class
Class::method | Class#method | Class.method | method
gem_name: | gem_name:README | gem_name:History
ruby: | ruby:NEWS | ruby:globals
l class names may be abbreviated to their minimum unambiguous form.
 a name is ambiguous, all valid options will be listed.
'.' matches either class or instance methods, while #method
tches only instance and ::method matches only class methods.
ADME and other files may be displayed by prefixing them with the gem name
ey're contained in.  If the gem name is followed by a ':' all files in the
m will be shown.  The file name extension may be omitted where it is
ambiguous.
uby' can be used as a pseudo gem name to display files from the Ruby
re documentation. Use 'ruby:' by itself to get a list of all available
re documentation files.
r example:
  #{opt.program_name} Fil
  #{opt.program_name} File
  #{opt.program_name} File.new
  #{opt.program_name} zip
  #{opt.program_name} rdoc:README
  #{opt.program_name} ruby:comments
te that shell quoting or escaping may be required for method names
ntaining punctuation:
  #{opt.program_name} 'Array.[]'
  #{opt.program_name} compact\\!
 see the default directories #{opt.program_name} will search, run:
  #{opt.program_name} --list-doc-dirs
ecifying the --system, --site, --home, --gems, or --doc-dir options
ll limit ri to searching only the specified directories.
 options may be set in the RI environment variable.
e ri pager can be set with the RI_PAGER environment variable
 the PAGER environment variable.
    EOT
    opt.separator nil
    opt.separator "Options:"
    opt.separator nil
    opt.on("--[no-]interactive", "-i",
           "In interactive mode you can repeatedly",
           "look up methods with autocomplete.") do |interactive|
      options[:interactive] = interactive
    end
    opt.separator nil
    opt.on("--[no-]all", "-a",
           "Show all documentation for a class or",
           "module.") do |show_all|
      options[:show_all] = show_all
    end
    opt.separator nil
    opt.on("--[no-]list", "-l",
           "List classes ri knows about.") do |list|
      options[:list] = list
    end
    opt.separator nil
    opt.on("--[no-]pager",
           "Send output to a pager,",
           "rather than directly to stdout.") do |use_pager|
      options[:use_stdout] = !use_pager
    end
    opt.separator nil
    opt.on("-T",
           "Synonym for --no-pager.") do
      options[:use_stdout] = true
    end
    opt.separator nil
    opt.on("--width=WIDTH", "-w", OptionParser::DecimalInteger,
           "Set the width of the output.") do |width|
      options[:width] = width
    end
    opt.separator nil
    opt.on("--server[=PORT]", Integer,
           "Run RDoc server on the given port.",
           "The default port is 8214.") do |port|
      options[:server] = port || 8214
    end
    opt.separator nil
    formatters = RDoc::Markup.constants.grep(/^To[A-Z][a-z]+$/).sort
    formatters = formatters.sort.map do |formatter|
      formatter.to_s.sub('To', '').downcase
    end
    formatters -= %w[html label test] # remove useless output formats
    opt.on("--format=NAME", "-f",
           "Use the selected formatter.  The default",
           "formatter is bs for paged output and ansi",
           "otherwise.  Valid formatters are:",
           "#{formatters.join(', ')}.", formatters) do |value|
      options[:formatter] = RDoc::Markup.const_get "To#{value.capitalize}"
    end
    opt.separator nil
    opt.on("--[no-]expand-refs", "Expand rdoc-refs at the end of output") do |value|
      options[:expand_refs] = value
    end
    opt.separator nil
    opt.on("--help", "-h",
           "Show help and exit.") do
      puts opts
      exit
    end
    opt.separator nil
    opt.on("--version", "-v",
           "Output version information and exit.") do
      puts "#{opts.program_name} #{opts.version}"
      exit
    end
    opt.separator nil
    opt.separator "Data source options:"
    opt.separator nil
    opt.on("--[no-]list-doc-dirs",
           "List the directories from which ri will",
           "source documentation on stdout and exit.") do |list_doc_dirs|
      options[:list_doc_dirs] = list_doc_dirs
    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-standard-docs",
           "Do not include documentation from",
           "the Ruby standard library, site_lib,",
           "installed gems, or ~/.rdoc.",
           "Use with --doc-dir.") 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.separator "Debug options:"
    opt.separator nil
    opt.on("--[no-]profile",
           "Run with the ruby profiler.") do |value|
      options[:profile] = value
    end
    opt.separator nil
    opt.on("--dump=CACHE",
           "Dump data from an ri cache or data file.") do |value|
      unless File.readable?(value)
        abort "#{value.inspect} is not readable"
      end
      if File.directory?(value)
        abort "#{value.inspect} is a directory"
      end
      options[:dump_path] = File.new(value)
    end
  end
  argv = ENV['RI'].to_s.split(' ').concat argv
  opts.parse! argv
  options[:names] = argv
  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

def self.run(argv = ARGV)

def self.run(argv = ARGV)
  options = process_args argv
  if options[:dump_path] then
    dump options[:dump_path]
    return
  end
  ri = new options
  ri.run
end

def add_also_in(out, also_in)

def add_also_in(out, also_in)
  return if also_in.empty?
  out << RDoc::Markup::Rule.new(1)
  out << RDoc::Markup::Paragraph.new("Also found in:")
  paths = RDoc::Markup::Verbatim.new
  also_in.each do |store|
    paths.parts.push store.friendly_path, "\n"
  end
  out << paths
end

def add_class(out, name, classes)

def add_class(out, name, classes)
  heading = if classes.all? { |klass| klass.module? } then
              name
            else
              superclass = classes.map do |klass|
                klass.superclass unless klass.module?
              end.compact.shift || 'Object'
              superclass = superclass.full_name unless String === superclass
              "#{name} < #{superclass}"
            end
  out << RDoc::Markup::Heading.new(1, heading)
  out << RDoc::Markup::BlankLine.new
end

def add_extends(out, extends)

def add_extends(out, extends)
  add_extension_modules out, 'Extended by', extends
end

def add_extension_modules(out, type, extensions)

def add_extension_modules(out, type, extensions)
  return if extensions.empty?
  out << RDoc::Markup::Rule.new(1)
  out << RDoc::Markup::Heading.new(1, "#{type}:")
  extensions.each do |modules, store|
    if modules.length == 1 then
      add_extension_modules_single out, store, modules.first
    else
      add_extension_modules_multiple out, store, modules
    end
  end
end

def add_extension_modules_multiple(out, store, modules) # :nodoc:

:nodoc:
def add_extension_modules_multiple(out, store, modules) # :nodoc:
  out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})")
  wout, with = modules.partition { |incl| incl.comment.empty? }
  out << RDoc::Markup::BlankLine.new unless with.empty?
  with.each do |incl|
    out << RDoc::Markup::Paragraph.new(incl.name)
    out << RDoc::Markup::BlankLine.new
    out << incl.comment.parse
  end
  unless wout.empty? then
    verb = RDoc::Markup::Verbatim.new
    wout.each do |incl|
      verb.push incl.name, "\n"
    end
    out << verb
  end
end

def add_extension_modules_single(out, store, include) # :nodoc:

:nodoc:
def add_extension_modules_single(out, store, include) # :nodoc:
  name = include.name
  path = store.friendly_path
  out << RDoc::Markup::Paragraph.new("#{name} (from #{path})")
  if include.comment then
    out << RDoc::Markup::BlankLine.new
    out << include.comment.parse
  end
end

def add_from(out, store)

def add_from(out, store)
  out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})")
end

def add_includes(out, includes)

def add_includes(out, includes)
  add_extension_modules out, 'Includes', includes
end

def add_method(out, name)

def add_method(out, name)
  filtered = lookup_method name
  method_document out, name, filtered
end

def add_method_documentation(out, klass)

def add_method_documentation(out, klass)
  klass.method_list.each do |method|
    begin
      add_method out, method.full_name
    rescue NotFoundError
      next
    end
  end
end

def add_method_list(out, methods, name)

def add_method_list(out, methods, name)
  return if methods.empty?
  out << RDoc::Markup::Heading.new(1, "#{name}:")
  out << RDoc::Markup::BlankLine.new
  if @use_stdout and !@interactive then
    out.concat methods.map { |method|
      RDoc::Markup::Verbatim.new method
    }
  else
    out << RDoc::Markup::IndentedParagraph.new(2, methods.join(', '))
  end
  out << RDoc::Markup::BlankLine.new
end

def ancestors_of(klass)

def ancestors_of(klass)
  ancestors = []
  unexamined = [klass]
  seen = []
  loop do
    break if unexamined.empty?
    current = unexamined.shift
    seen << current
    stores = classes[current]
    next unless stores and not stores.empty?
    klasses = stores.flat_map do |store|
      store.ancestors[current] || []
    end.uniq
    klasses = klasses - seen
    ancestors.concat klasses
    unexamined.concat klasses
  end
  ancestors.reverse
end

def check_did_you_mean # :nodoc:

:nodoc:
def check_did_you_mean # :nodoc:
  if defined? DidYouMean::SpellChecker
    true
  else
    begin
      require 'did_you_mean'
      if defined? DidYouMean::SpellChecker
        true
      else
        false
      end
    rescue LoadError
      false
    end
  end
end

def class_cache # :nodoc:

:nodoc:
def class_cache # :nodoc:
end

def class_document(name, found, klasses, includes, extends)

def class_document(name, found, klasses, includes, extends)
  also_in = []
  out = RDoc::Markup::Document.new
  add_class out, name, klasses
  add_includes out, includes
  add_extends  out, extends
  found.each do |store, klass|
    render_class out, store, klass, also_in
  end
  add_also_in out, also_in
  expand_rdoc_refs_at_the_bottom(out)
  out
end

def class_document_comment(out, document) # :nodoc:

:nodoc:
def class_document_comment(out, document) # :nodoc:
  unless document.empty? then
    out << RDoc::Markup::Rule.new(1)
    if document.merged? then
      parts = document.parts
      parts = parts.zip [RDoc::Markup::BlankLine.new] * parts.length
      parts.flatten!
      parts.pop
      out.concat parts
    else
      out << comment
    end
  end
end

def class_document_constants(out, klass) # :nodoc:

:nodoc:
def class_document_constants(out, klass) # :nodoc:
  return if klass.constants.empty?
  out << RDoc::Markup::Heading.new(1, "Constants:")
  out << RDoc::Markup::BlankLine.new
  list = RDoc::Markup::List.new :NOTE
  constants = klass.constants.sort_by { |constant| constant.name }
  list.items.concat constants.map { |constant|
    parts = constant.comment.parse.parts
    parts << RDoc::Markup::Paragraph.new('[not documented]') if
      parts.empty?
    RDoc::Markup::ListItem.new(constant.name, *parts)
  }
  out << list
  out << RDoc::Markup::BlankLine.new
end

def classes

def classes
  return @classes if @classes
  @classes = {}
  @stores.each do |store|
    store.cache[:modules].each do |mod|
      # using default block causes searched-for modules to be added
      @classes[mod] ||= []
      @classes[mod] << store
    end
  end
  @classes
end

def classes_and_includes_and_extends_for(name)

def classes_and_includes_and_extends_for(name)
  klasses = []
  extends = []
  includes = []
  found = @stores.map do |store|
    begin
      klass = store.load_class name
      klasses  << klass
      extends  << [klass.extends,  store] if klass.extends
      includes << [klass.includes, store] if klass.includes
      [store, klass]
    rescue RDoc::Store::MissingFileError
    end
  end.compact
  extends.reject!  do |modules,| modules.empty? end
  includes.reject! do |modules,| modules.empty? end
  [found, klasses, includes, extends]
end

def complete(name)

def complete(name)
  completions = []
  klass, selector, method = parse_name name
  complete_klass  name, klass, selector, method, completions
  complete_method name, klass, selector,         completions
  completions.uniq.select {|s| s.start_with? name }.sort
end

def complete_klass(name, klass, selector, method, completions) # :nodoc:

:nodoc:
def complete_klass(name, klass, selector, method, completions) # :nodoc:
  klasses = classes.keys
  # may need to include Foo when given Foo::
  klass_name = method ? name : klass
  if name !~ /#|\./ then
    completions.replace klasses.grep(/^#{Regexp.escape klass_name}[^:]*$/)
    completions.concat klasses.grep(/^#{Regexp.escape name}[^:]*$/) if
      name =~ /::$/
    completions << klass if classes.key? klass # to complete a method name
  elsif selector then
    completions << klass if classes.key? klass
  elsif classes.key? klass_name then
    completions << klass_name
  end
end

def complete_method(name, klass, selector, completions) # :nodoc:

:nodoc:
def complete_method(name, klass, selector, completions) # :nodoc:
  if completions.include? klass and name =~ /#|\.|::/ then
    methods = list_methods_matching name
    if not methods.empty? then
      # remove Foo if given Foo:: and a method was found
      completions.delete klass
    elsif selector then
      # replace Foo with Foo:: as given
      completions.delete klass
      completions << "#{klass}#{selector}"
    end
    methods.each do |klass_sel_method|
      match = klass_sel_method.match(/^(.+)(#|\.|::)([^#.:]+)$/)
      # match[2] is `::` for class method and `#` for instance method.
      # To be consistent with old completion that completes `['Foo#i', 'Foo::c']` for `Foo.`,
      # `.` should be a wildcard for both `#` and `::` here.
      if match && match[2] == selector || selector == '.'
        completions << match[1] + selector + match[3]
      end
    end
  end
end

def display(document)

def display(document)
  page do |io|
    f = formatter(io)
    f.width = @width if @width and f.respond_to?(:width)
    text = document.accept f
    io.write text
  end
end

def display_class(name)

def display_class(name)
  return if name =~ /#|\./
  found, klasses, includes, extends =
    classes_and_includes_and_extends_for name
  return if found.empty?
  out = class_document name, found, klasses, includes, extends
  display out
end

def display_method(name)

def display_method(name)
  out = RDoc::Markup::Document.new
  add_method out, name
  expand_rdoc_refs_at_the_bottom(out)
  display out
end

def display_name(name)

def display_name(name)
  if name =~ /\w:(\w|$)/ then
    display_page name
    return true
  end
  return true if display_class name
  display_method name if name =~ /::|#|\./
  true
rescue NotFoundError
  matches = list_methods_matching name if name =~ /::|#|\./
  matches = classes.keys.grep(/^#{Regexp.escape name}/) if matches.empty?
  raise if matches.empty?
  page do |io|
    io.puts "#{name} not found, maybe you meant:"
    io.puts
    io.puts matches.sort.join("\n")
  end
  false
end

def display_names(names)

def display_names(names)
  names.each do |name|
    name = expand_name name
    display_name name
  end
end

def display_page(name)

def display_page(name)
  store_name, page_name = name.split ':', 2
  store = @stores.find { |s| s.source == store_name }
  return display_page_list store if page_name.empty?
  pages = store.cache[:pages]
  unless pages.include? page_name then
    found_names = pages.select do |n|
      n =~ /#{Regexp.escape page_name}\.[^.]+$/
    end
    if found_names.length.zero? then
      return display_page_list store, pages
    elsif found_names.length > 1 then
      return display_page_list store, found_names, page_name
    end
    page_name = found_names.first
  end
  page = store.load_page page_name
  display page.comment.parse
end

def display_page_list(store, pages = store.cache[:pages], search = nil)

def display_page_list(store, pages = store.cache[:pages], search = nil)
  out = RDoc::Markup::Document.new
  title = if search then
            "#{search} pages"
          else
            'Pages'
          end
  out << RDoc::Markup::Heading.new(1, "#{title} in #{store.friendly_path}")
  out << RDoc::Markup::BlankLine.new
  list = RDoc::Markup::List.new(:BULLET)
  pages.each do |page|
    list << RDoc::Markup::Paragraph.new(page)
  end
  out << list
  display out
end

def expand_class(klass)

def expand_class(klass)
  class_names = classes.keys
  ary = class_names.grep(Regexp.new("\\A#{klass.gsub(/(?=::|\z)/, '[^:]*')}\\z"))
  if ary.length != 1 && ary.first != klass
    if check_did_you_mean
      suggestion_proc = -> { DidYouMean::SpellChecker.new(dictionary: class_names).correct(klass) }
      raise NotFoundError.new(klass, suggestion_proc)
    else
      raise NotFoundError, klass
    end
  end
  ary.first
end

def expand_name(name)

def expand_name(name)
  klass, selector, method = parse_name name
  return [selector, method].join if klass.empty?
  case selector
  when ':' then
    [find_store(klass),   selector, method]
  else
    [expand_class(klass), selector, method]
  end.join
end

def expand_rdoc_refs_at_the_bottom(out)

def expand_rdoc_refs_at_the_bottom(out)
  return unless @expand_refs
  extracted_rdoc_refs = []
  out.each do |part|
    content = if part.respond_to?(:text)
      part.text
    else
      next
    end
    rdoc_refs = content.scan(RDOC_REFS_REGEXP).uniq.map do |file_name, _anchor|
      file_name
    end
    extracted_rdoc_refs.concat(rdoc_refs)
  end
  found_pages = extracted_rdoc_refs.map do |ref|
    begin
      @stores.first.load_page(ref)
    rescue RDoc::Store::MissingFileError
    end
  end.compact
  found_pages.each do |page|
    out << RDoc::Markup::Heading.new(4, "Expanded from #{page.full_name}")
    out << RDoc::Markup::BlankLine.new
    out << page.comment.parse
  end
end

def filter_methods(found, name)

def filter_methods(found, name)
  regexp = name_regexp name
  filtered = found.find_all do |store, methods|
    methods.any? { |method| method.full_name =~ regexp }
  end
  return filtered unless filtered.empty?
  found
end

def find_methods(name)

def find_methods(name)
  klass, selector, method = parse_name name
  types = method_type selector
  klasses = nil
  ambiguous = klass.empty?
  if ambiguous then
    klasses = classes.keys
  else
    klasses = ancestors_of klass
    klasses.unshift klass
  end
  methods = []
  klasses.each do |ancestor|
    ancestors = classes[ancestor]
    next unless ancestors
    klass = ancestor if ambiguous
    ancestors.each do |store|
      methods << [store, klass, ancestor, types, method]
    end
  end
  methods = methods.sort_by do |_, k, a, _, m|
    [k, a, m].compact
  end
  methods.each do |item|
    yield(*item) # :yields: store, klass, ancestor, types, method
  end
  self
end

def find_store(name)

def find_store(name)
  @stores.each do |store|
    source = store.source
    return source if source == name
    return source if
      store.type == :gem and source =~ /^#{Regexp.escape name}-\d/
  end
  raise RDoc::RI::Driver::NotFoundError, name
end

def formatter(io)

def formatter(io)
  if @formatter_klass then
    @formatter_klass.new
  elsif paging? or !io.tty? then
    RDoc::Markup::ToBs.new
  else
    RDoc::Markup::ToAnsi.new
  end
end

def initialize(initial_options = {})

def initialize(initial_options = {})
  @paging = false
  @classes = nil
  options = self.class.default_options.update(initial_options)
  @formatter_klass = options[:formatter]
  require 'profile' if options[:profile]
  @names = options[:names]
  @list = options[:list]
  @doc_dirs = []
  @stores   = []
  RDoc::RI::Paths.each(options[:use_system], options[:use_site],
                       options[:use_home], options[:use_gems],
                       *options[:extra_doc_dirs]) do |path, type|
    @doc_dirs << path
    store = RDoc::RI::Store.new(RDoc::Options.new, path: path, type: type)
    store.load_cache
    @stores << store
  end
  @list_doc_dirs = options[:list_doc_dirs]
  @interactive = options[:interactive]
  @server      = options[:server]
  @use_stdout  = options[:use_stdout]
  @show_all    = options[:show_all]
  @width       = options[:width]
  @expand_refs = options[:expand_refs]
end

def interactive

def interactive
  puts "\nEnter the method name you want to look up."
  begin
    require 'readline'
  rescue LoadError
  end
  if defined? Readline then
    Readline.completion_proc = method :complete
    puts "You can use tab to autocomplete."
  end
  puts "Enter a blank line to exit.\n\n"
  loop do
    name = if defined? Readline then
             Readline.readline ">> ", true
           else
             print ">> "
             $stdin.gets
           end
    return if name.nil? or name.empty?
    begin
      display_name expand_name(name.strip)
    rescue NotFoundError => e
      puts e.message
    end
  end
rescue Interrupt
  exit
end

def list_known_classes(names = [])

def list_known_classes(names = [])
  classes = []
  stores.each do |store|
    classes << store.module_names
  end
  classes = classes.flatten.uniq.sort
  unless names.empty? then
    filter = Regexp.union names.map { |name| /^#{name}/ }
    classes = classes.grep filter
  end
  page do |io|
    if paging? or io.tty? then
      if names.empty? then
        io.puts "Classes and Modules known to ri:"
      else
        io.puts "Classes and Modules starting with #{names.join ', '}:"
      end
      io.puts
    end
    io.puts classes.join("\n")
  end
end

def list_methods_matching(name)

def list_methods_matching(name)
  found = []
  find_methods name do |store, klass, ancestor, types, method|
    if types == :instance or types == :both then
      methods = store.instance_methods[ancestor]
      if methods then
        matches = methods.grep(/^#{Regexp.escape method.to_s}/)
        matches = matches.map do |match|
          "#{klass}##{match}"
        end
        found.concat matches
      end
    end
    if types == :class or types == :both then
      methods = store.class_methods[ancestor]
      next unless methods
      matches = methods.grep(/^#{Regexp.escape method.to_s}/)
      matches = matches.map do |match|
        "#{klass}::#{match}"
      end
      found.concat matches
    end
  end
  found.uniq
end

def load_method(store, cache, klass, type, name)

def load_method(store, cache, klass, type, name)
  methods = store.public_send(cache)[klass]
  return unless methods
  method = methods.find do |method_name|
    method_name == name
  end
  return unless method
  store.load_method klass, "#{type}#{method}"
rescue RDoc::Store::MissingFileError => e
  comment = RDoc::Comment.new("missing documentation at #{e.file}")
  comment.parse
  method = RDoc::AnyMethod.new nil, name
  method.comment = comment
  method
end

def load_methods_matching(name)

def load_methods_matching(name)
  found = []
  find_methods name do |store, klass, ancestor, types, method|
    methods = []
    methods << load_method(store, :class_methods, ancestor, '::',  method) if
      [:class, :both].include? types
    methods << load_method(store, :instance_methods, ancestor, '#',  method) if
      [:instance, :both].include? types
    found << [store, methods.compact]
  end
  found.reject do |path, methods| methods.empty? end
end

def lookup_method(name)

def lookup_method(name)
  found = load_methods_matching name
  if found.empty?
    if check_did_you_mean
      methods = []
      _, _, method_name = parse_name name
      find_methods name do |store, klass, ancestor, types, method|
        methods.push(*store.class_methods[klass]) if [:class, :both].include? types
        methods.push(*store.instance_methods[klass]) if [:instance, :both].include? types
      end
      methods = methods.uniq
      suggestion_proc = -> { DidYouMean::SpellChecker.new(dictionary: methods).correct(method_name) }
      raise NotFoundError.new(name, suggestion_proc)
    else
      raise NotFoundError, name
    end
  end
  filter_methods found, name
end

def method_document(out, name, filtered)

def method_document(out, name, filtered)
  out << RDoc::Markup::Heading.new(1, name)
  out << RDoc::Markup::BlankLine.new
  filtered.each do |store, methods|
    methods.each do |method|
      render_method out, store, method, name
    end
  end
  out
end

def method_type(selector)

def method_type(selector)
  case selector
  when '.', nil then :both
  when '#'      then :instance
  else               :class
  end
end

def name_regexp(name)

def name_regexp(name)
  klass, type, name = parse_name name
  case type
  when '#', '::' then
    /^#{klass}#{type}#{Regexp.escape name}$/
  else
    /^#{klass}(#|::)#{Regexp.escape name}$/
  end
end

def page

def page
  if pager = setup_pager then
    begin
      yield pager
    ensure
      pager.close
    end
  else
    yield $stdout
  end
rescue Errno::EPIPE
ensure
  @paging = false
end

def paging?

def paging?
  @paging
end

def parse_name(name)

def parse_name(name)
  parts = name.split(/(::?|#|\.)/)
  if parts.length == 1 then
    if parts.first =~ /^[a-z]|^([%&*+\/<>^`|~-]|\+@|-@|<<|<=>?|===?|=>|=~|>>|\[\]=?|~@)$/ then
      type = '.'
      meth = parts.pop
    else
      type = nil
      meth = nil
    end
  elsif parts.length == 2 or parts.last =~ /::|#|\./ then
    type = parts.pop
    meth = nil
  elsif parts[1] == ':' then
    klass = parts.shift
    type  = parts.shift
    meth  = parts.join
  elsif parts[-2] != '::' or parts.last !~ /^[A-Z]/ then
    meth = parts.pop
    type = parts.pop
  end
  klass ||= parts.join
  [klass, type, meth]
end

def render_class(out, store, klass, also_in) # :nodoc:

:nodoc:
def render_class(out, store, klass, also_in) # :nodoc:
  document = klass.comment.parse
  # TODO the store's cache should always return an empty Array
  class_methods    = store.class_methods[klass.full_name]    || []
  instance_methods = store.instance_methods[klass.full_name] || []
  attributes       = store.attributes[klass.full_name]       || []
  if document.empty? and
     instance_methods.empty? and class_methods.empty? then
    also_in << store
    return
  end
  add_from out, store
  class_document_comment out, document
  if class_methods or instance_methods or not klass.constants.empty? then
    out << RDoc::Markup::Rule.new(1)
  end
  class_document_constants out, klass
  add_method_list out, class_methods,    'Class methods'
  add_method_list out, instance_methods, 'Instance methods'
  add_method_list out, attributes,       'Attributes'
  add_method_documentation out, klass if @show_all
end

def render_method(out, store, method, name) # :nodoc:

:nodoc:
def render_method(out, store, method, name) # :nodoc:
  out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})")
  unless name =~ /^#{Regexp.escape method.parent_name}/ then
    out << RDoc::Markup::Heading.new(3, "Implementation from #{method.parent_name}")
  end
  out << RDoc::Markup::Rule.new(1)
  render_method_arguments out, method.arglists
  render_method_superclass out, method
  if method.is_alias_for
    al = method.is_alias_for
    alias_for = store.load_method al.parent_name, "#{al.name_prefix}#{al.name}"
    render_method_comment out, method, alias_for
  else
    render_method_comment out, method
  end
end

def render_method_arguments(out, arglists) # :nodoc:

:nodoc:
def render_method_arguments(out, arglists) # :nodoc:
  return unless arglists
  arglists = arglists.chomp.split "\n"
  arglists = arglists.map { |line| line + "\n" }
  out << RDoc::Markup::Verbatim.new(*arglists)
  out << RDoc::Markup::Rule.new(1)
end

def render_method_comment(out, method, alias_for = nil)# :nodoc:

:nodoc:
def render_method_comment(out, method, alias_for = nil)# :nodoc:
  if alias_for
    unless method.comment.nil? or method.comment.empty?
      out << RDoc::Markup::BlankLine.new
      out << method.comment.parse
    end
    out << RDoc::Markup::BlankLine.new
    out << RDoc::Markup::Paragraph.new("(This method is an alias for #{alias_for.full_name}.)")
    out << RDoc::Markup::BlankLine.new
    out << alias_for.comment.parse
    out << RDoc::Markup::BlankLine.new
  else
    out << RDoc::Markup::BlankLine.new
    out << method.comment.parse
    out << RDoc::Markup::BlankLine.new
  end
end

def render_method_superclass(out, method) # :nodoc:

:nodoc:
def render_method_superclass(out, method) # :nodoc:
  return unless
    method.respond_to?(:superclass_method) and method.superclass_method
  out << RDoc::Markup::BlankLine.new
  out << RDoc::Markup::Heading.new(4, "(Uses superclass method #{method.superclass_method})")
  out << RDoc::Markup::Rule.new(1)
end

def run

def run
  if @list_doc_dirs then
    puts @doc_dirs
  elsif @list then
    list_known_classes @names
  elsif @server then
    start_server
  elsif @interactive or @names.empty? then
    interactive
  else
    display_names @names
  end
rescue NotFoundError => e
  abort e.message
end

def setup_pager

def setup_pager
  return if @use_stdout
  pagers = [ENV['RI_PAGER'], ENV['PAGER'], 'pager', 'less', 'more']
  require 'shellwords'
  pagers.compact.uniq.each do |pager|
    pager = Shellwords.split(pager)
    next if pager.empty?
    io = IO.popen(pager, 'w') rescue next
    next if $? and $?.pid == io.pid and $?.exited? # pager didn't work
    @paging = true
    return io
  end
  @use_stdout = true
  nil
end

def start_server

def start_server
  begin
    require 'webrick'
  rescue LoadError
    abort "webrick is not found. You may need to `gem install webrick` to install webrick."
  end
  server = WEBrick::HTTPServer.new :Port => @server
  extra_doc_dirs = @stores.map {|s| s.type == :extra ? s.path : nil}.compact
  server.mount '/', RDoc::Servlet, nil, extra_doc_dirs
  trap 'INT'  do server.shutdown end
  trap 'TERM' do server.shutdown end
  server.start
end