зеркало из https://github.com/github/ruby.git
227 строки
5.5 KiB
Ruby
227 строки
5.5 KiB
Ruby
require 'rdoc/generator'
|
|
require 'rdoc/markup/to_flow'
|
|
|
|
require 'rdoc/ri/cache'
|
|
require 'rdoc/ri/reader'
|
|
require 'rdoc/ri/writer'
|
|
require 'rdoc/ri/descriptions'
|
|
|
|
class RDoc::Generator::RI
|
|
|
|
##
|
|
# Generator may need to return specific subclasses depending on the
|
|
# options they are passed. Because of this we create them using a factory
|
|
|
|
def self.for(options)
|
|
new(options)
|
|
end
|
|
|
|
##
|
|
# Set up a new ri generator
|
|
|
|
def initialize(options) #:not-new:
|
|
@options = options
|
|
@ri_writer = RDoc::RI::Writer.new "."
|
|
@markup = RDoc::Markup.new
|
|
@to_flow = RDoc::Markup::ToFlow.new
|
|
|
|
@generated = {}
|
|
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 recurse into this class' constituent classes
|
|
from_class.each_classmodule do |mod|
|
|
process_class(mod)
|
|
end
|
|
end
|
|
|
|
def generate_class_info(cls)
|
|
case cls
|
|
when RDoc::NormalModule then
|
|
cls_desc = RDoc::RI::ModuleDescription.new
|
|
else
|
|
cls_desc = RDoc::RI::ClassDescription.new
|
|
cls_desc.superclass = cls.superclass
|
|
end
|
|
|
|
cls_desc.name = cls.name
|
|
cls_desc.full_name = cls.full_name
|
|
cls_desc.comment = markup(cls.comment)
|
|
|
|
cls_desc.attributes = cls.attributes.sort.map do |a|
|
|
RDoc::RI::Attribute.new(a.name, a.rw, markup(a.comment))
|
|
end
|
|
|
|
cls_desc.constants = cls.constants.map do |c|
|
|
RDoc::RI::Constant.new(c.name, c.value, markup(c.comment))
|
|
end
|
|
|
|
cls_desc.includes = cls.includes.map do |i|
|
|
RDoc::RI::IncludedModule.new(i.name)
|
|
end
|
|
|
|
class_methods, instance_methods = method_list(cls)
|
|
|
|
cls_desc.class_methods = class_methods.map do |m|
|
|
RDoc::RI::MethodSummary.new(m.name)
|
|
end
|
|
|
|
cls_desc.instance_methods = instance_methods.map do |m|
|
|
RDoc::RI::MethodSummary.new(m.name)
|
|
end
|
|
|
|
update_or_replace(cls_desc)
|
|
|
|
class_methods.each do |m|
|
|
generate_method_info(cls_desc, m)
|
|
end
|
|
|
|
instance_methods.each do |m|
|
|
generate_method_info(cls_desc, m)
|
|
end
|
|
end
|
|
|
|
def generate_method_info(cls_desc, method)
|
|
meth_desc = RDoc::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|
|
|
RDoc::RI::AliasName.new(a.name)
|
|
end
|
|
|
|
@ri_writer.add_method(cls_desc, meth_desc)
|
|
end
|
|
|
|
private
|
|
|
|
##
|
|
# Returns a list of class and instance 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.visibility == :protected || m.force_documentation
|
|
end
|
|
end
|
|
|
|
c = []
|
|
i = []
|
|
list.sort.each do |m|
|
|
if m.singleton
|
|
c << m
|
|
else
|
|
i << m
|
|
end
|
|
end
|
|
return c,i
|
|
end
|
|
|
|
def params_of(method)
|
|
if method.call_seq
|
|
method.call_seq
|
|
else
|
|
params = method.params || ""
|
|
|
|
p = 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
|
|
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
|
|
|
|
##
|
|
# By default we replace existing classes with the same name. If the
|
|
# --merge option was given, we instead merge this definition into an
|
|
# existing class. We add our methods, aliases, etc to that class, but do
|
|
# not change the class's description.
|
|
|
|
def update_or_replace(cls_desc)
|
|
old_cls = nil
|
|
|
|
if @options.merge
|
|
rdr = RDoc::RI::Reader.new RDoc::RI::Cache.new(@options.op_dir)
|
|
|
|
namespace = rdr.top_level_namespace
|
|
namespace = rdr.lookup_namespace_in(cls_desc.name, namespace)
|
|
if namespace.empty?
|
|
$stderr.puts "You asked me to merge this source into existing "
|
|
$stderr.puts "documentation. This file references a class or "
|
|
$stderr.puts "module called #{cls_desc.name} which I don't"
|
|
$stderr.puts "have existing documentation for."
|
|
$stderr.puts
|
|
$stderr.puts "Perhaps you need to generate its documentation first"
|
|
exit 1
|
|
else
|
|
old_cls = namespace[0]
|
|
end
|
|
end
|
|
|
|
prev_cls = @generated[cls_desc.full_name]
|
|
|
|
if old_cls and not prev_cls then
|
|
old_desc = rdr.get_class old_cls
|
|
cls_desc.merge_in old_desc
|
|
end
|
|
|
|
if prev_cls then
|
|
cls_desc.merge_in prev_cls
|
|
end
|
|
|
|
@generated[cls_desc.full_name] = cls_desc
|
|
|
|
@ri_writer.remove_class cls_desc
|
|
@ri_writer.add_class cls_desc
|
|
end
|
|
|
|
end
|
|
|