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:
parent
dcd30a1236
commit
c5bbcadbe6
12 changed files with 1203 additions and 9 deletions
10
MANIFEST
10
MANIFEST
|
@ -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
201
bin/ri
Executable 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
|
202
lib/rdoc/generators/ri_generator.rb
Normal file
202
lib/rdoc/generators/ri_generator.rb
Normal 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
|
187
lib/rdoc/markup/simple_markup/to_flow.rb
Normal file
187
lib/rdoc/markup/simple_markup/to_flow.rb
Normal 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
|
|
@ -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
145
lib/rdoc/ri/ri_cache.rb
Normal 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
|
58
lib/rdoc/ri/ri_descriptions.rb
Normal file
58
lib/rdoc/ri/ri_descriptions.rb
Normal 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
170
lib/rdoc/ri/ri_formatter.rb
Normal 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(/>/, '>').
|
||||
gsub(/</, '<').
|
||||
gsub(/"/, '"').
|
||||
gsub(/&/, '&')
|
||||
|
||||
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
41
lib/rdoc/ri/ri_paths.rb
Normal 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
46
lib/rdoc/ri/ri_reader.rb
Normal 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
63
lib/rdoc/ri/ri_util.rb
Normal 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
48
lib/rdoc/ri/ri_writer.rb
Normal 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
|
Loading…
Reference in a new issue