class RDoc::RI::Driver

RI 驱动程序实现了命令行 ri 工具。

该驱动程序支持

  • 从以下位置加载 RI 数据

    • Ruby 的标准库

    • RubyGems

    • ~/.rdoc

    • 用户提供的目录

  • 分页输出(使用 RI_PAGER 环境变量、PAGER 环境变量或 less、more 和 pager 程序)

  • 带有制表符补全的交互模式

  • 缩写名称(ri Zl 显示 Zlib 文档)

  • 彩色输出

  • 合并来自多个 RI 数据源的输出

常量

RDOC_REFS_REGEXP

属性

show_all[RW]

显示类或模块之后的所有方法文档

stores[RW]

RI 路径中每个条目的 RDoc::RI::Store

use_stdout[RW]

控制分页器与 $stdout 的用户

公共类方法

default_options() 点击切换源代码

ri 的默认选项

# File rdoc/ri/driver.rb, line 77
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
dump(data_path) 点击切换源代码

使用 pp 转储 data_path

# File rdoc/ri/driver.rb, line 99
def self.dump data_path
  require 'pp'

  File.open data_path, 'rb' do |io|
    pp Marshal.load(io.read)
  end
end
new(initial_options = {}) 点击切换源代码

使用来自 ::process_argsinitial_options 创建一个新的驱动程序

# File rdoc/ri/driver.rb, line 402
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 path, 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
process_args(argv) 点击切换源代码

解析 argv 并返回选项的哈希

# File rdoc/ri/driver.rb, line 110
  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
Usage: #{opt.program_name} [options] [name ...]

Where 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

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

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

README and other files may be displayed by prefixing them with the gem name
they're contained in.  If the gem name is followed by a ':' all files in the
gem will be shown.  The file name extension may be omitted where it is
unambiguous.

'ruby' can be used as a pseudo gem name to display files from the Ruby
core documentation. Use 'ruby:' by itself to get a list of all available
core documentation files.

For 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

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

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

To see the default directories #{opt.program_name} will search, run:

    #{opt.program_name} --list-doc-dirs

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

ri options may be set in the RI environment variable.

The ri pager can be set with the RI_PAGER environment variable
or 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
run(argv = ARGV) 点击切换源代码

使用 argv 运行 ri 命令行可执行文件

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

公共实例方法

add_also_in(out, also_in) 点击切换源代码

将未文档化的类 also_in 的路径添加到 out

# File rdoc/ri/driver.rb, line 441
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
add_class(out, name, classes) 点击切换源代码

为在 classes 中描述的类 nameout 添加类标题。

# File rdoc/ri/driver.rb, line 458
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
add_extends(out, extends) 点击切换源代码

extends 添加到 out

# File rdoc/ri/driver.rb, line 485
def add_extends out, extends
  add_extension_modules out, 'Extended by', extends
end
add_extension_modules(out, type, extensions) 点击切换源代码

将给定 type 的此模块的 extensions 列表添加到 outadd_includesadd_extends 调用此方法,因此您应该直接使用它们。

# File rdoc/ri/driver.rb, line 493
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
add_from(out, store) 点击切换源代码

storeout 添加“(from …)”

# File rdoc/ri/driver.rb, line 478
def add_from out, store
  out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})")
end
add_includes(out, includes) 点击切换源代码

includes 添加到 out

# File rdoc/ri/driver.rb, line 552
def add_includes out, includes
  add_extension_modules out, 'Includes', includes
end
add_method(out, name) 点击切换源代码

查找方法 name 并将其添加到 out

# File rdoc/ri/driver.rb, line 559
def add_method out, name
  filtered = lookup_method name
  method_document out, name, filtered
end
add_method_documentation(out, klass) 点击切换源代码

klass 中所有方法的文档添加到 out

# File rdoc/ri/driver.rb, line 567
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
add_method_list(out, methods, name) 点击切换源代码

methods 列表添加到 out,标题为 name

# File rdoc/ri/driver.rb, line 580
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
ancestors_of(klass) 点击切换源代码

返回 klass 的祖先类

# File rdoc/ri/driver.rb, line 600
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
class_document(name, found, klasses, includes, extends) 点击切换源代码

foundklasessincludes 构建 RDoc::Markup::Document

# File rdoc/ri/driver.rb, line 637
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
classes() 点击切换源代码

将已知类或模块映射到可以从中加载的存储的哈希

# File rdoc/ri/driver.rb, line 704
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
classes_and_includes_and_extends_for(name) 点击切换源代码

返回在其中找到 name 的存储以及与之匹配的类、扩展和包含项

# File rdoc/ri/driver.rb, line 724
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
complete(name) 点击切换源代码

根据缓存完成 name。用于 Readline

# File rdoc/ri/driver.rb, line 749
def complete name
  completions = []

  klass, selector, method = parse_name name

  complete_klass  name, klass, selector, method, completions
  complete_method name, klass, selector,         completions

  completions.sort.uniq
end
display(document) 点击切换源代码

document 转换为文本并将其写入分页器

# File rdoc/ri/driver.rb, line 799
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
display_class(name) 点击切换源代码

输出类 name 的格式化 RI 数据。对未文档化的类进行分组

# File rdoc/ri/driver.rb, line 812
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
display_method(name) 点击切换源代码

输出方法 name 的格式化 RI 数据

