class FastRI::RiIndex
This class provides the same functionality as RiReader, with some improvements:
-
lower memory consumption
-
ability to handle information from different sources separately.
Some operations can be restricted to a given “scope”, that is, a “RI DB directory”. This allows you to e.g. look for all the instance methods in String defined by a package.
Such operations take a scope
argument, which is either an
integer which indexes the source in paths, or a name identifying the
source (either “system” or a package name). If scope == nil
,
the information from all sources is merged.
Constants
- MAGIC
Attributes
Public Class Methods
# File lib/fastri/ri_index.rb, line 209 def self.new_from_IO(anIO) obj = new obj.load(anIO) obj end
# File lib/fastri/ri_index.rb, line 203 def self.new_from_paths(paths = nil) obj = new obj.rebuild_index(paths) obj end
Public Instance Methods
Return a list of all classes, modules, and methods.
# File lib/fastri/ri_index.rb, line 375 def all_names(scope = nil) full_class_names(scope).concat(full_method_names(scope)) end
Serializes index to the given IO.
# File lib/fastri/ri_index.rb, line 287 def dump(anIO) anIO.puts MAGIC anIO.puts "Sources:" @paths.zip(@gem_names).each{|p,g| anIO.puts "%-30s %s" % [g, p]} anIO.puts "=" * 80 anIO.puts "Namespaces:" anIO.puts @namespace_array anIO.puts "=" * 80 anIO.puts "Methods:" anIO.puts @method_array anIO.puts "=" * 80 end
Returns the ClassDescription associated to the given
full_name
.
# File lib/fastri/ri_index.rb, line 318 def find_class_by_name(full_name, scope = nil) entry = get_entry(@namespace_array, full_name, ClassEntry, scope) return nil unless entry && entry.full_name == full_name get_class(entry) end
Returns the MethodDescription associated to the given
full_name
. Only the first definition is returned when
scope = nil
.
# File lib/fastri/ri_index.rb, line 326 def find_method_by_name(full_name, scope = nil) entry = get_entry(@method_array, full_name, MethodEntry, scope) return nil unless entry && entry.full_name == full_name get_method(entry) end
Returns an array of MethodEntry
objects, corresponding to the methods in the ClassEntry objects in the
namespaces
array.
# File lib/fastri/ri_index.rb, line 334 def find_methods(name, is_class_method, namespaces) result = [] namespaces.each do |ns| result.concat ns.methods_matching(name, is_class_method) end result end
Return the names of all classes and modules.
# File lib/fastri/ri_index.rb, line 365 def full_class_names(scope = nil) all_entries(@namespace_array, scope) end
Return the names of all methods.
# File lib/fastri/ri_index.rb, line 370 def full_method_names(scope = nil) all_entries(@method_array, scope) end
Return a ClassDescription for a given ClassEntry.
# File lib/fastri/ri_index.rb, line 350 def get_class(class_entry) result = nil for path in class_entry.path_names path = RI::RiWriter.class_desc_path(path, class_entry) desc = File.open(path) {|f| RI::Description.deserialize(f) } if result result.merge_in(desc) else result = desc end end result end
Returns the ClassEntry associated to
the given full_name
.
# File lib/fastri/ri_index.rb, line 392 def get_class_entry(full_name, scope = nil) entry = get_entry(@namespace_array, full_name, ClassEntry, scope) return nil unless entry && entry.full_name == full_name entry end
Return the MethodDescription for a given MethodEntry by deserializing the YAML.
# File lib/fastri/ri_index.rb, line 344 def get_method(method_entry) path = method_entry.path_name File.open(path) { |f| RI::Description.deserialize(f) } end
Returns the MethodEntry associated
to the given full_name
.
# File lib/fastri/ri_index.rb, line 399 def get_method_entry(full_name, scope = nil) entry = get_entry(@method_array, full_name, MethodEntry, scope) return nil unless entry && entry.full_name == full_name entry end
Load the index from the given IO. It must contain a textual representation generated by dump.
# File lib/fastri/ri_index.rb, line 259 def load(anIO) header = anIO.gets raise "Invalid format." unless header.chomp == MAGIC anIO.gets # discard "Sources:" paths = [] gem_names = [] until (line = anIO.gets).index("=" * 80) == 0 gemname, path = line.strip.split(/\s+/) paths << path gem_names << gemname end anIO.gets # discard "Namespaces:" namespace_array = [] until (line = anIO.gets).index("=" * 80) == 0 namespace_array << line end anIO.gets # discard "Methods:" method_array = [] until (line = anIO.gets).index("=" * 80) == 0 method_array << line end @paths = paths @gem_names = gem_names @namespace_array = namespace_array @method_array = method_array end
Returns an array of ClassEntry
objects whose names match target
, and which correspond to the
namespaces contained in namespaces
. namespaces
is
an array of ClassEntry objects.
# File lib/fastri/ri_index.rb, line 309 def lookup_namespace_in(target, namespaces) result = [] namespaces.each do |ns| result.concat(ns.contained_modules_matching(target)) end result end
Returns array of MethodEntry objects under class_entry_or_name (either String or ClassEntry) in the hierarchy.
# File lib/fastri/ri_index.rb, line 446 def methods_under(class_entry_or_name, recursive, scope = nil) methods_under_matching(class_entry_or_name, //, recursive, scope) end
Returns array of MethodEntry objects
under class_entry_or_name (either String or ClassEntry) in the hierarchy whose
full_name
matches the given regexp.
# File lib/fastri/ri_index.rb, line 453 def methods_under_matching(class_entry_or_name, regexp, recursive, scope = nil) case class_entry_or_name when ClassEntry full_name = class_entry_or_name.full_name else full_name = class_entry_or_name end method_entry = get_entry(@method_array, full_name, MethodEntry) return [] unless method_entry ret = [] re1, re2 = matching_regexps_method(full_name) (method_entry.index...@method_array.size).each do |i| entry = @method_array[i] break unless re1 =~ entry next if !recursive && re2 !~ entry full_name = entry[/\S+/] next unless regexp =~ full_name if scope sources = method_sources(i) if sources.include?(sindex = scope_to_sindex(scope)) ret << MethodEntry.new(self, full_name, i, sindex) end else ret << MethodEntry.new(self, full_name, i, nil) end end ret end
Returns array of ClassEntry objects under class_entry_or_name (either String or ClassEntry) in the hierarchy.
# File lib/fastri/ri_index.rb, line 407 def namespaces_under(class_entry_or_name, recursive, scope = nil) namespaces_under_matching(class_entry_or_name, //, recursive, scope) end
Returns array of ClassEntry objects
under class_entry_or_name (either String or ClassEntry) in the hierarchy whose
full_name
matches the given regexp.
# File lib/fastri/ri_index.rb, line 414 def namespaces_under_matching(class_entry_or_name, regexp, recursive, scope = nil) case class_entry_or_name when ClassEntry class_entry = class_entry_or_name when "" class_entry = top_level_namespace(scope)[0] else class_entry = get_entry(@namespace_array, class_entry_or_name, ClassEntry, scope) end return [] unless class_entry ret = [] re1, re2 = matching_regexps_namespace(class_entry.full_name) (class_entry.index+1...@namespace_array.size).each do |i| entry = @namespace_array[i] break unless re1 =~ entry next if !recursive && re2 !~ entry full_name = entry[/\S+/] next unless regexp =~ full_name if scope sources = namespace_sources(i) if sources.include?(sindex = scope_to_sindex(scope)) ret << ClassEntry.new(self, full_name, i, sindex) end else ret << ClassEntry.new(self, full_name, i, nil) end end ret end
Returns the number of methods in the index.
# File lib/fastri/ri_index.rb, line 382 def num_methods @method_array.size end
Returns the number of namespaces in the index.
# File lib/fastri/ri_index.rb, line 387 def num_namespaces @namespace_array.size end
# File lib/fastri/ri_index.rb, line 215 def rebuild_index(paths = nil) @paths = paths || RI::Paths::PATH @gem_names = paths.map do |p| fullp = File.expand_path(p) gemname = nil begin require 'rubygems' Gem.path.each do |gempath| re = %r!^#{Regexp.escape(File.expand_path(gempath))}/doc/! if re =~ fullp gemname = fullp.gsub(re,"")[%r{^[^/]+}] break end end rescue LoadError # no RubyGems, no gems installed, skip it end gemname ? gemname : "system" end methods = Hash.new{|h,k| h[k] = []} namespaces = methods.clone @paths.each_with_index do |path, source_index| ri_reader = RI::RiReader.new(RI::RiCache.new(path)) obtain_classes(ri_reader.top_level_namespace.first).each{|name| namespaces[name] << source_index } obtain_methods(ri_reader.top_level_namespace.first).each{|name| methods[name] << source_index } end @method_array = methods.sort_by{|h,k| h}.map do |name, sources| "#{name} #{sources.map{|x| x.to_s}.join(' ')}" end @namespace_array = namespaces.sort_by{|h,k| h}.map do |name, sources| "#{name} #{sources.map{|x| x.to_s}.join(' ')}" end puts "@method_array: #{@method_array.size}" puts "@namespace_array: #{@namespace_array.size}" puts @method_array.inject(0){|s,x| s + x.size} puts @namespace_array.inject(0){|s,x| s + x.size} end
Returns array of Strings corresponding to the base directories of all the sources fo the given entry_or_name.
# File lib/fastri/ri_index.rb, line 484 def source_paths_for(entry_or_name) case entry_or_name when ClassEntry namespace_sources(entry_or_name.index).map{|i| @paths[i] } when MethodEntry method_sources(entry_or_name.index).map{|i| @paths[i]} when nil [] else case entry_or_name when /[#.]\S+/ method_entry = get_entry(@method_array, entry_or_name, MethodEntry, nil) source_paths_for(method_entry) when "" [] else class_entry = get_entry(@namespace_array, entry_or_name, ClassEntry, nil) source_paths_for(class_entry) end end end
Returns an array with the top level namespace.
# File lib/fastri/ri_index.rb, line 302 def top_level_namespace(scope = nil) [TopLevelEntry.new(self, "", -1, scope ? scope_to_sindex(scope) : nil)] end
Private Instance Methods
# File lib/fastri/ri_index.rb, line 515 def all_entries(array, scope) if scope wanted_sidx = scope_to_sindex(scope) chosen = array.select{|x| x[/ (.*$)/, 1].split(/\s+/).map{|x| x.to_i}.include? wanted_sidx } else chosen = array end chosen.map{|x| x[/(\S+)/]} end
# File lib/fastri/ri_index.rb, line 564 def binary_search(array, name, from = 0, to = array.size - 1) middle = (from + to) / 2 pivot = array[middle][/\S+/] if from == to if pivot.index(name) == 0 from else nil end elsif name <= pivot binary_search(array, name, from, middle) elsif name > pivot binary_search(array, name, middle+1, to) end end
# File lib/fastri/ri_index.rb, line 550 def get_entry(array, fullname, klass, scope = nil) index = binary_search(array, fullname) return nil unless index entry = array[index] sources = entry[/\S+ (.*)/,1].split(/\s+/).map{|x| x.to_i} if scope wanted_sidx = scope_to_sindex(scope) return nil unless wanted_sidx return nil unless sources.include?(wanted_sidx) return klass.new(self, entry[/\S+/], index, wanted_sidx) end klass.new(self, entry[/\S+/], index, nil) end
# File lib/fastri/ri_index.rb, line 533 def matching_regexps_method(prefix) if prefix.empty? [//, /^[#.] /] # the second should never match else [/^#{Regexp.escape(prefix)}([#.]|::)/, /^#{Regexp.escape(prefix)}([#.])\S+ / ] end end
# File lib/fastri/ri_index.rb, line 525 def matching_regexps_namespace(prefix) if prefix.empty? [//, /^[^:]+ /] else [/^#{Regexp.escape(prefix)}/, /^#{Regexp.escape(prefix)}(::|[#.])[^:]+ / ] end end
# File lib/fastri/ri_index.rb, line 511 def method_sources(index) @method_array[index][/\S+ (.*)/,1].split(/\s+/).map{|x| x.to_i} end
# File lib/fastri/ri_index.rb, line 507 def namespace_sources(index) @namespace_array[index][/\S+ (.*)/,1].split(/\s+/).map{|x| x.to_i} end
# File lib/fastri/ri_index.rb, line 580 def obtain_classes(namespace, res = []) subnamespaces = namespace.classes_and_modules subnamespaces.each do |ns| res << ns.full_name obtain_classes(ns, res) end res end
# File lib/fastri/ri_index.rb, line 589 def obtain_methods(namespace, res = []) subnamespaces = namespace.classes_and_modules subnamespaces.each do |ns| res.concat ns.all_method_names obtain_methods(ns, res) end res end
# File lib/fastri/ri_index.rb, line 541 def scope_to_sindex(scope) case scope when Integer scope else @gem_names.index(scope) end end