diff options
Diffstat (limited to 'lib/rdoc/ri/driver.rb')
-rw-r--r-- | lib/rdoc/ri/driver.rb | 1557 |
1 files changed, 0 insertions, 1557 deletions
diff --git a/lib/rdoc/ri/driver.rb b/lib/rdoc/ri/driver.rb deleted file mode 100644 index 18bfc941f1..0000000000 --- a/lib/rdoc/ri/driver.rb +++ /dev/null @@ -1,1557 +0,0 @@ -# frozen_string_literal: true -require 'optparse' - -require_relative '../../rdoc' - -require_relative 'formatter' # For RubyGems backwards compatibility -# TODO: Fix weird documentation with `require_relative` - -## -# The RI driver implements the command-line ri tool. -# -# The driver supports: -# * loading RI data from: -# * Ruby's standard library -# * RubyGems -# * ~/.rdoc -# * A user-supplied directory -# * Paging output (uses RI_PAGER environment variable, PAGER environment -# variable or the less, more and pager programs) -# * Interactive mode with tab-completion -# * Abbreviated names (ri Zl shows Zlib documentation) -# * Colorized output -# * Merging output from multiple RI data sources - -class RDoc::RI::Driver - - ## - # Base Driver error class - - class Error < RDoc::RI::Error; end - - ## - # Raised when a name isn't found in the ri data stores - - class NotFoundError < Error - - def initialize(klass, suggestion_proc = nil) # :nodoc: - @klass = klass - @suggestion_proc = suggestion_proc - end - - ## - # Name that wasn't found - - def name - @klass - end - - def message # :nodoc: - str = "Nothing known about #{@klass}" - suggestions = @suggestion_proc&.call - if suggestions and !suggestions.empty? - str += "\nDid you mean? #{suggestions.join("\n ")}" - end - str - end - end - - ## - # Show all method documentation following a class or module - - attr_accessor :show_all - - ## - # An RDoc::RI::Store for each entry in the RI path - - attr_accessor :stores - - ## - # Controls the user of the pager vs $stdout - - attr_accessor :use_stdout - - ## - # Default options for ri - - 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+ using pp - - def self.dump data_path - require 'pp' - - File.open data_path, 'rb' do |io| - pp Marshal.load(io.read) - end - end - - ## - # Parses +argv+ and returns a Hash of options - - 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 - - ## - # Runs the ri command line executable using +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 - - ## - # Creates a new driver using +initial_options+ from ::process_args - - 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 - - ## - # Adds paths for undocumented classes +also_in+ to +out+ - - 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 - - ## - # Adds a class header to +out+ for class +name+ which is described in - # +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 - - ## - # Adds "(from ...)" to +out+ for +store+ - - def add_from out, store - out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})") - end - - ## - # Adds +extends+ to +out+ - - def add_extends out, extends - add_extension_modules out, 'Extended by', extends - end - - ## - # Adds a list of +extensions+ to this module of the given +type+ to +out+. - # add_includes and add_extends call this, so you should use those directly. - - 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 - - ## - # Renders multiple included +modules+ from +store+ to +out+. - - 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 - end - - unless wout.empty? then - verb = RDoc::Markup::Verbatim.new - - wout.each do |incl| - verb.push incl.name, "\n" - end - - out << verb - end - end - - ## - # Adds a single extension module +include+ from +store+ to +out+ - - 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 - end - end - - ## - # Adds +includes+ to +out+ - - def add_includes out, includes - add_extension_modules out, 'Includes', includes - end - - ## - # Looks up the method +name+ and adds it to +out+ - - def add_method out, name - filtered = lookup_method name - method_document out, name, filtered - end - - ## - # Adds documentation for all methods in +klass+ to +out+ - - 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 - - ## - # Adds a list of +methods+ to +out+ with a heading of +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 - - ## - # Returns ancestor classes 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 - - ## - # For RubyGems backwards compatibility - - def class_cache # :nodoc: - end - - ## - # Builds a RDoc::Markup::Document from +found+, +klasess+ and +includes+ - - 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 - - ## - # Adds the class +comment+ to +out+. - - def class_document_comment out, comment # :nodoc: - unless comment.empty? then - out << RDoc::Markup::Rule.new(1) - - if comment.merged? then - parts = comment.parts - parts = parts.zip [RDoc::Markup::BlankLine.new] * parts.length - parts.flatten! - parts.pop - - out.concat parts - else - out << comment - end - end - end - - ## - # Adds the constants from +klass+ to the Document +out+. - - 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.parts if constant.comment - 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 - - ## - # Hash mapping a known class or module to the stores it can be loaded from - - 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 - - ## - # Returns the stores wherein +name+ is found along with the classes, - # extends and includes that match it - - 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 - - ## - # Completes +name+ based on the caches. For Readline - - 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 - - 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: - 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 - - completions.concat methods - end - end - - ## - # Converts +document+ to text and writes it to the pager - - 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 - - ## - # Outputs formatted RI data for class +name+. Groups undocumented classes - - 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 - - ## - # Outputs formatted RI data for 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 - - ## - # Outputs formatted RI data for the class or method +name+. - # - # Returns true if +name+ was found, false if it was not an alternative could - # be guessed, raises an error if +name+ couldn't be guessed. - - 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 - - ## - # Displays each name in +name+ - - def display_names names - names.each do |name| - name = expand_name name - - display_name name - end - end - - ## - # Outputs formatted RI data for 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 - end - - ## - # Outputs a formatted RI page list for the pages in +store+. - - 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 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 - - ## - # Expands abbreviated klass +klass+ into a fully-qualified class. "Zl::Da" - # will be expanded to Zlib::DataError. - - 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 - - ## - # Expands the class portion of +name+ into a fully-qualified class. See - # #expand_class. - - 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 - - ## - # Filters the methods in +found+ trying to find a match for +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 - - ## - # Yields items matching +name+ including the store they were found in, the - # class being searched for, the class they were found in (an ancestor) the - # types of methods to look up (from #method_type), and the method name being - # searched for - - 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 - - ## - # Finds a store that matches +name+ which can be the name of a gem, "ruby", - # "home" or "site". - # - # See also RDoc::Store#source - - 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 - - ## - # Creates a new RDoc::Markup::Formatter. If a formatter is given with -f, - # use it. If we're outputting to a pager, use bs, otherwise ansi. - - 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 - - ## - # Runs ri interactively using Readline if it is available. - - 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 - - ## - # Lists classes known to ri starting with +names+. If +names+ is empty all - # known classes are shown. - - 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 - - ## - # Returns an Array of 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 - - ## - # Loads RI data for method +name+ on +klass+ from +store+. +type+ and - # +cache+ indicate if it is a class or instance method. - - 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 - - ## - # Returns an Array of RI data for 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 - - ## - # Returns a filtered list of methods matching +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 - - ## - # Builds a RDoc::Markup::Document from +found+, +klasses+ and +includes+ - - 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 - - ## - # Returns the type of method (:both, :instance, :class) for +selector+ - - def method_type selector - case selector - when '.', nil then :both - when '#' then :instance - else :class - end - end - - ## - # Returns a regular expression for +name+ that will match an - # RDoc::AnyMethod's 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 - - ## - # Paginates output through a pager program. - - 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 - - ## - # Are we using a pager? - - def paging? - @paging - end - - ## - # Extracts the class, selector and method name parts from +name+ like - # Foo::Bar#baz. - # - # NOTE: Given Foo::Bar, Bar is considered a class even though it may be a - # method - - 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 - - ## - # Renders the +klass+ from +store+ to +out+. If the klass has no - # documentable items the class is added to +also_in+ instead. - - def render_class out, store, klass, also_in # :nodoc: - comment = klass.comment - # 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 comment.empty? and - instance_methods.empty? and class_methods.empty? then - also_in << store - return - end - - add_from out, store - - class_document_comment out, comment - - 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: - 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: - 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: - if alias_for - unless method.comment.nil? or method.comment.empty? - out << RDoc::Markup::BlankLine.new - out << method.comment - 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 - out << RDoc::Markup::BlankLine.new - else - out << RDoc::Markup::BlankLine.new - out << method.comment - out << RDoc::Markup::BlankLine.new - end - end - - 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 - - ## - # Looks up and displays ri data according to the options given. - - 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 - - ## - # Sets up a pager program to pass output through. Tries the RI_PAGER and - # PAGER environment variables followed by pager, less then more. - - 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 - - ## - # Starts a WEBrick server for ri. - - 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 - - RDOC_REFS_REGEXP = /\[rdoc-ref:([\w.]+)(@.*)?\]/ - - 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 -end |