require 'rdoc/context' ## # ClassModule is the base class for objects representing either a class or a # module. class RDoc::ClassModule < RDoc::Context MARSHAL_VERSION = 0 # :nodoc: ## # Constants that are aliases for this class or module attr_accessor :constant_aliases 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. def self.from_module(class_type, mod) klass = class_type.new(mod.name) klass.comment = mod.comment 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.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.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 super() 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 ## # Clears the comment. Used by the ruby parser. def clear_comment @comment = '' end ## # Appends +comment+ to the current comment, but separated by a rule. Works # more like +=. def comment= comment return if comment.empty? comment = normalize_comment comment comment = "#{@comment}\n---\n#{comment}" unless @comment.empty? super end ## # Prepares this ClassModule for use by a generator. # # See RDoc::TopLevel::complete def complete min_visibility update_aliases remove_nodoc_children update_includes remove_invisible min_visibility end ## # Iterates the ancestors of this class or module for which an # RDoc::ClassModule exists. def each_ancestor # :yields: module ancestors.each do |mod| next if String === 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 def marshal_dump # :nodoc: # TODO must store the singleton attribute attrs = attributes.sort.map do |attr| [attr.name, attr.rw] end method_types = methods_by_type.map do |type, visibilities| visibilities = visibilities.map do |visibility, methods| method_names = methods.map do |method| method.name end [visibility, method_names.uniq] end [type, visibilities] end [ MARSHAL_VERSION, @name, full_name, @superclass, parse(@comment), attrs, constants.map do |const| [const.name, parse(const.comment)] end, includes.map do |incl| [incl.name, parse(incl.comment)] end, method_types, ] end def marshal_load array # :nodoc: # TODO must restore the singleton attribute initialize_methods_etc @document_self = true @done_documenting = false @current_section = nil @parent = nil @visibility = nil @name = array[1] @full_name = array[2] @superclass = array[3] @comment = array[4] array[5].each do |name, rw| add_attribute RDoc::Attr.new(nil, name, rw, nil) end array[6].each do |name, comment| add_constant RDoc::Constant.new(name, nil, comment) end array[7].each do |name, comment| add_include RDoc::Include.new(name, comment) end array[8].each do |type, visibilities| visibilities.each do |visibility, methods| @visibility = visibility methods.each do |name| method = RDoc::AnyMethod.new nil, name method.singleton = true if type == 'class' add_method method end end end end ## # Merges +class_module+ into this ClassModule def merge class_module comment = class_module.comment if comment then document = parse @comment comment.parts.concat document.parts @comment = comment end class_module.each_attribute do |attr| if match = attributes.find { |a| a.name == attr.name } then match.rw = [match.rw, attr.rw].compact.uniq.join else add_attribute attr end end class_module.each_constant do |const| add_constant const end class_module.each_include do |incl| add_include incl end class_module.each_method do |meth| add_method meth 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 ## # Path to this class or module def path http_url RDoc::RDoc.current.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 RDoc::TopLevel.all_modules_hash[full_name] end classes_hash.each_key do |name| full_name = prefix + name classes_hash.delete name unless RDoc::TopLevel.all_classes_hash[full_name] end end ## # Get the superclass of this class. Attempts to retrieve the superclass # object, returns the name if it is not known. def superclass RDoc::TopLevel.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::TopLevel::all_modules_hash or RDoc::TopLevel::all_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 cm_alias.parent = self cm_alias.full_name = nil # force update for new parent cm_alias.aliases.clear cm_alias.is_alias_for = cm if cm.module? then RDoc::TopLevel.all_modules_hash[cm_alias.full_name] = cm_alias modules_hash[const.name] = cm_alias else RDoc::TopLevel.all_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) && RDoc::TopLevel.all_modules_hash[mod.full_name].nil? end end end