|
From: <caw...@us...> - 2007-07-31 23:37:01
|
Revision: 2907
http://rubyeclipse.svn.sourceforge.net/rubyeclipse/?rev=2907&view=rev
Author: cawilliams
Date: 2007-07-31 16:37:00 -0700 (Tue, 31 Jul 2007)
Log Message:
-----------
Modify RI view to use fastri under the covers, and to cache the listing of all classes/methods/modules
Modified Paths:
--------------
trunk/org.rubypeople.rdt.launching/build.properties
trunk/org.rubypeople.rdt.launching/plugin.xml
trunk/org.rubypeople.rdt.launching/src/org/rubypeople/rdt/launching/RubyRuntime.java
Added Paths:
-----------
trunk/org.rubypeople.rdt.launching/ruby/fastri/
trunk/org.rubypeople.rdt.launching/ruby/fastri/full_text_index.rb
trunk/org.rubypeople.rdt.launching/ruby/fastri/full_text_indexer.rb
trunk/org.rubypeople.rdt.launching/ruby/fastri/name_descriptor.rb
trunk/org.rubypeople.rdt.launching/ruby/fastri/ri_index.rb
trunk/org.rubypeople.rdt.launching/ruby/fastri/ri_service.rb
trunk/org.rubypeople.rdt.launching/ruby/fastri/util.rb
trunk/org.rubypeople.rdt.launching/ruby/fastri/version.rb
trunk/org.rubypeople.rdt.launching/ruby/fastri-server
trunk/org.rubypeople.rdt.launching/ruby/fri
Modified: trunk/org.rubypeople.rdt.launching/build.properties
===================================================================
--- trunk/org.rubypeople.rdt.launching/build.properties 2007-07-31 23:36:45 UTC (rev 2906)
+++ trunk/org.rubypeople.rdt.launching/build.properties 2007-07-31 23:37:00 UTC (rev 2907)
@@ -1,10 +1,21 @@
bin.includes = plugin.xml,\
plugin.properties,\
- ruby/*,\
launching.jar,\
.options,\
- META-INF/
+ META-INF/,\
+ ruby/,\
+ schema/
plugin = org.rubypeople.rdt.launching
plugin.name = launching
plugin.classpath = ../org.eclipse.core.runtime/runtime.jar;../org.eclipse.core.resources/resources.jar;../org.eclipse.core.boot/boot.jar;../org.eclipse.debug.core/dtcore.jar;../org.eclipse.ui/workbench.jar;../org.apache.xerces/xmlParserAPIs.jar;../org.rubypeople.rdt.core/bin;../org.rubypeople.rdt.debug.core/bin;
source.launching.jar = src/
+src.includes = ruby/,\
+ schema/,\
+ src/,\
+ plugin.xml,\
+ plugin.properties,\
+ build.properties,\
+ META-INF/,\
+ .project,\
+ .loadpath,\
+ .classpath
Modified: trunk/org.rubypeople.rdt.launching/plugin.xml
===================================================================
--- trunk/org.rubypeople.rdt.launching/plugin.xml 2007-07-31 23:36:45 UTC (rev 2906)
+++ trunk/org.rubypeople.rdt.launching/plugin.xml 2007-07-31 23:37:00 UTC (rev 2907)
@@ -19,10 +19,18 @@
<extension
point="org.eclipse.debug.core.launchConfigurationTypes">
<launchConfigurationType
- name="%LaunchConfigurationTypeRubyApplication.name"
delegate="org.rubypeople.rdt.launching.RubyLaunchDelegate"
+ id="org.rubypeople.rdt.launching.LaunchConfigurationTypeRubyApplication"
modes="run,debug"
- id="org.rubypeople.rdt.launching.LaunchConfigurationTypeRubyApplication">
+ name="%LaunchConfigurationTypeRubyApplication.name"
+ public="true"
+ sourceLocatorId="org.rubypeople.rdt.debug.ui.rubySourceLocator">
+ <fileExtension
+ default="true"
+ extension="rb"/>
+ <fileExtension
+ default="true"
+ extension="rbw"/>
</launchConfigurationType>
</extension>
<extension
@@ -63,6 +71,6 @@
id="org.rubypeople.rdt.launching.loadpathentry.variableLoadpathEntry"
class="org.rubypeople.rdt.internal.launching.VariableLoadpathEntry">
</runtimeLoadpathEntry>
- </extension>
+ </extension>
</plugin>
Added: trunk/org.rubypeople.rdt.launching/ruby/fastri/full_text_index.rb
===================================================================
--- trunk/org.rubypeople.rdt.launching/ruby/fastri/full_text_index.rb (rev 0)
+++ trunk/org.rubypeople.rdt.launching/ruby/fastri/full_text_index.rb 2007-07-31 23:37:00 UTC (rev 2907)
@@ -0,0 +1,245 @@
+# Copyright (C) 2006 Mauricio Fernandez <mf...@ac...>
+#
+
+require 'fastri/full_text_indexer'
+require 'stringio'
+
+module FastRI
+
+class FullTextIndex
+ MAX_QUERY_SIZE = 20
+ MAX_REGEXP_MATCH_SIZE = 255
+ class Result
+ attr_reader :path, :query, :index, :metadata
+
+ def initialize(searcher, query, index, path, metadata)
+ @searcher = searcher
+ @index = index
+ @query = query
+ @path = path
+ @metadata = metadata
+ end
+
+ def context(size)
+ @searcher.fetch_data(@index, 2*size+1, -size)
+ end
+
+ def text(size)
+ @searcher.fetch_data(@index, size, 0)
+ end
+ end
+
+ class << self; private :new end
+
+ DEFAULT_OPTIONS = {
+ :max_query_size => MAX_QUERY_SIZE,
+ }
+
+ def self.new_from_ios(fulltext_IO, suffix_arrray_IO, options = {})
+ new(:io, fulltext_IO, suffix_arrray_IO, options)
+ end
+
+ def self.new_from_filenames(fulltext_fname, suffix_arrray_fname, options = {})
+ new(:filenames, fulltext_fname, suffix_arrray_fname, options)
+ end
+
+ attr_reader :max_query_size
+ def initialize(type, fulltext, sarray, options)
+ options = DEFAULT_OPTIONS.merge(options)
+ case type
+ when :io
+ @fulltext_IO = fulltext
+ @sarray_IO = sarray
+ when :filenames
+ @fulltext_fname = fulltext
+ @sarray_fname = sarray
+ else raise "Unknown type"
+ end
+ @type = type
+ @max_query_size = options[:max_query_size]
+ check_magic
+ end
+
+ def lookup(term)
+ get_fulltext_IO do |fulltextIO|
+ get_sarray_IO do |sarrayIO|
+ case sarrayIO
+ when StringIO
+ num_suffixes = sarrayIO.string.size / 4 - 1
+ else
+ num_suffixes = sarrayIO.stat.size / 4 - 1
+ end
+
+ index, offset = binary_search(sarrayIO, fulltextIO, term, 0, num_suffixes)
+ if offset
+ fulltextIO.pos = offset
+ path, metadata = find_metadata(fulltextIO)
+ return Result.new(self, term, index, path, metadata) if path
+ else
+ nil
+ end
+ end
+ end
+ end
+
+ def next_match(result, term_or_regexp = "")
+ case term_or_regexp
+ when String; size = [result.query.size, term_or_regexp.size].max
+ when Regexp; size = MAX_REGEXP_MATCH_SIZE
+ end
+ get_fulltext_IO do |fulltextIO|
+ get_sarray_IO do |sarrayIO|
+ idx = result.index
+ loop do
+ idx += 1
+ str = get_string(sarrayIO, fulltextIO, idx, size)
+ upto = str.index("\0")
+ str = str[0, upto] if upto
+ break unless str.index(result.query) == 0
+ if str[term_or_regexp]
+ fulltextIO.pos = index_to_offset(sarrayIO, idx)
+ path, metadata = find_metadata(fulltextIO)
+ return Result.new(self, result.query, idx, path, metadata) if path
+ end
+ end
+ end
+ end
+ end
+
+ def next_matches(result, term_or_regexp = "")
+ case term_or_regexp
+ when String; size = [result.query.size, term_or_regexp.size].max
+ when Regexp; size = MAX_REGEXP_MATCH_SIZE
+ end
+ ret = []
+ get_fulltext_IO do |fulltextIO|
+ get_sarray_IO do |sarrayIO|
+ idx = result.index
+ loop do
+ idx += 1
+ str = get_string(sarrayIO, fulltextIO, idx, size)
+ upto = str.index("\0")
+ str = str[0, upto] if upto
+ break unless str.index(result.query) == 0
+ if str[term_or_regexp]
+ fulltextIO.pos = index_to_offset(sarrayIO, idx)
+ path, metadata = find_metadata(fulltextIO)
+ ret << Result.new(self, result.query, idx, path, metadata) if path
+ end
+ end
+ end
+ end
+
+ ret
+ end
+
+ def fetch_data(index, size, offset = 0)
+ raise "Bad offset" unless offset <= 0
+ get_fulltext_IO do |fulltextIO|
+ get_sarray_IO do |sarrayIO|
+ base = index_to_offset(sarrayIO, index)
+ actual_offset = offset
+ newsize = size
+ if base + offset < 0 # at the beginning
+ excess = (base + offset).abs # remember offset is < 0
+ newsize = size - excess
+ actual_offset = offset + excess
+ end
+ str = get_string(sarrayIO, fulltextIO, index, newsize, offset)
+ from = (str.rindex("\0", -actual_offset) || -1) + 1
+ to = (str.index("\0", -actual_offset) || 0) - 1
+ str[from..to]
+ end
+ end
+ end
+
+ private
+ def check_magic
+ get_fulltext_IO do |io|
+ io.rewind
+ header = io.read(FullTextIndexer::MAGIC.size)
+ raise "Unsupported index format." unless header
+ version = header[/\d+\.\d+\.\d+/]
+ raise "Unsupported index format." unless version
+ major, minor, teeny = version.scan(/\d+/)
+ if major != FASTRI_FT_INDEX_FORMAT_MAJOR or
+ minor > FASTRI_FT_INDEX_FORMAT_MINOR
+ raise "Unsupported index format"
+ end
+ end
+ end
+
+ def get_fulltext_IO
+ case @type
+ when :io; yield @fulltext_IO
+ when :filenames
+ File.open(@fulltext_fname, "rb"){|f| yield f}
+ end
+ end
+
+ def get_sarray_IO
+ case @type
+ when :io; yield @sarray_IO
+ when :filenames
+ File.open(@sarray_fname, "rb"){|f| yield f}
+ end
+ end
+
+ def index_to_offset(sarrayIO, index)
+ sarrayIO.pos = index * 4
+ sarrayIO.read(4).unpack("V")[0]
+ end
+
+ def find_metadata(fulltextIO)
+ oldtext = ""
+ loop do
+ text = fulltextIO.read(4096)
+ break unless text
+ if idx = text.index("\0")
+ if idx + 4 >= text.size
+ text.concat(fulltextIO.read(4096))
+ end
+ len = text[idx+1, 4].unpack("V")[0]
+ missing = idx + 5 + len - text.size
+ if missing > 0
+ text.concat(fulltextIO.read(missing))
+ end
+ footer = text[idx + 5, len - 1]
+ path, metadata = /(.*?)\0(.*)/m.match(footer).captures
+ return [path, Marshal.load(metadata)]
+ end
+ oldtext = text
+ end
+ nil
+ end
+
+ def get_string(sarrayIO, fulltextIO, index, size, off = 0)
+ sarrayIO.pos = index * 4
+ offset = sarrayIO.read(4).unpack("V")[0]
+ fulltextIO.pos = [offset + off, 0].max
+ fulltextIO.read(size)
+ end
+
+ def binary_search(sarrayIO, fulltextIO, term, from, to)
+ #puts "BINARY #{from} -- #{to}"
+ #left = get_string(sarrayIO, fulltextIO, from, @max_query_size)
+ #right = get_string(sarrayIO, fulltextIO, to, @max_query_size)
+ #puts " #{left.inspect} -- #{right.inspect}"
+ middle = (from + to) / 2
+ pivot = get_string(sarrayIO, fulltextIO, middle, @max_query_size)
+ if from == to
+ if pivot.index(term) == 0
+ sarrayIO.pos = middle * 4
+ [middle, sarrayIO.read(4).unpack("V")[0]]
+ else
+ nil
+ end
+ elsif term <= pivot
+ binary_search(sarrayIO, fulltextIO, term, from, middle)
+ elsif term > pivot
+ binary_search(sarrayIO, fulltextIO, term, middle+1, to)
+ end
+ end
+end # class FullTextIndex
+
+end # module FastRI
Property changes on: trunk/org.rubypeople.rdt.launching/ruby/fastri/full_text_index.rb
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added: trunk/org.rubypeople.rdt.launching/ruby/fastri/full_text_indexer.rb
===================================================================
--- trunk/org.rubypeople.rdt.launching/ruby/fastri/full_text_indexer.rb (rev 0)
+++ trunk/org.rubypeople.rdt.launching/ruby/fastri/full_text_indexer.rb 2007-07-31 23:37:00 UTC (rev 2907)
@@ -0,0 +1,100 @@
+# Copyright (C) 2006 Mauricio Fernandez <mf...@ac...>
+#
+
+require 'fastri/version'
+
+module FastRI
+
+class FullTextIndexer
+ WORD_RE = /[A-Za-z0-9_]+/
+ NONWORD_RE = /[^A-Za-z0-9_]+/
+ MAGIC = "FastRI full-text index #{FASTRI_FT_INDEX_FORMAT}\0"
+
+ def initialize(max_querysize)
+ @documents = []
+ @doc_hash = {}
+ @max_wordsize = max_querysize
+ end
+
+ def add_document(name, data, metadata = {})
+ @doc_hash[name] = [data, metadata.merge(:size => data.size)]
+ @documents << name
+ end
+
+ def data(name)
+ @doc_hash[name][0]
+ end
+
+ def documents
+ @documents = @documents.uniq
+ end
+
+ def preprocess(str)
+ str.gsub(/\0/,"")
+ end
+
+ require 'strscan'
+ def find_suffixes(text, offset)
+ find_suffixes_simple(text, WORD_RE, NONWORD_RE, offset)
+ end
+
+ def find_suffixes_simple(string, word_re, nonword_re, offset)
+ suffixes = []
+ sc = StringScanner.new(string)
+ until sc.eos?
+ sc.skip(nonword_re)
+ len = string.size
+ loop do
+ break if sc.pos == len
+ suffixes << offset + sc.pos
+ skipped_word = sc.skip(word_re)
+ break unless skipped_word
+ loop do
+ skipped_nonword = sc.skip(nonword_re)
+ break unless skipped_nonword
+ end
+ end
+ end
+ suffixes
+ end
+
+ require 'enumerator'
+ def build_index(full_text_IO, suffix_array_IO)
+ fulltext = ""
+ io = StringIO.new(fulltext)
+ io.write MAGIC
+ full_text_IO.write MAGIC
+ documents.each do |doc|
+ data, metadata = @doc_hash[doc]
+ io.write(data)
+ full_text_IO.write(data)
+ meta_txt = Marshal.dump(metadata)
+ footer = "\0....#{doc}\0#{meta_txt}\0"
+ footer[1,4] = [footer.size - 5].pack("V")
+ io.write(footer)
+ full_text_IO.write(footer)
+ end
+
+ scanner = StringScanner.new(fulltext)
+ scanner.scan(Regexp.new(Regexp.escape(MAGIC)))
+
+ count = 0
+ suffixes = []
+ until scanner.eos?
+ count += 1
+ start = scanner.pos
+ text = scanner.scan_until(/\0/)
+ suffixes.concat find_suffixes(text[0..-2], start)
+ len = scanner.scan(/..../).unpack("V")[0]
+ #puts "LEN: #{len} #{scanner.pos} #{scanner.string.size}"
+ #puts "#{scanner.string[scanner.pos,20].inspect}"
+ scanner.pos += len
+ #scanner.terminate if !text
+ end
+ sorted = suffixes.sort_by{|x| fulltext[x, @max_wordsize]}
+ sorted.each_slice(10000){|x| suffix_array_IO.write x.pack("V*")}
+ nil
+ end
+end # class FullTextIndexer
+
+end # module FastRI
Property changes on: trunk/org.rubypeople.rdt.launching/ruby/fastri/full_text_indexer.rb
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added: trunk/org.rubypeople.rdt.launching/ruby/fastri/name_descriptor.rb
===================================================================
--- trunk/org.rubypeople.rdt.launching/ruby/fastri/name_descriptor.rb (rev 0)
+++ trunk/org.rubypeople.rdt.launching/ruby/fastri/name_descriptor.rb 2007-07-31 23:37:00 UTC (rev 2907)
@@ -0,0 +1,71 @@
+# Copyright (C) 2006 Mauricio Fernandez <mf...@ac...>
+#
+
+module FastRI
+
+# Alternative NameDescriptor implementation which doesn't require class/module
+# names to be properly capitalized.
+#
+# Rules:
+# * <tt>#foo</tt>: instance method +foo+
+# * <tt>.foo</tt>: method +foo+ (either singleton or instance)
+# * <tt>::foo</tt>: singleton method +foo+
+# * <tt>foo::bar#bar<tt>: instance method +bar+ under <tt>foo::bar</tt>
+# * <tt>foo::bar.bar<tt>: either singleton or instance method +bar+ under
+# <tt>foo::bar</tt>
+# * <tt>foo::bar::Baz<tt>: module/class foo:bar::Baz
+# * <tt>foo::bar::baz</tt>: singleton method +baz+ from <tt>foo::bar</tt>
+# * other: raise RiError
+class NameDescriptor
+ attr_reader :class_names
+ attr_reader :method_name
+
+ # true and false have the obvious meaning. nil means we don't care
+ attr_reader :is_class_method
+
+ def initialize(arg)
+ @class_names = []
+ @method_name = nil
+ @is_class_method = nil
+
+ case arg
+ when /((?:[^:]*::)*[^:]*)(#|::|\.)(.*)$/
+ ns, sep, meth_or_class = $~.captures
+ # optimization attempt: try to guess the real capitalization,
+ # so we get a direct hit
+ @class_names = ns.split(/::/).map{|x| x[0,1] = x[0,1].upcase; x }
+ if %w[# .].include? sep
+ @method_name = meth_or_class
+ @is_class_method =
+ case sep
+ when "#"; false
+ when "."; nil
+ end
+ else
+ if ("A".."Z").include? meth_or_class[0,1] # 1.9 compatibility
+ @class_names << meth_or_class
+ else
+ @method_name = meth_or_class
+ @is_class_method = true
+ end
+ end
+ when /^[^#:.]+/
+ if ("A".."Z").include? arg[0,1]
+ @class_names = [arg]
+ else
+ @method_name = arg.dup
+ @is_class_method = nil
+ end
+ else
+ raise RiError, "Cannot create NameDescriptor from #{arg}"
+ end
+ end
+
+ # Return the full class name (with '::' between the components)
+ # or "" if there's no class name
+ def full_class_name
+ @class_names.join("::")
+ end
+end
+
+end #module FastRI
Property changes on: trunk/org.rubypeople.rdt.launching/ruby/fastri/name_descriptor.rb
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added: trunk/org.rubypeople.rdt.launching/ruby/fastri/ri_index.rb
===================================================================
--- trunk/org.rubypeople.rdt.launching/ruby/fastri/ri_index.rb (rev 0)
+++ trunk/org.rubypeople.rdt.launching/ruby/fastri/ri_index.rb 2007-07-31 23:37:00 UTC (rev 2907)
@@ -0,0 +1,601 @@
+# Copyright (C) 2006 Mauricio Fernandez <mf...@ac...>
+#
+
+require 'rdoc/ri/ri_cache'
+require 'rdoc/ri/ri_reader'
+require 'rdoc/ri/ri_descriptions'
+require 'fastri/version'
+
+
+# This is taken straight from 1.8.5's rdoc/ri/ri_descriptions.rb.
+# Older releases have a buggy #merge_in that crashes when old.comment is nil.
+if RUBY_RELEASE_DATE < "2006-06-15"
+ module ::RI # :nodoc:
+ class ModuleDescription # :nodoc:
+ remove_method :merge_in
+ # merge in another class desscription into this one
+ def merge_in(old)
+ merge(@class_methods, old.class_methods)
+ merge(@instance_methods, old.instance_methods)
+ merge(@attributes, old.attributes)
+ merge(@constants, old.constants)
+ merge(@includes, old.includes)
+ if @comment.nil? || @comment.empty?
+ @comment = old.comment
+ else
+ unless old.comment.nil? or old.comment.empty? then
+ @comment << SM::Flow::RULE.new
+ @comment.concat old.comment
+ end
+ end
+ end
+ end
+ end
+end
+
+
+module FastRI
+
+# 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 <tt>scope == nil</tt>, the information from
+# all sources is merged.
+class RiIndex
+ # Redefine RI::MethodEntry#full_name to use the following notation:
+ # Namespace::Foo.singleton_method (instead of ::). RiIndex depends on this to
+ # tell singleton methods apart.
+ class ::RI::MethodEntry # :nodoc:
+ remove_method :full_name
+ def full_name
+ res = @in_class.full_name
+ unless res.empty?
+ if @is_class_method
+ res << "."
+ else
+ res << "#"
+ end
+ end
+ res << @name
+ end
+ end
+
+ class MethodEntry
+ attr_reader :full_name, :name, :index, :source_index
+
+ def initialize(ri_index, fullname, index, source_index)
+ # index is the index in ri_index' array
+ # source_index either nil (all scopes) or the integer referencing the
+ # path (-> we'll do @ri_index.paths[@source_index])
+ @ri_index = ri_index
+ @full_name = fullname
+ @name = fullname[/[.#](.*)$/, 1]
+ @index = index
+ @source_index = source_index
+ end
+
+ # Returns the "fully resolved" file name of the yaml containing our
+ # description.
+ def path_name
+ prefix = @full_name.split(/::|[#.]/)[0..-2]
+ case @source_index
+ when nil
+ ## we'd like to do
+ #@ri_index.source_paths_for(self).map do |path|
+ # File.join(File.join(path, *prefix), RI::RiWriter.internal_to_external(@name))
+ #end
+ # but RI doesn't support merging at the method-level, so
+ path = @ri_index.source_paths_for(self).first
+ File.join(File.join(path, *prefix),
+ RI::RiWriter.internal_to_external(@name) +
+ (singleton_method? ? "-c" : "-i" ) + ".yaml")
+ else
+ path = @ri_index.paths[@source_index]
+ File.join(File.join(path, *prefix),
+ RI::RiWriter.internal_to_external(@name) +
+ (singleton_method? ? "-c" : "-i" ) + ".yaml")
+ end
+ end
+
+ def singleton_method?
+ /\.[^:]+$/ =~ @full_name
+ end
+
+ def instance_method?
+ !singleton_method?
+ end
+
+ # Returns the type of this entry (<tt>:method</tt>).
+ def type
+ :method
+ end
+ end
+
+ class ClassEntry
+ attr_reader :full_name, :name, :index, :source_index
+
+ def initialize(ri_index, fullname, index, source_index)
+ @ri_index = ri_index
+ @full_name = fullname
+ @name = fullname.split(/::/).last
+ @index = index
+ @source_index = source_index
+ end
+
+ # Returns an array of directory names holding the cdesc-Classname.yaml
+ # files.
+ def path_names
+ prefix = @full_name.split(/::/)
+ case @source_index
+ when nil
+ @ri_index.source_paths_for(self).map{|path| File.join(path, *prefix) }
+ else
+ [File.join(@ri_index.paths[@source_index], *prefix)]
+ end
+ end
+
+ # Returns nested classes and modules matching name (non-recursive).
+ def contained_modules_matching(name)
+ @ri_index.namespaces_under(self, false, @source_index).select do |x|
+ x.name[name]
+ end
+ end
+
+ # Returns all nested classes and modules (non-recursive).
+ def classes_and_modules
+ @ri_index.namespaces_under(self, false, @source_index)
+ end
+
+ # Returns nested class or module named exactly +name+ (non-recursive).
+ def contained_class_named(name)
+ contained_modules_matching(name).find{|x| x.name == name}
+ end
+
+ # Returns instance or singleton methods matching name (non-recursive).
+ def methods_matching(name, is_class_method)
+ @ri_index.methods_under(self, false, @source_index).select do |meth|
+ meth.name[name] &&
+ (is_class_method ? meth.singleton_method? : meth.instance_method?)
+ end
+ end
+
+ # Returns instance or singleton methods matching name (recursive).
+ def recursively_find_methods_matching(name, is_class_method)
+ @ri_index.methods_under(self, true, @source_index).select do |meth|
+ meth.name[name] &&
+ (is_class_method ? meth.singleton_method? : meth.instance_method?)
+ end
+ end
+
+ # Returns all methods, both instance and singleton (non-recursive).
+ def all_method_names
+ @ri_index.methods_under(self, false, @source_index).map{|meth| meth.full_name}
+ end
+
+ # Returns the type of this entry (<tt>:namespace</tt>).
+ def type
+ :namespace
+ end
+ end
+
+ class TopLevelEntry < ClassEntry
+ def methods_matching(name, is_class_method)
+ recursively_find_methods_matching(name, is_class_method)
+ end
+
+ def module_named(name)
+
+ end
+ end
+
+ attr_reader :paths
+
+ class << self; private :new end
+
+ def self.new_from_paths(paths = nil)
+ obj = new
+ obj.rebuild_index(paths)
+ obj
+ end
+
+ def self.new_from_IO(anIO)
+ obj = new
+ obj.load(anIO)
+ obj
+ end
+
+ 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
+
+=begin
+ 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
+ end
+
+ MAGIC = "FastRI index #{FASTRI_INDEX_FORMAT}"
+ # Load the index from the given IO.
+ # It must contain a textual representation generated by #dump.
+ 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
+
+ # Serializes index to the given IO.
+ 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
+#{{{ RiReader compatibility interface
+
+ # Returns an array with the top level namespace.
+ def top_level_namespace(scope = nil)
+ [TopLevelEntry.new(self, "", -1, scope ? scope_to_sindex(scope) : nil)]
+ 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.
+ def lookup_namespace_in(target, namespaces)
+ result = []
+ namespaces.each do |ns|
+ result.concat(ns.contained_modules_matching(target))
+ end
+ result
+ end
+
+ # Returns the ClassDescription associated to the given +full_name+.
+ 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 <tt>scope = nil</tt>.
+ 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.
+ 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 MethodDescription for a given MethodEntry
+ # by deserializing the YAML.
+ def get_method(method_entry)
+ path = method_entry.path_name
+ File.open(path) { |f| RI::Description.deserialize(f) }
+ end
+
+ # Return a ClassDescription for a given ClassEntry.
+ 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
+
+ # Return the names of all classes and modules.
+ def full_class_names(scope = nil)
+ all_entries(@namespace_array, scope)
+ end
+
+ # Return the names of all methods.
+ def full_method_names(scope = nil)
+ all_entries(@method_array, scope)
+ end
+
+ # Return a list of all classes, modules, and methods.
+ def all_names(scope = nil)
+ full_class_names(scope).concat(full_method_names(scope))
+ end
+
+#{{{ New (faster) interface
+
+ # Returns the number of methods in the index.
+ def num_methods
+ @method_array.size
+ end
+
+ # Returns the number of namespaces in the index.
+ def num_namespaces
+ @namespace_array.size
+ end
+
+ # Returns the ClassEntry associated to the given +full_name+.
+ 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
+
+ # Returns the MethodEntry associated to the given +full_name+.
+ 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
+
+ # Returns array of ClassEntry objects under class_entry_or_name
+ # (either String or ClassEntry) in the hierarchy.
+ 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.
+ 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 array of MethodEntry objects under class_entry_or_name
+ # (either String or ClassEntry) in the hierarchy.
+ 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.
+ 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 Strings corresponding to the base directories of all the
+ # sources fo the given entry_or_name.
+ 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
+
+ private
+ def namespace_sources(index)
+ @namespace_array[index][/\S+ (.*)/,1].split(/\s+/).map{|x| x.to_i}
+ end
+
+ def method_sources(index)
+ @method_array[index][/\S+ (.*)/,1].split(/\s+/).map{|x| x.to_i}
+ end
+
+ 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
+
+ def matching_regexps_namespace(prefix)
+ if prefix.empty?
+ [//, /^[^:]+ /]
+ else
+ [/^#{Regexp.escape(prefix)}/, /^#{Regexp.escape(prefix)}(::|[#.])[^:]+ / ]
+ end
+ end
+
+ def matching_regexps_method(prefix)
+ if prefix.empty?
+ [//, /^[#.] /] # the second should never match
+ else
+ [/^#{Regexp.escape(prefix)}([#.]|::)/, /^#{Regexp.escape(prefix)}([#.])\S+ / ]
+ end
+ end
+
+ def scope_to_sindex(scope)
+ case scope
+ when Integer
+ scope
+ else
+ @gem_names.index(scope)
+ end
+ end
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+end
+
+end #module FastRI
+
+# vi: set sw=2 expandtab:
Property changes on: trunk/org.rubypeople.rdt.launching/ruby/fastri/ri_index.rb
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added: trunk/org.rubypeople.rdt.launching/ruby/fastri/ri_service.rb
===================================================================
--- trunk/org.rubypeople.rdt.launching/ruby/fastri/ri_service.rb (rev 0)
+++ trunk/org.rubypeople.rdt.launching/ruby/fastri/ri_service.rb 2007-07-31 23:37:00 UTC (rev 2907)
@@ -0,0 +1,423 @@
+# Copyright (C) 2006 Mauricio Fernandez <mf...@ac...>
+#
+# Inspired by ri-emacs.rb by Kristof Bastiaensen <kr...@vl...>
+
+require 'rdoc/ri/ri_paths'
+require 'rdoc/ri/ri_util'
+require 'rdoc/ri/ri_formatter'
+require 'rdoc/ri/ri_display'
+
+require 'fastri/ri_index.rb'
+require 'fastri/name_descriptor'
+
+
+module FastRI
+
+class ::DefaultDisplay
+ def full_params(method)
+ method.params.split(/\n/).each do |p|
+ p.sub!(/^#{method.name}\(/o,'(')
+ unless p =~ /\b\.\b/
+ p = method.full_name + p
+ end
+ @formatter.wrap(p)
+ @formatter.break_to_newline
+ end
+ end
+end
+
+class StringRedirectedDisplay < ::DefaultDisplay
+ attr_reader :stringio, :formatter
+ def initialize(*args)
+ super(*args)
+ reset_stringio
+ end
+
+ def puts(*a)
+ @stringio.puts(*a)
+ end
+
+ def print(*a)
+ @stringio.print(*a)
+ end
+
+ def reset_stringio
+ @stringio = StringIO.new("")
+ @formatter.stringio = @stringio
+ end
+end
+
+class ::RI::TextFormatter
+ def puts(*a); @stringio.puts(*a) end
+ def print(*a); @stringio.print(*a) end
+end
+
+module FormatterRedirection
+ attr_accessor :stringio
+ def initialize(*options)
+ @stringio = StringIO.new("")
+ super
+ end
+end
+
+class RedirectedAnsiFormatter < RI::AnsiFormatter
+ include FormatterRedirection
+end
+
+class RedirectedTextFormatter < RI::TextFormatter
+ include FormatterRedirection
+end
+
+class RiService
+
+ class MatchFinder
+ def self.new
+ ret = super
+ yield ret if block_given?
+ ret
+ end
+
+ def initialize
+ @matchers = {}
+ end
+
+ def add_matcher(name, &block)
+ @matchers[name] = block
+ end
+
+ def get_matches(methods)
+ catch(:MatchFinder_return) do
+ methods.each do |name|
+ matcher = @matchers[name]
+ matcher.call(self) if matcher
+ end
+ []
+ end
+ end
+
+ def yield(matches)
+ case matches
+ when nil, []; nil
+ when Array
+ throw :MatchFinder_return, matches
+ else
+ throw :MatchFinder_return, [matches]
+ end
+ end
+ end # MatchFinder
+
+
+ Options = Struct.new(:formatter, :use_stdout, :width)
+
+ def initialize(ri_reader)
+ @ri_reader = ri_reader
+ end
+
+ DEFAULT_OBTAIN_ENTRIES_OPTIONS = {
+ :lookup_order => [
+ :exact, :exact_ci, :nested, :nested_ci, :partial, :partial_ci,
+ :nested_partial, :nested_partial_ci,
+ ],
+ }
+ def obtain_entries(descriptor, options = {})
+ options = DEFAULT_OBTAIN_ENTRIES_OPTIONS.merge(options)
+ if descriptor.class_names.empty?
+ seps = separators(descriptor.is_class_method)
+ return obtain_unqualified_method_entries(descriptor.method_name, seps,
+ options[:lookup_order])
+ end
+
+ # if we're here, some namespace was given
+ full_ns_name = descriptor.class_names.join("::")
+ if descriptor.method_name == nil
+ return obtain_namespace_entries(full_ns_name, options[:lookup_order])
+ else # both namespace and method
+ seps = separators(descriptor.is_class_method)
+ return obtain_qualified_method_entries(full_ns_name, descriptor.method_name,
+ seps, options[:lookup_order])
+ end
+ end
+
+ def completion_list(keyw)
+ return @ri_reader.full_class_names if keyw == ""
+
+ descriptor = NameDescriptor.new(keyw)
+
+ if descriptor.class_names.empty?
+ # try partial matches
+ meths = @ri_reader.methods_under_matching("", /(#|\.)#{descriptor.method_name}/, true)
+ ret = meths.map{|x| x.name}.uniq.sort
+ return ret.empty? ? nil : ret
+ end
+
+ # if we're here, some namespace was given
+ full_ns_name = descriptor.class_names.join("::")
+ if descriptor.method_name == nil && ! [?#, ?:, ?.].include?(keyw[-1])
+ # partial match
+ namespaces = @ri_reader.namespaces_under_matching("", /^#{full_ns_name}/, false)
+ ret = namespaces.map{|x| x.full_name}.uniq.sort
+ return ret.empty? ? nil : ret
+ else
+ if [?#, ?:, ?.].include?(keyw[-1])
+ seps = case keyw[-1]
+ when ?#; %w[#]
+ when ?:; %w[.]
+ when ?.; %w[. #]
+ end
+ else # both namespace and method
+ seps = separators(descriptor.is_class_method)
+ end
+ sep_re = "(" + seps.map{|x| Regexp.escape(x)}.join("|") + ")"
+ # partial
+ methods = @ri_reader.methods_under_matching(full_ns_name, /#{sep_re}#{descriptor.method_name}/, false)
+ ret = methods.map{|x| x.full_name}.uniq.sort
+ return ret.empty? ? nil : ret
+ end
+ rescue RiError
+ return nil
+ end
+
+ DEFAULT_INFO_OPTIONS = {
+ :formatter => :ansi,
+ :width => 72,
+ :extended => false,
+ }
+
+ def matches(keyword, options = {})
+ options = DEFAULT_INFO_OPTIONS.merge(options)
+ return nil if keyword.strip.empty?
+ descriptor = NameDescriptor.new(keyword)
+ ret = obtain_entries(descriptor, options).map{|x| x.full_name}
+ ret ? ret : nil
+ rescue RiError
+ return nil
+ end
+
+ def info(keyw, options = {})
+ options = DEFAULT_INFO_OPTIONS.merge(options)
+ return nil if keyw.strip.empty?
+ descriptor = NameDescriptor.new(keyw)
+ entries = obtain_entries(descriptor, options)
+
+ case entries.size
+ when 0; nil
+ when 1
+ case entries[0].type
+ when :namespace
+ capture_stdout(display(options)) do |display|
+ display.display_class_info(@ri_reader.get_class(entries[0]), @ri_reader)
+ if options[:extended]
+ methods = @ri_reader.methods_under(entries[0], true)
+ methods.each do |meth_entry|
+ display.display_method_info(@ri_reader.get_method(meth_entry))
+ end
+ end
+ end
+ when :method
+ capture_stdout(display(options)) do |display|
+ display.display_method_info(@ri_reader.get_method(entries[0]))
+ end
+ end
+ else
+ capture_stdout(display(options)) do |display|
+ formatter = display.formatter
+ formatter.draw_line("Multiple choices:")
+ formatter.blankline
+ formatter.wrap(entries.map{|x| x.full_name}.join(", "))
+ end
+ end
+ rescue RiError
+ return nil
+ end
+
+ def args(keyword, options = {})
+ options = DEFAULT_INFO_OPTIONS.merge(options)
+ return nil if keyword.strip.empty?
+ descriptor = NameDescriptor.new(keyword)
+ entries = obtain_entries(descriptor, options)
+ return nil if entries.empty? || RiIndex::ClassEntry === entries[0]
+
+ params_text = ""
+ entries.each do |entry|
+ desc = @ri_reader.get_method(entry)
+ params_text << capture_stdout(display(options)) do |display|
+ display.full_params(desc)
+ end
+ end
+ params_text
+ rescue RiError
+ return nil
+ end
+
+ # Returns a list with the names of the modules/classes that define the given
+ # method, or +nil+.
+ def class_list(keyword)
+ _class_list(keyword, '\1')
+ end
+
+ # Returns a list with the names of the modules/classes that define the given
+ # method, followed by a flag (#|::), or +nil+.
+ # e.g. ["Array#", "IO#", "IO::", ... ]
+ def class_list_with_flag(keyword)
+ r = _class_list(keyword, '\1\2')
+ r ? r.map{|x| x.gsub(/\./, "::")} : nil
+ end
+
+ # Return array of strings with the names of all known methods.
+ def all_methods
+ @ri_reader.full_method_names
+ end
+
+ # Return array of strings with the names of all known classes.
+ def all_classes
+ @ri_reader.full_class_names
+ end
+
+ private
+
+ def obtain_unqualified_method_entries(name, separators, order)
+ name = Regexp.escape(name)
+ sep_re = "(" + separators.map{|x| Regexp.escape(x)}.join("|") + ")"
+ matcher = MatchFinder.new do |m|
+ m.add_matcher(:exact) do
+ m.yield @ri_reader.methods_under_matching("", /#{sep_re}#{name}$/, true)
+ end
+ m.add_matcher(:exact_ci) do
+ m.yield @ri_reader.methods_under_matching("", /#{sep_re}#{name}$/i, true)
+ end
+ m.add_matcher(:partial) do
+ m.yield @ri_reader.methods_under_matching("", /#{sep_re}#{name}/, true)
+ end
+ m.add_matcher(:partial_ci) do
+ m.yield @ri_reader.methods_under_matching("", /#{sep_re}#{name}/i, true)
+ end
+ m.add_matcher(:anywhere) do
+ m.yield @ri_reader.methods_under_matching("", /#{sep_re}.*#{name}/, true)
+ end
+ m.add_matcher(:anywhere_ci) do
+ m.yield @ri_reader.methods_under_matching("", /#{sep_re}.*#{name}/i, true)
+ end
+ end
+ matcher.get_matches(order)
+ end
+
+ def obtain_qualified_method_entries(namespace, method, separators, order)
+ namespace, unescaped_namespace = Regexp.escape(namespace), namespace
+ method = Regexp.escape(method)
+ matcher = MatchFinder.new do |m|
+ m.add_matcher(:exact) do
+ separators.each do |sep|
+ m.yield @ri_reader.get_method_entry("#{namespace}#{sep}#{method}")
+ end
+ end
+ sep_re = "(" + separators.map{|x| Regexp.escape(x)}.join("|") + ")"
+ m.add_matcher(:exact_ci) do
+ m.yield @ri_reader.methods_under_matching("", /^#{namespace}#{sep_re}#{method}$/i, true)
+ end
+ m.add_matcher(:nested) do
+ m.yield @ri_reader.methods_under_matching("", /::#{namespace}#{sep_re}#{method}$/, true)
+ end
+ m.add_matcher(:nested_ci) do
+ m.yield @ri_reader.methods_under_matching("", /::#{namespace}#{sep_re}#{method}$/i, true)
+ end
+ m.add_matcher(:partial) do
+ m.yield @ri_reader.methods_under_matching(unescaped_namespace, /#{sep_re}#{method}/, false)
+ end
+ m.add_matcher(:partial_ci) do
+ m.yield @ri_reader.methods_under_matching("", /^#{namespace}#{sep_re}#{method}/i, true)
+ end
+ m.add_matcher(:nested_partial) do
+ m.yield @ri_reader.methods_under_matching("", /::#{namespace}#{sep_re}#{method}/, true)
+ end
+ m.add_matcher(:nested_partial_ci) do
+ m.yield @ri_reader.methods_under_matching("", /::#{namespace}#{sep_re}#{method}/i, true)
+ end
+ m.add_matcher(:namespace_partial) do
+ m.yield @ri_reader.methods_under_matching("", /^#{namespace}[^:]*#{sep_re}#{method}$/, true)
+ end
+ m.add_matcher(:namespace_partial_ci) do
+ m.yield @ri_reader.methods_under_matching("", /^#{namespace}[^:]*#{sep_re}#{method}$/i, true)
+ end
+ m.add_matcher(:full_partial) do
+ m.yield @ri_reader.methods_under_matching("", /^#{namespace}[^:]*#{sep_re}#{method}/, true)
+ end
+ m.add_matcher(:full_partial_ci) do
+ m.yield @ri_reader.methods_under_matching("", /^#{namespace}[^:]*#{sep_re}#{method}/i, true)
+ end
+ end
+ matcher.get_matches(order)
+ end
+
+ def obtain_namespace_entries(name, order)
+ name = Regexp.escape(name)
+ matcher = MatchFinder.new do |m|
+ m.add_matcher(:exact){ m.yield @ri_reader.get_class_entry(name) }
+ m.add_matcher(:exact_ci) do
+ m.yield @ri_reader.namespaces_under_matching("", /^#{name}$/i, true)
+ end
+ m.add_matcher(:nested) do
+ m.yield @ri_reader.namespaces_under_matching("", /::#{name}$/, true)
+ end
+ m.add_matcher(:nested_ci) do
+ m.yield @ri_reader.namespaces_under_matching("", /::#{name}$/i, true)
+ end
+ m.add_matcher(:partial) do
+ m.yield @ri_reader.namespaces_under_matching("", /^#{name}/, true)
+ end
+ m.add_matcher(:partial_ci) do
+ m.yield @ri_reader.namespaces_under_matching("", /^#{name}/i, true)
+ end
+ m.add_matcher(:nested_partial) do
+ m.yield @ri_reader.namespaces_under_matching("", /::#{name}[^:]*$/, true)
+ end
+ m.add_matcher(:nested_partial_ci) do
+ m.yield @ri_reader.namespaces_under_matching("", /::#{name}[^:]*$/i, true)
+ end
+ end
+ matcher.get_matches(order)
+ end
+
+ def _class_list(keyword, rep)
+ return nil if keyword.strip.empty?
+ entries = @ri_reader.methods_under_matching("", /#{keyword}$/, true)
+ return nil if entries.empty?
+
+ entries.map{|entry| entry.full_name.sub(/(.*)(#|\.).*/, rep) }.uniq
+ rescue RiError
+ return nil
+ end
+
+
+ def separators(is_class_method)
+ case is_class_method
+ when true; ["."]
+ when false; ["#"]
+ when nil; [".","#"]
+ end
+ end
+
+ DEFAULT_DISPLAY_OPTIONS = {
+ :formatter => :ansi,
+ :width => 72,
+ }
+ def display(opt = {})
+ opt = DEFAULT_DISPLAY_OPTIONS.merge(opt)
+ options = Options.new
+ options.use_stdout = true
+ case opt[:formatter].to_sym
+ when :ansi
+ options.formatter = RedirectedAnsiFormatter
+ else
+ options.formatter = RedirectedTextFormatter
+ end
+ options.width = opt[:width]
+ StringRedirectedDisplay.new(options)
+ end
+
+ def capture_stdout(display)
+ yield display
+ display.stringio.string
+ end
+end
+
+end # module FastRI
Property changes on: trunk/org.rubypeople.rdt.launching/ruby/fastri/ri_service.rb
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added: trunk/org.rubypeople.rdt.launching/ruby/fastri/util.rb
===================================================================
--- trunk/org.rubypeople.rdt.launching/ruby/fastri/util.rb (rev 0)
+++ trunk/org.rubypeople.rdt.launching/ruby/fastri/util.rb 2007-07-31 23:37:00 UTC (rev 2907)
@@ -0,0 +1,169 @@
+# Copyright (C) 2006 Mauricio Fernandez <mf...@ac...>
+
+# emulate rubygems.rb and define Gem.path if not loaded
+# This is much faster than requiring rubygems.rb, which loads way too much
+# stuff.
+unless defined? ::Gem
+ require 'rbconfig'
+ module Gem
+ def self.path
+ ENV['GEM_HOME'] || default_dir
+ end
+ def self.default_dir
+ if defined? RUBY_FRAMEWORK_VERSION
+ return File.join(File.dirname(Config::CONFIG["sitedir"]), "Gems")
+ else
+ File.join(Config::CONFIG['libdir'], 'ruby', 'gems', Config::CONFIG['ruby_version'])
+ end
+ end
+ end
+end
+# don't let rdoc/ri/ri_paths load rubygems.rb, that takes ~100ms !
+emulation = $".all?{|x| /rubygems\.rb$/ !~ x} # 1.9 compatibility
+$".unshift "rubygems.rb" if emulation
+require 'rdoc/ri/ri_paths'
+$".delete "rubygems.rb" if emulation
+require 'rdoc/ri/ri_writer'
+
+module FastRI
+module Util
+ # Return an array of <tt>[name, version, path]</tt> arrays corresponding to
+ # the last version of each installed gem. +path+ is the base path of the RI
+ # documentation from the gem. If the version cannot be determined, it will
+ # be +nil+, and the corresponding gem might be repeated in the output array
+ # (once per version).
+ def gem_directories_unique
+ return [] unless defined? Gem
+ gemdirs = Dir["#{Gem.path}/doc/*/ri"]
+ gems = Hash.new{|h,k| h[k] = []}
+ gemdirs.each do |path|
+ gemname, version = %r{/([^/]+)-(.*)/ri$}.match(path).captures
+ if gemname.nil? # doesn't follow any conventions :(
+ gems[path[%r{/([^/]+)/ri$}, 1]] << [nil, path]
+ else
+ gems[gemname] << [version, path]
+ end
+ end
+ gems.sort_by{|name, _| name}.map do |name, versions|
+ version, path = versions.sort.last
+ [name, version, File.expand_path(path)]
+ end
+ end
+ module_function :gem_directories_unique
+
+ # Return the <tt>[name, version, path]</tt> array for the gem owning the RI
+ # information stored in +path+, or +nil+.
+ def gem_info_for_path(path, gem_dir_info = FastRI::Util.gem_directories_unique)
+ path = File.expand_path(path)
+ matches = gem_dir_info.select{|name, version, gem_path| path.index(gem_path) == 0}
+ matches.sort_by{|name, version, gem_path| [gem_path.size, version, name]}.last
+ end
+ module_function :gem_info_for_path
+
+ # Return the +full_name+ (in ClassEntry or MethodEntry's sense) given a path
+ # to a .yaml file relative to a "base RI DB path".
+ def gem_relpath_to_full_name(relpath)
+ case relpath
+ when %r{^(.*)/cdesc-([^/]*)\.yaml$}
+ path, name = $~.captures
+ (path.split(%r{/})[0..-2] << name).join("::")
+ when %r{^(.*)/([^/]*)-(i|c)\.yaml$}
+ path, escaped_name, type = $~.captures
+ name = RI::RiWriter.external_to_internal(escaped_name)
+ sep = ( type == 'c' ) ? "." : "#"
+ path.gsub("/", "::") + sep + name
+ end
+ end
+ module_function :gem_relpath_to_full_name
+
+ # Returns the home directory (win32-aware).
+ def find_home
+ # stolen from RubyGems
+ ['HOME', 'USERPROFILE'].each do |homekey|
+ return ENV[homekey] if ENV[homekey]
+ end
+ if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
+ return "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}"
+ end
+ begin
+ File.expand_path("~")
+ rescue StandardError => ex
+ if File::ALT_SEPARATOR
+ "C:/"
+ else
+ "/"
+ end
+ end
+ end
+ module_function :find_home
+
+ def change_query_method_type(query)
+ if md = /\A(.*)(#|\.|::)([^#.:]+)\z/.match(query)
+ namespace, sep, meth = md.captures
+ case sep
+ when /::/ then "#{namespace}##{meth}"
+ when /#/ then "#{namespace}::#{meth}"
+ else
+ query
+ end
+ else
+ query
+ end
+ end
+ module_function :change_query_method_type
+
+
+ module MagicHelp
+ def help_method_extract(m) # :nodoc:
+ unless m.inspect =~ %r[\A#<(?:Unbound)?Method: (.*?)>\Z]
+ raise "Cannot parse result of #{m.class}#inspect: #{m.inspect}"
+ end
+ $1.sub(/\A.*?\((.*?)\)(.*)\Z/){ "#{$1}#{$2}" }.sub(/\./, "::").sub(/#<Class:(.*?)>#/) { "#{$1}::" }
+ end
+
+ def magic_help(query)
+ if query =~ /\A(.*?)(#|::|\.)([^:#.]+)\Z/
+ c, k, m = $1, $2, $3
+ mid = m
+ begin
+ c = c.split(/::/).inject(Object){|s,x| s.const_get(x)}
+ m = case k
+ when "#"
+ c.instance_method(m)
+ when "::"
+ c.method(m)
+ when "."
+ begin
+ # if it's a private_instance_method, assume it was created
+ # with module_function
+ if c.private_instance_methods.include?(m)
+ c.instance_method(m)
+ else
+ c.method(m)
+ end
+ rescue NameError
+ c.instance_method(m)
+ end
+ end
+
+ ret = help_method_extract(m)
+ if ret == 'Class#new' and
+ c.private_method_defined?(:initialize)
+ return c.name + "::new"
+ elsif ret =~ /^Kernel#/ and
+ Kernel.instance_methods(false).include? mid
+ return "Object##{mid}"
+ end
+ ret
+ rescue Exception
+ query
+ end
+ else
+ query
+ end
+ end
+ end
+
+
+end # module Util
+end # module FastRI
Property changes on: trunk/org.rubypeople.rdt.launching/ruby/fastri/util.rb
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added: trunk/org.rubypeople.rdt.launching/ruby/fastri/version.rb
===================================================================
--- trunk/org.rubypeople.rdt.launching/ruby/fastri/version.rb (rev 0)
+++ trunk/org.rubypeople.rdt.launching/ruby/fastri/version.rb 2007-07-31 23:37:00 UTC (rev 2907)
@@ -0,0 +1,13 @@
+# Copyright (C) 2006 Mauricio Fernandez <mf...@ac...>
+#
+
+module FastRI
+ FASTRI_VERSION = "0.3.0"
+ FASTRI_RELEASE_DATE = "2007-01-29"
+ FASTRI_INDEX_FORMAT = "0.1.0"
+ FASTRI_FT_INDEX_FORMAT = "1.0.0"
+ FASTRI_FT_INDEX_FORMAT_MAJOR = "1"
+ FASTRI_FT_INDEX_FORMAT_MINOR = "0"
+ FASTRI_FT_INDEX_FORMAT_TEENY = "0"
+end
+# vi: set sw=2 expandtab:
Property changes on: trunk/org.rubypeople.rdt.launching/ruby/fastri/version.rb
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added: trunk/org.rubypeople.rdt.launching/ruby/fastri-server
===================================================================
--- trunk/org.rubypeople.rdt.launching/ruby/fastri-server (rev 0)
+++ trunk/org.rubypeople.rdt.launching/ruby/fastri-server 2007-07-31 23:37:00 UTC (rev 2907)
@@ -0,0 +1,251 @@
+#!/usr/bin/env ruby
+# fastri-server: serve RI documentation over DRb
+# Copyright (C) 2006 Mauricio Fernandez <mf...@ac...>
+
+require 'fastri/version'
+require 'fastri/ri_index'
+require 'fastri/ri_service'
+require 'fastri/util'
+require 'fastri/full_text_indexer'
+require 'enumerator'
+
+FASTRI_SERVER_VERSION = "0.0.1"
+
+def make_index(index_file)
+ # The local environment is trusted --- what we don't trust is what would come
+ # from the DRb connection. This way the RiService will be untainted, and we
+ # will be able to use it with $SAFE = 1.
+ ObjectSpace.each_object{|obj| obj.untaint unless obj.frozen? }
+
+ paths = [ RI::Paths::SYSDIR, RI::Paths::SITEDIR, RI::Paths::HOMEDIR ].find_all do |p|
+ p && File.directory?(p)
+ end
+ FastRI::Util.gem_directories_unique.each do |name, version, path|
+ paths << path
+ puts "Indexing RI docs for #{name} version #{version || "unknown"}."
+ end
+
+ puts "Building index."
+ t0 = Time.new
+ #ri_reader = RI::RiReader.new(RI::RiCache.new(paths))
+ ri_reader = FastRI::RiIndex.new_from_paths(paths)
+ open(index_file, "wb"){|io| Marshal.dump ri_reader, io}
+ puts <<EOF
+Indexed:
+* #{ri_reader.num_methods} methods
+* #{ri_reader.num_namespaces} classes/modules
+Needed #{Time.new - t0} seconds
+EOF
+ ri_reader
+end
+
+def linearize(comment)
+ case s = comment["body"]
+ when String; s
+ else
+ if Array === (y = comment["contents"])
+ y.map{|z| linearize(z)}.join("\n")
+ elsif s = comment["text"]
+ s
+ else
+ nil
+ end
+ end
+end
+
+def make_full_text_index(dir)
+ paths = [ RI::Paths::SYSDIR, RI::Paths::SITEDIR, RI::Paths::HOMEDIR ].find_all do |p|
+ p && File.directory?(p)
+ end
+ FastRI::Util.gem_directories_unique.each do |name, version, path|
+ paths << path
+ puts "Indexing RI docs for #{name} version #{version || "unknown"}."
+ end
+ unless File.exist?(dir)
+ Dir.mkdir(dir)
+ end
+ indexer = FastRI::FullTextIndexer.new(40)
+ bad = 0
+ paths.each do |path|
+ Dir["#{path}/**/*.yaml"].each do |yamlfile|
+ yaml = File.read(yamlfile)
+ begin
+ data = YAML.load(yaml.gsub(/ \!.*/, ''))
+ rescue Exception
+ bad += 1
+ #puts "Couldn't load #{yamlfile}"
+ next
+ end
+
+ desc = (data['comment']||[]).map{|x| linearize(x)}.join("\n")
+ desc.gsub!(/<\/?(em|b|tt|ul|ol|table)>/, "")
+ desc.gsub!(/"/, "'")
+ desc.gsub!(/</, "<")
+ desc.gsub!(/>/, ">")
+ desc.gsub!(/&/, "&")
+ unless desc.empty?
+ indexer.add_document(yamlfile, desc)
+ end
+ end
+ end
+
+ File.open(File.join(dir, "full_text.dat"), "wb") do |fulltextIO|
+ File.open(File.join(dir, "suffixes.dat"), "wb") do |suffixesIO|
+ indexer.build_index(fulltextIO, suffixesIO)
+ end
+ end
+end
+
+#{{{ Main program
+
+require 'optparse'
+
+home = FastRI::Util.find_home
+options = {:allowed_hosts => ["127.0.0.1"], :addr => "127.0.0.1",
+ :index_file => File.join(home, ".fastri-index"),
+ :do_full_text => false,
+ :full_text_dir => File.join(home, ".fastri-fulltext"),
+}
+OptionParser.new do |opts|
+ opts.version = FastRI::FASTRI_VERSION
+ opts.release = FastRI::FASTRI_RELEASE_DATE
+ opts.banner = "Usag...
[truncated message content] |