summaryrefslogtreecommitdiff
path: root/lib/rdoc/ri
diff options
context:
space:
mode:
authordave <dave@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2003-12-16 05:44:25 +0000
committerdave <dave@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2003-12-16 05:44:25 +0000
commitc5bbcadbe64477433a243be191c41010c7ae10dc (patch)
tree0d09db2cbe31c84eac3c29575e7008c9d7a6d57b /lib/rdoc/ri
parentdcd30a1236cdb2e06b6dd1a74a4c0a0c29549be6 (diff)
Initial load of support for ri/rdoc integration
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@5199 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/rdoc/ri')
-rw-r--r--lib/rdoc/ri/ri_cache.rb145
-rw-r--r--lib/rdoc/ri/ri_descriptions.rb58
-rw-r--r--lib/rdoc/ri/ri_formatter.rb170
-rw-r--r--lib/rdoc/ri/ri_paths.rb41
-rw-r--r--lib/rdoc/ri/ri_reader.rb46
-rw-r--r--lib/rdoc/ri/ri_util.rb63
-rw-r--r--lib/rdoc/ri/ri_writer.rb48
7 files changed, 571 insertions, 0 deletions
diff --git a/lib/rdoc/ri/ri_cache.rb b/lib/rdoc/ri/ri_cache.rb
new file mode 100644
index 00000000000..4ca976c4b96
--- /dev/null
+++ b/lib/rdoc/ri/ri_cache.rb
@@ -0,0 +1,145 @@
+module RI
+
+ class ClassEntry
+
+ attr_reader :name
+ attr_reader :path_name
+
+ def initialize(path_name, name, in_class)
+ @path_name = path_name
+ @name = name
+ @in_class = in_class
+ @class_methods = []
+ @instance_methods = []
+ @inferior_classes = []
+ end
+
+ # read in our methods and any classes
+ # and modules in our namespace. Methods are
+ # stored in files called name-c|i.yaml,
+ # where the 'name' portion is the external
+ # form of the method name and the c|i is a class|instance
+ # flag
+
+ def load_from(dir)
+ Dir.foreach(dir) do |name|
+ next if name =~ /^\./
+
+ # convert from external to internal form, and
+ # extract the instance/class flag
+
+ if name =~ /^(.*?)-(c|i).yaml$/
+ external_name = $1
+ is_class_method = $2 == "c"
+ internal_name = external_name
+ list = is_class_method ? @class_methods : @instance_methods
+ path = File.join(dir, name)
+ list << MethodEntry.new(path, internal_name, is_class_method, self)
+ else
+ full_name = File.join(dir, name)
+ if File.directory?(full_name)
+ inf_class = ClassEntry.new(full_name, name, self)
+ inf_class.load_from(full_name)
+ @inferior_classes << inf_class
+ end
+ end
+ end
+ end
+
+ # Return a list of any classes or modules that we contain
+ # that match a given string
+
+ def contained_modules_matching(name)
+ @inferior_classes.find_all {|c| c.name[name]}
+ end
+
+ # return the list of local methods matching name
+ # We're split into two because we need distinct behavior
+ # when called from the toplevel
+ def methods_matching(name)
+ local_methods_matching(name)
+ end
+
+ # Find methods matching 'name' in ourselves and in
+ # any classes we contain
+ def recursively_find_methods_matching(name)
+ res = local_methods_matching(name)
+ @inferior_classes.each do |c|
+ res.concat(c.recursively_find_methods_matching(name))
+ end
+ res
+ end
+
+
+ # Return our full name
+ def full_name
+ res = @in_class.full_name
+ res << "::" unless res.empty?
+ res << @name
+ end
+
+ private
+
+ # Return a list of all our methods matching a given string
+ def local_methods_matching(name)
+ @class_methods.find_all {|m| m.name[name] } +
+ @instance_methods.find_all {|m| m.name[name] }
+ end
+ end
+
+ # A TopLevelEntry is like a class entry, but when asked to search
+ # for methods searches all classes, not just itself
+
+ class TopLevelEntry < ClassEntry
+ def methods_matching(name)
+ res = recursively_find_methods_matching(name)
+ end
+
+ def full_name
+ ""
+ end
+ end
+
+ class MethodEntry
+ attr_reader :name
+ attr_reader :path_name
+
+ def initialize(path_name, name, is_class_method, in_class)
+ @path_name = path_name
+ @name = name
+ @is_class_method = is_class_method
+ @in_class = in_class
+ end
+
+ def full_name
+ res = @in_class.full_name
+ unless res.empty?
+ if @is_class_method
+ res << "::"
+ else
+ res << "#"
+ end
+ end
+ res << @name
+ end
+ end
+
+ # We represent everything know about all 'ri' files
+ # accessible to this program
+
+ class RiCache
+
+ attr_reader :toplevel
+
+ def initialize(dirs)
+ # At the top level we have a dummy module holding the
+ # overall namespace
+ @toplevel = TopLevelEntry.new('', '::', nil)
+
+ dirs.each do |dir|
+ @toplevel.load_from(dir)
+ end
+ end
+
+ end
+end
diff --git a/lib/rdoc/ri/ri_descriptions.rb b/lib/rdoc/ri/ri_descriptions.rb
new file mode 100644
index 00000000000..e80b4ebe05b
--- /dev/null
+++ b/lib/rdoc/ri/ri_descriptions.rb
@@ -0,0 +1,58 @@
+require 'yaml'
+
+module RI
+ Alias = Struct.new(:old_name, :new_name)
+ AliasName = Struct.new(:name)
+ Attribute = Struct.new(:name, :rw, :comment)
+ Constant = Struct.new(:name, :value, :comment)
+ IncludedModule = Struct.new(:name)
+
+ class MethodSummary
+ attr_accessor :name
+ def initialize(name="")
+ @name = name
+ end
+
+ def <=>(other)
+ self.name <=> other.name
+ end
+ end
+
+
+ class Description
+ attr_accessor :name
+ attr_accessor :full_name
+ attr_accessor :comment
+
+ def serialize
+ self.to_yaml
+ end
+
+ def Description.deserialize(from)
+ YAML.load(from)
+ end
+ end
+
+ class ClassDescription < Description
+
+ attr_accessor :method_list
+ attr_accessor :attributes
+ attr_accessor :constants
+ attr_accessor :superclass
+ attr_accessor :includes
+
+ end
+
+ class MethodDescription < Description
+
+ attr_accessor :is_class_method
+ attr_accessor :visibility
+ attr_accessor :block_params
+ attr_accessor :is_singleton
+ attr_accessor :aliases
+ attr_accessor :is_alias_for
+ attr_accessor :params
+
+ end
+
+end
diff --git a/lib/rdoc/ri/ri_formatter.rb b/lib/rdoc/ri/ri_formatter.rb
new file mode 100644
index 00000000000..052ac879063
--- /dev/null
+++ b/lib/rdoc/ri/ri_formatter.rb
@@ -0,0 +1,170 @@
+module RI
+ class RiFormatter
+
+ attr_reader :indent
+
+ def initialize(width, indent)
+ @width = width
+ @indent = indent
+ end
+
+
+ ######################################################################
+
+ def draw_line(label=nil)
+ len = @width
+ len -= (label.size+1) if label
+ print "-"*len
+ print(" ", label) if label
+ puts
+ end
+
+ ######################################################################
+
+ def wrap(txt, prefix=@indent, linelen=@width)
+ return unless txt && !txt.empty?
+ work = txt.dup
+ textLen = linelen - prefix.length
+ patt = Regexp.new("^(.{0,#{textLen}})[ \n]")
+ next_prefix = prefix.tr("^ ", " ")
+
+ res = []
+
+ while work.length > textLen
+ if work =~ patt
+ res << $1
+ work.slice!(0, $&.length)
+ else
+ res << work.slice!(0, textLen)
+ end
+ end
+ res << work if work.length.nonzero?
+ puts (prefix + res.join("\n" + next_prefix))
+ end
+
+ ######################################################################
+
+ def blankline
+ puts
+ end
+
+ ######################################################################
+
+ # convert HTML entities back to ASCII
+ def conv_html(txt)
+ txt.
+ gsub(%r{<tt>(.*?)</tt>}) { "+#$1+" } .
+ gsub(%r{<b>(.*?)</b>}) { "*#$1*" } .
+ gsub(%r{<i>(.*?)</i>}) { "_#$1_" } .
+ gsub(/&gt;/, '>').
+ gsub(/&lt;/, '<').
+ gsub(/&quot;/, '"').
+ gsub(/&amp;/, '&')
+
+ end
+
+ ######################################################################
+
+ def display_list(list)
+ case list.type
+
+ when SM::ListBase::BULLET
+ prefixer = proc { |ignored| @indent + "* " }
+
+ when SM::ListBase::NUMBER,
+ SM::ListBase::UPPERALPHA,
+ SM::ListBase::LOWERALPHA
+
+ start = case list.type
+ when SM::ListBase::NUMBER then 1
+ when SM::ListBase::UPPERALPHA then 'A'
+ when SM::ListBase::LOWERALPHA then 'a'
+ end
+ prefixer = proc do |ignored|
+ res = @indent + "#{start}.".ljust(4)
+ start = start.succ
+ res
+ end
+
+ when SM::ListBase::LABELED
+ prefixer = proc do |li|
+ li.label
+ end
+
+ when SM::ListBase::NOTE
+ longest = 0
+ list.contents.each do |item|
+ if item.kind_of?(SM::Flow::LI) && item.label.length > longest
+ longest = item.label.length
+ end
+ end
+
+ prefixer = proc do |li|
+ @indent + li.label.ljust(longest+1)
+ end
+
+ else
+ fail "unknown list type"
+
+ end
+
+ list.contents.each do |item|
+ if item.kind_of? SM::Flow::LI
+ prefix = prefixer.call(item)
+ display_flow_item(item, prefix)
+ else
+ display_flow_item(item)
+ end
+ end
+ end
+
+ ######################################################################
+
+ def display_flow_item(item, prefix=@indent)
+ case item
+ when SM::Flow::P, SM::Flow::LI
+ wrap(conv_html(item.body), prefix)
+ blankline
+
+ when SM::Flow::LIST
+ display_list(item)
+
+ when SM::Flow::VERB
+ item.body.split(/\n/).each do |line|
+ print @indent, conv_html(line), "\n"
+ end
+ blankline
+
+ when SM::Flow::H
+ text = conv_html(item.text.join)
+ case item.level
+ when 1
+ ul = "=" * text.length
+ puts
+ puts text.upcase
+ puts ul
+ puts
+
+ when 2
+ ul = "-" * text.length
+ puts
+ puts text
+ puts ul
+ puts
+ else
+ print "\n", @indent, text, "\n\n"
+ end
+ else
+ fail "Unknown flow element: #{item.class}"
+ end
+ end
+
+ ######################################################################
+
+ def display_flow(flow)
+ flow.each do |f|
+ display_flow_item(f)
+ end
+ end
+ end
+end
diff --git a/lib/rdoc/ri/ri_paths.rb b/lib/rdoc/ri/ri_paths.rb
new file mode 100644
index 00000000000..14288d9a94f
--- /dev/null
+++ b/lib/rdoc/ri/ri_paths.rb
@@ -0,0 +1,41 @@
+module RI
+
+ # Encapsulate all the strangeness to do with finding out
+ # where to find RDoc files
+ #
+ # We basically deal with three directories:
+ #
+ # 1. The 'system' documentation directory, which holds
+ # the documentation distributed with Ruby, and which
+ # is managed by the Ruby install process
+ # 2. The 'site' directory, which contains site-wide
+ # documentation added locally.
+ # 3. The 'user' documentation directory, stored under the
+ # user's own home directory.
+ #
+ # There's contention about all this, but for now:
+ #
+ # system:: $prefix/lib/ruby/<version>/doc/rdoc
+ # site:: $prefix/lib/ruby/site_dir/<version>/doc/rdoc
+ # user:: ~/.rdoc
+
+ module Paths
+
+ #:stopdoc:
+ require 'rbconfig'
+
+ DOC_DIR = "doc/rdoc"
+
+ SYSDIR = File.join(Config::CONFIG['rubylibdir'], DOC_DIR)
+ SITEDIR = File.join(Config::CONFIG['sitelibdir'], DOC_DIR)
+ homedir = ENV['HOME'] || ENV['USERPROFILE'] || ENV['HOMEPATH']
+
+ if homedir
+ HOMEDIR = File.join(homedir, ".rdoc")
+ else
+ HOMEDIR = nil
+ end
+
+ PATH = [ SYSDIR, SITEDIR, HOMEDIR ].find_all {|p| p && File.directory?(p)}
+ end
+end
diff --git a/lib/rdoc/ri/ri_reader.rb b/lib/rdoc/ri/ri_reader.rb
new file mode 100644
index 00000000000..f7e9e3074c6
--- /dev/null
+++ b/lib/rdoc/ri/ri_reader.rb
@@ -0,0 +1,46 @@
+require 'rdoc/ri/ri_descriptions'
+require 'rdoc/ri/ri_writer'
+require 'rdoc/markup/simple_markup/to_flow'
+
+module RI
+ class RiReader
+
+ def initialize(ri_cache)
+ @cache = ri_cache
+ end
+
+ def top_level_namespace
+ [ @cache.toplevel ]
+ end
+
+ def lookup_namespace_in(target, namespaces)
+ result = []
+ for n in namespaces
+ result.concat(n.contained_modules_matching(target))
+ end
+ result
+ end
+
+ def find_methods(name, is_class_method, namespaces)
+ result = []
+ namespaces.each do |ns|
+ result.concat ns.methods_matching(name)
+ 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 class description
+ def get_class(class_entry)
+ path = RiWriter.class_desc_path(class_entry.path_name, class_entry)
+ File.open(path) {|f| RI::Description.deserialize(f) }
+ end
+
+ end
+end
diff --git a/lib/rdoc/ri/ri_util.rb b/lib/rdoc/ri/ri_util.rb
new file mode 100644
index 00000000000..07f79b1d628
--- /dev/null
+++ b/lib/rdoc/ri/ri_util.rb
@@ -0,0 +1,63 @@
+######################################################################
+
+class RiError < Exception; end
+#
+# Break argument into its constituent class or module names, an
+# optional method type, and a method name
+
+class NameDescriptor
+
+ attr_reader :class_names
+ attr_reader :method_name
+ attr_reader :is_class_method
+
+ # arg may be
+ # 1. a class or module name (optionally qualified with other class
+ # or module names (Kernel, File::Stat etc)
+ # 2. a method name
+ # 3. a method name qualified by a optionally fully qualified class
+ # or module name
+ #
+ # We're fairly casual about delimiters: folks can say Kernel::puts,
+ # Kernel.puts, or Kernel\#puts for example. There's one exception:
+ # if you say IO::read, we look for a class method, but if you
+ # say IO.read, we look for an instance method
+
+ def initialize(arg)
+ @class_names = []
+ separator = "."
+
+ tokens = arg.split(/\b/)
+
+ # Skip leading '::', '#' or '.', but remember it might
+ # be a method name qualifier
+ separator = tokens.shift if tokens[0] =~ /^(\.|::|#)/
+
+ # Skip leading '::', but remember we potentially have an inst
+
+ # leading stuff must be class names
+
+ while tokens[0] =~ /^[A-Z]/
+ @class_names << tokens.shift
+ unless tokens.empty?
+ separator = tokens.shift
+ end
+ end
+
+ # Now must have a single token, the method name, or an empty
+ # array
+ unless tokens.empty?
+ @method_name = tokens.shift
+ # We may now have a trailing !, ?, or = to roll into
+ # the method name
+ if !tokens.empty? && tokens[0] =~ /^[!?=]$/
+ @method_name << tokens.shift
+ end
+
+ if @method_name =~ /::|\.|#/ or !tokens.empty?
+ raise RiError.new("Bad argument: #{arg}")
+ end
+ @is_class_method = separator == "::"
+ end
+ end
+end
diff --git a/lib/rdoc/ri/ri_writer.rb b/lib/rdoc/ri/ri_writer.rb
new file mode 100644
index 00000000000..072b3acfeac
--- /dev/null
+++ b/lib/rdoc/ri/ri_writer.rb
@@ -0,0 +1,48 @@
+require 'fileutils'
+
+module RI
+ class RiWriter
+
+ def RiWriter.class_desc_path(dir, class_desc)
+ File.join(dir, "cdesc-" + class_desc.name + ".yaml")
+ end
+
+
+ def initialize(base_dir)
+ @base_dir = base_dir
+ end
+
+ def remove_class(class_desc)
+ FileUtils.rm_rf(path_to_dir(class_desc.full_name))
+ end
+
+ def add_class(class_desc)
+ dir = path_to_dir(class_desc.full_name)
+ FileUtils.mkdir_p(dir)
+ class_file_name = RiWriter.class_desc_path(dir, class_desc)
+ File.open(class_file_name, "w") do |f|
+ f.write(class_desc.serialize)
+ end
+ end
+
+ def add_method(class_desc, method_desc)
+ dir = path_to_dir(class_desc.full_name)
+ meth_file_name = File.join(dir, method_desc.name)
+ if method_desc.is_class_method
+ meth_file_name += "-c.yaml"
+ else
+ meth_file_name += "-i.yaml"
+ end
+
+ File.open(meth_file_name, "w") do |f|
+ f.write(method_desc.serialize)
+ end
+ end
+
+ private
+
+ def path_to_dir(class_name)
+ File.join(@base_dir, *class_name.split('::'))
+ end
+ end
+end