1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

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
This commit is contained in:
dave 2003-12-16 05:44:25 +00:00
parent dcd30a1236
commit c5bbcadbe6
12 changed files with 1203 additions and 9 deletions

View file

@ -84,6 +84,7 @@ bcc32/setup.mak
bin/erb
bin/irb
bin/rdoc
bin/ri
bin/testrb
cygwin/GNUmakefile.in
djgpp/GNUmakefile.in
@ -231,6 +232,7 @@ lib/rdoc/tokenstream.rb
lib/rdoc/dot/dot.rb
lib/rdoc/generators/chm_generator.rb
lib/rdoc/generators/html_generator.rb
lib/rdoc/generators/ri_generator.rb
lib/rdoc/generators/xml_generator.rb
lib/rdoc/generators/template/chm/chm.rb
lib/rdoc/generators/template/html/css2.rb
@ -246,6 +248,7 @@ lib/rdoc/markup/simple_markup/fragments.rb
lib/rdoc/markup/simple_markup/inline.rb
lib/rdoc/markup/simple_markup/lines.rb
lib/rdoc/markup/simple_markup/preprocess.rb
lib/rdoc/markup/simple_markup/to_flow.rb
lib/rdoc/markup/simple_markup/to_html.rb
lib/rdoc/markup/simple_markup/to_latex.rb
lib/rdoc/markup/test/AllTests.rb
@ -256,6 +259,13 @@ lib/rdoc/parsers/parse_f95.rb
lib/rdoc/parsers/parse_rb.rb
lib/rdoc/parsers/parse_simple.rb
lib/rdoc/parsers/parserfactory.rb
lib/rdoc/ri/ri_cache.rb
lib/rdoc/ri/ri_descriptions.rb
lib/rdoc/ri/ri_formatter.rb
lib/rdoc/ri/ri_paths.rb
lib/rdoc/ri/ri_reader.rb
lib/rdoc/ri/ri_util.rb
lib/rdoc/ri/ri_writer.rb
lib/readbytes.rb
lib/resolv-replace.rb
lib/resolv.rb

201
bin/ri Executable file
View file