# File rdoc/ri/driver.rb, line 828
def display_method name
  out = RDoc::Markup::Document.new

  add_method out, name

  expand_rdoc_refs_at_the_bottom(out)

  display out
end
display_name(name) 点击切换源代码

输出类或方法 name 的格式化 RI 数据。

如果找到 name 返回 true,如果不是则猜测替代项返回 false,如果无法猜测 name 则引发错误。

# File rdoc/ri/driver.rb, line 844
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
display_names(names) 点击切换源代码

显示 name 中的每个名称

# File rdoc/ri/driver.rb, line 873
def display_names names
  names.each do |name|
    name = expand_name name

    display_name name
  end
end
display_page(name) 点击切换源代码

输出页面 name 的格式化 RI 数据。

# File rdoc/ri/driver.rb, line 884
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
end
display_page_list(store, pages = store.cache[:pages], search = nil) 点击切换源代码

store 中的页面输出格式化的 RI 页面列表。

# File rdoc/ri/driver.rb, line 915
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
expand_class(klass) 点击切换源代码

将缩写的 klass klass 扩展为完全限定的类。“Zl::Da” 将扩展为 Zlib::DataError。

# File rdoc/ri/driver.rb, line 959
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
expand_name(name) 点击切换源代码

name 的类部分扩展为完全限定的类。请参阅 expand_class

# File rdoc/ri/driver.rb, line 977
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
expand_rdoc_refs_at_the_bottom(out) 点击切换源代码
# File rdoc/ri/driver.rb, line 1525
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
  end
end
filter_methods(found, name) 点击切换源代码

过滤 found 中的方法,尝试查找与 name 匹配的方法。

# File rdoc/ri/driver.rb, line 993
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
find_methods(name) { |*item| ... } 点击切换源代码

生成与 name 匹配的项目,包括找到它们的存储,正在搜索的类,找到它们的类(祖先),要查找的方法类型(来自 method_type)和正在搜索的方法名称

# File rdoc/ri/driver.rb, line 1011
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
find_store(name) 点击切换源代码

查找与 name 匹配的存储,它可以是 gem 的名称、“ruby”、“home” 或 “site”。

另请参阅 RDoc::Store#source

# File rdoc/ri/driver.rb, line 1057
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
formatter(io) 点击切换源代码

创建一个新的 RDoc::Markup::Formatter。如果使用 -f 给出了格式化程序,则使用它。如果要输出到分页器,则使用 bs,否则使用 ansi。

# File rdoc/ri/driver.rb, line 1074
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
interactive() 点击切换源代码

如果 Readline 可用,则使用它以交互方式运行 ri。

# File rdoc/ri/driver.rb, line 1087
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
list_known_classes(names = []) 点击切换源代码

列出 ri 已知的以 names 开头的类。如果 names 为空,则显示所有已知类。

# File rdoc/ri/driver.rb, line 1126
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
list_methods_matching(name) 点击切换源代码

返回与 name 匹配的方法数组

# File rdoc/ri/driver.rb, line 1158
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
load_method(store, cache, klass, type, name) 点击切换源代码

store 加载 klass 上方法 name 的 RI 数据。typecache 指示它是类方法还是实例方法。

# File rdoc/ri/driver.rb, line 1197
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}").parse

  method = RDoc::AnyMethod.new nil, name
  method.comment = comment
  method
end
load_methods_matching(name) 点击切换源代码

返回与 name 匹配的方法的 RI 数据数组

# File rdoc/ri/driver.rb, line 1220
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
lookup_method(name) 点击切换源代码

返回与 name 匹配的经过筛选的方法列表

# File rdoc/ri/driver.rb, line 1241
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
method_document(out, name, filtered) 点击切换源代码

foundklassesincludes 构建 RDoc::Markup::Document

# File rdoc/ri/driver.rb, line 1266
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
method_type(selector) 点击切换源代码

返回 selector 的方法类型(:both、:instance、:class)

# File rdoc/ri/driver.rb, line 1282
def method_type selector
  case selector
  when '.', nil then :both
  when '#'      then :instance
  else               :class
  end
end
name_regexp(name) 点击切换源代码

返回 name 的正则表达式,该正则表达式将匹配 RDoc::AnyMethod 的名称。

# File rdoc/ri/driver.rb, line 1294
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
page() { |pager| ... } 点击切换源代码

通过分页程序分页输出。

# File rdoc/ri/driver.rb, line 1308
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
paging?() 点击切换源代码

我们正在使用分页器吗?

# File rdoc/ri/driver.rb, line 1326
def paging?
  @paging
end
parse_name(name) 点击切换源代码

从像 Foo::Bar#baz 这样的 name 中提取类、选择器和方法名称部分。

注意:给定 Foo::Bar,即使 Bar 可能是一个方法,也被视为一个类。

# File rdoc/ri/driver.rb, line 1337
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
run() 点击切换源代码

根据给定的选项查找并显示 ri 数据。

# File rdoc/ri/driver.rb, line 1458
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
setup_pager() 点击切换源代码

设置一个分页程序来传递输出。依次尝试 RI_PAGER 和 PAGER 环境变量,然后是 pager、less,最后是 more。

# File rdoc/ri/driver.rb, line 1478
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
start_server() 点击切换源代码

为 ri 启动一个 WEBrick 服务器。

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