require 'rdoc/context'
##
# A TopLevel context is a representation of the contents of a single file
class RDoc::TopLevel < RDoc::Context
##
# 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
##
# Returns all classes discovered by RDoc
def self.all_classes
@all_classes_hash.values
end
##
# Returns all classes and modules discovered by RDoc
def self.all_classes_and_modules
@all_classes_hash.values + @all_modules_hash.values
end
##
# Hash of all classes known to RDoc
def self.all_classes_hash
@all_classes_hash
end
##
# All TopLevels known to RDoc
def self.all_files
@all_files_hash.values
end
##
# Hash of all files known to RDoc
def self.all_files_hash
@all_files_hash
end
##
# Returns all modules discovered by RDoc
def self.all_modules
all_modules_hash.values
end
##
# Hash of all modules known to RDoc
def self.all_modules_hash
@all_modules_hash
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 self.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 @all_classes_hash
remove_nodoc @all_modules_hash
@unique_classes = find_unique @all_classes_hash
@unique_modules = find_unique @all_modules_hash
unique_classes_and_modules.each do |cm|
cm.complete min_visibility
end
@all_files_hash.each_key do |file_name|
tl = @all_files_hash[file_name]
unless RDoc::Parser::Simple === tl.parser 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 @all_classes_hash[name]
else
tl.modules_hash[name] = cm if @all_modules_hash[name]
end
end
end
end
end
##
# Finds the class with +name+ in all discovered classes
def self.find_class_named(name)
@all_classes_hash[name]
end
##
# Finds the class with +name+ starting in namespace +from+
def self.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 self.find_class_or_module(name)
name = $' if name =~ /^::/
RDoc::TopLevel.classes_hash[name] || RDoc::TopLevel.modules_hash[name]
end
##
# Finds the file with +name+ in all discovered files
def self.find_file_named(name)
@all_files_hash[name]
end
##
# Finds the module with +name+ in all discovered modules
def self.find_module_named(name)
modules_hash[name]
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 self.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 self.fix_basic_object_inheritance
basic = all_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
##
# Creates a new RDoc::TopLevel with +file_name+ only if one with the same
# name does not exist in all_files.
def self.new file_name
if top_level = @all_files_hash[file_name] then
top_level
else
top_level = super
@all_files_hash[file_name] = top_level
top_level
end
end
##
# Removes from +all_hash+ the contexts that are nodoc or have no content.
#
# See RDoc::Context#remove_from_documentation?
def self.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
##
# Empties RDoc of stored class, module and file information
def self.reset
@all_classes_hash = {}
@all_modules_hash = {}
@all_files_hash = {}
end
##
# Returns the unique classes discovered by RDoc.
#
# ::complete must have been called prior to using this method.
def self.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 self.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 self.unique_modules
@unique_modules
end
class << self
alias classes all_classes
alias classes_hash all_classes_hash
alias files all_files
alias files_hash all_files_hash
alias modules all_modules
alias modules_hash all_modules_hash
end
reset
##
# Creates a new TopLevel for +file_name+
def initialize(file_name)
super()
@name = nil
@relative_name = file_name
@absolute_name = file_name
@file_stat = File.stat(file_name) rescue nil # HACK for testing
@diagram = nil
@parser = nil
@classes_or_modules = []
RDoc::TopLevel.files_hash[file_name] = self
end
##
# An RDoc::TopLevel is equal to another with the same absolute_name
def == other
other.class === self and @absolute_name == other.absolute_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 @absolute_name
end
alias name base_name
##
# 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
RDoc::TopLevel.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
# absolute_name
def hash
@absolute_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
##
# Returns the NormalClass "Object", creating it if not found.
#
# Records +self+ as a location in "Object".
def object_class
@object_class ||= begin
oc = self.class.find_class_named('Object') || add_class(RDoc::NormalClass, 'Object')
oc.record_location self
oc
end
end
##
# Path to this file
def path
http_url RDoc::RDoc.current.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.push(*@modules.map { |n,c| c })
q.seplist items do |mod| q.pp mod end
end
end
def to_s # :nodoc:
"file #{full_name}"
end
end