@ -0,0 +1,201 @@
#!/usr/bin/env ruby
# usage:
#
# ri name...
#
# where name can be
#
# Class | Class::method | Class#method | Class.method | method
#
# All names may be abbreviated to their minimum unbiguous form. If a name
# _is_ ambiguous, all valid options will be listed.
#
require 'rdoc/ri/ri_paths'
require 'rdoc/ri/ri_cache'
require 'rdoc/ri/ri_util'
require 'rdoc/ri/ri_reader'
require 'rdoc/ri/ri_formatter'
######################################################################
def display_usage
File.open(__FILE__) do |f|
f.gets
puts $1 while (f.gets =~ /^# ?(.*)/)
end
exit
end
######################################################################
class RiDisplay
def initialize
paths = RI::Paths::PATH
if paths.empty?
$stderr.puts "No ri documentation found in:"
[ RI::Paths::SYSDIR, RI::Paths::SITEDIR, RI::Paths::HOMEDIR].each do |d|
$stderr.puts " #{d}"
end
$stderr.puts "\nIs ri correctly installed?"
exit 1
end
@ri_reader = RI::RiReader.new(RI::RiCache.new(paths))
@formatter = RI::RiFormatter.new(72, " ")
end
######################################################################
def display_params(method)
params = method.params
if params[0,1] == "("
if method.is_singleton
params = method.full_name + params
else
params = method.name + params
end
end
@formatter.wrap(params)
end
######################################################################
def display_flow(flow)
if !flow || flow.empty?
@formatter.wrap("(no description...)")
else
@formatter.display_flow(flow)
end
end
######################################################################
def display_method_info(method_entry)
method = @ri_reader.get_method(method_entry)
@formatter.draw_line(method.full_name)
display_params(method)
@formatter.draw_line
display_flow(method.comment)
if method.aliases && !method.aliases.empty?
@formatter.blankline
aka = "(also known as "
aka << method.aliases.map {|a| a.name }.join(", ")
aka << ")"
@formatter.wrap(aka)
end
end
######################################################################
def display_class_info(class_entry)
klass = @ri_reader.get_class(class_entry)
@formatter.draw_line("Class: " + klass.full_name)
display_flow(klass.comment)
@formatter.draw_line
unless klass.constants.empty?
@formatter.blankline
@formatter.wrap("Constants:", "")
len = 0
klass.constants.each { |c| len = c.name.length if c.name.length > len }
len += 2
klass.constants.each do |c|
@formatter.wrap(c.value,
@formatter.indent+((c.name+":").ljust(len)))
end
end
unless klass.method_list.empty?
@formatter.blankline
@formatter.wrap("Methods:", "")
@formatter.wrap(klass.method_list.map{|m| m.name}.sort.join(', '))
end
unless klass.attributes.empty?
@formatter.blankline
@formatter.wrap("Attributes:", "")
@formatter.wrap(klass.attributes.map{|a| a.name}.sort.join(', '))
end
end
######################################################################
# If the list of matching methods contains exactly one entry, or
# if it contains an entry that exactly matches the requested method,
# then display that entry, otherwise display the list of
# matching method names
def report_method_stuff(requested_method_name, methods)
if methods.size == 1
display_method_info(methods[0])
elsif (entry = methods.find {|m| m.name == requested_method_name})
display_method_info(entry)
else
puts "More than one method matched your request. You can refine"
puts "your search by asking for information on one of:\n\n"
@formatter.wrap(methods.map {|m| m.full_name} .join(", "))
end
end
######################################################################
def report_class_stuff(namespaces)
if namespaces.size > 1
puts "More than one class or module matched your request. You can refine"
puts "your search by asking for information on one of:\n\n"
puts @formatter.wrap("", namespaces.map {|m| m.full_name} .join(", "))
else
class_desc = @ri_reader.get_class(namespaces[0])
display_class_info(namespaces[0])
end
end
######################################################################
def display_info_for(arg)
desc = NameDescriptor.new(arg)
namespaces = @ri_reader.top_level_namespace
for class_name in desc.class_names
namespaces = @ri_reader.lookup_namespace_in(class_name, namespaces)
if namespaces.empty?
raise RiError.new("Nothing known about #{arg}")
end
end
if desc.method_name.nil?
report_class_stuff(namespaces)
else
methods = @ri_reader.find_methods(desc.method_name,
desc.is_class_method,
namespaces)
if methods.empty?
raise RiError.new("Nothing known about #{arg}")
else
report_method_stuff(desc.method_name, methods)
end
end
end
end
######################################################################
if ARGV.size.zero?
display_usage
else
ri = RiDisplay.new
begin
ARGV.each do |arg|
ri.display_info_for(arg)
end
rescue RiError => e
$stderr.puts(e.message)
exit(1)
end
end

View file

@ -0,0 +1,202 @@
# We're responsible for generating all the HTML files
# from the object tree defined in code_objects.rb. We
# generate:
#
# [files] an html file for each input file given. These
# input files appear as objects of class
# TopLevel
#
# [classes] an html file for each class or module encountered.
# These classes are not grouped by file: if a file
# contains four classes, we'll generate an html
# file for the file itself, and four html files
# for the individual classes.
#
# [indices] we generate three indices for files, classes,
# and methods. These are displayed in a browser
# like window with three index panes across the
# top and the selected description below
#
# Method descriptions appear in whatever entity (file, class,
# or module) that contains them.
#
# We generate files in a structure below a specified subdirectory,
# normally +doc+.
#
# opdir
# |
# |___ files
# | |__ per file summaries
# |
# |___ classes
# |__ per class/module descriptions
#
# HTML is generated using the Template class.
#
require 'ftools'
require 'rdoc/options'
require 'rdoc/template'
require 'rdoc/markup/simple_markup'
require 'rdoc/markup/simple_markup/to_flow'
require 'cgi'
require 'rdoc/ri/ri_writer'
require 'rdoc/ri/ri_descriptions'
module Generators
class RIGenerator
# Generators may need to return specific subclasses depending
# on the options they are passed. Because of this
# we create them using a factory
def RIGenerator.for(options)
new(options)
end
class <<self
protected :new
end
# Set up a new HTML generator. Basically all we do here is load
# up the correct output temlate
def initialize(options) #:not-new:
@options = options
@ri_writer = RI::RiWriter.new(options.op_dir)
@markup = SM::SimpleMarkup.new
@to_flow = SM::ToFlow.new
end
##
# Build the initial indices and output objects
# based on an array of TopLevel objects containing
# the extracted information.
def generate(toplevels)
RDoc::TopLevel.all_classes_and_modules.each do |cls|
process_class(cls)
end
end
def process_class(from_class)
generate_class_info(from_class)
# now recure into this classes constituent classess
from_class.each_classmodule do |mod|
process_class(mod)
end
end
def generate_class_info(cls)
cls_desc = RI::ClassDescription.new
cls_desc.name = cls.name
cls_desc.full_name = cls.full_name
cls_desc.superclass = cls.superclass
cls_desc.comment = markup(cls.comment)
cls_desc.method_list = method_list(cls)
cls_desc.attributes =cls.attributes.sort.map do |a|
RI::Attribute.new(a.name, a.rw, markup(a.comment))
end
cls_desc.constants = cls.constants.map do |c|
RI::Constant.new(c.name, c.value, markup(c.comment))
end
cls_desc.includes = cls.includes.map do |i|
RI::IncludedModule.new(i.name)
end
methods = method_list(cls)
cls_desc.method_list = methods.map do |m|
RI::MethodSummary.new(m.name)
end
@ri_writer.remove_class(cls_desc)
@ri_writer.add_class(cls_desc)
methods.each do |m|
generate_method_info(cls_desc, m)
end
end
def generate_method_info(cls_desc, method)
meth_desc = RI::MethodDescription.new
meth_desc.name = method.name
meth_desc.full_name = cls_desc.full_name
if method.singleton
meth_desc.full_name += "::"
else
meth_desc.full_name += "#"
end
meth_desc.full_name << method.name
meth_desc.comment = markup(method.comment)
meth_desc.params = params_of(method)
meth_desc.visibility = method.visibility.to_s
meth_desc.is_singleton = method.singleton
meth_desc.block_params = method.block_params
meth_desc.aliases = method.aliases.map do |a|
RI::AliasName.new(a.name)
end
@ri_writer.add_method(cls_desc, meth_desc)
end
private
# return a list of methods that we'll be documenting
def method_list(cls)
list = cls.method_list
unless @options.show_all
list = list.find_all do |m|
m.visibility == :public || m.force_documentation
end
end
list.sort
end
def params_of(method)
p = method.params.gsub(/\s*\#.*/, '')
p = p.tr("\n", " ").squeeze(" ")
p = "(" + p + ")" unless p[0] == ?(
if (block = method.block_params)
block.gsub!(/\s*\#.*/, '')
block = block.tr("\n", " ").squeeze(" ")
if block[0] == ?(
block.sub!(/^\(/, '').sub!(/\)/, '')
end
p << " {|#{block.strip}| ...}"
end
p
end
def markup(comment)
return nil if !comment || comment.empty?
# Convert leading comment markers to spaces, but only
# if all non-blank lines have them
if comment =~ /^(?>\s*)[^\#]/
content = comment
else
content = comment.gsub(/^\s*(#+)/) { $1.tr('#',' ') }
end
@markup.convert(content, @to_flow)
end
end
end

View file

@ -0,0 +1,187 @@
require 'rdoc/markup/simple_markup/fragments'
require 'rdoc/markup/simple_markup/inline'
module SM
module Flow
P = Struct.new(:body)
VERB = Struct.new(:body)
RULE = Struct.new(:width)
class LIST
attr_reader :type, :contents
def initialize(type)
@type = type
@contents = []
end
def <<(stuff)
@contents << stuff
end
end
LI = Struct.new(:label, :body)
H = Struct.new(:level, :text)
end
class ToFlow
LIST_TYPE_TO_HTML = {
ListBase::BULLET => [ "<ul>", "</ul>" ],
ListBase::NUMBER => [ "<ol>", "</ol>" ],
ListBase::UPPERALPHA => [ "<ol>", "</ol>" ],
ListBase::LOWERALPHA => [ "<ol>", "</ol>" ],
ListBase::LABELED => [ "<dl>", "</dl>" ],
ListBase::NOTE => [ "<table>", "</table>" ],
}
InlineTag = Struct.new(:bit, :on, :off)
def initialize
init_tags
end
##
# Set up the standard mapping of attributes to HTML tags
#
def init_tags
@attr_tags = [
InlineTag.new(SM::Attribute.bitmap_for(:BOLD), "<b>", "</b>"),
InlineTag.new(SM::Attribute.bitmap_for(:TT), "<tt>", "</tt>"),
InlineTag.new(SM::Attribute.bitmap_for(:EM), "<em>", "</em>"),
]
end
##
# Add a new set of HTML tags for an attribute. We allow
# separate start and end tags for flexibility
#
def add_tag(name, start, stop)
@attr_tags << InlineTag.new(SM::Attribute.bitmap_for(name), start, stop)
end
##
# Given an HTML tag, decorate it with class information
# and the like if required. This is a no-op in the base
# class, but is overridden in HTML output classes that
# implement style sheets
def annotate(tag)
tag
end
##
# Here's the client side of the visitor pattern
def start_accepting
@res = []
@list_stack = []
end
def end_accepting
@res
end
def accept_paragraph(am, fragment)
@res << Flow::P.new((convert_flow(am.flow(fragment.txt))))
end
def accept_verbatim(am, fragment)
@res << Flow::VERB.new((convert_flow(am.flow(fragment.txt))))
end
def accept_rule(am, fragment)
@res << Rule.new(size)
end
def accept_list_start(am, fragment)
@list_stack.push(@res)
list = Flow::LIST.new(fragment.type)
@res << list
@res = list
end
def accept_list_end(am, fragment)
@res = @list_stack.pop
end
def accept_list_item(am, fragment)
@res << Flow::LI.new(fragment.param, convert_flow(am.flow(fragment.txt)))
end
def accept_blank_line(am, fragment)
# @res << annotate("<p />") << "\n"
end
def accept_heading(am, fragment)
@res << Flow::H.new(fragment.head_level, am.flow(fragment.txt))
end
#######################################################################
private
#######################################################################
def on_tags(res, item)
attr_mask = item.turn_on
return if attr_mask.zero?
@attr_tags.each do |tag|
if attr_mask & tag.bit != 0
res << annotate(tag.on)
end
end
end
def off_tags(res, item)
attr_mask = item.turn_off
return if attr_mask.zero?
@attr_tags.reverse_each do |tag|
if attr_mask & tag.bit != 0
res << annotate(tag.off)
end
end
end
def convert_flow(flow)
res = ""
flow.each do |item|
case item
when String
res << convert_string(item)
when AttrChanger
off_tags(res, item)
on_tags(res, item)
when Special
res << convert_special(item)
else
raise "Unknown flow element: #{item.inspect}"
end
end
res
end
# some of these patterns are taken from SmartyPants...
def convert_string(item)
CGI.escapeHTML(item)
end
def convert_special(special)
handled = false
Attribute.each_name_of(special.type) do |name|
method_name = "handle_special_#{name}"
if self.respond_to? method_name
special.text = send(method_name, special)
handled = true
end
end
raise "Unhandled special: #{special}" unless handled
special.text
end
end
end

View file

@ -1,6 +1,8 @@
# We handle the parsing of options, and subsequently as a singleton
# object to be queried for option values
require "rdoc/ri/ri_paths"
class Options
require 'singleton'
@ -178,6 +180,17 @@ class Options
[ "--quiet", "-q", nil,
"don't show progress as we parse" ],
[ "--ri", "-r", nil,
"generate output for use by 'ri.' The files are\n" +
"stored in the '.rdoc' directory under your home\n"+
"directory unless overridden by a subsequent\n" +
"--op parameter, so no special privileges are needed." ],
[ "--ri-site", "-R", nil,
"generate output for use by 'ri.' The files are\n" +
"stored in a site-wide directory, making them accessible\n"+
"to others, so special privileges are needed." ],
[ "--show-hash", "-H", nil,
"A name of the form #name in a comment\n" +
"is a possible hyperlink to an instance\n" +
@ -391,15 +404,7 @@ class Options
when "--fmt"
@generator_name = arg.downcase
@generator = generators[@generator_name]
if !@generator
OptionList.error("Invalid output formatter")
end
if @generator_name == "xml"
@all_one_file = true
@inline_source = true
end
setup_generator(generators)
when "--help"
OptionList.usage(generators.keys)
@ -417,6 +422,11 @@ class Options
when "--include"
@rdoc_include.concat arg.split(/\s*,\s*/)
when "--ri", "--ri-site"
@generator_name = "ri"
@op_dir = opt == "--ri" ? RI::Paths::HOMEDIR : RI::Paths::SITEDIR
setup_generator(generators)
when "--tab-width"
begin
@tab_width = Integer(arg)
@ -479,6 +489,19 @@ class Options
private
# Set up an output generator for the format in @generator_name
def setup_generator(generators)
@generator = generators[@generator_name]
if !@generator
OptionList.error("Invalid output formatter")
end
if @generator_name == "xml"
@all_one_file = true
@inline_source = true
end
end
# Check that the right version of 'dot' is available.
# Unfortuately this doesn't work correctly under Windows NT,
# so we'll bypass the test under Windows

145
lib/rdoc/ri/ri_cache.rb Normal file
View file

@ -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

View file

@ -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

170
lib/rdoc/ri/ri_formatter.rb Normal file
View file

@ -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

41
lib/rdoc/ri/ri_paths.rb Normal file
View file

@ -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

46
lib/rdoc/ri/ri_reader.rb Normal file
View file

@ -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

63
lib/rdoc/ri/ri_util.rb Normal file
View file

@ -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

48
lib/rdoc/ri/ri_writer.rb Normal file
View file

@ -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