#{content}
", content
result
end
def _reduce_15(val, _values, result)
content = val[1]
result = inline "+#{content}+", content
result
end
def _reduce_16(val, _values, result)
content = val[1]
result = inline "#{content}", content
result
end
def _reduce_17(val, _values, result)
label = val[1]
@block_parser.add_label label.reference
result = "#{label}"
result
end
def _reduce_18(val, _values, result)
result = "{#{val[1]}}[#{val[2].join}]"
result
end
def _reduce_19(val, _values, result)
scheme, inline = val[1]
result = "{#{inline}}[#{scheme}#{inline.reference}]"
result
end
def _reduce_20(val, _values, result)
result = [nil, inline(val[1])]
result
end
def _reduce_21(val, _values, result)
result = [
'rdoc-label:',
inline("#{val[0].reference}/#{val[1].reference}")
]
result
end
def _reduce_22(val, _values, result)
result = ['rdoc-label:', val[0].reference]
result
end
def _reduce_23(val, _values, result)
result = ['rdoc-label:', "#{val[0].reference}/"]
result
end
def _reduce_24(val, _values, result)
result = [nil, inline(val[1])]
result
end
def _reduce_25(val, _values, result)
result = [
'rdoc-label:',
inline("#{val[0].reference}/#{val[1].reference}")
]
result
end
def _reduce_26(val, _values, result)
result = ['rdoc-label:', val[0]]
result
end
def _reduce_27(val, _values, result)
ref = val[0].reference
result = ['rdoc-label:', inline(ref, "#{ref}/")]
result
end
# reduce 28 omitted
def _reduce_29(val, _values, result)
result = val[1]
result
end
def _reduce_30(val, _values, result)
result = val[1]
result
end
def _reduce_31(val, _values, result)
result = inline val[0]
result
end
def _reduce_32(val, _values, result)
result = inline "\"#{val[1]}\""
result
end
def _reduce_33(val, _values, result)
result = inline val[0]
result
end
def _reduce_34(val, _values, result)
result = inline "\"#{val[1]}\""
result
end
# reduce 35 omitted
def _reduce_36(val, _values, result)
result = val[1]
result
end
def _reduce_37(val, _values, result)
result = inline val[1]
result
end
def _reduce_38(val, _values, result)
result = val[0].append val[1]
result
end
def _reduce_39(val, _values, result)
result = val[0].append val[1]
result
end
def _reduce_40(val, _values, result)
result = val[0]
result
end
def _reduce_41(val, _values, result)
result = inline val[0]
result
end
# reduce 42 omitted
def _reduce_43(val, _values, result)
result = val[0].append val[1]
result
end
def _reduce_44(val, _values, result)
result = inline val[0]
result
end
def _reduce_45(val, _values, result)
result = val[0].append val[1]
result
end
def _reduce_46(val, _values, result)
result = val[0]
result
end
# reduce 47 omitted
# reduce 48 omitted
# reduce 49 omitted
# reduce 50 omitted
# reduce 51 omitted
# reduce 52 omitted
# reduce 53 omitted
# reduce 54 omitted
# reduce 55 omitted
# reduce 56 omitted
def _reduce_57(val, _values, result)
result = val[0]
result
end
def _reduce_58(val, _values, result)
result = inline val[0]
result
end
def _reduce_59(val, _values, result)
result = inline val[0]
result
end
def _reduce_60(val, _values, result)
result << val[1]
result
end
# reduce 61 omitted
def _reduce_62(val, _values, result)
result << val[1]
result
end
# reduce 63 omitted
def _reduce_64(val, _values, result)
result << val[1]
result
end
# reduce 65 omitted
# reduce 66 omitted
# reduce 67 omitted
# reduce 68 omitted
# reduce 69 omitted
# reduce 70 omitted
# reduce 71 omitted
# reduce 72 omitted
# reduce 73 omitted
# reduce 74 omitted
# reduce 75 omitted
# reduce 76 omitted
# reduce 77 omitted
def _reduce_78(val, _values, result)
result << val[1]
result
end
# reduce 79 omitted
# reduce 80 omitted
# reduce 81 omitted
# reduce 82 omitted
# reduce 83 omitted
# reduce 84 omitted
# reduce 85 omitted
# reduce 86 omitted
# reduce 87 omitted
# reduce 88 omitted
# reduce 89 omitted
# reduce 90 omitted
# reduce 91 omitted
# reduce 92 omitted
# reduce 93 omitted
# reduce 94 omitted
# reduce 95 omitted
# reduce 96 omitted
# reduce 97 omitted
# reduce 98 omitted
# reduce 99 omitted
# reduce 100 omitted
def _reduce_101(val, _values, result)
index = @block_parser.add_footnote val[1].rdoc
result = "{*#{index}}[rdoc-label:foottext-#{index}:footmark-#{index}]"
result
end
def _reduce_102(val, _values, result)
result = inline "#{val[1]}", val[1]
result
end
# reduce 103 omitted
# reduce 104 omitted
# reduce 105 omitted
# reduce 106 omitted
# reduce 107 omitted
# reduce 108 omitted
def _reduce_109(val, _values, result)
result << val[1]
result
end
# reduce 110 omitted
def _reduce_111(val, _values, result)
result = inline val[0]
result
end
# reduce 112 omitted
def _reduce_113(val, _values, result)
result = val[1]
result
end
def _reduce_114(val, _values, result)
result = val[1]
result
end
def _reduce_115(val, _values, result)
result = val[1]
result
end
# reduce 116 omitted
# reduce 117 omitted
# reduce 118 omitted
# reduce 119 omitted
# reduce 120 omitted
# reduce 121 omitted
# reduce 122 omitted
# reduce 123 omitted
# reduce 124 omitted
# reduce 125 omitted
# reduce 126 omitted
# reduce 127 omitted
# reduce 128 omitted
# reduce 129 omitted
# reduce 130 omitted
# reduce 131 omitted
# reduce 132 omitted
# reduce 133 omitted
# reduce 134 omitted
# reduce 135 omitted
def _reduce_136(val, _values, result)
result << val[1]
result
end
# reduce 137 omitted
def _reduce_none(val, _values, result)
val[0]
end
end # class InlineParser
end
class_module.rb 0000644 00000046231 14763537411 0007564 0 ustar 00 ##
# ClassModule is the base class for objects representing either a class or a
# module.
class RDoc::ClassModule < RDoc::Context
##
# 1::
# RDoc 3.7
# * Added visibility, singleton and file to attributes
# * Added file to constants
# * Added file to includes
# * Added file to methods
# 2::
# RDoc 3.13
# * Added extends
# 3::
# RDoc 4.0
# * Added sections
# * Added in_files
# * Added parent name
# * Complete Constant dump
MARSHAL_VERSION = 3 # :nodoc:
##
# Constants that are aliases for this class or module
attr_accessor :constant_aliases
##
# Comment and the location it came from. Use #add_comment to add comments
attr_accessor :comment_location
attr_accessor :diagram # :nodoc:
##
# Class or module this constant is an alias for
attr_accessor :is_alias_for
##
# Return a RDoc::ClassModule of class +class_type+ that is a copy
# of module +module+. Used to promote modules to classes.
#--
# TODO move to RDoc::NormalClass (I think)
def self.from_module class_type, mod
klass = class_type.new mod.name
mod.comment_location.each do |comment, location|
klass.add_comment comment, location
end
klass.parent = mod.parent
klass.section = mod.section
klass.viewer = mod.viewer
klass.attributes.concat mod.attributes
klass.method_list.concat mod.method_list
klass.aliases.concat mod.aliases
klass.external_aliases.concat mod.external_aliases
klass.constants.concat mod.constants
klass.includes.concat mod.includes
klass.extends.concat mod.extends
klass.methods_hash.update mod.methods_hash
klass.constants_hash.update mod.constants_hash
klass.current_section = mod.current_section
klass.in_files.concat mod.in_files
klass.sections.concat mod.sections
klass.unmatched_alias_lists = mod.unmatched_alias_lists
klass.current_section = mod.current_section
klass.visibility = mod.visibility
klass.classes_hash.update mod.classes_hash
klass.modules_hash.update mod.modules_hash
klass.metadata.update mod.metadata
klass.document_self = mod.received_nodoc ? nil : mod.document_self
klass.document_children = mod.document_children
klass.force_documentation = mod.force_documentation
klass.done_documenting = mod.done_documenting
# update the parent of all children
(klass.attributes +
klass.method_list +
klass.aliases +
klass.external_aliases +
klass.constants +
klass.includes +
klass.extends +
klass.classes +
klass.modules).each do |obj|
obj.parent = klass
obj.full_name = nil
end
klass
end
##
# Creates a new ClassModule with +name+ with optional +superclass+
#
# This is a constructor for subclasses, and must never be called directly.
def initialize(name, superclass = nil)
@constant_aliases = []
@diagram = nil
@is_alias_for = nil
@name = name
@superclass = superclass
@comment_location = [] # [[comment, location]]
super()
end
##
# Adds +comment+ to this ClassModule's list of comments at +location+. This
# method is preferred over #comment= since it allows ri data to be updated
# across multiple runs.
def add_comment comment, location
return unless document_self
original = comment
comment = case comment
when RDoc::Comment then
comment.normalize
else
normalize_comment comment
end
@comment_location.delete_if { |(_, l)| l == location }
@comment_location << [comment, location]
self.comment = original
end
def add_things my_things, other_things # :nodoc:
other_things.each do |group, things|
my_things[group].each { |thing| yield false, thing } if
my_things.include? group
things.each do |thing|
yield true, thing
end
end
end
##
# Ancestors list for this ClassModule: the list of included modules
# (classes will add their superclass if any).
#
# Returns the included classes or modules, not the includes
# themselves. The returned values are either String or
# RDoc::NormalModule instances (see RDoc::Include#module).
#
# The values are returned in reverse order of their inclusion,
# which is the order suitable for searching methods/attributes
# in the ancestors. The superclass, if any, comes last.
def ancestors
includes.map { |i| i.module }.reverse
end
##
# Ancestors of this class or module only
alias direct_ancestors ancestors
##
# Clears the comment. Used by the ruby parser.
def clear_comment
@comment = ''
end
##
# This method is deprecated, use #add_comment instead.
#
# Appends +comment+ to the current comment, but separated by a rule. Works
# more like +=.
def comment= comment # :nodoc:
comment = case comment
when RDoc::Comment then
comment.normalize
else
normalize_comment comment
end
comment = "#{@comment}\n---\n#{comment}" unless @comment.empty?
super comment
end
##
# Prepares this ClassModule for use by a generator.
#
# See RDoc::Store#complete
def complete min_visibility
update_aliases
remove_nodoc_children
update_includes
remove_invisible min_visibility
end
##
# Does this ClassModule or any of its methods have document_self set?
def document_self_or_methods
document_self || method_list.any?{ |m| m.document_self }
end
##
# Does this class or module have a comment with content or is
# #received_nodoc true?
def documented?
super or !@comment_location.empty?
end
##
# Iterates the ancestors of this class or module for which an
# RDoc::ClassModule exists.
def each_ancestor # :yields: module
return enum_for __method__ unless block_given?
ancestors.each do |mod|
next if String === mod
next if self == mod
yield mod
end
end
##
# Looks for a symbol in the #ancestors. See Context#find_local_symbol.
def find_ancestor_local_symbol symbol
each_ancestor do |m|
res = m.find_local_symbol(symbol)
return res if res
end
nil
end
##
# Finds a class or module with +name+ in this namespace or its descendants
def find_class_named name
return self if full_name == name
return self if @name == name
@classes.values.find do |klass|
next if klass == self
klass.find_class_named name
end
end
##
# Return the fully qualified name of this class or module
def full_name
@full_name ||= if RDoc::ClassModule === parent then
"#{parent.full_name}::#{@name}"
else
@name
end
end
##
# TODO: filter included items by #display?
def marshal_dump # :nodoc:
attrs = attributes.sort.map do |attr|
[ attr.name, attr.rw,
attr.visibility, attr.singleton, attr.file_name,
]
end
method_types = methods_by_type.map do |type, visibilities|
visibilities = visibilities.map do |visibility, methods|
method_names = methods.map do |method|
[method.name, method.file_name]
end
[visibility, method_names.uniq]
end
[type, visibilities]
end
[ MARSHAL_VERSION,
@name,
full_name,
@superclass,
parse(@comment_location),
attrs,
constants,
includes.map do |incl|
[incl.name, parse(incl.comment), incl.file_name]
end,
method_types,
extends.map do |ext|
[ext.name, parse(ext.comment), ext.file_name]
end,
@sections.values,
@in_files.map do |tl|
tl.relative_name
end,
parent.full_name,
parent.class,
]
end
def marshal_load array # :nodoc:
initialize_visibility
initialize_methods_etc
@current_section = nil
@document_self = true
@done_documenting = false
@parent = nil
@temporary_section = nil
@visibility = nil
@classes = {}
@modules = {}
@name = array[1]
@full_name = array[2]
@superclass = array[3]
@comment = array[4]
@comment_location = if RDoc::Markup::Document === @comment.parts.first then
@comment
else
RDoc::Markup::Document.new @comment
end
array[5].each do |name, rw, visibility, singleton, file|
singleton ||= false
visibility ||= :public
attr = RDoc::Attr.new nil, name, rw, nil, singleton
add_attribute attr
attr.visibility = visibility
attr.record_location RDoc::TopLevel.new file
end
array[6].each do |constant, comment, file|
case constant
when RDoc::Constant then
add_constant constant
else
constant = add_constant RDoc::Constant.new(constant, nil, comment)
constant.record_location RDoc::TopLevel.new file
end
end
array[7].each do |name, comment, file|
incl = add_include RDoc::Include.new(name, comment)
incl.record_location RDoc::TopLevel.new file
end
array[8].each do |type, visibilities|
visibilities.each do |visibility, methods|
@visibility = visibility
methods.each do |name, file|
method = RDoc::AnyMethod.new nil, name
method.singleton = true if type == 'class'
method.record_location RDoc::TopLevel.new file
add_method method
end
end
end
array[9].each do |name, comment, file|
ext = add_extend RDoc::Extend.new(name, comment)
ext.record_location RDoc::TopLevel.new file
end if array[9] # Support Marshal version 1
sections = (array[10] || []).map do |section|
[section.title, section]
end
@sections = Hash[*sections.flatten]
@current_section = add_section nil
@in_files = []
(array[11] || []).each do |filename|
record_location RDoc::TopLevel.new filename
end
@parent_name = array[12]
@parent_class = array[13]
end
##
# Merges +class_module+ into this ClassModule.
#
# The data in +class_module+ is preferred over the receiver.
def merge class_module
@parent = class_module.parent
@parent_name = class_module.parent_name
other_document = parse class_module.comment_location
if other_document then
document = parse @comment_location
document = document.merge other_document
@comment = @comment_location = document
end
cm = class_module
other_files = cm.in_files
merge_collections attributes, cm.attributes, other_files do |add, attr|
if add then
add_attribute attr
else
@attributes.delete attr
@methods_hash.delete attr.pretty_name
end
end
merge_collections constants, cm.constants, other_files do |add, const|
if add then
add_constant const
else
@constants.delete const
@constants_hash.delete const.name
end
end
merge_collections includes, cm.includes, other_files do |add, incl|
if add then
add_include incl
else
@includes.delete incl
end
end
@includes.uniq! # clean up
merge_collections extends, cm.extends, other_files do |add, ext|
if add then
add_extend ext
else
@extends.delete ext
end
end
@extends.uniq! # clean up
merge_collections method_list, cm.method_list, other_files do |add, meth|
if add then
add_method meth
else
@method_list.delete meth
@methods_hash.delete meth.pretty_name
end
end
merge_sections cm
self
end
##
# Merges collection +mine+ with +other+ preferring other. +other_files+ is
# used to help determine which items should be deleted.
#
# Yields whether the item should be added or removed (true or false) and the
# item to be added or removed.
#
# merge_collections things, other.things, other.in_files do |add, thing|
# if add then
# # add the thing
# else
# # remove the thing
# end
# end
def merge_collections mine, other, other_files, &block # :nodoc:
my_things = mine. group_by { |thing| thing.file }
other_things = other.group_by { |thing| thing.file }
remove_things my_things, other_files, &block
add_things my_things, other_things, &block
end
##
# Merges the comments in this ClassModule with the comments in the other
# ClassModule +cm+.
def merge_sections cm # :nodoc:
my_sections = sections.group_by { |section| section.title }
other_sections = cm.sections.group_by { |section| section.title }
other_files = cm.in_files
remove_things my_sections, other_files do |_, section|
@sections.delete section.title
end
other_sections.each do |group, sections|
if my_sections.include? group
my_sections[group].each do |my_section|
other_section = cm.sections_hash[group]
my_comments = my_section.comments
other_comments = other_section.comments
other_files = other_section.in_files
merge_collections my_comments, other_comments, other_files do |add, comment|
if add then
my_section.add_comment comment
else
my_section.remove_comment comment
end
end
end
else
sections.each do |section|
add_section group, section.comments
end
end
end
end
##
# Does this object represent a module?
def module?
false
end
##
# Allows overriding the initial name.
#
# Used for modules and classes that are constant aliases.
def name= new_name
@name = new_name
end
##
# Parses +comment_location+ into an RDoc::Markup::Document composed of
# multiple RDoc::Markup::Documents with their file set.
def parse comment_location
case comment_location
when String then
super
when Array then
docs = comment_location.map do |comment, location|
doc = super comment
doc.file = location
doc
end
RDoc::Markup::Document.new(*docs)
when RDoc::Comment then
doc = super comment_location.text, comment_location.format
doc.file = comment_location.location
doc
when RDoc::Markup::Document then
return comment_location
else
raise ArgumentError, "unknown comment class #{comment_location.class}"
end
end
##
# Path to this class or module for use with HTML generator output.
def path
http_url @store.rdoc.generator.class_dir
end
##
# Name to use to generate the url:
# modules and classes that are aliases for another
# module or class return the name of the latter.
def name_for_path
is_alias_for ? is_alias_for.full_name : full_name
end
##
# Returns the classes and modules that are not constants
# aliasing another class or module. For use by formatters
# only (caches its result).
def non_aliases
@non_aliases ||= classes_and_modules.reject { |cm| cm.is_alias_for }
end
##
# Updates the child modules or classes of class/module +parent+ by
# deleting the ones that have been removed from the documentation.
#
# +parent_hash+ is either parent.modules_hash or
# parent.classes_hash and +all_hash+ is ::all_modules_hash or
# ::all_classes_hash.
def remove_nodoc_children
prefix = self.full_name + '::'
modules_hash.each_key do |name|
full_name = prefix + name
modules_hash.delete name unless @store.modules_hash[full_name]
end
classes_hash.each_key do |name|
full_name = prefix + name
classes_hash.delete name unless @store.classes_hash[full_name]
end
end
def remove_things my_things, other_files # :nodoc:
my_things.delete_if do |file, things|
next false unless other_files.include? file
things.each do |thing|
yield false, thing
end
true
end
end
##
# Search record used by RDoc::Generator::JsonIndex
def search_record
[
name,
full_name,
full_name,
'',
path,
'',
snippet(@comment_location),
]
end
##
# Sets the store for this class or module and its contained code objects.
def store= store
super
@attributes .each do |attr| attr.store = store end
@constants .each do |const| const.store = store end
@includes .each do |incl| incl.store = store end
@extends .each do |ext| ext.store = store end
@method_list.each do |meth| meth.store = store end
end
##
# Get the superclass of this class. Attempts to retrieve the superclass
# object, returns the name if it is not known.
def superclass
@store.find_class_named(@superclass) || @superclass
end
##
# Set the superclass of this class to +superclass+
def superclass=(superclass)
raise NoMethodError, "#{full_name} is a module" if module?
@superclass = superclass
end
def to_s # :nodoc:
if is_alias_for then
"#{self.class.name} #{self.full_name} -> #{is_alias_for}"
else
super
end
end
##
# 'module' or 'class'
def type
module? ? 'module' : 'class'
end
##
# Updates the child modules & classes by replacing the ones that are
# aliases through a constant.
#
# The aliased module/class is replaced in the children and in
# RDoc::Store#modules_hash or RDoc::Store#classes_hash
# by a copy that has RDoc::ClassModule#is_alias_for set to
# the aliased module/class, and this copy is added to #aliases
# of the aliased module/class.
#
# Formatters can use the #non_aliases method to retrieve children that
# are not aliases, for instance to list the namespace content, since
# the aliased modules are included in the constants of the class/module,
# that are listed separately.
def update_aliases
constants.each do |const|
next unless cm = const.is_alias_for
cm_alias = cm.dup
cm_alias.name = const.name
# Don't move top-level aliases under Object, they look ugly there
unless RDoc::TopLevel === cm_alias.parent then
cm_alias.parent = self
cm_alias.full_name = nil # force update for new parent
end
cm_alias.aliases.clear
cm_alias.is_alias_for = cm
if cm.module? then
@store.modules_hash[cm_alias.full_name] = cm_alias
modules_hash[const.name] = cm_alias
else
@store.classes_hash[cm_alias.full_name] = cm_alias
classes_hash[const.name] = cm_alias
end
cm.aliases << cm_alias
end
end
##
# Deletes from #includes those whose module has been removed from the
# documentation.
#--
# FIXME: includes are not reliably removed, see _possible_bug test case
def update_includes
includes.reject! do |include|
mod = include.module
!(String === mod) && @store.modules_hash[mod.full_name].nil?
end
includes.uniq!
end
##
# Deletes from #extends those whose module has been removed from the
# documentation.
#--
# FIXME: like update_includes, extends are not reliably removed
def update_extends
extends.reject! do |ext|
mod = ext.module
!(String === mod) && @store.modules_hash[mod.full_name].nil?
end
extends.uniq!
end
end
token_stream.rb 0000644 00000004734 14763537411 0007607 0 ustar 00 ##
# A TokenStream is a list of tokens, gathered during the parse of some entity
# (say a method). Entities populate these streams by being registered with the
# lexer. Any class can collect tokens by including TokenStream. From the
# outside, you use such an object by calling the start_collecting_tokens
# method, followed by calls to add_token and pop_token.
module RDoc::TokenStream
##
# Converts +token_stream+ to HTML wrapping various tokens with
# elements. The following tokens types are wrapped in spans
# with the given class names:
#
# TkCONSTANT :: 'ruby-constant'
# TkKW :: 'ruby-keyword'
# TkIVAR :: 'ruby-ivar'
# TkOp :: 'ruby-operator'
# TkId :: 'ruby-identifier'
# TkNode :: 'ruby-node'
# TkCOMMENT :: 'ruby-comment'
# TkREGEXP :: 'ruby-regexp'
# TkSTRING :: 'ruby-string'
# TkVal :: 'ruby-value'
#
# Other token types are not wrapped in spans.
def self.to_html token_stream
token_stream.map do |t|
next unless t
style = case t
when RDoc::RubyToken::TkCONSTANT then 'ruby-constant'
when RDoc::RubyToken::TkKW then 'ruby-keyword'
when RDoc::RubyToken::TkIVAR then 'ruby-ivar'
when RDoc::RubyToken::TkOp then 'ruby-operator'
when RDoc::RubyToken::TkId then 'ruby-identifier'
when RDoc::RubyToken::TkNode then 'ruby-node'
when RDoc::RubyToken::TkCOMMENT then 'ruby-comment'
when RDoc::RubyToken::TkREGEXP then 'ruby-regexp'
when RDoc::RubyToken::TkSTRING then 'ruby-string'
when RDoc::RubyToken::TkVal then 'ruby-value'
end
text = CGI.escapeHTML t.text
if style then
"#{text}"
else
text
end
end.join
end
##
# Adds +tokens+ to the collected tokens
def add_tokens(*tokens)
tokens.flatten.each { |token| @token_stream << token }
end
alias add_token add_tokens
##
# Starts collecting tokens
def collect_tokens
@token_stream = []
end
alias start_collecting_tokens collect_tokens
##
# Remove the last token from the collected tokens
def pop_token
@token_stream.pop
end
##
# Current token stream
def token_stream
@token_stream
end
##
# Returns a string representation of the token stream
def tokens_to_s
token_stream.map { |token| token.text }.join ''
end
end
stats/verbose.rb 0000644 00000001726 14763537411 0007715 0 ustar 00 ##
# Stats printer that prints everything documented, including the documented
# status
class RDoc::Stats::Verbose < RDoc::Stats::Normal
##
# Returns a marker for RDoc::CodeObject +co+ being undocumented
def nodoc co
" (undocumented)" unless co.documented?
end
def print_alias as # :nodoc:
puts " alias #{as.new_name} #{as.old_name}#{nodoc as}"
end
def print_attribute attribute # :nodoc:
puts " #{attribute.definition} #{attribute.name}#{nodoc attribute}"
end
def print_class(klass) # :nodoc:
puts " class #{klass.full_name}#{nodoc klass}"
end
def print_constant(constant) # :nodoc:
puts " #{constant.name}#{nodoc constant}"
end
def print_file(files_so_far, file) # :nodoc:
super
puts
end
def print_method(method) # :nodoc:
puts " #{method.singleton ? '::' : '#'}#{method.name}#{nodoc method}"
end
def print_module(mod) # :nodoc:
puts " module #{mod.full_name}#{nodoc mod}"
end
end
stats/quiet.rb 0000644 00000001442 14763537411 0007372 0 ustar 00 ##
# Stats printer that prints nothing
class RDoc::Stats::Quiet
##
# Creates a new Quiet that will print nothing
def initialize num_files
@num_files = num_files
end
##
# Prints a message at the beginning of parsing
def begin_adding(*) end
##
# Prints when an alias is added
def print_alias(*) end
##
# Prints when an attribute is added
def print_attribute(*) end
##
# Prints when a class is added
def print_class(*) end
##
# Prints when a constant is added
def print_constant(*) end
##
# Prints when a file is added
def print_file(*) end
##
# Prints when a method is added
def print_method(*) end
##
# Prints when a module is added
def print_module(*) end
##
# Prints when RDoc is done
def done_adding(*) end
end
stats/normal.rb 0000644 00000002437 14763537411 0007540 0 ustar 00 ##
# Stats printer that prints just the files being documented with a progress
# bar
class RDoc::Stats::Normal < RDoc::Stats::Quiet
def begin_adding # :nodoc:
puts "Parsing sources..." if $stdout.tty?
end
##
# Prints a file with a progress bar
def print_file files_so_far, filename
return unless $stdout.tty?
progress_bar = sprintf("%3d%% [%2d/%2d] ",
100 * files_so_far / @num_files,
files_so_far,
@num_files)
# Print a progress bar, but make sure it fits on a single line. Filename
# will be truncated if necessary.
terminal_width = (ENV['COLUMNS'] || 80).to_i
max_filename_size = terminal_width - progress_bar.size
if filename.size > max_filename_size then
# Turn "some_long_filename.rb" to "...ong_filename.rb"
filename = filename[(filename.size - max_filename_size) .. -1]
filename[0..2] = "..."
end
# Pad the line with whitespaces so that leftover output from the
# previous line doesn't show up.
line = "#{progress_bar}#{filename}"
padding = terminal_width - line.size
line << (" " * padding) if padding > 0
$stdout.print("#{line}\r")
$stdout.flush
end
def done_adding # :nodoc:
puts if $stdout.tty?
end
end
normal_module.rb 0000644 00000002555 14763537411 0007750 0 ustar 00 ##
# A normal module, like NormalClass
class RDoc::NormalModule < RDoc::ClassModule
def inspect # :nodoc:
"#<%s:0x%x module %s includes: %p extends: %p attributes: %p methods: %p aliases: %p>" % [
self.class, object_id,
full_name, @includes, @extends, @attributes, @method_list, @aliases
]
end
##
# The definition of this module, module MyModuleName
def definition
"module #{full_name}"
end
##
# This is a module, returns true
def module?
true
end
def pretty_print q # :nodoc:
q.group 2, "[module #{full_name}: ", "]" do
q.breakable
q.text "includes:"
q.breakable
q.seplist @includes do |inc| q.pp inc end
q.breakable
q.breakable
q.text "constants:"
q.breakable
q.seplist @constants do |const| q.pp const end
q.text "attributes:"
q.breakable
q.seplist @attributes do |attr| q.pp attr end
q.breakable
q.text "methods:"
q.breakable
q.seplist @method_list do |meth| q.pp meth end
q.breakable
q.text "aliases:"
q.breakable
q.seplist @aliases do |aliaz| q.pp aliaz end
q.breakable
q.text "comment:"
q.breakable
q.pp comment
end
end
##
# Modules don't have one, raises NoMethodError
def superclass
raise NoMethodError, "#{full_name} is a module"
end
end
rubygems_hook.rb 0000644 00000011646 14763537411 0007771 0 ustar 00 require 'rubygems'
require 'rubygems/user_interaction'
require 'fileutils'
require 'rdoc'
##
# Gem::RDoc provides methods to generate RDoc and ri data for installed gems
# upon gem installation.
#
# This file is automatically required by RubyGems 1.9 and newer.
class RDoc::RubygemsHook
include Gem::UserInteraction
@rdoc_version = nil
@specs = []
##
# Force installation of documentation?
attr_accessor :force
##
# Generate rdoc?
attr_accessor :generate_rdoc
##
# Generate ri data?
attr_accessor :generate_ri
class << self
##
# Loaded version of RDoc. Set by ::load_rdoc
attr_reader :rdoc_version
end
##
# Post installs hook that generates documentation for each specification in
# +specs+
def self.generation_hook installer, specs
types = installer.document
generate_rdoc = types.include? 'rdoc'
generate_ri = types.include? 'ri'
specs.each do |spec|
new(spec, generate_rdoc, generate_ri).generate
end
end
##
# Loads the RDoc generator
def self.load_rdoc
return if @rdoc_version
require 'rdoc/rdoc'
@rdoc_version = Gem::Version.new ::RDoc::VERSION
end
##
# Creates a new documentation generator for +spec+. RDoc and ri data
# generation can be enabled or disabled through +generate_rdoc+ and
# +generate_ri+ respectively.
#
# Only +generate_ri+ is enabled by default.
def initialize spec, generate_rdoc = false, generate_ri = true
@doc_dir = spec.doc_dir
@force = false
@rdoc = nil
@spec = spec
@generate_rdoc = generate_rdoc
@generate_ri = generate_ri
@rdoc_dir = spec.doc_dir 'rdoc'
@ri_dir = spec.doc_dir 'ri'
end
##
# Removes legacy rdoc arguments from +args+
#--
# TODO move to RDoc::Options
def delete_legacy_args args
args.delete '--inline-source'
args.delete '--promiscuous'
args.delete '-p'
args.delete '--one-file'
end
##
# Generates documentation using the named +generator+ ("darkfish" or "ri")
# and following the given +options+.
#
# Documentation will be generated into +destination+
def document generator, options, destination
generator_name = generator
options = options.dup
options.exclude ||= [] # TODO maybe move to RDoc::Options#finish
options.setup_generator generator
options.op_dir = destination
options.finish
generator = options.generator.new @rdoc.store, options
@rdoc.options = options
@rdoc.generator = generator
say "Installing #{generator_name} documentation for #{@spec.full_name}"
FileUtils.mkdir_p options.op_dir
Dir.chdir options.op_dir do
begin
@rdoc.class.current = @rdoc
@rdoc.generator.generate
ensure
@rdoc.class.current = nil
end
end
end
##
# Generates RDoc and ri data
def generate
return if @spec.default_gem?
return unless @generate_ri or @generate_rdoc
setup
options = nil
args = @spec.rdoc_options
args.concat @spec.require_paths
args.concat @spec.extra_rdoc_files
case config_args = Gem.configuration[:rdoc]
when String then
args = args.concat config_args.split
when Array then
args = args.concat config_args
end
delete_legacy_args args
Dir.chdir @spec.full_gem_path do
options = ::RDoc::Options.new
options.default_title = "#{@spec.full_name} Documentation"
options.parse args
end
options.quiet = !Gem.configuration.really_verbose
@rdoc = new_rdoc
@rdoc.options = options
store = RDoc::Store.new
store.encoding = options.encoding if options.respond_to? :encoding
store.dry_run = options.dry_run
store.main = options.main_page
store.title = options.title
@rdoc.store = store
say "Parsing documentation for #{@spec.full_name}"
Dir.chdir @spec.full_gem_path do
@rdoc.parse_files options.files
end
document 'ri', options, @ri_dir if
@generate_ri and (@force or not File.exist? @ri_dir)
document 'darkfish', options, @rdoc_dir if
@generate_rdoc and (@force or not File.exist? @rdoc_dir)
end
##
# #new_rdoc creates a new RDoc instance. This method is provided only to
# make testing easier.
def new_rdoc # :nodoc:
::RDoc::RDoc.new
end
##
# Is rdoc documentation installed?
def rdoc_installed?
File.exist? @rdoc_dir
end
##
# Removes generated RDoc and ri data
def remove
base_dir = @spec.base_dir
raise Gem::FilePermissionError, base_dir unless File.writable? base_dir
FileUtils.rm_rf @rdoc_dir
FileUtils.rm_rf @ri_dir
end
##
# Is ri data installed?
def ri_installed?
File.exist? @ri_dir
end
##
# Prepares the spec for documentation generation
def setup
self.class.load_rdoc
raise Gem::FilePermissionError, @doc_dir if
File.exist?(@doc_dir) and not File.writable?(@doc_dir)
FileUtils.mkdir_p @doc_dir unless File.exist? @doc_dir
end
end
store.rb 0000644 00000054506 14763537411 0006252 0 ustar 00 require 'fileutils'
##
# A set of rdoc data for a single project (gem, path, etc.).
#
# The store manages reading and writing ri data for a project and maintains a
# cache of methods, classes and ancestors in the store.
#
# The store maintains a #cache of its contents for faster lookup. After
# adding items to the store it must be flushed using #save_cache. The cache
# contains the following structures:
#
# @cache = {
# :ancestors => {}, # class name => ancestor names
# :attributes => {}, # class name => attributes
# :class_methods => {}, # class name => class methods
# :instance_methods => {}, # class name => instance methods
# :modules => [], # classes and modules in this store
# :pages => [], # page names
# }
#--
# TODO need to prune classes
class RDoc::Store
##
# Errors raised from loading or saving the store
class Error < RDoc::Error
end
##
# Raised when a stored file for a class, module, page or method is missing.
class MissingFileError < Error
##
# The store the file should exist in
attr_reader :store
##
# The file the #name should be saved as
attr_reader :file
##
# The name of the object the #file would be loaded from
attr_reader :name
##
# Creates a new MissingFileError for the missing +file+ for the given
# +name+ that should have been in the +store+.
def initialize store, file, name
@store = store
@file = file
@name = name
end
def message # :nodoc:
"store at #{@store.path} missing file #{@file} for #{@name}"
end
end
##
# Stores the name of the C variable a class belongs to. This helps wire up
# classes defined from C across files.
attr_reader :c_enclosure_classes # :nodoc:
attr_reader :c_enclosure_names # :nodoc:
##
# Maps C variables to class or module names for each parsed C file.
attr_reader :c_class_variables
##
# Maps C variables to singleton class names for each parsed C file.
attr_reader :c_singleton_class_variables
##
# If true this Store will not write any files
attr_accessor :dry_run
##
# Path this store reads or writes
attr_accessor :path
##
# The RDoc::RDoc driver for this parse tree. This allows classes consulting
# the documentation tree to access user-set options, for example.
attr_accessor :rdoc
##
# Type of ri datastore this was loaded from. See RDoc::RI::Driver,
# RDoc::RI::Paths.
attr_accessor :type
##
# The contents of the Store
attr_reader :cache
##
# The encoding of the contents in the Store
attr_accessor :encoding
##
# Creates a new Store of +type+ that will load or save to +path+
def initialize path = nil, type = nil
@dry_run = false
@encoding = nil
@path = path
@rdoc = nil
@type = type
@cache = {
:ancestors => {},
:attributes => {},
:class_methods => {},
:c_class_variables => {},
:c_singleton_class_variables => {},
:encoding => @encoding,
:instance_methods => {},
:main => nil,
:modules => [],
:pages => [],
:title => nil,
}
@classes_hash = {}
@modules_hash = {}
@files_hash = {}
@c_enclosure_classes = {}
@c_enclosure_names = {}
@c_class_variables = {}
@c_singleton_class_variables = {}
@unique_classes = nil
@unique_modules = nil
end
##
# Adds +module+ as an enclosure (namespace) for the given +variable+ for C
# files.
def add_c_enclosure variable, namespace
@c_enclosure_classes[variable] = namespace
end
##
# Adds C variables from an RDoc::Parser::C
def add_c_variables c_parser
filename = c_parser.top_level.relative_name
@c_class_variables[filename] = make_variable_map c_parser.classes
@c_singleton_class_variables[filename] = c_parser.singleton_classes
end
##
# Adds the file with +name+ as an RDoc::TopLevel to the store. Returns the
# created RDoc::TopLevel.
def add_file absolute_name, relative_name = absolute_name
unless top_level = @files_hash[relative_name] then
top_level = RDoc::TopLevel.new absolute_name, relative_name
top_level.store = self
@files_hash[relative_name] = top_level
end
top_level
end
##
# Returns all classes discovered by RDoc
def all_classes
@classes_hash.values
end
##
# Returns all classes and modules discovered by RDoc
def all_classes_and_modules
@classes_hash.values + @modules_hash.values
end
##
# All TopLevels known to RDoc
def all_files
@files_hash.values
end
##
# Returns all modules discovered by RDoc
def all_modules
modules_hash.values
end
##
# Ancestors cache accessor. Maps a klass name to an Array of its ancestors
# in this store. If Foo in this store inherits from Object, Kernel won't be
# listed (it will be included from ruby's ri store).
def ancestors
@cache[:ancestors]
end
##
# Attributes cache accessor. Maps a class to an Array of its attributes.
def attributes
@cache[:attributes]
end
##
# Path to the cache file
def cache_path
File.join @path, 'cache.ri'
end
##
# Path to the ri data for +klass_name+
def class_file klass_name
name = klass_name.split('::').last
File.join class_path(klass_name), "cdesc-#{name}.ri"
end
##
# Class methods cache accessor. Maps a class to an Array of its class
# methods (not full name).
def class_methods
@cache[:class_methods]
end
##
# Path where data for +klass_name+ will be stored (methods or class data)
def class_path klass_name
File.join @path, *klass_name.split('::')
end
##
# Hash of all classes known to RDoc
def classes_hash
@classes_hash
end
##
# Removes empty items and ensures item in each collection are unique and
# sorted
def clean_cache_collection collection # :nodoc:
collection.each do |name, item|
if item.empty? then
collection.delete name
else
# HACK mongrel-1.1.5 documents its files twice
item.uniq!
item.sort!
end
end
end
##
# Prepares the RDoc code object tree for use by a generator.
#
# It finds unique classes/modules defined, and replaces classes/modules that
# are aliases for another one by a copy with RDoc::ClassModule#is_alias_for
# set.
#
# It updates the RDoc::ClassModule#constant_aliases attribute of "real"
# classes or modules.
#
# It also completely removes the classes and modules that should be removed
# from the documentation and the methods that have a visibility below
# +min_visibility+, which is the --visibility option.
#
# See also RDoc::Context#remove_from_documentation?
def complete min_visibility
fix_basic_object_inheritance
# cache included modules before they are removed from the documentation
all_classes_and_modules.each { |cm| cm.ancestors }
remove_nodoc @classes_hash
remove_nodoc @modules_hash
@unique_classes = find_unique @classes_hash
@unique_modules = find_unique @modules_hash
unique_classes_and_modules.each do |cm|
cm.complete min_visibility
end
@files_hash.each_key do |file_name|
tl = @files_hash[file_name]
unless tl.text? then
tl.modules_hash.clear
tl.classes_hash.clear
tl.classes_or_modules.each do |cm|
name = cm.full_name
if cm.type == 'class' then
tl.classes_hash[name] = cm if @classes_hash[name]
else
tl.modules_hash[name] = cm if @modules_hash[name]
end
end
end
end
end
##
# Hash of all files known to RDoc
def files_hash
@files_hash
end
##
# Finds the enclosure (namespace) for the given C +variable+.
def find_c_enclosure variable
@c_enclosure_classes.fetch variable do
break unless name = @c_enclosure_names[variable]
mod = find_class_or_module name
unless mod then
loaded_mod = load_class_data name
file = loaded_mod.in_files.first
return unless file # legacy data source
file.store = self
mod = file.add_module RDoc::NormalModule, name
end
@c_enclosure_classes[variable] = mod
end
end
##
# Finds the class with +name+ in all discovered classes
def find_class_named name
@classes_hash[name]
end
##
# Finds the class with +name+ starting in namespace +from+
def find_class_named_from name, from
from = find_class_named from unless RDoc::Context === from
until RDoc::TopLevel === from do
return nil unless from
klass = from.find_class_named name
return klass if klass
from = from.parent
end
find_class_named name
end
##
# Finds the class or module with +name+
def find_class_or_module name
name = $' if name =~ /^::/
@classes_hash[name] || @modules_hash[name]
end
##
# Finds the file with +name+ in all discovered files
def find_file_named name
@files_hash[name]
end
##
# Finds the module with +name+ in all discovered modules
def find_module_named name
@modules_hash[name]
end
##
# Returns the RDoc::TopLevel that is a text file and has the given
# +file_name+
def find_text_page file_name
@files_hash.each_value.find do |file|
file.text? and file.full_name == file_name
end
end
##
# Finds unique classes/modules defined in +all_hash+,
# and returns them as an array. Performs the alias
# updates in +all_hash+: see ::complete.
#--
# TODO aliases should be registered by Context#add_module_alias
def find_unique all_hash
unique = []
all_hash.each_pair do |full_name, cm|
unique << cm if full_name == cm.full_name
end
unique
end
##
# Fixes the erroneous BasicObject < Object in 1.9.
#
# Because we assumed all classes without a stated superclass
# inherit from Object, we have the above wrong inheritance.
#
# We fix BasicObject right away if we are running in a Ruby
# version >= 1.9. If not, we may be documenting 1.9 source
# while running under 1.8: we search the files of BasicObject
# for "object.c", and fix the inheritance if we find it.
def fix_basic_object_inheritance
basic = classes_hash['BasicObject']
return unless basic
if RUBY_VERSION >= '1.9'
basic.superclass = nil
elsif basic.in_files.any? { |f| File.basename(f.full_name) == 'object.c' }
basic.superclass = nil
end
end
##
# Friendly rendition of #path
def friendly_path
case type
when :gem then
parent = File.expand_path '..', @path
"gem #{File.basename parent}"
when :home then '~/.rdoc'
when :site then 'ruby site'
when :system then 'ruby core'
else @path
end
end
def inspect # :nodoc:
"#<%s:0x%x %s %p>" % [self.class, object_id, @path, module_names.sort]
end
##
# Instance methods cache accessor. Maps a class to an Array of its
# instance methods (not full name).
def instance_methods
@cache[:instance_methods]
end
##
# Loads all items from this store into memory. This recreates a
# documentation tree for use by a generator
def load_all
load_cache
module_names.each do |module_name|
mod = find_class_or_module(module_name) || load_class(module_name)
# load method documentation since the loaded class/module does not have
# it
loaded_methods = mod.method_list.map do |method|
load_method module_name, method.full_name
end
mod.method_list.replace loaded_methods
loaded_attributes = mod.attributes.map do |attribute|
load_method module_name, attribute.full_name
end
mod.attributes.replace loaded_attributes
end
all_classes_and_modules.each do |mod|
descendent_re = /^#{mod.full_name}::[^:]+$/
module_names.each do |name|
next unless name =~ descendent_re
descendent = find_class_or_module name
case descendent
when RDoc::NormalClass then
mod.classes_hash[name] = descendent
when RDoc::NormalModule then
mod.modules_hash[name] = descendent
end
end
end
@cache[:pages].each do |page_name|
page = load_page page_name
@files_hash[page_name] = page
end
end
##
# Loads cache file for this store
def load_cache
#orig_enc = @encoding
open cache_path, 'rb' do |io|
@cache = Marshal.load io.read
end
load_enc = @cache[:encoding]
# TODO this feature will be time-consuming to add:
# a) Encodings may be incompatible but transcodeable
# b) Need to warn in the appropriate spots, wherever they may be
# c) Need to handle cross-cache differences in encodings
# d) Need to warn when generating into a cache with different encodings
#
#if orig_enc and load_enc != orig_enc then
# warn "Cached encoding #{load_enc} is incompatible with #{orig_enc}\n" \
# "from #{path}/cache.ri" unless
# Encoding.compatible? orig_enc, load_enc
#end
@encoding = load_enc unless @encoding
@cache[:pages] ||= []
@cache[:main] ||= nil
@cache[:c_class_variables] ||= {}
@cache[:c_singleton_class_variables] ||= {}
@cache[:c_class_variables].each do |_, map|
map.each do |variable, name|
@c_enclosure_names[variable] = name
end
end
@cache
rescue Errno::ENOENT
end
##
# Loads ri data for +klass_name+ and hooks it up to this store.
def load_class klass_name
obj = load_class_data klass_name
obj.store = self
case obj
when RDoc::NormalClass then
@classes_hash[klass_name] = obj
when RDoc::NormalModule then
@modules_hash[klass_name] = obj
end
end
##
# Loads ri data for +klass_name+
def load_class_data klass_name
file = class_file klass_name
open file, 'rb' do |io|
Marshal.load io.read
end
rescue Errno::ENOENT => e
error = MissingFileError.new(self, file, klass_name)
error.set_backtrace e.backtrace
raise error
end
##
# Loads ri data for +method_name+ in +klass_name+
def load_method klass_name, method_name
file = method_file klass_name, method_name
open file, 'rb' do |io|
obj = Marshal.load io.read
obj.store = self
obj.parent =
find_class_or_module(klass_name) || load_class(klass_name) unless
obj.parent
obj
end
rescue Errno::ENOENT => e
error = MissingFileError.new(self, file, klass_name + method_name)
error.set_backtrace e.backtrace
raise error
end
##
# Loads ri data for +page_name+
def load_page page_name
file = page_file page_name
open file, 'rb' do |io|
obj = Marshal.load io.read
obj.store = self
obj
end
rescue Errno::ENOENT => e
error = MissingFileError.new(self, file, page_name)
error.set_backtrace e.backtrace
raise error
end
##
# Gets the main page for this RDoc store. This page is used as the root of
# the RDoc server.
def main
@cache[:main]
end
##
# Sets the main page for this RDoc store.
def main= page
@cache[:main] = page
end
##
# Converts the variable => ClassModule map +variables+ from a C parser into
# a variable => class name map.
def make_variable_map variables
map = {}
variables.each { |variable, class_module|
map[variable] = class_module.full_name
}
map
end
##
# Path to the ri data for +method_name+ in +klass_name+
def method_file klass_name, method_name
method_name = method_name.split('::').last
method_name =~ /#(.*)/
method_type = $1 ? 'i' : 'c'
method_name = $1 if $1
method_name = if ''.respond_to? :ord then
method_name.gsub(/\W/) { "%%%02x" % $&[0].ord }
else
method_name.gsub(/\W/) { "%%%02x" % $&[0] }
end
File.join class_path(klass_name), "#{method_name}-#{method_type}.ri"
end
##
# Modules cache accessor. An Array of all the module (and class) names in
# the store.
def module_names
@cache[:modules]
end
##
# Hash of all modules known to RDoc
def modules_hash
@modules_hash
end
##
# Returns the RDoc::TopLevel that is a text file and has the given +name+
def page name
@files_hash.each_value.find do |file|
file.text? and file.page_name == name
end
end
##
# Path to the ri data for +page_name+
def page_file page_name
file_name = File.basename(page_name).gsub('.', '_')
File.join @path, File.dirname(page_name), "page-#{file_name}.ri"
end
##
# Removes from +all_hash+ the contexts that are nodoc or have no content.
#
# See RDoc::Context#remove_from_documentation?
def remove_nodoc all_hash
all_hash.keys.each do |name|
context = all_hash[name]
all_hash.delete(name) if context.remove_from_documentation?
end
end
##
# Saves all entries in the store
def save
load_cache
all_classes_and_modules.each do |klass|
save_class klass
klass.each_method do |method|
save_method klass, method
end
klass.each_attribute do |attribute|
save_method klass, attribute
end
end
all_files.each do |file|
save_page file
end
save_cache
end
##
# Writes the cache file for this store
def save_cache
clean_cache_collection @cache[:ancestors]
clean_cache_collection @cache[:attributes]
clean_cache_collection @cache[:class_methods]
clean_cache_collection @cache[:instance_methods]
@cache[:modules].uniq!
@cache[:modules].sort!
@cache[:pages].uniq!
@cache[:pages].sort!
@cache[:encoding] = @encoding # this gets set twice due to assert_cache
@cache[:c_class_variables].merge! @c_class_variables
@cache[:c_singleton_class_variables].merge! @c_singleton_class_variables
return if @dry_run
marshal = Marshal.dump @cache
open cache_path, 'wb' do |io|
io.write marshal
end
end
##
# Writes the ri data for +klass+ (or module)
def save_class klass
full_name = klass.full_name
FileUtils.mkdir_p class_path(full_name) unless @dry_run
@cache[:modules] << full_name
path = class_file full_name
begin
disk_klass = load_class full_name
klass = disk_klass.merge klass
rescue MissingFileError
end
# BasicObject has no ancestors
ancestors = klass.direct_ancestors.compact.map do |ancestor|
# HACK for classes we don't know about (class X < RuntimeError)
String === ancestor ? ancestor : ancestor.full_name
end
@cache[:ancestors][full_name] ||= []
@cache[:ancestors][full_name].concat ancestors
attribute_definitions = klass.attributes.map do |attribute|
"#{attribute.definition} #{attribute.name}"
end
unless attribute_definitions.empty? then
@cache[:attributes][full_name] ||= []
@cache[:attributes][full_name].concat attribute_definitions
end
to_delete = []
unless klass.method_list.empty? then
@cache[:class_methods][full_name] ||= []
@cache[:instance_methods][full_name] ||= []
class_methods, instance_methods =
klass.method_list.partition { |meth| meth.singleton }
class_methods = class_methods. map { |method| method.name }
instance_methods = instance_methods.map { |method| method.name }
attribute_names = klass.attributes.map { |attr| attr.name }
old = @cache[:class_methods][full_name] - class_methods
to_delete.concat old.map { |method|
method_file full_name, "#{full_name}::#{method}"
}
old = @cache[:instance_methods][full_name] -
instance_methods - attribute_names
to_delete.concat old.map { |method|
method_file full_name, "#{full_name}##{method}"
}
@cache[:class_methods][full_name] = class_methods
@cache[:instance_methods][full_name] = instance_methods
end
return if @dry_run
FileUtils.rm_f to_delete
marshal = Marshal.dump klass
open path, 'wb' do |io|
io.write marshal
end
end
##
# Writes the ri data for +method+ on +klass+
def save_method klass, method
full_name = klass.full_name
FileUtils.mkdir_p class_path(full_name) unless @dry_run
cache = if method.singleton then
@cache[:class_methods]
else
@cache[:instance_methods]
end
cache[full_name] ||= []
cache[full_name] << method.name
return if @dry_run
marshal = Marshal.dump method
open method_file(full_name, method.full_name), 'wb' do |io|
io.write marshal
end
end
##
# Writes the ri data for +page+
def save_page page
return unless page.text?
path = page_file page.full_name
FileUtils.mkdir_p File.dirname(path) unless @dry_run
cache[:pages] ||= []
cache[:pages] << page.full_name
return if @dry_run
marshal = Marshal.dump page
open path, 'wb' do |io|
io.write marshal
end
end
##
# Source of the contents of this store.
#
# For a store from a gem the source is the gem name. For a store from the
# home directory the source is "home". For system ri store (the standard
# library documentation) the source is"ruby". For a store from the site
# ri directory the store is "site". For other stores the source is the
# #path.
def source
case type
when :gem then File.basename File.expand_path '..', @path
when :home then 'home'
when :site then 'site'
when :system then 'ruby'
else @path
end
end
##
# Gets the title for this RDoc store. This is used as the title in each
# page on the RDoc server
def title
@cache[:title]
end
##
# Sets the title page for this RDoc store.
def title= title
@cache[:title] = title
end
##
# Returns the unique classes discovered by RDoc.
#
# ::complete must have been called prior to using this method.
def unique_classes
@unique_classes
end
##
# Returns the unique classes and modules discovered by RDoc.
# ::complete must have been called prior to using this method.
def unique_classes_and_modules
@unique_classes + @unique_modules
end
##
# Returns the unique modules discovered by RDoc.
# ::complete must have been called prior to using this method.
def unique_modules
@unique_modules
end
end
code_object.rb 0000644 00000017331 14763537411 0007351 0 ustar 00 ##
# Base class for the RDoc code tree.
#
# We contain the common stuff for contexts (which are containers) and other
# elements (methods, attributes and so on)
#
# Here's the tree of the CodeObject subclasses:
#
# * RDoc::Context
# * RDoc::TopLevel
# * RDoc::ClassModule
# * RDoc::AnonClass (never used so far)
# * RDoc::NormalClass
# * RDoc::NormalModule
# * RDoc::SingleClass
# * RDoc::MethodAttr
# * RDoc::Attr
# * RDoc::AnyMethod
# * RDoc::GhostMethod
# * RDoc::MetaMethod
# * RDoc::Alias
# * RDoc::Constant
# * RDoc::Require
# * RDoc::Include
class RDoc::CodeObject
include RDoc::Text
##
# Our comment
attr_reader :comment
##
# Do we document our children?
attr_reader :document_children
##
# Do we document ourselves?
attr_reader :document_self
##
# Are we done documenting (ie, did we come across a :enddoc:)?
attr_reader :done_documenting
##
# Which file this code object was defined in
attr_reader :file
##
# Force documentation of this CodeObject
attr_reader :force_documentation
##
# Line in #file where this CodeObject was defined
attr_accessor :line
##
# Hash of arbitrary metadata for this CodeObject
attr_reader :metadata
##
# Offset in #file where this CodeObject was defined
#--
# TODO character or byte?
attr_accessor :offset
##
# Sets the parent CodeObject
attr_writer :parent
##
# Did we ever receive a +:nodoc:+ directive?
attr_reader :received_nodoc
##
# Set the section this CodeObject is in
attr_writer :section
##
# The RDoc::Store for this object.
attr_accessor :store
##
# We are the model of the code, but we know that at some point we will be
# worked on by viewers. By implementing the Viewable protocol, viewers can
# associated themselves with these objects.
attr_accessor :viewer
##
# Creates a new CodeObject that will document itself and its children
def initialize
@metadata = {}
@comment = ''
@parent = nil
@parent_name = nil # for loading
@parent_class = nil # for loading
@section = nil
@section_title = nil # for loading
@file = nil
@full_name = nil
@store = nil
initialize_visibility
end
##
# Initializes state for visibility of this CodeObject and its children.
def initialize_visibility # :nodoc:
@document_children = true
@document_self = true
@done_documenting = false
@force_documentation = false
@received_nodoc = false
@ignored = false
end
##
# Replaces our comment with +comment+, unless it is empty.
def comment=(comment)
@comment = case comment
when NilClass then ''
when RDoc::Markup::Document then comment
when RDoc::Comment then comment.normalize
else
if comment and not comment.empty? then
normalize_comment comment
else
# HACK correct fix is to have #initialize create @comment
# with the correct encoding
if String === @comment and
Object.const_defined? :Encoding and @comment.empty? then
@comment.force_encoding comment.encoding
end
@comment
end
end
end
##
# Should this CodeObject be shown in documentation?
def display?
@document_self and not @ignored
end
##
# Enables or disables documentation of this CodeObject's children unless it
# has been turned off by :enddoc:
def document_children=(document_children)
@document_children = document_children unless @done_documenting
end
##
# Enables or disables documentation of this CodeObject unless it has been
# turned off by :enddoc:. If the argument is +nil+ it means the
# documentation is turned off by +:nodoc:+.
def document_self=(document_self)
return if @done_documenting
@document_self = document_self
@received_nodoc = true if document_self.nil?
end
##
# Does this object have a comment with content or is #received_nodoc true?
def documented?
@received_nodoc or !@comment.empty?
end
##
# Turns documentation on/off, and turns on/off #document_self
# and #document_children.
#
# Once documentation has been turned off (by +:enddoc:+),
# the object will refuse to turn #document_self or
# #document_children on, so +:doc:+ and +:start_doc:+ directives
# will have no effect in the current file.
def done_documenting=(value)
@done_documenting = value
@document_self = !value
@document_children = @document_self
end
##
# Yields each parent of this CodeObject. See also
# RDoc::ClassModule#each_ancestor
def each_parent
code_object = self
while code_object = code_object.parent do
yield code_object
end
self
end
##
# File name where this CodeObject was found.
#
# See also RDoc::Context#in_files
def file_name
return unless @file
@file.absolute_name
end
##
# Force the documentation of this object unless documentation
# has been turned off by :enddoc:
#--
# HACK untested, was assigning to an ivar
def force_documentation=(value)
@force_documentation = value unless @done_documenting
end
##
# Sets the full_name overriding any computed full name.
#
# Set to +nil+ to clear RDoc's cached value
def full_name= full_name
@full_name = full_name
end
##
# Use this to ignore a CodeObject and all its children until found again
# (#record_location is called). An ignored item will not be shown in
# documentation.
#
# See github issue #55
#
# The ignored status is temporary in order to allow implementation details
# to be hidden. At the end of processing a file RDoc allows all classes
# and modules to add new documentation to previously created classes.
#
# If a class was ignored (via stopdoc) then reopened later with additional
# documentation it should be shown. If a class was ignored and never
# reopened it should not be shown. The ignore flag allows this to occur.
def ignore
@ignored = true
stop_doc
end
##
# Has this class been ignored?
def ignored?
@ignored
end
##
# Our parent CodeObject. The parent may be missing for classes loaded from
# legacy RI data stores.
def parent
return @parent if @parent
return nil unless @parent_name
if @parent_class == RDoc::TopLevel then
@parent = @store.add_file @parent_name
else
@parent = @store.find_class_or_module @parent_name
return @parent if @parent
begin
@parent = @store.load_class @parent_name
rescue RDoc::Store::MissingFileError
nil
end
end
end
##
# File name of our parent
def parent_file_name
@parent ? @parent.base_name : '(unknown)'
end
##
# Name of our parent
def parent_name
@parent ? @parent.full_name : '(unknown)'
end
##
# Records the RDoc::TopLevel (file) where this code object was defined
def record_location top_level
@ignored = false
@file = top_level
end
##
# The section this CodeObject is in. Sections allow grouping of constants,
# attributes and methods inside a class or module.
def section
return @section if @section
@section = parent.add_section @section_title if parent
end
##
# Enable capture of documentation unless documentation has been
# turned off by :enddoc:
def start_doc
return if @done_documenting
@document_self = true
@document_children = true
@ignored = false
end
##
# Disable capture of documentation
def stop_doc
@document_self = false
@document_children = false
end
end
context/section.rb 0000644 00000011471 14763537411 0010240 0 ustar 00 ##
# A section of documentation like:
#
# # :section: The title
# # The body
#
# Sections can be referenced multiple times and will be collapsed into a
# single section.
class RDoc::Context::Section
include RDoc::Text
MARSHAL_VERSION = 0 # :nodoc:
##
# Section comment
attr_reader :comment
##
# Section comments
attr_reader :comments
##
# Context this Section lives in
attr_reader :parent
##
# Section title
attr_reader :title
@@sequence = "SEC00000"
##
# Creates a new section with +title+ and +comment+
def initialize parent, title, comment
@parent = parent
@title = title ? title.strip : title
@@sequence.succ!
@sequence = @@sequence.dup
@comments = []
add_comment comment
end
##
# Sections are equal when they have the same #title
def == other
self.class === other and @title == other.title
end
##
# Adds +comment+ to this section
def add_comment comment
comment = extract_comment comment
return if comment.empty?
case comment
when RDoc::Comment then
@comments << comment
when RDoc::Markup::Document then
@comments.concat comment.parts
when Array then
@comments.concat comment
else
raise TypeError, "unknown comment type: #{comment.inspect}"
end
end
##
# Anchor reference for linking to this section
def aref
title = @title || '[untitled]'
CGI.escape(title).gsub('%', '-').sub(/^-/, '')
end
##
# Extracts the comment for this section from the original comment block.
# If the first line contains :section:, strip it and use the rest.
# Otherwise remove lines up to the line containing :section:, and look
# for those lines again at the end and remove them. This lets us write
#
# # :section: The title
# # The body
def extract_comment comment
case comment
when Array then
comment.map do |c|
extract_comment c
end
when nil
RDoc::Comment.new ''
when RDoc::Comment then
if comment.text =~ /^#[ \t]*:section:.*\n/ then
start = $`
rest = $'
comment.text = if start.empty? then
rest
else
rest.sub(/#{start.chomp}\Z/, '')
end
end
comment
when RDoc::Markup::Document then
comment
else
raise TypeError, "unknown comment #{comment.inspect}"
end
end
def inspect # :nodoc:
"#<%s:0x%x %p>" % [self.class, object_id, title]
end
##
# The files comments in this section come from
def in_files
return [] if @comments.empty?
case @comments
when Array then
@comments.map do |comment|
comment.file
end
when RDoc::Markup::Document then
@comment.parts.map do |document|
document.file
end
else
raise RDoc::Error, "BUG: unknown comment class #{@comments.class}"
end
end
##
# Serializes this Section. The title and parsed comment are saved, but not
# the section parent which must be restored manually.
def marshal_dump
[
MARSHAL_VERSION,
@title,
parse,
]
end
##
# De-serializes this Section. The section parent must be restored manually.
def marshal_load array
@parent = nil
@title = array[1]
@comments = array[2]
end
##
# Parses +comment_location+ into an RDoc::Markup::Document composed of
# multiple RDoc::Markup::Documents with their file set.
def parse
case @comments
when String then
super
when Array then
docs = @comments.map do |comment, location|
doc = super comment
doc.file = location if location
doc
end
RDoc::Markup::Document.new(*docs)
when RDoc::Comment then
doc = super @comments.text, comments.format
doc.file = @comments.location
doc
when RDoc::Markup::Document then
return @comments
else
raise ArgumentError, "unknown comment class #{comments.class}"
end
end
##
# The section's title, or 'Top Section' if the title is nil.
#
# This is used by the table of contents template so the name is silly.
def plain_html
@title || 'Top Section'
end
##
# Removes a comment from this section if it is from the same file as
# +comment+
def remove_comment comment
return if @comments.empty?
case @comments
when Array then
@comments.delete_if do |my_comment|
my_comment.file == comment.file
end
when RDoc::Markup::Document then
@comments.parts.delete_if do |document|
document.file == comment.file.name
end
else
raise RDoc::Error, "BUG: unknown comment class #{@comments.class}"
end
end
##
# Section sequence number (deprecated)
def sequence
warn "RDoc::Context::Section#sequence is deprecated, use #aref"
@sequence
end
end
options.rb 0000644 00000070764 14763537411 0006615 0 ustar 00 require 'optparse'
require 'pathname'
##
# RDoc::Options handles the parsing and storage of options
#
# == Saved Options
#
# You can save some options like the markup format in the
# .rdoc_options file in your gem. The easiest way to do this is:
#
# rdoc --markup tomdoc --write-options
#
# Which will automatically create the file and fill it with the options you
# specified.
#
# The following options will not be saved since they interfere with the user's
# preferences or with the normal operation of RDoc:
#
# * +--coverage-report+
# * +--dry-run+
# * +--encoding+
# * +--force-update+
# * +--format+
# * +--pipe+
# * +--quiet+
# * +--template+
# * +--verbose+
#
# == Custom Options
#
# Generators can hook into RDoc::Options to add generator-specific command
# line options.
#
# When --format is encountered in ARGV, RDoc calls ::setup_options on
# the generator class to add extra options to the option parser. Options for
# custom generators must occur after --format. rdoc --help
# will list options for all installed generators.
#
# Example:
#
# class RDoc::Generator::Spellcheck
# RDoc::RDoc.add_generator self
#
# def self.setup_options rdoc_options
# op = rdoc_options.option_parser
#
# op.on('--spell-dictionary DICTIONARY',
# RDoc::Options::Path) do |dictionary|
# rdoc_options.spell_dictionary = dictionary
# end
# end
# end
#
# == Option Validators
#
# OptionParser validators will validate and cast user input values. In
# addition to the validators that ship with OptionParser (String, Integer,
# Float, TrueClass, FalseClass, Array, Regexp, Date, Time, URI, etc.),
# RDoc::Options adds Path, PathArray and Template.
class RDoc::Options
##
# The deprecated options.
DEPRECATED = {
'--accessor' => 'support discontinued',
'--diagram' => 'support discontinued',
'--help-output' => 'support discontinued',
'--image-format' => 'was an option for --diagram',
'--inline-source' => 'source code is now always inlined',
'--merge' => 'ri now always merges class information',
'--one-file' => 'support discontinued',
'--op-name' => 'support discontinued',
'--opname' => 'support discontinued',
'--promiscuous' => 'files always only document their content',
'--ri-system' => 'Ruby installers use other techniques',
}
##
# RDoc options ignored (or handled specially) by --write-options
SPECIAL = %w[
coverage_report
dry_run
encoding
files
force_output
force_update
generator
generator_name
generator_options
generators
op_dir
option_parser
pipe
rdoc_include
root
static_path
stylesheet_url
template
template_dir
update_output_dir
verbosity
write_options
]
##
# Option validator for OptionParser that matches a directory that exists on
# the filesystem.
Directory = Object.new
##
# Option validator for OptionParser that matches a file or directory that
# exists on the filesystem.
Path = Object.new
##
# Option validator for OptionParser that matches a comma-separated list of
# files or directories that exist on the filesystem.
PathArray = Object.new
##
# Option validator for OptionParser that matches a template directory for an
# installed generator that lives in
# "rdoc/generator/template/#{template_name}"
Template = Object.new
##
# Character-set for HTML output. #encoding is preferred over #charset
attr_accessor :charset
##
# If true, RDoc will not write any files.
attr_accessor :dry_run
##
# The output encoding. All input files will be transcoded to this encoding.
#
# The default encoding is UTF-8. This is set via --encoding.
attr_accessor :encoding
##
# Files matching this pattern will be excluded
attr_accessor :exclude
##
# The list of files to be processed
attr_accessor :files
##
# Create the output even if the output directory does not look
# like an rdoc output directory
attr_accessor :force_output
##
# Scan newer sources than the flag file if true.
attr_accessor :force_update
##
# Formatter to mark up text with
attr_accessor :formatter
##
# Description of the output generator (set with the --format option)
attr_accessor :generator
##
# For #==
attr_reader :generator_name # :nodoc:
##
# Loaded generator options. Used to prevent --help from loading the same
# options multiple times.
attr_accessor :generator_options
##
# Old rdoc behavior: hyperlink all words that match a method name,
# even if not preceded by '#' or '::'
attr_accessor :hyperlink_all
##
# Include line numbers in the source code
attr_accessor :line_numbers
##
# Name of the file, class or module to display in the initial index page (if
# not specified the first file we encounter is used)
attr_accessor :main_page
##
# The default markup format. The default is 'rdoc'. 'markdown', 'tomdoc'
# and 'rd' are also built-in.
attr_accessor :markup
##
# If true, only report on undocumented files
attr_accessor :coverage_report
##
# The name of the output directory
attr_accessor :op_dir
##
# The OptionParser for this instance
attr_accessor :option_parser
##
# Directory where guides, FAQ, and other pages not associated with a class
# live. You may leave this unset if these are at the root of your project.
attr_accessor :page_dir
##
# Is RDoc in pipe mode?
attr_accessor :pipe
##
# Array of directories to search for files to satisfy an :include:
attr_accessor :rdoc_include
##
# Root of the source documentation will be generated for. Set this when
# building documentation outside the source directory. Defaults to the
# current directory.
attr_accessor :root
##
# Include the '#' at the front of hyperlinked instance method names
attr_accessor :show_hash
##
# Directory to copy static files from
attr_accessor :static_path
##
# The number of columns in a tab
attr_accessor :tab_width
##
# Template to be used when generating output
attr_accessor :template
##
# Directory the template lives in
attr_accessor :template_dir
##
# Documentation title
attr_accessor :title
##
# Should RDoc update the timestamps in the output dir?
attr_accessor :update_output_dir
##
# Verbosity, zero means quiet
attr_accessor :verbosity
##
# URL of web cvs frontend
attr_accessor :webcvs
##
# Minimum visibility of a documented method. One of +:public+,
# +:protected+, +:private+. May be overridden on a per-method
# basis with the :doc: directive.
attr_accessor :visibility
def initialize # :nodoc:
init_ivars
end
def init_ivars # :nodoc:
@dry_run = false
@exclude = []
@files = nil
@force_output = false
@force_update = true
@generator = nil
@generator_name = nil
@generator_options = []
@generators = RDoc::RDoc::GENERATORS
@hyperlink_all = false
@line_numbers = false
@main_page = nil
@markup = 'rdoc'
@coverage_report = false
@op_dir = nil
@page_dir = nil
@pipe = false
@rdoc_include = []
@root = Pathname(Dir.pwd)
@show_hash = false
@static_path = []
@stylesheet_url = nil # TODO remove in RDoc 4
@tab_width = 8
@template = nil
@template_dir = nil
@title = nil
@update_output_dir = true
@verbosity = 1
@visibility = :protected
@webcvs = nil
@write_options = false
if Object.const_defined? :Encoding then
@encoding = Encoding::UTF_8
@charset = @encoding.name
else
@encoding = nil
@charset = 'UTF-8'
end
end
def init_with map # :nodoc:
init_ivars
encoding = map['encoding']
@encoding = if Object.const_defined? :Encoding then
encoding ? Encoding.find(encoding) : encoding
end
@charset = map['charset']
@exclude = map['exclude']
@generator_name = map['generator_name']
@hyperlink_all = map['hyperlink_all']
@line_numbers = map['line_numbers']
@main_page = map['main_page']
@markup = map['markup']
@op_dir = map['op_dir']
@show_hash = map['show_hash']
@tab_width = map['tab_width']
@template_dir = map['template_dir']
@title = map['title']
@visibility = map['visibility']
@webcvs = map['webcvs']
@rdoc_include = sanitize_path map['rdoc_include']
@static_path = sanitize_path map['static_path']
end
def yaml_initialize tag, map # :nodoc:
init_with map
end
def == other # :nodoc:
self.class === other and
@encoding == other.encoding and
@generator_name == other.generator_name and
@hyperlink_all == other.hyperlink_all and
@line_numbers == other.line_numbers and
@main_page == other.main_page and
@markup == other.markup and
@op_dir == other.op_dir and
@rdoc_include == other.rdoc_include and
@show_hash == other.show_hash and
@static_path == other.static_path and
@tab_width == other.tab_width and
@template == other.template and
@title == other.title and
@visibility == other.visibility and
@webcvs == other.webcvs
end
##
# Check that the files on the command line exist
def check_files
@files.delete_if do |file|
if File.exist? file then
if File.readable? file then
false
else
warn "file '#{file}' not readable"
true
end
else
warn "file '#{file}' not found"
true
end
end
end
##
# Ensure only one generator is loaded
def check_generator
if @generator then
raise OptionParser::InvalidOption,
"generator already set to #{@generator_name}"
end
end
##
# Set the title, but only if not already set. Used to set the title
# from a source file, so that a title set from the command line
# will have the priority.
def default_title=(string)
@title ||= string
end
##
# For dumping YAML
def encode_with coder # :nodoc:
encoding = @encoding ? @encoding.name : nil
coder.add 'encoding', encoding
coder.add 'static_path', sanitize_path(@static_path)
coder.add 'rdoc_include', sanitize_path(@rdoc_include)
ivars = instance_variables.map { |ivar| ivar.to_s[1..-1] }
ivars -= SPECIAL
ivars.sort.each do |ivar|
coder.add ivar, instance_variable_get("@#{ivar}")
end
end
##
# Completes any unfinished option setup business such as filtering for
# existent files, creating a regexp for #exclude and setting a default
# #template.
def finish
@op_dir ||= 'doc'
@rdoc_include << "." if @rdoc_include.empty?
if @exclude.nil? or Regexp === @exclude then
# done, #finish is being re-run
elsif @exclude.empty? then
@exclude = nil
else
@exclude = Regexp.new(@exclude.join("|"))
end
finish_page_dir
check_files
# If no template was specified, use the default template for the output
# formatter
unless @template then
@template = @generator_name
@template_dir = template_dir_for @template
end
self
end
##
# Fixes the page_dir to be relative to the root_dir and adds the page_dir to
# the files list.
def finish_page_dir
return unless @page_dir
@files << @page_dir.to_s
page_dir = @page_dir.expand_path.relative_path_from @root
@page_dir = page_dir
end
##
# Returns a properly-space list of generators and their descriptions.
def generator_descriptions
lengths = []
generators = RDoc::RDoc::GENERATORS.map do |name, generator|
lengths << name.length
description = generator::DESCRIPTION if
generator.const_defined? :DESCRIPTION
[name, description]
end
longest = lengths.max
generators.sort.map do |name, description|
if description then
" %-*s - %s" % [longest, name, description]
else
" #{name}"
end
end.join "\n"
end
##
# Parses command line options.
def parse argv
ignore_invalid = true
argv.insert(0, *ENV['RDOCOPT'].split) if ENV['RDOCOPT']
opts = OptionParser.new do |opt|
@option_parser = opt
opt.program_name = File.basename $0
opt.version = RDoc::VERSION
opt.release = nil
opt.summary_indent = ' ' * 4
opt.banner = <<-EOF
Usage: #{opt.program_name} [options] [names...]
Files are parsed, and the information they contain collected, before any
output is produced. This allows cross references between all files to be
resolved. If a name is a directory, it is traversed. If no names are
specified, all Ruby files in the current directory (and subdirectories) are
processed.
How RDoc generates output depends on the output formatter being used, and on
the options you give.
Options can be specified via the RDOCOPT environment variable, which
functions similar to the RUBYOPT environment variable for ruby.
$ export RDOCOPT="--show-hash"
will make rdoc show hashes in method links by default. Command-line options
always will override those in RDOCOPT.
Available formatters:
#{generator_descriptions}
RDoc understands the following file formats:
EOF
parsers = Hash.new { |h,parser| h[parser] = [] }
RDoc::Parser.parsers.each do |regexp, parser|
parsers[parser.name.sub('RDoc::Parser::', '')] << regexp.source
end
parsers.sort.each do |parser, regexp|
opt.banner << " - #{parser}: #{regexp.join ', '}\n"
end
opt.banner << "\n The following options are deprecated:\n\n"
name_length = DEPRECATED.keys.sort_by { |k| k.length }.last.length
DEPRECATED.sort_by { |k,| k }.each do |name, reason|
opt.banner << " %*1$2$s %3$s\n" % [-name_length, name, reason]
end
opt.accept Template do |template|
template_dir = template_dir_for template
unless template_dir then
$stderr.puts "could not find template #{template}"
nil
else
[template, template_dir]
end
end
opt.accept Directory do |directory|
directory = File.expand_path directory
raise OptionParser::InvalidArgument unless File.directory? directory
directory
end
opt.accept Path do |path|
path = File.expand_path path
raise OptionParser::InvalidArgument unless File.exist? path
path
end
opt.accept PathArray do |paths,|
paths = if paths then
paths.split(',').map { |d| d unless d.empty? }
end
paths.map do |path|
path = File.expand_path path
raise OptionParser::InvalidArgument unless File.exist? path
path
end
end
opt.separator nil
opt.separator "Parsing options:"
opt.separator nil
if Object.const_defined? :Encoding then
opt.on("--encoding=ENCODING", "-e", Encoding.list.map { |e| e.name },
"Specifies the output encoding. All files",
"read will be converted to this encoding.",
"The default encoding is UTF-8.",
"--encoding is preferred over --charset") do |value|
@encoding = Encoding.find value
@charset = @encoding.name # may not be valid value
end
opt.separator nil
end
opt.on("--all", "-a",
"Synonym for --visibility=private.") do |value|
@visibility = :private
end
opt.separator nil
opt.on("--exclude=PATTERN", "-x", Regexp,
"Do not process files or directories",
"matching PATTERN.") do |value|
@exclude << value
end
opt.separator nil
opt.on("--extension=NEW=OLD", "-E",
"Treat files ending with .new as if they",
"ended with .old. Using '-E cgi=rb' will",
"cause xxx.cgi to be parsed as a Ruby file.") do |value|
new, old = value.split(/=/, 2)
unless new and old then
raise OptionParser::InvalidArgument, "Invalid parameter to '-E'"
end
unless RDoc::Parser.alias_extension old, new then
raise OptionParser::InvalidArgument, "Unknown extension .#{old} to -E"
end
end
opt.separator nil
opt.on("--[no-]force-update", "-U",
"Forces rdoc to scan all sources even if",
"newer than the flag file.") do |value|
@force_update = value
end
opt.separator nil
opt.on("--pipe", "-p",
"Convert RDoc on stdin to HTML") do
@pipe = true
end
opt.separator nil
opt.on("--tab-width=WIDTH", "-w", OptionParser::DecimalInteger,
"Set the width of tab characters.") do |value|
@tab_width = value
end
opt.separator nil
opt.on("--visibility=VISIBILITY", "-V", RDoc::VISIBILITIES,
"Minimum visibility to document a method.",
"One of 'public', 'protected' (the default)",
"or 'private'. Can be abbreviated.") do |value|
@visibility = value
end
opt.separator nil
markup_formats = RDoc::Text::MARKUP_FORMAT.keys.sort
opt.on("--markup=MARKUP", markup_formats,
"The markup format for the named files.",
"The default is rdoc. Valid values are:",
markup_formats.join(', ')) do |value|
@markup = value
end
opt.separator nil
opt.on("--root=ROOT", Directory,
"Root of the source tree documentation",
"will be generated for. Set this when",
"building documentation outside the",
"source directory. Default is the",
"current directory.") do |root|
@root = Pathname(root)
end
opt.separator nil
opt.on("--page-dir=DIR", Directory,
"Directory where guides, your FAQ or",
"other pages not associated with a class",
"live. Set this when you don't store",
"such files at your project root.",
"NOTE: Do not use the same file name in",
"the page dir and the root of your project") do |page_dir|
@page_dir = Pathname(page_dir)
end
opt.separator nil
opt.separator "Common generator options:"
opt.separator nil
opt.on("--force-output", "-O",
"Forces rdoc to write the output files,",
"even if the output directory exists",
"and does not seem to have been created",
"by rdoc.") do |value|
@force_output = value
end
opt.separator nil
generator_text = @generators.keys.map { |name| " #{name}" }.sort
opt.on("-f", "--fmt=FORMAT", "--format=FORMAT", @generators.keys,
"Set the output formatter. One of:", *generator_text) do |value|
check_generator
@generator_name = value.downcase
setup_generator
end
opt.separator nil
opt.on("--include=DIRECTORIES", "-i", PathArray,
"Set (or add to) the list of directories to",
"be searched when satisfying :include:",
"requests. Can be used more than once.") do |value|
@rdoc_include.concat value.map { |dir| dir.strip }
end
opt.separator nil
opt.on("--[no-]coverage-report=[LEVEL]", "--[no-]dcov", "-C", Integer,
"Prints a report on undocumented items.",
"Does not generate files.") do |value|
value = 0 if value.nil? # Integer converts -C to nil
@coverage_report = value
@force_update = true if value
end
opt.separator nil
opt.on("--output=DIR", "--op", "-o",
"Set the output directory.") do |value|
@op_dir = value
end
opt.separator nil
opt.on("-d",
"Deprecated --diagram option.",
"Prevents firing debug mode",
"with legacy invocation.") do |value|
end
opt.separator nil
opt.separator 'HTML generator options:'
opt.separator nil
opt.on("--charset=CHARSET", "-c",
"Specifies the output HTML character-set.",
"Use --encoding instead of --charset if",
"available.") do |value|
@charset = value
end
opt.separator nil
opt.on("--hyperlink-all", "-A",
"Generate hyperlinks for all words that",
"correspond to known methods, even if they",
"do not start with '#' or '::' (legacy",
"behavior).") do |value|
@hyperlink_all = value
end
opt.separator nil
opt.on("--main=NAME", "-m",
"NAME will be the initial page displayed.") do |value|
@main_page = value
end
opt.separator nil
opt.on("--[no-]line-numbers", "-N",
"Include line numbers in the source code.",
"By default, only the number of the first",
"line is displayed, in a leading comment.") do |value|
@line_numbers = value
end
opt.separator nil
opt.on("--show-hash", "-H",
"A name of the form #name in a comment is a",
"possible hyperlink to an instance method",
"name. When displayed, the '#' is removed",
"unless this option is specified.") do |value|
@show_hash = value
end
opt.separator nil
opt.on("--template=NAME", "-T", Template,
"Set the template used when generating",
"output. The default depends on the",
"formatter used.") do |(template, template_dir)|
@template = template
@template_dir = template_dir
end
opt.separator nil
opt.on("--title=TITLE", "-t",
"Set TITLE as the title for HTML output.") do |value|
@title = value
end
opt.separator nil
opt.on("--copy-files=PATH", Path,
"Specify a file or directory to copy static",
"files from.",
"If a file is given it will be copied into",
"the output dir. If a directory is given the",
"entire directory will be copied.",
"You can use this multiple times") do |value|
@static_path << value
end
opt.separator nil
opt.on("--webcvs=URL", "-W",
"Specify a URL for linking to a web frontend",
"to CVS. If the URL contains a '\%s', the",
"name of the current file will be",
"substituted; if the URL doesn't contain a",
"'\%s', the filename will be appended to it.") do |value|
@webcvs = value
end
opt.separator nil
opt.separator "ri generator options:"
opt.separator nil
opt.on("--ri", "-r",
"Generate output for use by `ri`. The files",
"are stored in the '.rdoc' directory under",
"your home directory unless overridden by a",
"subsequent --op parameter, so no special",
"privileges are needed.") do |value|
check_generator
@generator_name = "ri"
@op_dir ||= RDoc::RI::Paths::HOMEDIR
setup_generator
end
opt.separator nil
opt.on("--ri-site", "-R",
"Generate output for use by `ri`. The files",
"are stored in a site-wide directory,",
"making them accessible to others, so",
"special privileges are needed.") do |value|
check_generator
@generator_name = "ri"
@op_dir = RDoc::RI::Paths::SITEDIR
setup_generator
end
opt.separator nil
opt.separator "Generic options:"
opt.separator nil
opt.on("--write-options",
"Write .rdoc_options to the current",
"directory with the given options. Not all",
"options will be used. See RDoc::Options",
"for details.") do |value|
@write_options = true
end
opt.separator nil
opt.on("--[no-]dry-run",
"Don't write any files") do |value|
@dry_run = value
end
opt.separator nil
opt.on("-D", "--[no-]debug",
"Displays lots on internal stuff.") do |value|
$DEBUG_RDOC = value
end
opt.separator nil
opt.on("--[no-]ignore-invalid",
"Ignore invalid options and continue",
"(default true).") do |value|
ignore_invalid = value
end
opt.separator nil
opt.on("--quiet", "-q",
"Don't show progress as we parse.") do |value|
@verbosity = 0
end
opt.separator nil
opt.on("--verbose", "-v",
"Display extra progress as RDoc parses") do |value|
@verbosity = 2
end
opt.separator nil
opt.on("--help",
"Display this help") do
RDoc::RDoc::GENERATORS.each_key do |generator|
setup_generator generator
end
puts opt.help
exit
end
opt.separator nil
end
setup_generator 'darkfish' if
argv.grep(/\A(-f|--fmt|--format|-r|-R|--ri|--ri-site)\b/).empty?
deprecated = []
invalid = []
begin
opts.parse! argv
rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e
if DEPRECATED[e.args.first] then
deprecated << e.args.first
elsif %w[--format --ri -r --ri-site -R].include? e.args.first then
raise
else
invalid << e.args.join(' ')
end
retry
end
unless @generator then
@generator = RDoc::Generator::Darkfish
@generator_name = 'darkfish'
end
if @pipe and not argv.empty? then
@pipe = false
invalid << '-p (with files)'
end
unless quiet then
deprecated.each do |opt|
$stderr.puts 'option ' << opt << ' is deprecated: ' << DEPRECATED[opt]
end
unless invalid.empty? then
invalid = "invalid options: #{invalid.join ', '}"
if ignore_invalid then
$stderr.puts invalid
$stderr.puts '(invalid options are ignored)'
else
$stderr.puts opts
$stderr.puts invalid
exit 1
end
end
end
@files = argv.dup
finish
if @write_options then
write_options
exit
end
self
end
##
# Don't display progress as we process the files
def quiet
@verbosity.zero?
end
##
# Set quietness to +bool+
def quiet= bool
@verbosity = bool ? 0 : 1
end
##
# Removes directories from +path+ that are outside the current directory
def sanitize_path path
require 'pathname'
dot = Pathname.new('.').expand_path
path.reject do |item|
path = Pathname.new(item).expand_path
relative = path.relative_path_from(dot).to_s
relative.start_with? '..'
end
end
##
# Set up an output generator for the named +generator_name+.
#
# If the found generator responds to :setup_options it will be called with
# the options instance. This allows generators to add custom options or set
# default options.
def setup_generator generator_name = @generator_name
@generator = @generators[generator_name]
unless @generator then
raise OptionParser::InvalidArgument,
"Invalid output formatter #{generator_name}"
end
return if @generator_options.include? @generator
@generator_name = generator_name
@generator_options << @generator
if @generator.respond_to? :setup_options then
@option_parser ||= OptionParser.new
@generator.setup_options self
end
end
##
# Finds the template dir for +template+
def template_dir_for template
template_path = File.join 'rdoc', 'generator', 'template', template
$LOAD_PATH.map do |path|
File.join File.expand_path(path), template_path
end.find do |dir|
File.directory? dir
end
end
##
# This is compatibility code for syck
def to_yaml opts = {} # :nodoc:
return super if YAML.const_defined?(:ENGINE) and not YAML::ENGINE.syck?
YAML.quick_emit self, opts do |out|
out.map taguri, to_yaml_style do |map|
encode_with map
end
end
end
##
# Displays a warning using Kernel#warn if we're being verbose
def warn message
super message if @verbosity > 1
end
##
# Writes the YAML file .rdoc_options to the current directory containing the
# parsed options.
def write_options
RDoc.load_yaml
open '.rdoc_options', 'w' do |io|
io.set_encoding Encoding::UTF_8 if Object.const_defined? :Encoding
YAML.dump self, io
end
end
end
parser.rb 0000644 00000017516 14763537411 0006412 0 ustar 00 # -*- coding: us-ascii -*-
##
# A parser is simple a class that subclasses RDoc::Parser and implements #scan
# to fill in an RDoc::TopLevel with parsed data.
#
# The initialize method takes an RDoc::TopLevel to fill with parsed content,
# the name of the file to be parsed, the content of the file, an RDoc::Options
# object and an RDoc::Stats object to inform the user of parsed items. The
# scan method is then called to parse the file and must return the
# RDoc::TopLevel object. By calling super these items will be set for you.
#
# In order to be used by RDoc the parser needs to register the file extensions
# it can parse. Use ::parse_files_matching to register extensions.
#
# require 'rdoc'
#
# class RDoc::Parser::Xyz < RDoc::Parser
# parse_files_matching /\.xyz$/
#
# def initialize top_level, file_name, content, options, stats
# super
#
# # extra initialization if needed
# end
#
# def scan
# # parse file and fill in @top_level
# end
# end
class RDoc::Parser
@parsers = []
class << self
##
# An Array of arrays that maps file extension (or name) regular
# expressions to parser classes that will parse matching filenames.
#
# Use parse_files_matching to register a parser's file extensions.
attr_reader :parsers
end
##
# The name of the file being parsed
attr_reader :file_name
##
# Alias an extension to another extension. After this call, files ending
# "new_ext" will be parsed using the same parser as "old_ext"
def self.alias_extension(old_ext, new_ext)
old_ext = old_ext.sub(/^\.(.*)/, '\1')
new_ext = new_ext.sub(/^\.(.*)/, '\1')
parser = can_parse_by_name "xxx.#{old_ext}"
return false unless parser
RDoc::Parser.parsers.unshift [/\.#{new_ext}$/, parser]
true
end
##
# Determines if the file is a "binary" file which basically means it has
# content that an RDoc parser shouldn't try to consume.
def self.binary?(file)
return false if file =~ /\.(rdoc|txt)$/
s = File.read(file, 1024) or return false
have_encoding = s.respond_to? :encoding
return true if s[0, 2] == Marshal.dump('')[0, 2] or s.index("\x00")
if have_encoding then
mode = "r"
s.sub!(/\A#!.*\n/, '') # assume shebang line isn't longer than 1024.
encoding = s[/^\s*\#\s*(?:-\*-\s*)?(?:en)?coding:\s*([^\s;]+?)(?:-\*-|[\s;])/, 1]
mode = "r:#{encoding}" if encoding
s = File.open(file, mode) {|f| f.gets(nil, 1024)}
not s.valid_encoding?
else
if 0.respond_to? :fdiv then
s.count("\x00-\x7F", "^ -~\t\r\n").fdiv(s.size) > 0.3
else # HACK 1.8.6
(s.count("\x00-\x7F", "^ -~\t\r\n").to_f / s.size) > 0.3
end
end
end
##
# Processes common directives for CodeObjects for the C and Ruby parsers.
#
# Applies +directive+'s +value+ to +code_object+, if appropriate
def self.process_directive code_object, directive, value
warn "RDoc::Parser::process_directive is deprecated and wil be removed in RDoc 4. Use RDoc::Markup::PreProcess#handle_directive instead" if $-w
case directive
when 'nodoc' then
code_object.document_self = nil # notify nodoc
code_object.document_children = value.downcase != 'all'
when 'doc' then
code_object.document_self = true
code_object.force_documentation = true
when 'yield', 'yields' then
# remove parameter &block
code_object.params.sub!(/,?\s*&\w+/, '') if code_object.params
code_object.block_params = value
when 'arg', 'args' then
code_object.params = value
end
end
##
# Checks if +file+ is a zip file in disguise. Signatures from
# http://www.garykessler.net/library/file_sigs.html
def self.zip? file
zip_signature = File.read file, 4
zip_signature == "PK\x03\x04" or
zip_signature == "PK\x05\x06" or
zip_signature == "PK\x07\x08"
rescue
false
end
##
# Return a parser that can handle a particular extension
def self.can_parse file_name
parser = can_parse_by_name file_name
# HACK Selenium hides a jar file using a .txt extension
return if parser == RDoc::Parser::Simple and zip? file_name
parser
end
##
# Returns a parser that can handle the extension for +file_name+. This does
# not depend upon the file being readable.
def self.can_parse_by_name file_name
_, parser = RDoc::Parser.parsers.find { |regexp,| regexp =~ file_name }
# The default parser must not parse binary files
ext_name = File.extname file_name
return parser if ext_name.empty?
if parser == RDoc::Parser::Simple and ext_name !~ /txt|rdoc/ then
case check_modeline file_name
when nil, 'rdoc' then # continue
else return nil
end
end
parser
rescue Errno::EACCES
end
##
# Returns the file type from the modeline in +file_name+
def self.check_modeline file_name
line = open file_name do |io|
io.gets
end
/-\*-\s*(.*?\S)\s*-\*-/ =~ line
return nil unless type = $1
if /;/ =~ type then
return nil unless /(?:\s|\A)mode:\s*([^\s;]+)/i =~ type
type = $1
end
return nil if /coding:/i =~ type
type.downcase
rescue ArgumentError # invalid byte sequence, etc.
end
##
# Finds and instantiates the correct parser for the given +file_name+ and
# +content+.
def self.for top_level, file_name, content, options, stats
return if binary? file_name
parser = use_markup content
unless parser then
parse_name = file_name
# If no extension, look for shebang
if file_name !~ /\.\w+$/ && content =~ %r{\A#!(.+)} then
shebang = $1
case shebang
when %r{env\s+ruby}, %r{/ruby}
parse_name = 'dummy.rb'
end
end
parser = can_parse parse_name
end
return unless parser
parser.new top_level, file_name, content, options, stats
rescue SystemCallError
nil
end
##
# Record which file types this parser can understand.
#
# It is ok to call this multiple times.
def self.parse_files_matching(regexp)
RDoc::Parser.parsers.unshift [regexp, self]
end
##
# If there is a markup: parser_name comment at the front of the
# file, use it to determine the parser. For example:
#
# # markup: rdoc
# # Class comment can go here
#
# class C
# end
#
# The comment should appear as the first line of the +content+.
#
# If the content contains a shebang or editor modeline the comment may
# appear on the second or third line.
#
# Any comment style may be used to hide the markup comment.
def self.use_markup content
markup = content.lines.first(3).grep(/markup:\s+(\w+)/) { $1 }.first
return unless markup
# TODO Ruby should be returned only when the filename is correct
return RDoc::Parser::Ruby if %w[tomdoc markdown].include? markup
markup = Regexp.escape markup
RDoc::Parser.parsers.find do |_, parser|
/^#{markup}$/i =~ parser.name.sub(/.*:/, '')
end.last
end
##
# Creates a new Parser storing +top_level+, +file_name+, +content+,
# +options+ and +stats+ in instance variables. In +@preprocess+ an
# RDoc::Markup::PreProcess object is created which allows processing of
# directives.
def initialize top_level, file_name, content, options, stats
@top_level = top_level
@top_level.parser = self.class
@store = @top_level.store
@file_name = file_name
@content = content
@options = options
@stats = stats
@preprocess = RDoc::Markup::PreProcess.new @file_name, @options.rdoc_include
@preprocess.options = @options
end
autoload :RubyTools, 'rdoc/parser/ruby_tools'
autoload :Text, 'rdoc/parser/text'
end
# simple must come first in order to show up last in the parsers list
require 'rdoc/parser/simple'
require 'rdoc/parser/c'
require 'rdoc/parser/changelog'
require 'rdoc/parser/markdown'
require 'rdoc/parser/rd'
require 'rdoc/parser/ruby'
ri/paths.rb 0000644 00000011341 14763537411 0006635 0 ustar 00 require 'rdoc/ri'
##
# The directories where ri data lives. Paths can be enumerated via ::each, or
# queried individually via ::system_dir, ::site_dir, ::home_dir and ::gem_dir.
module RDoc::RI::Paths
#:stopdoc:
require 'rbconfig'
version = RbConfig::CONFIG['ruby_version']
BASE = if RbConfig::CONFIG.key? 'ridir' then
File.join RbConfig::CONFIG['ridir'], version
else
File.join RbConfig::CONFIG['datadir'], 'ri', version
end
homedir = begin
File.expand_path('~')
rescue ArgumentError
end
homedir ||= ENV['HOME'] ||
ENV['USERPROFILE'] || ENV['HOMEPATH'] # for 1.8 compatibility
HOMEDIR = if homedir then
File.join homedir, ".rdoc"
end
#:startdoc:
##
# Iterates over each selected path yielding the directory and type.
#
# Yielded types:
# :system:: Where Ruby's ri data is stored. Yielded when +system+ is
# true
# :site:: Where ri for installed libraries are stored. Yielded when
# +site+ is true. Normally no ri data is stored here.
# :home:: ~/.rdoc. Yielded when +home+ is true.
# :gem:: ri data for an installed gem. Yielded when +gems+ is true.
# :extra:: ri data directory from the command line. Yielded for each
# entry in +extra_dirs+
def self.each system = true, site = true, home = true, gems = :latest, *extra_dirs # :yields: directory, type
return enum_for __method__, system, site, home, gems, *extra_dirs unless
block_given?
extra_dirs.each do |dir|
yield dir, :extra
end
yield system_dir, :system if system
yield site_dir, :site if site
yield home_dir, :home if home and HOMEDIR
gemdirs(gems).each do |dir|
yield dir, :gem
end if gems
nil
end
##
# The ri directory for the gem with +gem_name+.
def self.gem_dir name, version
req = Gem::Requirement.new "= #{version}"
spec = Gem::Specification.find_by_name name, req
File.join spec.doc_dir, 'ri'
end
##
# The latest installed gems' ri directories. +filter+ can be :all or
# :latest.
#
# A +filter+ :all includes all versions of gems and includes gems without
# ri documentation.
def self.gemdirs filter = :latest
require 'rubygems' unless defined?(Gem)
ri_paths = {}
all = Gem::Specification.map do |spec|
[File.join(spec.doc_dir, 'ri'), spec.name, spec.version]
end
if filter == :all then
gemdirs = []
all.group_by do |_, name, _|
name
end.sort_by do |group, _|
group
end.map do |group, items|
items.sort_by do |_, _, version|
version
end.reverse_each do |dir,|
gemdirs << dir
end
end
return gemdirs
end
all.each do |dir, name, ver|
next unless File.exist? dir
if ri_paths[name].nil? or ver > ri_paths[name].first then
ri_paths[name] = [ver, name, dir]
end
end
ri_paths.sort_by { |_, (_, name, _)| name }.map { |k, v| v.last }
rescue LoadError
[]
end
##
# The location of the rdoc data in the user's home directory.
#
# Like ::system, ri data in the user's home directory is rare and predates
# libraries distributed via RubyGems. ri data is rarely generated into this
# directory.
def self.home_dir
HOMEDIR
end
##
# Returns existing directories from the selected documentation directories
# as an Array.
#
# See also ::each
def self.path(system = true, site = true, home = true, gems = :latest, *extra_dirs)
path = raw_path system, site, home, gems, *extra_dirs
path.select { |directory| File.directory? directory }
end
##
# Returns selected documentation directories including nonexistent
# directories.
#
# See also ::each
def self.raw_path(system, site, home, gems, *extra_dirs)
path = []
each(system, site, home, gems, *extra_dirs) do |dir, type|
path << dir
end
path.compact
end
##
# The location of ri data installed into the site dir.
#
# Historically this was available for documentation installed by ruby
# libraries predating RubyGems. It is unlikely to contain any content for
# modern ruby installations.
def self.site_dir
File.join BASE, 'site'
end
##
# The location of the built-in ri data.
#
# This data is built automatically when `make` is run when ruby is
# installed. If you did not install ruby by hand you may need to install
# the documentation yourself. Please consult the documentation for your
# package manager or ruby installer for details. You can also use the
# rdoc-data gem to install system ri data for common versions of ruby.
def self.system_dir
File.join BASE, 'system'
end
end
ri/store.rb 0000644 00000000067 14763537411 0006655 0 ustar 00 module RDoc::RI
Store = RDoc::Store # :nodoc:
end
ri/formatter.rb 0000644 00000000124 14763537411 0007516 0 ustar 00 ##
# For RubyGems backwards compatibility
module RDoc::RI::Formatter # :nodoc:
end
ri/driver.rb 0000644 00000101341 14763537411 0007011 0 ustar 00 require 'abbrev'
require 'optparse'
begin
require 'readline'
rescue LoadError
end
begin
require 'win32console'
rescue LoadError
end
require 'rdoc'
##
# For RubyGems backwards compatibility
require 'rdoc/ri/formatter'
##
# The RI driver implements the command-line ri tool.
#
# The driver supports:
# * loading RI data from:
# * Ruby's standard library
# * RubyGems
# * ~/.rdoc
# * A user-supplied directory
# * Paging output (uses RI_PAGER environment variable, PAGER environment
# variable or the less, more and pager programs)
# * Interactive mode with tab-completion
# * Abbreviated names (ri Zl shows Zlib documentation)
# * Colorized output
# * Merging output from multiple RI data sources
class RDoc::RI::Driver
##
# Base Driver error class
class Error < RDoc::RI::Error; end
##
# Raised when a name isn't found in the ri data stores
class NotFoundError < Error
##
# Name that wasn't found
alias name message
def message # :nodoc:
"Nothing known about #{super}"
end
end
##
# Show all method documentation following a class or module
attr_accessor :show_all
##
# An RDoc::RI::Store for each entry in the RI path
attr_accessor :stores
##
# Controls the user of the pager vs $stdout
attr_accessor :use_stdout
##
# Default options for ri
def self.default_options
options = {}
options[:interactive] = false
options[:profile] = false
options[:show_all] = false
options[:use_cache] = true
options[:use_stdout] = !$stdout.tty?
options[:width] = 72
# By default all standard paths are used.
options[:use_system] = true
options[:use_site] = true
options[:use_home] = true
options[:use_gems] = true
options[:extra_doc_dirs] = []
return options
end
##
# Dump +data_path+ using pp
def self.dump data_path
require 'pp'
open data_path, 'rb' do |io|
pp Marshal.load(io.read)
end
end
##
# Parses +argv+ and returns a Hash of options
def self.process_args argv
options = default_options
opts = OptionParser.new do |opt|
opt.accept File do |file,|
File.readable?(file) and not File.directory?(file) and file
end
opt.program_name = File.basename $0
opt.version = RDoc::VERSION
opt.release = nil
opt.summary_indent = ' ' * 4
opt.banner = <<-EOT
Usage: #{opt.program_name} [options] [names...]
Where name can be:
Class | Module | Module::Class
Class::method | Class#method | Class.method | method
gem_name: | gem_name:README | gem_name:History
All class names may be abbreviated to their minimum unambiguous form. If a name
is ambiguous, all valid options will be listed.
A '.' matches either class or instance methods, while #method
matches only instance and ::method matches only class methods.
README and other files may be displayed by prefixing them with the gem name
they're contained in. If the gem name is followed by a ':' all files in the
gem will be shown. The file name extension may be omitted where it is
unambiguous.
For example:
#{opt.program_name} Fil
#{opt.program_name} File
#{opt.program_name} File.new
#{opt.program_name} zip
#{opt.program_name} rdoc:README
Note that shell quoting or escaping may be required for method names containing
punctuation:
#{opt.program_name} 'Array.[]'
#{opt.program_name} compact\\!
To see the default directories ri will search, run:
#{opt.program_name} --list-doc-dirs
Specifying the --system, --site, --home, --gems or --doc-dir options will
limit ri to searching only the specified directories.
ri options may be set in the 'RI' environment variable.
The ri pager can be set with the 'RI_PAGER' environment variable or the
'PAGER' environment variable.
EOT
opt.separator nil
opt.separator "Options:"
opt.separator nil
opt.on("--[no-]interactive", "-i",
"In interactive mode you can repeatedly",
"look up methods with autocomplete.") do |interactive|
options[:interactive] = interactive
end
opt.separator nil
opt.on("--[no-]all", "-a",
"Show all documentation for a class or",
"module.") do |show_all|
options[:show_all] = show_all
end
opt.separator nil
opt.on("--[no-]list", "-l",
"List classes ri knows about.") do |list|
options[:list] = list
end
opt.separator nil
opt.on("--[no-]pager", "-T",
"Send output directly to stdout,",
"rather than to a pager.") do |use_pager|
options[:use_stdout] = !use_pager
end
opt.separator nil
opt.on("--width=WIDTH", "-w", OptionParser::DecimalInteger,
"Set the width of the output.") do |width|
options[:width] = width
end
opt.separator nil
opt.on("--server [PORT]", Integer,
"Run RDoc server on the given port.",
"The default port is 8214.") do |port|
options[:server] = port || 8214
end
opt.separator nil
formatters = RDoc::Markup.constants.grep(/^To[A-Z][a-z]+$/).sort
formatters = formatters.sort.map do |formatter|
formatter.to_s.sub('To', '').downcase
end
formatters -= %w[html label test] # remove useless output formats
opt.on("--format=NAME", "-f",
"Uses the selected formatter. The default",
"formatter is bs for paged output and ansi",
"otherwise. Valid formatters are:",
formatters.join(' '), formatters) do |value|
options[:formatter] = RDoc::Markup.const_get "To#{value.capitalize}"
end
opt.separator nil
opt.separator "Data source options:"
opt.separator nil
opt.on("--[no-]list-doc-dirs",
"List the directories from which ri will",
"source documentation on stdout and exit.") do |list_doc_dirs|
options[:list_doc_dirs] = list_doc_dirs
end
opt.separator nil
opt.on("--doc-dir=DIRNAME", "-d", Array,
"List of directories from which to source",
"documentation in addition to the standard",
"directories. May be repeated.") do |value|
value.each do |dir|
unless File.directory? dir then
raise OptionParser::InvalidArgument, "#{dir} is not a directory"
end
options[:extra_doc_dirs] << File.expand_path(dir)
end
end
opt.separator nil
opt.on("--no-standard-docs",
"Do not include documentation from",
"the Ruby standard library, site_lib,",
"installed gems, or ~/.rdoc.",
"Use with --doc-dir") do
options[:use_system] = false
options[:use_site] = false
options[:use_gems] = false
options[:use_home] = false
end
opt.separator nil
opt.on("--[no-]system",
"Include documentation from Ruby's standard",
"library. Defaults to true.") do |value|
options[:use_system] = value
end
opt.separator nil
opt.on("--[no-]site",
"Include documentation from libraries",
"installed in site_lib.",
"Defaults to true.") do |value|
options[:use_site] = value
end
opt.separator nil
opt.on("--[no-]gems",
"Include documentation from RubyGems.",
"Defaults to true.") do |value|
options[:use_gems] = value
end
opt.separator nil
opt.on("--[no-]home",
"Include documentation stored in ~/.rdoc.",
"Defaults to true.") do |value|
options[:use_home] = value
end
opt.separator nil
opt.separator "Debug options:"
opt.separator nil
opt.on("--[no-]profile",
"Run with the ruby profiler") do |value|
options[:profile] = value
end
opt.separator nil
opt.on("--dump=CACHE", File,
"Dumps data from an ri cache or data file") do |value|
options[:dump_path] = value
end
end
argv = ENV['RI'].to_s.split.concat argv
opts.parse! argv
options[:names] = argv
options[:use_stdout] ||= !$stdout.tty?
options[:use_stdout] ||= options[:interactive]
options[:width] ||= 72
options
rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e
puts opts
puts
puts e
exit 1
end
##
# Runs the ri command line executable using +argv+
def self.run argv = ARGV
options = process_args argv
if options[:dump_path] then
dump options[:dump_path]
return
end
ri = new options
ri.run
end
##
# Creates a new driver using +initial_options+ from ::process_args
def initialize initial_options = {}
@paging = false
@classes = nil
options = self.class.default_options.update(initial_options)
@formatter_klass = options[:formatter]
require 'profile' if options[:profile]
@names = options[:names]
@list = options[:list]
@doc_dirs = []
@stores = []
RDoc::RI::Paths.each(options[:use_system], options[:use_site],
options[:use_home], options[:use_gems],
*options[:extra_doc_dirs]) do |path, type|
@doc_dirs << path
store = RDoc::RI::Store.new path, type
store.load_cache
@stores << store
end
@list_doc_dirs = options[:list_doc_dirs]
@interactive = options[:interactive]
@server = options[:server]
@use_stdout = options[:use_stdout]
@show_all = options[:show_all]
# pager process for jruby
@jruby_pager_process = nil
end
##
# Adds paths for undocumented classes +also_in+ to +out+
def add_also_in out, also_in
return if also_in.empty?
out << RDoc::Markup::Rule.new(1)
out << RDoc::Markup::Paragraph.new("Also found in:")
paths = RDoc::Markup::Verbatim.new
also_in.each do |store|
paths.parts.push store.friendly_path, "\n"
end
out << paths
end
##
# Adds a class header to +out+ for class +name+ which is described in
# +classes+.
def add_class out, name, classes
heading = if classes.all? { |klass| klass.module? } then
name
else
superclass = classes.map do |klass|
klass.superclass unless klass.module?
end.compact.shift || 'Object'
superclass = superclass.full_name unless String === superclass
"#{name} < #{superclass}"
end
out << RDoc::Markup::Heading.new(1, heading)
out << RDoc::Markup::BlankLine.new
end
##
# Adds "(from ...)" to +out+ for +store+
def add_from out, store
out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})")
end
##
# Adds +extends+ to +out+
def add_extends out, extends
add_extension_modules out, 'Extended by', extends
end
##
# Adds a list of +extensions+ to this module of the given +type+ to +out+.
# add_includes and add_extends call this, so you should use those directly.
def add_extension_modules out, type, extensions
return if extensions.empty?
out << RDoc::Markup::Rule.new(1)
out << RDoc::Markup::Heading.new(1, "#{type}:")
extensions.each do |modules, store|
if modules.length == 1 then
include = modules.first
name = include.name
path = store.friendly_path
out << RDoc::Markup::Paragraph.new("#{name} (from #{path})")
if include.comment then
out << RDoc::Markup::BlankLine.new
out << include.comment
end
else
out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})")
wout, with = modules.partition { |incl| incl.comment.empty? }
out << RDoc::Markup::BlankLine.new unless with.empty?
with.each do |incl|
out << RDoc::Markup::Paragraph.new(incl.name)
out << RDoc::Markup::BlankLine.new
out << incl.comment
end
unless wout.empty? then
verb = RDoc::Markup::Verbatim.new
wout.each do |incl|
verb.push incl.name, "\n"
end
out << verb
end
end
end
end
##
# Adds +includes+ to +out+
def add_includes out, includes
add_extension_modules out, 'Includes', includes
end
##
# Looks up the method +name+ and adds it to +out+
def add_method out, name
filtered = lookup_method name
method_out = method_document name, filtered
out.concat method_out.parts
end
##
# Adds documentation for all methods in +klass+ to +out+
def add_method_documentation out, klass
klass.method_list.each do |method|
begin
add_method out, method.full_name
rescue NotFoundError
next
end
end
end
##
# Adds a list of +methods+ to +out+ with a heading of +name+
def add_method_list out, methods, name
return if methods.empty?
out << RDoc::Markup::Heading.new(1, "#{name}:")
out << RDoc::Markup::BlankLine.new
if @use_stdout and !@interactive then
out.concat methods.map { |method|
RDoc::Markup::Verbatim.new method
}
else
out << RDoc::Markup::IndentedParagraph.new(2, methods.join(', '))
end
out << RDoc::Markup::BlankLine.new
end
##
# Returns ancestor classes of +klass+
def ancestors_of klass
ancestors = []
unexamined = [klass]
seen = []
loop do
break if unexamined.empty?
current = unexamined.shift
seen << current
stores = classes[current]
break unless stores and not stores.empty?
klasses = stores.map do |store|
store.ancestors[current]
end.flatten.uniq
klasses = klasses - seen
ancestors.concat klasses
unexamined.concat klasses
end
ancestors.reverse
end
##
# For RubyGems backwards compatibility
def class_cache # :nodoc:
end
##
# Builds a RDoc::Markup::Document from +found+, +klasess+ and +includes+
def class_document name, found, klasses, includes, extends
also_in = []
out = RDoc::Markup::Document.new
add_class out, name, klasses
add_includes out, includes
add_extends out, extends
found.each do |store, klass|
comment = klass.comment
# TODO the store's cache should always return an empty Array
class_methods = store.class_methods[klass.full_name] || []
instance_methods = store.instance_methods[klass.full_name] || []
attributes = store.attributes[klass.full_name] || []
if comment.empty? and
instance_methods.empty? and class_methods.empty? then
also_in << store
next
end
add_from out, store
unless comment.empty? then
out << RDoc::Markup::Rule.new(1)
if comment.merged? then
parts = comment.parts
parts = parts.zip [RDoc::Markup::BlankLine.new] * parts.length
parts.flatten!
parts.pop
out.concat parts
else
out << comment
end
end
if class_methods or instance_methods or not klass.constants.empty? then
out << RDoc::Markup::Rule.new(1)
end
unless klass.constants.empty? then
out << RDoc::Markup::Heading.new(1, "Constants:")
out << RDoc::Markup::BlankLine.new
list = RDoc::Markup::List.new :NOTE
constants = klass.constants.sort_by { |constant| constant.name }
list.items.concat constants.map { |constant|
parts = constant.comment.parts if constant.comment
parts << RDoc::Markup::Paragraph.new('[not documented]') if
parts.empty?
RDoc::Markup::ListItem.new(constant.name, *parts)
}
out << list
out << RDoc::Markup::BlankLine.new
end
add_method_list out, class_methods, 'Class methods'
add_method_list out, instance_methods, 'Instance methods'
add_method_list out, attributes, 'Attributes'
add_method_documentation out, klass if @show_all
end
add_also_in out, also_in
out
end
##
# Hash mapping a known class or module to the stores it can be loaded from
def classes
return @classes if @classes
@classes = {}
@stores.each do |store|
store.cache[:modules].each do |mod|
# using default block causes searched-for modules to be added
@classes[mod] ||= []
@classes[mod] << store
end
end
@classes
end
##
# Returns the stores wherein +name+ is found along with the classes,
# extends and includes that match it
def classes_and_includes_and_extends_for name
klasses = []
extends = []
includes = []
found = @stores.map do |store|
begin
klass = store.load_class name
klasses << klass
extends << [klass.extends, store] if klass.extends
includes << [klass.includes, store] if klass.includes
[store, klass]
rescue RDoc::Store::MissingFileError
end
end.compact
extends.reject! do |modules,| modules.empty? end
includes.reject! do |modules,| modules.empty? end
[found, klasses, includes, extends]
end
##
# Completes +name+ based on the caches. For Readline
def complete name
klasses = classes.keys
completions = []
klass, selector, method = parse_name name
# may need to include Foo when given Foo::
klass_name = method ? name : klass
if name !~ /#|\./ then
completions = klasses.grep(/^#{Regexp.escape klass_name}[^:]*$/)
completions.concat klasses.grep(/^#{Regexp.escape name}[^:]*$/) if
name =~ /::$/
completions << klass if classes.key? klass # to complete a method name
elsif selector then
completions << klass if classes.key? klass
elsif classes.key? klass_name then
completions << klass_name
end
if completions.include? klass and name =~ /#|\.|::/ then
methods = list_methods_matching name
if not methods.empty? then
# remove Foo if given Foo:: and a method was found
completions.delete klass
elsif selector then
# replace Foo with Foo:: as given
completions.delete klass
completions << "#{klass}#{selector}"
end
completions.concat methods
end
completions.sort.uniq
end
##
# Converts +document+ to text and writes it to the pager
def display document
page do |io|
text = document.accept formatter(io)
io.write text
end
end
##
# Outputs formatted RI data for class +name+. Groups undocumented classes
def display_class name
return if name =~ /#|\./
found, klasses, includes, extends =
classes_and_includes_and_extends_for name
return if found.empty?
out = class_document name, found, klasses, includes, extends
display out
end
##
# Outputs formatted RI data for method +name+
def display_method name
out = RDoc::Markup::Document.new
add_method out, name
display out
end
##
# Outputs formatted RI data for the class or method +name+.
#
# Returns true if +name+ was found, false if it was not an alternative could
# be guessed, raises an error if +name+ couldn't be guessed.
def display_name name
if name =~ /\w:(\w|$)/ then
display_page name
return true
end
return true if display_class name
display_method name if name =~ /::|#|\./
true
rescue NotFoundError
matches = list_methods_matching name if name =~ /::|#|\./
matches = classes.keys.grep(/^#{name}/) if matches.empty?
raise if matches.empty?
page do |io|
io.puts "#{name} not found, maybe you meant:"
io.puts
io.puts matches.sort.join("\n")
end
false
end
##
# Displays each name in +name+
def display_names names
names.each do |name|
name = expand_name name
display_name name
end
end
##
# Outputs formatted RI data for page +name+.
def display_page name
store_name, page_name = name.split ':', 2
store = @stores.find { |s| s.source == store_name }
return display_page_list store if page_name.empty?
pages = store.cache[:pages]
unless pages.include? page_name then
found_names = pages.select do |n|
n =~ /#{Regexp.escape page_name}\.[^.]+$/
end
if found_names.length.zero? then
return display_page_list store, pages
elsif found_names.length > 1 then
return display_page_list store, found_names, page_name
end
page_name = found_names.first
end
page = store.load_page page_name
display page.comment
end
##
# Outputs a formatted RI page list for the pages in +store+.
def display_page_list store, pages = store.cache[:pages], search = nil
out = RDoc::Markup::Document.new
title = if search then
"#{search} pages"
else
'Pages'
end
out << RDoc::Markup::Heading.new(1, "#{title} in #{store.friendly_path}")
out << RDoc::Markup::BlankLine.new
list = RDoc::Markup::List.new(:BULLET)
pages.each do |page|
list << RDoc::Markup::Paragraph.new(page)
end
out << list
display out
end
##
# Expands abbreviated klass +klass+ into a fully-qualified class. "Zl::Da"
# will be expanded to Zlib::DataError.
def expand_class klass
klass.split('::').inject '' do |expanded, klass_part|
expanded << '::' unless expanded.empty?
short = expanded << klass_part
subset = classes.keys.select do |klass_name|
klass_name =~ /^#{expanded}[^:]*$/
end
abbrevs = Abbrev.abbrev subset
expanded = abbrevs[short]
raise NotFoundError, short unless expanded
expanded.dup
end
end
##
# Expands the class portion of +name+ into a fully-qualified class. See
# #expand_class.
def expand_name name
klass, selector, method = parse_name name
return [selector, method].join if klass.empty?
case selector
when ':' then
[find_store(klass), selector, method]
else
[expand_class(klass), selector, method]
end.join
end
##
# Filters the methods in +found+ trying to find a match for +name+.
def filter_methods found, name
regexp = name_regexp name
filtered = found.find_all do |store, methods|
methods.any? { |method| method.full_name =~ regexp }
end
return filtered unless filtered.empty?
found
end
##
# Yields items matching +name+ including the store they were found in, the
# class being searched for, the class they were found in (an ancestor) the
# types of methods to look up (from #method_type), and the method name being
# searched for
def find_methods name
klass, selector, method = parse_name name
types = method_type selector
klasses = nil
ambiguous = klass.empty?
if ambiguous then
klasses = classes.keys
else
klasses = ancestors_of klass
klasses.unshift klass
end
methods = []
klasses.each do |ancestor|
ancestors = classes[ancestor]
next unless ancestors
klass = ancestor if ambiguous
ancestors.each do |store|
methods << [store, klass, ancestor, types, method]
end
end
methods = methods.sort_by do |_, k, a, _, m|
[k, a, m].compact
end
methods.each do |item|
yield(*item) # :yields: store, klass, ancestor, types, method
end
self
end
##
# Finds the given +pager+ for jruby. Returns an IO if +pager+ was found.
#
# Returns false if +pager+ does not exist.
#
# Returns nil if the jruby JVM doesn't support ProcessBuilder redirection
# (1.6 and older).
def find_pager_jruby pager
require 'java'
require 'shellwords'
return nil unless java.lang.ProcessBuilder.constants.include? :Redirect
pager = Shellwords.split pager
pb = java.lang.ProcessBuilder.new(*pager)
pb = pb.redirect_output java.lang.ProcessBuilder::Redirect::INHERIT
@jruby_pager_process = pb.start
input = @jruby_pager_process.output_stream
io = input.to_io
io.sync = true
io
rescue java.io.IOException
false
end
##
# Finds a store that matches +name+ which can be the name of a gem, "ruby",
# "home" or "site".
#
# See also RDoc::Store#source
def find_store name
@stores.each do |store|
source = store.source
return source if source == name
return source if
store.type == :gem and source =~ /^#{Regexp.escape name}-\d/
end
raise RDoc::RI::Driver::NotFoundError, name
end
##
# Creates a new RDoc::Markup::Formatter. If a formatter is given with -f,
# use it. If we're outputting to a pager, use bs, otherwise ansi.
def formatter(io)
if @formatter_klass then
@formatter_klass.new
elsif paging? or !io.tty? then
RDoc::Markup::ToBs.new
else
RDoc::Markup::ToAnsi.new
end
end
##
# Runs ri interactively using Readline if it is available.
def interactive
puts "\nEnter the method name you want to look up."
if defined? Readline then
Readline.completion_proc = method :complete
puts "You can use tab to autocomplete."
end
puts "Enter a blank line to exit.\n\n"
loop do
name = if defined? Readline then
Readline.readline ">> "
else
print ">> "
$stdin.gets
end
return if name.nil? or name.empty?
name = expand_name name.strip
begin
display_name name
rescue NotFoundError => e
puts e.message
end
end
rescue Interrupt
exit
end
##
# Is +file+ in ENV['PATH']?
def in_path? file
return true if file =~ %r%\A/% and File.exist? file
ENV['PATH'].split(File::PATH_SEPARATOR).any? do |path|
File.exist? File.join(path, file)
end
end
##
# Lists classes known to ri starting with +names+. If +names+ is empty all
# known classes are shown.
def list_known_classes names = []
classes = []
stores.each do |store|
classes << store.module_names
end
classes = classes.flatten.uniq.sort
unless names.empty? then
filter = Regexp.union names.map { |name| /^#{name}/ }
classes = classes.grep filter
end
page do |io|
if paging? or io.tty? then
if names.empty? then
io.puts "Classes and Modules known to ri:"
else
io.puts "Classes and Modules starting with #{names.join ', '}:"
end
io.puts
end
io.puts classes.join("\n")
end
end
##
# Returns an Array of methods matching +name+
def list_methods_matching name
found = []
find_methods name do |store, klass, ancestor, types, method|
if types == :instance or types == :both then
methods = store.instance_methods[ancestor]
if methods then
matches = methods.grep(/^#{Regexp.escape method.to_s}/)
matches = matches.map do |match|
"#{klass}##{match}"
end
found.concat matches
end
end
if types == :class or types == :both then
methods = store.class_methods[ancestor]
next unless methods
matches = methods.grep(/^#{Regexp.escape method.to_s}/)
matches = matches.map do |match|
"#{klass}::#{match}"
end
found.concat matches
end
end
found.uniq
end
##
# Loads RI data for method +name+ on +klass+ from +store+. +type+ and
# +cache+ indicate if it is a class or instance method.
def load_method store, cache, klass, type, name
methods = store.send(cache)[klass]
return unless methods
method = methods.find do |method_name|
method_name == name
end
return unless method
store.load_method klass, "#{type}#{method}"
end
##
# Returns an Array of RI data for methods matching +name+
def load_methods_matching name
found = []
find_methods name do |store, klass, ancestor, types, method|
methods = []
methods << load_method(store, :class_methods, ancestor, '::', method) if
[:class, :both].include? types
methods << load_method(store, :instance_methods, ancestor, '#', method) if
[:instance, :both].include? types
found << [store, methods.compact]
end
found.reject do |path, methods| methods.empty? end
end
##
# Returns a filtered list of methods matching +name+
def lookup_method name
found = load_methods_matching name
raise NotFoundError, name if found.empty?
filter_methods found, name
end
##
# Builds a RDoc::Markup::Document from +found+, +klasses+ and +includes+
def method_document name, filtered
out = RDoc::Markup::Document.new
out << RDoc::Markup::Heading.new(1, name)
out << RDoc::Markup::BlankLine.new
filtered.each do |store, methods|
methods.each do |method|
out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})")
unless name =~ /^#{Regexp.escape method.parent_name}/ then
out << RDoc::Markup::Heading.new(3, "Implementation from #{method.parent_name}")
end
out << RDoc::Markup::Rule.new(1)
if method.arglists then
arglists = method.arglists.chomp.split "\n"
arglists = arglists.map { |line| line + "\n" }
out << RDoc::Markup::Verbatim.new(*arglists)
out << RDoc::Markup::Rule.new(1)
end
if method.respond_to?(:superclass_method) and method.superclass_method
out << RDoc::Markup::BlankLine.new
out << RDoc::Markup::Heading.new(4, "(Uses superclass method #{method.superclass_method})")
out << RDoc::Markup::Rule.new(1)
end
out << RDoc::Markup::BlankLine.new
out << method.comment
out << RDoc::Markup::BlankLine.new
end
end
out
end
##
# Returns the type of method (:both, :instance, :class) for +selector+
def method_type selector
case selector
when '.', nil then :both
when '#' then :instance
else :class
end
end
##
# Returns a regular expression for +name+ that will match an
# RDoc::AnyMethod's name.
def name_regexp name
klass, type, name = parse_name name
case type
when '#', '::' then
/^#{klass}#{type}#{Regexp.escape name}$/
else
/^#{klass}(#|::)#{Regexp.escape name}$/
end
end
##
# Paginates output through a pager program.
def page
if pager = setup_pager then
begin
yield pager
ensure
pager.close
@jruby_pager_process.wait_for if @jruby_pager_process
end
else
yield $stdout
end
rescue Errno::EPIPE
ensure
@paging = false
end
##
# Are we using a pager?
def paging?
@paging
end
##
# Extracts the class, selector and method name parts from +name+ like
# Foo::Bar#baz.
#
# NOTE: Given Foo::Bar, Bar is considered a class even though it may be a
# method
def parse_name name
parts = name.split(/(::?|#|\.)/)
if parts.length == 1 then
if parts.first =~ /^[a-z]|^([%&*+\/<>^`|~-]|\+@|-@|<<|<=>?|===?|=>|=~|>>|\[\]=?|~@)$/ then
type = '.'
meth = parts.pop
else
type = nil
meth = nil
end
elsif parts.length == 2 or parts.last =~ /::|#|\./ then
type = parts.pop
meth = nil
elsif parts[1] == ':' then
klass = parts.shift
type = parts.shift
meth = parts.join
elsif parts[-2] != '::' or parts.last !~ /^[A-Z]/ then
meth = parts.pop
type = parts.pop
end
klass ||= parts.join
[klass, type, meth]
end
##
# Looks up and displays ri data according to the options given.
def run
if @list_doc_dirs then
puts @doc_dirs
elsif @list then
list_known_classes @names
elsif @server then
start_server
elsif @interactive or @names.empty? then
interactive
else
display_names @names
end
rescue NotFoundError => e
abort e.message
end
##
# Sets up a pager program to pass output through. Tries the RI_PAGER and
# PAGER environment variables followed by pager, less then more.
def setup_pager
return if @use_stdout
jruby = Object.const_defined?(:RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
pagers = [ENV['RI_PAGER'], ENV['PAGER'], 'pager', 'less', 'more']
pagers.compact.uniq.each do |pager|
next unless pager
pager_cmd = pager.split.first
next unless in_path? pager_cmd
if jruby then
case io = find_pager_jruby(pager)
when nil then break
when false then next
else io
end
else
io = IO.popen(pager, 'w') rescue next
end
next if $? and $?.pid == io.pid and $?.exited? # pager didn't work
@paging = true
return io
end
@use_stdout = true
nil
end
##
# Starts a WEBrick server for ri.
def start_server
require 'webrick'
server = WEBrick::HTTPServer.new :Port => @server
server.mount '/', RDoc::Servlet
trap 'INT' do server.shutdown end
trap 'TERM' do server.shutdown end
server.start
end
end
text.rb 0000644 00000017140 14763537411 0006073 0 ustar 00 # coding: utf-8
##
# For RDoc::Text#to_html
require 'strscan'
##
# For RDoc::Text#snippet
begin
gem 'json'
rescue Gem::LoadError
end
require 'json'
##
# Methods for manipulating comment text
module RDoc::Text
##
# Maps markup formats to classes that can parse them. If the format is
# unknown, "rdoc" format is used.
MARKUP_FORMAT = {
'markdown' => RDoc::Markdown,
'rdoc' => RDoc::Markup,
'rd' => RDoc::RD,
'tomdoc' => RDoc::TomDoc,
}
MARKUP_FORMAT.default = RDoc::Markup
##
# Maps an encoding to a Hash of characters properly transcoded for that
# encoding.
#
# See also encode_fallback.
TO_HTML_CHARACTERS = Hash.new do |h, encoding|
h[encoding] = {
:close_dquote => encode_fallback('”', encoding, '"'),
:close_squote => encode_fallback('’', encoding, '\''),
:copyright => encode_fallback('©', encoding, '(c)'),
:ellipsis => encode_fallback('…', encoding, '...'),
:em_dash => encode_fallback('—', encoding, '---'),
:en_dash => encode_fallback('–', encoding, '--'),
:open_dquote => encode_fallback('“', encoding, '"'),
:open_squote => encode_fallback('‘', encoding, '\''),
:trademark => encode_fallback('®', encoding, '(r)'),
}
end if Object.const_defined? :Encoding
##
# Transcodes +character+ to +encoding+ with a +fallback+ character.
def self.encode_fallback character, encoding, fallback
character.encode(encoding, :fallback => { character => fallback },
:undef => :replace, :replace => fallback)
end
##
# Expands tab characters in +text+ to eight spaces
def expand_tabs text
expanded = []
text.each_line do |line|
nil while line.gsub!(/(?:\G|\r)((?:.{8})*?)([^\t\r\n]{0,7})\t/) do
r = "#{$1}#{$2}#{' ' * (8 - $2.size)}"
r.force_encoding text.encoding if Object.const_defined? :Encoding
r
end
expanded << line
end
expanded.join
end
##
# Flush +text+ left based on the shortest line
def flush_left text
indent = 9999
text.each_line do |line|
line_indent = line =~ /\S/ || 9999
indent = line_indent if indent > line_indent
end
empty = ''
empty.force_encoding text.encoding if Object.const_defined? :Encoding
text.gsub(/^ {0,#{indent}}/, empty)
end
##
# Convert a string in markup format into HTML.
#
# Requires the including class to implement #formatter
def markup text
parse(text).accept formatter
end
##
# Strips hashes, expands tabs then flushes +text+ to the left
def normalize_comment text
return text if text.empty?
text = strip_stars text
text = strip_hashes text
text = expand_tabs text
text = flush_left text
text = strip_newlines text
text
end
##
# Normalizes +text+ then builds a RDoc::Markup::Document from it
def parse text, format = 'rdoc'
return text if RDoc::Markup::Document === text
return text.parse if RDoc::Comment === text
text = normalize_comment text # TODO remove, should not be necessary
return RDoc::Markup::Document.new if text =~ /\A\n*\z/
MARKUP_FORMAT[format].parse text
end
##
# The first +limit+ characters of +text+ as HTML
def snippet text, limit = 100
document = parse text
RDoc::Markup::ToHtmlSnippet.new(limit).convert document
end
##
# Strips leading # characters from +text+
def strip_hashes text
return text if text =~ /^(?>\s*)[^\#]/
empty = ''
empty.force_encoding text.encoding if Object.const_defined? :Encoding
text.gsub(/^\s*(#+)/) { $1.tr '#', ' ' }.gsub(/^\s+$/, empty)
end
##
# Strips leading and trailing \n characters from +text+
def strip_newlines text
text.gsub(/\A\n*(.*?)\n*\z/m) do $1 end # block preserves String encoding
end
##
# Strips /* */ style comments
def strip_stars text
return text unless text =~ %r%/\*.*\*/%m
encoding = text.encoding if Object.const_defined? :Encoding
text = text.gsub %r%Document-method:\s+[\w:.#=!?]+%, ''
space = ' '
space.force_encoding encoding if encoding
text.sub! %r%/\*+% do space * $&.length end
text.sub! %r%\*+/% do space * $&.length end
text.gsub! %r%^[ \t]*\*%m do space * $&.length end
empty = ''
empty.force_encoding encoding if encoding
text.gsub(/^\s+$/, empty)
end
##
# Converts ampersand, dashes, ellipsis, quotes, copyright and registered
# trademark symbols in +text+ to properly encoded characters.
def to_html text
if Object.const_defined? :Encoding then
html = ''.encode text.encoding
encoded = RDoc::Text::TO_HTML_CHARACTERS[text.encoding]
else
html = ''
encoded = {
:close_dquote => '”',
:close_squote => '’',
:copyright => '©',
:ellipsis => '…',
:em_dash => '—',
:en_dash => '–',
:open_dquote => '“',
:open_squote => '‘',
:trademark => '®',
}
end
s = StringScanner.new text
insquotes = false
indquotes = false
after_word = nil
until s.eos? do
case
when s.scan(/<(tt|code)>.*?<\/\1>/) then # skip contents of tt
html << s.matched.gsub('\\\\', '\\')
when s.scan(/<(tt|code)>.*?/) then
warn "mismatched <#{s[1]}> tag" # TODO signal file/line
html << s.matched
when s.scan(/<[^>]+\/?s*>/) then # skip HTML tags
html << s.matched
when s.scan(/\\(\S)/) then # unhandled suppressed crossref
html << s[1]
after_word = nil
when s.scan(/\.\.\.(\.?)/) then
html << s[1] << encoded[:ellipsis]
after_word = nil
when s.scan(/\(c\)/) then
html << encoded[:copyright]
after_word = nil
when s.scan(/\(r\)/) then
html << encoded[:trademark]
after_word = nil
when s.scan(/---/) then
html << encoded[:em_dash]
after_word = nil
when s.scan(/--/) then
html << encoded[:en_dash]
after_word = nil
when s.scan(/"|"/) then
html << encoded[indquotes ? :close_dquote : :open_dquote]
indquotes = !indquotes
after_word = nil
when s.scan(/``/) then # backtick double quote
html << encoded[:open_dquote]
after_word = nil
when s.scan(/''/) then # tick double quote
html << encoded[:close_dquote]
after_word = nil
when s.scan(/'/) then # single quote
if insquotes
html << encoded[:close_squote]
insquotes = false
elsif after_word
# Mary's dog, my parents' house: do not start paired quotes
html << encoded[:close_squote]
else
html << encoded[:open_squote]
insquotes = true
end
after_word = nil
else # advance to the next potentially significant character
match = s.scan(/.+?(?=[<\\.("'`&-])/) #"
if match then
html << match
after_word = match =~ /\w$/
else
html << s.rest
break
end
end
end
html
end
##
# Wraps +txt+ to +line_len+
def wrap(txt, line_len = 76)
res = []
sp = 0
ep = txt.length
while sp < ep
# scan back for a space
p = sp + line_len - 1
if p >= ep
p = ep
else
while p > sp and txt[p] != ?\s
p -= 1
end
if p <= sp
p = sp + line_len
while p < ep and txt[p] != ?\s
p += 1
end
end
end
res << txt[sp...p] << "\n"
sp = p
sp += 1 while sp < ep and txt[sp] == ?\s
end
res.join.strip
end
end
test_case.rb 0000644 00000006733 14763537411 0007067 0 ustar 00 require 'rubygems'
require 'minitest/autorun'
require 'minitest/benchmark' if ENV['BENCHMARK']
require 'fileutils'
require 'pp'
require 'tempfile'
require 'tmpdir'
require 'stringio'
require 'rdoc'
##
# RDoc::TestCase is an abstract TestCase to provide common setup and teardown
# across all RDoc tests. The test case uses minitest, so all the assertions
# of minitest may be used.
#
# The testcase provides the following:
#
# * A reset code-object tree
# * A reset markup preprocessor (RDoc::Markup::PreProcess)
# * The @RM
alias of RDoc::Markup (for less typing)
# * @pwd
containing the current working directory
# * FileUtils, pp, Tempfile, Dir.tmpdir and StringIO
class RDoc::TestCase < MiniTest::Unit::TestCase
##
# Abstract test-case setup
def setup
super
@top_level = nil
@have_encoding = Object.const_defined? :Encoding
@RM = RDoc::Markup
RDoc::Markup::PreProcess.reset
@pwd = Dir.pwd
@store = RDoc::Store.new
@rdoc = RDoc::RDoc.new
@rdoc.store = @store
g = Object.new
def g.class_dir() end
def g.file_dir() end
@rdoc.generator = g
end
##
# Shortcut for RDoc::Markup::BlankLine.new
def blank_line
@RM::BlankLine.new
end
##
# Shortcut for RDoc::Markup::BlockQuote.new with +contents+
def block *contents
@RM::BlockQuote.new(*contents)
end
##
# Creates an RDoc::Comment with +text+ which was defined on +top_level+.
# By default the comment has the 'rdoc' format.
def comment text, top_level = @top_level
RDoc::Comment.new text, top_level
end
##
# Shortcut for RDoc::Markup::Document.new with +contents+
def doc *contents
@RM::Document.new(*contents)
end
##
# Shortcut for RDoc::Markup::HardBreak.new
def hard_break
@RM::HardBreak.new
end
##
# Shortcut for RDoc::Markup::Heading.new with +level+ and +text+
def head level, text
@RM::Heading.new level, text
end
##
# Shortcut for RDoc::Markup::ListItem.new with +label+ and +parts+
def item label = nil, *parts
@RM::ListItem.new label, *parts
end
##
# Shortcut for RDoc::Markup::List.new with +type+ and +items+
def list type = nil, *items
@RM::List.new type, *items
end
##
# Shortcut for RDoc::Markup::Paragraph.new with +contents+
def para *a
@RM::Paragraph.new(*a)
end
##
# Shortcut for RDoc::Markup::Rule.new with +weight+
def rule weight
@RM::Rule.new weight
end
##
# Shortcut for RDoc::Markup::Raw.new with +contents+
def raw *contents
@RM::Raw.new(*contents)
end
##
# Creates a temporary directory changes the current directory to it for the
# duration of the block.
#
# Depends upon Dir.mktmpdir
def temp_dir
skip "No Dir::mktmpdir, upgrade your ruby" unless Dir.respond_to? :mktmpdir
Dir.mktmpdir do |temp_dir|
Dir.chdir temp_dir do
yield temp_dir
end
end
end
##
# Shortcut for RDoc::Markup::Verbatim.new with +parts+
def verb *parts
@RM::Verbatim.new(*parts)
end
##
# run capture_io with setting $VERBOSE = true
def verbose_capture_io
capture_io do
begin
orig_verbose = $VERBOSE
$VERBOSE = true
yield
ensure
$VERBOSE = orig_verbose
end
end
end
end
# This hack allows autoload to work when Dir.pwd is changed for Ruby 1.8 since
# -I paths are not expanded.
$LOAD_PATH.each do |load_path|
break if load_path[0] == ?/
load_path.replace File.expand_path load_path
end if RUBY_VERSION < '1.9'
require.rb 0000644 00000001647 14763537411 0006570 0 ustar 00 ##
# A file loaded by \#require
class RDoc::Require < RDoc::CodeObject
##
# Name of the required file
attr_accessor :name
##
# Creates a new Require that loads +name+ with +comment+
def initialize(name, comment)
super()
@name = name.gsub(/'|"/, "") #'
@top_level = nil
self.comment = comment
end
def inspect # :nodoc:
"#<%s:0x%x require '%s' in %s>" % [
self.class,
object_id,
@name,
parent_file_name,
]
end
def to_s # :nodoc:
"require #{name} in: #{parent}"
end
##
# The RDoc::TopLevel corresponding to this require, or +nil+ if not found.
def top_level
@top_level ||= begin
tl = RDoc::TopLevel.all_files_hash[name + '.rb']
if tl.nil? and RDoc::TopLevel.all_files.first.full_name =~ %r(^lib/) then
# second chance
tl = RDoc::TopLevel.all_files_hash['lib/' + name + '.rb']
end
tl
end
end
end
top_level.rb 0000644 00000012636 14763537411 0007105 0 ustar 00 ##
# A TopLevel context is a representation of the contents of a single file
class RDoc::TopLevel < RDoc::Context
MARSHAL_VERSION = 0 # :nodoc:
##
# This TopLevel's File::Stat struct
attr_accessor :file_stat
##
# Relative name of this file
attr_accessor :relative_name
##
# Absolute name of this file
attr_accessor :absolute_name
##
# All the classes or modules that were declared in
# this file. These are assigned to either +#classes_hash+
# or +#modules_hash+ once we know what they really are.
attr_reader :classes_or_modules
attr_accessor :diagram # :nodoc:
##
# The parser that processed this file
attr_accessor :parser
##
# Creates a new TopLevel for the file at +absolute_name+. If documentation
# is being generated outside the source dir +relative_name+ is relative to
# the source directory.
def initialize absolute_name, relative_name = absolute_name
super()
@name = nil
@absolute_name = absolute_name
@relative_name = relative_name
@file_stat = File.stat(absolute_name) rescue nil # HACK for testing
@diagram = nil
@parser = nil
@classes_or_modules = []
end
##
# An RDoc::TopLevel is equal to another with the same relative_name
def == other
self.class === other and @relative_name == other.relative_name
end
alias eql? ==
##
# Adds +an_alias+ to +Object+ instead of +self+.
def add_alias(an_alias)
object_class.record_location self
return an_alias unless @document_self
object_class.add_alias an_alias
end
##
# Adds +constant+ to +Object+ instead of +self+.
def add_constant constant
object_class.record_location self
return constant unless @document_self
object_class.add_constant constant
end
##
# Adds +include+ to +Object+ instead of +self+.
def add_include(include)
object_class.record_location self
return include unless @document_self
object_class.add_include include
end
##
# Adds +method+ to +Object+ instead of +self+.
def add_method(method)
object_class.record_location self
return method unless @document_self
object_class.add_method method
end
##
# Adds class or module +mod+. Used in the building phase
# by the ruby parser.
def add_to_classes_or_modules mod
@classes_or_modules << mod
end
##
# Base name of this file
def base_name
File.basename @relative_name
end
alias name base_name
##
# Only a TopLevel that contains text file) will be displayed. See also
# RDoc::CodeObject#display?
def display?
text? and super
end
##
# See RDoc::TopLevel::find_class_or_module
#--
# TODO Why do we search through all classes/modules found, not just the
# ones of this instance?
def find_class_or_module name
@store.find_class_or_module name
end
##
# Finds a class or module named +symbol+
def find_local_symbol(symbol)
find_class_or_module(symbol) || super
end
##
# Finds a module or class with +name+
def find_module_named(name)
find_class_or_module(name)
end
##
# Returns the relative name of this file
def full_name
@relative_name
end
##
# An RDoc::TopLevel has the same hash as another with the same
# relative_name
def hash
@relative_name.hash
end
##
# URL for this with a +prefix+
def http_url(prefix)
path = [prefix, @relative_name.tr('.', '_')]
File.join(*path.compact) + '.html'
end
def inspect # :nodoc:
"#<%s:0x%x %p modules: %p classes: %p>" % [
self.class, object_id,
base_name,
@modules.map { |n,m| m },
@classes.map { |n,c| c }
]
end
##
# Time this file was last modified, if known
def last_modified
@file_stat ? file_stat.mtime : nil
end
##
# Dumps this TopLevel for use by ri. See also #marshal_load
def marshal_dump
[
MARSHAL_VERSION,
@relative_name,
@parser,
parse(@comment),
]
end
##
# Loads this TopLevel from +array+.
def marshal_load array # :nodoc:
initialize array[1]
@parser = array[2]
@comment = array[3]
@file_stat = nil
end
##
# Returns the NormalClass "Object", creating it if not found.
#
# Records +self+ as a location in "Object".
def object_class
@object_class ||= begin
oc = @store.find_class_named('Object') || add_class(RDoc::NormalClass, 'Object')
oc.record_location self
oc
end
end
##
# Base name of this file without the extension
def page_name
basename = File.basename @relative_name
basename =~ /\.(rb|rdoc|txt|md)$/i
$` || basename
end
##
# Path to this file for use with HTML generator output.
def path
http_url @store.rdoc.generator.file_dir
end
def pretty_print q # :nodoc:
q.group 2, "[#{self.class}: ", "]" do
q.text "base name: #{base_name.inspect}"
q.breakable
items = @modules.map { |n,m| m }
items.concat @modules.map { |n,c| c }
q.seplist items do |mod| q.pp mod end
end
end
##
# Search record used by RDoc::Generator::JsonIndex
def search_record
return unless @parser < RDoc::Parser::Text
[
page_name,
'',
page_name,
'',
path,
'',
snippet(@comment),
]
end
##
# Is this TopLevel from a text file instead of a source code file?
def text?
@parser and @parser.ancestors.include? RDoc::Parser::Text
end
def to_s # :nodoc:
"file #{full_name}"
end
end
known_classes.rb 0000644 00000005122 14763537411 0007755 0 ustar 00 module RDoc
##
# Ruby's built-in classes, modules and exceptions
KNOWN_CLASSES = {
"rb_cArray" => "Array",
"rb_cBasicObject" => "BasicObject",
"rb_cBignum" => "Bignum",
"rb_cClass" => "Class",
"rb_cData" => "Data",
"rb_cDir" => "Dir",
"rb_cEncoding" => "Encoding",
"rb_cFalseClass" => "FalseClass",
"rb_cFile" => "File",
"rb_cFixnum" => "Fixnum",
"rb_cFloat" => "Float",
"rb_cHash" => "Hash",
"rb_cIO" => "IO",
"rb_cInteger" => "Integer",
"rb_cModule" => "Module",
"rb_cNilClass" => "NilClass",
"rb_cNumeric" => "Numeric",
"rb_cObject" => "Object",
"rb_cProc" => "Proc",
"rb_cRange" => "Range",
"rb_cRegexp" => "Regexp",
"rb_cRubyVM" => "RubyVM",
"rb_cSocket" => "Socket",
"rb_cString" => "String",
"rb_cStruct" => "Struct",
"rb_cSymbol" => "Symbol",
"rb_cThread" => "Thread",
"rb_cTime" => "Time",
"rb_cTrueClass" => "TrueClass",
"rb_eArgError" => "ArgError",
"rb_eEOFError" => "EOFError",
"rb_eException" => "Exception",
"rb_eFatal" => "fatal",
"rb_eFloatDomainError" => "FloatDomainError",
"rb_eIOError" => "IOError",
"rb_eIndexError" => "IndexError",
"rb_eInterrupt" => "Interrupt",
"rb_eLoadError" => "LoadError",
"rb_eNameError" => "NameError",
"rb_eNoMemError" => "NoMemError",
"rb_eNotImpError" => "NotImpError",
"rb_eRangeError" => "RangeError",
"rb_eRuntimeError" => "RuntimeError",
"rb_eScriptError" => "ScriptError",
"rb_eSecurityError" => "SecurityError",
"rb_eSignal" => "SignalException",
"rb_eStandardError" => "StandardError",
"rb_eSyntaxError" => "SyntaxError",
"rb_eSystemCallError" => "SystemCallError",
"rb_eSystemExit" => "SystemExit",
"rb_eTypeError" => "TypeError",
"rb_eZeroDivError" => "ZeroDivError",
"rb_mComparable" => "Comparable",
"rb_mDL" => "DL",
"rb_mEnumerable" => "Enumerable",
"rb_mErrno" => "Errno",
"rb_mFileTest" => "FileTest",
"rb_mGC" => "GC",
"rb_mKernel" => "Kernel",
"rb_mMath" => "Math",
"rb_mProcess" => "Process"
}
end
code_objects.rb 0000644 00000000171 14763537411 0007526 0 ustar 00 # This file was used to load all the RDoc::CodeObject subclasses at once. Now
# autoload handles this.
require 'rdoc'
attr.rb 0000644 00000007353 14763537411 0006066 0 ustar 00 ##
# An attribute created by \#attr, \#attr_reader, \#attr_writer or
# \#attr_accessor
class RDoc::Attr < RDoc::MethodAttr
##
# 3::
# RDoc 4
# Added parent name and class
# Added section title
MARSHAL_VERSION = 3 # :nodoc:
##
# Is the attribute readable ('R'), writable ('W') or both ('RW')?
attr_accessor :rw
##
# Creates a new Attr with body +text+, +name+, read/write status +rw+ and
# +comment+. +singleton+ marks this as a class attribute.
def initialize(text, name, rw, comment, singleton = false)
super text, name
@rw = rw
@singleton = singleton
self.comment = comment
end
##
# Attributes are equal when their names, singleton and rw are identical
def == other
self.class == other.class and
self.name == other.name and
self.rw == other.rw and
self.singleton == other.singleton
end
##
# Add +an_alias+ as an attribute in +context+.
def add_alias(an_alias, context)
new_attr = self.class.new(self.text, an_alias.new_name, self.rw,
self.comment, self.singleton)
new_attr.record_location an_alias.file
new_attr.visibility = self.visibility
new_attr.is_alias_for = self
@aliases << new_attr
context.add_attribute new_attr
new_attr
end
##
# The #aref prefix for attributes
def aref_prefix
'attribute'
end
##
# Attributes never call super. See RDoc::AnyMethod#calls_super
#
# An RDoc::Attr can show up in the method list in some situations (see
# Gem::ConfigFile)
def calls_super # :nodoc:
false
end
##
# Returns attr_reader, attr_writer or attr_accessor as appropriate.
def definition
case @rw
when 'RW' then 'attr_accessor'
when 'R' then 'attr_reader'
when 'W' then 'attr_writer'
end
end
def inspect # :nodoc:
alias_for = @is_alias_for ? " (alias for #{@is_alias_for.name})" : nil
visibility = self.visibility
visibility = "forced #{visibility}" if force_documentation
"#<%s:0x%x %s %s (%s)%s>" % [
self.class, object_id,
full_name,
rw,
visibility,
alias_for,
]
end
##
# Dumps this Attr for use by ri. See also #marshal_load
def marshal_dump
[ MARSHAL_VERSION,
@name,
full_name,
@rw,
@visibility,
parse(@comment),
singleton,
@file.relative_name,
@parent.full_name,
@parent.class,
@section.title
]
end
##
# Loads this Attr from +array+. For a loaded Attr the following
# methods will return cached values:
#
# * #full_name
# * #parent_name
def marshal_load array
initialize_visibility
@aliases = []
@parent = nil
@parent_name = nil
@parent_class = nil
@section = nil
@file = nil
version = array[0]
@name = array[1]
@full_name = array[2]
@rw = array[3]
@visibility = array[4]
@comment = array[5]
@singleton = array[6] || false # MARSHAL_VERSION == 0
# 7 handled below
@parent_name = array[8]
@parent_class = array[9]
@section_title = array[10]
@file = RDoc::TopLevel.new array[7] if version > 1
@parent_name ||= @full_name.split('#', 2).first
end
def pretty_print q # :nodoc:
q.group 2, "[#{self.class.name} #{full_name} #{rw} #{visibility}", "]" do
unless comment.empty? then
q.breakable
q.text "comment:"
q.breakable
q.pp @comment
end
end
end
def to_s # :nodoc:
"#{definition} #{name} in: #{parent}"
end
##
# Attributes do not have token streams.
#
# An RDoc::Attr can show up in the method list in some situations (see
# Gem::ConfigFile)
def token_stream # :nodoc:
end
end
context.rb 0000644 00000070410 14763537411 0006572 0 ustar 00 require 'cgi'
##
# A Context is something that can hold modules, classes, methods, attributes,
# aliases, requires, and includes. Classes, modules, and files are all
# Contexts.
class RDoc::Context < RDoc::CodeObject
include Comparable
##
# Types of methods
TYPES = %w[class instance]
##
# If a context has these titles it will be sorted in this order.
TOMDOC_TITLES = [nil, 'Public', 'Internal', 'Deprecated'] # :nodoc:
TOMDOC_TITLES_SORT = TOMDOC_TITLES.sort_by { |title| title.to_s } # :nodoc:
##
# Class/module aliases
attr_reader :aliases
##
# All attr* methods
attr_reader :attributes
##
# Block params to be used in the next MethodAttr parsed under this context
attr_accessor :block_params
##
# Constants defined
attr_reader :constants
##
# Sets the current documentation section of documentation
attr_writer :current_section
##
# Files this context is found in
attr_reader :in_files
##
# Modules this context includes
attr_reader :includes
##
# Modules this context is extended with
attr_reader :extends
##
# Methods defined in this context
attr_reader :method_list
##
# Name of this class excluding namespace. See also full_name
attr_reader :name
##
# Files this context requires
attr_reader :requires
##
# Use this section for the next method, attribute or constant added.
attr_accessor :temporary_section
##
# Hash old_name => [aliases], for aliases
# that haven't (yet) been resolved to a method/attribute.
# (Not to be confused with the aliases of the context.)
attr_accessor :unmatched_alias_lists
##
# Aliases that could not be resolved.
attr_reader :external_aliases
##
# Current visibility of this context
attr_accessor :visibility
##
# Hash of registered methods. Attributes are also registered here,
# twice if they are RW.
attr_reader :methods_hash
##
# Params to be used in the next MethodAttr parsed under this context
attr_accessor :params
##
# Hash of registered constants.
attr_reader :constants_hash
##
# Creates an unnamed empty context with public current visibility
def initialize
super
@in_files = []
@name ||= "unknown"
@parent = nil
@visibility = :public
@current_section = Section.new self, nil, nil
@sections = { nil => @current_section }
@temporary_section = nil
@classes = {}
@modules = {}
initialize_methods_etc
end
##
# Sets the defaults for methods and so-forth
def initialize_methods_etc
@method_list = []
@attributes = []
@aliases = []
@requires = []
@includes = []
@extends = []
@constants = []
@external_aliases = []
# This Hash maps a method name to a list of unmatched aliases (aliases of
# a method not yet encountered).
@unmatched_alias_lists = {}
@methods_hash = {}
@constants_hash = {}
@params = nil
@store ||= nil
end
##
# Contexts are sorted by full_name
def <=>(other)
full_name <=> other.full_name
end
##
# Adds +an_alias+ that is automatically resolved
def add_alias an_alias
return an_alias unless @document_self
method_attr = find_method(an_alias.old_name, an_alias.singleton) ||
find_attribute(an_alias.old_name, an_alias.singleton)
if method_attr then
method_attr.add_alias an_alias, self
else
add_to @external_aliases, an_alias
unmatched_alias_list =
@unmatched_alias_lists[an_alias.pretty_old_name] ||= []
unmatched_alias_list.push an_alias
end
an_alias
end
##
# Adds +attribute+ if not already there. If it is (as method(s) or attribute),
# updates the comment if it was empty.
#
# The attribute is registered only if it defines a new method.
# For instance, attr_reader :foo will not be registered
# if method +foo+ exists, but attr_accessor :foo will be registered
# if method +foo+ exists, but foo= does not.
def add_attribute attribute
return attribute unless @document_self
# mainly to check for redefinition of an attribute as a method
# TODO find a policy for 'attr_reader :foo' + 'def foo=()'
register = false
key = nil
if attribute.rw.index 'R' then
key = attribute.pretty_name
known = @methods_hash[key]
if known then
known.comment = attribute.comment if known.comment.empty?
elsif registered = @methods_hash[attribute.pretty_name << '='] and
RDoc::Attr === registered then
registered.rw = 'RW'
else
@methods_hash[key] = attribute
register = true
end
end
if attribute.rw.index 'W' then
key = attribute.pretty_name << '='
known = @methods_hash[key]
if known then
known.comment = attribute.comment if known.comment.empty?
elsif registered = @methods_hash[attribute.pretty_name] and
RDoc::Attr === registered then
registered.rw = 'RW'
else
@methods_hash[key] = attribute
register = true
end
end
if register then
attribute.visibility = @visibility
add_to @attributes, attribute
resolve_aliases attribute
end
attribute
end
##
# Adds a class named +given_name+ with +superclass+.
#
# Both +given_name+ and +superclass+ may contain '::', and are
# interpreted relative to the +self+ context. This allows handling correctly
# examples like these:
# class RDoc::Gauntlet < Gauntlet
# module Mod
# class Object # implies < ::Object
# class SubObject < Object # this is _not_ ::Object
#
# Given class Container::Item RDoc assumes +Container+ is a module
# unless it later sees class Container. +add_class+ automatically
# upgrades +given_name+ to a class in this case.
def add_class class_type, given_name, superclass = '::Object'
# superclass +nil+ is passed by the C parser in the following cases:
# - registering Object in 1.8 (correct)
# - registering BasicObject in 1.9 (correct)
# - registering RubyVM in 1.9 in iseq.c (incorrect: < Object in vm.c)
#
# If we later find a superclass for a registered class with a nil
# superclass, we must honor it.
# find the name & enclosing context
if given_name =~ /^:+(\w+)$/ then
full_name = $1
enclosing = top_level
name = full_name.split(/:+/).last
else
full_name = child_name given_name
if full_name =~ /^(.+)::(\w+)$/ then
name = $2
ename = $1
enclosing = @store.classes_hash[ename] || @store.modules_hash[ename]
# HACK: crashes in actionpack/lib/action_view/helpers/form_helper.rb (metaprogramming)
unless enclosing then
# try the given name at top level (will work for the above example)
enclosing = @store.classes_hash[given_name] ||
@store.modules_hash[given_name]
return enclosing if enclosing
# not found: create the parent(s)
names = ename.split('::')
enclosing = self
names.each do |n|
enclosing = enclosing.classes_hash[n] ||
enclosing.modules_hash[n] ||
enclosing.add_module(RDoc::NormalModule, n)
end
end
else
name = full_name
enclosing = self
end
end
# fix up superclass
superclass = nil if full_name == 'BasicObject'
superclass = nil if full_name == 'Object' and defined?(::BasicObject)
superclass = '::BasicObject' if
defined?(::BasicObject) and full_name == 'Object'
# find the superclass full name
if superclass then
if superclass =~ /^:+/ then
superclass = $' #'
else
if superclass =~ /^(\w+):+(.+)$/ then
suffix = $2
mod = find_module_named($1)
superclass = mod.full_name + '::' + suffix if mod
else
mod = find_module_named(superclass)
superclass = mod.full_name if mod
end
end
# did we believe it was a module?
mod = @store.modules_hash.delete superclass
upgrade_to_class mod, RDoc::NormalClass, mod.parent if mod
# e.g., Object < Object
superclass = nil if superclass == full_name
end
klass = @store.classes_hash[full_name]
if klass then
# if TopLevel, it may not be registered in the classes:
enclosing.classes_hash[name] = klass
# update the superclass if needed
if superclass then
existing = klass.superclass
existing = existing.full_name unless existing.is_a?(String) if existing
if existing.nil? ||
(existing == 'Object' && superclass != 'Object') then
klass.superclass = superclass
end
end
else
# this is a new class
mod = @store.modules_hash.delete full_name
if mod then
klass = upgrade_to_class mod, RDoc::NormalClass, enclosing
klass.superclass = superclass unless superclass.nil?
else
klass = class_type.new name, superclass
enclosing.add_class_or_module(klass, enclosing.classes_hash,
@store.classes_hash)
end
end
klass.parent = self
klass
end
##
# Adds the class or module +mod+ to the modules or
# classes Hash +self_hash+, and to +all_hash+ (either
# TopLevel::modules_hash or TopLevel::classes_hash),
# unless #done_documenting is +true+. Sets the #parent of +mod+
# to +self+, and its #section to #current_section. Returns +mod+.
def add_class_or_module mod, self_hash, all_hash
mod.section = current_section # TODO declaring context? something is
# wrong here...
mod.parent = self
mod.store = @store
unless @done_documenting then
self_hash[mod.name] = mod
# this must be done AFTER adding mod to its parent, so that the full
# name is correct:
all_hash[mod.full_name] = mod
end
mod
end
##
# Adds +constant+ if not already there. If it is, updates the comment,
# value and/or is_alias_for of the known constant if they were empty/nil.
def add_constant constant
return constant unless @document_self
# HACK: avoid duplicate 'PI' & 'E' in math.c (1.8.7 source code)
# (this is a #ifdef: should be handled by the C parser)
known = @constants_hash[constant.name]
if known then
known.comment = constant.comment if known.comment.empty?
known.value = constant.value if
known.value.nil? or known.value.strip.empty?
known.is_alias_for ||= constant.is_alias_for
else
@constants_hash[constant.name] = constant
add_to @constants, constant
end
constant
end
##
# Adds included module +include+ which should be an RDoc::Include
def add_include include
add_to @includes, include
include
end
##
# Adds extension module +ext+ which should be an RDoc::Extend
def add_extend ext
add_to @extends, ext
ext
end
##
# Adds +method+ if not already there. If it is (as method or attribute),
# updates the comment if it was empty.
def add_method method
return method unless @document_self
# HACK: avoid duplicate 'new' in io.c & struct.c (1.8.7 source code)
key = method.pretty_name
known = @methods_hash[key]
if known then
if @store then # otherwise we are loading
known.comment = method.comment if known.comment.empty?
previously = ", previously in #{known.file}" unless
method.file == known.file
@store.rdoc.options.warn \
"Duplicate method #{known.full_name} in #{method.file}#{previously}"
end
else
@methods_hash[key] = method
method.visibility = @visibility
add_to @method_list, method
resolve_aliases method
end
method
end
##
# Adds a module named +name+. If RDoc already knows +name+ is a class then
# that class is returned instead. See also #add_class.
def add_module(class_type, name)
mod = @classes[name] || @modules[name]
return mod if mod
full_name = child_name name
mod = @store.modules_hash[full_name] || class_type.new(name)
add_class_or_module mod, @modules, @store.modules_hash
end
##
# Adds an alias from +from+ (a class or module) to +name+ which was defined
# in +file+.
def add_module_alias from, name, file
return from if @done_documenting
to_name = child_name name
# if we already know this name, don't register an alias:
# see the metaprogramming in lib/active_support/basic_object.rb,
# where we already know BasicObject is a class when we find
# BasicObject = BlankSlate
return from if @store.find_class_or_module to_name
to = from.dup
to.name = name
to.full_name = nil
if to.module? then
@store.modules_hash[to_name] = to
@modules[name] = to
else
@store.classes_hash[to_name] = to
@classes[name] = to
end
# Registers a constant for this alias. The constant value and comment
# will be updated later, when the Ruby parser adds the constant
const = RDoc::Constant.new name, nil, to.comment
const.record_location file
const.is_alias_for = from
add_constant const
to
end
##
# Adds +require+ to this context's top level
def add_require(require)
return require unless @document_self
if RDoc::TopLevel === self then
add_to @requires, require
else
parent.add_require require
end
end
##
# Returns a section with +title+, creating it if it doesn't already exist.
# +comment+ will be appended to the section's comment.
#
# A section with a +title+ of +nil+ will return the default section.
#
# See also RDoc::Context::Section
def add_section title, comment = nil
if section = @sections[title] then
section.add_comment comment if comment
else
section = Section.new self, title, comment
@sections[title] = section
end
section
end
##
# Adds +thing+ to the collection +array+
def add_to array, thing
array << thing if @document_self
thing.parent = self
thing.store = @store if @store
thing.section = current_section
end
##
# Is there any content?
#
# This means any of: comment, aliases, methods, attributes, external
# aliases, require, constant.
#
# Includes and extends are also checked unless includes == false.
def any_content(includes = true)
@any_content ||= !(
@comment.empty? &&
@method_list.empty? &&
@attributes.empty? &&
@aliases.empty? &&
@external_aliases.empty? &&
@requires.empty? &&
@constants.empty?
)
@any_content || (includes && !(@includes + @extends).empty? )
end
##
# Creates the full name for a child with +name+
def child_name name
if name =~ /^:+/
$' #'
elsif RDoc::TopLevel === self then
name
else
"#{self.full_name}::#{name}"
end
end
##
# Class attributes
def class_attributes
@class_attributes ||= attributes.select { |a| a.singleton }
end
##
# Class methods
def class_method_list
@class_method_list ||= method_list.select { |a| a.singleton }
end
##
# Array of classes in this context
def classes
@classes.values
end
##
# All classes and modules in this namespace
def classes_and_modules
classes + modules
end
##
# Hash of classes keyed by class name
def classes_hash
@classes
end
##
# The current documentation section that new items will be added to. If
# temporary_section is available it will be used.
def current_section
if section = @temporary_section then
@temporary_section = nil
else
section = @current_section
end
section
end
##
# Is part of this thing was defined in +file+?
def defined_in?(file)
@in_files.include?(file)
end
def display(method_attr) # :nodoc:
if method_attr.is_a? RDoc::Attr
"#{method_attr.definition} #{method_attr.pretty_name}"
else
"method #{method_attr.pretty_name}"
end
end
##
# Iterator for ancestors for duck-typing. Does nothing. See
# RDoc::ClassModule#each_ancestor.
#
# This method exists to make it easy to work with Context subclasses that
# aren't part of RDoc.
def each_ancestor # :nodoc:
end
##
# Iterator for attributes
def each_attribute # :yields: attribute
@attributes.each { |a| yield a }
end
##
# Iterator for classes and modules
def each_classmodule(&block) # :yields: module
classes_and_modules.sort.each(&block)
end
##
# Iterator for constants
def each_constant # :yields: constant
@constants.each {|c| yield c}
end
##
# Iterator for included modules
def each_include # :yields: include
@includes.each do |i| yield i end
end
##
# Iterator for extension modules
def each_extend # :yields: extend
@extends.each do |e| yield e end
end
##
# Iterator for methods
def each_method # :yields: method
return enum_for __method__ unless block_given?
@method_list.sort.each { |m| yield m }
end
##
# Iterator for each section's contents sorted by title. The +section+, the
# section's +constants+ and the sections +attributes+ are yielded. The
# +constants+ and +attributes+ collections are sorted.
#
# To retrieve methods in a section use #methods_by_type with the optional
# +section+ parameter.
#
# NOTE: Do not edit collections yielded by this method
def each_section # :yields: section, constants, attributes
return enum_for __method__ unless block_given?
constants = @constants.group_by do |constant| constant.section end
attributes = @attributes.group_by do |attribute| attribute.section end
constants.default = []
attributes.default = []
sort_sections.each do |section|
yield section, constants[section].sort, attributes[section].sort
end
end
##
# Finds an attribute +name+ with singleton value +singleton+.
def find_attribute(name, singleton)
name = $1 if name =~ /^(.*)=$/
@attributes.find { |a| a.name == name && a.singleton == singleton }
end
##
# Finds an attribute with +name+ in this context
def find_attribute_named(name)
case name
when /\A#/ then
find_attribute name[1..-1], false
when /\A::/ then
find_attribute name[2..-1], true
else
@attributes.find { |a| a.name == name }
end
end
##
# Finds a class method with +name+ in this context
def find_class_method_named(name)
@method_list.find { |meth| meth.singleton && meth.name == name }
end
##
# Finds a constant with +name+ in this context
def find_constant_named(name)
@constants.find {|m| m.name == name}
end
##
# Find a module at a higher scope
def find_enclosing_module_named(name)
parent && parent.find_module_named(name)
end
##
# Finds an external alias +name+ with singleton value +singleton+.
def find_external_alias(name, singleton)
@external_aliases.find { |m| m.name == name && m.singleton == singleton }
end
##
# Finds an external alias with +name+ in this context
def find_external_alias_named(name)
case name
when /\A#/ then
find_external_alias name[1..-1], false
when /\A::/ then
find_external_alias name[2..-1], true
else
@external_aliases.find { |a| a.name == name }
end
end
##
# Finds a file with +name+ in this context
def find_file_named name
@store.find_file_named name
end
##
# Finds an instance method with +name+ in this context
def find_instance_method_named(name)
@method_list.find { |meth| !meth.singleton && meth.name == name }
end
##
# Finds a method, constant, attribute, external alias, module or file
# named +symbol+ in this context.
def find_local_symbol(symbol)
find_method_named(symbol) or
find_constant_named(symbol) or
find_attribute_named(symbol) or
find_external_alias_named(symbol) or
find_module_named(symbol) or
find_file_named(symbol)
end
##
# Finds a method named +name+ with singleton value +singleton+.
def find_method(name, singleton)
@method_list.find { |m| m.name == name && m.singleton == singleton }
end
##
# Finds a instance or module method with +name+ in this context
def find_method_named(name)
case name
when /\A#/ then
find_method name[1..-1], false
when /\A::/ then
find_method name[2..-1], true
else
@method_list.find { |meth| meth.name == name }
end
end
##
# Find a module with +name+ using ruby's scoping rules
def find_module_named(name)
res = @modules[name] || @classes[name]
return res if res
return self if self.name == name
find_enclosing_module_named name
end
##
# Look up +symbol+, first as a module, then as a local symbol.
def find_symbol(symbol)
find_symbol_module(symbol) || find_local_symbol(symbol)
end
##
# Look up a module named +symbol+.
def find_symbol_module(symbol)
result = nil
# look for a class or module 'symbol'
case symbol
when /^::/ then
result = @store.find_class_or_module symbol
when /^(\w+):+(.+)$/
suffix = $2
top = $1
searched = self
while searched do
mod = searched.find_module_named(top)
break unless mod
result = @store.find_class_or_module "#{mod.full_name}::#{suffix}"
break if result || searched.is_a?(RDoc::TopLevel)
searched = searched.parent
end
else
searched = self
while searched do
result = searched.find_module_named(symbol)
break if result || searched.is_a?(RDoc::TopLevel)
searched = searched.parent
end
end
result
end
##
# The full name for this context. This method is overridden by subclasses.
def full_name
'(unknown)'
end
##
# Does this context and its methods and constants all have documentation?
#
# (Yes, fully documented doesn't mean everything.)
def fully_documented?
documented? and
attributes.all? { |a| a.documented? } and
method_list.all? { |m| m.documented? } and
constants.all? { |c| c.documented? }
end
##
# URL for this with a +prefix+
def http_url(prefix)
path = name_for_path
path = path.gsub(/<<\s*(\w*)/, 'from-\1') if path =~ /<
path = [prefix] + path.split('::')
File.join(*path.compact) + '.html'
end
##
# Instance attributes
def instance_attributes
@instance_attributes ||= attributes.reject { |a| a.singleton }
end
##
# Instance methods
#--
# TODO rename to instance_methods
def instance_method_list
@instance_method_list ||= method_list.reject { |a| a.singleton }
end
##
# Breaks method_list into a nested hash by type ('class' or
# 'instance') and visibility (+:public+, +:protected+, +:private+).
#
# If +section+ is provided only methods in that RDoc::Context::Section will
# be returned.
def methods_by_type section = nil
methods = {}
TYPES.each do |type|
visibilities = {}
RDoc::VISIBILITIES.each do |vis|
visibilities[vis] = []
end
methods[type] = visibilities
end
each_method do |method|
next if section and not method.section == section
methods[method.type][method.visibility] << method
end
methods
end
##
# Yields AnyMethod and Attr entries matching the list of names in +methods+.
def methods_matching(methods, singleton = false, &block)
(@method_list + @attributes).each do |m|
yield m if methods.include?(m.name) and m.singleton == singleton
end
each_ancestor do |parent|
parent.methods_matching(methods, singleton, &block)
end
end
##
# Array of modules in this context
def modules
@modules.values
end
##
# Hash of modules keyed by module name
def modules_hash
@modules
end
##
# Name to use to generate the url.
# #full_name by default.
def name_for_path
full_name
end
##
# Changes the visibility for new methods to +visibility+
def ongoing_visibility=(visibility)
@visibility = visibility
end
##
# Record +top_level+ as a file +self+ is in.
def record_location(top_level)
@in_files << top_level unless @in_files.include?(top_level)
end
##
# Should we remove this context from the documentation?
#
# The answer is yes if:
# * #received_nodoc is +true+
# * #any_content is +false+ (not counting includes)
# * All #includes are modules (not a string), and their module has
# #remove_from_documentation? == true
# * All classes and modules have #remove_from_documentation? == true
def remove_from_documentation?
@remove_from_documentation ||=
@received_nodoc &&
!any_content(false) &&
@includes.all? { |i| !i.module.is_a?(String) && i.module.remove_from_documentation? } &&
classes_and_modules.all? { |cm| cm.remove_from_documentation? }
end
##
# Removes methods and attributes with a visibility less than +min_visibility+.
#--
# TODO mark the visibility of attributes in the template (if not public?)
def remove_invisible(min_visibility)
return if min_visibility == :private
remove_invisible_in @method_list, min_visibility
remove_invisible_in @attributes, min_visibility
end
##
# Only called when min_visibility == :public or :private
def remove_invisible_in array, min_visibility # :nodoc:
if min_visibility == :public then
array.reject! { |e|
e.visibility != :public and not e.force_documentation
}
else
array.reject! { |e|
e.visibility == :private and not e.force_documentation
}
end
end
##
# Tries to resolve unmatched aliases when a method or attribute has just
# been added.
def resolve_aliases added
# resolve any pending unmatched aliases
key = added.pretty_name
unmatched_alias_list = @unmatched_alias_lists[key]
return unless unmatched_alias_list
unmatched_alias_list.each do |unmatched_alias|
added.add_alias unmatched_alias, self
@external_aliases.delete unmatched_alias
end
@unmatched_alias_lists.delete key
end
##
# Returns RDoc::Context::Section objects referenced in this context for use
# in a table of contents.
def section_contents
used_sections = {}
each_method do |method|
next unless method.display?
used_sections[method.section] = true
end
# order found sections
sections = sort_sections.select do |section|
used_sections[section]
end
# only the default section is used
return [] if
sections.length == 1 and not sections.first.title
sections
end
##
# Sections in this context
def sections
@sections.values
end
def sections_hash # :nodoc:
@sections
end
##
# Sets the current section to a section with +title+. See also #add_section
def set_current_section title, comment
@current_section = add_section title, comment
end
##
# Given an array +methods+ of method names, set the visibility of each to
# +visibility+
def set_visibility_for(methods, visibility, singleton = false)
methods_matching methods, singleton do |m|
m.visibility = visibility
end
end
##
# Sorts sections alphabetically (default) or in TomDoc fashion (none,
# Public, Internal, Deprecated)
def sort_sections
titles = @sections.map { |title, _| title }
if titles.length > 1 and
TOMDOC_TITLES_SORT ==
(titles | TOMDOC_TITLES).sort_by { |title| title.to_s } then
@sections.values_at(*TOMDOC_TITLES).compact
else
@sections.sort_by { |title, _|
title.to_s
}.map { |_, section|
section
}
end
end
def to_s # :nodoc:
"#{self.class.name} #{self.full_name}"
end
##
# Return the TopLevel that owns us
#--
# FIXME we can be 'owned' by several TopLevel (see #record_location &
# #in_files)
def top_level
return @top_level if defined? @top_level
@top_level = self
@top_level = @top_level.parent until RDoc::TopLevel === @top_level
@top_level
end
##
# Upgrades NormalModule +mod+ in +enclosing+ to a +class_type+
def upgrade_to_class mod, class_type, enclosing
enclosing.modules_hash.delete mod.name
klass = RDoc::ClassModule.from_module class_type, mod
klass.store = @store
# if it was there, then we keep it even if done_documenting
@store.classes_hash[mod.full_name] = klass
enclosing.classes_hash[mod.name] = klass
klass
end
autoload :Section, 'rdoc/context/section'
end
normal_class.rb 0000644 00000004074 14763537411 0007566 0 ustar 00 ##
# A normal class, neither singleton nor anonymous
class RDoc::NormalClass < RDoc::ClassModule
##
# The ancestors of this class including modules. Unlike Module#ancestors,
# this class is not included in the result. The result will contain both
# RDoc::ClassModules and Strings.
def ancestors
if String === superclass then
super << superclass
elsif superclass then
ancestors = super
ancestors << superclass
ancestors.concat superclass.ancestors
else
super
end
end
##
# The definition of this class, class MyClassName
def definition
"class #{full_name}"
end
def direct_ancestors
superclass ? super + [superclass] : super
end
def inspect # :nodoc:
superclass = @superclass ? " < #{@superclass}" : nil
"<%s:0x%x class %s%s includes: %p extends: %p attributes: %p methods: %p aliases: %p>" % [
self.class, object_id,
full_name, superclass, @includes, @extends, @attributes, @method_list, @aliases
]
end
def to_s # :nodoc:
display = "#{self.class.name} #{self.full_name}"
if superclass
display << ' < ' << (superclass.is_a?(String) ? superclass : superclass.full_name)
end
display << ' -> ' << is_alias_for.to_s if is_alias_for
display
end
def pretty_print q # :nodoc:
superclass = @superclass ? " < #{@superclass}" : nil
q.group 2, "[class #{full_name}#{superclass} ", "]" do
q.breakable
q.text "includes:"
q.breakable
q.seplist @includes do |inc| q.pp inc end
q.breakable
q.text "constants:"
q.breakable
q.seplist @constants do |const| q.pp const end
q.breakable
q.text "attributes:"
q.breakable
q.seplist @attributes do |attr| q.pp attr end
q.breakable
q.text "methods:"
q.breakable
q.seplist @method_list do |meth| q.pp meth end
q.breakable
q.text "aliases:"
q.breakable
q.seplist @aliases do |aliaz| q.pp aliaz end
q.breakable
q.text "comment:"
q.breakable
q.pp comment
end
end
end
rd.rb 0000644 00000007047 14763537411 0005521 0 ustar 00 ##
# RDoc::RD implements the RD format from the rdtool gem.
#
# To choose RD as your only default format see
# RDoc::Options@Saved+Options for instructions on setting up a
# .doc_options
file to store your project default.
#
# == LICENSE
#
# The grammar that produces RDoc::RD::BlockParser and RDoc::RD::InlineParser
# is included in RDoc under the Ruby License.
#
# You can find the original source for rdtool at
# https://github.com/uwabami/rdtool/
#
# You can use, re-distribute or change these files under Ruby's License or GPL.
#
# 1. You may make and give away verbatim copies of the source form of the
# software without restriction, provided that you duplicate all of the
# original copyright notices and associated disclaimers.
#
# 2. You may modify your copy of the software in any way, provided that
# you do at least ONE of the following:
#
# a. place your modifications in the Public Domain or otherwise
# make them Freely Available, such as by posting said
# modifications to Usenet or an equivalent medium, or by allowing
# the author to include your modifications in the software.
#
# b. use the modified software only within your corporation or
# organization.
#
# c. give non-standard binaries non-standard names, with
# instructions on where to get the original software distribution.
#
# d. make other distribution arrangements with the author.
#
# 3. You may distribute the software in object code or binary form,
# provided that you do at least ONE of the following:
#
# a. distribute the binaries and library files of the software,
# together with instructions (in the manual page or equivalent)
# on where to get the original distribution.
#
# b. accompany the distribution with the machine-readable source of
# the software.
#
# c. give non-standard binaries non-standard names, with
# instructions on where to get the original software distribution.
#
# d. make other distribution arrangements with the author.
#
# 4. You may modify and include the part of the software into any other
# software (possibly commercial). But some files in the distribution
# are not written by the author, so that they are not under these terms.
#
# For the list of those files and their copying conditions, see the
# file LEGAL.
#
# 5. The scripts and library files supplied as input to or produced as
# output from the software do not automatically fall under the
# copyright of the software, but belong to whomever generated them,
# and may be sold commercially, and may be aggregated with this
# software.
#
# 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE.
class RDoc::RD
##
# Parses +rd+ source and returns an RDoc::Markup::Document. If the
# =begin or =end lines are missing they will be added.
def self.parse rd
rd = rd.lines.to_a
if rd.find { |i| /\S/ === i } and !rd.find{|i| /^=begin\b/ === i } then
rd.unshift("=begin\n").push("=end\n")
end
parser = RDoc::RD::BlockParser.new
document = parser.parse rd
# isn't this always true?
document.parts.shift if RDoc::Markup::BlankLine === document.parts.first
document.parts.pop if RDoc::Markup::BlankLine === document.parts.last
document
end
autoload :BlockParser, 'rdoc/rd/block_parser'
autoload :InlineParser, 'rdoc/rd/inline_parser'
autoload :Inline, 'rdoc/rd/inline'
end
markup.rb 0000644 00000070472 14763537411 0006415 0 ustar 00 ##
# RDoc::Markup parses plain text documents and attempts to decompose them into
# their constituent parts. Some of these parts are high-level: paragraphs,
# chunks of verbatim text, list entries and the like. Other parts happen at
# the character level: a piece of bold text, a word in code font. This markup
# is similar in spirit to that used on WikiWiki webs, where folks create web
# pages using a simple set of formatting rules.
#
# RDoc::Markup and other markup formats do no output formatting, this is
# handled by the RDoc::Markup::Formatter subclasses.
#
# = Supported Formats
#
# Besides the RDoc::Markup format, the following formats are built in to RDoc:
#
# markdown::
# The markdown format as described by
# http://daringfireball.net/projects/markdown/. See RDoc::Markdown for
# details on the parser and supported extensions.
# rd::
# The rdtool format. See RDoc::RD for details on the parser and format.
# tomdoc::
# The TomDoc format as described by http://tomdoc.org/. See RDoc::TomDoc
# for details on the parser and supported extensions.
#
# You can choose a markup format using the following methods:
#
# per project::
# If you build your documentation with rake use RDoc::Task#markup.
#
# If you build your documentation by hand run:
#
# rdoc --markup your_favorite_format --write-options
#
# and commit .rdoc_options and ship it with your packaged gem.
# per file::
# At the top of the file use the :markup: directive to set the
# default format for the rest of the file.
# per comment::
# Use the :markup: directive at the top of a comment you want
# to write in a different format.
#
# = RDoc::Markup
#
# RDoc::Markup is extensible at runtime: you can add \new markup elements to
# be recognized in the documents that RDoc::Markup parses.
#
# RDoc::Markup is intended to be the basis for a family of tools which share
# the common requirement that simple, plain-text should be rendered in a
# variety of different output formats and media. It is envisaged that
# RDoc::Markup could be the basis for formatting RDoc style comment blocks,
# Wiki entries, and online FAQs.
#
# == Synopsis
#
# This code converts +input_string+ to HTML. The conversion takes place in
# the +convert+ method, so you can use the same RDoc::Markup converter to
# convert multiple input strings.
#
# require 'rdoc'
#
# h = RDoc::Markup::ToHtml.new
#
# puts h.convert(input_string)
#
# You can extend the RDoc::Markup parser to recognize new markup
# sequences, and to add special processing for text that matches a
# regular expression. Here we make WikiWords significant to the parser,
# and also make the sequences {word} and \
)
#
# Unlike conventional Wiki markup, general markup can cross line
# boundaries. You can turn off the interpretation of markup by
# preceding the first character with a backslash (see Escaping
# Text Markup, below).
#
# === Links
#
# Links to starting with +http:+, +https:+, +mailto:+, +ftp:+ or +www.+
# are recognized. An HTTP url that references an external image is converted
# into an inline image element.
#
# Classes and methods will be automatically linked to their definition. For
# example, RDoc::Markup will link to this documentation. By default
# methods will only be automatically linked if they contain an _ (all
# methods can be automatically linked through the --hyperlink-all
# command line option).
#
# Single-word methods can be linked by using the # character for
# instance methods or :: for class methods. For example,
# #convert links to #convert. A class or method may be combined like
# RDoc::Markup#convert.
#
# A heading inside the documentation can be linked by following the class
# or method by an @ then the heading name.
# RDoc::Markup@Links will link to this section like this:
# RDoc::Markup@Links. Spaces in headings with multiple words must be escaped
# with + like RDoc::Markup@Escaping+Text+Markup.
# Punctuation and other special characters must be escaped like CGI.escape.
#
# Links can also be of the form label[url], in which case +label+ is
# used in the displayed text, and +url+ is used as the target. If +label+
# contains multiple words, put it in braces: {multi word label}[url].
# The +url+ may be an +http:+-type link or a cross-reference to a class,
# module or method with a label.
#
# Links with the rdoc-ref: scheme will link to the referenced class,
# module, method, file, etc. If the referenced item is does not exist
# no link will be generated and rdoc-ref: will be removed from the
# resulting text.
#
# Links starting with rdoc-label:label_name will link to the
# +label_name+. You can create a label for the current link (for
# bidirectional links) by supplying a name for the current link like
# rdoc-label:label-other:label-mine.
#
# Links starting with +link:+ refer to local files whose path is relative to
# the --op directory. Use rdoc-ref: instead of
# link: to link to files generated by RDoc as the link target may
# be different across RDoc generators.
#
# Example links:
#
# https://github.com/rdoc/rdoc
# mailto:user@example.com
# {RDoc Documentation}[http://rdoc.rubyforge.org]
# {RDoc Markup}[rdoc-ref:RDoc::Markup]
#
# === Escaping Text Markup
#
# Text markup can be escaped with a backslash, as in \, which was obtained
# with \\. Except in verbatim sections and between \ tags,
# to produce a backslash you have to double it unless it is followed by a
# space, tab or newline. Otherwise, the HTML formatter will discard it, as it
# is used to escape potential links:
#
# * The \ must be doubled if not followed by white space: \\.
# * But not in \ tags: in a Regexp, \S matches non-space.
# * This is a link to {ruby-lang}[www.ruby-lang.org].
# * This is not a link, however: \{ruby-lang.org}[www.ruby-lang.org].
# * This will not be linked to \RDoc::RDoc#document
#
# generates:
#
# * The \ must be doubled if not followed by white space: \\.
# * But not in \ tags: in a Regexp, \S matches non-space.
# * This is a link to {ruby-lang}[www.ruby-lang.org]
# * This is not a link, however: \{ruby-lang.org}[www.ruby-lang.org]
# * This will not be linked to \RDoc::RDoc#document
#
# Inside \ tags, more precisely, leading backslashes are removed only if
# followed by a markup character (<*_+), a backslash, or a known link
# reference (a known class or method). So in the example above, the backslash
# of \S would be removed if there was a class or module named +S+ in
# the current context.
#
# This behavior is inherited from RDoc version 1, and has been kept for
# compatibility with existing RDoc documentation.
#
# === Conversion of characters
#
# HTML will convert two/three dashes to an em-dash. Other common characters are
# converted as well:
#
# em-dash:: -- or ---
# ellipsis:: ...
#
# single quotes:: 'text' or `text'
# double quotes:: "text" or ``text''
#
# copyright:: (c)
# registered trademark:: (r)
#
# produces:
#
# em-dash:: -- or ---
# ellipsis:: ...
#
# single quotes:: 'text' or `text'
# double quotes:: "text" or ``text''
#
# copyright:: (c)
# registered trademark:: (r)
#
#
# == Documenting Source Code
#
# Comment blocks can be written fairly naturally, either using # on
# successive lines of the comment, or by including the comment in
# a =begin/=end block. If you use the latter form,
# the =begin line _must_ be flagged with an +rdoc+ tag:
#
# =begin rdoc
# Documentation to be processed by RDoc.
#
# ...
# =end
#
# RDoc stops processing comments if it finds a comment line starting
# with -- right after the # character (otherwise,
# it will be treated as a rule if it has three dashes or more).
# This can be used to separate external from internal comments,
# or to stop a comment being associated with a method, class, or module.
# Commenting can be turned back on with a line that starts with ++.
#
# ##
# # Extract the age and calculate the date-of-birth.
# #--
# # FIXME: fails if the birthday falls on February 29th
# #++
# # The DOB is returned as a Time object.
#
# def get_dob(person)
# # ...
# end
#
# Names of classes, files, and any method names containing an underscore or
# preceded by a hash character are automatically linked from comment text to
# their description. This linking works inside the current class or module,
# and with ancestor methods (in included modules or in the superclass).
#
# Method parameter lists are extracted and displayed with the method
# description. If a method calls +yield+, then the parameters passed to yield
# will also be displayed:
#
# def fred
# ...
# yield line, address
#
# This will get documented as:
#
# fred() { |line, address| ... }
#
# You can override this using a comment containing ':yields: ...' immediately
# after the method definition
#
# def fred # :yields: index, position
# # ...
#
# yield line, address
#
# which will get documented as
#
# fred() { |index, position| ... }
#
# +:yields:+ is an example of a documentation directive. These appear
# immediately after the start of the document element they are modifying.
#
# RDoc automatically cross-references words with underscores or camel-case.
# To suppress cross-references, prefix the word with a \ character. To
# include special characters like "\n", you'll need to use
# two \ characters in normal text, but only one in \ text:
#
# "\\n" or "\n"
#
# produces:
#
# "\\n" or "\n"
#
# == Directives
#
# Directives are keywords surrounded by ":" characters.
#
# === Controlling what is documented
#
# [+:nodoc:+ / :nodoc: all]
# This directive prevents documentation for the element from
# being generated. For classes and modules, methods, aliases,
# constants, and attributes directly within the affected class or
# module also will be omitted. By default, though, modules and
# classes within that class or module _will_ be documented. This is
# turned off by adding the +all+ modifier.
#
# module MyModule # :nodoc:
# class Input
# end
# end
#
# module OtherModule # :nodoc: all
# class Output
# end
# end
#
# In the above code, only class MyModule::Input will be documented.
#
# The +:nodoc:+ directive, like +:enddoc:+, +:stopdoc:+ and +:startdoc:+
# presented below, is local to the current file: if you do not want to
# document a module that appears in several files, specify +:nodoc:+ on each
# appearance, at least once per file.
#
# [+:stopdoc:+ / +:startdoc:+]
# Stop and start adding new documentation elements to the current container.
# For example, if a class has a number of constants that you don't want to
# document, put a +:stopdoc:+ before the first, and a +:startdoc:+ after the
# last. If you don't specify a +:startdoc:+ by the end of the container,
# disables documentation for the rest of the current file.
#
# [+:doc:+]
# Forces a method or attribute to be documented even if it wouldn't be
# otherwise. Useful if, for example, you want to include documentation of a
# particular private method.
#
# [+:enddoc:+]
# Document nothing further at the current level: directives +:startdoc:+ and
# +:doc:+ that appear after this will not be honored for the current container
# (file, class or module), in the current file.
#
# [+:notnew:+ / +:not_new:+ / +:not-new:+ ]
# Only applicable to the +initialize+ instance method. Normally RDoc
# assumes that the documentation and parameters for +initialize+ are
# actually for the +new+ method, and so fakes out a +new+ for the class.
# The +:notnew:+ directive stops this. Remember that +initialize+ is private,
# so you won't see the documentation unless you use the +-a+ command line
# option.
#
# === Method arguments
#
# [+:arg:+ or +:args:+ _parameters_]
# Overrides the default argument handling with exactly these parameters.
#
# ##
# # :args: a, b
#
# def some_method(*a)
# end
#
# [+:yield:+ or +:yields:+ _parameters_]
# Overrides the default yield discovery with these parameters.
#
# ##
# # :yields: key, value
#
# def each_thing &block
# @things.each(&block)
# end
#
# [+:call-seq:+]
# Lines up to the next blank line or lines with a common prefix in the
# comment are treated as the method's calling sequence, overriding the
# default parsing of method parameters and yield arguments.
#
# Multiple lines may be used.
#
# # :call-seq:
# # ARGF.readlines(sep=$/) -> array
# # ARGF.readlines(limit) -> array
# # ARGF.readlines(sep, limit) -> array
# #
# # ARGF.to_a(sep=$/) -> array
# # ARGF.to_a(limit) -> array
# # ARGF.to_a(sep, limit) -> array
# #
# # The remaining lines are documentation ...
#
# === Sections
#
# Sections allow you to group methods in a class into sensible containers. If
# you use the sections 'Public', 'Internal' and 'Deprecated' (the three
# allowed method statuses from TomDoc) the sections will be displayed in that
# order placing the most useful methods at the top. Otherwise, sections will
# be displayed in alphabetical order.
#
# [+:category:+ _section_]
# Adds this item to the named +section+ overriding the current section. Use
# this to group methods by section in RDoc output while maintaining a
# sensible ordering (like alphabetical).
#
# # :category: Utility Methods
# #
# # CGI escapes +text+
#
# def convert_string text
# CGI.escapeHTML text
# end
#
# An empty category will place the item in the default category:
#
# # :category:
# #
# # This method is in the default category
#
# def some_method
# # ...
# end
#
# Unlike the :section: directive, :category: is not sticky. The category
# only applies to the item immediately following the comment.
#
# Use the :section: directive to provide introductory text for a section of
# documentation.
#
# [+:section:+ _title_]
# Provides section introductory text in RDoc output. The title following
# +:section:+ is used as the section name and the remainder of the comment
# containing the section is used as introductory text. A section's comment
# block must be separated from following comment blocks. Use an empty title
# to switch to the default section.
#
# The :section: directive is sticky, so subsequent methods, aliases,
# attributes, and classes will be contained in this section until the
# section is changed. The :category: directive will override the :section:
# directive.
#
# A :section: comment block may have one or more lines before the :section:
# directive. These will be removed, and any identical lines at the end of
# the block are also removed. This allows you to add visual cues to the
# section.
#
# Example:
#
# # ----------------------------------------
# # :section: My Section
# # This is the section that I wrote.
# # See it glisten in the noon-day sun.
# # ----------------------------------------
#
# ##
# # Comment for some_method
#
# def some_method
# # ...
# end
#
# === Other directives
#
# [+:markup:+ _type_]
# Overrides the default markup type for this comment with the specified
# markup type. For ruby files, if the first comment contains this directive
# it is applied automatically to all comments in the file.
#
# Unless you are converting between markup formats you should use a
# .rdoc_options
file to specify the default documentation
# format for your entire project. See RDoc::Options@Saved+Options for
# instructions.
#
# At the top of a file the +:markup:+ directive applies to the entire file:
#
# # coding: UTF-8
# # :markup: TomDoc
#
# # TomDoc comment here ...
#
# class MyClass
# # ...
#
# For just one comment:
#
# # ...
# end
#
# # :markup: RDoc
# #
# # This is a comment in RDoc markup format ...
#
# def some_method
# # ...
#
# See Markup@DEVELOPERS for instructions on adding a new markup format.
#
# [+:include:+ _filename_]
# Include the contents of the named file at this point. This directive
# must appear alone on one line, possibly preceded by spaces. In this
# position, it can be escaped with a \ in front of the first colon.
#
# The file will be searched for in the directories listed by the +--include+
# option, or in the current directory by default. The contents of the file
# will be shifted to have the same indentation as the ':' at the start of
# the +:include:+ directive.
#
# [+:title:+ _text_]
# Sets the title for the document. Equivalent to the --title
# command line parameter. (The command line parameter overrides any :title:
# directive in the source).
#
# [+:main:+ _name_]
# Equivalent to the --main command line parameter.
#
#--
# Original Author:: Dave Thomas, dave@pragmaticprogrammer.com
# License:: Ruby license
class RDoc::Markup
##
# An AttributeManager which handles inline markup.
attr_reader :attribute_manager
##
# Parses +str+ into an RDoc::Markup::Document.
def self.parse str
RDoc::Markup::Parser.parse str
rescue RDoc::Markup::Parser::Error => e
$stderr.puts <<-EOF
While parsing markup, RDoc encountered a #{e.class}:
#{e}
\tfrom #{e.backtrace.join "\n\tfrom "}
---8<---
#{text}
---8<---
RDoc #{RDoc::VERSION}
Ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} #{RUBY_RELEASE_DATE}
Please file a bug report with the above information at:
https://github.com/rdoc/rdoc/issues
EOF
raise
end
##
# Take a block of text and use various heuristics to determine its
# structure (paragraphs, lists, and so on). Invoke an event handler as we
# identify significant chunks.
def initialize attribute_manager = nil
@attribute_manager = attribute_manager || RDoc::Markup::AttributeManager.new
@output = nil
end
##
# Add to the sequences used to add formatting to an individual word (such
# as *bold*). Matching entries will generate attributes that the output
# formatters can recognize by their +name+.
def add_word_pair(start, stop, name)
@attribute_manager.add_word_pair(start, stop, name)
end
##
# Add to the sequences recognized as general markup.
def add_html(tag, name)
@attribute_manager.add_html(tag, name)
end
##
# Add to other inline sequences. For example, we could add WikiWords using
# something like:
#
# parser.add_special(/\b([A-Z][a-z]+[A-Z]\w+)/, :WIKIWORD)
#
# Each wiki word will be presented to the output formatter via the
# accept_special method.
def add_special(pattern, name)
@attribute_manager.add_special(pattern, name)
end
##
# We take +input+, parse it if necessary, then invoke the output +formatter+
# using a Visitor to render the result.
def convert input, formatter
document = case input
when RDoc::Markup::Document then
input
else
RDoc::Markup::Parser.parse input
end
document.accept formatter
end
autoload :Parser, 'rdoc/markup/parser'
autoload :PreProcess, 'rdoc/markup/pre_process'
# Inline markup classes
autoload :AttrChanger, 'rdoc/markup/attr_changer'
autoload :AttrSpan, 'rdoc/markup/attr_span'
autoload :Attributes, 'rdoc/markup/attributes'
autoload :AttributeManager, 'rdoc/markup/attribute_manager'
autoload :Special, 'rdoc/markup/special'
# RDoc::Markup AST
autoload :BlankLine, 'rdoc/markup/blank_line'
autoload :BlockQuote, 'rdoc/markup/block_quote'
autoload :Document, 'rdoc/markup/document'
autoload :HardBreak, 'rdoc/markup/hard_break'
autoload :Heading, 'rdoc/markup/heading'
autoload :Include, 'rdoc/markup/include'
autoload :IndentedParagraph, 'rdoc/markup/indented_paragraph'
autoload :List, 'rdoc/markup/list'
autoload :ListItem, 'rdoc/markup/list_item'
autoload :Paragraph, 'rdoc/markup/paragraph'
autoload :Raw, 'rdoc/markup/raw'
autoload :Rule, 'rdoc/markup/rule'
autoload :Verbatim, 'rdoc/markup/verbatim'
# Formatters
autoload :Formatter, 'rdoc/markup/formatter'
autoload :FormatterTestCase, 'rdoc/markup/formatter_test_case'
autoload :TextFormatterTestCase, 'rdoc/markup/text_formatter_test_case'
autoload :ToAnsi, 'rdoc/markup/to_ansi'
autoload :ToBs, 'rdoc/markup/to_bs'
autoload :ToHtml, 'rdoc/markup/to_html'
autoload :ToHtmlCrossref, 'rdoc/markup/to_html_crossref'
autoload :ToHtmlSnippet, 'rdoc/markup/to_html_snippet'
autoload :ToLabel, 'rdoc/markup/to_label'
autoload :ToMarkdown, 'rdoc/markup/to_markdown'
autoload :ToRdoc, 'rdoc/markup/to_rdoc'
autoload :ToTableOfContents, 'rdoc/markup/to_table_of_contents'
autoload :ToTest, 'rdoc/markup/to_test'
autoload :ToTtOnly, 'rdoc/markup/to_tt_only'
end
markup/verbatim.rb 0000644 00000002405 14763537411 0010215 0 ustar 00 ##
# A section of verbatim text
class RDoc::Markup::Verbatim < RDoc::Markup::Raw
##
# Format of this verbatim section
attr_accessor :format
def initialize *parts # :nodoc:
super
@format = nil
end
def == other # :nodoc:
super and @format == other.format
end
##
# Calls #accept_verbatim on +visitor+
def accept visitor
visitor.accept_verbatim self
end
##
# Collapses 3+ newlines into two newlines
def normalize
parts = []
newlines = 0
@parts.each do |part|
case part
when /^\s*\n/ then
newlines += 1
parts << part if newlines == 1
else
newlines = 0
parts << part
end
end
parts.pop if parts.last =~ /\A\r?\n\z/
@parts = parts
end
def pretty_print q # :nodoc:
self.class.name =~ /.*::(\w{1,4})/i
q.group 2, "[#{$1.downcase}: ", ']' do
if @format then
q.text "format: #{@format}"
q.breakable
end
q.seplist @parts do |part|
q.pp part
end
end
end
##
# Is this verbatim section ruby code?
def ruby?
@format ||= nil # TODO for older ri data, switch the tree to marshal_dump
@format == :ruby
end
##
# The text of the section
def text
@parts.join
end
end
markup/rule.rb 0000644 00000000435 14763537411 0007354 0 ustar 00 ##
# A horizontal rule with a weight
class RDoc::Markup::Rule < Struct.new :weight
##
# Calls #accept_rule on +visitor+
def accept visitor
visitor.accept_rule self
end
def pretty_print q # :nodoc:
q.group 2, '[rule:', ']' do
q.pp weight
end
end
end
markup/paragraph.rb 0000644 00000000717 14763537411 0010355 0 ustar 00 ##
# A Paragraph of text
class RDoc::Markup::Paragraph < RDoc::Markup::Raw
##
# Calls #accept_paragraph on +visitor+
def accept visitor
visitor.accept_paragraph self
end
##
# Joins the raw paragraph text and converts inline HardBreaks to the
# +hard_break+ text.
def text hard_break = ''
@parts.map do |part|
if RDoc::Markup::HardBreak === part then
hard_break
else
part
end
end.join
end
end
markup/hard_break.rb 0000644 00000000640 14763537411 0010465 0 ustar 00 ##
# A hard-break in the middle of a paragraph.
class RDoc::Markup::HardBreak
@instance = new
##
# RDoc::Markup::HardBreak is a singleton
def self.new
@instance
end
##
# Calls #accept_hard_break on +visitor+
def accept visitor
visitor.accept_hard_break self
end
def == other # :nodoc:
self.class === other
end
def pretty_print q # :nodoc:
q.text "[break]"
end
end
markup/raw.rb 0000644 00000001712 14763537411 0007175 0 ustar 00 ##
# A section of text that is added to the output document as-is
class RDoc::Markup::Raw
##
# The component parts of the list
attr_reader :parts
##
# Creates a new Raw containing +parts+
def initialize *parts
@parts = []
@parts.concat parts
end
##
# Appends +text+
def << text
@parts << text
end
def == other # :nodoc:
self.class == other.class and @parts == other.parts
end
##
# Calls #accept_raw+ on +visitor+
def accept visitor
visitor.accept_raw self
end
##
# Appends +other+'s parts
def merge other
@parts.concat other.parts
end
def pretty_print q # :nodoc:
self.class.name =~ /.*::(\w{1,4})/i
q.group 2, "[#{$1.downcase}: ", ']' do
q.seplist @parts do |part|
q.pp part
end
end
end
##
# Appends +texts+ onto this Paragraph
def push *texts
self.parts.concat texts
end
##
# The raw text
def text
@parts.join ' '
end
end
markup/to_label.rb 0000644 00000003422 14763537411 0010165 0 ustar 00 require 'cgi'
##
# Creates HTML-safe labels suitable for use in id attributes. Tidylinks are
# converted to their link part and cross-reference links have the suppression
# marks removed (\\SomeClass is converted to SomeClass).
class RDoc::Markup::ToLabel < RDoc::Markup::Formatter
attr_reader :res # :nodoc:
##
# Creates a new formatter that will output HTML-safe labels
def initialize markup = nil
super nil, markup
@markup.add_special RDoc::CrossReference::CROSSREF_REGEXP, :CROSSREF
@markup.add_special(/(((\{.*?\})|\b\S+?)\[\S+?\])/, :TIDYLINK)
add_tag :BOLD, '', ''
add_tag :TT, '', ''
add_tag :EM, '', ''
@res = []
end
##
# Converts +text+ to an HTML-safe label
def convert text
label = convert_flow @am.flow text
CGI.escape label
end
##
# Converts the CROSSREF +special+ to plain text, removing the suppression
# marker, if any
def handle_special_CROSSREF special
text = special.text
text.sub(/^\\/, '')
end
##
# Converts the TIDYLINK +special+ to just the text part
def handle_special_TIDYLINK special
text = special.text
return text unless text =~ /\{(.*?)\}\[(.*?)\]/ or text =~ /(\S+)\[(.*?)\]/
$1
end
alias accept_blank_line ignore
alias accept_block_quote ignore
alias accept_heading ignore
alias accept_list_end ignore
alias accept_list_item_end ignore
alias accept_list_item_start ignore
alias accept_list_start ignore
alias accept_paragraph ignore
alias accept_raw ignore
alias accept_rule ignore
alias accept_verbatim ignore
alias end_accepting ignore
alias handle_special_HARD_BREAK ignore
alias start_accepting ignore
end
markup/to_tt_only.rb 0000644 00000004361 14763537411 0010601 0 ustar 00 ##
# Extracts sections of text enclosed in plus, tt or code. Used to discover
# undocumented parameters.
class RDoc::Markup::ToTtOnly < RDoc::Markup::Formatter
##
# Stack of list types
attr_reader :list_type
##
# Output accumulator
attr_reader :res
##
# Creates a new tt-only formatter.
def initialize markup = nil
super nil, markup
add_tag :TT, nil, nil
end
##
# Adds tts from +block_quote+ to the output
def accept_block_quote block_quote
tt_sections block_quote.text
end
##
# Pops the list type for +list+ from #list_type
def accept_list_end list
@list_type.pop
end
##
# Pushes the list type for +list+ onto #list_type
def accept_list_start list
@list_type << list.type
end
##
# Prepares the visitor for consuming +list_item+
def accept_list_item_start list_item
case @list_type.last
when :NOTE, :LABEL then
Array(list_item.label).map do |label|
tt_sections label
end.flatten
end
end
##
# Adds +paragraph+ to the output
def accept_paragraph paragraph
tt_sections(paragraph.text)
end
##
# Does nothing to +markup_item+ because it doesn't have any user-built
# content
def do_nothing markup_item
end
alias accept_blank_line do_nothing # :nodoc:
alias accept_heading do_nothing # :nodoc:
alias accept_list_item_end do_nothing # :nodoc:
alias accept_raw do_nothing # :nodoc:
alias accept_rule do_nothing # :nodoc:
alias accept_verbatim do_nothing # :nodoc:
##
# Extracts tt sections from +text+
def tt_sections text
flow = @am.flow text.dup
flow.each do |item|
case item
when String then
@res << item if in_tt?
when RDoc::Markup::AttrChanger then
off_tags res, item
on_tags res, item
when RDoc::Markup::Special then
@res << convert_special(item) if in_tt? # TODO can this happen?
else
raise "Unknown flow element: #{item.inspect}"
end
end
res
end
##
# Returns an Array of items that were wrapped in plus, tt or code.
def end_accepting
@res.compact
end
##
# Prepares the visitor for gathering tt sections
def start_accepting
@res = []
@list_type = []
end
end
markup/to_html.rb 0000644 00000020160 14763537411 0010050 0 ustar 00 require 'cgi'
##
# Outputs RDoc markup as HTML.
class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
include RDoc::Text
# :section: Utilities
##
# Maps RDoc::Markup::Parser::LIST_TOKENS types to HTML tags
LIST_TYPE_TO_HTML = {
:BULLET => ['
def handle_special_HARD_BREAK special
'" block_quote.parts.each do |part| part.accept self end @res << "\n" end ## # Adds +paragraph+ to the output def accept_paragraph paragraph @res << "\n
" text = paragraph.text @hard_break @res << wrap(to_html(text)) @res << "
\n" end ## # Adds +verbatim+ to the output def accept_verbatim verbatim text = verbatim.text.rstrip @res << if verbatim.ruby? or parseable? text then begin tokens = RDoc::RubyLex.tokenize text, @options html = RDoc::TokenStream.to_html tokens "\n#{html}\n" rescue RDoc::RubyLex::Error "\n
#{CGI.escapeHTML text}\n" end else "\n
#{CGI.escapeHTML text}\n" end end ## # Adds +rule+ to the output def accept_rule(rule) size = rule.weight size = 10 if size > 10 @res << "
", "
"
add_tag :EM, "", ""
end
##
# Returns the HTML tag for +list_type+, possible using a label from
# +list_item+
def list_item_start(list_item, list_type)
case list_type
when :BULLET, :LALPHA, :NUMBER, :UALPHA then
"#{to_html heading.text}\n" add_paragraph end ## # Raw sections are untrusted and ignored alias accept_raw ignore ## # Rules are ignored alias accept_rule ignore def accept_paragraph paragraph para = @in_list_entry.last || "
"
text = paragraph.text @hard_break
@res << "#{para}#{wrap to_html text}\n"
add_paragraph
end
##
# Finishes consumption of +list_item+
def accept_list_item_end list_item
end
##
# Prepares the visitor for consuming +list_item+
def accept_list_item_start list_item
@res << list_item_start(list_item, @list.last)
end
##
# Prepares the visitor for consuming +list+
def accept_list_start list
@list << list.type
@res << html_list_name(list.type, true)
@in_list_entry.push ''
end
##
# Adds +verbatim+ to the output
def accept_verbatim verbatim
throw :done if @characters >= @character_limit
input = verbatim.text.rstrip
text = truncate input
text << ' ...' unless text == input
super RDoc::Markup::Verbatim.new text
add_paragraph
end
##
# Prepares the visitor for HTML snippet generation
def start_accepting
super
@characters = 0
end
##
# Removes escaping from the cross-references in +special+
def handle_special_CROSSREF special
special.text.sub(/\A\\/, '')
end
##
# +special+ is a
def handle_special_HARD_BREAK special
@characters -= 4
'
'
end
##
# Lists are paragraphs, but notes and labels have a separator
def list_item_start list_item, list_type
throw :done if @characters >= @character_limit
case list_type
when :BULLET, :LALPHA, :NUMBER, :UALPHA then
"
" when :LABEL, :NOTE then labels = Array(list_item.label).map do |label| to_html label end.join ', ' labels << " — " unless labels.empty? start = "
#{labels}"
@characters += 1 # try to include the label
start
else
raise RDoc::Error, "Invalid list type: #{list_type.inspect}"
end
end
##
# Returns just the text of +link+, +url+ is only used to determine the link
# type.
def gen_url url, text
if url =~ /^rdoc-label:([^:]*)(?::(.*))?/ then
type = "link"
elsif url =~ /([A-Za-z]+):(.*)/ then
type = $1
else
type = "http"
end
if (type == "http" or type == "https" or type == "link") and
url =~ /\.(gif|png|jpg|jpeg|bmp)$/ then
''
else
text.sub(%r%^#{type}:/*%, '')
end
end
##
# In snippets, there are no lists
def html_list_name list_type, open_tag
''
end
##
# Throws +:done+ when paragraph_limit paragraphs have been encountered
def add_paragraph
@paragraphs += 1
throw :done if @paragraphs >= @paragraph_limit
end
##
# Marks up +content+
def convert content
catch :done do
return super
end
end_accepting
end
##
# Converts flow items +flow+
def convert_flow flow
throw :done if @characters >= @character_limit
res = []
@mask = 0
flow.each do |item|
case item
when RDoc::Markup::AttrChanger then
off_tags res, item
on_tags res, item
when String then
text = convert_string item
res << truncate(text)
when RDoc::Markup::Special then
text = convert_special item
res << truncate(text)
else
raise "Unknown flow element: #{item.inspect}"
end
if @characters >= @character_limit then
off_tags res, RDoc::Markup::AttrChanger.new(0, @mask)
break
end
end
res << ' ...' if @characters >= @character_limit
res.join
end
##
# Maintains a bitmask to allow HTML elements to be closed properly. See
# RDoc::Markup::Formatter.
def on_tags res, item
@mask ^= item.turn_on
super
end
##
# Maintains a bitmask to allow HTML elements to be closed properly. See
# RDoc::Markup::Formatter.
def off_tags res, item
@mask ^= item.turn_off
super
end
##
# Truncates +text+ at the end of the first word after the character_limit.
def truncate text
length = text.length
characters = @characters
@characters += length
return text if @characters < @character_limit
remaining = @character_limit - characters
text =~ /\A(.{#{remaining},}?)(\s|$)/m # TODO word-break instead of \s?
$1
end
end
markup/formatter_test_case.rb 0000644 00000041541 14763537411 0012445 0 ustar 00 require 'minitest/unit'
##
# Test case for creating new RDoc::Markup formatters. See
# test/test_rdoc_markup_to_*.rb for examples.
#
# This test case adds a variety of tests to your subclass when
# #add_visitor_tests is called. Most tests set up a scenario then call a
# method you will provide to perform the assertion on the output.
#
# Your subclass must instantiate a visitor and assign it to @to.
#
# For example, test_accept_blank_line sets up a RDoc::Markup::BlockLine then
# calls accept_blank_line on your visitor. You are responsible for asserting
# that the output is correct.
#
# Example:
#
# class TestRDocMarkupToNewFormat < RDoc::Markup::FormatterTestCase
#
# add_visitor_tests
#
# def setup
# super
#
# @to = RDoc::Markup::ToNewFormat.new
# end
#
# def accept_blank_line
# assert_equal :junk, @to.res.join
# end
#
# # ...
#
# end
class RDoc::Markup::FormatterTestCase < RDoc::TestCase
##
# Call #setup when inheriting from this test case.
#
# Provides the following instance variables:
#
# +@m+:: RDoc::Markup.new
# +@RM+:: RDoc::Markup # to reduce typing
# +@bullet_list+:: @RM::List.new :BULLET, # ...
# +@label_list+:: @RM::List.new :LABEL, # ...
# +@lalpha_list+:: @RM::List.new :LALPHA, # ...
# +@note_list+:: @RM::List.new :NOTE, # ...
# +@number_list+:: @RM::List.new :NUMBER, # ...
# +@ualpha_list+:: @RM::List.new :UALPHA, # ...
def setup
super
@options = RDoc::Options.new
@m = @RM.new
@bullet_list = @RM::List.new(:BULLET,
@RM::ListItem.new(nil, @RM::Paragraph.new('l1')),
@RM::ListItem.new(nil, @RM::Paragraph.new('l2')))
@label_list = @RM::List.new(:LABEL,
@RM::ListItem.new('cat', @RM::Paragraph.new('cats are cool')),
@RM::ListItem.new('dog', @RM::Paragraph.new('dogs are cool too')))
@lalpha_list = @RM::List.new(:LALPHA,
@RM::ListItem.new(nil, @RM::Paragraph.new('l1')),
@RM::ListItem.new(nil, @RM::Paragraph.new('l2')))
@note_list = @RM::List.new(:NOTE,
@RM::ListItem.new('cat', @RM::Paragraph.new('cats are cool')),
@RM::ListItem.new('dog', @RM::Paragraph.new('dogs are cool too')))
@number_list = @RM::List.new(:NUMBER,
@RM::ListItem.new(nil, @RM::Paragraph.new('l1')),
@RM::ListItem.new(nil, @RM::Paragraph.new('l2')))
@ualpha_list = @RM::List.new(:UALPHA,
@RM::ListItem.new(nil, @RM::Paragraph.new('l1')),
@RM::ListItem.new(nil, @RM::Paragraph.new('l2')))
end
##
# Call to add the visitor tests to your test case
def self.add_visitor_tests
class_eval do
##
# Calls start_accepting which needs to verify startup state
def test_start_accepting
@to.start_accepting
start_accepting
end
##
# Calls end_accepting on your test case which needs to call
# @to.end_accepting and verify document generation
def test_end_accepting
@to.start_accepting
@to.res << 'hi'
end_accepting
end
##
# Calls accept_blank_line
def test_accept_blank_line
@to.start_accepting
@to.accept_blank_line @RM::BlankLine.new
accept_blank_line
end
##
# Calls accept_block_quote
def test_accept_block_quote
@to.start_accepting
@to.accept_block_quote block para 'quote'
accept_block_quote
end
##
# Test case that calls @to.accept_document
def test_accept_document
@to.start_accepting
@to.accept_document @RM::Document.new @RM::Paragraph.new 'hello'
accept_document
end
##
# Calls accept_heading with a level 5 RDoc::Markup::Heading
def test_accept_heading
@to.start_accepting
@to.accept_heading @RM::Heading.new(5, 'Hello')
accept_heading
end
##
# Calls accept_heading_1 with a level 1 RDoc::Markup::Heading
def test_accept_heading_1
@to.start_accepting
@to.accept_heading @RM::Heading.new(1, 'Hello')
accept_heading_1
end
##
# Calls accept_heading_2 with a level 2 RDoc::Markup::Heading
def test_accept_heading_2
@to.start_accepting
@to.accept_heading @RM::Heading.new(2, 'Hello')
accept_heading_2
end
##
# Calls accept_heading_3 with a level 3 RDoc::Markup::Heading
def test_accept_heading_3
# HACK this doesn't belong here
skip "No String#chars, upgrade your ruby" unless ''.respond_to? :chars
@to.start_accepting
@to.accept_heading @RM::Heading.new(3, 'Hello')
accept_heading_3
end
##
# Calls accept_heading_4 with a level 4 RDoc::Markup::Heading
def test_accept_heading_4
@to.start_accepting
@to.accept_heading @RM::Heading.new(4, 'Hello')
accept_heading_4
end
##
# Calls accept_heading_b with a bold level 1 RDoc::Markup::Heading
def test_accept_heading_b
@to.start_accepting
@to.accept_heading @RM::Heading.new(1, '*Hello*')
accept_heading_b
end
##
# Calls accept_heading_suppressed_crossref with a level 1
# RDoc::Markup::Heading containing a suppressed crossref
def test_accept_heading_suppressed_crossref # HACK to_html_crossref test
@to.start_accepting
@to.accept_heading @RM::Heading.new(1, '\\Hello')
accept_heading_suppressed_crossref
end
##
# Calls accept_paragraph
def test_accept_paragraph
@to.start_accepting
@to.accept_paragraph @RM::Paragraph.new('hi')
accept_paragraph
end
##
# Calls accept_paragraph_b with a RDoc::Markup::Paragraph containing
# bold words
def test_accept_paragraph_b
@to.start_accepting
@to.accept_paragraph @RM::Paragraph.new('reg bold words reg')
accept_paragraph_b
end
##
# Calls accept_paragraph_br with a RDoc::Markup::Paragraph containing
# a \
def test_accept_paragraph_br
@to.start_accepting
@to.accept_paragraph para 'one
two'
accept_paragraph_br
end
##
# Calls accept_paragraph with a Paragraph containing a hard break
def test_accept_paragraph_break
@to.start_accepting
@to.accept_paragraph para('hello', hard_break, 'world')
accept_paragraph_break
end
##
# Calls accept_paragraph_i with a RDoc::Markup::Paragraph containing
# emphasized words
def test_accept_paragraph_i
@to.start_accepting
@to.accept_paragraph @RM::Paragraph.new('reg italic words reg')
accept_paragraph_i
end
##
# Calls accept_paragraph_plus with a RDoc::Markup::Paragraph containing
# teletype words
def test_accept_paragraph_plus
@to.start_accepting
@to.accept_paragraph @RM::Paragraph.new('reg +teletype+ reg')
accept_paragraph_plus
end
##
# Calls accept_paragraph_star with a RDoc::Markup::Paragraph containing
# bold words
def test_accept_paragraph_star
@to.start_accepting
@to.accept_paragraph @RM::Paragraph.new('reg *bold* reg')
accept_paragraph_star
end
##
# Calls accept_paragraph_underscore with a RDoc::Markup::Paragraph
# containing emphasized words
def test_accept_paragraph_underscore
@to.start_accepting
@to.accept_paragraph @RM::Paragraph.new('reg _italic_ reg')
accept_paragraph_underscore
end
##
# Calls accept_verbatim with a RDoc::Markup::Verbatim
def test_accept_verbatim
@to.start_accepting
@to.accept_verbatim @RM::Verbatim.new("hi\n", " world\n")
accept_verbatim
end
##
# Calls accept_raw with a RDoc::Markup::Raw
def test_accept_raw
@to.start_accepting
@to.accept_raw @RM::Raw.new("
Name | Count", " |
---|---|
a | 1", " |
b | 2", " |
.rdoc_options
file to store
# your project default.
#
# There are a few differences between this parser and the specification. A
# best-effort was made to follow the specification as closely as possible but
# some choices to deviate were made.
#
# A future version of RDoc will warn when a MUST or MUST NOT is violated and
# may warn when a SHOULD or SHOULD NOT is violated. RDoc will always try
# to emit documentation even if given invalid TomDoc.
#
# Here are some implementation choices this parser currently makes:
#
# This parser allows rdoc-style inline markup but you should not depended on
# it.
#
# This parser allows a space between the comment and the method body.
#
# This parser does not require the default value to be described for an
# optional argument.
#
# This parser does not examine the order of sections. An Examples section may
# precede the Arguments section.
#
# This class is documented in TomDoc format. Since this is a subclass of the
# RDoc markup parser there isn't much to see here, unfortunately.
class RDoc::TomDoc < RDoc::Markup::Parser
# Internal: Token accessor
attr_reader :tokens
# Internal: Adds a post-processor which sets the RDoc section based on the
# comment's status.
#
# Returns nothing.
def self.add_post_processor # :nodoc:
RDoc::Markup::PreProcess.post_process do |comment, code_object|
next unless code_object and
RDoc::Comment === comment and comment.format == 'tomdoc'
comment.text.gsub!(/(\A\s*# )(Public|Internal|Deprecated):\s+/) do
section = code_object.add_section $2
code_object.temporary_section = section
$1
end
end
end
add_post_processor
# Public: Parses TomDoc from text
#
# text - A String containing TomDoc-format text.
#
# Examples
#
# RDoc::TomDoc.parse <<-TOMDOC
# This method does some things
#
# Returns nothing.
# TOMDOC
# # => #(Not documented) <% end %>
(Not documented) <% end %> <% if method.calls_super then %>
<%= method.markup_code %>
The page <%=h path %> was not found