diff options
author | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2012-11-27 04:28:14 +0000 |
---|---|---|
committer | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2012-11-27 04:28:14 +0000 |
commit | 1c279a7d2753949c725754e1302f791b76358114 (patch) | |
tree | 36aa3bdde250e564445eba5f2e25fcb96bcb6cef /lib/rdoc/ri | |
parent | c72f0daa877808e4fa5018b3191ca09d4b97c03d (diff) | |
download | ruby-1c279a7d2753949c725754e1302f791b76358114.tar.gz |
* lib/rdoc*: Updated to RDoc 4.0 (pre-release)
* bin/rdoc: ditto
* test/rdoc: ditto
* NEWS: Updated with RDoc 4.0 information
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@37889 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/rdoc/ri')
-rw-r--r-- | lib/rdoc/ri/driver.rb | 404 | ||||
-rw-r--r-- | lib/rdoc/ri/paths.rb | 121 | ||||
-rw-r--r-- | lib/rdoc/ri/store.rb | 356 |
3 files changed, 414 insertions, 467 deletions
diff --git a/lib/rdoc/ri/driver.rb b/lib/rdoc/ri/driver.rb index 26304dca96..4beda55881 100644 --- a/lib/rdoc/ri/driver.rb +++ b/lib/rdoc/ri/driver.rb @@ -11,11 +11,7 @@ begin rescue LoadError end -require 'rdoc/ri' -require 'rdoc/ri/paths' -require 'rdoc/markup' -require 'rdoc/markup/formatter' -require 'rdoc/text' +require 'rdoc' ## # For RubyGems backwards compatibility @@ -61,6 +57,11 @@ class RDoc::RI::Driver 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 @@ -75,17 +76,18 @@ class RDoc::RI::Driver def self.default_options options = {} - options[:use_stdout] = !$stdout.tty? - options[:width] = 72 options[:interactive] = false - options[:use_cache] = true - options[:profile] = false + options[:profile] = false + options[:show_all] = false + options[:use_cache] = 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[:use_system] = true + options[:use_site] = true + options[:use_home] = true + options[:use_gems] = true options[:extra_doc_dirs] = [] return options @@ -123,7 +125,11 @@ Usage: #{opt.program_name} [options] [names...] Where name can be: - Class | Class::method | Class#method | Class.method | method + Class | Module | Module::Class + + Class::method | Class#method | Class.method | method + + gem_name: | gem_name:README | gem_name:History All class names may be abbreviated to their minimum unambiguous form. If a name is ambiguous, all valid options will be listed. @@ -131,12 +137,18 @@ 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. + For example: #{opt.program_name} Fil #{opt.program_name} File #{opt.program_name} File.new #{opt.program_name} zip + #{opt.program_name} rdoc:README Note that shell quoting or escaping may be required for method names containing punctuation: @@ -151,7 +163,10 @@ To see the default directories ri will search, run: Specifying the --system, --site, --home, --gems or --doc-dir options will limit ri to searching only the specified directories. -Options may also be set in the 'RI' environment variable. +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 @@ -159,64 +174,74 @@ Options may also be set in the 'RI' environment variable. 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 + opt.on("--[no-]interactive", "-i", + "In interactive mode you can repeatedly", + "look up methods with autocomplete.") do |interactive| + options[:interactive] = interactive end - opt.on("--format=NAME", "-f", - "Uses 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}" + 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-pager", "-T", - "Send output directly to stdout,", - "rather than to a pager.") do - options[:use_stdout] = true + opt.on("--[no-]list", "-l", + "List classes ri knows about.") do |list| + options[:list] = list end opt.separator nil - opt.on("--width=WIDTH", "-w", OptionParser::DecimalInteger, - "Set the width of the output.") do |value| - options[:width] = value + opt.on("--[no-]pager", "-T", + "Send output directly to stdout,", + "rather than to a pager.") do |use_pager| + options[:use_stdout] = !use_pager end opt.separator nil - opt.on("--interactive", "-i", - "In interactive mode you can repeatedly", - "look up methods with autocomplete.") do - options[:interactive] = true + opt.on("--width=WIDTH", "-w", OptionParser::DecimalInteger, + "Set the width of the output.") do |width| + options[:width] = width end opt.separator nil - opt.on("--list", "-l", - "List classes ri knows about.") do - options[:list] = true + 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 - opt.on("--[no-]profile", - "Run with the ruby profiler") do |value| - options[:profile] = value + 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", + "Uses 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.separator "Data source options:" opt.separator nil - opt.on("--list-doc-dirs", + opt.on("--[no-]list-doc-dirs", "List the directories from which ri will", - "source documentation on stdout and exit.") do - options[:list_doc_dirs] = true + "source documentation on stdout and exit.") do |list_doc_dirs| + options[:list_doc_dirs] = list_doc_dirs end opt.separator nil @@ -284,6 +309,13 @@ Options may also be set in the 'RI' environment variable. 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", File, "Dumps data from an ri cache or data file") do |value| options[:dump_path] = value @@ -356,7 +388,12 @@ Options may also be set in the 'RI' environment variable. @list_doc_dirs = options[:list_doc_dirs] @interactive = options[:interactive] + @server = options[:server] @use_stdout = options[:use_stdout] + @show_all = options[:show_all] + + # pager process for jruby + @jruby_pager_process = nil end ## @@ -404,15 +441,23 @@ Options may also be set in the 'RI' environment variable. end ## - # Adds +includes+ to +out+ + # Adds +extends+ to +out+ - def add_includes out, includes - return if includes.empty? + 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, "Includes:") + out << RDoc::Markup::Heading.new(1, "#{type}:") - includes.each do |modules, store| + extensions.each do |modules, store| if modules.length == 1 then include = modules.first name = include.name @@ -450,6 +495,37 @@ Options may also be set in the 'RI' environment variable. 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_out = method_document name, filtered + + out.concat method_out.parts + 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 @@ -458,10 +534,10 @@ Options may also be set in the 'RI' environment variable. out << RDoc::Markup::Heading.new(1, "#{name}:") out << RDoc::Markup::BlankLine.new - if @use_stdout and !@interactive - out.push(*methods.map do |method| + if @use_stdout and !@interactive then + out.concat methods.map { |method| RDoc::Markup::Verbatim.new method - end) + } else out << RDoc::Markup::IndentedParagraph.new(2, methods.join(', ')) end @@ -493,8 +569,8 @@ Options may also be set in the 'RI' environment variable. klasses = klasses - seen - ancestors.push(*klasses) - unexamined.push(*klasses) + ancestors.concat klasses + unexamined.concat klasses end ancestors.reverse @@ -509,7 +585,7 @@ Options may also be set in the 'RI' environment variable. ## # Builds a RDoc::Markup::Document from +found+, +klasess+ and +includes+ - def class_document name, found, klasses, includes + def class_document name, found, klasses, includes, extends also_in = [] out = RDoc::Markup::Document.new @@ -517,6 +593,7 @@ Options may also be set in the 'RI' environment variable. add_class out, name, klasses add_includes out, includes + add_extends out, extends found.each do |store, klass| comment = klass.comment @@ -542,7 +619,7 @@ Options may also be set in the 'RI' environment variable. parts.flatten! parts.pop - out.push(*parts) + out.concat parts else out << comment end @@ -559,13 +636,13 @@ Options may also be set in the 'RI' environment variable. constants = klass.constants.sort_by { |constant| constant.name } - list.push(*constants.map do |constant| + 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) - end) + } out << list out << RDoc::Markup::BlankLine.new @@ -574,6 +651,8 @@ Options may also be set in the 'RI' environment variable. 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 add_also_in out, also_in @@ -601,26 +680,29 @@ Options may also be set in the 'RI' environment variable. end ## - # Returns the stores wherin +name+ is found along with the classes and - # includes that match it + # Returns the stores wherein +name+ is found along with the classes, + # extends and includes that match it - def classes_and_includes_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 Errno::ENOENT + rescue RDoc::Store::MissingFileError end end.compact + extends.reject! do |modules,| modules.empty? end includes.reject! do |modules,| modules.empty? end - [found, klasses, includes] + [found, klasses, includes, extends] end ## @@ -659,7 +741,7 @@ Options may also be set in the 'RI' environment variable. completions << "#{klass}#{selector}" end - completions.push(*methods) + completions.concat methods end completions.sort.uniq @@ -682,11 +764,12 @@ Options may also be set in the 'RI' environment variable. def display_class name return if name =~ /#|\./ - found, klasses, includes = classes_and_includes_for name + found, klasses, includes, extends = + classes_and_includes_and_extends_for name return if found.empty? - out = class_document name, found, klasses, includes + out = class_document name, found, klasses, includes, extends display out end @@ -695,13 +778,9 @@ Options may also be set in the 'RI' environment variable. # Outputs formatted RI data for method +name+ def display_method name - found = load_methods_matching name - - raise NotFoundError, name if found.empty? - - filtered = filter_methods found, name + out = RDoc::Markup::Document.new - out = method_document name, filtered + add_method out, name display out end @@ -713,6 +792,11 @@ Options may also be set in the 'RI' environment variable. # 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 =~ /::|#|\./ @@ -727,7 +811,7 @@ Options may also be set in the 'RI' environment variable. page do |io| io.puts "#{name} not found, maybe you meant:" io.puts - io.puts matches.join("\n") + io.puts matches.sort.join("\n") end false @@ -745,6 +829,61 @@ Options may also be set in the 'RI' environment variable. 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 > 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 + + ## # Expands abbreviated klass +klass+ into a fully-qualified class. "Zl::Da" # will be expanded to Zlib::DataError. @@ -776,7 +915,12 @@ Options may also be set in the 'RI' environment variable. return [selector, method].join if klass.empty? - "#{expand_class klass}#{selector}#{method}" + case selector + when ':' then + [find_store(klass), selector, method] + else + [expand_class(klass), selector, method] + end.join end ## @@ -841,6 +985,55 @@ Options may also be set in the 'RI' environment variable. end ## + # Finds the given +pager+ for jruby. Returns an IO if +pager+ was found. + # + # Returns false if +pager+ does not exist. + # + # Returns nil if the jruby JVM doesn't support ProcessBuilder redirection + # (1.6 and older). + + def find_pager_jruby pager + require 'java' + require 'shellwords' + + return nil unless java.lang.ProcessBuilder.constants.include? :Redirect + + pager = Shellwords.split pager + + pb = java.lang.ProcessBuilder.new(*pager) + pb = pb.redirect_output java.lang.ProcessBuilder::Redirect::INHERIT + + @jruby_pager_process = pb.start + + input = @jruby_pager_process.output_stream + + io = input.to_io + io.sync = true + io + rescue java.io.IOException + false + 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. @@ -909,7 +1102,7 @@ Options may also be set in the 'RI' environment variable. classes = [] stores.each do |store| - classes << store.modules + classes << store.module_names end classes = classes.flatten.uniq.sort @@ -951,7 +1144,7 @@ Options may also be set in the 'RI' environment variable. "#{klass}##{match}" end - found.push(*matches) + found.concat matches end end @@ -965,7 +1158,7 @@ Options may also be set in the 'RI' environment variable. "#{klass}::#{match}" end - found.push(*matches) + found.concat matches end end @@ -1012,7 +1205,18 @@ Options may also be set in the 'RI' environment variable. end ## - # Builds a RDoc::Markup::Document from +found+, +klasess+ and +includes+ + # Returns a filtered list of methods matching +name+ + + def lookup_method name + found = load_methods_matching name + + raise NotFoundError, name if found.empty? + + filter_methods found, name + end + + ## + # Builds a RDoc::Markup::Document from +found+, +klasses+ and +includes+ def method_document name, filtered out = RDoc::Markup::Document.new @@ -1027,6 +1231,7 @@ Options may also be set in the 'RI' environment variable. 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) if method.arglists then @@ -1036,6 +1241,12 @@ Options may also be set in the 'RI' environment variable. out << RDoc::Markup::Rule.new(1) end + if 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 + out << RDoc::Markup::BlankLine.new out << method.comment out << RDoc::Markup::BlankLine.new @@ -1080,6 +1291,7 @@ Options may also be set in the 'RI' environment variable. yield pager ensure pager.close + @jruby_pager_process.wait_for if @jruby_pager_process end else yield $stdout @@ -1101,10 +1313,10 @@ Options may also be set in the 'RI' environment variable. # Foo::Bar#baz. # # NOTE: Given Foo::Bar, Bar is considered a class even though it may be a - # method + # method def parse_name name - parts = name.split(/(::|#|\.)/) + parts = name.split(/(::?|#|\.)/) if parts.length == 1 then if parts.first =~ /^[a-z]|^([%&*+\/<>^`|~-]|\+@|-@|<<|<=>?|===?|=>|=~|>>|\[\]=?|~@)$/ then @@ -1135,6 +1347,8 @@ Options may also be set in the 'RI' environment variable. puts @doc_dirs elsif @list then list_known_classes @names + elsif @server then + start_server elsif @interactive or @names.empty? then interactive else @@ -1151,6 +1365,8 @@ Options may also be set in the 'RI' environment variable. def setup_pager return if @use_stdout + jruby = Object.const_defined?(:RUBY_ENGINE) && RUBY_ENGINE == 'jruby' + pagers = [ENV['RI_PAGER'], ENV['PAGER'], 'pager', 'less', 'more'] pagers.compact.uniq.each do |pager| @@ -1160,9 +1376,17 @@ Options may also be set in the 'RI' environment variable. next unless in_path? pager_cmd - io = IO.popen(pager, 'w') rescue next + if jruby then + case io = find_pager_jruby(pager) + when nil then break + when false then next + else io + end + else + io = IO.popen(pager, 'w') rescue next + end - next if $? and $?.exited? # pager didn't work + next if $? and $?.pid == io.pid and $?.exited? # pager didn't work @paging = true @@ -1174,5 +1398,21 @@ Options may also be set in the 'RI' environment variable. nil end + ## + # Starts a WEBrick server for ri. + + def start_server + require 'webrick' + + server = WEBrick::HTTPServer.new :Port => @server + + server.mount '/', RDoc::Servlet + + trap 'INT' do server.shutdown end + trap 'TERM' do server.shutdown end + + server.start + end + end diff --git a/lib/rdoc/ri/paths.rb b/lib/rdoc/ri/paths.rb index a3c65bf928..d7ea285eaa 100644 --- a/lib/rdoc/ri/paths.rb +++ b/lib/rdoc/ri/paths.rb @@ -1,7 +1,8 @@ require 'rdoc/ri' ## -# The directories where ri data lives. +# The directories where ri data lives. Paths can be enumerated via ::each, or +# queried individually via ::system_dir, ::site_dir, ::home_dir and ::gem_dir. module RDoc::RI::Paths @@ -10,15 +11,12 @@ module RDoc::RI::Paths version = RbConfig::CONFIG['ruby_version'] - base = if RbConfig::CONFIG.key? 'ridir' then + BASE = if RbConfig::CONFIG.key? 'ridir' then File.join RbConfig::CONFIG['ridir'], version else File.join RbConfig::CONFIG['datadir'], 'ri', version end - SYSDIR = File.join base, "system" - SITEDIR = File.join base, "site" - homedir = begin File.expand_path('~') rescue ArgumentError @@ -32,8 +30,6 @@ module RDoc::RI::Paths end #:startdoc: - @gemdirs = nil - ## # Iterates over each selected path yielding the directory and type. # @@ -47,16 +43,19 @@ module RDoc::RI::Paths # :extra:: ri data directory from the command line. Yielded for each # entry in +extra_dirs+ - def self.each system, site, home, gems, *extra_dirs # :yields: directory, type + def self.each system = true, site = true, home = true, gems = :latest, *extra_dirs # :yields: directory, type + return enum_for __method__, system, site, home, gems, *extra_dirs unless + block_given? + extra_dirs.each do |dir| yield dir, :extra end - yield SYSDIR, :system if system - yield SITEDIR, :site if site - yield HOMEDIR, :home if home and HOMEDIR + yield system_dir, :system if system + yield site_dir, :site if site + yield home_dir, :home if home and HOMEDIR - gemdirs.each do |dir| + gemdirs(gems).each do |dir| yield dir, :gem end if gems @@ -64,36 +63,72 @@ module RDoc::RI::Paths end ## - # The latest installed gems' ri directories + # The ri directory for the gem with +gem_name+. - def self.gemdirs - return @gemdirs if @gemdirs + def self.gem_dir name, version + req = Gem::Requirement.new "= #{version}" - require 'rubygems' unless defined?(Gem) + spec = Gem::Specification.find_by_name name, req - # HACK dup'd from Gem.latest_partials and friends - all_paths = [] + File.join spec.doc_dir, 'ri' + end - all_paths = Gem.path.map do |dir| - Dir[File.join(dir, 'doc', '*', 'ri')] - end.flatten + ## + # The latest installed gems' ri directories. +filter+ can be :all or + # :latest. + # + # A +filter+ :all includes all versions of gems and includes gems without + # ri documentation. + + def self.gemdirs filter = :latest + require 'rubygems' unless defined?(Gem) ri_paths = {} - all_paths.each do |dir| - base = File.basename File.dirname(dir) - if base =~ /(.*)-((\d+\.)*\d+)/ then - name, version = $1, $2 - ver = Gem::Version.new version - if ri_paths[name].nil? or ver > ri_paths[name][0] then - ri_paths[name] = [ver, dir] + all = Gem::Specification.map do |spec| + [File.join(spec.doc_dir, 'ri'), spec.name, spec.version] + end + + if filter == :all then + gemdirs = [] + + all.group_by do |_, name, _| + name + end.sort_by do |group, _| + group + end.map do |group, items| + items.sort_by do |_, _, version| + version + end.reverse_each do |dir,| + gemdirs << dir end end + + return gemdirs + end + + all.each do |dir, name, ver| + next unless File.exist? dir + + if ri_paths[name].nil? or ver > ri_paths[name].first then + ri_paths[name] = [ver, name, dir] + end end - @gemdirs = ri_paths.map { |k,v| v.last }.sort + ri_paths.sort_by { |_, (_, name, _)| name }.map { |k, v| v.last } rescue LoadError - @gemdirs = [] + [] + end + + ## + # The location of the rdoc data in the user's home directory. + # + # Like ::system, ri data in the user's home directory is rare and predates + # libraries distributed via RubyGems. ri data is rarely generated into this + # directory. + + def self.home_dir + HOMEDIR end ## @@ -102,7 +137,7 @@ module RDoc::RI::Paths # # See also ::each - def self.path(system, site, home, gems, *extra_dirs) + def self.path(system = true, site = true, home = true, gems = :latest, *extra_dirs) path = raw_path system, site, home, gems, *extra_dirs path.select { |directory| File.directory? directory } @@ -124,5 +159,29 @@ module RDoc::RI::Paths path.compact end + ## + # The location of ri data installed into the site dir. + # + # Historically this was available for documentation installed by ruby + # libraries predating RubyGems. It is unlikely to contain any content for + # modern ruby installations. + + def self.site_dir + File.join BASE, 'site' + end + + ## + # The location of the built-in ri data. + # + # This data is built automatically when `make` is run when ruby is + # installed. If you did not install ruby by hand you may need to install + # the documentation yourself. Please consult the documentation for your + # package manager or ruby installer for details. You can also use the + # rdoc-data gem to install system ri data for common versions of ruby. + + def self.system_dir + File.join BASE, 'system' + end + end diff --git a/lib/rdoc/ri/store.rb b/lib/rdoc/ri/store.rb index fe4ccc244d..9fa9bbb03c 100644 --- a/lib/rdoc/ri/store.rb +++ b/lib/rdoc/ri/store.rb @@ -1,358 +1,6 @@ -require 'rdoc/code_objects' -require 'fileutils' +module RDoc::RI -## -# A set of ri data. -# -# The store manages reading and writing ri data for a project (gem, path, -# etc.) and maintains a cache of methods, classes and ancestors in the -# store. -# -# The store maintains a #cache of its contents for faster lookup. After -# adding items to the store it must be flushed using #save_cache. The cache -# contains the following structures: -# -# @cache = { -# :class_methods => {}, # class name => class methods -# :instance_methods => {}, # class name => instance methods -# :attributes => {}, # class name => attributes -# :modules => [], # classes and modules in this store -# :ancestors => {}, # class name => ancestor names -# } -#-- -# TODO need to store the list of files and prune classes - -class RDoc::RI::Store - - ## - # If true this Store will not write any files - - attr_accessor :dry_run - - ## - # Path this store reads or writes - - attr_accessor :path - - ## - # Type of ri datastore this was loaded from. See RDoc::RI::Driver, - # RDoc::RI::Paths. - - attr_accessor :type - - ## - # The contents of the Store - - attr_reader :cache - - ## - # The encoding of the contents in the Store - - attr_accessor :encoding - - ## - # Creates a new Store of +type+ that will load or save to +path+ - - def initialize path, type = nil - @dry_run = false - @type = type - @path = path - @encoding = nil - - @cache = { - :ancestors => {}, - :attributes => {}, - :class_methods => {}, - :encoding => @encoding, - :instance_methods => {}, - :modules => [], - } - end - - ## - # Ancestors cache accessor. Maps a klass name to an Array of its ancestors - # in this store. If Foo in this store inherits from Object, Kernel won't be - # listed (it will be included from ruby's ri store). - - def ancestors - @cache[:ancestors] - end - - ## - # Attributes cache accessor. Maps a class to an Array of its attributes. - - def attributes - @cache[:attributes] - end - - ## - # Path to the cache file - - def cache_path - File.join @path, 'cache.ri' - end - - ## - # Path to the ri data for +klass_name+ - - def class_file klass_name - name = klass_name.split('::').last - File.join class_path(klass_name), "cdesc-#{name}.ri" - end - - ## - # Class methods cache accessor. Maps a class to an Array of its class - # methods (not full name). - - def class_methods - @cache[:class_methods] - end - - ## - # Path where data for +klass_name+ will be stored (methods or class data) - - def class_path klass_name - File.join @path, *klass_name.split('::') - end - - ## - # Removes empty items and ensures item in each collection are unique and - # sorted - - def clean_cache_collection collection # :nodoc: - collection.each do |name, item| - if item.empty? then - collection.delete name - else - # HACK mongrel-1.1.5 documents its files twice - item.uniq! - item.sort! - end - end - end - - ## - # Friendly rendition of #path - - def friendly_path - case type - when :gem then - sep = Regexp.union(*['/', File::ALT_SEPARATOR].compact) - @path =~ /#{sep}doc#{sep}(.*?)#{sep}ri$/ - "gem #{$1}" - when :home then '~/.ri' - when :site then 'ruby site' - when :system then 'ruby core' - else @path - end - end - - def inspect # :nodoc: - "#<%s:0x%x %s %p>" % [self.class, object_id, @path, modules.sort] - end - - ## - # Instance methods cache accessor. Maps a class to an Array of its - # instance methods (not full name). - - def instance_methods - @cache[:instance_methods] - end - - ## - # Loads cache file for this store - - def load_cache - #orig_enc = @encoding - - open cache_path, 'rb' do |io| - @cache = Marshal.load io.read - end - - load_enc = @cache[:encoding] - - # TODO this feature will be time-consuming to add: - # a) Encodings may be incompatible but transcodeable - # b) Need to warn in the appropriate spots, wherever they may be - # c) Need to handle cross-cache differences in encodings - # d) Need to warn when generating into a cache with diffent encodings - # - #if orig_enc and load_enc != orig_enc then - # warn "Cached encoding #{load_enc} is incompatible with #{orig_enc}\n" \ - # "from #{path}/cache.ri" unless - # Encoding.compatible? orig_enc, load_enc - #end - - @encoding = load_enc unless @encoding - - @cache - rescue Errno::ENOENT - end - - ## - # Loads ri data for +klass_name+ - - def load_class klass_name - open class_file(klass_name), 'rb' do |io| - Marshal.load io.read - end - end - - ## - # Loads ri data for +method_name+ in +klass_name+ - - def load_method klass_name, method_name - open method_file(klass_name, method_name), 'rb' do |io| - Marshal.load io.read - end - end - - ## - # Path to the ri data for +method_name+ in +klass_name+ - - def method_file klass_name, method_name - method_name = method_name.split('::').last - method_name =~ /#(.*)/ - method_type = $1 ? 'i' : 'c' - method_name = $1 if $1 - - method_name = if ''.respond_to? :ord then - method_name.gsub(/\W/) { "%%%02x" % $&[0].ord } - else - method_name.gsub(/\W/) { "%%%02x" % $&[0] } - end - - File.join class_path(klass_name), "#{method_name}-#{method_type}.ri" - end - - ## - # Modules cache accessor. An Array of all the modules (and classes) in the - # store. - - def modules - @cache[:modules] - end - - ## - # Writes the cache file for this store - - def save_cache - clean_cache_collection @cache[:ancestors] - clean_cache_collection @cache[:attributes] - clean_cache_collection @cache[:class_methods] - clean_cache_collection @cache[:instance_methods] - - @cache[:modules].uniq! - @cache[:modules].sort! - @cache[:encoding] = @encoding # this gets set twice due to assert_cache - - return if @dry_run - - marshal = Marshal.dump @cache - - open cache_path, 'wb' do |io| - io.write marshal - end - end - - ## - # Writes the ri data for +klass+ - - def save_class klass - full_name = klass.full_name - - FileUtils.mkdir_p class_path(full_name) unless @dry_run - - @cache[:modules] << full_name - - path = class_file full_name - - begin - disk_klass = load_class full_name - - klass = disk_klass.merge klass - rescue Errno::ENOENT - end - - # BasicObject has no ancestors - ancestors = klass.ancestors.compact.map do |ancestor| - # HACK for classes we don't know about (class X < RuntimeError) - String === ancestor ? ancestor : ancestor.full_name - end - - @cache[:ancestors][full_name] ||= [] - @cache[:ancestors][full_name].push(*ancestors) - - attributes = klass.attributes.map do |attribute| - "#{attribute.definition} #{attribute.name}" - end - - unless attributes.empty? then - @cache[:attributes][full_name] ||= [] - @cache[:attributes][full_name].push(*attributes) - end - - to_delete = [] - - unless klass.method_list.empty? then - @cache[:class_methods][full_name] ||= [] - @cache[:instance_methods][full_name] ||= [] - - class_methods, instance_methods = - klass.method_list.partition { |meth| meth.singleton } - - class_methods = class_methods. map { |method| method.name } - instance_methods = instance_methods.map { |method| method.name } - - old = @cache[:class_methods][full_name] - class_methods - to_delete.concat old.map { |method| - method_file full_name, "#{full_name}::#{method}" - } - - old = @cache[:instance_methods][full_name] - instance_methods - to_delete.concat old.map { |method| - method_file full_name, "#{full_name}##{method}" - } - - @cache[:class_methods][full_name] = class_methods - @cache[:instance_methods][full_name] = instance_methods - end - - return if @dry_run - - FileUtils.rm_f to_delete - - marshal = Marshal.dump klass - - open path, 'wb' do |io| - io.write marshal - end - end - - ## - # Writes the ri data for +method+ on +klass+ - - def save_method klass, method - full_name = klass.full_name - - FileUtils.mkdir_p class_path(full_name) unless @dry_run - - cache = if method.singleton then - @cache[:class_methods] - else - @cache[:instance_methods] - end - cache[full_name] ||= [] - cache[full_name] << method.name - - return if @dry_run - - marshal = Marshal.dump method - - open method_file(full_name, method.full_name), 'wb' do |io| - io.write marshal - end - end + Store = RDoc::Store # :nodoc: end |