diff --git a/ChangeLog b/ChangeLog index 579dbaa712..6c0d268288 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +Mon Dec 20 12:15:32 2010 Eric Hodel + + * lib/rdoc: Import RDoc 3.0. + Mon Dec 20 01:55:03 2010 KOSAKI Motohiro * io.c (Init_IO): Added O_DIRECT. This feature was propsed by Run Paint Run Run. diff --git a/lib/rdoc.rb b/lib/rdoc.rb index 7ce7b53a35..a453ee1039 100644 --- a/lib/rdoc.rb +++ b/lib/rdoc.rb @@ -18,14 +18,16 @@ $DEBUG_RDOC = nil # == Roadmap # # * If you want to use RDoc to create documentation for your Ruby source files, -# read on. +# read the summary below, and refer to rdoc --help for command line +# usage, and RDoc::Markup for a detailed description of RDoc's markup. # * If you want to generate documentation for extensions written in C, see # RDoc::Parser::C # * If you want to drive RDoc programmatically, see RDoc::RDoc. -# * If you want to use the library to format text blocks into HTML, have a look -# at RDoc::Markup. -# * If you want to try writing your own HTML output template, see -# RDoc::Generator::HTML +# * If you want to use the library to format text blocks into HTML, look at +# RDoc::Markup. +# * If you want to make an RDoc plugin such as a generator or directive +# handler see RDoc::RDoc. +# * If you want to try writing your own output generator see RDoc::Generator. # # == Summary # @@ -50,7 +52,7 @@ $DEBUG_RDOC = nil # index page contain the documentation for the primary file. In our # case, we could type # -# % rdoc --main rdoc.rb +# % rdoc --main README.txt # # You'll find information on the various formatting tricks you can use # in comment blocks in the documentation this generates. @@ -62,281 +64,9 @@ $DEBUG_RDOC = nil # markers). If directory names are passed to RDoc, they are scanned # recursively for C and Ruby source files only. # -# == \Options -# -# rdoc can be passed a variety of command-line options. In addition, -# options can be specified via the +RDOCOPT+ environment variable, which -# functions similarly to the +RUBYOPT+ environment variable. -# -# % export RDOCOPT="-S" -# -# will make rdoc default to inline method source code. Command-line options -# always will override those in +RDOCOPT+. -# -# Run: -# -# rdoc --help -# -# for full details on rdoc's options. -# -# == 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 containing -# a --. 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 a -# ++. -# -# ## -# # 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 hyperlinked -# from comment text to their description. -# -# 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 like "\\\\\\n". -# -# == \Markup -# -# * The markup engine looks for a document's natural left margin. This is -# used as the initial margin for the document. -# -# * Consecutive lines starting at this margin are considered to be a -# paragraph. -# -# * If a paragraph starts with a "*", "-", or with ".", then it is -# taken to be the start of a list. The margin in increased to be the first -# non-space following the list start flag. Subsequent lines should be -# indented to this new margin until the list ends. For example: -# -# * this is a list with three paragraphs in -# the first item. This is the first paragraph. -# -# And this is the second paragraph. -# -# 1. This is an indented, numbered list. -# 2. This is the second item in that list -# -# This is the third conventional paragraph in the -# first list item. -# -# * This is the second item in the original list -# -# * You can also construct labeled lists, sometimes called description -# or definition lists. Do this by putting the label in square brackets -# and indenting the list body: -# -# [cat] a small furry mammal -# that seems to sleep a lot -# -# [ant] a little insect that is known -# to enjoy picnics -# -# A minor variation on labeled lists uses two colons to separate the -# label from the list body: -# -# cat:: a small furry mammal -# that seems to sleep a lot -# -# ant:: a little insect that is known -# to enjoy picnics -# -# This latter style guarantees that the list bodies' left margins are -# aligned: think of them as a two column table. -# -# * Any line that starts to the right of the current margin is treated -# as verbatim text. This is useful for code listings. The example of a -# list above is also verbatim text. -# -# * A line starting with an equals sign (=) is treated as a -# heading. Level one headings have one equals sign, level two headings -# have two,and so on. -# -# * A line starting with three or more hyphens (at the current indent) -# generates a horizontal rule. The more hyphens, the thicker the rule -# (within reason, and if supported by the output device) -# -# * You can use markup within text (except verbatim) to change the -# appearance of parts of that text. Out of the box, RDoc::Markup -# supports word-based and general markup. -# -# Word-based markup uses flag characters around individual words: -# -# [\*word*] displays word in a *bold* font -# [\_word_] displays word in an _emphasized_ font -# [\+word+] displays word in a +code+ font -# -# General markup affects text between a start delimiter and and end -# delimiter. Not surprisingly, these delimiters look like HTML markup. -# -# [\text...] displays word in a *bold* font -# [\text...] displays word in an _emphasized_ font -# [\text...] displays word in an italicized font -# [\text...\] displays word in a +code+ font -# -# 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. This only works for -# simple markup, not HTML-style markup. -# -# * Hyperlinks to the web starting http:, mailto:, ftp:, or www. are -# recognized. An HTTP url that references an external image file is -# converted into an inline \. Hyperlinks starting 'link:' are -# assumed to refer to local files whose path is relative to the --op -# directory. -# -# Hyperlinks can also be of the form label[url], in which -# case the 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]. -# -# Example hyperlinks: -# -# link:RDoc.html -# http://rdoc.rubyforge.org -# mailto:user@example.com -# {RDoc Documentation}[http://rdoc.rubyforge.org] -# {RDoc Markup}[link:RDoc/Markup.html] -# -# == Directives -# -# [+:nodoc:+ / +:nodoc:+ all] -# This directive prevents documentation for the element from -# being generated. For classes and modules, the 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 of 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 is global across all files for the class or module -# to which it applies, so use +:stopdoc:+/+:startdoc:+ to suppress -# documentation only for a particular set of methods, etc. -# -# [+: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. -# -# [+:notnew:+] -# 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:+ modifier stops this. Remember that +initialize+ is private, -# so you won't see the documentation unless you use the +-a+ command line -# option. -# -# Comment blocks can contain other directives: -# -# [:section: title] -# Starts a new section in the output. The title following +:section:+ is -# used as the section heading, and the remainder of the comment containing -# the section is used as introductory text. Subsequent methods, aliases, -# attributes, and classes will be documented in this section. 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 such as: -# -# # ---------------------------------------- -# # :section: My Section -# # This is the section that I wrote. -# # See it glisten in the noon-day sun. -# # ---------------------------------------- -# -# [+:call-seq:+] -# Lines up to the next blank line in the comment are treated as the method's -# calling sequence, overriding the default parsing of method parameters and -# yield arguments. -# -# [+:include:+ _filename_] -# \Include the contents of the named file at this point. 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). -# -# [+:enddoc:+] -# Document nothing further at the current level. -# -# [+:main:+ _name_] -# Equivalent to the --main command line parameter. -# -# [+: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 entire class or module. -# -# Further directives can be found in RDoc::Parser::Ruby and RDoc::Parser::C -# # == Other stuff # -# RDoc is currently being maintained by Eric Hodel +# RDoc is currently being maintained by Eric Hodel . # # Dave Thomas is the original author of RDoc. # @@ -345,24 +75,6 @@ $DEBUG_RDOC = nil # * The Ruby parser in rdoc/parse.rb is based heavily on the outstanding # work of Keiju ISHITSUKA of Nippon Rational Inc, who produced the Ruby # parser for irb and the rtags package. -# -# * Charset patch from MoonWolf. -# -# * Rich Kilmer wrote the kilmer.rb output template. -# -# * Dan Brickley led the design of the RDF format. -# -# == License -# -# RDoc is Copyright (c) 2001-2003 Dave Thomas, The Pragmatic Programmers. It -# is free software, and may be redistributed under the terms specified -# in the README file of the Ruby distribution. -# -# == Warranty -# -# This software is provided "as is" and without any express or implied -# warranties, including, without limitation, the implied warranties of -# merchantibility and fitness for a particular purpose. module RDoc @@ -383,7 +95,12 @@ module RDoc ## # RDoc version you are using - VERSION = '2.5.8' + VERSION = '3.0' + + ## + # Method visibilities + + VISIBILITIES = [:public, :protected, :private] ## # Name of the dotfile that contains the description of files to be processed diff --git a/lib/rdoc/alias.rb b/lib/rdoc/alias.rb index 6cd9af09d4..fa433dc0a9 100644 --- a/lib/rdoc/alias.rb +++ b/lib/rdoc/alias.rb @@ -3,41 +3,77 @@ require 'rdoc/code_object' ## # Represent an alias, which is an old_name/new_name pair associated with a # particular context +#-- +# TODO implement Alias as a proxy to a method/attribute, inheriting from +# MethodAttr class RDoc::Alias < RDoc::CodeObject ## - # Allow comments to be overridden + # Aliased method's name - attr_writer :comment + attr_reader :new_name + + alias name new_name ## - # Aliased name + # Aliasee method's name - attr_accessor :new_name + attr_reader :old_name ## - # Aliasee's name + # Is this an alias declared in a singleton context? - attr_accessor :old_name + attr_accessor :singleton ## # Source file token stream - attr_accessor :text + attr_reader :text ## # Creates a new Alias with a token stream of +text+ that aliases +old_name+ - # to +new_name+ and has +comment+ + # to +new_name+, has +comment+ and is a +singleton+ context. - def initialize(text, old_name, new_name, comment) + def initialize(text, old_name, new_name, comment, singleton = false) super() + @text = text + @singleton = singleton @old_name = old_name @new_name = new_name self.comment = comment end + ## + # Order by #singleton then #new_name + + def <=>(other) + [@singleton ? 0 : 1, new_name] <=> [other.singleton ? 0 : 1, other.new_name] + end + + ## + # HTML fragment reference for this alias + + def aref + type = singleton ? 'c' : 'i' + "#alias-#{type}-#{html_name}" + end + + ## + # Full old name including namespace + + def full_old_name + @full_name || "#{parent.name}#{pretty_old_name}" + end + + ## + # HTML id-friendly version of +#new_name+. + + def html_name + CGI.escape(@new_name.gsub('-', '-2D')).gsub('%','-').sub(/^-/, '') + end + def inspect # :nodoc: parent_name = parent ? parent.name : '(unknown)' "#<%s:0x%x %s.alias_method %s, %s>" % [ @@ -46,8 +82,31 @@ class RDoc::Alias < RDoc::CodeObject ] end + ## + # '::' for the alias of a singleton method/attribute, '#' for instance-level. + + def name_prefix + singleton ? '::' : '#' + end + + ## + # Old name with prefix '::' or '#'. + + def pretty_old_name + "#{singleton ? '::' : '#'}#{@old_name}" + end + + ## + # New name with prefix '::' or '#'. + + def pretty_new_name + "#{singleton ? '::' : '#'}#{@new_name}" + end + + alias pretty_name pretty_new_name + def to_s # :nodoc: - "alias: #{self.old_name} -> #{self.new_name}\n#{self.comment}" + "alias: #{self.new_name} -> #{self.pretty_old_name} in: #{parent}" end end diff --git a/lib/rdoc/any_method.rb b/lib/rdoc/any_method.rb index d742daa7b8..f993621f8b 100644 --- a/lib/rdoc/any_method.rb +++ b/lib/rdoc/any_method.rb @@ -1,29 +1,12 @@ -require 'rdoc/code_object' -require 'rdoc/tokenstream' +require 'rdoc/method_attr' +require 'rdoc/token_stream' ## # AnyMethod is the base class for objects representing methods -class RDoc::AnyMethod < RDoc::CodeObject +class RDoc::AnyMethod < RDoc::MethodAttr - MARSHAL_VERSION = 1 # :nodoc: - - include Comparable - - ## - # Method name - - attr_writer :name - - ## - # public, protected, private - - attr_accessor :visibility - - ## - # Parameters yielded by the called block - - attr_accessor :block_params + MARSHAL_VERSION = 0 # :nodoc: ## # Don't rename \#initialize to \::new @@ -31,76 +14,49 @@ class RDoc::AnyMethod < RDoc::CodeObject attr_accessor :dont_rename_initialize ## - # Is this a singleton method? + # Different ways to call this method - attr_accessor :singleton - - ## - # Source file token stream - - attr_reader :text - - ## - # Array of other names for this method - - attr_reader :aliases - - ## - # The method we're aliasing - - attr_accessor :is_alias_for + attr_accessor :call_seq ## # Parameters for this method attr_accessor :params - ## - # Different ways to call this method - - attr_accessor :call_seq - include RDoc::TokenStream - def initialize(text, name) - super() + ## + # Creates a new AnyMethod with a token stream +text+ and +name+ - @text = text - @name = name + def initialize text, name + super - @aliases = [] - @block_params = nil - @call_seq = nil @dont_rename_initialize = false - @is_alias_for = nil - @params = nil - @parent_name = nil - @singleton = nil @token_stream = nil - @visibility = :public end ## - # Order by #singleton then #name + # Adds +an_alias+ as an alias for this method in +context+. - def <=>(other) - [@singleton ? 0 : 1, @name] <=> [other.singleton ? 0 : 1, other.name] - end + def add_alias(an_alias, context) + method = self.class.new an_alias.text, an_alias.new_name - ## - # Adds +method+ as an alias for this method - - def add_alias(method) + method.record_location an_alias.file + method.singleton = self.singleton + method.params = self.params + method.visibility = self.visibility + method.comment = an_alias.comment + method.is_alias_for = self @aliases << method + context.add_method method + method end ## - # HTML fragment reference for this method + # Prefix for +aref+ is 'method'. - def aref - type = singleton ? 'c' : 'i' - - "method-#{type}-#{CGI.escape name}" + def aref_prefix + 'method' end ## @@ -116,30 +72,6 @@ class RDoc::AnyMethod < RDoc::CodeObject end end - ## - # HTML id-friendly method name - - def html_name - @name.gsub(/[^a-z]+/, '-') - end - - def inspect # :nodoc: - alias_for = @is_alias_for ? " (alias for #{@is_alias_for.name})" : nil - "#<%s:0x%x %s (%s)%s>" % [ - self.class, object_id, - full_name, - visibility, - alias_for, - ] - end - - ## - # Full method name including namespace - - def full_name - @full_name ||= "#{@parent ? @parent.full_name : '(unknown)'}#{pretty_name}" - end - ## # Dumps this AnyMethod for use by ri. See also #marshal_load @@ -192,12 +124,14 @@ class RDoc::AnyMethod < RDoc::CodeObject end array[8].each do |new_name, comment| - add_alias RDoc::Alias.new(nil, @name, new_name, comment) + add_alias RDoc::Alias.new(nil, @name, new_name, comment, @singleton) end end ## # Method name + # + # If the method has no assigned name, it extracts it from #call_seq. def name return @name if @name @@ -229,62 +163,5 @@ class RDoc::AnyMethod < RDoc::CodeObject params end - ## - # Name of our parent with special handling for un-marshaled methods - - def parent_name - @parent_name || super - end - - ## - # Path to this method - - def path - "#{@parent.path}##{aref}" - end - - ## - # Method name with class/instance indicator - - def pretty_name - "#{singleton ? '::' : '#'}#{@name}" - end - - def pretty_print q # :nodoc: - alias_for = @is_alias_for ? "alias for #{@is_alias_for.name}" : nil - - q.group 2, "[#{self.class.name} #{full_name} #{visibility}", "]" do - if alias_for then - q.breakable - q.text alias_for - end - - if text then - q.breakable - q.text "text:" - q.breakable - q.pp @text - end - - unless comment.empty? then - q.breakable - q.text "comment:" - q.breakable - q.pp @comment - end - end - end - - def to_s # :nodoc: - "#{self.class.name}: #{full_name} (#{@text})\n#{@comment}" - end - - ## - # Type of method (class or instance) - - def type - singleton ? 'class' : 'instance' - end - end diff --git a/lib/rdoc/attr.rb b/lib/rdoc/attr.rb index 9b8c4562c2..97fac3222d 100644 --- a/lib/rdoc/attr.rb +++ b/lib/rdoc/attr.rb @@ -1,104 +1,71 @@ -require 'rdoc/code_object' +require 'rdoc/method_attr' ## # An attribute created by \#attr, \#attr_reader, \#attr_writer or # \#attr_accessor -class RDoc::Attr < RDoc::CodeObject +class RDoc::Attr < RDoc::MethodAttr - MARSHAL_VERSION = 0 # :nodoc: + MARSHAL_VERSION = 1 # :nodoc: ## - # Name of the attribute - - attr_accessor :name - - ## - # Is the attribute readable, writable or both? + # Is the attribute readable ('R'), writable ('W') or both ('RW')? attr_accessor :rw ## - # Source file token stream + # Creates a new Attr with body +text+, +name+, read/write status +rw+ and + # +comment+. +singleton+ marks this as a class attribute. - attr_accessor :text + def initialize(text, name, rw, comment, singleton = false) + super text, name - ## - # public, protected, private - - attr_accessor :visibility - - def initialize(text, name, rw, comment) - super() - @text = text - @name = name @rw = rw - @visibility = :public + @singleton = singleton self.comment = comment end ## - # Attributes are ordered by name - - def <=>(other) - self.name <=> other.name - end - - ## - # Attributes are equal when their names and rw is identical + # 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 + self.rw == other.rw and + self.singleton == other.singleton end ## - # Returns nil, for duck typing with RDoc::AnyMethod + # Add +an_alias+ as an attribute in +context+. - def arglists + 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 ## - # Returns nil, for duck typing with RDoc::AnyMethod + # The #aref prefix for attributes - def block_params + def aref_prefix + 'attribute' end ## - # Returns nil, for duck typing with RDoc::AnyMethod + # Returns attr_reader, attr_writer or attr_accessor as appropriate. - def call_seq - end - - ## - # Partially bogus as Attr has no parent. For duck typing with - # RDoc::AnyMethod. - - def full_name - @full_name ||= "#{@parent ? @parent.full_name : '(unknown)'}##{name}" - end - - ## - # An HTML id-friendly representation of #name - - def html_name - @name.gsub(/[^a-z]+/, '-') - end - - def inspect # :nodoc: - attr = case rw - when 'RW' then :attr_accessor - when 'R' then :attr_reader - when 'W' then :attr_writer - else - " (#{rw})" - end - - "#<%s:0x%x %s.%s :%s>" % [ - self.class, object_id, - parent_name, attr, @name, - ] + def definition + case @rw + when 'RW' then 'attr_accessor' + when 'R' then 'attr_reader' + when 'W' then 'attr_writer' + end end ## @@ -111,11 +78,12 @@ class RDoc::Attr < RDoc::CodeObject @rw, @visibility, parse(@comment), + singleton, ] end ## - # Loads this AnyMethod from +array+. For a loaded AnyMethod the following + # Loads this Attr from +array+. For a loaded Attr the following # methods will return cached values: # # * #full_name @@ -127,51 +95,13 @@ class RDoc::Attr < RDoc::CodeObject @rw = array[3] @visibility = array[4] @comment = array[5] + @singleton = array[6] || false # MARSHAL_VERSION == 0 @parent_name = @full_name end - ## - # Name of our parent with special handling for un-marshaled methods - - def parent_name - @parent_name || super - end - - ## - # For duck typing with RDoc::AnyMethod, returns nil - - def params - nil - end - - ## - # URL path for this attribute - - def path - "#{@parent.path}##{@name}" - end - - ## - # For duck typing with RDoc::AnyMethod - - def singleton - false - end - def to_s # :nodoc: - "#{type} #{name}\n#{comment}" - end - - ## - # Returns attr_reader, attr_writer or attr_accessor as appropriate - - def type - case @rw - when 'RW' then 'attr_accessor' - when 'R' then 'attr_reader' - when 'W' then 'attr_writer' - end + "#{definition} #{name} in: #{parent}" end end diff --git a/lib/rdoc/class_module.rb b/lib/rdoc/class_module.rb index 16b85d7918..1e75699ffa 100644 --- a/lib/rdoc/class_module.rb +++ b/lib/rdoc/class_module.rb @@ -8,31 +8,114 @@ class RDoc::ClassModule < RDoc::Context MARSHAL_VERSION = 0 # :nodoc: - attr_accessor :diagram + ## + # 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 = 'Object') - @diagram = nil - @full_name = nil - @name = name - @superclass = superclass + def initialize(name, superclass = nil) + @constant_aliases = [] + @diagram = nil + @is_alias_for = nil + @name = name + @superclass = superclass super() end ## - # Ancestors list for this ClassModule (abstract) + # 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 - raise NotImplementedError + 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) + def comment= comment return if comment.empty? comment = "#{@comment}\n---\n#{normalize_comment comment}" unless @@ -41,10 +124,35 @@ class RDoc::ClassModule < RDoc::Context 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 + + ## + # Looks for a symbol in the #ancestors. See Context#find_local_symbol. + + def find_ancestor_local_symbol symbol + ancestors.each do |m| + next if m.is_a?(String) + 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 descendents - def find_class_named(name) + def find_class_named name return self if full_name == name return self if @name == name @@ -65,14 +173,8 @@ class RDoc::ClassModule < RDoc::Context end end - ## - # 'module' or 'class' - - def type - module? ? 'module' : 'class' - end - def marshal_dump # :nodoc: + # TODO must store the singleton attribute attrs = attributes.sort.map do |attr| [attr.name, attr.rw] end @@ -106,10 +208,13 @@ class RDoc::ClassModule < RDoc::Context 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] @@ -183,6 +288,15 @@ class RDoc::ClassModule < RDoc::Context 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 @@ -190,12 +304,52 @@ class RDoc::ClassModule < RDoc::Context 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 classe 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_from(@superclass, parent) || @superclass + RDoc::TopLevel.find_class_named(@superclass) || @superclass end ## @@ -203,12 +357,72 @@ class RDoc::ClassModule < RDoc::Context def superclass=(superclass) raise NoMethodError, "#{full_name} is a module" if module? - - @superclass = superclass if @superclass.nil? or @superclass == 'Object' + @superclass = superclass end def to_s # :nodoc: - "#{self.class}: #{full_name} #{@comment} #{super}" + 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 diff --git a/lib/rdoc/code_object.rb b/lib/rdoc/code_object.rb index f8c4e33f7e..02b2f2fcf6 100644 --- a/lib/rdoc/code_object.rb +++ b/lib/rdoc/code_object.rb @@ -12,15 +12,16 @@ require 'rdoc/text' # * RDoc::Context # * RDoc::TopLevel # * RDoc::ClassModule -# * RDoc::AnonClass +# * RDoc::AnonClass (never used so far) # * RDoc::NormalClass # * RDoc::NormalModule # * RDoc::SingleClass -# * RDoc::AnyMethod -# * RDoc::GhostMethod -# * RDoc::MetaMethod +# * RDoc::MethodAttr +# * RDoc::Attr +# * RDoc::AnyMethod +# * RDoc::GhostMethod +# * RDoc::MetaMethod # * RDoc::Alias -# * RDoc::Attr # * RDoc::Constant # * RDoc::Require # * RDoc::Include @@ -47,12 +48,17 @@ class RDoc::CodeObject ## # Are we done documenting (ie, did we come across a :enddoc:)? - attr_accessor :done_documenting + attr_reader :done_documenting + + ## + # Which file this code object was defined in + + attr_reader :file ## # Force documentation of this CodeObject - attr_accessor :force_documentation + attr_reader :force_documentation ## # Hash of arbitrary metadata for this CodeObject @@ -64,6 +70,11 @@ class RDoc::CodeObject attr_accessor :parent + ## + # Did we ever receive a +:nodoc:+ directive? + + attr_reader :received_nodoc + ## # Which section are we in @@ -80,15 +91,17 @@ class RDoc::CodeObject # Creates a new CodeObject that will document itself and its children def initialize - @metadata = {} - @comment = '' + @metadata = {} + @comment = '' + @parent = nil + @file = nil + @full_name = nil @document_children = true @document_self = true @done_documenting = false @force_documentation = false - - @parent = nil + @received_nodoc = false end ## @@ -108,28 +121,64 @@ class RDoc::CodeObject end ## - # Enables or disables documentation of this CodeObject's children. Calls - # remove_classes_and_modules when disabling. + # 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 - remove_classes_and_modules unless document_children + @document_children = document_children unless @done_documenting end ## - # Enables or disables documentation of this CodeObject. Calls - # remove_methods_etc when disabling. + # 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 - remove_methods_etc unless document_self + @received_nodoc = true if document_self.nil? end ## - # Does this class have a comment with content or is document_self false. + # Does this object have a comment with content or is #received_nodoc true? def documented? - !(@document_self and @comment.empty?) + @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 + + ## + # Force the documentation of this object unless documentation + # has been turned off by :endoc: + #-- + # 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 ## @@ -147,23 +196,19 @@ class RDoc::CodeObject end ## - # Callback called upon disabling documentation of children. See - # #document_children= + # Records the RDoc::TopLevel (file) where this code object was defined - def remove_classes_and_modules + def record_location top_level + @file = top_level end ## - # Callback called upon disabling documentation of ourself. See - # #document_self= - - def remove_methods_etc - end - - ## - # Enable capture of documentation + # Enable capture of documentation unless documentation has been + # turned off by :endoc: def start_doc + return if @done_documenting + @document_self = true @document_children = true end diff --git a/lib/rdoc/constant.rb b/lib/rdoc/constant.rb index 908990855d..056ce130be 100644 --- a/lib/rdoc/constant.rb +++ b/lib/rdoc/constant.rb @@ -5,6 +5,13 @@ require 'rdoc/code_object' class RDoc::Constant < RDoc::CodeObject + ## + # If this constant is an alias for a module or class, + # this is the RDoc::ClassModule it is an alias for. + # +nil+ otherwise. + + attr_accessor :is_alias_for + ## # The constant's name @@ -22,6 +29,7 @@ class RDoc::Constant < RDoc::CodeObject super() @name = name @value = value + @is_alias_for = nil self.comment = comment end @@ -34,17 +42,28 @@ class RDoc::Constant < RDoc::CodeObject [parent_name, name] <=> [other.parent_name, other.name] end + ## + # Constants are equal when their #parent and #name is the same + def == other self.class == other.class and @parent == other.parent and @name == other.name end + ## + # A constant is documented if it has a comment, or is an alias + # for a documented class or module. + + def documented? + super or is_alias_for && is_alias_for.documented? + end + def inspect # :nodoc: - "#<%s:0x%x %s::%s>" % [ - self.class, object_id, - parent_name, @name, - ] + "#<%s:0x%x %s::%s>" % [ + self.class, object_id, + parent_name, @name, + ] end ## @@ -54,5 +73,14 @@ class RDoc::Constant < RDoc::CodeObject "#{@parent.path}##{@name}" end + def to_s # :nodoc: + parent_name = parent ? parent.full_name : '(unknown)' + if is_alias_for + "constant #{parent_name}::#@name -> #{is_alias_for}" + else + "constant #{parent_name}::#@name" + end + end + end diff --git a/lib/rdoc/context.rb b/lib/rdoc/context.rb index d55c5a9164..c424ef1676 100644 --- a/lib/rdoc/context.rb +++ b/lib/rdoc/context.rb @@ -15,17 +15,12 @@ class RDoc::Context < RDoc::CodeObject TYPES = %w[class instance] ## - # Method visibilities - - VISIBILITIES = [:public, :protected, :private] - - ## - # Aliased methods + # Class/module aliases attr_reader :aliases ## - # attr* methods + # All attr* methods attr_reader :attributes @@ -37,7 +32,7 @@ class RDoc::Context < RDoc::CodeObject ## # Current section of documentation - attr_reader :current_section + attr_accessor :current_section ## # Files this context is found in @@ -70,19 +65,37 @@ class RDoc::Context < RDoc::CodeObject attr_reader :sections ## - # Aliases that haven't been resolved to a method + # 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 eventually be resolved. + + attr_reader :external_aliases + ## # Current visibility of this context - attr_reader :visibility + attr_accessor :visibility + + ## + # Hash of registered methods. Attributes are also registered here, + # twice if they are RW. + + attr_reader :methods_hash + + ## + # Hash of registered constants. + + attr_reader :constants_hash ## # A per-comment section of documentation like: # - # # :SECTION: The title + # # :section: The title # # The body class Section @@ -137,14 +150,12 @@ class RDoc::Context < RDoc::CodeObject end ## - # Set the comment for this section from the original comment block If + # Set 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 # - # # blah blah blah - # # - # # :SECTION: The title + # # :section: The title # # The body def set_comment(comment) @@ -169,7 +180,7 @@ class RDoc::Context < RDoc::CodeObject end ## - # Creates an unnamed empty context with public visibility + # Creates an unnamed empty context with public current visibility def initialize super @@ -184,16 +195,10 @@ class RDoc::Context < RDoc::CodeObject @current_section = Section.new self, nil, nil @sections = [@current_section] - initialize_methods_etc - initialize_classes_and_modules - end - - ## - # Sets the defaults for classes and modules - - def initialize_classes_and_modules @classes = {} @modules = {} + + initialize_methods_etc end ## @@ -206,10 +211,14 @@ class RDoc::Context < RDoc::CodeObject @requires = [] @includes = [] @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 = {} end ## @@ -222,14 +231,18 @@ class RDoc::Context < RDoc::CodeObject ## # Adds +an_alias+ that is automatically resolved - def add_alias(an_alias) - meth = find_instance_method_named(an_alias.old_name) + def add_alias an_alias + return an_alias unless @document_self - if meth then - add_alias_impl an_alias, meth + 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 @aliases, an_alias - unmatched_alias_list = @unmatched_alias_lists[an_alias.old_name] ||= [] + add_to @external_aliases, an_alias + unmatched_alias_list = + @unmatched_alias_lists[an_alias.pretty_old_name] ||= [] unmatched_alias_list.push an_alias end @@ -237,175 +250,279 @@ class RDoc::Context < RDoc::CodeObject end ## - # Turns +an_alias+ into an AnyMethod that points to +meth+ + # 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_alias_impl(an_alias, meth) - new_meth = RDoc::AnyMethod.new an_alias.text, an_alias.new_name - new_meth.is_alias_for = meth - new_meth.singleton = meth.singleton - new_meth.params = meth.params + def add_attribute attribute + return attribute unless @document_self - new_meth.comment = an_alias.comment + # mainly to check for redefinition of an attribute as a method + # TODO find a policy for 'attr_reader :foo' + 'def foo=()' + register = false - meth.add_alias new_meth + 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? + else + @methods_hash[key] = attribute + register = true + end + end - add_method new_meth + if attribute.rw.index('W') + key = attribute.pretty_name << '=' + known = @methods_hash[key] + if known then + known.comment = attribute.comment if known.comment.empty? + else + @methods_hash[key] = attribute + register = true + end + end - # aliases don't use ongoing visibility - new_meth.visibility = meth.visibility - - new_meth + if register then + attribute.visibility = @visibility + add_to @attributes, attribute + resolve_aliases attribute + end end ## - # Adds +attribute+ - - def add_attribute(attribute) - add_to @attributes, attribute - end - - ## - # Adds a class named +name+ with +superclass+. + # 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 +name+ to a class in this case. + # unless it later sees class Container. +add_class+ automatically + # upgrades +given_name+ to a class in this case. - def add_class(class_type, name, superclass = 'Object') - klass = add_class_or_module @classes, class_type, name, superclass + 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. - existing = klass.superclass - existing = existing.name if existing and not String === existing + # 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 superclass != existing and superclass != 'Object' then - klass.superclass = superclass + if full_name =~ /^(.+)::(\w+)$/ then + name = $2 + ename = $1 + enclosing = RDoc::TopLevel.classes_hash[ename] || + RDoc::TopLevel.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 = RDoc::TopLevel.classes_hash[given_name] || RDoc::TopLevel.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 - # If the parser encounters Container::Item before encountering - # Container, then it assumes that Container is a module. This may not - # be the case, so remove Container from the module list if present and - # transfer any contained classes and modules to the new class. + # 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 - mod = RDoc::TopLevel.modules_hash.delete klass.full_name + # did we believe it was a module? + mod = RDoc::TopLevel.modules_hash.delete superclass - if mod then - klass.classes_hash.update mod.classes_hash - klass.modules_hash.update mod.modules_hash - klass.method_list.concat mod.method_list + upgrade_to_class mod, RDoc::NormalClass, mod.parent if mod - @modules.delete klass.name + # e.g., Object < Object + superclass = nil if superclass == full_name end - RDoc::TopLevel.classes_hash[klass.full_name] = klass + klass = RDoc::TopLevel.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 = RDoc::TopLevel.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, + RDoc::TopLevel.classes_hash) + end + end klass end ## - # Instantiates a +class_type+ named +name+ and adds it the modules or - # classes Hash +collection+. + # 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(collection, class_type, name, superclass = nil) - full_name = child_name name + 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 = collection[name] - - if mod then - mod.superclass = superclass unless mod.module? - else - all = if class_type == RDoc::NormalModule then - RDoc::TopLevel.modules_hash - else - RDoc::TopLevel.classes_hash - end - - mod = all[full_name] - - unless mod then - mod = class_type.new name, superclass - else - # If the class has been encountered already, check that its - # superclass has been set (it may not have been, depending on the - # context in which it was encountered). - if class_type == RDoc::NormalClass then - mod.superclass = superclass unless mod.superclass - end - end - - unless @done_documenting then - all[full_name] = mod - collection[name] = mod - end - - mod.section = @current_section - mod.parent = self + 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+ + # 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) - add_to @constants, 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 + #$stderr.puts "\nconstant #{constant.name} already registered" + 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 end ## - # Adds included module +include+ + # Adds included module +include+ which should be an RDoc::Include def add_include(include) add_to @includes, include end ## - # Adds +method+ + # Adds +method+ if not already there. If it is (as method or attribute), + # updates the comment if it was empty. def add_method(method) - method.visibility = @visibility - add_to @method_list, method + return method unless @document_self - unmatched_alias_list = @unmatched_alias_lists[method.name] - if unmatched_alias_list then - unmatched_alias_list.each do |unmatched_alias| - add_alias_impl unmatched_alias, method - @aliases.delete unmatched_alias - end - - @unmatched_alias_lists.delete method.name + # HACK: avoid duplicate 'new' in io.c & struct.c (1.8.7 source code) + key = method.pretty_name + known = @methods_hash[key] + if known + # TODO issue stderr messages if --verbose + #$stderr.puts "\n#{display(method)} already registered as #{display(known)}" + known.comment = method.comment if known.comment.empty? + else + @methods_hash[key] = method + method.visibility = @visibility + add_to @method_list, method + resolve_aliases method end end ## # Adds a module named +name+. If RDoc already knows +name+ is a class then - # that class is returned instead. See also #add_class + # that class is returned instead. See also #add_class. def add_module(class_type, name) - return @classes[name] if @classes.key? name + mod = @classes[name] || @modules[name] + return mod if mod - add_class_or_module @modules, class_type, name, nil + full_name = child_name name + mod = RDoc::TopLevel.modules_hash[full_name] || class_type.new(name) + + add_class_or_module(mod, @modules, RDoc::TopLevel.modules_hash) end ## - # Adds an alias from +from+ to +name+ + # Adds an alias from +from+ (a class or module) to +name+. def add_module_alias from, name - to_name = child_name name + return from if @done_documenting - unless @done_documenting then - if from.module? then - RDoc::TopLevel.modules_hash - else - RDoc::TopLevel.classes_hash - end[to_name] = from + to_name = child_name(name) - if from.module? then - @modules - else - @classes - end[name] = from + # 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 as a class when we find + # BasicObject = BlankSlate + return from if RDoc::TopLevel.find_class_or_module(to_name) + + if from.module? then + RDoc::TopLevel.modules_hash[to_name] = from + @modules[name] = from + else + RDoc::TopLevel.classes_hash[to_name] = from + @classes[name] = from end + # HACK: register a constant for this alias: + # constant value and comment will be updated after, + # when the Ruby parser adds the constant + const = RDoc::Constant.new(name, nil, '') + const.is_alias_for = from + add_constant const + from end @@ -413,6 +530,8 @@ class RDoc::Context < RDoc::CodeObject # 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 @@ -424,22 +543,57 @@ class RDoc::Context < RDoc::CodeObject # Adds +thing+ to the collection +array+ def add_to(array, thing) - array << thing if @document_self and not @done_documenting + array << thing if @document_self thing.parent = self thing.section = @current_section end + ## + # Is there any content? + # This means any of: comment, aliases, methods, attributes, + # external aliases, require, constant. + # Includes 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.empty?) + end + ## # Creates the full name for a child with +name+ def child_name name - if RDoc::TopLevel === self then + 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 @@ -468,6 +622,14 @@ class RDoc::Context < RDoc::CodeObject @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 attributes @@ -503,11 +665,26 @@ class RDoc::Context < RDoc::CodeObject @method_list.sort.each {|m| yield m} 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) - @attributes.find { |m| m.name == 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 ## @@ -531,6 +708,27 @@ class RDoc::Context < RDoc::CodeObject 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 @@ -546,26 +744,34 @@ class RDoc::Context < RDoc::CodeObject end ## - # Finds a method, constant, attribute, module or files named +symbol+ in - # this context + # 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_instance_method_named name[1..-1] + find_method name[1..-1], false when /\A::/ then - find_class_method_named name[2..-1] + find_method name[2..-1], true else @method_list.find { |meth| meth.name == name } end @@ -582,51 +788,42 @@ class RDoc::Context < RDoc::CodeObject end ## - # Look up +symbol+. If +method+ is non-nil, then we assume the symbol - # references a module that contains that method. + # Look up +symbol+, first as a module, then as a local symbol. - def find_symbol(symbol, method = nil) + 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 /^::([A-Z].*)/ then - result = top_level.find_symbol($1) - when /::/ then - modules = symbol.split(/::/) - - unless modules.empty? then - module_name = modules.shift - result = find_module_named(module_name) - - if result then - modules.each do |name| - result = result.find_module_named name - break unless result - end - end + when /^::/ then + result = RDoc::TopLevel.find_class_or_module(symbol) + when /^(\w+):+(.+)$/ + suffix = $2 + top = $1 + searched = self + loop do + mod = searched.find_module_named(top) + break unless mod + result = RDoc::TopLevel.find_class_or_module(mod.full_name + '::' + suffix) + break if result || searched.is_a?(RDoc::TopLevel) + searched = searched.parent + end + else + searched = self + loop do + result = searched.find_module_named(symbol) + break if result || searched.is_a?(RDoc::TopLevel) + searched = searched.parent end end - unless result then - # if a method is specified, then we're definitely looking for - # a module, otherwise it could be any symbol - if method then - result = find_module_named symbol - else - result = find_local_symbol symbol - if result.nil? then - if symbol =~ /^[A-Z]/ then - result = parent - while result && result.name != symbol do - result = result.parent - end - end - end - end - end - - result = result.find_local_symbol method if result and method - result end @@ -637,27 +834,53 @@ class RDoc::Context < RDoc::CodeObject '(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 = full_name + path = name_for_path path = path.gsub(/<<\s*(\w*)/, 'from-\1') if path =~ /<#full_name by default. + + def name_for_path + full_name + end + ## # Changes the visibility for new methods to +visibility+ @@ -714,18 +933,71 @@ class RDoc::Context < RDoc::CodeObject end ## - # Record which file +top_level+ is in + # 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 ## - # If a class's documentation is turned off after we've started collecting - # methods etc., we need to remove the ones we have + # 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_methods_etc - initialize_methods_etc + 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 + + def remove_invisible_in(array, min_visibility) # :nodoc: + if min_visibility == :public + array.reject! { |e| e.visibility != :public } + else + array.reject! { |e| e.visibility == :private } + 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 + + ## + # Creates a new section with +title+ and +comment+ + + def set_current_section(title, comment) + @current_section = Section.new self, title, comment + @sections << @current_section end ## @@ -738,23 +1010,15 @@ class RDoc::Context < RDoc::CodeObject end end - ## - # Removes classes and modules when we see a :nodoc: all - - def remove_classes_and_modules - initialize_classes_and_modules - end - - ## - # Creates a new section with +title+ and +comment+ - - def set_current_section(title, comment) - @current_section = Section.new self, title, comment - @sections << @current_section + 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 @@ -763,5 +1027,20 @@ class RDoc::Context < RDoc::CodeObject @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 + + # if it was there, then we keep it even if done_documenting + RDoc::TopLevel.classes_hash[mod.full_name] = klass + enclosing.classes_hash[mod.name] = klass + + klass + end + end diff --git a/lib/rdoc/encoding.rb b/lib/rdoc/encoding.rb new file mode 100644 index 0000000000..4f0779881c --- /dev/null +++ b/lib/rdoc/encoding.rb @@ -0,0 +1,79 @@ +require 'rdoc' + +## +# This class is a wrapper around File IO and Encoding that helps RDoc load +# files and convert them to the correct encoding. + +module RDoc::Encoding + + ## + # Reads the contents of +filename+ and handles any encoding directives in + # the file. + # + # The content will be converted to the +encoding+. If the file cannot be + # converted a warning will be printed and nil will be returned. + + def self.read_file filename, encoding + content = open filename, "rb" do |f| f.read end + + utf8 = content.sub!(/\A\xef\xbb\xbf/, '') + + RDoc::Encoding.set_encoding content + + if Object.const_defined? :Encoding then + encoding ||= Encoding.default_external + orig_encoding = content.encoding + + if utf8 then + content.force_encoding Encoding::UTF_8 + content.encode! encoding + else + # assume the content is in our output encoding + content.force_encoding encoding + end + + unless content.valid_encoding? then + # revert and try to transcode + content.force_encoding orig_encoding + content.encode! encoding + end + + unless content.valid_encoding? then + warn "unable to convert #{filename} to #{encoding}, skipping" + content = nil + end + end + + content + rescue ArgumentError => e + raise unless e.message =~ /unknown encoding name - (.*)/ + warn "unknown encoding name \"#{$1}\" for #{filename}, skipping" + nil + rescue Encoding::UndefinedConversionError => e + warn "unable to convert #{e.message} for #{filename}, skipping" + nil + rescue Errno::EISDIR, Errno::ENOENT + nil + end + + ## + # Sets the encoding of +string+ based on the magic comment + + def self.set_encoding string + return unless Object.const_defined? :Encoding + + first_line = string[/\A(?:#!.*\n)?.*\n/] + + name = case first_line + when /^<\?xml[^?]*encoding=(["'])(.*?)\1/ then $2 + when /\b(?:en)?coding[=:]\s*([^\s;]+)/i then $1 + else return + end + + enc = Encoding.find name + string.force_encoding enc if enc + end + +end + + diff --git a/lib/rdoc/erbio.rb b/lib/rdoc/erbio.rb new file mode 100644 index 0000000000..04a89fbd34 --- /dev/null +++ b/lib/rdoc/erbio.rb @@ -0,0 +1,37 @@ +require 'erb' + +## +# A subclass of ERB that writes directly to an IO. Credit to Aaron Patterson +# and Masatoshi SEKI. +# +# To use: +# +# erbio = RDoc::ERBIO.new '<%= "hello world" %>', nil, nil +# +# open 'hello.txt', 'w' do |io| +# erbio.result binding +# end +# +# Note that binding must enclose the io you wish to output on. + +class RDoc::ERBIO < ERB + + ## + # Defaults +eoutvar+ to 'io', otherwise is identical to ERB's initialize + + def initialize str, safe_level = nil, trim_mode = nil, eoutvar = 'io' + super + end + + ## + # Instructs +compiler+ how to write to +io_variable+ + + def set_eoutvar compiler, io_variable + compiler.put_cmd = "#{io_variable}.write" + compiler.insert_cmd = "#{io_variable}.write" + compiler.pre_cmd = [] + compiler.post_cmd = [] + end + +end + diff --git a/lib/rdoc/generator.rb b/lib/rdoc/generator.rb index b65002977a..d02a7538c0 100644 --- a/lib/rdoc/generator.rb +++ b/lib/rdoc/generator.rb @@ -1,7 +1,39 @@ require 'rdoc' ## -# Namespace for generators +# RDoc uses generators to turn parsed source code in the form of an +# RDoc::CodeObject tree into some form of output. RDoc comes with the HTML +# generator RDoc::Generator::Darkfish and an ri data generator +# RDoc::Generator::RI. +# +# = Registering a Generator +# +# Generators are registered by calling RDoc::RDoc.add_generator with the class +# of the generator: +# +# class My::Awesome::Generator +# RDoc::RDoc.add_generator self +# end +# +# = Adding Options to +rdoc+ +# +# Before option processing in +rdoc+, RDoc::Options will call ::setup_options +# on the generator class with an RDoc::Options instance. The generator can +# use RDoc::Options#option_parser to add command-line options to the +rdoc+ +# tool. See OptionParser for details on how to add options. +# +# You can extend the RDoc::Options instance with additional accesors for your +# generator. +# +# = Generator Instantiation +# +# After parsing, RDoc::RDoc will instantiate a generator by calling +# #initialize with an RDoc::Options instance. +# +# RDoc will then call #generate on the generator instance and pass in an Array +# of RDoc::TopLevel instances, each representing a parsed file. You can use +# the various class methods on RDoc::TopLevel and in the RDoc::CodeObject tree +# to create your desired output format. module RDoc::Generator end diff --git a/lib/rdoc/generator/darkfish.rb b/lib/rdoc/generator/darkfish.rb index f64641873e..e5a6e57424 100644 --- a/lib/rdoc/generator/darkfish.rb +++ b/lib/rdoc/generator/darkfish.rb @@ -1,15 +1,12 @@ # -*- mode: ruby; ruby-indent-level: 2; tab-width: 2 -*- -# vim: noet ts=2 sts=8 sw=2 require 'pathname' require 'fileutils' -require 'erb' +require 'rdoc/erbio' require 'rdoc/generator/markup' -$DARKFISH_DRYRUN = false # TODO make me non-global - -# +## # Darkfish RDoc HTML Generator # # $Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $ @@ -52,401 +49,314 @@ $DARKFISH_DRYRUN = false # TODO make me non-global # class RDoc::Generator::Darkfish - RDoc::RDoc.add_generator( self ) + RDoc::RDoc.add_generator self - include ERB::Util + include ERB::Util - # Subversion rev - SVNRev = %$Rev: 52 $ + # Path to this file's parent directory. Used to find templates and other + # resources. - # Subversion ID - SVNId = %$Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $ + GENERATOR_DIR = File.join 'rdoc', 'generator' - # Path to this file's parent directory. Used to find templates and other - # resources. - GENERATOR_DIR = File.join 'rdoc', 'generator' + ## + # Release Version - # Release Version - VERSION = '1.1.6' + VERSION = '2' - # Directory where generated classes live relative to the root - CLASS_DIR = nil + ## + # Initialize a few instance variables before we start - # Directory where generated files live relative to the root - FILE_DIR = nil + def initialize options + @options = options + @template_dir = Pathname.new options.template_dir + @template_cache = {} - ################################################################# - ### C L A S S M E T H O D S - ################################################################# + @files = nil + @classes = nil - ### Standard generator factory method - def self::for( options ) - new( options ) - end + @basedir = Pathname.pwd.expand_path + end + ## + # The output directory - ################################################################# - ### I N S T A N C E M E T H O D S - ################################################################# + attr_reader :outputdir - ### Initialize a few instance variables before we start - def initialize( options ) - @options = options + ## + # Output progress information if debugging is enabled - template = @options.template || 'darkfish' + def debug_msg *msg + return unless $DEBUG_RDOC + $stderr.puts(*msg) + end - template_dir = $LOAD_PATH.map do |path| - File.join File.expand_path(path), GENERATOR_DIR, 'template', template - end.find do |dir| - File.directory? dir - end + ## + # Directory where generated class HTML files live relative to the output + # dir. - raise RDoc::Error, "could not find template #{template.inspect}" unless - template_dir + def class_dir + nil + end - @template_dir = Pathname.new File.expand_path(template_dir) + ## + # Directory where generated class HTML files live relative to the output + # dir. - @files = nil - @classes = nil + def file_dir + nil + end - @basedir = Pathname.pwd.expand_path - end + ## + # Create the directories the generated docs will live in if they don't + # already exist. - ###### - public - ###### + def gen_sub_directories + @outputdir.mkpath + end - # The output directory - attr_reader :outputdir + ## + # Copy over the stylesheet into the appropriate place in the output + # directory. + def write_style_sheet + debug_msg "Copying static files" + options = { :verbose => $DEBUG_RDOC, :noop => @options.dry_run } - ### Output progress information if debugging is enabled - def debug_msg( *msg ) - return unless $DEBUG_RDOC - $stderr.puts( *msg ) - end + FileUtils.cp @template_dir + 'rdoc.css', '.', options - def class_dir - CLASS_DIR - end + Dir[(@template_dir + "{js,images}/**/*").to_s].each do |path| + next if File.directory? path + next if File.basename(path) =~ /^\./ - def file_dir - FILE_DIR - end + dst = Pathname.new(path).relative_path_from @template_dir + + # I suck at glob + dst_dir = dst.dirname + FileUtils.mkdir_p dst_dir, options unless File.exist? dst_dir + + FileUtils.cp @template_dir + path, dst, options + end + end + + ## + # Build the initial indices and output objects based on an array of TopLevel + # objects containing the extracted information. + + def generate top_levels + @outputdir = Pathname.new(@options.op_dir).expand_path(@basedir) + + @files = top_levels.sort + @classes = RDoc::TopLevel.all_classes_and_modules.sort + @methods = @classes.map { |m| m.method_list }.flatten.sort + @modsort = get_sorted_module_list(@classes) + + # Now actually write the output + write_style_sheet + generate_index + generate_class_files + generate_file_files + + rescue StandardError => err + debug_msg "%s: %s\n %s" % [ + err.class.name, err.message, err.backtrace.join("\n ") + ] + + raise + end + + protected + + ## + # Return a list of the documented modules sorted by salience first, then + # by name. + + def get_sorted_module_list(classes) + nscounts = classes.inject({}) do |counthash, klass| + top_level = klass.full_name.gsub(/::.*/, '') + counthash[top_level] ||= 0 + counthash[top_level] += 1 + + counthash + end + + # Sort based on how often the top level namespace occurs, and then on the + # name of the module -- this works for projects that put their stuff into + # a namespace, of course, but doesn't hurt if they don't. + classes.sort_by do |klass| + top_level = klass.full_name.gsub( /::.*/, '' ) + [nscounts[top_level] * -1, klass.full_name] + end.select do |klass| + klass.document_self + end + end + + ## + # Generate an index page which lists all the classes which are documented. + + def generate_index + template_file = @template_dir + 'index.rhtml' + return unless template_file.exist? + + debug_msg "Rendering the index page..." + + out_file = @basedir + @options.op_dir + 'index.html' + + render_template template_file, out_file do |io| binding end + end + + ## + # Generate a documentation file for each class + + def generate_class_files + template_file = @template_dir + 'classpage.rhtml' + return unless template_file.exist? + debug_msg "Generating class documentation in #@outputdir" + + @classes.each do |klass| + debug_msg " working on %s (%s)" % [klass.full_name, klass.path] + out_file = @outputdir + klass.path + # suppress 1.9.3 warning + rel_prefix = rel_prefix = @outputdir.relative_path_from(out_file.dirname) + svninfo = svninfo = self.get_svninfo(klass) + + debug_msg " rendering #{out_file}" + render_template template_file, out_file do |io| binding end + end + end + + ## + # Generate a documentation file for each file + + def generate_file_files + template_file = @template_dir + 'filepage.rhtml' + return unless template_file.exist? + debug_msg "Generating file documentation in #@outputdir" + + @files.each do |file| + out_file = @outputdir + file.path + debug_msg " working on %s (%s)" % [ file.full_name, out_file ] + # suppress 1.9.3 warning + rel_prefix = rel_prefix = @outputdir.relative_path_from(out_file.dirname) + + debug_msg " rendering #{out_file}" + render_template template_file, out_file do |io| binding end + end + end + + ## + # Return a string describing the amount of time in the given number of + # seconds in terms a human can understand easily. + + def time_delta_string seconds + return 'less than a minute' if seconds < 60 + return "#{seconds / 60} minute#{seconds / 60 == 1 ? '' : 's'}" if + seconds < 3000 # 50 minutes + return 'about one hour' if seconds < 5400 # 90 minutes + return "#{seconds / 3600} hours" if seconds < 64800 # 18 hours + return 'one day' if seconds < 86400 # 1 day + return 'about one day' if seconds < 172800 # 2 days + return "#{seconds / 86400} days" if seconds < 604800 # 1 week + return 'about one week' if seconds < 1209600 # 2 week + return "#{seconds / 604800} weeks" if seconds < 7257600 # 3 months + return "#{seconds / 2419200} months" if seconds < 31536000 # 1 year + return "#{seconds / 31536000} years" + end + + # %q$Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $" + SVNID_PATTERN = / + \$Id:\s + (\S+)\s # filename + (\d+)\s # rev + (\d{4}-\d{2}-\d{2})\s # Date (YYYY-MM-DD) + (\d{2}:\d{2}:\d{2}Z)\s # Time (HH:MM:SSZ) + (\w+)\s # committer + \$$ + /x + + ## + # Try to extract Subversion information out of the first constant whose + # value looks like a subversion Id tag. If no matching constant is found, + # and empty hash is returned. + + def get_svninfo klass + constants = klass.constants or return {} + + constants.find { |c| c.value =~ SVNID_PATTERN } or return {} + + filename, rev, date, time, committer = $~.captures + commitdate = Time.parse "#{date} #{time}" + + return { + :filename => filename, + :rev => Integer(rev), + :commitdate => commitdate, + :commitdelta => time_delta_string(Time.now - commitdate), + :committer => committer, + } + end + + ## + # Load and render the erb template in the given +template_file+ and write + # it out to +out_file+. + # + # Both +template_file+ and +out_file+ should be Pathname-like objects. + # + # An io will be yielded which must be captured by binding in the caller. + + def render_template template_file, out_file # :yield: io + template = template_for template_file + + unless @options.dry_run then + debug_msg "Outputting to %s" % [out_file.expand_path] + + out_file.dirname.mkpath + out_file.open 'w', 0644 do |io| + io.set_encoding @options.encoding if Object.const_defined? :Encoding + + context = yield io + + template_result template, context, template_file + end + else + context = yield nil + + output = template_result template, context, template_file + + debug_msg " would have written %d characters to %s" % [ + output.length, out_file.expand_path + ] + end + end + + ## + # Creates the result for +template+ with +context+. If an error is raised a + # Pathname +template_file+ will indicate the file where the error occurred. + + def template_result template, context, template_file + template.filename = template_file.to_s + template.result context + rescue NoMethodError => e + raise RDoc::Error, "Error while evaluating %s: %s" % [ + template_file.expand_path, + e.message, + ], e.backtrace + end + + ## + # Retrieves a cache template for +file+, if present, or fills the cache. + + def template_for file + template = @template_cache[file] + + return template if template + + klass = @options.dry_run ? ERB : RDoc::ERBIO + + template = klass.new file.read, nil, '<>' + @template_cache[file] = template + template + end - ### Create the directories the generated docs will live in if - ### they don't already exist. - def gen_sub_directories - @outputdir.mkpath - end - - ### Copy over the stylesheet into the appropriate place in the output - ### directory. - def write_style_sheet - debug_msg "Copying static files" - options = { :verbose => $DEBUG_RDOC, :noop => $DARKFISH_DRYRUN } - - FileUtils.cp @template_dir + 'rdoc.css', '.', options - - Dir[(@template_dir + "{js,images}/**/*").to_s].each do |path| - next if File.directory? path - next if path =~ /#{File::SEPARATOR}\./ - - dst = Pathname.new(path).relative_path_from @template_dir - - # I suck at glob - dst_dir = dst.dirname - FileUtils.mkdir_p dst_dir, options unless File.exist? dst_dir - - FileUtils.cp @template_dir + path, dst, options - end - end - - ### Build the initial indices and output objects - ### based on an array of TopLevel objects containing - ### the extracted information. - def generate( top_levels ) - @outputdir = Pathname.new( @options.op_dir ).expand_path( @basedir ) - - @files = top_levels.sort - @classes = RDoc::TopLevel.all_classes_and_modules.sort - @methods = @classes.map { |m| m.method_list }.flatten.sort - @modsort = get_sorted_module_list( @classes ) - - # Now actually write the output - write_style_sheet - generate_index - generate_class_files - generate_file_files - - rescue StandardError => err - debug_msg "%s: %s\n %s" % [ err.class.name, err.message, err.backtrace.join("\n ") ] - raise - end - - ######### - protected - ######### - - ### Return a list of the documented modules sorted by salience first, then - ### by name. - def get_sorted_module_list( classes ) - nscounts = classes.inject({}) do |counthash, klass| - top_level = klass.full_name.gsub( /::.*/, '' ) - counthash[top_level] ||= 0 - counthash[top_level] += 1 - - counthash - end - - # Sort based on how often the top level namespace occurs, and then on the - # name of the module -- this works for projects that put their stuff into - # a namespace, of course, but doesn't hurt if they don't. - classes.sort_by do |klass| - top_level = klass.full_name.gsub( /::.*/, '' ) - [ - nscounts[ top_level ] * -1, - klass.full_name - ] - end.select do |klass| - klass.document_self - end - end - - ### Generate an index page which lists all the classes which - ### are documented. - def generate_index - template_file = @template_dir + 'index.rhtml' - return unless template_file.exist? - - debug_msg "Rendering the index page..." - - template_src = template_file.read - template = ERB.new( template_src, nil, '<>' ) - template.filename = template_file.to_s - context = binding() - - output = nil - - begin - output = template.result( context ) - rescue NoMethodError => err - raise RDoc::Error, "Error while evaluating %s: %s (at %p)" % [ - template_file, - err.message, - eval( "_erbout[-50,50]", context ) - ], err.backtrace - end - - outfile = @basedir + @options.op_dir + 'index.html' - unless $DARKFISH_DRYRUN - debug_msg "Outputting to %s" % [outfile.expand_path] - outfile.open( 'w', 0644 ) do |fh| - fh.print( output ) - end - else - debug_msg "Would have output to %s" % [outfile.expand_path] - end - end - - ### Generate a documentation file for each class - def generate_class_files - template_file = @template_dir + 'classpage.rhtml' - return unless template_file.exist? - debug_msg "Generating class documentation in #@outputdir" - - @classes.each do |klass| - debug_msg " working on %s (%s)" % [ klass.full_name, klass.path ] - outfile = @outputdir + klass.path - rel_prefix = @outputdir.relative_path_from( outfile.dirname ) - svninfo = self.get_svninfo( klass ) - - debug_msg " rendering #{outfile}" - self.render_template( template_file, binding(), outfile ) - end - end - - ### Generate a documentation file for each file - def generate_file_files - template_file = @template_dir + 'filepage.rhtml' - return unless template_file.exist? - debug_msg "Generating file documentation in #@outputdir" - - @files.each do |file| - outfile = @outputdir + file.path - debug_msg " working on %s (%s)" % [ file.full_name, outfile ] - rel_prefix = @outputdir.relative_path_from( outfile.dirname ) - - debug_msg " rendering #{outfile}" - self.render_template( template_file, binding(), outfile ) - end - end - - - ### Return a string describing the amount of time in the given number of - ### seconds in terms a human can understand easily. - def time_delta_string( seconds ) - return 'less than a minute' if seconds < 1.minute - return (seconds / 1.minute).to_s + ' minute' + (seconds/60 == 1 ? '' : 's') if seconds < 50.minutes - return 'about one hour' if seconds < 90.minutes - return (seconds / 1.hour).to_s + ' hours' if seconds < 18.hours - return 'one day' if seconds < 1.day - return 'about one day' if seconds < 2.days - return (seconds / 1.day).to_s + ' days' if seconds < 1.week - return 'about one week' if seconds < 2.week - return (seconds / 1.week).to_s + ' weeks' if seconds < 3.months - return (seconds / 1.month).to_s + ' months' if seconds < 1.year - return (seconds / 1.year).to_s + ' years' - end - - - # %q$Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $" - SVNID_PATTERN = / - \$Id:\s - (\S+)\s # filename - (\d+)\s # rev - (\d{4}-\d{2}-\d{2})\s # Date (YYYY-MM-DD) - (\d{2}:\d{2}:\d{2}Z)\s # Time (HH:MM:SSZ) - (\w+)\s # committer - \$$ - /x - - ### Try to extract Subversion information out of the first constant whose value looks like - ### a subversion Id tag. If no matching constant is found, and empty hash is returned. - def get_svninfo( klass ) - constants = klass.constants or return {} - - constants.find {|c| c.value =~ SVNID_PATTERN } or return {} - - filename, rev, date, time, committer = $~.captures - commitdate = Time.parse( date + ' ' + time ) - - return { - :filename => filename, - :rev => Integer( rev ), - :commitdate => commitdate, - :commitdelta => time_delta_string( Time.now.to_i - commitdate.to_i ), - :committer => committer, - } - end - - - ### Load and render the erb template in the given +template_file+ within the - ### specified +context+ (a Binding object) and write it out to +outfile+. - ### Both +template_file+ and +outfile+ should be Pathname-like objects. - - def render_template( template_file, context, outfile ) - template_src = template_file.read - template = ERB.new( template_src, nil, '<>' ) - template.filename = template_file.to_s - - output = begin - template.result( context ) - rescue NoMethodError => err - raise RDoc::Error, "Error while evaluating %s: %s (at %p)" % [ - template_file.to_s, - err.message, - eval( "_erbout[-50,50]", context ) - ], err.backtrace - end - - unless $DARKFISH_DRYRUN - outfile.dirname.mkpath - outfile.open( 'w', 0644 ) do |ofh| - ofh.print( output ) - end - else - debug_msg " would have written %d bytes to %s" % - [ output.length, outfile ] - end - end - -end # Roc::Generator::Darkfish - -# :stopdoc: - -### Time constants -module TimeConstantMethods # :nodoc: - - ### Number of seconds (returns receiver unmodified) - def seconds - return self - end - alias_method :second, :seconds - - ### Returns number of seconds in minutes - def minutes - return self * 60 - end - alias_method :minute, :minutes - - ### Returns the number of seconds in hours - def hours - return self * 60.minutes - end - alias_method :hour, :hours - - ### Returns the number of seconds in days - def days - return self * 24.hours - end - alias_method :day, :days - - ### Return the number of seconds in weeks - def weeks - return self * 7.days - end - alias_method :week, :weeks - - ### Returns the number of seconds in fortnights - def fortnights - return self * 2.weeks - end - alias_method :fortnight, :fortnights - - ### Returns the number of seconds in months (approximate) - def months - return self * 30.days - end - alias_method :month, :months - - ### Returns the number of seconds in years (approximate) - def years - return (self * 365.25.days).to_i - end - alias_method :year, :years - - - ### Returns the Time number of seconds before the - ### specified +time+. E.g., 2.hours.before( header.expiration ) - def before( time ) - return time - self - end - - - ### Returns the Time number of seconds ago. (e.g., - ### expiration > 2.hours.ago ) - def ago - return self.before( ::Time.now ) - end - - - ### Returns the Time number of seconds after the given +time+. - ### E.g., 10.minutes.after( header.expiration ) - def after( time ) - return time + self - end - - # Reads best without arguments: 10.minutes.from_now - def from_now - return self.after( ::Time.now ) - end -end # module TimeConstantMethods - - -# Extend Numeric with time constants -class Numeric # :nodoc: - include TimeConstantMethods end diff --git a/lib/rdoc/generator/markup.rb b/lib/rdoc/generator/markup.rb index a90b15a1e7..482fd2b2a3 100644 --- a/lib/rdoc/generator/markup.rb +++ b/lib/rdoc/generator/markup.rb @@ -36,8 +36,9 @@ module RDoc::Generator::Markup return @formatter if defined? @formatter show_hash = RDoc::RDoc.current.options.show_hash + hyperlink_all = RDoc::RDoc.current.options.hyperlink_all this = RDoc::Context === self ? self : @parent - @formatter = RDoc::Markup::ToHtmlCrossref.new this.path, this, show_hash + @formatter = RDoc::Markup::ToHtmlCrossref.new this.path, this, show_hash, hyperlink_all end ## @@ -57,36 +58,65 @@ end class RDoc::AnyMethod + ## + # Maps RDoc::RubyToken classes to CSS class names + + STYLE_MAP = { + RDoc::RubyToken::TkCONSTANT => 'ruby-constant', + RDoc::RubyToken::TkKW => 'ruby-keyword', + RDoc::RubyToken::TkIVAR => 'ruby-ivar', + RDoc::RubyToken::TkOp => 'ruby-operator', + RDoc::RubyToken::TkId => 'ruby-identifier', + RDoc::RubyToken::TkNode => 'ruby-node', + RDoc::RubyToken::TkCOMMENT => 'ruby-comment', + RDoc::RubyToken::TkREGEXP => 'ruby-regexp', + RDoc::RubyToken::TkSTRING => 'ruby-string', + RDoc::RubyToken::TkVal => 'ruby-value', + } + include RDoc::Generator::Markup + @add_line_numbers = false + + class << self + ## + # Allows controlling whether #markup_code adds line numbers to + # the source code. + + attr_accessor :add_line_numbers + end + ## # Prepend +src+ with line numbers. Relies on the first line of a source # code listing having: # - # # File xxxxx, line dddd + # # File xxxxx, line dddd + # + # If it has, line numbers are added an ', line dddd' is removed. def add_line_numbers(src) - if src =~ /\A.*, line (\d+)/ then - first = $1.to_i - 1 - last = first + src.count("\n") - size = last.to_s.length + return unless src.sub!(/\A(.*)(, line (\d+))/, '\1') + first = $3.to_i - 1 + last = first + src.count("\n") + size = last.to_s.length - line = first - src.gsub!(/^/) do - res = if line == first then - " " * (size + 2) - else - "%2$*1$d: " % [size, line] - end + line = first + src.gsub!(/^/) do + res = if line == first then + " " * (size + 1) + else + "%2$*1$d " % [size, line] + end - line += 1 - res - end + line += 1 + res end end ## - # Turns the method's token stream into HTML + # Turns the method's token stream into HTML. + # + # Prepends line numbers if +add_line_numbers+ is true. def markup_code return '' unless @token_stream @@ -95,32 +125,32 @@ class RDoc::AnyMethod @token_stream.each do |t| next unless t - # style = STYLE_MAP[t.class] - style = case t - when RDoc::RubyToken::TkCONSTANT then "ruby-constant" - when RDoc::RubyToken::TkKW then "ruby-keyword kw" - 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 cmt" - when RDoc::RubyToken::TkREGEXP then "ruby-regexp re" - when RDoc::RubyToken::TkSTRING then "ruby-value str" - when RDoc::RubyToken::TkVal then "ruby-value" - else - nil - end + + style = STYLE_MAP[t.class] text = CGI.escapeHTML t.text - if style + if style then src << "#{text}" else src << text end end - add_line_numbers src + # dedent the source + indent = src.length + lines = src.lines.to_a + lines.shift if src =~ /\A.*#\ *File/i # remove '# File' comment + lines.each do |line| + if line =~ /^ *(?=\S)/ + n = $&.length + indent = n if n < indent + break if n == 0 + end + end + src.gsub!(/^#{' ' * indent}/, '') if indent > 0 + + add_line_numbers(src) if self.class.add_line_numbers src end @@ -133,6 +163,12 @@ class RDoc::Attr end +class RDoc::Alias + + include RDoc::Generator::Markup + +end + class RDoc::Constant include RDoc::Generator::Markup diff --git a/lib/rdoc/generator/ri.rb b/lib/rdoc/generator/ri.rb index 819eb52d40..fb52997e89 100644 --- a/lib/rdoc/generator/ri.rb +++ b/lib/rdoc/generator/ri.rb @@ -8,10 +8,6 @@ class RDoc::Generator::RI RDoc::RDoc.add_generator self - def self.for options - new options - end - ## # Set up a new ri generator @@ -20,6 +16,8 @@ class RDoc::Generator::RI @store = RDoc::RI::Store.new '.' @old_siginfo = nil @current = nil + + @store.dry_run = @options.dry_run end ## diff --git a/lib/rdoc/generator/template/darkfish/classpage.rhtml b/lib/rdoc/generator/template/darkfish/classpage.rhtml index 7151087988..72b86ec6a7 100644 --- a/lib/rdoc/generator/template/darkfish/classpage.rhtml +++ b/lib/rdoc/generator/template/darkfish/classpage.rhtml @@ -1,296 +1,289 @@ "?> + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + - <%= klass.type.capitalize %>: <%= klass.full_name %> + <%= klass.type.capitalize %>: <%= klass.full_name %> - + - - - - + + + + -
-
-
+
+ +
+
-
-
-

In Files

-
- -
-
+
+
+

In Files

+
+ +
+
- <% if !svninfo.empty? %> -
-

Subversion Info

-
-
-
Rev
-
<%= svninfo[:rev] %>
+ <% if !svninfo.empty? %> +
+

Subversion Info

+
+
+
Rev
+
<%= svninfo[:rev] %>
-
Last Checked In
-
<%= svninfo[:commitdate].strftime('%Y-%m-%d %H:%M:%S') %> - (<%= svninfo[:commitdelta] %> ago)
+
Last Checked In
+
<%= svninfo[:commitdate].strftime('%Y-%m-%d %H:%M:%S') %> + (<%= svninfo[:commitdelta] %> ago)
-
Checked in by
-
<%= svninfo[:committer] %>
-
-
-
- <% end %> -
+
Checked in by
+
<%= svninfo[:committer] %>
+ +
+
+ <% end %> +
-
+
- - <% if klass.type == 'class' %> -
-

Parent

- <% unless String === klass.superclass %> - - <% else %> - - <% end %> -
- <% end %> + + <% if klass.type == 'class' %> +
+

Parent

+ <% if klass.superclass and not String === klass.superclass then %> + + <% else %> + + <% end %> +
+ <% end %> - - <% unless klass.classes_and_modules.empty? %> -
-

Namespace

- -
- <% end %> + + <% unless klass.classes_and_modules.empty? %> +
+

Namespace

+ +
+ <% end %> - - <% unless klass.method_list.empty? %> -
-

Methods

- -
- <% end %> + + <% unless klass.method_list.empty? %> +
+

Methods

+ +
+ <% end %> - - <% unless klass.includes.empty? %> -
-

Included Modules

- -
- <% end %> -
+ + <% unless klass.includes.empty? %> +
+

Included Modules

+ +
+ <% end %> +
-
- <% simple_files = @files.select {|tl| tl.parser == RDoc::Parser::Simple } %> - <% unless simple_files.empty? then %> -
-

Files

- -
- <% end %> +
+ <% simple_files = @files.select {|tl| tl.parser == RDoc::Parser::Simple } %> + <% unless simple_files.empty? then %> +
+

Files

+ +
+ <% end %> -
-

Class Index - [+]

-
-
- Quicksearch - -
-
+
+

Class Index + [+]

+
+
+ Quicksearch + +
+
- - -
+ + +
- <% if $DEBUG_RDOC %> -
toggle debugging
- <% end %> -
-
+ <% if $DEBUG_RDOC %> +
toggle debugging
+ <% end %> +
+
-
-

<%= klass.full_name %>

+
+

<%= klass.full_name %>

-
- <%= klass.description %> -
+
+ <%= klass.description %> +
- - <% unless klass.constants.empty? %> -
-

Constants

-
- <% klass.each_constant do |const| %> -
<%= const.name %>
- <% if const.comment %> -
<%= const.description.strip %>
- <% else %> -
(Not documented)
- <% end %> - <% end %> -
-
- <% end %> + + <% unless klass.constants.empty? %> +
+

Constants

+
+ <% klass.each_constant do |const| %> +
<%= const.name %>
+ <% if const.comment %> +
<%= const.description.strip %>
+ <% else %> +
(Not documented)
+ <% end %> + <% end %> +
+
+ <% end %> - - <% unless klass.attributes.empty? %> -
-

Attributes

+ + <% unless klass.attributes.empty? %> +
+

Attributes

- <% klass.each_attribute do |attrib| %> -
- - <% if attrib.rw =~ /w/i %> - - <% end %> -
- <%= h attrib.name %>[<%= attrib.rw %>] -
+ <% klass.each_attribute do |attrib| %> +
+ + <% if attrib.rw =~ /w/i %> + + <% end %> +
+ <%= h attrib.name %>[<%= attrib.rw %>] +
-
- <% if attrib.comment %> - <%= attrib.description.strip %> - <% else %> -

(Not documented)

- <% end %> -
-
- <% end %> -
- <% end %> +
+ <% if attrib.comment %> + <%= attrib.description.strip %> + <% else %> +

(Not documented)

+ <% end %> +
+
+ <% end %> +
+ <% end %> - - <% klass.methods_by_type.each do |type, visibilities| - next if visibilities.empty? - visibilities.each do |visibility, methods| - next if methods.empty? %> -
-

<%= visibility.to_s.capitalize %> <%= type.capitalize %> Methods

+ + <% klass.methods_by_type.each do |type, visibilities| + next if visibilities.empty? + visibilities.each do |visibility, methods| + next if methods.empty? %> +
+

<%= visibility.to_s.capitalize %> <%= type.capitalize %> Methods

- <% methods.each do |method| %> -
"> - + <% methods.each do |method| %> +
"> + -
- <% if method.call_seq %> - <%= method.call_seq.strip.gsub(/->/, '→').gsub( /^\w.+\./m, '') %> - click to toggle source - <% else %> - <%= h method.name %><%= method.params %> - click to toggle source - <% end %> -
+
+ <% if method.call_seq %> + <%= method.call_seq.strip.gsub(/->/, '→').gsub( /^\w.+\./m, '') %> + click to toggle source + <% else %> + <%= h method.name %><%= method.params %> + click to toggle source + <% end %> +
-
- <% if method.comment %> - <%= method.description.strip %> - <% else %> -

(Not documented)

- <% end %> +
+ <% if method.comment %> + <%= method.description.strip %> + <% else %> +

(Not documented)

+ <% end %> - <% if method.token_stream %> -
+ <% if method.token_stream %> +
 <%= method.markup_code %>
 
-
- <% end %> -
+
+ <% end %> +
- <% unless method.aliases.empty? %> -
- Also aliased as: <%= method.aliases.map do |aka| - %{#{h aka.name}} - end.join(", ") %> -
- <% end %> + <% unless method.aliases.empty? %> +
+ Also aliased as: <%= method.aliases.map do |aka| + if aka.parent then # HACK lib/rexml/encodings + %{#{h aka.name}} + else + h aka.name + end + end.join ", " %> +
+ <% end %> - <% if method.is_alias_for then %> -
+ <% if method.is_alias_for then %> + - <% end %> -
+
+ <% end %> +
- <% end %> -
- <% end - end %> + <% end %> +
+ <% end + end %> -
+
- -
- <% if $DEBUG_RDOC - require 'pp' %> -
<%= h PP.pp(klass, _erbout) %>
-
- <% else %> -

Disabled; run with --debug to generate this.

- <% end %> - - -
-

[Validate]

-

Generated with the Darkfish - Rdoc Generator <%= RDoc::Generator::Darkfish::VERSION %>.

-
+
+

[Validate]

+

Generated with the Darkfish + Rdoc Generator <%= RDoc::Generator::Darkfish::VERSION %>.

+
diff --git a/lib/rdoc/generator/template/darkfish/filepage.rhtml b/lib/rdoc/generator/template/darkfish/filepage.rhtml index 33216dc8f1..b230a456a3 100644 --- a/lib/rdoc/generator/template/darkfish/filepage.rhtml +++ b/lib/rdoc/generator/template/darkfish/filepage.rhtml @@ -1,123 +1,123 @@ + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - + - File: <%= file.base_name %> [<%= @options.title %>] + File: <%= file.base_name %> [<%= @options.title %>] - + - - - - + + + + <% if file.parser == RDoc::Parser::Simple %> -
-
-
+
+ +
+
-
- <% simple_files = @files.select { |f| f.parser == RDoc::Parser::Simple } %> - <% unless simple_files.empty? then %> -
-

Files

- -
- <% end %> +
+ <% simple_files = @files.select { |f| f.parser == RDoc::Parser::Simple } %> + <% unless simple_files.empty? then %> +
+

Files

+ +
+ <% end %> -
-

Class Index - [+]

-
-
- Quicksearch - -
-
+
+

Class Index + [+]

+
+
+ Quicksearch + +
+
- - -
+ + +
- <% if $DEBUG_RDOC %> -
toggle debugging
- <% end %> -
-
+ <% if $DEBUG_RDOC %> +
toggle debugging
+ <% end %> +
+
-
- <%= file.description %> -
+
+ <%= file.description %> +
-
-

[Validate]

-

Generated with the Darkfish - Rdoc Generator <%= RDoc::Generator::Darkfish::VERSION %>.

-
+
+

[Validate]

+

Generated with the Darkfish + Rdoc Generator <%= RDoc::Generator::Darkfish::VERSION %>.

+
<% else %> -
-
-
Last Modified
-
<%= file.last_modified %>
+
+
+
Last Modified
+
<%= file.last_modified %>
- <% if file.requires %> -
Requires
-
-
    - <% file.requires.each do |require| %> -
  • <%= require.name %>
  • - <% end %> -
-
- <% end %> + <% if file.requires %> +
Requires
+
+
    + <% file.requires.each do |require| %> +
  • <%= require.name %>
  • + <% end %> +
+
+ <% end %> - <% if @options.webcvs %> -
Trac URL
-
<%= file.cvs_url %>
- <% end %> -
-
+ <% if @options.webcvs %> +
Trac URL
+
<%= file.cvs_url %>
+ <% end %> +
+
-
- <% if file.comment %> -
-

Description

- <%= file.description %> -
- <% end %> -
+
+ <% if file.comment %> +
+

Description

+ <%= file.description %> +
+ <% end %> +
<% end %> diff --git a/lib/rdoc/generator/template/darkfish/index.rhtml b/lib/rdoc/generator/template/darkfish/index.rhtml index e853235ddb..3198246f8a 100644 --- a/lib/rdoc/generator/template/darkfish/index.rhtml +++ b/lib/rdoc/generator/template/darkfish/index.rhtml @@ -1,64 +1,64 @@ + "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> - + - <%= h @options.title %> + <%= h @options.title %> - + - - - - + + + + - <% $stderr.sync = true %> -

<%= h @options.title %>

+ <% $stderr.sync = true %> +

<%= h @options.title %>

- <% if @options.main_page && main_page = @files.find { |f| f.full_name == @options.main_page } %> -
- <%= main_page.description.sub(%r{^\s*}i, '') %> -
- <% else %> -

This is the API documentation for '<%= @options.title %>'.

- <% end %> + <% if @options.main_page && main_page = @files.find { |f| f.full_name == @options.main_page } then %> +
+ <%= main_page.description.sub(%r{^\s*}i, '') %> +
+ <% else %> +

This is the API documentation for '<%= @options.title %>'.

+ <% end %> - <% simple_files = @files.select {|tl| tl.parser == RDoc::Parser::Simple } %> - <% unless simple_files.empty? then %> -

Files

- - <% end %> + <% simple_files = @files.select {|tl| tl.parser == RDoc::Parser::Simple } %> + <% unless simple_files.empty? then %> +

Files

+ + <% end %> -

Classes/Modules

- +

Classes/Modules

+ -

Methods

- +

Methods

+ -
-

[Validate]

-

Generated with the Darkfish - Rdoc Generator <%= RDoc::Generator::Darkfish::VERSION %>.

-
+
+

[Validate]

+

Generated with the Darkfish + Rdoc Generator <%= RDoc::Generator::Darkfish::VERSION %>.

+
diff --git a/lib/rdoc/generator/template/darkfish/rdoc.css b/lib/rdoc/generator/template/darkfish/rdoc.css index ffe996001a..231f9b7f04 100644 --- a/lib/rdoc/generator/template/darkfish/rdoc.css +++ b/lib/rdoc/generator/template/darkfish/rdoc.css @@ -12,76 +12,76 @@ body { background: #efefef; - font: 14px "Helvetica Neue", Helvetica, Tahoma, sans-serif; + font: 14px "Helvetica Neue", Helvetica, Tahoma, sans-serif; } body.class, body.module, body.file { - margin-left: 40px; + margin-left: 40px; } body.file-popup { - font-size: 90%; - margin-left: 0; + font-size: 90%; + margin-left: 0; } h1 { - font-size: 300%; - text-shadow: rgba(135,145,135,0.65) 2px 2px 3px; - color: #6C8C22; + font-size: 300%; + text-shadow: rgba(135,145,135,0.65) 2px 2px 3px; + color: #6C8C22; } h2,h3,h4 { margin-top: 1.5em; } :link, :visited { - color: #6C8C22; - text-decoration: none; + color: #6C8C22; + text-decoration: none; } :link:hover, :visited:hover { - border-bottom: 1px dotted #6C8C22; + border-bottom: 1px dotted #6C8C22; } pre { - background: #ddd; - padding: 0.5em 0; + background: #ddd; + padding: 0.5em 0; } /* @group Generic Classes */ .initially-hidden { - display: none; + display: none; } .quicksearch-field { - width: 98%; - background: #ddd; - border: 1px solid #aaa; - height: 1.5em; - -webkit-border-radius: 4px; + width: 98%; + background: #ddd; + border: 1px solid #aaa; + height: 1.5em; + -webkit-border-radius: 4px; } .quicksearch-field:focus { - background: #f1edba; + background: #f1edba; } .missing-docs { - font-size: 120%; - background: white url(images/wrench_orange.png) no-repeat 4px center; - color: #ccc; - line-height: 2em; - border: 1px solid #d00; - opacity: 1; - padding-left: 20px; - text-indent: 24px; - letter-spacing: 3px; - font-weight: bold; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; + font-size: 120%; + background: white url(images/wrench_orange.png) no-repeat 4px center; + color: #ccc; + line-height: 2em; + border: 1px solid #d00; + opacity: 1; + padding-left: 20px; + text-indent: 24px; + letter-spacing: 3px; + font-weight: bold; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; } .target-section { - border: 2px solid #dcce90; - border-left-width: 8px; - padding: 0 1em; - background: #fff3c2; + border: 2px solid #dcce90; + border-left-width: 8px; + padding: 0 1em; + background: #fff3c2; } /* @end */ @@ -89,37 +89,37 @@ pre { /* @group Index Page, Standalone file pages */ body.indexpage { - margin: 1em 3em; + margin: 1em 3em; } body.indexpage p, body.indexpage div, body.file p { - margin: 1em 0; + margin: 1em 0; } .indexpage ul, .file #documentation ul { - line-height: 160%; - list-style: none; + line-height: 160%; + list-style: none; } .indexpage ul :link, .indexpage ul :visited { - font-size: 16px; + font-size: 16px; } .indexpage li, .file #documentation li { - padding-left: 20px; - background: url(images/bullet_black.png) no-repeat left 4px; + padding-left: 20px; + background: url(images/bullet_black.png) no-repeat left 4px; } .indexpage li.module { - background: url(images/package.png) no-repeat left 4px; + background: url(images/package.png) no-repeat left 4px; } .indexpage li.class { - background: url(images/ruby.png) no-repeat left 4px; + background: url(images/ruby.png) no-repeat left 4px; } .indexpage li.file { - background: url(images/page_white_text.png) no-repeat left 4px; + background: url(images/page_white_text.png) no-repeat left 4px; } .file li p, .indexpage li p { @@ -133,48 +133,48 @@ body.file p { .class #metadata, .file #metadata, .module #metadata { - float: left; - width: 260px; + float: left; + width: 260px; } .class #documentation, .file #documentation, .module #documentation { - margin: 2em 1em 5em 300px; - min-width: 340px; + margin: 2em 1em 5em 300px; + min-width: 340px; } .file #metadata { - margin: 0.8em; + margin: 0.8em; } #validator-badges { - clear: both; - margin: 1em 1em 2em; + clear: both; + margin: 1em 1em 2em; } /* @end */ /* @group Metadata Section */ #metadata .section { - background-color: #dedede; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - border: 1px solid #aaa; - margin: 0 8px 16px; - font-size: 90%; - overflow: hidden; + background-color: #dedede; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border: 1px solid #aaa; + margin: 0 8px 16px; + font-size: 90%; + overflow: hidden; } #metadata h3.section-header { - margin: 0; - padding: 2px 8px; - background: #ccc; - color: #666; - -moz-border-radius-topleft: 4px; - -moz-border-radius-topright: 4px; - -webkit-border-top-left-radius: 4px; - -webkit-border-top-right-radius: 4px; - border-bottom: 1px solid #aaa; + margin: 0; + padding: 2px 8px; + background: #ccc; + color: #666; + -moz-border-radius-topleft: 4px; + -moz-border-radius-topright: 4px; + -webkit-border-top-left-radius: 4px; + -webkit-border-top-right-radius: 4px; + border-bottom: 1px solid #aaa; } #metadata #home-section h3.section-header { border-bottom: 0; @@ -183,33 +183,33 @@ body.file p { #metadata ul, #metadata dl, #metadata p { - padding: 8px; - list-style: none; + padding: 8px; + list-style: none; } #file-metadata ul { - padding-left: 28px; - list-style-image: url(images/page_green.png); + padding-left: 28px; + list-style-image: url(images/page_green.png); } dl.svninfo { - color: #666; - margin: 0; + color: #666; + margin: 0; } dl.svninfo dt { - font-weight: bold; + font-weight: bold; } ul.link-list li { - white-space: nowrap; + white-space: nowrap; } ul.link-list .type { - font-size: 8px; - text-transform: uppercase; - color: white; - background: #969696; - padding: 2px 4px; - -webkit-border-radius: 5px; + font-size: 8px; + text-transform: uppercase; + color: white; + background: #969696; + padding: 2px 4px; + -webkit-border-radius: 5px; } /* @end */ @@ -217,7 +217,7 @@ ul.link-list .type { /* @group Project Metadata Section */ #project-metadata { - margin-top: 3em; + margin-top: 3em; } .file #project-metadata { @@ -225,34 +225,34 @@ ul.link-list .type { } #project-metadata .section { - border: 1px solid #aaa; + border: 1px solid #aaa; } #project-metadata h3.section-header { - border-bottom: 1px solid #aaa; - position: relative; + border-bottom: 1px solid #aaa; + position: relative; } #project-metadata h3.section-header .search-toggle { - position: absolute; - right: 5px; + position: absolute; + right: 5px; } #project-metadata form { - color: #777; - background: #ccc; - padding: 8px 8px 16px; - border-bottom: 1px solid #bbb; + color: #777; + background: #ccc; + padding: 8px 8px 16px; + border-bottom: 1px solid #bbb; } #project-metadata fieldset { - border: 0; + border: 0; } #no-class-search-results { - margin: 0 auto 1em; - text-align: center; - font-size: 14px; - font-weight: bold; - color: #aaa; + margin: 0 auto 1em; + text-align: center; + font-size: 14px; + font-weight: bold; + color: #aaa; } /* @end */ @@ -260,12 +260,12 @@ ul.link-list .type { /* @group Documentation Section */ #description { - font-size: 100%; - color: #333; + font-size: 100%; + color: #333; } #description p { - margin: 1em 0.4em; + margin: 1em 0.4em; } #description li p { @@ -273,152 +273,152 @@ ul.link-list .type { } #description ul { - margin-left: 1.5em; + margin-left: 1.5em; } #description ul li { - line-height: 1.4em; + line-height: 1.4em; } #description dl, #documentation dl { - margin: 8px 1.5em; - border: 1px solid #ccc; + margin: 8px 1.5em; + border: 1px solid #ccc; } #description dl { - font-size: 14px; + font-size: 14px; } #description dt, #documentation dt { - padding: 2px 4px; - font-weight: bold; - background: #ddd; + padding: 2px 4px; + font-weight: bold; + background: #ddd; } #description dd, #documentation dd { - padding: 2px 12px; + padding: 2px 12px; } #description dd + dt, #documentation dd + dt { - margin-top: 0.7em; + margin-top: 0.7em; } #documentation .section { - font-size: 90%; + font-size: 90%; } #documentation h3.section-header { - margin-top: 2em; - padding: 0.75em 0.5em; - background-color: #dedede; - color: #333; - font-size: 150%; - border: 1px solid #bbb; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; + margin-top: 2em; + padding: 0.75em 0.5em; + background-color: #dedede; + color: #333; + font-size: 150%; + border: 1px solid #bbb; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; } #constants-list > dl, #attributes-list > dl { - margin: 1em 0 2em; - border: 0; + margin: 1em 0 2em; + border: 0; } #constants-list > dl dt, #attributes-list > dl dt { - padding-left: 0; - font-weight: bold; - font-family: Monaco, "Andale Mono"; - background: inherit; + padding-left: 0; + font-weight: bold; + font-family: Monaco, "Andale Mono"; + background: inherit; } #constants-list > dl dt a, #attributes-list > dl dt a { - color: inherit; + color: inherit; } #constants-list > dl dd, #attributes-list > dl dd { - margin: 0 0 1em 0; - padding: 0; - color: #666; + margin: 0 0 1em 0; + padding: 0; + color: #666; } /* @group Method Details */ #documentation .method-source-code { - display: none; + display: none; } #documentation .method-detail { - margin: 0.5em 0; - padding: 0.5em 0; - cursor: pointer; + margin: 0.5em 0; + padding: 0.5em 0; + cursor: pointer; } #documentation .method-detail:hover { - background-color: #f1edba; + background-color: #f1edba; } #documentation .method-heading { - position: relative; - padding: 2px 4px 0 20px; - font-size: 125%; - font-weight: bold; - color: #333; - background: url(images/brick.png) no-repeat left bottom; + position: relative; + padding: 2px 4px 0 20px; + font-size: 125%; + font-weight: bold; + color: #333; + background: url(images/brick.png) no-repeat left bottom; } #documentation .method-heading :link, #documentation .method-heading :visited { - color: inherit; + color: inherit; } #documentation .method-click-advice { - position: absolute; - top: 2px; - right: 5px; - font-size: 10px; - color: #9b9877; - visibility: hidden; - padding-right: 20px; - line-height: 20px; - background: url(images/zoom.png) no-repeat right top; + position: absolute; + top: 2px; + right: 5px; + font-size: 10px; + color: #9b9877; + visibility: hidden; + padding-right: 20px; + line-height: 20px; + background: url(images/zoom.png) no-repeat right top; } #documentation .method-detail:hover .method-click-advice { - visibility: visible; + visibility: visible; } #documentation .method-alias .method-heading { - color: #666; - background: url(images/brick_link.png) no-repeat left bottom; + color: #666; + background: url(images/brick_link.png) no-repeat left bottom; } #documentation .method-description, #documentation .aliases { - margin: 0 20px; - line-height: 1.2em; - color: #666; + margin: 0 20px; + line-height: 1.2em; + color: #666; } #documentation .aliases { - padding-top: 4px; - font-style: italic; - cursor: default; + padding-top: 4px; + font-style: italic; + cursor: default; } #documentation .method-description p { - padding: 0; + padding: 0; } #documentation .method-description p + p { - margin-bottom: 0.5em; + margin-bottom: 0.5em; } #documentation .method-description ul { margin-left: 1.5em; } #documentation .attribute-method-heading { - background: url(images/tag_green.png) no-repeat left bottom; + background: url(images/tag_green.png) no-repeat left bottom; } #documentation #attribute-method-details .method-detail:hover { - background-color: transparent; - cursor: default; + background-color: transparent; + cursor: default; } #documentation .attribute-access-type { - font-size: 60%; - text-transform: uppercase; - vertical-align: super; - padding: 0 2px; + font-size: 60%; + text-transform: uppercase; + vertical-align: super; + padding: 0 2px; } /* @end */ @@ -429,19 +429,19 @@ ul.link-list .type { /* @group Source Code */ div.method-source-code { - background: #262626; - color: #efefef; - margin: 1em; - padding: 0.5em; - border: 1px dashed #999; - overflow: hidden; + background: #262626; + color: #efefef; + margin: 1em; + padding: 0.5em; + border: 1px dashed #999; + overflow: hidden; } div.method-source-code pre { - background: inherit; - padding: 0; - color: white; - overflow: auto; + background: inherit; + padding: 0; + color: white; + overflow: auto; } /* @group Ruby keyword styles */ @@ -467,51 +467,51 @@ div.method-source-code pre { } .file-popup dl { - font-size: 80%; - padding: 0.75em; - background-color: #dedede; - color: #333; - border: 1px solid #bbb; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; + font-size: 80%; + padding: 0.75em; + background-color: #dedede; + color: #333; + border: 1px solid #bbb; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; } .file dt { - font-weight: bold; - padding-left: 22px; - line-height: 20px; - background: url(images/page_white_width.png) no-repeat left top; + font-weight: bold; + padding-left: 22px; + line-height: 20px; + background: url(images/page_white_width.png) no-repeat left top; } .file dt.modified-date { - background: url(images/date.png) no-repeat left top; + background: url(images/date.png) no-repeat left top; } .file dt.requires { - background: url(images/plugin.png) no-repeat left top; + background: url(images/plugin.png) no-repeat left top; } .file dt.scs-url { - background: url(images/wrench.png) no-repeat left top; + background: url(images/wrench.png) no-repeat left top; } .file dl dd { - margin: 0 0 1em 0; + margin: 0 0 1em 0; } .file #metadata dl dd ul { - list-style: circle; - margin-left: 20px; - padding-top: 0; + list-style: circle; + margin-left: 20px; + padding-top: 0; } .file #metadata dl dd ul li { } .file h2 { - margin-top: 2em; - padding: 0.75em 0.5em; - background-color: #dedede; - color: #333; - font-size: 120%; - border: 1px solid #bbb; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; + margin-top: 2em; + padding: 0.75em 0.5em; + background-color: #dedede; + color: #333; + font-size: 120%; + border: 1px solid #bbb; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; } /* @end */ @@ -521,13 +521,13 @@ div.method-source-code pre { /* @group ThickBox Styles */ #TB_window { - font: 12px Arial, Helvetica, sans-serif; - color: #333333; + font: 12px Arial, Helvetica, sans-serif; + color: #333333; } #TB_secondLine { - font: 10px Arial, Helvetica, sans-serif; - color:#666666; + font: 10px Arial, Helvetica, sans-serif; + color:#666666; } #TB_window :link, @@ -540,147 +540,147 @@ div.method-source-code pre { #TB_window :visited:focus { color: #666666; } #TB_overlay { - position: fixed; - z-index:100; - top: 0px; - left: 0px; - height:100%; - width:100%; + position: fixed; + z-index:100; + top: 0px; + left: 0px; + height:100%; + width:100%; } .TB_overlayMacFFBGHack {background: url(images/macFFBgHack.png) repeat;} .TB_overlayBG { - background-color:#000; - filter:alpha(opacity=75); - -moz-opacity: 0.75; - opacity: 0.75; + background-color:#000; + filter:alpha(opacity=75); + -moz-opacity: 0.75; + opacity: 0.75; } * html #TB_overlay { /* ie6 hack */ - position: absolute; - height: expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px'); + position: absolute; + height: expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px'); } #TB_window { - position: fixed; - background: #ffffff; - z-index: 102; - color:#000000; - display:none; - border: 4px solid #525252; - text-align:left; - top:50%; - left:50%; + position: fixed; + background: #ffffff; + z-index: 102; + color:#000000; + display:none; + border: 4px solid #525252; + text-align:left; + top:50%; + left:50%; } * html #TB_window { /* ie6 hack */ -position: absolute; -margin-top: expression(0 - parseInt(this.offsetHeight / 2) + (TBWindowMargin = document.documentElement && document.documentElement.scrollTop || document.body.scrollTop) + 'px'); + position: absolute; + margin-top: expression(0 - parseInt(this.offsetHeight / 2) + (TBWindowMargin = document.documentElement && document.documentElement.scrollTop || document.body.scrollTop) + 'px'); } #TB_window img#TB_Image { - display:block; - margin: 15px 0 0 15px; - border-right: 1px solid #ccc; - border-bottom: 1px solid #ccc; - border-top: 1px solid #666; - border-left: 1px solid #666; + display:block; + margin: 15px 0 0 15px; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; + border-top: 1px solid #666; + border-left: 1px solid #666; } #TB_caption{ - height:25px; - padding:7px 30px 10px 25px; - float:left; + height:25px; + padding:7px 30px 10px 25px; + float:left; } #TB_closeWindow{ - height:25px; - padding:11px 25px 10px 0; - float:right; + height:25px; + padding:11px 25px 10px 0; + float:right; } #TB_closeAjaxWindow{ - padding:7px 10px 5px 0; - margin-bottom:1px; - text-align:right; - float:right; + padding:7px 10px 5px 0; + margin-bottom:1px; + text-align:right; + float:right; } #TB_ajaxWindowTitle{ - float:left; - padding:7px 0 5px 10px; - margin-bottom:1px; - font-size: 22px; + float:left; + padding:7px 0 5px 10px; + margin-bottom:1px; + font-size: 22px; } #TB_title{ - background-color: #6C8C22; - color: #dedede; - height:40px; + background-color: #6C8C22; + color: #dedede; + height:40px; } #TB_title :link, #TB_title :visited { - color: white !important; - border-bottom: 1px dotted #dedede; + color: white !important; + border-bottom: 1px dotted #dedede; } #TB_ajaxContent{ - clear:both; - padding:2px 15px 15px 15px; - overflow:auto; - text-align:left; - line-height:1.4em; + clear:both; + padding:2px 15px 15px 15px; + overflow:auto; + text-align:left; + line-height:1.4em; } #TB_ajaxContent.TB_modal{ - padding:15px; + padding:15px; } #TB_ajaxContent p{ - padding:5px 0px 5px 0px; + padding:5px 0px 5px 0px; } #TB_load{ - position: fixed; - display:none; - height:13px; - width:208px; - z-index:103; - top: 50%; - left: 50%; - margin: -6px 0 0 -104px; /* -height/2 0 0 -width/2 */ + position: fixed; + display:none; + height:13px; + width:208px; + z-index:103; + top: 50%; + left: 50%; + margin: -6px 0 0 -104px; /* -height/2 0 0 -width/2 */ } * html #TB_load { /* ie6 hack */ -position: absolute; -margin-top: expression(0 - parseInt(this.offsetHeight / 2) + (TBWindowMargin = document.documentElement && document.documentElement.scrollTop || document.body.scrollTop) + 'px'); + position: absolute; + margin-top: expression(0 - parseInt(this.offsetHeight / 2) + (TBWindowMargin = document.documentElement && document.documentElement.scrollTop || document.body.scrollTop) + 'px'); } #TB_HideSelect{ - z-index:99; - position:fixed; - top: 0; - left: 0; - background-color:#fff; - border:none; - filter:alpha(opacity=0); - -moz-opacity: 0; - opacity: 0; - height:100%; - width:100%; + z-index:99; + position:fixed; + top: 0; + left: 0; + background-color:#fff; + border:none; + filter:alpha(opacity=0); + -moz-opacity: 0; + opacity: 0; + height:100%; + width:100%; } * html #TB_HideSelect { /* ie6 hack */ - position: absolute; - height: expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px'); + position: absolute; + height: expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px'); } #TB_iframeContent{ - clear:both; - border:none; - margin-bottom:-1px; - margin-top:1px; - _margin-bottom:1px; + clear:both; + border:none; + margin-bottom:-1px; + margin-top:1px; + _margin-bottom:1px; } /* @end */ @@ -688,17 +688,17 @@ margin-top: expression(0 - parseInt(this.offsetHeight / 2) + (TBWindowMargin = d /* @group Debugging Section */ #debugging-toggle { - text-align: center; + text-align: center; } #debugging-toggle img { - cursor: pointer; + cursor: pointer; } #rdoc-debugging-section-dump { - display: none; - margin: 0 2em 2em; - background: #ccc; - border: 1px solid #999; + display: none; + margin: 0 2em 2em; + background: #ccc; + border: 1px solid #999; } diff --git a/lib/rdoc/include.rb b/lib/rdoc/include.rb index 11a9bdc7ef..9cebd3d8ef 100644 --- a/lib/rdoc/include.rb +++ b/lib/rdoc/include.rb @@ -17,6 +17,7 @@ class RDoc::Include < RDoc::CodeObject super() @name = name self.comment = comment + @module = nil # cache for module if found end ## @@ -52,9 +53,47 @@ class RDoc::Include < RDoc::CodeObject ## # Attempts to locate the included module object. Returns the name if not # known. + # + # The scoping rules of Ruby to resolve the name of an included module are: + # - first look into the children of the current context; + # - if not found, look into the children of included modules, + # in reverse inclusion order; + # - if still not found, go up the hierarchy of names. def module - RDoc::TopLevel.find_module_named(@name) || @name + return @module if @module + + # search the current context + return @name unless parent + full_name = parent.child_name(@name) + @module = RDoc::TopLevel.modules_hash[full_name] + return @module if @module + return @name if @name =~ /^::/ + + # search the includes before this one, in reverse order + searched = parent.includes.take_while { |i| i != self }.reverse + searched.each do |i| + inc = i.module + next if String === inc + full_name = inc.child_name(@name) + @module = RDoc::TopLevel.modules_hash[full_name] + return @module if @module + end + + # go up the hierarchy of names + p = parent.parent + while p + full_name = p.child_name(@name) + @module = RDoc::TopLevel.modules_hash[full_name] + return @module if @module + p = p.parent + end + + @name + end + + def to_s # :nodoc: + "include #@name in: #{parent}" end end diff --git a/lib/rdoc/markup.rb b/lib/rdoc/markup.rb index 32c8179e0d..c8914fea11 100644 --- a/lib/rdoc/markup.rb +++ b/lib/rdoc/markup.rb @@ -59,12 +59,477 @@ require 'rdoc' # # puts "#{wh.convert ARGF.read}" # +# == Encoding +# +# Where Encoding support is available RDoc will automatically convert all +# documents to the same output encoding. The output encoding can be set via +# RDoc::Options#encoding and defaults to Encoding.default_external. +# +# = \RDoc Markup Reference +# +# == Block Markup +# +# === Paragraphs and Verbatim +# +# The markup engine looks for a document's natural left margin. This is +# used as the initial margin for the document. +# +# Consecutive lines starting at this margin are considered to be a +# paragraph. Empty lines separate paragraphs. +# +# Any line that starts to the right of the current margin is treated +# as verbatim text. This is useful for code listings: +# +# 3.times { puts "Ruby" } +# +# In verbatim text, two or more blank lines are collapsed into one, +# and trailing blank lines are removed: +# +# This is the first line +# +# +# This is the second non-blank line, +# after 2 blank lines in the source markup. +# +# +# There were two trailing blank lines right above this paragraph, that +# have been removed. In addition, the verbatim text has been shifted +# left, so the amount of indentation of verbatim text is unimportant. +# +# === Headers and Rules +# +# A line starting with an equal sign (=) is treated as a +# heading. Level one headings have one equals sign, level two headings +# have two, and so on until level six, which is the maximum +# (seven hyphens or more result in a level six heading). +# +# For example, the above header was obtained with: +# == Headers and Rules +# +# A line starting with three or more hyphens (at the current indent) +# generates a horizontal rule. The more hyphens, the thicker the rule +# (within reason, and if supported by the output device). +# +# In the case of HTML output, three dashes generate a 1-pixel high rule, +# four dashes result in 2 pixels, and so on. The actual height is limited +# to 10 pixels: +# +# --- +# ----- +# ----------------------------------------------------- +# +# produces: +# +# --- +# ----- +# ----------------------------------------------------- +# +# === Simple Lists +# +# If a paragraph starts with a "*", "-", "." or ".", +# then it is taken to be the start of a list. The margin in increased to be +# the first non-space following the list start flag. Subsequent lines +# should be indented to this new margin until the list ends. For example: +# +# * this is a list with three paragraphs in +# the first item. This is the first paragraph. +# +# And this is the second paragraph. +# +# 1. This is an indented, numbered list. +# 2. This is the second item in that list +# +# This is the third conventional paragraph in the +# first list item. +# +# * This is the second item in the original list +# +# produces: +# +# * this is a list with three paragraphs in +# the first item. This is the first paragraph. +# +# And this is the second paragraph. +# +# 1. This is an indented, numbered list. +# 2. This is the second item in that list +# +# This is the third conventional paragraph in the +# first list item. +# +# * This is the second item in the original list +# +# === Labeled Lists +# +# You can also construct labeled lists, sometimes called description +# or definition lists. Do this by putting the label in square brackets +# and indenting the list body: +# +# [cat] a small furry mammal +# that seems to sleep a lot +# +# [ant] a little insect that is known +# to enjoy picnics +# +# produces: +# +# [cat] a small furry mammal +# that seems to sleep a lot +# +# [ant] a little insect that is known +# to enjoy picnics +# +# If you want the list bodies to line up to the left of the labels, +# use two colons: +# +# cat:: a small furry mammal +# that seems to sleep a lot +# +# ant:: a little insect that is known +# to enjoy picnics +# +# produces: +# +# cat:: a small furry mammal +# that seems to sleep a lot +# +# ant:: a little insect that is known +# to enjoy picnics +# +# Notice that blank lines right after the label are ignored in labeled lists: +# +# [one] +# +# definition 1 +# +# [two] +# +# definition 2 +# +# produces the same output as +# +# [one] definition 1 +# [two] definition 2 +# +# +# === Lists and Verbatim +# +# If you want to introduce a verbatim section right after a list, it has to be +# less indented than the list item bodies, but more indented than the list +# label, letter, digit or bullet. For instance: +# +# * point 1 +# +# * point 2, first paragraph +# +# point 2, second paragraph +# verbatim text inside point 2 +# point 2, third paragraph +# verbatim text outside of the list (the list is therefore closed) +# regular paragraph after the list +# +# produces: +# +# * point 1 +# +# * point 2, first paragraph +# +# point 2, second paragraph +# verbatim text inside point 2 +# point 2, third paragraph +# verbatim text outside of the list (the list is therefore closed) +# regular paragraph after the list +# +# +# == Text Markup +# +# === Bold, Italic, Typewriter Text +# +# You can use markup within text (except verbatim) to change the +# appearance of parts of that text. Out of the box, RDoc::Markup +# supports word-based and general markup. +# +# Word-based markup uses flag characters around individual words: +# +# \*_word_\*:: displays _word_ in a *bold* font +# \__word_\_:: displays _word_ in an _emphasized_ font +# \+_word_\+:: displays _word_ in a +code+ font +# +# General markup affects text between a start delimiter and an end +# delimiter. Not surprisingly, these delimiters look like HTML markup. +# +# \_text_:: displays _text_ in a *bold* font +# \_text_:: displays _text_ in an _emphasized_ font +# (alternate tag: \) +# \_text_\:: displays _text_ in a +code+ font +# (alternate tag: \) +# +# 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). +# +# === Hyperlinks +# +# Hyperlinks to the web starting with +http:+, +mailto:+, +ftp:+ or +www.+ +# are recognized. An HTTP url that references an external image file is +# converted into an inline . Hyperlinks starting with +link:+ are +# assumed to refer to local files whose path is relative to the --op +# directory. +# +# Hyperlinks 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]. +# +# Example hyperlinks: +# +# link:RDoc.html +# http://rdoc.rubyforge.org +# mailto:user@example.com +# {RDoc Documentation}[http://rdoc.rubyforge.org] +# {RDoc Markup}[link:RDoc/Markup.html] +# +# === 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 hyperlinks: +# +# * 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 hyperlinked 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 hyperlinked to \RDoc::RDoc#document +# +# Inside \ tags, more precisely, leading backslashes are removed +# only if followed by a markup character (<*_+), a backslash, +# or a known hyperlink 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 hyperlinked +# from comment text to their description. This hyperlinking 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, the 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 of 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. +# +# === Other directives +# +# [+: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 escapd 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. +# +# [:section: title] +# Starts a new section in the output. The title following +:section:+ is +# used as the section heading, and the remainder of the comment containing +# the section is used as introductory text. Subsequent methods, aliases, +# attributes, and classes will be documented in this section. 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 such as: +# +# # ---------------------------------------- +# # :section: My Section +# # This is the section that I wrote. +# # See it glisten in the noon-day sun. +# # ---------------------------------------- +# +# Note: Current formatters to not take sections into account. +# +# [+:call-seq:+] +# Lines up to the next blank line in the comment are treated as the method's +# calling sequence, overriding the default parsing of method parameters and +# yield arguments. +# +# Further directives can be found in RDoc::Parser::Ruby and RDoc::Parser::C. #-- -# Author:: Dave Thomas, dave@pragmaticprogrammer.com -# License:: Ruby license +# Original Author:: Dave Thomas, dave@pragmaticprogrammer.com +# License:: Ruby license class RDoc::Markup + ## + # An AttributeManager which handles inline markup. + attr_reader :attribute_manager ## diff --git a/lib/rdoc/markup/attribute_manager.rb b/lib/rdoc/markup/attribute_manager.rb index e86e7f6812..2ee243ab0b 100644 --- a/lib/rdoc/markup/attribute_manager.rb +++ b/lib/rdoc/markup/attribute_manager.rb @@ -15,9 +15,12 @@ class RDoc::Markup::AttributeManager # optimistic #++ - A_PROTECT = 004 # :nodoc: + A_PROTECT = 004 # :nodoc: - PROTECT_ATTR = A_PROTECT.chr # :nodoc: + ## + # Special mask character to prevent inline markup handling + + PROTECT_ATTR = A_PROTECT.chr # :nodoc: ## # This maps delimiters that occur around words (such as *bold* or +tt+) @@ -56,7 +59,7 @@ class RDoc::Markup::AttributeManager def initialize @html_tags = {} @matching_word_pairs = {} - @protectable = %w[<\\] + @protectable = %w[<] @special = {} @word_pair_map = {} @@ -79,12 +82,19 @@ class RDoc::Markup::AttributeManager RDoc::Markup::AttrChanger.new turn_on, turn_off end - def change_attribute(current, new) + ## + # Changes the current attribute from +current+ to +new+ + + def change_attribute current, new diff = current ^ new attribute(new & diff, current & diff) end - def changed_attribute_by_name(current_set, new_set) + ## + # Used by the tests to change attributes by name from +current_set+ to + # +new_set+ + + def changed_attribute_by_name current_set, new_set current = new = 0 current_set.each do |name| current |= RDoc::Markup::Attribute.bitmap_for(name) @@ -97,6 +107,9 @@ class RDoc::Markup::AttributeManager change_attribute(current, new) end + ## + # Copies +start_pos+ to +end_pos+ from the current string + def copy_string(start_pos, end_pos) res = @str[start_pos...end_pos] res.gsub!(/\000/, '') @@ -112,7 +125,7 @@ class RDoc::Markup::AttributeManager # first do matching ones tags = @matching_word_pairs.keys.join("") - re = /(^|[^\w#{NULL}])([#{tags}])([#:\\]?[\w.\/-]+?\S?)\2(\W|$)/ + re = /(^|\W)([#{tags}])([#:\\]?[\w.\/-]+?\S?)\2(\W|$)/ 1 while str.gsub!(re) do attr = @matching_word_pairs[$2] @@ -164,6 +177,9 @@ class RDoc::Markup::AttributeManager # Escapes special sequences of text to prevent conversion to RDoc def mask_protected_sequences + # protect __send__, __FILE__, etc. + @str.gsub!(/__([a-z]+)__/i, + "_#{PROTECT_ATTR}_#{PROTECT_ATTR}\\1_#{PROTECT_ATTR}_#{PROTECT_ATTR}") @str.gsub!(/\\([#{Regexp.escape @protectable.join('')}])/, "\\1#{PROTECT_ATTR}") end @@ -228,8 +244,8 @@ class RDoc::Markup::AttributeManager @attrs = RDoc::Markup::AttrSpan.new @str.length - convert_html @str, @attrs convert_attrs @str, @attrs + convert_html @str, @attrs convert_specials @str, @attrs unmask_protected_sequences @@ -262,6 +278,9 @@ class RDoc::Markup::AttributeManager end end + ## + # Splits the string into chunks by attribute change + def split_into_flow res = [] current_attr = 0 diff --git a/lib/rdoc/markup/blank_line.rb b/lib/rdoc/markup/blank_line.rb index a8c07c8e57..5da0ac8d81 100644 --- a/lib/rdoc/markup/blank_line.rb +++ b/lib/rdoc/markup/blank_line.rb @@ -1,12 +1,20 @@ ## -# An empty line +# An empty line. This class is a singleton. class RDoc::Markup::BlankLine - def == other # :nodoc: - self.class == other.class + @instance = new + + ## + # RDoc::Markup::BlankLine is a singleton + + def self.new + @instance end + ## + # Calls #accept_blank_line on +visitor+ + def accept visitor visitor.accept_blank_line self end diff --git a/lib/rdoc/markup/document.rb b/lib/rdoc/markup/document.rb index 7963e9afe1..688e8e822e 100644 --- a/lib/rdoc/markup/document.rb +++ b/lib/rdoc/markup/document.rb @@ -39,6 +39,9 @@ class RDoc::Markup::Document self.class == other.class and @parts == other.parts end + ## + # Runs this document and all its #items through +visitor+ + def accept visitor visitor.start_accepting @@ -49,6 +52,9 @@ class RDoc::Markup::Document visitor.end_accepting end + ## + # Does this document have no parts? + def empty? @parts.empty? end diff --git a/lib/rdoc/markup/formatter.rb b/lib/rdoc/markup/formatter.rb index 993e523f0c..9308954de1 100644 --- a/lib/rdoc/markup/formatter.rb +++ b/lib/rdoc/markup/formatter.rb @@ -7,6 +7,10 @@ require 'rdoc/markup' class RDoc::Markup::Formatter + ## + # Tag for inline markup containing a +bit+ for the bitmask and the +on+ and + # +off+ triggers. + InlineTag = Struct.new(:bit, :on, :off) ## @@ -101,6 +105,9 @@ class RDoc::Markup::Formatter @in_tt > 0 end + ## + # Turns on tags for +item+ on +res+ + def on_tags res, item attr_mask = item.turn_on return if attr_mask.zero? @@ -113,6 +120,9 @@ class RDoc::Markup::Formatter end end + ## + # Turns off tags for +item+ on +res+ + def off_tags res, item attr_mask = item.turn_off return if attr_mask.zero? diff --git a/lib/rdoc/markup/formatter_test_case.rb b/lib/rdoc/markup/formatter_test_case.rb index 26c8d63332..dd755c55d1 100644 --- a/lib/rdoc/markup/formatter_test_case.rb +++ b/lib/rdoc/markup/formatter_test_case.rb @@ -4,14 +4,57 @@ require 'rdoc/markup/formatter' ## # 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 < MiniTest::Unit::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 @m = RDoc::Markup.new - @am = RDoc::Markup::AttributeManager.new @RM = RDoc::Markup @bullet_list = @RM::List.new(:BULLET, @@ -39,14 +82,25 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase @RM::ListItem.new(nil, @RM::Paragraph.new('l2'))) end + ## + # Call to add the visitor tests to your test case + def self.add_visitor_tests self.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' @@ -54,6 +108,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase end_accepting end + ## + # Calls accept_blank_line + def test_accept_blank_line @to.start_accepting @@ -62,6 +119,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_blank_line end + ## + # Calls accept_heading with a level 5 RDoc::Markup::Heading + def test_accept_heading @to.start_accepting @@ -70,6 +130,79 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase 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 @@ -78,15 +211,80 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase 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_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") + @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 @@ -99,6 +297,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_raw end + ## + # Calls accept_rule with a RDoc::Markup::Rule + def test_accept_rule @to.start_accepting @@ -107,6 +308,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_rule end + ## + # Calls accept_list_item_start_bullet + def test_accept_list_item_start_bullet @to.start_accepting @@ -117,6 +321,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_start_bullet end + ## + # Calls accept_list_item_start_label + def test_accept_list_item_start_label @to.start_accepting @@ -127,6 +334,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_start_label end + ## + # Calls accept_list_item_start_lalpha + def test_accept_list_item_start_lalpha @to.start_accepting @@ -137,6 +347,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_start_lalpha end + ## + # Calls accept_list_item_start_note + def test_accept_list_item_start_note @to.start_accepting @@ -147,6 +360,26 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_start_note end + ## + # Calls accept_list_item_start_note_2 + + def test_accept_list_item_start_note_2 + list = @RM::List.new(:NOTE, + @RM::ListItem.new('teletype', + @RM::Paragraph.new('teletype description'))) + + @to.start_accepting + + list.accept @to + + @to.end_accepting + + accept_list_item_start_note_2 + end + + ## + # Calls accept_list_item_start_number + def test_accept_list_item_start_number @to.start_accepting @@ -157,6 +390,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_start_number end + ## + # Calls accept_list_item_start_ualpha + def test_accept_list_item_start_ualpha @to.start_accepting @@ -167,6 +403,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_start_ualpha end + ## + # Calls accept_list_item_end_bullet + def test_accept_list_item_end_bullet @to.start_accepting @@ -179,6 +418,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_end_bullet end + ## + # Calls accept_list_item_end_label + def test_accept_list_item_end_label @to.start_accepting @@ -191,6 +433,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_end_label end + ## + # Calls accept_list_item_end_lalpha + def test_accept_list_item_end_lalpha @to.start_accepting @@ -203,6 +448,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_end_lalpha end + ## + # Calls accept_list_item_end_note + def test_accept_list_item_end_note @to.start_accepting @@ -215,6 +463,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_end_note end + ## + # Calls accept_list_item_end_number + def test_accept_list_item_end_number @to.start_accepting @@ -227,6 +478,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_end_number end + ## + # Calls accept_list_item_end_ualpha + def test_accept_list_item_end_ualpha @to.start_accepting @@ -239,6 +493,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_end_ualpha end + ## + # Calls accept_list_start_bullet + def test_accept_list_start_bullet @to.start_accepting @@ -247,6 +504,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_start_bullet end + ## + # Calls accept_list_start_label + def test_accept_list_start_label @to.start_accepting @@ -255,6 +515,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_start_label end + ## + # Calls accept_list_start_lalpha + def test_accept_list_start_lalpha @to.start_accepting @@ -263,6 +526,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_start_lalpha end + ## + # Calls accept_list_start_note + def test_accept_list_start_note @to.start_accepting @@ -271,6 +537,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_start_note end + ## + # Calls accept_list_start_number + def test_accept_list_start_number @to.start_accepting @@ -279,6 +548,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_start_number end + ## + # Calls accept_list_start_ualpha + def test_accept_list_start_ualpha @to.start_accepting @@ -287,6 +559,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_start_ualpha end + ## + # Calls accept_list_end_bullet + def test_accept_list_end_bullet @to.start_accepting @@ -297,6 +572,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_end_bullet end + ## + # Calls accept_list_end_label + def test_accept_list_end_label @to.start_accepting @@ -307,6 +585,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_end_label end + ## + # Calls accept_list_end_lalpha + def test_accept_list_end_lalpha @to.start_accepting @@ -317,6 +598,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_end_lalpha end + ## + # Calls accept_list_end_number + def test_accept_list_end_number @to.start_accepting @@ -327,6 +611,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_end_number end + ## + # Calls accept_list_end_note + def test_accept_list_end_note @to.start_accepting @@ -337,6 +624,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_end_note end + ## + # Calls accept_list_end_ulpha + def test_accept_list_end_ualpha @to.start_accepting @@ -346,6 +636,52 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_end_ualpha end + + ## + # Calls list_nested with a two-level list + + def test_list_nested + doc = @RM::Document.new( + @RM::List.new(:BULLET, + @RM::ListItem.new(nil, + @RM::Paragraph.new('l1'), + @RM::List.new(:BULLET, + @RM::ListItem.new(nil, + @RM::Paragraph.new('l1.1')))), + @RM::ListItem.new(nil, + @RM::Paragraph.new('l2')))) + + doc.accept @to + + list_nested + end + + ## + # Calls list_verbatim with a list containing a verbatim block + + def test_list_verbatim # HACK overblown + doc = @RM::Document.new( + @RM::List.new(:BULLET, + @RM::ListItem.new(nil, + @RM::Paragraph.new('list', 'stuff'), + @RM::BlankLine.new, + @RM::Verbatim.new("* list\n", + " with\n", + "\n", + " second\n", + "\n", + " 1. indented\n", + " 2. numbered\n", + "\n", + " third\n", + "\n", + "* second\n")))) + + doc.accept @to + + list_verbatim + end + end end diff --git a/lib/rdoc/markup/heading.rb b/lib/rdoc/markup/heading.rb index 21e2574d68..081d637729 100644 --- a/lib/rdoc/markup/heading.rb +++ b/lib/rdoc/markup/heading.rb @@ -3,6 +3,9 @@ class RDoc::Markup::Heading < Struct.new :level, :text + ## + # Calls #accept_heading on +wisitor+ + def accept visitor visitor.accept_heading self end diff --git a/lib/rdoc/markup/inline.rb b/lib/rdoc/markup/inline.rb index 1b5eac45ae..f5bf98a071 100644 --- a/lib/rdoc/markup/inline.rb +++ b/lib/rdoc/markup/inline.rb @@ -1,3 +1,4 @@ +require 'rdoc' class RDoc::Markup ## @@ -14,6 +15,9 @@ class RDoc::Markup @@name_to_bitmap = { :_SPECIAL_ => SPECIAL } @@next_bitmap = 2 + ## + # Returns a unique bit for +name+ + def self.bitmap_for(name) bitmap = @@name_to_bitmap[name] unless bitmap then @@ -24,6 +28,9 @@ class RDoc::Markup bitmap end + ## + # Returns a string reperesentation of +bitmap+ + def self.as_string(bitmap) return "none" if bitmap.zero? res = [] @@ -33,6 +40,9 @@ class RDoc::Markup res.join(",") end + ## + # yields each attribute name in +bitmap+ + def self.each_name_of(bitmap) @@name_to_bitmap.each do |name, bit| next if bit == SPECIAL @@ -75,7 +85,7 @@ class RDoc::Markup end ## - # Acccesses flags for character +n+ + # Accesses flags for character +n+ def [](n) @attrs[n] diff --git a/lib/rdoc/markup/list.rb b/lib/rdoc/markup/list.rb index 75326ed836..820b4c9645 100644 --- a/lib/rdoc/markup/list.rb +++ b/lib/rdoc/markup/list.rb @@ -35,6 +35,9 @@ class RDoc::Markup::List @items == other.items end + ## + # Runs this list and all its #items through +visitor+ + def accept visitor visitor.accept_list_start self diff --git a/lib/rdoc/markup/list_item.rb b/lib/rdoc/markup/list_item.rb index 500e814fe1..d719c352ec 100644 --- a/lib/rdoc/markup/list_item.rb +++ b/lib/rdoc/markup/list_item.rb @@ -35,6 +35,9 @@ class RDoc::Markup::ListItem @parts == other.parts end + ## + # Runs this list item and all its #parts through +visitor+ + def accept visitor visitor.accept_list_item_start self diff --git a/lib/rdoc/markup/paragraph.rb b/lib/rdoc/markup/paragraph.rb index a9923ed24d..808430d576 100644 --- a/lib/rdoc/markup/paragraph.rb +++ b/lib/rdoc/markup/paragraph.rb @@ -3,6 +3,9 @@ class RDoc::Markup::Paragraph < RDoc::Markup::Raw + ## + # Calls #accept_paragraph on +visitor+ + def accept visitor visitor.accept_paragraph self end diff --git a/lib/rdoc/markup/parser.rb b/lib/rdoc/markup/parser.rb index 9fba69dc29..ea02ee3c5b 100644 --- a/lib/rdoc/markup/parser.rb +++ b/lib/rdoc/markup/parser.rb @@ -52,13 +52,13 @@ class RDoc::Markup::Parser attr_reader :tokens ## - # Parsers +str+ into a Document + # Parses +str+ into a Document def self.parse str parser = new - #parser.debug = true parser.tokenize str - RDoc::Markup::Document.new(*parser.parse) + doc = RDoc::Markup::Document.new + parser.parse doc end ## @@ -86,6 +86,7 @@ class RDoc::Markup::Parser # Builds a Heading of +level+ def build_heading level + _, text, = get # TEXT heading = RDoc::Markup::Heading.new level, text skip :NEWLINE @@ -105,38 +106,69 @@ class RDoc::Markup::Parser case type when :BULLET, :LABEL, :LALPHA, :NOTE, :NUMBER, :UALPHA then - list_type = type - if column < margin then + if column < margin || (list.type && list.type != type) then unget break end - if list.type and list.type != list_type then - unget - break - end - - list.type = list_type + list.type = type + peek_type, _, column, = peek_token case type when :NOTE, :LABEL then - _, indent, = get # SPACE - if :NEWLINE == peek_token.first then - get - peek_type, new_indent, peek_column, = peek_token - indent = new_indent if - peek_type == :INDENT and peek_column >= column - unget + if peek_type == :NEWLINE then + # description not on the same line as LABEL/NOTE + # skip the trailing newline & any blank lines below + while peek_type == :NEWLINE + get + peek_type, _, column, = peek_token + end + + # we may be: + # - at end of stream + # - at a column < margin: + # [text] + # blah blah blah + # - at the same column, but with a different type of list item + # [text] + # * blah blah + # - at the same column, with the same type of list item + # [one] + # [two] + # In all cases, we have an empty description. + # In the last case only, we continue. + if peek_type.nil? || column < margin then + empty = 1 + elsif column == margin then + case peek_type + when type + empty = 2 # continue + when *LIST_TOKENS + empty = 1 + else + empty = 0 + end + else + empty = 0 + end + + if empty > 0 then + item = RDoc::Markup::ListItem.new(data) + item << RDoc::Markup::BlankLine.new + list << item + break if empty == 1 + next + end end else data = nil - _, indent, = get end - list_item = build_list_item(margin + indent, data) + list_item = RDoc::Markup::ListItem.new data + parse list_item, column + list << list_item - list << list_item if list_item else unget break @@ -150,54 +182,6 @@ class RDoc::Markup::Parser list end - ## - # Builds a ListItem that is flush to +indent+ with type +item_type+ - - def build_list_item indent, item_type = nil - p :list_item_start => [indent, item_type] if @debug - - list_item = RDoc::Markup::ListItem.new item_type - - until @tokens.empty? do - type, data, column = get - - if column < indent and - not type == :NEWLINE and - (type != :INDENT or data < indent) then - unget - break - end - - case type - when :INDENT then - unget - list_item.push(*parse(indent)) - when :TEXT then - unget - list_item << build_paragraph(indent) - when :HEADER then - list_item << build_heading(data) - when :NEWLINE then - list_item << RDoc::Markup::BlankLine.new - when *LIST_TOKENS then - unget - list_item << build_list(column) - else - raise ParseError, "Unhandled token #{@current_token.inspect}" - end - end - - p :list_item_end => [indent, item_type] if @debug - - return nil if list_item.empty? - - list_item.parts.shift if - RDoc::Markup::BlankLine === list_item.parts.first and - list_item.length > 1 - - list_item - end - ## # Builds a Paragraph that is flush to +margin+ @@ -209,18 +193,7 @@ class RDoc::Markup::Parser until @tokens.empty? do type, data, column, = get - case type - when :INDENT then - next if data == margin and peek_token[0] == :TEXT - - unget - break - when :TEXT then - if column != margin then - unget - break - end - + if type == :TEXT && column == margin then paragraph << data skip :NEWLINE else @@ -235,67 +208,81 @@ class RDoc::Markup::Parser end ## - # Builds a Verbatim that is flush to +margin+ + # Builds a Verbatim that is indented from +margin+. + # + # The verbatim block is shifted left (the least indented lines start in + # column 0). Each part of the verbatim is one line of text, always + # terminated by a newline. Blank lines always consist of a single newline + # character, and there is never a single newline at the end of the verbatim. def build_verbatim margin p :verbatim_begin => margin if @debug verbatim = RDoc::Markup::Verbatim.new + min_indent = nil + generate_leading_spaces = true + line = '' + until @tokens.empty? do type, data, column, = get - case type - when :INDENT then - if margin >= data then - unget - break - end + if type == :NEWLINE then + line << data + verbatim << line + line = '' + generate_leading_spaces = true + next + end - indent = data - margin - - verbatim << ' ' * indent - when :HEADER then - verbatim << '=' * data - - _, _, peek_column, = peek_token - peek_column ||= column + data - verbatim << ' ' * (peek_column - column - data) - when :RULE then - width = 2 + data - verbatim << '-' * width - - _, _, peek_column, = peek_token - peek_column ||= column + data + 2 - verbatim << ' ' * (peek_column - column - width) - when :TEXT then - verbatim << data - when *LIST_TOKENS then - if column <= margin then - unget - break - end - - list_marker = case type - when :BULLET then '*' - when :LABEL then "[#{data}]" - when :LALPHA, :NUMBER, :UALPHA then "#{data}." - when :NOTE then "#{data}::" - end - - verbatim << list_marker - - _, data, = get - - verbatim << ' ' * (data - list_marker.length) - when :NEWLINE then - verbatim << data - break unless [:INDENT, :NEWLINE].include? peek_token[0] - else + if column <= margin unget break end + + if generate_leading_spaces then + indent = column - margin + line << ' ' * indent + min_indent = indent if min_indent.nil? || indent < min_indent + generate_leading_spaces = false + end + + case type + when :HEADER then + line << '=' * data + _, _, peek_column, = peek_token + peek_column ||= column + data + indent = peek_column - column - data + line << ' ' * indent + when :RULE then + width = 2 + data + line << '-' * width + _, _, peek_column, = peek_token + peek_column ||= column + width + indent = peek_column - column - width + line << ' ' * indent + when :TEXT then + line << data + else # *LIST_TOKENS + list_marker = case type + when :BULLET then data + when :LABEL then "[#{data}]" + when :NOTE then "#{data}::" + else # :LALPHA, :NUMBER, :UALPHA + "#{data}." + end + line << list_marker + peek_type, _, peek_column = peek_token + unless peek_type == :NEWLINE then + peek_column ||= column + list_marker.length + indent = peek_column - column - list_marker.length + line << ' ' * indent + end + end + end + verbatim << line << "\n" unless line.empty? + verbatim.parts.each { |p| p.slice!(0, min_indent) unless p == "\n" } if min_indent > 0 verbatim.normalize p :verbatim_end => margin if @debug @@ -313,65 +300,60 @@ class RDoc::Markup::Parser end ## - # Parses the tokens into a Document + # Parses the tokens into an array of RDoc::Markup::XXX objects, + # and appends them to the passed +parent+ RDoc::Markup::YYY object. + # + # Exits at the end of the token stream, or when it encounters a token + # in a column less than +indent+ (unless it is a NEWLINE). + # + # Returns +parent+. - def parse indent = 0 + def parse parent, indent = 0 p :parse_start => indent if @debug - document = [] - until @tokens.empty? do type, data, column, = get - if type != :INDENT and column < indent then - unget - break + if type == :NEWLINE then + # trailing newlines are skipped below, so this is a blank line + parent << RDoc::Markup::BlankLine.new + skip :NEWLINE, false + next end + # indentation change: break or verbattim + if column < indent then + unget + break + elsif column > indent then + unget + parent << build_verbatim(indent) + next + end + + # indentation is the same case type when :HEADER then - document << build_heading(data) - when :INDENT then - if indent > data then - unget - break - elsif indent == data then - next - end - - unget - document << build_verbatim(indent) - when :NEWLINE then - document << RDoc::Markup::BlankLine.new - skip :NEWLINE, false + parent << build_heading(data) when :RULE then - document << RDoc::Markup::Rule.new(data) + parent << RDoc::Markup::Rule.new(data) skip :NEWLINE when :TEXT then unget - document << build_paragraph(indent) - - # we're done with this paragraph (indent mismatch) - break if peek_token[0] == :TEXT + parent << build_paragraph(indent) when *LIST_TOKENS then unget - - list = build_list(indent) - - document << list if list - - # we're done with this list (indent mismatch) - break if LIST_TOKENS.include? peek_token.first and indent > 0 + parent << build_list(indent) else type, data, column, line = @current_token - raise ParseError, - "Unhandled token #{type} (#{data.inspect}) at #{line}:#{column}" + raise ParseError, "Unhandled token #{type} (#{data.inspect}) at #{line}:#{column}" end end p :parse_end => indent if @debug - document + parent + end ## @@ -384,63 +366,16 @@ class RDoc::Markup::Parser end ## - # Skips a token of +token_type+, optionally raising an error. + # Skips the next token if its type is +token_type+. + # + # Optionally raises an error if the next token is not of the expected type. def skip token_type, error = true type, = get - return unless type # end of stream - return @current_token if token_type == type - unget - - raise ParseError, "expected #{token_type} got #{@current_token.inspect}" if - error - end - - ## - # Consumes tokens until NEWLINE and turns them back into text - - def text - text = '' - - loop do - type, data, = get - - text << case type - when :BULLET then - _, space, = get # SPACE - "*#{' ' * (space - 1)}" - when :LABEL then - _, space, = get # SPACE - "[#{data}]#{' ' * (space - data.length - 2)}" - when :LALPHA, :NUMBER, :UALPHA then - _, space, = get # SPACE - "#{data}.#{' ' * (space - 2)}" - when :NOTE then - _, space = get # SPACE - "#{data}::#{' ' * (space - data.length - 2)}" - when :TEXT then - data - when :NEWLINE then - unget - break - when nil then - break - else - raise ParseError, "unhandled token #{@current_token.inspect}" - end - end - - text - end - - ## - # Calculates the column and line of the current token based on +offset+. - - def token_pos offset - [offset - @line_pos, @line] + raise ParseError, "expected #{token_type} got #{@current_token.inspect}" if error end ## @@ -455,51 +390,62 @@ class RDoc::Markup::Parser until s.eos? do pos = s.pos + # leading spaces will be reflected by the column of the next token + # the only thing we loose are trailing spaces at the end of the file + next if s.scan(/ +/) + + # note: after BULLET, LABEL, etc., + # indent will be the column of the next non-newline token + @tokens << case + # [CR]LF => :NEWLINE when s.scan(/\r?\n/) then token = [:NEWLINE, s.matched, *token_pos(pos)] @line_pos = s.pos @line += 1 token - when s.scan(/ +/) then - [:INDENT, s.matched_size, *token_pos(pos)] + # === text => :HEADER then :TEXT when s.scan(/(=+)\s*/) then level = s[1].length level = 6 if level > 6 @tokens << [:HEADER, level, *token_pos(pos)] - pos = s.pos s.scan(/.*/) - [:TEXT, s.matched, *token_pos(pos)] - when s.scan(/^(-{3,}) *$/) then + [:TEXT, s.matched.sub(/\r$/, ''), *token_pos(pos)] + # --- (at least 3) and nothing else on the line => :RULE + when s.scan(/(-{3,}) *$/) then [:RULE, s[1].length - 2, *token_pos(pos)] - when s.scan(/([*-])\s+/) then - @tokens << [:BULLET, :BULLET, *token_pos(pos)] - [:SPACE, s.matched_size, *token_pos(pos)] - when s.scan(/([a-z]|\d+)\.[ \t]+\S/i) then + # * or - followed by white space and text => :BULLET + when s.scan(/([*-]) +(\S)/) then + s.pos -= s[2].bytesize # unget \S + [:BULLET, s[1], *token_pos(pos)] + # A. text, a. text, 12. text => :UALPHA, :LALPHA, :NUMBER + when s.scan(/([a-z]|\d+)\. +(\S)/i) then + # FIXME if tab(s), the column will be wrong + # either support tabs everywhere by first expanding them to + # spaces, or assume that they will have been replaced + # before (and provide a check for that at least in debug + # mode) list_label = s[1] - width = s.matched_size - 1 - - s.pos -= 1 # unget \S - - list_type = case list_label - when /[a-z]/ then :LALPHA - when /[A-Z]/ then :UALPHA - when /\d/ then :NUMBER - else - raise ParseError, "BUG token #{list_label}" - end - - @tokens << [list_type, list_label, *token_pos(pos)] - [:SPACE, width, *token_pos(pos)] + s.pos -= s[2].bytesize # unget \S + list_type = + case list_label + when /[a-z]/ then :LALPHA + when /[A-Z]/ then :UALPHA + when /\d/ then :NUMBER + else + raise ParseError, "BUG token #{list_label}" + end + [list_type, list_label, *token_pos(pos)] + # [text] followed by spaces or end of line => :LABEL when s.scan(/\[(.*?)\]( +|$)/) then - @tokens << [:LABEL, s[1], *token_pos(pos)] - [:SPACE, s.matched_size, *token_pos(pos)] + [:LABEL, s[1], *token_pos(pos)] + # text:: followed by spaces or end of line => :NOTE when s.scan(/(.*?)::( +|$)/) then - @tokens << [:NOTE, s[1], *token_pos(pos)] - [:SPACE, s.matched_size, *token_pos(pos)] + [:NOTE, s[1], *token_pos(pos)] + # anything else: :TEXT else s.scan(/.*/) - [:TEXT, s.matched, *token_pos(pos)] + [:TEXT, s.matched.sub(/\r$/, ''), *token_pos(pos)] end end @@ -507,9 +453,17 @@ class RDoc::Markup::Parser end ## - # Returns the current token or +token+ to the token stream + # Calculates the column and line of the current token based on +offset+. - def unget token = @current_token + def token_pos offset + [offset - @line_pos, @line] + end + + ## + # Returns the current token to the token stream + + def unget + token = @current_token p :unget => token if @debug raise Error, 'too many #ungets' if token == @tokens.first @tokens.unshift token if token diff --git a/lib/rdoc/markup/preprocess.rb b/lib/rdoc/markup/pre_process.rb similarity index 60% rename from lib/rdoc/markup/preprocess.rb rename to lib/rdoc/markup/pre_process.rb index cefb498916..e59bd227b7 100644 --- a/lib/rdoc/markup/preprocess.rb +++ b/lib/rdoc/markup/pre_process.rb @@ -1,12 +1,15 @@ require 'rdoc/markup' +require 'rdoc/encoding' ## # Handle common directives that can occur in a block of text: # -# : include : filename +# \:include: filename # -# RDoc plugin authors can register additional directives to be handled through -# RDoc::Markup::PreProcess::register +# Directives can be escaped by preceding them with a backslash. +# +# RDoc plugin authors can register additional directives to be handled by +# using RDoc::Markup::PreProcess::register class RDoc::Markup::PreProcess @@ -52,18 +55,25 @@ class RDoc::Markup::PreProcess # +code_object+. See RDoc::CodeObject#metadata def handle text, code_object = nil - text.gsub!(/^([ \t]*#?[ \t]*):(\w+):([ \t]*)(.+)?\n/) do - next $& if $3.empty? and $4 and $4[0, 1] == ':' + # regexp helper (square brackets for optional) + # $1 $2 $3 $4 $5 + # [prefix][\]:directive:[spaces][param]newline + text.gsub!(/^([ \t]*#?[ \t]*)(\\?):(\w+):([ \t]*)(.+)?\n/) do + # skip something like ':toto::' + next $& if $4.empty? and $5 and $5[0, 1] == ':' + + # skip if escaped + next "#$1:#$3:#$4#$5\n" unless $2.empty? prefix = $1 - directive = $2.downcase - param = $4 + directive = $3.downcase + param = $5 case directive when 'include' then filename = param.split[0] - include_file filename, prefix - + encoding = if defined?(Encoding) then text.encoding else nil end + include_file filename, prefix, encoding else result = yield directive, param if block_given? @@ -88,27 +98,38 @@ class RDoc::Markup::PreProcess end ## - # Include a file, indenting it correctly. + # Handles the :include: _filename_ directive. + # + # If the first line of the included file starts with '#', and contains + # an encoding information in the form 'coding:' or 'coding=', it is + # removed. + # + # If all lines in the included file start with a '#', this leading '#' + # is removed before inclusion. The included content is indented like + # the :include: directive. + #-- + # so all content will be verbatim because of the likely space after '#'? + # TODO shift left the whole file content in that case + # TODO comment stop/start #-- and #++ in included file must be processed here - def include_file(name, indent) - if full_name = find_include_file(name) then - content = if defined?(Encoding) then - File.binread full_name - else - File.read full_name - end - # HACK determine content type and force encoding - content = content.sub(/\A# .*coding[=:].*$/, '').lstrip + def include_file name, indent, encoding + full_name = find_include_file name - # strip leading '#'s, but only if all lines start with them - if content =~ /^[^#]/ then - content.gsub(/^/, indent) - else - content.gsub(/^#?/, indent) - end - else + unless full_name then warn "Couldn't find file to include '#{name}' from #{@input_file_name}" - '' + return '' + end + + content = RDoc::Encoding.read_file full_name, encoding + + # strip magic comment + content = content.sub(/\A# .*coding[=:].*$/, '').lstrip + + # strip leading '#'s, but only if all lines start with them + if content =~ /^[^#]/ then + content.gsub(/^/, indent) + else + content.gsub(/^#?/, indent) end end diff --git a/lib/rdoc/markup/raw.rb b/lib/rdoc/markup/raw.rb index 1124be7cc8..ca877c79af 100644 --- a/lib/rdoc/markup/raw.rb +++ b/lib/rdoc/markup/raw.rb @@ -27,6 +27,9 @@ class RDoc::Markup::Raw self.class == other.class and text == other.text end + ## + # Calls #accept_raw+ on +visitor+ + def accept visitor visitor.accept_raw self end @@ -63,3 +66,4 @@ class RDoc::Markup::Raw end end + diff --git a/lib/rdoc/markup/rule.rb b/lib/rdoc/markup/rule.rb index 4fcd040d2b..b778f2bc09 100644 --- a/lib/rdoc/markup/rule.rb +++ b/lib/rdoc/markup/rule.rb @@ -3,6 +3,9 @@ class RDoc::Markup::Rule < Struct.new :weight + ## + # Calls #accept_rule on +visitor+ + def accept visitor visitor.accept_rule self end diff --git a/lib/rdoc/markup/text_formatter_test_case.rb b/lib/rdoc/markup/text_formatter_test_case.rb new file mode 100644 index 0000000000..ba9e7c6187 --- /dev/null +++ b/lib/rdoc/markup/text_formatter_test_case.rb @@ -0,0 +1,116 @@ +require 'rdoc/markup/formatter_test_case' + +## +# Test case for creating new plain-text RDoc::Markup formatters. See also +# RDoc::Markup::FormatterTestCase +# +# See test_rdoc_markup_to_rdoc.rb for a complete example. +# +# Example: +# +# class TestRDocMarkupToNewTextFormat < RDoc::Markup::TextFormatterTestCase +# +# add_visitor_tests +# add_text_tests +# +# def setup +# super +# +# @to = RDoc::Markup::ToNewTextFormat.new +# end +# +# def accept_blank_line +# assert_equal :junk, @to.res.join +# end +# +# # ... +# +# end + +class RDoc::Markup::TextFormatterTestCase < RDoc::Markup::FormatterTestCase + + ## + # Adds test cases to the calling TestCase. + + def self.add_text_tests + self.class_eval do + + ## + # Test case that calls @to.accept_heading + + def test_accept_heading_indent + @to.start_accepting + @to.indent = 3 + @to.accept_heading @RM::Heading.new(1, 'Hello') + + accept_heading_indent + end + + ## + # Test case that calls @to.accept_rule + + def test_accept_rule_indent + @to.start_accepting + @to.indent = 3 + @to.accept_rule @RM::Rule.new(1) + + accept_rule_indent + end + + ## + # Test case that calls @to.accept_verbatim + + def test_accept_verbatim_indent + @to.start_accepting + @to.indent = 2 + @to.accept_verbatim @RM::Verbatim.new("hi\n", " world\n") + + accept_verbatim_indent + end + + ## + # Test case that calls @to.accept_verbatim with a big indent + + def test_accept_verbatim_big_indent + @to.start_accepting + @to.indent = 2 + @to.accept_verbatim @RM::Verbatim.new("hi\n", "world\n") + + accept_verbatim_big_indent + end + + ## + # Test case that calls @to.accept_paragraph with an indent + + def test_accept_paragraph_indent + @to.start_accepting + @to.indent = 3 + @to.accept_paragraph @RM::Paragraph.new(('words ' * 30).strip) + + accept_paragraph_indent + end + + ## + # Test case that calls @to.accept_paragraph with a long line + + def test_accept_paragraph_wrap + @to.start_accepting + @to.accept_paragraph @RM::Paragraph.new(('words ' * 30).strip) + + accept_paragraph_wrap + end + + ## + # Test case that calls @to.attributes with an escaped + # cross-reference. If this test doesn't pass something may be very + # wrong. + + def test_attributes + assert_equal 'Dog', @to.attributes("\\Dog") + end + + end + end + +end + diff --git a/lib/rdoc/markup/to_ansi.rb b/lib/rdoc/markup/to_ansi.rb index 9a5be8babb..c9f874ea3c 100644 --- a/lib/rdoc/markup/to_ansi.rb +++ b/lib/rdoc/markup/to_ansi.rb @@ -1,10 +1,13 @@ -require 'rdoc/markup/inline' +require 'rdoc/markup/to_rdoc' ## # Outputs RDoc markup with vibrant ANSI color! class RDoc::Markup::ToAnsi < RDoc::Markup::ToRdoc + ## + # Creates a new ToAnsi visitor that is ready to output vibrant ANSI color! + def initialize super @@ -23,12 +26,15 @@ class RDoc::Markup::ToAnsi < RDoc::Markup::ToRdoc add_tag :EM, "\e[4m", "\e[m" end + ## + # Overrides indent width to ensure output lines up correctly. + def accept_list_item_end list_item width = case @list_type.last when :BULLET then 2 when :NOTE, :LABEL then - @res << "\n" + @res << "\n" unless res.length == 1 2 else bullet = @list_index.last.to_s @@ -39,6 +45,9 @@ class RDoc::Markup::ToAnsi < RDoc::Markup::ToRdoc @indent -= width end + ## + # Adds coloring to note and label list items + def accept_list_item_start list_item bullet = case @list_type.last when :BULLET then @@ -62,6 +71,9 @@ class RDoc::Markup::ToAnsi < RDoc::Markup::ToRdoc end end + ## + # Starts accepting with a reset screen + def start_accepting super diff --git a/lib/rdoc/markup/to_bs.rb b/lib/rdoc/markup/to_bs.rb index e7af129824..931edd81ea 100644 --- a/lib/rdoc/markup/to_bs.rb +++ b/lib/rdoc/markup/to_bs.rb @@ -1,4 +1,4 @@ -require 'rdoc/markup/inline' +require 'rdoc/markup/to_rdoc' ## # Outputs RDoc markup with hot backspace action! You will probably need a @@ -8,6 +8,9 @@ require 'rdoc/markup/inline' class RDoc::Markup::ToBs < RDoc::Markup::ToRdoc + ## + # Returns a new ToBs that is ready for hot backspace action! + def initialize super @@ -22,8 +25,12 @@ class RDoc::Markup::ToBs < RDoc::Markup::ToRdoc def init_tags add_tag :BOLD, '+b', '-b' add_tag :EM, '+_', '-_' + add_tag :TT, '' , '' # we need in_tt information maintained end + ## + # Makes heading text bold. + def accept_heading heading use_prefix or @res << ' ' * @indent @res << @headings[heading.level][0] @@ -44,7 +51,6 @@ class RDoc::Markup::ToBs < RDoc::Markup::ToRdoc when '+_' then @in_em = true when '-_' then @in_em = false end - '' end diff --git a/lib/rdoc/markup/to_html.rb b/lib/rdoc/markup/to_html.rb index 74e3137eb2..de723921e9 100644 --- a/lib/rdoc/markup/to_html.rb +++ b/lib/rdoc/markup/to_html.rb @@ -8,6 +8,8 @@ require 'cgi' class RDoc::Markup::ToHtml < RDoc::Markup::Formatter + include RDoc::Text + ## # Maps RDoc::Markup::Parser::LIST_TOKENS types to HTML tags @@ -15,7 +17,7 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter :BULLET => ['
    ', '
'], :LABEL => ['
', '
'], :LALPHA => ['
    ', '
'], - :NOTE => ['', '
'], + :NOTE => ['', '
'], :NUMBER => ['
    ', '
'], :UALPHA => ['
    ', '
'], } @@ -48,6 +50,9 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter File.join(*from) end + ## + # Creates a new formatter that will output HTML + def initialize super @@ -103,13 +108,15 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter end end + # :section: Special handling + ## - # And we're invoked with a potential external hyperlink mailto: - # just gets inserted. http: links are checked to see if they + # And we're invoked with a potential external hyperlink. mailto: + # just gets inserted. http: links are checked to see if they # reference an image. If so, that image gets inserted using an - # tag. Otherwise a conventional is used. We also - # support a special type of hyperlink, link:, which is a reference - # to a local file whose path is relative to the --op directory. + # tag. Otherwise a conventional is used. + # We also support a special type of hyperlink, link:, which is a + # reference to a local file whose path is relative to the --op directory. def handle_special_HYPERLINK(special) url = special.text @@ -118,7 +125,7 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter ## # Here's a hypedlink where the label is different to the URL - #
  • " when :LABEL then - annotate("
    ") + - convert_flow(@am.flow(list_item.label)) + - annotate("
    ") + - annotate("
    ") - + "
    #{to_html list_item.label}
    \n
    " when :NOTE then - annotate("") + - annotate("") + - convert_flow(@am.flow(list_item.label)) + - annotate("") + - annotate("") + "

    #{to_html list_item.label}

    \n" else raise RDoc::Error, "Invalid list type: #{list_type.inspect}" end end ## - # Ends a list item + # Returns the HTML end-tag for +list_type+ def list_end_for(list_type) case list_type @@ -340,5 +326,12 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter end end + ## + # Converts +item+ to HTML using RDoc::Text#to_html + + def to_html item + super convert_flow @am.flow item + end + end diff --git a/lib/rdoc/markup/to_html_crossref.rb b/lib/rdoc/markup/to_html_crossref.rb index 44e71486fb..a3feb848a2 100644 --- a/lib/rdoc/markup/to_html_crossref.rb +++ b/lib/rdoc/markup/to_html_crossref.rb @@ -9,10 +9,10 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml ## # Regular expression to match class references # - # 1) There can be a '\' in front of text to suppress any cross-references - # 2) There can be a '::' in front of class names to reference from the + # 1. There can be a '\\' in front of text to suppress the cross-reference + # 2. There can be a '::' in front of class names to reference from the # top-level namespace. - # 3) The method can be followed by parenthesis + # 3. The method can be followed by parenthesis (not recommended) CLASS_REGEXP_STR = '\\\\?((?:\:{2})?[A-Z]\w*(?:\:\:\w+)*)' @@ -34,10 +34,10 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml # A::B::C.meth #{CLASS_REGEXP_STR}(?:[.#]|::)#{METHOD_REGEXP_STR} - # Stand-alone method (proceeded by a #) + # Stand-alone method (preceeded by a #) | \\?\##{METHOD_REGEXP_STR} - # Stand-alone method (proceeded by ::) + # Stand-alone method (preceeded by ::) | ::#{METHOD_REGEXP_STR} # A::B::C @@ -51,9 +51,10 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml # In order that words like "can't" not # be flagged as potential cross-references, only # flag potential class cross-references if the character - # after the cross-referece is a space or sentence - # punctuation. - | #{CLASS_REGEXP_STR}(?=[\s\)\.\?\!\,\;]|\z) + # after the cross-referece is a space, sentence + # punctuation, tag start character, or attribute + # marker. + | #{CLASS_REGEXP_STR}(?=[\s\)\.\?\!\,\;<\000]|\z) # Things that look like filenames # The key thing is that there must be at least @@ -62,7 +63,29 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml | (?:\.\.\/)*[-\/\w]+[_\/\.][-\w\/\.]+ # Things that have markup suppressed - | \\[^\s] + # Don't process things like '\<' in \, though. + # TODO: including < is a hack, not very satisfying. + | \\[^\s<] + )/x + + ## + # Version of CROSSREF_REGEXP used when --hyperlink-all is specified. + + ALL_CROSSREF_REGEXP = /( + # A::B::C.meth + #{CLASS_REGEXP_STR}(?:[.#]|::)#{METHOD_REGEXP_STR} + + # Stand-alone method + | \\?#{METHOD_REGEXP_STR} + + # A::B::C + | #{CLASS_REGEXP_STR}(?=[\s\)\.\?\!\,\;<\000]|\z) + + # Things that look like filenames + | (?:\.\.\/)*[-\/\w]+[_\/\.][-\w\/\.]+ + + # Things that have markup suppressed + | \\[^\s<] )/x ## @@ -70,20 +93,29 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml attr_accessor :context + ## + # Should we show '#' characters on method references? + + attr_accessor :show_hash + ## # Creates a new crossref resolver that generates links relative to +context+ # which lives at +from_path+ in the generated files. '#' characters on - # references are removed unless +show_hash+ is true. + # references are removed unless +show_hash+ is true. Only method names + # preceded by '#' or '::' are hyperlinked, unless +hyperlink_all+ is true. - def initialize(from_path, context, show_hash) + def initialize(from_path, context, show_hash, hyperlink_all = false) raise ArgumentError, 'from_path cannot be nil' if from_path.nil? super() - @markup.add_special(CROSSREF_REGEXP, :CROSSREF) + crossref_re = hyperlink_all ? ALL_CROSSREF_REGEXP : CROSSREF_REGEXP + + @markup.add_special crossref_re, :CROSSREF @from_path = from_path @context = context @show_hash = show_hash + @hyperlink_all = hyperlink_all @seen = {} end @@ -92,22 +124,24 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml # We're invoked when any text matches the CROSSREF pattern. If we find the # corresponding reference, generate a hyperlink. If the name we're looking # for contains no punctuation, we look for it up the module/class chain. - # For example, HyperlinkHtml is found, even without the Generator:: prefix, - # because we look for it in module Generator first. + # For example, ToHtml is found, even without the RDoc::Markup:: + # prefix, because we look for it in module Markup first. def handle_special_CROSSREF(special) name = special.text - # This ensures that words entirely consisting of lowercase letters will - # not have cross-references generated (to suppress lots of erroneous - # cross-references to "new" in text, for instance) - return name if name =~ /\A[a-z]*\z/ + unless @hyperlink_all then + # This ensures that words entirely consisting of lowercase letters will + # not have cross-references generated (to suppress lots of erroneous + # cross-references to "new" in text, for instance) + return name if name =~ /\A[a-z]*\z/ + end return @seen[name] if @seen.include? name lookup = name - name = name[0, 1] unless @show_hash if name[0, 1] == '#' + name = name[1..-1] unless @show_hash if name[0, 1] == '#' # Find class, module, or method in class or module. # @@ -120,26 +154,47 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml # whether the string as a whole is a known symbol). if /#{CLASS_REGEXP_STR}([.#]|::)#{METHOD_REGEXP_STR}/ =~ lookup then - container = $1 type = $2 - type = '#' if type == '.' + type = '' if type == '.' # will find either #method or ::method method = "#{type}#{$3}" - ref = @context.find_symbol container, method + container = @context.find_symbol_module($1) + elsif /^([.#]|::)#{METHOD_REGEXP_STR}/ =~ lookup then + type = $1 + type = '' if type == '.' + method = "#{type}#{$2}" + container = @context + else + container = nil + end + + if container then + ref = container.find_local_symbol method + + unless ref || RDoc::TopLevel === container then + ref = container.find_ancestor_local_symbol method + end end ref = @context.find_symbol lookup unless ref + ref = nil if RDoc::Alias === ref # external alias: can't link to it out = if lookup == '\\' then lookup elsif lookup =~ /^\\/ then - $' - elsif ref and ref.document_self then - "
    #{name}" + # we remove the \ only in front of what we know: + # other backslashes are treated later, only outside of + ref ? $' : lookup + elsif ref then + if ref.document_self then + "#{name}" + else + name + end else - name + lookup end - @seen[name] = out + @seen[lookup] = out out end diff --git a/lib/rdoc/markup/to_rdoc.rb b/lib/rdoc/markup/to_rdoc.rb index 867715bb1e..b1ac59e5b0 100644 --- a/lib/rdoc/markup/to_rdoc.rb +++ b/lib/rdoc/markup/to_rdoc.rb @@ -1,3 +1,4 @@ +require 'rdoc/markup/formatter' require 'rdoc/markup/inline' ## @@ -5,21 +6,49 @@ require 'rdoc/markup/inline' class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter + ## + # Current indent amount for output in characters + attr_accessor :indent + + ## + # Output width in characters + + attr_accessor :width + + ## + # Stack of current list indexes for alphabetic and numeric lists + attr_reader :list_index + + ## + # Stack of list types + attr_reader :list_type + + ## + # Stack of list widths for indentation + attr_reader :list_width + + ## + # Prefix for the next list item. See #use_prefix + attr_reader :prefix + + ## + # Output accumulator + attr_reader :res + ## + # Creates a new formatter that will output (mostly) \RDoc markup + def initialize super - @markup.add_special(/\\[^\s]/, :SUPPRESSED_CROSSREF) - + @markup.add_special(/\\\S/, :SUPPRESSED_CROSSREF) @width = 78 - @prefix = '' - init_tags @headings = {} @@ -34,7 +63,7 @@ class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter end ## - # Maps attributes to ANSI sequences + # Maps attributes to HTML sequences def init_tags add_tag :BOLD, "", "" @@ -42,10 +71,16 @@ class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter add_tag :EM, "", "" end + ## + # Adds +blank_line+ to the output + def accept_blank_line blank_line @res << "\n" end + ## + # Adds +heading+ to the output + def accept_heading heading use_prefix or @res << ' ' * @indent @res << @headings[heading.level][0] @@ -54,12 +89,18 @@ class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter @res << "\n" end + ## + # Finishes consumption of +list+ + def accept_list_end list @list_index.pop @list_type.pop @list_width.pop end + ## + # Finishes consumption of +list_item+ + def accept_list_item_end list_item width = case @list_type.last when :BULLET then @@ -76,29 +117,29 @@ class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter @indent -= width end + ## + # Prepares the visitor for consuming +list_item+ + def accept_list_item_start list_item - bullet = case @list_type.last - when :BULLET then - '*' - when :NOTE, :LABEL then - attributes(list_item.label) + ":\n" - else - @list_index.last.to_s + '.' - end + type = @list_type.last - case @list_type.last + case type when :NOTE, :LABEL then + bullet = attributes(list_item.label) + ":\n" + @prefix = ' ' * @indent @indent += 2 - @prefix = bullet + (' ' * @indent) + @prefix << bullet + (' ' * @indent) else + bullet = type == :BULLET ? '*' : @list_index.last.to_s + '.' @prefix = (' ' * @indent) + bullet.ljust(bullet.length + 1) - width = bullet.length + 1 - @indent += width end end + ## + # Prepares the visitor for consuming +list+ + def accept_list_start list case list.type when :BULLET then @@ -123,14 +164,23 @@ class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter @list_type << list.type end + ## + # Adds +paragraph+ to the output + def accept_paragraph paragraph wrap attributes(paragraph.text) end + ## + # Adds +raw+ to the output + def accept_raw raw @res << raw.parts.join("\n") end + ## + # Adds +rule+ to the output + def accept_rule rule use_prefix or @res << ' ' * @indent @res << '-' * (@width - @indent) @@ -138,58 +188,46 @@ class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter end ## - # Outputs +verbatim+ flush left and indented 2 columns + # Outputs +verbatim+ indented 2 columns def accept_verbatim verbatim indent = ' ' * (@indent + 2) - lines = [] - current_line = [] - - # split into lines verbatim.parts.each do |part| - current_line << part - - if part == "\n" then - lines << current_line - current_line = [] - end + @res << indent unless part == "\n" + @res << part end - lines << current_line unless current_line.empty? - - # calculate margin - indented = lines.select { |line| line != ["\n"] } - margin = indented.map { |line| line.first.length }.min - - # flush left - indented.each { |line| line[0][0...margin] = '' } - - # output - use_prefix or @res << indent # verbatim is unlikely to have prefix - @res << lines.shift.join - - lines.each do |line| - @res << indent unless line == ["\n"] - @res << line.join - end - - @res << "\n" + @res << "\n" unless @res =~ /\n\z/ end + ## + # Applies attribute-specific markup to +text+ using RDoc::AttributeManager + def attributes text flow = @am.flow text.dup convert_flow flow end + ## + # Returns the generated output + def end_accepting @res.join end + ## + # Removes preceeding \\ from the suppressed crossref +special+ + def handle_special_SUPPRESSED_CROSSREF special - special.text.sub(/\\/, '') + text = special.text + text = text.sub('\\', '') unless in_tt? + text end + ## + # Prepares the visitor for text generation + def start_accepting @res = [""] @indent = 0 @@ -200,6 +238,10 @@ class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter @list_width = [] end + ## + # Adds the stored #prefix to the output and clears it. Lists generate a + # prefix for later consumption. + def use_prefix prefix = @prefix @prefix = nil @@ -208,6 +250,9 @@ class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter prefix end + ## + # Wraps +text+ to #width + def wrap text return unless text && !text.empty? diff --git a/lib/rdoc/markup/to_test.rb b/lib/rdoc/markup/to_test.rb index 0afdb96a18..f79f9475f1 100644 --- a/lib/rdoc/markup/to_test.rb +++ b/lib/rdoc/markup/to_test.rb @@ -6,6 +6,8 @@ require 'rdoc/markup/formatter' class RDoc::Markup::ToTest < RDoc::Markup::Formatter + # :stopdoc: + ## # :section: Visitor @@ -22,8 +24,12 @@ class RDoc::Markup::ToTest < RDoc::Markup::Formatter @res << paragraph.text end + def accept_raw raw + @res << raw.parts.join + end + def accept_verbatim(verbatim) - @res << verbatim.text + @res << verbatim.text.gsub(/^(\S)/, ' \1') end def accept_list_start(list) @@ -60,5 +66,7 @@ class RDoc::Markup::ToTest < RDoc::Markup::Formatter @res << '-' * rule.weight end + # :startdoc: + end diff --git a/lib/rdoc/markup/verbatim.rb b/lib/rdoc/markup/verbatim.rb index c684d78765..8fe2184699 100644 --- a/lib/rdoc/markup/verbatim.rb +++ b/lib/rdoc/markup/verbatim.rb @@ -3,6 +3,9 @@ class RDoc::Markup::Verbatim < RDoc::Markup::Raw + ## + # Calls #accept_verbatim on +visitor+ + def accept visitor visitor.accept_verbatim self end @@ -17,16 +20,16 @@ class RDoc::Markup::Verbatim < RDoc::Markup::Raw @parts.each do |part| case part - when /\n/ then + when /^\s*\n/ then newlines += 1 - parts << part if newlines <= 2 + parts << part if newlines == 1 else newlines = 0 parts << part end end - parts.slice!(-1) if parts[-2..-1] == ["\n", "\n"] + parts.pop if parts.last =~ /\A\r?\n\z/ @parts = parts end diff --git a/lib/rdoc/method_attr.rb b/lib/rdoc/method_attr.rb new file mode 100644 index 0000000000..15924d9ed0 --- /dev/null +++ b/lib/rdoc/method_attr.rb @@ -0,0 +1,353 @@ +require 'rdoc/code_object' + +## +# Abstract class representing either a method or an attribute. + +class RDoc::MethodAttr < RDoc::CodeObject + + include Comparable + + ## + # Name of this method/attribute. + + attr_accessor :name + + ## + # public, protected, private + + attr_accessor :visibility + + ## + # Is this a singleton method/attribute? + + attr_accessor :singleton + + ## + # Source file token stream + + attr_reader :text + + ## + # Array of other names for this method/attribute + + attr_reader :aliases + + ## + # The method/attribute we're aliasing + + attr_accessor :is_alias_for + + #-- + # The attributes below are for AnyMethod only. + # They are left here for the time being to + # allow ri to operate. + # TODO modify ri to avoid calling these on attributes. + #++ + + ## + # Parameters yielded by the called block + + attr_reader :block_params + + ## + # Parameters for this method + + attr_accessor :params + + ## + # Different ways to call this method + + attr_accessor :call_seq + + ## + # The call_seq or the param_seq with method name, if there is no call_seq. + + attr_reader :arglists + + ## + # Pretty parameter list for this method + + attr_reader :param_seq + + + ## + # Creates a new MethodAttr from token stream +text+ and method or attribute + # name +name+. + # + # Usually this is called by super from a subclass. + + def initialize text, name + super() + + @text = text + @name = name + + @aliases = [] + @is_alias_for = nil + @parent_name = nil + @singleton = nil + @visibility = :public + @see = false + + @arglists = nil + @block_params = nil + @call_seq = nil + @param_seq = nil + @params = nil + end + + ## + # Order by #singleton then #name + + def <=>(other) + [@singleton ? 0 : 1, name] <=> [other.singleton ? 0 : 1, other.name] + end + + ## + # A method/attribute is documented if any of the following is true: + # - it was marked with :nodoc:; + # - it has a comment; + # - it is an alias for a documented method; + # - it has a +#see+ method that is documented. + + def documented? + super or + (is_alias_for and is_alias_for.documented?) or + (see and see.documented?) + end + + ## + # A method/attribute to look at, + # in particular if this method/attribute has no documentation. + # + # It can be a method/attribute of the superclass or of an included module, + # including the Kernel module, which is always appended to the included + # modules. + # + # Returns +nil+ if there is no such method/attribute. + # The +#is_alias_for+ method/attribute, if any, is not included. + # + # Templates may generate a "see also ..." if this method/attribute + # has documentation, and "see ..." if it does not. + + def see + @see = find_see if @see == false + @see + end + + def find_see # :nodoc: + return nil if singleton || is_alias_for + + # look for the method + other = find_method_or_attribute name + return other if other + + # if it is a setter, look for a getter + return nil unless name =~ /[a-z_]=$/i # avoid == or === + return find_method_or_attribute name[0..-2] + end + + def find_method_or_attribute name # :nodoc: + return nil unless parent.respond_to? :ancestors + + searched = parent.ancestors + kernel = RDoc::TopLevel.all_modules_hash['Kernel'] + + searched << kernel if kernel && + parent != kernel && !searched.include?(kernel) + + searched.each do |ancestor| + next if parent == ancestor + next if String === ancestor + + other = ancestor.find_method_named('#' << name) || + ancestor.find_attribute_named(name) + + return other if other + end + + nil + end + + ## + # Abstract method. Contexts in their building phase call this + # to register a new alias for this known method/attribute. + # + # - creates a new AnyMethod/Attribute +newa+ named an_alias.new_name; + # - adds +self+ as +newa.is_alias_for+; + # - adds +newa+ to #aliases + # - adds +newa+ to the methods/attributes of +context+. + + def add_alias(an_alias, context) + raise NotImplementedError + end + + ## + # HTML fragment reference for this method + + def aref + type = singleton ? 'c' : 'i' + # % characters are not allowed in html names => dash instead + "#{aref_prefix}-#{type}-#{html_name}" + end + + ## + # Prefix for +aref+, defined by subclasses. + + def aref_prefix + raise NotImplementedError + end + + ## + # Attempts to sanitize the content passed by the ruby parser: + # remove outer parentheses, etc. + + def block_params=(value) + # 'yield.to_s' or 'assert yield, msg' + return @block_params = '' if value =~ /^[\.,]/ + + # remove trailing 'if/unless ...' + return @block_params = '' if value =~ /^(if|unless)\s/ + + value = $1.strip if value =~ /^(.+)\s(if|unless)\s/ + + # outer parentheses + value = $1 if value =~ /^\s*\((.*)\)\s*$/ + value = value.strip + + # proc/lambda + return @block_params = $1 if value =~ /^(proc|lambda)(\s*\{|\sdo)/ + + # surrounding +...+ or [...] + value = $1.strip if value =~ /^\+(.*)\+$/ + value = $1.strip if value =~ /^\[(.*)\]$/ + + return @block_params = '' if value.empty? + + # global variable + return @block_params = 'str' if value =~ /^\$[&0-9]$/ + + # wipe out array/hash indices + value.gsub!(/(\w)\[[^\[]+\]/, '\1') + + # remove @ from class/instance variables + value.gsub!(/@@?([a-z0-9_]+)/, '\1') + + # method calls => method name + value.gsub!(/([A-Z:a-z0-9_]+)\.([a-z0-9_]+)(\s*\(\s*[a-z0-9_.,\s]*\s*\)\s*)?/) do + case $2 + when 'to_s' then $1 + when 'const_get' then 'const' + when 'new' then + $1.split('::').last. # ClassName => class_name + gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). + gsub(/([a-z\d])([A-Z])/,'\1_\2'). + downcase + else + $2 + end + end + + # class prefixes + value.gsub!(/[A-Za-z0-9_:]+::/, '') + + # simple expressions + value = $1 if value =~ /^([a-z0-9_]+)\s*[-*+\/]/ + + @block_params = value.strip + end + + ## + # HTML id-friendly method/attribute name + + def html_name + CGI.escape(@name.gsub('-', '-2D')).gsub('%','-').sub(/^-/, '') + end + + ## + # Full method/attribute name including namespace + + def full_name + @full_name || "#{parent_name}#{pretty_name}" + end + + ## + # '::' for a class method/attribute, '#' for an instance method. + + def name_prefix + singleton ? '::' : '#' + end + + ## + # Method/attribute name with class/instance indicator + + def pretty_name + "#{name_prefix}#{@name}" + end + + ## + # Type of method/attribute (class or instance) + + def type + singleton ? 'class' : 'instance' + end + + ## + # Path to this method + + def path + "#{@parent.path}##{aref}" + end + + ## + # Name of our parent with special handling for un-marshaled methods + + def parent_name + @parent_name || super + end + + def pretty_print q # :nodoc: + alias_for = @is_alias_for ? "alias for #{@is_alias_for.name}" : nil + + q.group 2, "[#{self.class.name} #{full_name} #{visibility}", "]" do + if alias_for then + q.breakable + q.text alias_for + end + + if text then + q.breakable + q.text "text:" + q.breakable + q.pp @text + end + + unless comment.empty? then + q.breakable + q.text "comment:" + q.breakable + q.pp @comment + end + end + end + + def inspect # :nodoc: + alias_for = @is_alias_for ? " (alias for #{@is_alias_for.name})" : nil + "#<%s:0x%x %s (%s)%s>" % [ + self.class, object_id, + full_name, + visibility, + alias_for, + ] + end + + def to_s # :nodoc: + if @is_alias_for + "#{self.class.name}: #{full_name} -> #{is_alias_for}" + else + "#{self.class.name}: #{full_name}" + end + end + +end + diff --git a/lib/rdoc/normal_class.rb b/lib/rdoc/normal_class.rb index e7ca6fffde..1ed8eaf974 100644 --- a/lib/rdoc/normal_class.rb +++ b/lib/rdoc/normal_class.rb @@ -6,10 +6,10 @@ require 'rdoc/class_module' class RDoc::NormalClass < RDoc::ClassModule ## - # Ancestor ClassModules + # Appends the superclass, if any, to the included modules. def ancestors - includes + [superclass] + superclass ? super + [superclass] : super end def inspect # :nodoc: @@ -20,6 +20,15 @@ class RDoc::NormalClass < RDoc::ClassModule ] 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 diff --git a/lib/rdoc/normal_module.rb b/lib/rdoc/normal_module.rb index 92abe6b440..74a31f2668 100644 --- a/lib/rdoc/normal_module.rb +++ b/lib/rdoc/normal_module.rb @@ -5,11 +5,6 @@ require 'rdoc/class_module' class RDoc::NormalModule < RDoc::ClassModule - ## - # Included NormalModules - - alias ancestors includes - def inspect # :nodoc: "#<%s:0x%x module %s includes: %p attributes: %p methods: %p aliases: %p>" % [ self.class, object_id, diff --git a/lib/rdoc/options.rb b/lib/rdoc/options.rb index 90415f0aa4..810f7fac0a 100644 --- a/lib/rdoc/options.rb +++ b/lib/rdoc/options.rb @@ -8,9 +8,41 @@ require 'rdoc/ri/paths' class RDoc::Options ## - # Character-set + # The deprecated options. - attr_reader :charset + 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', + } + + ## + # Template option validator for OptionParser + + Template = nil + + ## + # 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 + + ## + # Encoding of output where. This is set via --encoding. + + attr_accessor :encoding if Object.const_defined? :Encoding ## # Files matching this pattern will be excluded @@ -22,10 +54,21 @@ class RDoc::Options 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_reader :force_update + attr_accessor :force_update + + ## + # Formatter to mark up text with + + attr_accessor :formatter ## # Description of the output generator (set with the -fmt option) @@ -33,9 +76,21 @@ class RDoc::Options attr_accessor :generator ## - # Formatter to mark up text with + # Loaded generator options. Used to prevent --help from loading the same + # options multiple times. - attr_accessor :formatter + 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 @@ -43,11 +98,21 @@ class RDoc::Options attr_accessor :main_page + ## + # 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 + ## # Is RDoc in pipe mode? @@ -56,32 +121,32 @@ class RDoc::Options ## # Array of directories to search for files to satisfy an :include: - attr_reader :rdoc_include - - ## - # Include private and protected methods in the output? - - attr_accessor :show_all + attr_accessor :rdoc_include ## # Include the '#' at the front of hyperlinked instance method names - attr_reader :show_hash + attr_accessor :show_hash ## # The number of columns in a tab - attr_reader :tab_width + attr_accessor :tab_width ## # Template to be used when generating output - attr_reader :template + attr_accessor :template + + ## + # Directory the template lives in + + attr_accessor :template_dir ## # Documentation title - attr_reader :title + attr_accessor :title ## # Verbosity, zero means quiet @@ -91,29 +156,88 @@ class RDoc::Options ## # URL of web cvs frontend - attr_reader :webcvs + 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: require 'rdoc/rdoc' - @op_dir = nil - @show_all = false - @main_page = nil + @dry_run = false @exclude = [] - @generators = RDoc::RDoc::GENERATORS - @generator = RDoc::Generator::Darkfish - @generator_name = nil - @rdoc_include = [] - @title = nil - @template = nil - @show_hash = false - @tab_width = 8 + @force_output = false @force_update = true - @verbosity = 1 + @generator = nil + @generator_name = nil + @generator_options = [] + @generators = RDoc::RDoc::GENERATORS + @hyperlink_all = false + @line_numbers = false + @main_page = nil + @coverage_report = false + @op_dir = nil @pipe = false - + @rdoc_include = [] + @show_hash = false + @stylesheet_url = nil + @tab_width = 8 + @template = nil + @template_dir = nil + @title = nil + @verbosity = 1 + @visibility = :protected @webcvs = nil - @charset = 'utf-8' + if Object.const_defined? :Encoding then + @encoding = Encoding.default_external + @charset = @encoding.to_s + else + @charset = 'UTF-8' + end + 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 ## @@ -122,7 +246,10 @@ class RDoc::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 @@ -139,6 +266,14 @@ Usage: #{opt.program_name} [options] [names...] 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. + - Darkfish creates frameless HTML output by Michael Granger. - ri creates ri data files @@ -156,14 +291,44 @@ Usage: #{opt.program_name} [options] [names...] 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 + warn "could not find template #{template}" + nil + else + [template, template_dir] + end + end + opt.separator nil - opt.separator "Parsing Options:" + 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.", + "Preferred over --charset") do |value| + @encoding = Encoding.find value + @charset = @encoding.to_s # may not be valid value + end + + opt.separator nil + end + opt.on("--all", "-a", - "Include all methods (not just public) in", - "the output.") do |value| - @show_all = value + "Synonym for --visibility=private.") do |value| + @visibility = :private end opt.separator nil @@ -207,20 +372,41 @@ Usage: #{opt.program_name} [options] [names...] end opt.separator nil - opt.separator "Generator Options:" + + 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("--charset=CHARSET", "-c", - "Specifies the output HTML character-set.") do |value| - @charset = value + 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 + 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("--fmt=FORMAT", "--format=FORMAT", "-f", @generators.keys, + 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 @@ -236,9 +422,11 @@ Usage: #{opt.program_name} [options] [names...] opt.separator nil - opt.on("--main=NAME", "-m", - "NAME will be the initial page displayed.") do |value| - @main_page = value + opt.on("--[no-]coverage-report", "--[no-]dcov", "-C", + "Prints a report on undocumented items.", + "Does not generate files.") do |value| + @coverage_report = value + @force_update = true if value end opt.separator nil @@ -250,6 +438,51 @@ Usage: #{opt.program_name} [options] [names...] 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", @@ -260,17 +493,12 @@ Usage: #{opt.program_name} [options] [names...] 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("--template=NAME", "-T", + opt.on("--template=NAME", "-T", Template, "Set the template used when generating", - "output.") do |value| - @template = value + "output. The default depends on the", + "formatter used.") do |(template, template_dir)| + @template = template + @template_dir = template_dir end opt.separator nil @@ -292,11 +520,7 @@ Usage: #{opt.program_name} [options] [names...] end opt.separator nil - - opt.on("-d", "--diagram", "Prevents -d from tripping --debug") - - opt.separator nil - opt.separator "ri Generator Options:" + opt.separator "ri generator options:" opt.separator nil opt.on("--ri", "-r", @@ -305,6 +529,8 @@ Usage: #{opt.program_name} [options] [names...] "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 @@ -317,22 +543,30 @@ Usage: #{opt.program_name} [options] [names...] "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 "Generic options:" opt.separator nil + opt.on("--[no-]dry-run", + "Don't write any files") do |value| + @dry_run = value + end + opt.on("-D", "--[no-]debug", "Displays lots on internal stuff.") do |value| $DEBUG_RDOC = value end opt.on("--[no-]ignore-invalid", - "Ignore invalid options and continue.") do |value| + "Ignore invalid options and continue", + "(default true).") do |value| ignore_invalid = value end @@ -342,38 +576,70 @@ Usage: #{opt.program_name} [options] [names...] end opt.on("--verbose", "-v", - "Display extra progress as we parse.") do |value| + "Display extra progress as RDoc parses") do |value| @verbosity = 2 end + 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 - argv.insert(0, *ENV['RDOCOPT'].split) if ENV['RDOCOPT'] - ignored = [] + 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 ignore_invalid then - ignored << e.args.join(' ') - retry + 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 - $stderr.puts opts - $stderr.puts - $stderr.puts e - exit 1 + 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 - ignored << '-p (with files)' + invalid << '-p (with files)' end - unless ignored.empty? or quiet then - $stderr.puts "invalid options: #{ignored.join ', '}" - $stderr.puts '(invalid options are ignored)' + 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 @op_dir ||= 'doc' @@ -392,15 +658,10 @@ Usage: #{opt.program_name} [options] [names...] # If no template was specified, use the default template for the output # formatter - @template ||= @generator_name - end - - ## - # Set the title, but only if not already set. This means that a title set - # from the command line trumps one set in a source file - - def title=(string) - @title ||= string + unless @template then + @template = @generator_name + @template_dir = template_dir_for @template + end end ## @@ -410,30 +671,46 @@ Usage: #{opt.program_name} [options] [names...] @verbosity.zero? end - def quiet=(bool) + ## + # Set quietness to +bool+ + + def quiet= bool @verbosity = bool ? 0 : 1 end - private - ## - # Set up an output generator for the format in @generator_name + # 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 = @generators[@generator_name] + def setup_generator generator_name = @generator_name + @generator = @generators[generator_name] unless @generator then - raise OptionParser::InvalidArgument, "Invalid output formatter" + raise OptionParser::InvalidArgument, + "Invalid output formatter #{generator_name}" end + + return if @generator_options.include? @generator + + @generator_name = generator_name + @generator_options << @generator + + @generator.setup_options self if @generator.respond_to? :setup_options end ## - # Check that the files on the command line exist + # Finds the template dir for +template+ - def check_files - @files.each do |f| - stat = File.stat f rescue next - raise RDoc::Error, "file '#{f}' not readable" unless stat.readable? + 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 diff --git a/lib/rdoc/parser.rb b/lib/rdoc/parser.rb index 1e4ab7c7a4..d218aba62a 100644 --- a/lib/rdoc/parser.rb +++ b/lib/rdoc/parser.rb @@ -1,6 +1,6 @@ require 'rdoc' require 'rdoc/code_objects' -require 'rdoc/markup/preprocess' +require 'rdoc/markup/pre_process' require 'rdoc/stats' ## @@ -43,7 +43,15 @@ class RDoc::Parser @parsers = [] class << self + + ## + # A Hash that maps file exetensions regular expressions to parsers that + # will consume them. + # + # Use parse_files_matching to register a parser's file extensions. + attr_reader :parsers + end ## @@ -67,18 +75,51 @@ class RDoc::Parser # 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 - if s[0, 2] == Marshal.dump('')[0, 2] then - true - elsif file =~ /erb\.rb$/ then - false - elsif s.scan(/<%|%>/).length >= 4 || s.index("\x00") then - true - elsif 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 + have_encoding = s.respond_to? :encoding + + if have_encoding then + return false if s.encoding != Encoding::ASCII_8BIT and s.valid_encoding? + end + + return true if s[0, 2] == Marshal.dump('')[0, 2] or s.index("\x00") + + if have_encoding then + s.force_encoding Encoding.default_external + + 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 + 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 @@ -143,6 +184,12 @@ class RDoc::Parser RDoc::Parser.parsers.unshift [regexp, self] end + ## + # Creates a new Parser storing +top_level+, +file_name+, +content+, + # +options+ and +stats+ in instance variables. + # + # Usually invoked by +super+ + def initialize(top_level, file_name, content, options, stats) @top_level = top_level @file_name = file_name diff --git a/lib/rdoc/parser/c.rb b/lib/rdoc/parser/c.rb index e218298dfe..60e9fefd61 100644 --- a/lib/rdoc/parser/c.rb +++ b/lib/rdoc/parser/c.rb @@ -3,9 +3,9 @@ require 'rdoc/parser/ruby' require 'rdoc/known_classes' ## -# We attempt to parse C extension files. Basically we look for +# RDoc::Parser::C attempts to parse C extension files. It looks for # the standard patterns that you find in extensions: rb_define_class, -# rb_define_method and so on. We also try to find the corresponding +# rb_define_method and so on. It tries to find the corresponding # C source for the methods and extract comments, but if we fail # we don't worry too much. # @@ -49,13 +49,26 @@ require 'rdoc/known_classes' # # The comment blocks may include special directives: # -# [Document-class: name] -# This comment block is documentation for the given class. Use this -# when the Init_xxx method is not named after the class. +# [Document-class: +name+] +# Documentation for the named class. # -# [Document-method: name] -# This comment documents the named method. Use when RDoc cannot -# automatically find the method from it's declaration +# [Document-module: +name+] +# Documentation for the named module. +# +# [Document-const: +name+] +# Documentation for the named +rb_define_const+. +# +# [Document-global: +name+] +# Documentation for the named +rb_define_global_const+ +# +# [Document-variable: +name+] +# Documentation for the named +rb_define_variable+ +# +# [Document-method: +name+] +# Documentation for the named method. +# +# [Document-attr: +name+] +# Documentation for the named attribute. # # [call-seq: text up to an empty line] # Because C source doesn't give descripive names to Ruby-level parameters, @@ -120,21 +133,61 @@ class RDoc::Parser::C < RDoc::Parser @known_classes = RDoc::KNOWN_CLASSES.dup @content = handle_tab_width handle_ifdefs_in(@content) @classes = Hash.new + @singleton_classes = Hash.new @file_dir = File.dirname(@file_name) end + ## + # Scans #content for rb_define_alias + def do_aliases - @content.scan(%r{rb_define_alias\s*\(\s*(\w+),\s*"([^"]+)",\s*"([^"]+)"\s*\)}m) do - |var_name, new_name, old_name| + @content.scan(/rb_define_alias\s*\( + \s*(\w+), + \s*"(.+?)", + \s*"(.+?)" + \s*\)/xm) do |var_name, new_name, old_name| class_name = @known_classes[var_name] || var_name - class_obj = find_class(var_name, class_name) + class_obj = find_class var_name, class_name - as = class_obj.add_alias RDoc::Alias.new("", old_name, new_name, "") + al = RDoc::Alias.new '', old_name, new_name, '' + al.singleton = @singleton_classes.key?(var_name) - @stats.add_alias as + comment = find_alias_comment var_name, new_name, old_name + comment = strip_stars comment + al.comment = comment + + class_obj.add_alias al + @stats.add_alias al end end + ## + # Scans #content for rb_attr and rb_define_attr + + def do_attrs + @content.scan(/rb_attr\s*\( + \s*(\w+), + \s*([\w"()]+), + \s*([01]), + \s*([01]), + \s*\w+\);/xm) do |var_name, attr_name, read, write| + handle_attr var_name, attr_name, read, write + end + + @content.scan(%r%rb_define_attr\( + \s*([\w\.]+), + \s*"([^"]+)", + \s*(\d+), + \s*(\d+)\s*\); + %xm) do |var_name, attr_name, read, write| + handle_attr var_name, attr_name, read, write + end + end + + ## + # Scans #content for rb_define_module, rb_define_class, boot_defclass, + # rb_define_module_under, rb_define_class_under and rb_singleton_class + def do_classes @content.scan(/(\w+)\s* = \s*rb_define_module\s*\(\s*"(\w+)"\s*\)/mx) do |var_name, class_name| @@ -165,35 +218,44 @@ class RDoc::Parser::C < RDoc::Parser end @content.scan(/([\w\.]+)\s* = \s*rb_define_class_under\s* - \( - \s*(\w+), - \s*"(\w+)", - \s*([\w\*\s\(\)\.\->]+)\s* # for SWIG - \s*\)/mx) do |var_name, in_module, class_name, parent| + \( + \s*(\w+), + \s*"(\w+)", + \s*([\w\*\s\(\)\.\->]+)\s* # for SWIG + \s*\)/mx) do |var_name, in_module, class_name, parent| handle_class_module(var_name, "class", class_name, parent, in_module) end + + @content.scan(/([\w\.]+)\s* = \s*rb_singleton_class\s* + \( + \s*(\w+) + \s*\)/mx) do |sclass_var, class_var| + handle_singleton sclass_var, class_var + end end + ## + # Scans #content for rb_define_variable, rb_define_readonly_variable, + # rb_define_const and rb_define_global_const + def do_constants - @content.scan(%r{\Wrb_define_ + @content.scan(%r%\Wrb_define_ ( variable | readonly_variable | const | - global_const | ) + global_const ) \s*\( (?:\s*(\w+),)? \s*"(\w+)", \s*(.*?)\s*\)\s*; - }xm) do |type, var_name, const_name, definition| + %xm) do |type, var_name, const_name, definition| var_name = "rb_cObject" if !var_name or var_name == "rb_mKernel" handle_constants type, var_name, const_name, definition end end ## - # Look for includes of the form: - # - # rb_include_module(rb_cArray, rb_mEnumerable); + # Scans #content for rb_include_module def do_includes @content.scan(/rb_include_module\s*\(\s*(\w+?),\s*(\w+?)\s*\)/) do |c,m| @@ -204,8 +266,13 @@ class RDoc::Parser::C < RDoc::Parser end end + ## + # Scans #content for rb_define_method, rb_define_singleton_method, + # rb_define_module_function, rb_define_private_method, + # rb_define_global_function and define_filetest_function + def do_methods - @content.scan(%r{rb_define_ + @content.scan(%r%rb_define_ ( singleton_method | method | @@ -217,8 +284,7 @@ class RDoc::Parser::C < RDoc::Parser \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, \s*(-?\w+)\s*\) (?:;\s*/[*/]\s+in\s+(\w+?\.[cy]))? - }xm) do - |type, var_name, meth_name, meth_body, param_count, source_file| + %xm) do |type, var_name, meth_name, meth_body, param_count, source_file| # Ignore top-object and weird struct.c dynamic stuff next if var_name == "ruby_top_self" @@ -231,44 +297,69 @@ class RDoc::Parser::C < RDoc::Parser source_file) end - @content.scan(%r{rb_define_attr\( - \s*([\w\.]+), - \s*"([^"]+)", - \s*(\d+), - \s*(\d+)\s*\); - }xm) do |var_name, attr_name, attr_reader, attr_writer| - #var_name = "rb_cObject" if var_name == "rb_mKernel" - handle_attr(var_name, attr_name, - attr_reader.to_i != 0, - attr_writer.to_i != 0) - end - - @content.scan(%r{rb_define_global_function\s*\( + @content.scan(%r%rb_define_global_function\s*\( \s*"([^"]+)", \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, \s*(-?\w+)\s*\) (?:;\s*/[*/]\s+in\s+(\w+?\.[cy]))? - }xm) do |meth_name, meth_body, param_count, source_file| + %xm) do |meth_name, meth_body, param_count, source_file| handle_method("method", "rb_mKernel", meth_name, meth_body, param_count, source_file) end @content.scan(/define_filetest_function\s*\( - \s*"([^"]+)", - \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, - \s*(-?\w+)\s*\)/xm) do - |meth_name, meth_body, param_count| + \s*"([^"]+)", + \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, + \s*(-?\w+)\s*\)/xm) do |meth_name, meth_body, param_count| handle_method("method", "rb_mFileTest", meth_name, meth_body, param_count) handle_method("singleton_method", "rb_cFile", meth_name, meth_body, param_count) end end - def find_attr_comment(attr_name) - if @content =~ %r{((?>/\*.*?\*/\s+)) - rb_define_attr\((?:\s*(\w+),)?\s*"#{attr_name}"\s*,.*?\)\s*;}xmi + ## + # Finds the comment for an alias on +class_name+ from +new_name+ to + # +old_name+ + + def find_alias_comment class_name, new_name, old_name + content =~ %r%((?>/\*.*?\*/\s+)) + rb_define_alias\(\s*#{Regexp.escape class_name}\s*, + \s*"#{Regexp.escape new_name}"\s*, + \s*"#{Regexp.escape old_name}"\s*\);%xm + + $1 || '' + end + + ## + # Finds a comment for rb_define_attr, rb_attr or Document-attr. + # + # +var_name+ is the C class variable the attribute is defined on. + # +attr_name+ is the attribute's name. + # + # +read+ and +write+ are the read/write flags ('1' or '0'). Either both or + # neither must be provided. + + def find_attr_comment var_name, attr_name, read = nil, write = nil + attr_name = Regexp.escape attr_name + + rw = if read and write then + /\s*#{read}\s*,\s*#{write}\s*/xm + else + /.*?/m + end + + if @content =~ %r%((?>/\*.*?\*/\s+)) + rb_define_attr\((?:\s*#{var_name},)?\s* + "#{attr_name}"\s*, + #{rw}\)\s*;%xm then $1 - elsif @content =~ %r{Document-attr:\s#{attr_name}\s*?\n((?>.*?\*/))}m + elsif @content =~ %r%((?>/\*.*?\*/\s+)) + rb_attr\(\s*#{var_name}\s*, + \s*#{attr_name}\s*, + #{rw},.*?\)\s*;%xm then + $1 + elsif @content =~ %r%Document-attr:\s#{attr_name}\s*?\n + ((?>.*?\*/))%xm then $1 else '' @@ -280,8 +371,10 @@ class RDoc::Parser::C < RDoc::Parser def find_body(class_name, meth_name, meth_obj, body, quiet = false) case body - when %r"((?>/\*.*?\*/\s*))((?:(?:static|SWIGINTERN)\s+)?(?:intern\s+)?VALUE\s+#{meth_name} - \s*(\([^)]*\))([^;]|$))"xm + when %r%((?>/\*.*?\*/\s*)) + ((?:(?:static|SWIGINTERN)\s+)? + (?:intern\s+)?VALUE\s+#{meth_name} + \s*(\([^)]*\))([^;]|$))%xm then comment = $1 body_text = $2 @@ -303,12 +396,13 @@ class RDoc::Parser::C < RDoc::Parser find_modifiers comment, meth_obj if comment + #meth_obj.params = params meth_obj.start_collecting_tokens tk = RDoc::RubyToken::Token.new nil, 1, 1 tk.set_text body_text meth_obj.add_token tk meth_obj.comment = strip_stars comment - when %r{((?>/\*.*?\*/\s*))^\s*(\#\s*define\s+#{meth_name}\s+(\w+))}m + when %r%((?>/\*.*?\*/\s*))^\s*(\#\s*define\s+#{meth_name}\s+(\w+))%m comment = $1 body_text = $2 find_body class_name, $3, meth_obj, body, true @@ -319,26 +413,29 @@ class RDoc::Parser::C < RDoc::Parser tk.set_text body_text meth_obj.add_token tk meth_obj.comment = strip_stars(comment) + meth_obj.comment.to_s - when %r{^\s*\#\s*define\s+#{meth_name}\s+(\w+)}m + when %r%^\s*\#\s*define\s+#{meth_name}\s+(\w+)%m unless find_body(class_name, $1, meth_obj, body, true) - warn "No definition for #{meth_name}" unless @options.quiet + warn "No definition for #{meth_name}" if @options.verbosity > 1 return false end - else - # No body, but might still have an override comment - comment = find_override_comment(class_name, meth_obj.name) + else # No body, but might still have an override comment + comment = find_override_comment class_name, meth_obj.name if comment - find_modifiers(comment, meth_obj) + find_modifiers comment, meth_obj meth_obj.comment = strip_stars comment else - warn "No definition for #{meth_name}" unless @options.quiet + warn "No definition for #{meth_name}" if @options.verbosity > 1 return false end end + true end + ## + # Finds a RDoc::NormalClass or RDoc::NormalModule for +raw_name+ + def find_class(raw_name, name) unless @classes[raw_name] if raw_name =~ /^rb_m/ @@ -382,16 +479,17 @@ class RDoc::Parser::C < RDoc::Parser def find_class_comment(class_name, class_mod) comment = nil - if @content =~ %r{ + if @content =~ %r% ((?>/\*.*?\*/\s+)) (static\s+)? void\s+ - Init_#{class_name}\s*(?:_\(\s*)?\(\s*(?:void\s*)?\)}xmi then # ) + Init_#{class_name}\s*(?:_\(\s*)?\(\s*(?:void\s*)?\)%xmi then + comment = $1.sub(%r%Document-(?:class|module):\s+#{class_name}%, '') + elsif @content =~ %r%Document-(?:class|module):\s+#{class_name}\s*? + (?:<\s+[:,\w]+)?\n((?>.*?\*/))%xm then comment = $1 - elsif @content =~ %r{Document-(?:class|module):\s+#{class_name}\s*?(?:<\s+[:,\w]+)?\n((?>.*?\*/))}m then - comment = $1 - elsif @content =~ %r{((?>/\*.*?\*/\s+)) - ([\w\.\s]+\s* = \s+)?rb_define_(class|module).*?"(#{class_name})"}xm then # " + elsif @content =~ %r%((?>/\*.*?\*/\s+)) + ([\w\.\s]+\s* = \s+)?rb_define_(class|module).*?"(#{class_name})"%xm then comment = $1 end @@ -409,10 +507,13 @@ class RDoc::Parser::C < RDoc::Parser # comment or in the matching Document- section. def find_const_comment(type, const_name) - if @content =~ %r{((?>^\s*/\*.*?\*/\s+)) - rb_define_#{type}\((?:\s*(\w+),)?\s*"#{const_name}"\s*,.*?\)\s*;}xmi + if @content =~ %r%((?>^\s*/\*.*?\*/\s+)) + rb_define_#{type}\((?:\s*(\w+),)?\s* + "#{const_name}"\s*, + .*?\)\s*;%xmi then $1 - elsif @content =~ %r{Document-(?:const|global|variable):\s#{const_name}\s*?\n((?>.*?\*/))}m + elsif @content =~ %r%Document-(?:const|global|variable):\s#{const_name} + \s*?\n((?>.*?\*/))%xm $1 else '' @@ -420,56 +521,111 @@ class RDoc::Parser::C < RDoc::Parser end ## - # If the comment block contains a section that looks like: + # Handles modifiers in +comment+ and updates +meth_obj+ as appropriate. # - # call-seq: - # Array.new - # Array.new(10) + # If :nodoc: is found, documentation on +meth_obj+ is suppressed. # - # use it for the parameters. + # If :yields: is followed by an argument list it is used for the + # #block_params of +meth_obj+. + # + # If the comment block contains a call-seq: section like: + # + # 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 + # + # it is used for the parameters of +meth_obj+. - def find_modifiers(comment, meth_obj) - if comment.sub!(/:nodoc:\s*^\s*\*?\s*$/m, '') or - comment.sub!(/\A\/\*\s*:nodoc:\s*\*\/\Z/, '') - meth_obj.document_self = false - end - if comment.sub!(/call-seq:(.*?)^\s*\*?\s*$/m, '') or - comment.sub!(/\A\/\*\s*call-seq:(.*?)\*\/\Z/, '') - seq = $1 - seq.gsub!(/^\s*\*\s*/, '') + def find_modifiers comment, meth_obj + # we must handle situations like the above followed by an unindented first + # comment. The difficulty is to make sure not to match lines starting + # with ARGF at the same indent, but that are after the first description + # paragraph. + + if comment =~ /call-seq:(.*?[^\s\*].*?)^\s*\*?\s*$/m then + all_start, all_stop = $~.offset(0) + seq_start, seq_stop = $~.offset(1) + + # we get the following lines that start with the leading word at the + # same indent, even if they have blank lines before + if $1 =~ /(^\s*\*?\s*\n)+^(\s*\*?\s*\w+)/m then + leading = $2 # ' * ARGF' in the example above + re = %r% + \A( + (^\s*\*?\s*\n)+ + (^#{Regexp.escape leading}.*?\n)+ + )+ + ^\s*\*?\s*$ + %xm + if comment[seq_stop..-1] =~ re then + all_stop = seq_stop + $~.offset(0).last + seq_stop = seq_stop + $~.offset(1).last + end + end + + seq = comment[seq_start..seq_stop] + seq.gsub!(/^(\s*\*?\s*?)(\S|\n)/m, '\2') + comment.slice! all_start...all_stop meth_obj.call_seq = seq + elsif comment.sub!(/\A\/\*\s*call-seq:(.*?)\*\/\Z/, '') then + meth_obj.call_seq = $1.strip + end + + if comment.sub!(/\s*:(nodoc|doc|yields?|args?):\s*(.*)/, '') then + RDoc::Parser.process_directive meth_obj, $1, $2 end end + ## + # Finds a Document-method override for +meth_name+ in +class_name+ + def find_override_comment(class_name, meth_name) name = Regexp.escape(meth_name) - if @content =~ %r{Document-method:\s+#{class_name}(?:\.|::|#)#{name}\s*?\n((?>.*?\*/))}m then + + if @content =~ %r%Document-method:\s+#{class_name}(?:\.|::|#)#{name}\s*?\n((?>.*?\*/))%m then $1 - elsif @content =~ %r{Document-method:\s#{name}\s*?\n((?>.*?\*/))}m then + elsif @content =~ %r%Document-method:\s#{name}\s*?\n((?>.*?\*/))%m then $1 end end - def handle_attr(var_name, attr_name, reader, writer) + ## + # Creates a new RDoc::Attr +attr_name+ on class +var_name+ that is either + # +read+, +write+ or both + + def handle_attr(var_name, attr_name, read, write) rw = '' - rw << 'R' if reader - rw << 'W' if writer + rw << 'R' if '1' == read + rw << 'W' if '1' == write class_name = @known_classes[var_name] return unless class_name - class_obj = find_class(var_name, class_name) + class_obj = find_class var_name, class_name - if class_obj - comment = find_attr_comment(attr_name) - comment = strip_stars comment - att = RDoc::Attr.new '', attr_name, rw, comment - @stats.add_method att - class_obj.add_attribute(att) - end + return unless class_obj + + comment = find_attr_comment var_name, attr_name + comment = strip_stars comment + + name = attr_name.gsub(/rb_intern\("([^"]+)"\)/, '\1') + + attr = RDoc::Attr.new '', name, rw, comment + + class_obj.add_attribute attr + @stats.add_attribute attr end + ## + # Creates a new RDoc::NormalClass or RDoc::NormalModule based on +type+ + # named +class_name+ in +parent+ which was assigned to the C +var_name+. + def handle_class_module(var_name, type, class_name, parent, in_module) parent_name = @known_classes[parent] || parent @@ -497,7 +653,7 @@ class RDoc::Parser::C < RDoc::Parser class_name end - if @content =~ %r{Document-class:\s+#{full_name}\s*<\s+([:,\w]+)} then + if @content =~ %r%Document-class:\s+#{full_name}\s*<\s+([:,\w]+)% then parent_name = $1 end @@ -519,15 +675,14 @@ class RDoc::Parser::C < RDoc::Parser end ## - # Adds constant comments. By providing some_value: at the start ofthe - # comment you can override the C value of the comment to give a friendly - # definition. + # Adds constants. By providing some_value: at the start of the comment you + # can override the C value of the comment to give a friendly definition. # # /* 300: The perfect score in bowling */ # rb_define_const(cFoo, "PERFECT", INT2FIX(300); # - # Will override +INT2FIX(300)+ with the value +300+ in the output RDoc. - # Values may include quotes and escaped colons (\:). + # Will override INT2FIX(300) with the value +300+ in the output + # RDoc. Values may include quotes and escaped colons (\:). def handle_constants(type, var_name, const_name, definition) class_name = @known_classes[var_name] @@ -588,22 +743,35 @@ class RDoc::Parser::C < RDoc::Parser body.gsub(/^#ifdef HAVE_PROTOTYPES.*?#else.*?\n(.*?)#endif.*?\n/m, '\1') end + ## + # Adds an RDoc::AnyMethod +meth_name+ defined on a class or module assigned + # to +var_name+. +type+ is the type of method definition function used. + # +singleton_method+ and +module_function+ create a singleton method. + def handle_method(type, var_name, meth_name, meth_body, param_count, source_file = nil) + singleton = false class_name = @known_classes[var_name] + unless class_name then + class_name = @singleton_classes[var_name] + singleton = true if class_name + end + return unless class_name class_obj = find_class var_name, class_name if class_obj then - if meth_name == "initialize" then - meth_name = "new" - type = "singleton_method" + if meth_name == 'initialize' then + meth_name = 'new' + singleton = true + type = 'method' # force public end meth_obj = RDoc::AnyMethod.new '', meth_name - meth_obj.singleton = %w[singleton_method module_function].include? type + meth_obj.singleton = + singleton || %w[singleton_method module_function].include?(type) p_count = Integer(param_count) rescue -1 @@ -627,7 +795,8 @@ class RDoc::Parser::C < RDoc::Parser body = @content end - if find_body(class_name, meth_body, meth_obj, body) and meth_obj.document_self then + if find_body(class_name, meth_body, meth_obj, body) and + meth_obj.document_self then class_obj.add_method meth_obj @stats.add_method meth_obj meth_obj.visibility = :private if 'private_method' == type @@ -635,13 +804,27 @@ class RDoc::Parser::C < RDoc::Parser end end + ## + # Registers a singleton class +sclass_var+ as a singleton of +class_var+ + + def handle_singleton sclass_var, class_var + class_name = @known_classes[class_var] + + @singleton_classes[sclass_var] = class_name + end + + ## + # Normalizes tabs in +body+ + def handle_tab_width(body) if /\t/ =~ body tab_width = @options.tab_width body.split(/\n/).map do |line| - 1 while line.gsub!(/\t+/) { ' ' * (tab_width*$&.length - $`.length % tab_width)} && $~ #` + 1 while line.gsub!(/\t+/) do + ' ' * (tab_width * $&.length - $`.length % tab_width) + end && $~ line - end .join("\n") + end.join "\n" else body end @@ -654,7 +837,7 @@ class RDoc::Parser::C < RDoc::Parser # * :title: My Awesome Project # */ # - # This routine modifies it's parameter + # This routine modifies its parameter def look_for_directives_in(context, comment) preprocess = RDoc::Markup::PreProcess.new @file_name, @options.rdoc_include @@ -665,29 +848,33 @@ class RDoc::Parser::C < RDoc::Parser @options.main_page = param '' when 'title' then - @options.title = param + @options.default_title = param if @options.respond_to? :default_title= '' end end comment end + ## # Removes lines that are commented out that might otherwise get picked up # when scanning for classes and methods def remove_commented_out_lines - @content.gsub!(%r{//.*rb_define_}, '//') + @content.gsub!(%r%//.*rb_define_%, '//') end + ## + # Removes private comments from +comment+ + def remove_private_comments(comment) comment.gsub!(/\/?\*--\n(.*?)\/?\*\+\+/m, '') comment.sub!(/\/?\*--\n.*/m, '') end ## - # Extract the classes/modules and methods from a C file and return the - # corresponding top-level object + # Extracts the classes, modules, methods, attributes, constants and aliases + # from a C file and returns an RDoc::TopLevel for this file def scan remove_commented_out_lines @@ -696,6 +883,7 @@ class RDoc::Parser::C < RDoc::Parser do_methods do_includes do_aliases + do_attrs @top_level end diff --git a/lib/rdoc/parser/perl.rb b/lib/rdoc/parser/perl.rb deleted file mode 100644 index 0023a013a6..0000000000 --- a/lib/rdoc/parser/perl.rb +++ /dev/null @@ -1,165 +0,0 @@ -require 'rdoc/parser' - -## -# -# This is an attamept to write a basic parser for Perl's -# POD (Plain old Documentation) format. Ruby code must -# co-exist with Perl, and some tasks are easier in Perl -# than Ruby because of existing libraries. -# -# One difficult is that Perl POD has no means of identifying -# the classes (packages) and methods (subs) with which it -# is associated, it is more like literate programming in so -# far as it just happens to be in the same place as the code, -# but need not be. -# -# We would like to support all the markup the POD provides -# so that it will convert happily to HTML. At the moment -# I don't think I can do that: time constraints. -# - -class RDoc::Parser::PerlPOD < RDoc::Parser - - parse_files_matching(/.p[lm]$/) - - ## - # Prepare to parse a perl file - - def initialize(top_level, file_name, content, options, stats) - super - - preprocess = RDoc::Markup::PreProcess.new @file_name, @options.rdoc_include - - preprocess.handle @content do |directive, param| - warn "Unrecognized directive '#{directive}' in #{@file_name}" - end - end - - ## - # Extract the Pod(-like) comments from the code. - # At its most basic there will ne no need to distinguish - # between the different types of header, etc. - # - # This uses a simple finite state machine, in a very - # procedural pattern. I could "replace case with polymorphism" - # but I think it would obscure the intent, scatter the - # code all over tha place. This machine is necessary - # because POD requires that directives be preceded by - # blank lines, so reading line by line is necessary, - # and preserving state about what is seen is necesary. - - def scan - - @top_level.comment ||= "" - state=:code_blank - line_number = 0 - line = nil - - # This started out as a really long nested case statement, - # which also led to repetitive code. I'd like to avoid that - # so I'm using a "table" instead. - - # Firstly we need some procs to do the transition and processing - # work. Because these are procs they are closures, and they can - # use variables in the local scope. - # - # First, the "nothing to see here" stuff. - code_noop = lambda do - if line =~ /^\s+$/ - state = :code_blank - end - end - - pod_noop = lambda do - if line =~ /^\s+$/ - state = :pod_blank - end - @top_level.comment += filter(line) - end - - begin_noop = lambda do - if line =~ /^\s+$/ - state = :begin_blank - end - @top_level.comment += filter(line) - end - - # Now for the blocks that process code and comments... - - transit_to_pod = lambda do - case line - when /^=(?:pod|head\d+)/ - state = :pod_no_blank - @top_level.comment += filter(line) - when /^=over/ - state = :over_no_blank - @top_level.comment += filter(line) - when /^=(?:begin|for)/ - state = :begin_no_blank - end - end - - process_pod = lambda do - case line - when /^\s*$/ - state = :pod_blank - @top_level.comment += filter(line) - when /^=cut/ - state = :code_no_blank - when /^=end/ - $stderr.puts "'=end' unexpected at #{line_number} in #{@file_name}" - else - @top_level.comment += filter(line) - end - end - - - process_begin = lambda do - case line - when /^\s*$/ - state = :begin_blank - @top_level.comment += filter(line) - when /^=end/ - state = :code_no_blank - when /^=cut/ - $stderr.puts "'=cut' unexpected at #{line_number} in #{@file_name}" - else - @top_level.comment += filter(line) - end - - end - - - transitions = { :code_no_blank => code_noop, - :code_blank => transit_to_pod, - :pod_no_blank => pod_noop, - :pod_blank => process_pod, - :begin_no_blank => begin_noop, - :begin_blank => process_begin} - @content.each_line do |l| - line = l - line_number += 1 - transitions[state].call - end # each line - - @top_level - end - - # Filter the perl markup that does the same as the rdoc - # filtering. Only basic for now. Will probably need a - # proper parser to cope with C<<...>> etc - def filter(comment) - return '' if comment =~ /^=pod\s*$/ - comment.gsub!(/^=pod/, '==') - comment.gsub!(/^=head(\d+)/) do - "=" * $1.to_i - end - comment.gsub!(/=item/, ''); - comment.gsub!(/C<(.*?)>/, '\1'); - comment.gsub!(/I<(.*?)>/, '\1'); - comment.gsub!(/B<(.*?)>/, '\1'); - comment - end - -end - diff --git a/lib/rdoc/parser/ruby.rb b/lib/rdoc/parser/ruby.rb index 2874c47a20..e6f07d66da 100644 --- a/lib/rdoc/parser/ruby.rb +++ b/lib/rdoc/parser/ruby.rb @@ -11,8 +11,8 @@ require 'rdoc/ruby_token' require 'rdoc/ruby_lex' require 'rdoc/code_objects' -require 'rdoc/tokenstream' -require 'rdoc/markup/preprocess' +require 'rdoc/token_stream' +require 'rdoc/markup/pre_process' require 'rdoc/parser' require 'rdoc/parser/ruby_tools' @@ -162,6 +162,9 @@ class RDoc::Parser::Ruby < RDoc::Parser SINGLE = "<<" + ## + # Creates a new Ruby parser. + def initialize(top_level, file_name, content, options, stats) super @@ -209,10 +212,13 @@ class RDoc::Parser::Ruby < RDoc::Parser comment end + ## + # Aborts with +msg+ + def error(msg) msg = make_message msg - $stderr.puts msg - exit false + + abort msg end ## @@ -229,6 +235,10 @@ class RDoc::Parser::Ruby < RDoc::Parser meth end + ## + # Looks for a true or false token. Returns false if TkFALSE or TkNIL are + # found. + def get_bool skip_tkspace tk = get_tk @@ -245,20 +255,24 @@ class RDoc::Parser::Ruby < RDoc::Parser ## # Look for the name of a class of module (optionally with a leading :: or - # with :: separated named) and return the ultimate name and container + # with :: separated named) and return the ultimate name, the associated + # container, and the given name (with the ::). def get_class_or_module(container) skip_tkspace name_t = get_tk + given_name = '' # class ::A -> A is in the top level case name_t when TkCOLON2, TkCOLON3 then # bug name_t = get_tk container = @top_level + given_name << '::' end skip_tkspace false + given_name << name_t.name while TkCOLON2 === peek_tk do prev_container = container @@ -268,9 +282,10 @@ class RDoc::Parser::Ruby < RDoc::Parser end get_tk name_t = get_tk + given_name << '::' << name_t.name end skip_tkspace false - return [container, name_t] + return [container, name_t, given_name] end ## @@ -347,6 +362,9 @@ class RDoc::Parser::Ruby < RDoc::Parser name end + ## + # Extracts a name or symbol from the token stream. + def get_symbol_or_name tk = get_tk case tk @@ -361,7 +379,10 @@ class RDoc::Parser::Ruby < RDoc::Parser text when TkId, TkOp then tk.name - when TkSTRING, TkDSTRING then + when TkAMPER, + TkDSTRING, + TkSTAR, + TkSTRING then tk.text else raise RDoc::Error, "Name or symbol expected (got #{tk})" @@ -374,7 +395,7 @@ class RDoc::Parser::Ruby < RDoc::Parser # # :stopdoc: # # Don't display comment from this point forward # - # This routine modifies it's parameter + # This routine modifies its +comment+ parameter. def look_for_directives_in(context, comment) preprocess = RDoc::Markup::PreProcess.new @file_name, @options.rdoc_include @@ -382,9 +403,10 @@ class RDoc::Parser::Ruby < RDoc::Parser preprocess.handle comment, context do |directive, param| case directive when 'enddoc' then - throw :enddoc + context.done_documenting = true + '' when 'main' then - @options.main_page = param + @options.main_page = param if @options.respond_to? :main_page '' when 'method', 'singleton-method', 'attr', 'attr_accessor', 'attr_reader', 'attr_writer' then @@ -401,7 +423,7 @@ class RDoc::Parser::Ruby < RDoc::Parser context.stop_doc '' when 'title' then - @options.title = param + @options.default_title = param if @options.respond_to? :default_title= '' end end @@ -426,23 +448,28 @@ class RDoc::Parser::Ruby < RDoc::Parser def parse_attr(context, single, tk, comment) args = parse_symbol_arg 1 - if args.size > 0 + if args.size > 0 then name = args[0] rw = "R" skip_tkspace false tk = get_tk + if TkCOMMA === tk then rw = "RW" if get_bool else unget_tk tk end - att = RDoc::Attr.new get_tkread, name, rw, comment + + att = RDoc::Attr.new get_tkread, name, rw, comment, single == SINGLE + att.record_location @top_level + read_documentation_modifiers att, RDoc::ATTR_MODIFIERS - if att.document_self - context.add_attribute(att) - end + + context.add_attribute att if att.document_self + + @stats.add_attribute att else - warn("'attr' ignored - looks like a variable") + warn "'attr' ignored - looks like a variable" end end @@ -452,12 +479,8 @@ class RDoc::Parser::Ruby < RDoc::Parser def parse_attr_accessor(context, single, tk, comment) args = parse_symbol_arg - get_tkread - rw = "?" - # TODO If nodoc is given, don't document any of them - tmp = RDoc::CodeObject.new read_documentation_modifiers tmp, RDoc::ATTR_MODIFIERS return unless tmp.document_self @@ -471,17 +494,25 @@ class RDoc::Parser::Ruby < RDoc::Parser end for name in args - att = RDoc::Attr.new get_tkread, name, rw, comment + att = RDoc::Attr.new get_tkread, name, rw, comment, single == SINGLE + att.record_location @top_level + context.add_attribute att + @stats.add_attribute att end end + ## + # Parses an +alias+ in +context+ with +comment+ + def parse_alias(context, single, tk, comment) skip_tkspace + if TkLPAREN === peek_tk then get_tk skip_tkspace end + new_name = get_symbol_or_name @scanner.instance_eval { @lex_state = EXPR_FNAME } @@ -498,11 +529,20 @@ class RDoc::Parser::Ruby < RDoc::Parser return end - al = RDoc::Alias.new get_tkread, old_name, new_name, comment + al = RDoc::Alias.new(get_tkread, old_name, new_name, comment, + single == SINGLE) + al.record_location @top_level + read_documentation_modifiers al, RDoc::ATTR_MODIFIERS context.add_alias al if al.document_self + @stats.add_alias al + + al end + ## + # Extracts call parameters from the token stream. + def parse_call_parameters(tk) end_token = case tk when TkLPAREN, TkfLPAREN @@ -540,28 +580,33 @@ class RDoc::Parser::Ruby < RDoc::Parser res end + ## + # Parses a class in +context+ with +comment+ + def parse_class(container, single, tk, comment) - container, name_t = get_class_or_module container + declaration_context = container + container, name_t, given_name = get_class_or_module container case name_t when TkCONSTANT name = name_t.name - superclass = "Object" + superclass = '::Object' if TkLT === peek_tk then get_tk skip_tkspace superclass = get_class_specification - superclass = "" if superclass.empty? + superclass = '(unknown)' if superclass.empty? end cls_type = single == SINGLE ? RDoc::SingleClass : RDoc::NormalClass - cls = container.add_class cls_type, name, superclass + cls = declaration_context.add_class cls_type, given_name, superclass read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS cls.record_location @top_level - cls.comment = comment + cls.comment = comment if cls.document_self + @top_level.add_to_classes_or_modules cls @stats.add_class cls parse_statements cls @@ -569,7 +614,7 @@ class RDoc::Parser::Ruby < RDoc::Parser case name = get_class_specification when "self", container.name parse_statements container, SINGLE - when /\A[A-Z]/ + else other = RDoc::TopLevel.find_class_named name unless other then @@ -578,6 +623,15 @@ class RDoc::Parser::Ruby < RDoc::Parser other.comment = comment end + # notify :nodoc: all if not a constant-named class/module + # (and remove any comment) + unless name =~ /\A(::)?[A-Z]/ + other.document_self = nil + other.document_children = false + other.clear_comment + end + + @top_level.add_to_classes_or_modules other @stats.add_class other read_documentation_modifiers other, RDoc::CLASS_MODIFIERS @@ -589,9 +643,15 @@ class RDoc::Parser::Ruby < RDoc::Parser end end + ## + # Parses a constant in +context+ with +comment+ + def parse_constant(container, tk, comment) name = tk.name skip_tkspace false + + return unless name =~ /^\w+$/ + eq_tk = get_tk unless TkASSIGN === eq_tk then @@ -615,9 +675,9 @@ class RDoc::Parser::Ruby < RDoc::Parser loop do case tk when TkSEMICOLON then - break - when TkLPAREN, TkfLPAREN, TkLBRACE, TkLBRACK, TkDO, TkIF, TkUNLESS, - TkCASE then + break if nest <= 0 + when TkLPAREN, TkfLPAREN, TkLBRACE, TkfLBRACE, TkLBRACK, TkfLBRACK, + TkDO, TkIF, TkUNLESS, TkCASE, TkDEF, TkBEGIN then nest += 1 when TkRPAREN, TkRBRACE, TkRBRACK, TkEND then nest -= 1 @@ -654,10 +714,11 @@ class RDoc::Parser::Ruby < RDoc::Parser tk = get_tk end - res = get_tkread.tr("\n", " ").strip + res = get_tkread.gsub(/^[ \t]+/, '').strip res = "" if res == ";" con = RDoc::Constant.new name, res, comment + con.record_location @top_level read_documentation_modifiers con, RDoc::CONSTANT_MODIFIERS @stats.add_constant con @@ -679,6 +740,7 @@ class RDoc::Parser::Ruby < RDoc::Parser name = $1 unless $1.empty? meth = RDoc::GhostMethod.new get_tkread, name + meth.record_location @top_level meth.singleton = singleton meth.start_collecting_tokens @@ -709,13 +771,19 @@ class RDoc::Parser::Ruby < RDoc::Parser name = $3 unless $3.empty? + # TODO authorize 'singleton-attr...'? att = RDoc::Attr.new get_tkread, name, rw, comment + att.record_location @top_level + container.add_attribute att - @stats.add_method att + @stats.add_attribute att end end + ## + # Parses an +include+ in +context+ with +comment+ + def parse_include(context, comment) loop do skip_tkspace_comment @@ -759,8 +827,6 @@ class RDoc::Parser::Ruby < RDoc::Parser def parse_meta_attr(context, single, tk, comment) args = parse_symbol_arg - get_tkread - rw = "?" # If nodoc is given, don't document any of them @@ -779,12 +845,19 @@ class RDoc::Parser::Ruby < RDoc::Parser end if name then - att = RDoc::Attr.new get_tkread, name, rw, comment + att = RDoc::Attr.new get_tkread, name, rw, comment, single == SINGLE + att.record_location @top_level + context.add_attribute att + @stats.add_attribute att else args.each do |attr_name| - att = RDoc::Attr.new get_tkread, attr_name, rw, comment + att = RDoc::Attr.new(get_tkread, attr_name, rw, comment, + single == SINGLE) + att.record_location @top_level + context.add_attribute att + @stats.add_attribute att end end end @@ -825,6 +898,7 @@ class RDoc::Parser::Ruby < RDoc::Parser end meth = RDoc::MetaMethod.new get_tkread, name + meth.record_location @top_level meth.singleton = singleton remove_token_listener self @@ -882,7 +956,7 @@ class RDoc::Parser::Ruby < RDoc::Parser token_listener self do @scanner.instance_eval do @lex_state = EXPR_FNAME end - skip_tkspace false + skip_tkspace name_t = get_tk back_tk = skip_tkspace meth = nil @@ -922,11 +996,17 @@ class RDoc::Parser::Ruby < RDoc::Parser container.record_location @top_level end - when TkIDENTIFIER, TkIVAR then + when TkIDENTIFIER, TkIVAR, TkGVAR then dummy = RDoc::Context.new dummy.parent = container skip_method dummy return + when TkTRUE, TkFALSE, TkNIL then + klass_name = "#{name_t.name.capitalize}Class" + container = RDoc::TopLevel.find_class_named klass_name + container ||= @top_level.add_class RDoc::NormalClass, klass_name + + name = name_t2.name else warn "unexpected method name token #{name_t.inspect}" # break @@ -959,6 +1039,8 @@ class RDoc::Parser::Ruby < RDoc::Parser end end + meth.record_location @top_level + meth.start_collecting_tokens indent = TkSPACE.new nil, 1, 1 indent.set_text " " * column @@ -1001,6 +1083,9 @@ class RDoc::Parser::Ruby < RDoc::Parser @stats.add_method meth end + ## + # Extracts +yield+ parameters from +method+ + def parse_method_or_yield_parameters(method = nil, modifiers = RDoc::METHOD_MODIFIERS) skip_tkspace false @@ -1024,14 +1109,16 @@ class RDoc::Parser::Ruby < RDoc::Parser loop do case tk when TkSEMICOLON then - break - when TkLBRACE then + break if nest == 0 + when TkLBRACE, TkfLBRACE then nest += 1 when TkRBRACE then - # we might have a.each {|i| yield i } - unget_tk(tk) if nest.zero? nest -= 1 - break if nest <= 0 + if nest <= 0 + # we might have a.each { |i| yield i } + unget_tk(tk) if nest < 0 + break + end when TkLPAREN, TkfLPAREN then nest += 1 when end_token then @@ -1041,6 +1128,8 @@ class RDoc::Parser::Ruby < RDoc::Parser else break unless @scanner.continue end + when TkRPAREN then + nest -= 1 when method && method.block_params.nil? && TkCOMMENT then unget_tk tk read_documentation_modifiers method, modifiers @@ -1078,8 +1167,11 @@ class RDoc::Parser::Ruby < RDoc::Parser end end + ## + # Parses an RDoc::NormalModule in +container+ with +comment+ + def parse_module(container, single, tk, comment) - container, name_t = get_class_or_module container + container, name_t, = get_class_or_module container name = name_t.name @@ -1087,12 +1179,16 @@ class RDoc::Parser::Ruby < RDoc::Parser mod.record_location @top_level read_documentation_modifiers mod, RDoc::CLASS_MODIFIERS + mod.comment = comment if mod.document_self parse_statements(mod) - mod.comment = comment + @top_level.add_to_classes_or_modules mod @stats.add_module mod end + ## + # Parses an RDoc::Require in +context+ containing +comment+ + def parse_require(context, comment) skip_tkspace_comment tk = get_tk @@ -1105,7 +1201,7 @@ class RDoc::Parser::Ruby < RDoc::Parser name = tk.text if TkSTRING === tk if name then - context.add_require RDoc::Require.new(name, comment) + @top_level.add_require RDoc::Require.new(name, comment) else unget_tk tk end @@ -1206,7 +1302,7 @@ class RDoc::Parser::Ruby < RDoc::Parser # We can't solve the general case, but we can handle most occurrences by # ignoring a do at the end of a line. - when TkUNTIL, TkWHILE then + when TkUNTIL, TkWHILE then nest += 1 skip_optional_do_after_expression @@ -1275,9 +1371,14 @@ class RDoc::Parser::Ruby < RDoc::Parser end end + ## + # Parse up to +no+ symbol arguments + def parse_symbol_arg(no = nil) args = [] + skip_tkspace_comment + case tk = get_tk when TkLPAREN loop do @@ -1320,28 +1421,40 @@ class RDoc::Parser::Ruby < RDoc::Parser end end end + args end + ## + # Returns symbol text from the next token + def parse_symbol_in_arg case tk = get_tk when TkSYMBOL tk.text.sub(/^:/, '') when TkSTRING eval @read[-1] + when TkDSTRING, TkIDENTIFIER then + nil # ignore else warn("Expected symbol or string, got #{tk.inspect}") if $DEBUG_RDOC nil end end + ## + # Parses statements at the toplevel in +container+ + def parse_top_level_statements(container) comment = collect_first_comment look_for_directives_in(container, comment) - container.comment = comment unless comment.empty? + container.comment = comment if container.document_self unless comment.empty? parse_statements container, NORMAL, nil, comment end + ## + # Determines the visibility in +container+ from +tk+ + def parse_visibility(container, single, tk) singleton = (single == SINGLE) @@ -1383,7 +1496,8 @@ class RDoc::Parser::Ruby < RDoc::Parser container.methods_matching args do |m| s_m = m.dup - s_m.singleton = true if RDoc::AnyMethod === s_m + s_m.record_location @top_level + s_m.singleton = true s_m.visibility = :public module_functions << s_m end @@ -1403,6 +1517,9 @@ class RDoc::Parser::Ruby < RDoc::Parser end end + ## + # Determines the block parameter for +context+ + def parse_yield(context, single, tk, method) return if method.block_params @@ -1423,93 +1540,81 @@ class RDoc::Parser::Ruby < RDoc::Parser # # We return the directive name and any parameters as a two element array - def read_directive(allowed) + def read_directive allowed tk = get_tk - result = nil if TkCOMMENT === tk then - if tk.text =~ /\s*:?(\w+):\s*(.*)/ then - directive = $1.downcase - if allowed.include? directive then - result = [directive, $2] - end - end + return unless tk.text =~ /\s*:?(\w+):\s*(.*)/ + + directive = $1.downcase + + return [directive, $2] if allowed.include? directive else unget_tk tk end - - result end + ## + # Handles the directive for +context+ if the directive is listed in +allow+. + # This method is called for directives following a definition. + def read_documentation_modifiers(context, allow) - dir = read_directive(allow) + directive, value = read_directive allow - case dir[0] - when "notnew", "not_new", "not-new" then + return unless directive + + case directive + when 'notnew', 'not_new', 'not-new' then context.dont_rename_initialize = true - - when "nodoc" then - context.document_self = false - if dir[1].downcase == "all" - context.document_children = false - end - - when "doc" then - context.document_self = true - context.force_documentation = true - - when "yield", "yields" then - unless context.params.nil? - context.params.sub!(/(,|)\s*&\w+/,'') # remove parameter &proc - end - - context.block_params = dir[1] - - when "arg", "args" then - context.params = dir[1] - end if dir + else + RDoc::Parser.process_directive context, directive, value + end end + ## + # Removes private comments from +comment+ + def remove_private_comments(comment) - comment.gsub!(/^#--\n.*?^#\+\+/m, '') - comment.sub!(/^#--\n.*/m, '') + comment.gsub!(/^#--\n.*?^#\+\+\n?/m, '') + comment.sub!(/^#--\n.*\n?/m, '') end + ## + # Scans this ruby file for ruby constructs + def scan reset catch :eof do - catch :enddoc do - begin - parse_top_level_statements @top_level - rescue StandardError => e - bytes = '' + begin + parse_top_level_statements @top_level + rescue StandardError => e + bytes = '' - 20.times do @scanner.ungetc end - count = 0 - 60.times do |i| - count = i - byte = @scanner.getc - break unless byte - bytes << byte - end - count -= 20 - count.times do @scanner.ungetc end + 20.times do @scanner.ungetc end + count = 0 + 60.times do |i| + count = i + byte = @scanner.getc + break unless byte + bytes << byte + end + count -= 20 + count.times do @scanner.ungetc end - $stderr.puts <<-EOF + $stderr.puts <<-EOF #{self.class} failure around line #{@scanner.line_no} of #{@file_name} - EOF + EOF - unless bytes.empty? then - $stderr.puts - $stderr.puts bytes.inspect - end - - raise e + unless bytes.empty? then + $stderr.puts + $stderr.puts bytes.inspect end + + raise e end end @@ -1574,6 +1679,9 @@ class RDoc::Parser::Ruby < RDoc::Parser unget_tk(tk) unless TkIN === tk end + ## + # Skips the next method in +container+ + def skip_method container meth = RDoc::AnyMethod.new "", "anon" parse_method_parameters meth @@ -1591,6 +1699,9 @@ class RDoc::Parser::Ruby < RDoc::Parser end end + ## + # Prints +msg+ to +$stderr+ unless we're being quiet + def warn(msg) return if @options.quiet msg = make_message msg diff --git a/lib/rdoc/parser/ruby_tools.rb b/lib/rdoc/parser/ruby_tools.rb index 90c03307b4..3f6190884e 100644 --- a/lib/rdoc/parser/ruby_tools.rb +++ b/lib/rdoc/parser/ruby_tools.rb @@ -49,7 +49,6 @@ module RDoc::Parser::RubyTools obj.pop_token end if @token_listeners else - warn("':' not followed by identifier or operator") tk = tk1 end end @@ -62,6 +61,10 @@ module RDoc::Parser::RubyTools tk end + ## + # Reads and returns all tokens up to one of +tokens+. Leaves the matched + # token in the token list. + def get_tk_until(*tokens) read = [] diff --git a/lib/rdoc/parser/simple.rb b/lib/rdoc/parser/simple.rb index e99d2d4319..1e82eb5097 100644 --- a/lib/rdoc/parser/simple.rb +++ b/lib/rdoc/parser/simple.rb @@ -1,7 +1,6 @@ ## # Parse a non-source file. We basically take the whole thing as one big -# comment. If the first character in the file is '#', we strip leading pound -# signs. +# comment. class RDoc::Parser::Simple < RDoc::Parser @@ -32,10 +31,16 @@ class RDoc::Parser::Simple < RDoc::Parser @top_level end - def remove_private_comments(comment) - comment.gsub(/^--\n.*?^\+\+/m, '').sub(/^--\n.*/m, '') + ## + # Removes comments wrapped in --/++ + + def remove_private_comments text + text.gsub(/^--\n.*?^\+\+/m, '').sub(/^--\n.*/m, '') end + ## + # Removes the encoding magic comment from +text+ + def remove_coding_comment text text.sub(/\A# .*coding[=:].*$/, '') end diff --git a/lib/rdoc/rdoc.rb b/lib/rdoc/rdoc.rb index 8771b408d9..a885d8dded 100644 --- a/lib/rdoc/rdoc.rb +++ b/lib/rdoc/rdoc.rb @@ -1,5 +1,6 @@ require 'rdoc' +require 'rdoc/encoding' require 'rdoc/parser' # Simple must come first @@ -23,7 +24,28 @@ require 'time' # rdoc.document(args) # # Where +args+ is an array of strings, each corresponding to an argument you'd -# give rdoc on the command line. See rdoc/rdoc.rb for details. +# give rdoc on the command line. See rdoc --help for details. +# +# = Plugins +# +# When you require 'rdoc/rdoc' RDoc looks for 'rdoc/discover' files +# in your installed gems. This can be used to load alternate generators or +# add additional preprocessor directives. +# +# You will want to wrap your plugin loading in an RDoc version check. +# Something like: +# +# begin +# gem 'rdoc', '~> 3' +# require 'path/to/my/awesome/rdoc/plugin' +# rescue Gem::LoadError +# end +# +# The most obvious plugin type is a new output generator. See RDoc::Generator +# for details. +# +# You can also hook into RDoc::Markup to add new directives (:nodoc: is a +# directive). See RDoc::Markup::PreProcess::register for details. class RDoc::RDoc @@ -79,6 +101,10 @@ class RDoc::RDoc @current = rdoc end + ## + # Creates a new RDoc::RDoc instance. Call #document to parse files and + # generate documentation. + def initialize @current = nil @exclude = nil @@ -142,7 +168,9 @@ class RDoc::RDoc last = {} - if File.exist? dir then + if @options.dry_run then + # do nothing + elsif File.exist? dir then error "#{dir} exists and is not a directory" unless File.directory? dir begin @@ -167,7 +195,7 @@ you'll need to specify a different output directory name (using the --op
    option) ERROR - end + end unless @options.force_output else FileUtils.mkdir_p dir end @@ -179,6 +207,8 @@ option) # Update the flag file in an output directory. def update_output_dir(op_dir, time, last = {}) + return if @options.dry_run + open output_flag_file(op_dir), "w" do |f| f.puts time.rfc2822 last.each do |n, t| @@ -277,7 +307,9 @@ option) def parse_file filename @stats.add_file filename - content = read_file_contents filename + encoding = @options.encoding if defined?(Encoding) + + content = RDoc::Encoding.read_file filename, encoding return unless content @@ -288,11 +320,22 @@ option) return unless parser parser.scan + + # restart documentation for the classes & modules found + top_level.classes_or_modules.each do |cm| + cm.done_documenting = false + end + + top_level + rescue => e $stderr.puts <<-EOF Before reporting this, could you check that the file you're documenting -compiles cleanly--RDoc is not a full Ruby parser, and gets confused easily if -fed invalid programs. +has proper syntax: + + #{Gem.ruby} -c #{filename} + +RDoc is not a full Ruby parser and will fail when fed invalid ruby programs. The internal error was: @@ -300,7 +343,7 @@ The internal error was: EOF - $stderr.puts e.backtrace.join("\n\t") if $RDOC_DEBUG + $stderr.puts e.backtrace.join("\n\t") if $DEBUG_RDOC raise e nil @@ -344,11 +387,9 @@ The internal error was: # For simplicity, +argv+ is an array of strings, equivalent to the strings # that would be passed on the command line. (This isn't a coincidence, as # we _do_ pass in ARGV when running interactively). For a list of options, - # see rdoc/rdoc.rb. By default, output will be stored in a directory + # see rdoc --help. By default, output will be stored in a directory # called +doc+ below the current directory, so make sure you're somewhere # writable before invoking. - # - # Throws: RDoc::Error on error def document(argv) RDoc::TopLevel.reset @@ -364,29 +405,36 @@ The internal error was: @exclude = @options.exclude - @last_modified = setup_output_dir @options.op_dir, @options.force_update + unless @options.coverage_report then + @last_modified = setup_output_dir @options.op_dir, @options.force_update + end start_time = Time.now file_info = parse_files @options.files - @options.title = "RDoc Documentation" + @options.default_title = "RDoc Documentation" - if file_info.empty? + RDoc::TopLevel.complete @options.visibility + + if @options.coverage_report then + puts + puts @stats.report + elsif file_info.empty? $stderr.puts "\nNo newer files." unless @options.quiet else gen_klass = @options.generator - unless @options.quiet then - $stderr.puts "\nGenerating #{gen_klass.name.sub(/^.*::/, '')}..." - end - - @generator = gen_klass.for @options + @generator = gen_klass.new @options Dir.chdir @options.op_dir do begin self.class.current = self + unless @options.quiet then + $stderr.puts "\nGenerating #{gen_klass.name.sub(/^.*::/, '')} format into #{Dir.pwd}..." + end + @generator.generate file_info update_output_dir ".", start_time, @last_modified ensure @@ -397,26 +445,10 @@ The internal error was: unless @options.quiet or not @stats then puts - @stats.print - end - end - - def read_file_contents(filename) - content = open filename, "rb" do |f| f.read end - - utf8 = content.sub!(/\A\xef\xbb\xbf/, '') - if defined? Encoding then - if /coding[=:]\s*([^\s;]+)/i =~ content[%r"\A(?:#!.*\n)?.*\n"] - enc = ::Encoding.find($1) - end - if enc ||= (Encoding::UTF_8 if utf8) - content.force_encoding(enc) - end + puts @stats.summary end - content - rescue Errno::EISDIR, Errno::ENOENT - nil + exit @stats.fully_documented? if @options.coverage_report end ## diff --git a/lib/rdoc/require.rb b/lib/rdoc/require.rb index 407b55af35..65d3d464da 100644 --- a/lib/rdoc/require.rb +++ b/lib/rdoc/require.rb @@ -16,6 +16,7 @@ class RDoc::Require < RDoc::CodeObject def initialize(name, comment) super() @name = name.gsub(/'|"/, "") #' + @top_level = nil self.comment = comment end @@ -28,5 +29,25 @@ class RDoc::Require < RDoc::CodeObject ] 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 diff --git a/lib/rdoc/ri/driver.rb b/lib/rdoc/ri/driver.rb index f3fd9539e3..9d61b1f243 100644 --- a/lib/rdoc/ri/driver.rb +++ b/lib/rdoc/ri/driver.rb @@ -6,6 +6,11 @@ begin rescue LoadError end +begin + require 'win32console' +rescue LoadError +end + require 'rdoc/ri' require 'rdoc/ri/paths' require 'rdoc/markup' @@ -55,6 +60,9 @@ class RDoc::RI::Driver end end + ## + # An RDoc::RI::Store for each entry in the RI path + attr_accessor :stores ## @@ -97,23 +105,10 @@ class RDoc::RI::Driver ## # Parses +argv+ and returns a Hash of options - def self.process_args argv = [] + def self.process_args argv options = default_options - opts = OptionParser.new - setup_options(opts, options) - argv = ENV['RI'].to_s.split.concat argv - opts.parse!(argv) - - fixup_options(options, argv) - - rescue OptionParser::ParseError => e - puts opts, nil, e - abort - end - - def self.setup_options(opt, options) - begin + opts = OptionParser.new do |opt| opt.accept File do |file,| File.readable?(file) and not File.directory?(file) and file end @@ -133,7 +128,7 @@ Where name can be: All class names may be abbreviated to their minimum unambiguous form. If a name is ambiguous, all valid options will be listed. -The form '.' method matches either class or instance methods, while #method +A '.' matches either class or instance methods, while #method matches only instance and ::method matches only class methods. For example: @@ -143,7 +138,7 @@ For example: #{opt.program_name} File.new #{opt.program_name} zip -Note that shell quoting may be required for method names containing +Note that shell quoting or escaping may be required for method names containing punctuation: #{opt.program_name} 'Array.[]' @@ -287,9 +282,11 @@ Options may also be set in the 'RI' environment variable. options[:dump_path] = value end end - end - def self.fixup_options(options, argv) + argv = ENV['RI'].to_s.split.concat argv + + opts.parse! argv + options[:names] = argv options[:use_stdout] ||= !$stdout.tty? @@ -297,6 +294,12 @@ Options may also be set in the 'RI' environment variable. options[:width] ||= 72 options + + rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e + puts opts + puts + puts e + exit 1 end ## @@ -359,7 +362,7 @@ Options may also be set in the 'RI' environment variable. paths = RDoc::Markup::Verbatim.new also_in.each do |store| - paths.parts.push ' ', store.friendly_path, "\n" + paths.parts.push store.friendly_path, "\n" end out << paths end @@ -427,7 +430,7 @@ Options may also be set in the 'RI' environment variable. verb = RDoc::Markup::Verbatim.new wout.each do |incl| - verb.push ' ', incl.name, "\n" + verb.push incl.name, "\n" end out << verb @@ -446,7 +449,7 @@ Options may also be set in the 'RI' environment variable. out << RDoc::Markup::BlankLine.new out.push(*methods.map do |method| - RDoc::Markup::Verbatim.new ' ', method + RDoc::Markup::Verbatim.new method end) out << RDoc::Markup::BlankLine.new @@ -664,8 +667,8 @@ Options may also be set in the 'RI' environment variable. if method.arglists then arglists = method.arglists.chomp.split "\n" - arglists = arglists.map { |line| [' ', line, "\n"] } - out << RDoc::Markup::Verbatim.new(*arglists.flatten) + arglists = arglists.map { |line| line + "\n" } + out << RDoc::Markup::Verbatim.new(*arglists) out << RDoc::Markup::Rule.new(1) end @@ -846,6 +849,17 @@ Options may also be set in the 'RI' environment variable. 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 @@ -1041,7 +1055,11 @@ Options may also be set in the 'RI' environment variable. pagers.compact.uniq.each do |pager| next unless pager - io = IO.popen pager, "w" rescue next + pager_cmd = pager.split.first + + next unless in_path? pager_cmd + + io = IO.popen(pager, 'w') rescue next next if $? and $?.exited? # pager didn't work diff --git a/lib/rdoc/ri/paths.rb b/lib/rdoc/ri/paths.rb index 9b338d7ad8..ec4d16b857 100644 --- a/lib/rdoc/ri/paths.rb +++ b/lib/rdoc/ri/paths.rb @@ -10,10 +10,21 @@ module RDoc::RI::Paths version = RbConfig::CONFIG['ruby_version'] - base = File.join RbConfig::CONFIG['ridir'], version + base = if RbConfig::CONFIG.key? 'ridir' then + File.join RbConfig::CONFIG['ridir'], version + else + File.join RbConfig::CONFIG['datadir'], 'ri', version + end + SYSDIR = File.join base, "system" SITEDIR = File.join base, "site" - HOMEDIR = (File.expand_path('~/.rdoc') rescue nil) + + homedir = File.expand_path('~') || + ENV['HOME'] || ENV['USERPROFILE'] || ENV['HOMEPATH'] + + HOMEDIR = if homedir then + File.join homedir, ".rdoc" + end #:startdoc: @gemdirs = nil diff --git a/lib/rdoc/ri/store.rb b/lib/rdoc/ri/store.rb index db02202f81..1fcd313d0f 100644 --- a/lib/rdoc/ri/store.rb +++ b/lib/rdoc/ri/store.rb @@ -10,6 +10,11 @@ require 'fileutils' class RDoc::RI::Store + ## + # If true this Store will not write any files + + attr_accessor :dry_run + ## # Path this store reads or writes @@ -21,14 +26,18 @@ class RDoc::RI::Store attr_accessor :type + ## + # The contents of the Store + attr_reader :cache ## # Creates a new Store of +type+ that will load or save to +path+ def initialize path, type = nil - @type = type - @path = path + @dry_run = false + @type = type + @path = path @cache = { :class_methods => {}, @@ -178,6 +187,8 @@ class RDoc::RI::Store @cache[:instance_methods].each do |_, m| m.uniq!; m.sort! end @cache[:modules].uniq!; @cache[:modules].sort! + return if @dry_run + open cache_path, 'wb' do |io| Marshal.dump @cache, io end @@ -187,7 +198,7 @@ class RDoc::RI::Store # Writes the ri data for +klass+ def save_class klass - FileUtils.mkdir_p class_path(klass.full_name) + FileUtils.mkdir_p class_path(klass.full_name) unless @dry_run @cache[:modules] << klass.full_name @@ -214,7 +225,7 @@ class RDoc::RI::Store @cache[:ancestors][klass.full_name].push(*ancestors) attributes = klass.attributes.map do |attribute| - "#{attribute.type} #{attribute.name}" + "#{attribute.definition} #{attribute.name}" end unless attributes.empty? then @@ -222,6 +233,8 @@ class RDoc::RI::Store @cache[:attributes][klass.full_name].push(*attributes) end + return if @dry_run + open path, 'wb' do |io| Marshal.dump klass, io end @@ -231,7 +244,7 @@ class RDoc::RI::Store # Writes the ri data for +method+ on +klass+ def save_method klass, method - FileUtils.mkdir_p class_path(klass.full_name) + FileUtils.mkdir_p class_path(klass.full_name) unless @dry_run cache = if method.singleton then @cache[:class_methods] @@ -241,6 +254,8 @@ class RDoc::RI::Store cache[klass.full_name] ||= [] cache[klass.full_name] << method.name + return if @dry_run + open method_file(klass.full_name, method.full_name), 'wb' do |io| Marshal.dump method, io end diff --git a/lib/rdoc/ruby_lex.rb b/lib/rdoc/ruby_lex.rb index fe289fb1a2..cbe3ec9061 100644 --- a/lib/rdoc/ruby_lex.rb +++ b/lib/rdoc/ruby_lex.rb @@ -92,9 +92,9 @@ class RDoc::RubyLex end def inspect # :nodoc: - "#<%s:0x%x lex_state %p space_seen %p>" % [ + "#<%s:0x%x pos %d lex_state %p space_seen %p>" % [ self.class, object_id, - @lex_state, @space_seen, + @io.pos, @lex_state, @space_seen, ] end @@ -149,6 +149,7 @@ class RDoc::RubyLex else @char_no += 1 end + c end @@ -674,7 +675,7 @@ class RDoc::RubyLex tk_c = TkLPAREN end @indent_stack.push tk_c - Token(tk_c) + Token tk_c end @OP.def_rule("[]", proc{|op, io| @lex_state == EXPR_FNAME}) do @@ -822,6 +823,12 @@ class RDoc::RubyLex end end + IDENT_RE = if defined? Encoding then + /[\w\u0080-\uFFFF]/u + else + /[\w\x80-\xFF]/ + end + def identify_identifier token = "" if peek(0) =~ /[$@]/ @@ -831,15 +838,7 @@ class RDoc::RubyLex end end - # HACK to avoid a warning the regexp is hidden behind an eval - # HACK need a better way to detect oniguruma - @identifier_re ||= if defined? Encoding then - eval '/[\p{Alnum}_]/u' - else - eval '/[\w\x80-\xff]/' - end - - while (ch = getc) =~ @identifier_re + while (ch = getc) =~ IDENT_RE do print " :#{ch}: " if RDoc::RubyLex.debug? token.concat ch end diff --git a/lib/rdoc/ruby_token.rb b/lib/rdoc/ruby_token.rb index 0c4f837193..93b7a5cbc8 100644 --- a/lib/rdoc/ruby_token.rb +++ b/lib/rdoc/ruby_token.rb @@ -178,7 +178,7 @@ module RDoc::RubyToken end class TkUnknownChar < Token - def initialize(seek, line_no, char_no, id) + def initialize(seek, line_no, char_no, name) super(seek, line_no, char_no) @name = name end @@ -253,7 +253,7 @@ module RDoc::RubyToken [:TkWHILE, TkKW, "while", EXPR_BEG, :TkWHILE_MOD], [:TkUNTIL, TkKW, "until", EXPR_BEG, :TkUNTIL_MOD], [:TkFOR, TkKW, "for", EXPR_BEG], - [:TkBREAK, TkKW, "break", EXPR_END], + [:TkBREAK, TkKW, "break", EXPR_MID], [:TkNEXT, TkKW, "next", EXPR_END], [:TkREDO, TkKW, "redo", EXPR_END], [:TkRETRY, TkKW, "retry", EXPR_END], diff --git a/lib/rdoc/single_class.rb b/lib/rdoc/single_class.rb index 1226d56f84..60336a759b 100644 --- a/lib/rdoc/single_class.rb +++ b/lib/rdoc/single_class.rb @@ -5,8 +5,9 @@ require 'rdoc/class_module' class RDoc::SingleClass < RDoc::ClassModule + # Adds the superclass to the included modules. def ancestors - includes + [superclass] + superclass ? super + [superclass] : super end end diff --git a/lib/rdoc/stats.rb b/lib/rdoc/stats.rb index 6a5d96faa8..e0af445539 100644 --- a/lib/rdoc/stats.rb +++ b/lib/rdoc/stats.rb @@ -1,247 +1,287 @@ require 'rdoc' ## -# RDoc stats collector +# RDoc statistics collector which prints a summary and report of a project's +# documentation totals. class RDoc::Stats - attr_reader :nodoc_constants - attr_reader :nodoc_methods + ## + # Count of files parsed during parsing + + attr_reader :files_so_far + + ## + # Total number of files found - attr_reader :num_constants attr_reader :num_files - attr_reader :num_methods - attr_reader :total_files + ## + # Creates a new Stats that will have +num_files+. +verbosity+ defaults to 1 + # which will create an RDoc::Stats::Normal outputter. - def initialize(total_files, verbosity = 1) - @nodoc_constants = 0 - @nodoc_methods = 0 - - @num_constants = 0 - @num_files = 0 - @num_methods = 0 - - @total_files = total_files + def initialize num_files, verbosity = 1 + @files_so_far = 0 + @num_files = num_files + @fully_documented = nil @start = Time.now @display = case verbosity - when 0 then Quiet.new total_files - when 1 then Normal.new total_files - else Verbose.new total_files + when 0 then Quiet.new num_files + when 1 then Normal.new num_files + else Verbose.new num_files end end - def begin_adding - @display.begin_adding - end + ## + # Records the parsing of an alias +as+. - def add_alias(as) + def add_alias as @display.print_alias as - @num_methods += 1 - @nodoc_methods += 1 if as.document_self and as.comment.empty? end - def add_class(klass) + ## + # Records the parsing of an attribute +attribute+ + + def add_attribute attribute + @display.print_attribute attribute + end + + ## + # Records the parsing of a class +klass+ + + def add_class klass @display.print_class klass end - def add_constant(constant) + ## + # Records the parsing of +constant+ + + def add_constant constant @display.print_constant constant - @num_constants += 1 - @nodoc_constants += 1 if constant.document_self and constant.comment.empty? end + ## + # Records the parsing of +file+ + def add_file(file) - @display.print_file @num_files, file - @num_files += 1 + @files_so_far += 1 + @display.print_file @files_so_far, file end + ## + # Records the parsing of +method+ + def add_method(method) @display.print_method method - @num_methods += 1 - @nodoc_methods += 1 if method.document_self and method.comment.empty? end + ## + # Records the parsing of a module +mod+ + def add_module(mod) @display.print_module mod end + ## + # Call this to mark the beginning of parsing for display purposes + + def begin_adding + @display.begin_adding + end + + ## + # Calculates documentation totals and percentages + + def calculate + return if @percent_doc + + ucm = RDoc::TopLevel.unique_classes_and_modules + constants = [] + ucm.each { |cm| constants.concat cm.constants } + + methods = [] + ucm.each { |cm| methods.concat cm.method_list } + + attributes = [] + ucm.each { |cm| attributes.concat cm.attributes } + + @num_attributes, @undoc_attributes = doc_stats attributes + @num_classes, @undoc_classes = doc_stats RDoc::TopLevel.unique_classes + @num_constants, @undoc_constants = doc_stats constants + @num_methods, @undoc_methods = doc_stats methods + @num_modules, @undoc_modules = doc_stats RDoc::TopLevel.unique_modules + + @num_items = + @num_attributes + + @num_classes + + @num_constants + + @num_methods + + @num_modules + + @undoc_items = + @undoc_attributes + + @undoc_classes + + @undoc_constants + + @undoc_methods + + @undoc_modules + + @doc_items = @num_items - @undoc_items + + @fully_documented = (@num_items - @doc_items) == 0 + + @percent_doc = @doc_items.to_f / @num_items * 100 if @num_items.nonzero? + end + + ## + # Returns the length and number of undocumented items in +collection+. + + def doc_stats collection + [collection.length, collection.count { |item| not item.documented? }] + end + + ## + # Call this to mark the end of parsing for display purposes + def done_adding @display.done_adding end - def print - classes = RDoc::TopLevel.classes - num_classes = classes.length - nodoc_classes = classes.select do |klass| - klass.document_self and klass.comment.empty? - end.length + ## + # The documentation status of this project. +true+ when 100%, +false+ when + # less than 100% and +nil+ when unknown. + # + # Set by calling #calculate - modules = RDoc::TopLevel.modules - num_modules = modules.length - nodoc_modules = modules.select do |mod| - mod.document_self and mod.comment.empty? - end.length - - items = num_classes + @num_constants + num_modules + @num_methods - doc_items = items - - nodoc_classes - @nodoc_constants - nodoc_modules - @nodoc_methods - - percent_doc = doc_items.to_f / items * 100 if items.nonzero? - - puts "Files: %5d" % @num_files - puts "Classes: %5d (%5d undocumented)" % [num_classes, nodoc_classes] - puts "Constants: %5d (%5d undocumented)" % - [@num_constants, @nodoc_constants] - puts "Modules: %5d (%5d undocumented)" % [num_modules, nodoc_modules] - puts "Methods: %5d (%5d undocumented)" % [@num_methods, @nodoc_methods] - puts "%6.2f%% documented" % percent_doc if percent_doc - puts - puts "Elapsed: %0.1fs" % (Time.now - @start) + def fully_documented? + @fully_documented end ## - # Stats printer that prints nothing + # Returns a report on which items are not documented - class Quiet + def report + report = [] - def initialize total_files - @total_files = total_files + calculate + + if @num_items == @doc_items then + report << '100% documentation!' + report << nil + report << 'Great Job!' + + return report.join "\n" end - ## - # Prints a message at the beginning of parsing + report << 'The following items are not documented:' + report << nil - def begin_adding(*) end + ucm = RDoc::TopLevel.unique_classes_and_modules - ## - # Prints when an alias is added + ucm.sort.each do |cm| + type = case cm # TODO #definition + when RDoc::NormalClass then 'class' + when RDoc::SingleClass then 'class <<' + when RDoc::NormalModule then 'module' + end - def print_alias(*) end + if cm.fully_documented? then + next + elsif cm.in_files.empty? or + (cm.constants.empty? and cm.method_list.empty?) then + report << "# #{type} #{cm.full_name} is referenced but empty." + report << '#' + report << '# It probably came from another project. ' \ + 'I\'m sorry I\'m holding it against you.' + report << nil - ## - # Prints when a class is added + next + elsif cm.documented? then + report << "#{type} #{cm.full_name} # is documented" + else + report << '# in files:' - 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 printer that prints just the files being documented with a progress - # bar - - class Normal < Quiet - - def begin_adding # :nodoc: - puts "Parsing sources..." - end - - ## - # Prints a file with a progress bar - - def print_file(files_so_far, filename) - progress_bar = sprintf("%3d%% [%2d/%2d] ", - 100 * (files_so_far + 1) / @total_files, - files_so_far + 1, - @total_files) - - if $stdout.tty? - # 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 - # Turn "some_long_filename.rb" to "...ong_filename.rb" - filename = filename[(filename.size - max_filename_size) .. -1] - filename[0..2] = "..." + cm.in_files.each do |file| + report << "# #{file.full_name}" 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 + report << nil - $stdout.print("#{line}\r") - else - $stdout.puts "#{progress_bar} #{filename}" + report << "#{type} #{cm.full_name}" end - $stdout.flush - end - - def done_adding # :nodoc: - puts + + unless cm.constants.empty? then + report << nil + + cm.each_constant do |constant| + next if constant.documented? + report << " # in file #{constant.file.full_name}" + report << " #{constant.name} = nil" + end + end + + unless cm.attributes.empty? then + report << nil + + cm.each_attribute do |attr| + next if attr.documented? + report << " #{attr.definition} #{attr.name} " \ + "# in file #{attr.file.full_name}" + end + end + + unless cm.method_list.empty? then + report << nil + + cm.each_method do |method| + next if method.documented? + report << " # in file #{method.file.full_name}" + report << " def #{method.name}#{method.params}; end" + report << nil + end + end + + report << 'end' + report << nil end + report.join "\n" end ## - # Stats printer that prints everything documented, including the documented - # status + # Returns a summary of the collected statistics. - class Verbose < Normal + def summary + calculate - ## - # Returns a marker for RDoc::CodeObject +co+ being undocumented + report = [] + report << 'Files: %5d' % @num_files + report << nil + report << 'Classes: %5d (%5d undocumented)' % [@num_classes, + @undoc_classes] + report << 'Modules: %5d (%5d undocumented)' % [@num_modules, + @undoc_modules] + report << 'Constants: %5d (%5d undocumented)' % [@num_constants, + @undoc_constants] + report << 'Attributes: %5d (%5d undocumented)' % [@num_attributes, + @undoc_attributes] + report << 'Methods: %5d (%5d undocumented)' % [@num_methods, + @undoc_methods] + report << nil + report << 'Total: %5d (%5d undocumented)' % [@num_items, + @undoc_items] - def nodoc co - " (undocumented)" unless co.documented? - end - - def print_alias as # :nodoc: - puts "\t\talias #{as.new_name} #{as.old_name}#{nodoc as}" - end - - def print_class(klass) # :nodoc: - puts "\tclass #{klass.full_name}#{nodoc klass}" - end - - def print_constant(constant) # :nodoc: - puts "\t\t#{constant.name}#{nodoc constant}" - end - - def print_file(files_so_far, file) # :nodoc: - super - puts - end - - def print_method(method) # :nodoc: - puts "\t\t#{method.singleton ? '::' : '#'}#{method.name}#{nodoc method}" - end - - def print_module(mod) # :nodoc: - puts "\tmodule #{mod.full_name}#{nodoc mod}" - end + report << '%6.2f%% documented' % @percent_doc if @percent_doc + report << nil + report << 'Elapsed: %0.1fs' % (Time.now - @start) + report.join "\n" end + autoload :Quiet, 'rdoc/stats/quiet' + autoload :Normal, 'rdoc/stats/normal' + autoload :Verbose, 'rdoc/stats/verbose' + end - diff --git a/lib/rdoc/stats/normal.rb b/lib/rdoc/stats/normal.rb new file mode 100644 index 0000000000..2057332b2d --- /dev/null +++ b/lib/rdoc/stats/normal.rb @@ -0,0 +1,51 @@ +## +# 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..." + end + + ## + # Prints a file with a progress bar + + def print_file files_so_far, filename + progress_bar = sprintf("%3d%% [%2d/%2d] ", + 100 * files_so_far / @num_files, + files_so_far, + @num_files) + + if $stdout.tty? then + # 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") + else + $stdout.puts "#{progress_bar} #{filename}" + end + + $stdout.flush + end + + def done_adding # :nodoc: + puts + end + +end + diff --git a/lib/rdoc/stats/quiet.rb b/lib/rdoc/stats/quiet.rb new file mode 100644 index 0000000000..eed27b2a88 --- /dev/null +++ b/lib/rdoc/stats/quiet.rb @@ -0,0 +1,59 @@ +## +# 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 + diff --git a/lib/rdoc/stats/verbose.rb b/lib/rdoc/stats/verbose.rb new file mode 100644 index 0000000000..430809ae07 --- /dev/null +++ b/lib/rdoc/stats/verbose.rb @@ -0,0 +1,45 @@ +## +# 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 + + diff --git a/lib/rdoc/task.rb b/lib/rdoc/task.rb index f87ef7dc0e..005c516eed 100644 --- a/lib/rdoc/task.rb +++ b/lib/rdoc/task.rb @@ -21,13 +21,238 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ +require 'rubygems' +begin + gem 'rdoc' +rescue Gem::LoadError +end + +begin + gem 'rake' +rescue Gem::LoadError +end + require 'rdoc' require 'rake' -require 'rake/rdoctask' +require 'rake/tasklib' + +## +# Create a documentation task that will generate the RDoc files for a project. +# +# The RDoc::Task will create the following targets: +# +# [rdoc] +# Main task for this RDoc task. +# +# [clobber_rdoc] +# Delete all the rdoc files. This target is automatically added to the main +# clobber target. +# +# [rerdoc] +# Rebuild the rdoc files from scratch, even if they are not out of date. +# +# Simple Example: +# +# RDoc::Task.new do |rd| +# rd.main = "README.rdoc" +# rd.rdoc_files.include("README.rdoc", "lib/**/*.rb") +# end +# +# The +rd+ object passed to the block is an RDoc::Task object. See the +# attributes list for the RDoc::Task class for available customization options. +# +# == Specifying different task names +# +# You may wish to give the task a different name, such as if you are +# generating two sets of documentation. For instance, if you want to have a +# development set of documentation including private methods: +# +# RDoc::Task.new :rdoc_dev do |rd| +# rd.main = "README.doc" +# rd.rdoc_files.include("README.rdoc", "lib/**/*.rb") +# rd.options << "--all" +# end +# +# The tasks would then be named :rdoc_dev, +# :clobber_rdoc_dev, and :rerdoc_dev. +# +# If you wish to have completely different task names, then pass a Hash as +# first argument. With the :rdoc, :clobber_rdoc and +# :rerdoc options, you can customize the task names to your liking. +# +# For example: +# +# RDoc::Task.new(:rdoc => "rdoc", :clobber_rdoc => "rdoc:clean", +# :rerdoc => "rdoc:force") +# +# This will create the tasks :rdoc, :rdoc:clean and +# :rdoc:force. + +class RDoc::Task < Rake::TaskLib + + ## + # Name of the main, top level task. (default is :rdoc) + + attr_accessor :name + + ## + # Name of directory to receive the html output files. (default is "html") + + attr_accessor :rdoc_dir + + ## + # Title of RDoc documentation. (defaults to rdoc's default) + + attr_accessor :title + + ## + # Name of file to be used as the main, top level file of the RDoc. (default + # is none) + + attr_accessor :main + + ## + # Name of template to be used by rdoc. (defaults to rdoc's default) + + attr_accessor :template + + ## + # List of files to be included in the rdoc generation. (default is []) + + attr_accessor :rdoc_files + + ## + # Additional list of options to be passed rdoc. (default is []) + + attr_accessor :options + + ## + # Whether to run the rdoc process as an external shell (default is false) + + attr_accessor :external + + ## + # Create an RDoc task with the given name. See the RDoc::Task class overview + # for documentation. + + def initialize(name = :rdoc) # :yield: self + if name.is_a? Hash then + invalid_options = name.keys.map { |k| k.to_sym } - + [:rdoc, :clobber_rdoc, :rerdoc] + + unless invalid_options.empty? then + raise ArgumentError, "invalid options: #{invalid_options.join(", ")}" + end + end + + @name = name + @rdoc_files = Rake::FileList.new + @rdoc_dir = 'html' + @main = nil + @title = nil + @template = nil + @options = [] + yield self if block_given? + define + end + + ## + # Create the tasks defined by this task lib. + + def define + desc "Build RDoc HTML files" + task rdoc_task_name + + desc "Rebuild RDoc HTML files" + task rerdoc_task_name => [clobber_task_name, rdoc_task_name] + + desc "Remove RDoc HTML files" + task clobber_task_name do + rm_r @rdoc_dir rescue nil + end + + task :clobber => [clobber_task_name] + + directory @rdoc_dir + + rdoc_target_deps = [ + @rdoc_files, + Rake.application.rakefile + ].flatten.compact + + task rdoc_task_name => [rdoc_target] + file rdoc_target => rdoc_target_deps do + @before_running_rdoc.call if @before_running_rdoc + args = option_list + @rdoc_files + + if Rake.application.options.trace then + $stderr.puts "rdoc #{args.join ' '}" + end + require 'rdoc/rdoc' + RDoc::RDoc.new.document(args) + end + + self + end + + ## + # List of options that will be supplied to RDoc + + def option_list + result = @options.dup + result << "-o" << @rdoc_dir + result << "--main" << main if main + result << "--title" << title if title + result << "-T" << template if template + result + end + + ## + # The block passed to this method will be called just before running the + # RDoc generator. It is allowed to modify RDoc::Task attributes inside the + # block. + + def before_running_rdoc(&block) + @before_running_rdoc = block + end + + private + + def rdoc_target + "#{rdoc_dir}/index.html" + end + + def rdoc_task_name + case name + when Hash then (name[:rdoc] || "rdoc").to_s + else name.to_s + end + end + + def clobber_task_name + case name + when Hash then (name[:clobber_rdoc] || "clobber_rdoc").to_s + else "clobber_#{name}" + end + end + + def rerdoc_task_name + case name + when Hash then (name[:rerdoc] || "rerdoc").to_s + else "re#{name}" + end + end + +end # :stopdoc: -module RDoc - Task = Rake::RDocTask +module Rake + + ## + # For backwards compatibility + + RDocTask = RDoc::Task + end # :startdoc: diff --git a/lib/rdoc/text.rb b/lib/rdoc/text.rb index 5280aa0fd2..20cd8a258a 100644 --- a/lib/rdoc/text.rb +++ b/lib/rdoc/text.rb @@ -1,8 +1,43 @@ +# coding: utf-8 + +## +# For RDoc::Text#to_html + +require 'strscan' + ## # Methods for manipulating comment text module RDoc::Text + ## + # 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 @@ -43,8 +78,7 @@ module RDoc::Text end ## - # Convert a string in markup format into HTML. Removes the first paragraph - # tags if +remove_para+ is true. + # Convert a string in markup format into HTML. # # Requires the including class to implement #formatter @@ -105,7 +139,7 @@ http://rubyforge.org/tracker/?atid=2472&group_id=627&func=browse def strip_hashes text return text if text =~ /^(?>\s*)[^\#]/ - text.gsub(/^\s*(#+)/) { $1.tr '#',' ' } + text.gsub(/^\s*(#+)/) { $1.tr '#',' ' }.gsub(/^\s+$/, '') end ## @@ -123,7 +157,102 @@ http://rubyforge.org/tracker/?atid=2472&group_id=627&func=browse text.sub! %r%/\*+% do " " * $&.length end text.sub! %r%\*+/% do " " * $&.length end text.gsub! %r%^[ \t]*\*%m do " " * $&.length end - text + text.gsub(/^\s+$/, '') + 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>/) then # skip contents of tt + html << s.matched.gsub('\\\\', '\\') + when s.scan(/.*?/) then + warn 'mismatched 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 end diff --git a/lib/rdoc/tokenstream.rb b/lib/rdoc/token_stream.rb similarity index 98% rename from lib/rdoc/tokenstream.rb rename to lib/rdoc/token_stream.rb index b1e86543f7..fb887f2fa4 100644 --- a/lib/rdoc/tokenstream.rb +++ b/lib/rdoc/token_stream.rb @@ -1,5 +1,3 @@ -module RDoc; end - ## # 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 diff --git a/lib/rdoc/top_level.rb b/lib/rdoc/top_level.rb index 306790fc15..d0ea621f8c 100644 --- a/lib/rdoc/top_level.rb +++ b/lib/rdoc/top_level.rb @@ -20,7 +20,14 @@ class RDoc::TopLevel < RDoc::Context attr_accessor :absolute_name - attr_accessor :diagram + ## + # 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 @@ -28,45 +35,110 @@ class RDoc::TopLevel < RDoc::Context attr_accessor :parser ## - # Returns all classes and modules discovered by RDoc + # Returns all classes discovered by RDoc - def self.all_classes_and_modules - classes_hash.values + modules_hash.values + def self.all_classes + @all_classes_hash.values end ## - # Returns all classes discovered by RDoc + # Returns all classes and modules discovered by RDoc - def self.classes - classes_hash.values + def self.all_classes_and_modules + @all_classes_hash.values + @all_modules_hash.values end ## # Hash of all classes known to RDoc - def self.classes_hash - @all_classes + def self.all_classes_hash + @all_classes_hash end ## # All TopLevels known to RDoc - def self.files - @all_files.values + def self.all_files + @all_files_hash.values end ## # Hash of all files known to RDoc - def self.files_hash - @all_files + 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) - classes_hash[name] + @all_classes_hash[name] end ## @@ -91,9 +163,7 @@ class RDoc::TopLevel < RDoc::Context # Finds the class or module with +name+ def self.find_class_or_module(name) - name =~ /^::/ - name = $' || name - + name = $' if name =~ /^::/ RDoc::TopLevel.classes_hash[name] || RDoc::TopLevel.modules_hash[name] end @@ -101,7 +171,7 @@ class RDoc::TopLevel < RDoc::Context # Finds the file with +name+ in all discovered files def self.find_file_named(name) - @all_files[name] + @all_files_hash[name] end ## @@ -112,26 +182,98 @@ class RDoc::TopLevel < RDoc::Context end ## - # Returns all modules discovered by RDoc + # 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.modules - modules_hash.values + 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 ## - # Hash of all modules known to RDoc + # 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.modules_hash - @all_modules + 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 + + ## + # 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 = {} - @all_modules = {} - @all_files = {} + @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 @@ -148,17 +290,49 @@ class RDoc::TopLevel < RDoc::Context @diagram = nil @parser = nil + @classes_or_modules = [] + RDoc::TopLevel.files_hash[file_name] = self end ## - # Adds +method+ to Object instead of RDoc::TopLevel + # Adds +an_alias+ to +Object+ instead of +self+. + + def add_alias(an_alias) + return an_alias unless @document_self + object_class.add_alias an_alias + end + + ## + # Adds +constant+ to +Object+ instead of +self+. + + def add_constant(constant) + return constant unless @document_self + object_class.add_constant constant + end + + ## + # Adds +include+ to +Object+ instead of +self+. + + def add_include(include) + return include unless @document_self + object_class.add_include include + end + + ## + # Adds +method+ to +Object+ instead of +self+. def add_method(method) - object = self.class.find_class_named 'Object' - object = add_class RDoc::NormalClass, 'Object' unless object + return method unless @document_self + object_class.add_method method + end - object.add_method method + ## + # 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 ## @@ -168,8 +342,13 @@ class RDoc::TopLevel < RDoc::Context File.basename @absolute_name end + alias name base_name + ## - # See RDoc::TopLevel.find_class_or_module + # 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 @@ -186,11 +365,11 @@ class RDoc::TopLevel < RDoc::Context # Finds a module or class with +name+ def find_module_named(name) - find_class_or_module(name) || find_enclosing_module_named(name) + find_class_or_module(name) end ## - # The name of this file + # Returns the relative name of this file def full_name @relative_name @@ -215,16 +394,24 @@ class RDoc::TopLevel < RDoc::Context end ## - # Date this file was last modified, if known + # Time this file was last modified, if known def last_modified - @file_stat ? file_stat.mtime.to_s : 'Unknown' + @file_stat ? file_stat.mtime : nil end ## - # Base name of this file + # Returns the NormalClass "Object", creating it if not found. + # + # Records +self+ as a location in "Object". - alias name base_name + 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 @@ -244,5 +431,9 @@ class RDoc::TopLevel < RDoc::Context end end + def to_s # :nodoc: + "file #{full_name}" + end + end diff --git a/test/rdoc/test.ja.rdoc b/test/rdoc/test.ja.rdoc index 96e1db93d3..cd01cab37a 100644 --- a/test/rdoc/test.ja.rdoc +++ b/test/rdoc/test.ja.rdoc @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + こんにちは! 初めまして。アーロンと申します。 diff --git a/test/rdoc/test_attribute_manager.rb b/test/rdoc/test_attribute_manager.rb index e908b86b02..25e8ca5e04 100644 --- a/test/rdoc/test_attribute_manager.rb +++ b/test/rdoc/test_attribute_manager.rb @@ -2,29 +2,35 @@ require 'rubygems' require 'minitest/autorun' require 'rdoc' require 'rdoc/markup' +require 'rdoc/markup/formatter' require 'rdoc/markup/attribute_manager' -class TestAttributeManager < MiniTest::Unit::TestCase +class TestAttributeManager < MiniTest::Unit::TestCase # HACK fix test name def setup @am = RDoc::Markup::AttributeManager.new @klass = RDoc::Markup::AttributeManager + @formatter = RDoc::Markup::Formatter.new + @formatter.add_tag :BOLD, '', '' + @formatter.add_tag :EM, '', '' + @formatter.add_tag :TT, '', '' end def test_convert_attrs_ignores_code - collector = RDoc::Markup::AttrSpan.new 10 - str = 'foo __send__ bar' - @am.convert_html str, collector - @am.convert_attrs str, collector - assert_match(/__send__/, str) + assert_equal 'foo __send__ bar', output('foo __send__ bar') end def test_convert_attrs_ignores_tt - collector = RDoc::Markup::AttrSpan.new 10 - str = 'foo __send__ bar' - @am.convert_html str, collector - @am.convert_attrs str, collector - assert_match(/__send__/, str) + assert_equal 'foo __send__ bar', output('foo __send__ bar') + end + + def test_convert_attrs_preserves_double + assert_equal 'foo.__send__ :bar', output('foo.__send__ :bar') + assert_equal 'use __FILE__ to', output('use __FILE__ to') + end + + def test_convert_attrs_does_not_ignore_after_tt + assert_equal 'the IF:key directive', output('the IF:_key_ directive') end def test_initial_word_pairs @@ -73,12 +79,41 @@ class TestAttributeManager < MiniTest::Unit::TestCase assert(specials.has_key?("WikiWord")) end - def silently(&block) - warn_level = $VERBOSE - $VERBOSE = nil - result = block.call - $VERBOSE = warn_level - result + def test_escapes + assert_equal 'text', output('text') + assert_equal 'text', output('\\text') + assert_equal '', output('\\') + assert_equal '', output('\\') + assert_equal '\\', output('\\\\') + assert_equal 'text', output('*text*') + assert_equal '*text*', output('\\*text*') + assert_equal '\\', output('\\') + assert_equal '\\text', output('\\text') + assert_equal '\\\\text', output('\\\\text') + assert_equal 'text \\ text', output('text \\ text') + + assert_equal 'and \\s matches space', + output('and \\s matches space') + assert_equal 'use text for code', + output('use \\text for code') + assert_equal 'use text for code', + output('use \\text\\ for code') + assert_equal 'use text for code', + output('use \\\\text for code') + assert_equal 'use text for code', + output('use \\text for code') + assert_equal 'use +text+ for code', + output('use \\+text+ for code') + assert_equal 'use text for code', + output('use \\+text+ for code') + assert_equal 'illegal not changed', + output('illegal not changed') + assert_equal 'unhandled

    tag

    unchanged', + output('unhandled

    tag

    unchanged') + end + + def output str + @formatter.convert_flow @am.flow str end end diff --git a/test/rdoc/test_rdoc_alias.rb b/test/rdoc/test_rdoc_alias.rb new file mode 100644 index 0000000000..ff499af962 --- /dev/null +++ b/test/rdoc/test_rdoc_alias.rb @@ -0,0 +1,13 @@ +require File.expand_path '../xref_test_case', __FILE__ + +class TestRDocAlias < XrefTestCase + + def test_to_s + a = RDoc::Alias.new nil, 'a', 'b', '' + a.parent = @c2 + + assert_equal 'alias: b -> #a in: RDoc::NormalClass C2 < Object', a.to_s + end + +end + diff --git a/test/rdoc/test_rdoc_any_method.rb b/test/rdoc/test_rdoc_any_method.rb index a4c3eec48c..2a7ae9042a 100644 --- a/test/rdoc/test_rdoc_any_method.rb +++ b/test/rdoc/test_rdoc_any_method.rb @@ -1,15 +1,17 @@ require File.expand_path '../xref_test_case', __FILE__ +require 'rdoc/code_objects' +require 'rdoc/generator/markup' class RDocAnyMethodTest < XrefTestCase def test_aref m = RDoc::AnyMethod.new nil, 'method?' - assert_equal 'method-i-method%3F', m.aref + assert_equal 'method-i-method-3F', m.aref m.singleton = true - assert_equal 'method-c-method%3F', m.aref + assert_equal 'method-c-method-3F', m.aref end def test_arglists @@ -36,6 +38,45 @@ method(a, b) { |c, d| ... } assert_equal 'C1::m', @c1.method_list.first.full_name end + def test_markup_code + tokens = [ + RDoc::RubyToken::TkCONSTANT. new(0, 0, 0, 'CONSTANT'), + RDoc::RubyToken::TkKW. new(0, 0, 0, 'KW'), + RDoc::RubyToken::TkIVAR. new(0, 0, 0, 'IVAR'), + RDoc::RubyToken::TkOp. new(0, 0, 0, 'Op'), + RDoc::RubyToken::TkId. new(0, 0, 0, 'Id'), + RDoc::RubyToken::TkNode. new(0, 0, 0, 'Node'), + RDoc::RubyToken::TkCOMMENT. new(0, 0, 0, 'COMMENT'), + RDoc::RubyToken::TkREGEXP. new(0, 0, 0, 'REGEXP'), + RDoc::RubyToken::TkSTRING. new(0, 0, 0, 'STRING'), + RDoc::RubyToken::TkVal. new(0, 0, 0, 'Val'), + RDoc::RubyToken::TkBACKSLASH.new(0, 0, 0, '\\'), + ] + + @c2_a.collect_tokens + @c2_a.add_tokens(*tokens) + + expected = [ + 'CONSTANT', + 'KW', + 'IVAR', + 'Op', + 'Id', + 'Node', + 'COMMENT', + 'REGEXP', + 'STRING', + 'Val', + '\\' + ].join + + assert_equal expected, @c2_a.markup_code + end + + def test_markup_code_empty + assert_equal '', @c2_a.markup_code + end + def test_marshal_load instance_method = Marshal.load Marshal.dump(@c1.method_list.last) diff --git a/test/rdoc/test_rdoc_attr.rb b/test/rdoc/test_rdoc_attr.rb index 10965d00b6..9751cc175d 100644 --- a/test/rdoc/test_rdoc_attr.rb +++ b/test/rdoc/test_rdoc_attr.rb @@ -8,6 +8,12 @@ class TestRDocAttr < MiniTest::Unit::TestCase @a = RDoc::Attr.new nil, 'attr', 'RW', '' end + def test_aref + m = RDoc::Attr.new nil, 'attr', 'RW', nil + + assert_equal 'attribute-i-attr', m.aref + end + def test_arglists assert_nil @a.arglists end @@ -20,6 +26,18 @@ class TestRDocAttr < MiniTest::Unit::TestCase assert_nil @a.call_seq end + def test_definition + assert_equal 'attr_accessor', @a.definition + + @a.rw = 'R' + + assert_equal 'attr_reader', @a.definition + + @a.rw = 'W' + + assert_equal 'attr_writer', @a.definition + end + def test_full_name assert_equal '(unknown)#attr', @a.full_name end @@ -33,15 +51,10 @@ class TestRDocAttr < MiniTest::Unit::TestCase end def test_type - assert_equal 'attr_accessor', @a.type + assert_equal 'instance', @a.type - @a.rw = 'R' - - assert_equal 'attr_reader', @a.type - - @a.rw = 'W' - - assert_equal 'attr_writer', @a.type + @a.singleton = true + assert_equal 'class', @a.type end end diff --git a/test/rdoc/test_rdoc_class_module.rb b/test/rdoc/test_rdoc_class_module.rb index 23291b969e..aa9bab5ec9 100644 --- a/test/rdoc/test_rdoc_class_module.rb +++ b/test/rdoc/test_rdoc_class_module.rb @@ -92,9 +92,142 @@ class TestRDocClassModule < XrefTestCase assert_equal expected, cm1.method_list.sort end + def test_remove_nodoc_children + parent = RDoc::ClassModule.new 'A' + parent.modules_hash.replace 'B' => true, 'C' => true + RDoc::TopLevel.all_modules_hash.replace 'A::B' => true + + parent.classes_hash.replace 'D' => true, 'E' => true + RDoc::TopLevel.all_classes_hash.replace 'A::D' => true + + parent.remove_nodoc_children + + assert_equal %w[B], parent.modules_hash.keys + assert_equal %w[D], parent.classes_hash.keys + end + def test_superclass assert_equal @c3_h1, @c3_h2.superclass end + def test_update_aliases_class + n1 = @xref_data.add_module RDoc::NormalClass, 'N1' + n1_k2 = n1.add_module RDoc::NormalClass, 'N2' + + n1.add_module_alias n1_k2, 'A1' + + n1_a1_c = n1.constants.find { |c| c.name == 'A1' } + refute_nil n1_a1_c + assert_equal n1_k2, n1_a1_c.is_alias_for, 'sanity check' + + n1.update_aliases + + n1_a1_k = @xref_data.find_class_or_module 'N1::A1' + refute_nil n1_a1_k + assert_equal n1_k2, n1_a1_k.is_alias_for + refute_equal n1_k2, n1_a1_k + + assert_equal 1, n1_k2.aliases.length + assert_equal n1_a1_k, n1_k2.aliases.first + + assert_equal 'N1::N2', n1_k2.full_name + assert_equal 'N1::A1', n1_a1_k.full_name + end + + def test_update_aliases_module + n1 = @xref_data.add_module RDoc::NormalModule, 'N1' + n1_n2 = n1.add_module RDoc::NormalModule, 'N2' + + n1.add_module_alias n1_n2, 'A1' + + n1_a1_c = n1.constants.find { |c| c.name == 'A1' } + refute_nil n1_a1_c + assert_equal n1_n2, n1_a1_c.is_alias_for, 'sanity check' + + n1.update_aliases + + n1_a1_m = @xref_data.find_class_or_module 'N1::A1' + refute_nil n1_a1_m + assert_equal n1_n2, n1_a1_m.is_alias_for + refute_equal n1_n2, n1_a1_m + + assert_equal 1, n1_n2.aliases.length + assert_equal n1_a1_m, n1_n2.aliases.first + + assert_equal 'N1::N2', n1_n2.full_name + assert_equal 'N1::A1', n1_a1_m.full_name + end + + def test_update_aliases_reparent + l1 = @xref_data.add_module RDoc::NormalModule, 'L1' + l1_l2 = l1.add_module RDoc::NormalModule, 'L2' + o1 = @xref_data.add_module RDoc::NormalModule, 'O1' + + o1.add_module_alias l1_l2, 'A1' + + o1_a1_c = o1.constants.find { |c| c.name == 'A1' } + refute_nil o1_a1_c + assert_equal l1_l2, o1_a1_c.is_alias_for + refute_equal l1_l2, o1_a1_c + + o1.update_aliases + + o1_a1_m = @xref_data.find_class_or_module 'O1::A1' + refute_nil o1_a1_m + assert_equal l1_l2, o1_a1_m.is_alias_for + + assert_equal 1, l1_l2.aliases.length + assert_equal o1_a1_m, l1_l2.aliases[0] + + assert_equal 'L1::L2', l1_l2.full_name + assert_equal 'O1::A1', o1_a1_m.full_name + end + + def test_update_includes + a = RDoc::Include.new 'M1', nil + b = RDoc::Include.new 'M2', nil + c = RDoc::Include.new 'C', nil + + @c1.add_include a + @c1.add_include b + @c1.add_include c + @c1.ancestors # cache included modules + + @m1_m2.document_self = nil + assert @m1_m2.remove_from_documentation? + + assert RDoc::TopLevel.all_modules_hash.key? @m1_m2.full_name + refute RDoc::TopLevel.all_modules_hash[@m1_m2.full_name].nil? + RDoc::TopLevel.remove_nodoc RDoc::TopLevel.all_modules_hash + refute RDoc::TopLevel.all_modules_hash.key? @m1_m2.full_name + + @c1.update_includes + + assert_equal [a, c], @c1.includes + end + + def test_update_includes_with_colons + a = RDoc::Include.new 'M1', nil + b = RDoc::Include.new 'M1::M2', nil + c = RDoc::Include.new 'C', nil + + @c1.add_include a + @c1.add_include b + @c1.add_include c + @c1.ancestors # cache included modules + + @m1_m2.document_self = nil + assert @m1_m2.remove_from_documentation? + + assert RDoc::TopLevel.all_modules_hash.key? @m1_m2.full_name + refute RDoc::TopLevel.all_modules_hash[@m1_m2.full_name].nil? + RDoc::TopLevel.remove_nodoc RDoc::TopLevel.all_modules_hash + refute RDoc::TopLevel.all_modules_hash.key? @m1_m2.full_name + + @c1.update_includes + + assert_equal [a, c], @c1.includes + end + end diff --git a/test/rdoc/test_rdoc_code_object.rb b/test/rdoc/test_rdoc_code_object.rb index 907bb7a3d5..8ae2d8b91e 100644 --- a/test/rdoc/test_rdoc_code_object.rb +++ b/test/rdoc/test_rdoc_code_object.rb @@ -16,6 +16,7 @@ class TestRDocCodeObject < XrefTestCase assert @co.document_children, 'document_children' refute @co.force_documentation, 'force_documentation' refute @co.done_documenting, 'done_documenting' + refute @co.received_nodoc, 'received_nodoc' assert_equal '', @co.comment, 'comment is empty' end @@ -33,16 +34,20 @@ class TestRDocCodeObject < XrefTestCase @co.document_children = false refute @co.document_children - @c2.document_children = false - assert_empty @c2.classes + # TODO this is not true anymore: + # test all the nodoc stuff etc... + #@c2.document_children = false + #assert_empty @c2.classes end def test_document_self_equals @co.document_self = false refute @co.document_self - @c1.document_self = false - assert_empty @c1.method_list + # TODO this is not true anymore: + # test all the nodoc stuff etc... + #@c1.document_self = false + #assert_empty @c1.method_list end def test_documented_eh @@ -56,11 +61,46 @@ class TestRDocCodeObject < XrefTestCase refute @co.documented? - @co.document_self = false + @co.document_self = nil # notify :nodoc: assert @co.documented? end + def test_done_documenting + # once done_documenting is set, other properties refuse to go to "true" + @co.done_documenting = true + + @co.document_self = true + refute @co.document_self + + @co.document_children = true + refute @co.document_children + + @co.force_documentation = true + refute @co.force_documentation + + @co.start_doc + refute @co.document_self + refute @co.document_children + + # turning done_documenting on + # resets others to true + + @co.done_documenting = false + assert @co.document_self + assert @co.document_children + end + + def test_full_name_equals + @co.full_name = 'hi' + + assert_equal 'hi', @co.instance_variable_get(:@full_name) + + @co.full_name = nil + + assert_nil @co.instance_variable_get(:@full_name) + end + def test_metadata assert_empty @co.metadata @@ -84,6 +124,23 @@ class TestRDocCodeObject < XrefTestCase assert_equal 'C2', @c2_c3.parent_name end + def test_received_ndoc + @co.document_self = false + refute @co.received_nodoc + + @co.document_self = nil + assert @co.received_nodoc + + @co.document_self = true + end + + def test_record_location + c = RDoc::CodeObject.new + c.record_location @xref_data + + assert_equal 'xref_data.rb', c.file.relative_name + end + def test_start_doc @co.document_self = false @co.document_children = false diff --git a/test/rdoc/test_rdoc_context.rb b/test/rdoc/test_rdoc_context.rb index cd0ad0fae3..6961f7d214 100644 --- a/test/rdoc/test_rdoc_context.rb +++ b/test/rdoc/test_rdoc_context.rb @@ -34,13 +34,38 @@ class TestRDocContext < XrefTestCase @context.add_alias as - assert_equal [as], @context.aliases - assert_equal [as], @context.unmatched_alias_lists['old_name'] + assert_equal [as], @context.external_aliases + assert_equal [as], @context.unmatched_alias_lists['#old_name'] + end + + def test_add_alias_method_attr + top_level = RDoc::TopLevel.new 'file.rb' + + attr = RDoc::Attr.new nil, 'old_name', 'R', '' + + as = RDoc::Alias.new nil, 'old_name', 'new_name', 'comment' + as.record_location top_level + as.parent = @context + + @context.add_attribute attr + @context.add_alias as + + assert_empty @context.aliases + assert_empty @context.unmatched_alias_lists + assert_equal %w[old_name new_name], @context.attributes.map { |m| m.name } + + new = @context.attributes.last + assert_equal top_level, new.file end def test_add_alias_method + top_level = RDoc::TopLevel.new 'file.rb' + meth = RDoc::AnyMethod.new nil, 'old_name' + meth.singleton = false + as = RDoc::Alias.new nil, 'old_name', 'new_name', 'comment' + as.record_location top_level as.parent = @context @context.add_method meth @@ -49,28 +74,28 @@ class TestRDocContext < XrefTestCase assert_empty @context.aliases assert_empty @context.unmatched_alias_lists assert_equal %w[old_name new_name], @context.method_list.map { |m| m.name } + + new = @context.method_list.last + assert_equal top_level, new.file end - def test_add_alias_impl + def test_add_alias_method_singleton meth = RDoc::AnyMethod.new nil, 'old_name' - meth.comment = 'old comment' - meth.singleton = false - meth.visibility = :private + meth.singleton = true - alas = RDoc::Alias.new nil, 'old_name', 'new_name', 'new comment' + as = RDoc::Alias.new nil, 'old_name', 'new_name', 'comment' + as.singleton = true - @context.add_alias_impl alas, meth + as.parent = @context - assert_equal 1, @context.method_list.length + @context.add_method meth + @context.add_alias as - alas_meth = @context.method_list.first - assert_equal 'new_name', alas_meth.name - assert_equal 'new comment', alas_meth.comment - assert_equal false, alas_meth.singleton - assert_equal meth, alas_meth.is_alias_for - assert_equal :private, alas_meth.visibility + assert_empty @context.aliases + assert_empty @context.unmatched_alias_lists + assert_equal %w[old_name new_name], @context.method_list.map { |m| m.name } - assert_equal [alas_meth], meth.aliases + assert @context.method_list.last.singleton end def test_add_class @@ -133,11 +158,11 @@ class TestRDocContext < XrefTestCase meth = RDoc::AnyMethod.new nil, 'old_name' @context.add_alias as - refute_empty @context.aliases + refute_empty @context.external_aliases @context.add_method meth - assert_empty @context.aliases + assert_empty @context.external_aliases assert_empty @context.unmatched_alias_lists assert_equal %w[old_name new_name], @context.method_list.map { |m| m.name } end @@ -292,6 +317,46 @@ class TestRDocContext < XrefTestCase assert_equal @c1__m, @c1.find_symbol('::m') end + def test_fully_documented_eh + context = RDoc::Context.new + + refute context.fully_documented? + + context.comment = 'hi' + + assert context.fully_documented? + + m = @c1_m + + context.add_method m + + refute context.fully_documented? + + m.comment = 'hi' + + assert context.fully_documented? + + c = RDoc::Constant.new 'C', '0', nil + + context.add_constant c + + refute context.fully_documented? + + c.comment = 'hi' + + assert context.fully_documented? + + a = RDoc::Attr.new '', 'a', 'RW', nil + + context.add_attribute a + + refute context.fully_documented? + + a.comment = 'hi' + + assert context.fully_documented? + end + def test_spaceship assert_equal(-1, @c2.<=>(@c3)) assert_equal 0, @c2.<=>(@c2) diff --git a/test/rdoc/test_rdoc_encoding.rb b/test/rdoc/test_rdoc_encoding.rb new file mode 100644 index 0000000000..b940d93606 --- /dev/null +++ b/test/rdoc/test_rdoc_encoding.rb @@ -0,0 +1,145 @@ +require 'rubygems' +require 'minitest/autorun' +require 'rdoc' +require 'rdoc/encoding' + +require 'tempfile' + +class TestRDocEncoding < MiniTest::Unit::TestCase + + def setup + @tempfile = Tempfile.new 'test_rdoc_encoding' + end + + def test_class_read_file + @tempfile.write "hi everybody" + @tempfile.flush + + assert_equal "hi everybody", RDoc::Encoding.read_file(@tempfile.path, nil) + end + + def test_class_read_file_encoding + skip "Encoding not implemented" unless Object.const_defined? :Encoding + + expected = "# coding: utf-8\nhi everybody" + + @tempfile.write expected + @tempfile.flush + + # FIXME 1.9 fix on windoze + expected.gsub!("\n", "\r\n") if RUBY_VERSION =~ /^1.9/ && RUBY_PLATFORM =~ /mswin|mingw/ + + contents = RDoc::Encoding.read_file @tempfile.path, Encoding::UTF_8 + assert_equal expected, contents + assert_equal Encoding::UTF_8, contents.encoding + end + + def test_class_read_file_encoding_convert + skip "Encoding not implemented" unless Object.const_defined? :Encoding + + content = "" + content.encode! 'ISO-8859-1' + content << "# coding: ISO-8859-1\nhi \xE9verybody" + + @tempfile.write content + @tempfile.flush + + contents = RDoc::Encoding.read_file @tempfile.path, Encoding::UTF_8 + assert_equal Encoding::UTF_8, contents.encoding + assert_equal "# coding: ISO-8859-1\nhi \u00e9verybody", contents.sub("\r", '') + end + + def test_class_read_file_encoding_fancy + skip "Encoding not implemented" unless Object.const_defined? :Encoding + + expected = "# -*- coding: utf-8; fill-column: 74 -*-\nhi everybody" + expected.encode! Encoding::UTF_8 + + @tempfile.write expected + @tempfile.flush + + # FIXME 1.9 fix on windoze + expected.gsub!("\n", "\r\n") if RUBY_VERSION =~ /^1.9/ && RUBY_PLATFORM =~ /win32|mingw32/ + + contents = RDoc::Encoding.read_file @tempfile.path, Encoding::UTF_8 + assert_equal expected, contents + assert_equal Encoding::UTF_8, contents.encoding + end + + def test_class_read_file_encoding_guess + skip "Encoding not implemented" unless Object.const_defined? :Encoding + + path = File.expand_path '../test.ja.txt', __FILE__ + content = RDoc::Encoding.read_file path, Encoding::UTF_8 + + assert_equal Encoding::UTF_8, content.encoding + end + + def test_class_read_file_encoding_with_signature + skip "Encoding not implemented" unless defined? ::Encoding + + @tempfile.write "\xEF\xBB\xBFhi everybody" + @tempfile.flush + + bug3360 = '[ruby-dev:41452]' + content = RDoc::Encoding.read_file @tempfile.path, Encoding::UTF_8 + assert_equal Encoding::UTF_8, content.encoding, bug3360 + assert_equal "hi everybody", content, bug3360 + end + + def test_class_set_encoding + s = "# coding: UTF-8\n" + RDoc::Encoding.set_encoding s + + # sanity check for 1.8 + + skip "Encoding not implemented" unless Object.const_defined? :Encoding + + assert_equal Encoding::UTF_8, s.encoding + + s = "#!/bin/ruby\n# coding: UTF-8\n" + RDoc::Encoding.set_encoding s + + assert_equal Encoding::UTF_8, s.encoding + + s = "\n" + expected = s.encoding + RDoc::Encoding.set_encoding s + + assert_equal Encoding::UTF_8, s.encoding + + s = "\n" + expected = s.encoding + RDoc::Encoding.set_encoding s + + assert_equal Encoding::UTF_8, s.encoding + end + + def test_class_set_encoding_bad + skip "Encoding not implemented" unless Object.const_defined? :Encoding + + s = "" + expected = s.encoding + RDoc::Encoding.set_encoding s + + assert_equal expected, s.encoding + + s = "# vim:set fileencoding=utf-8:\n" + expected = s.encoding + RDoc::Encoding.set_encoding s + + assert_equal expected, s.encoding + + s = "# vim:set fileencoding=utf-8:\n" + expected = s.encoding + RDoc::Encoding.set_encoding s + + assert_equal expected, s.encoding + + assert_raises ArgumentError do + RDoc::Encoding.set_encoding "# -*- encoding: undecided -*-\n" + end + end + +end + diff --git a/test/rdoc/test_rdoc_generator_darkfish.rb b/test/rdoc/test_rdoc_generator_darkfish.rb new file mode 100644 index 0000000000..b99803bac1 --- /dev/null +++ b/test/rdoc/test_rdoc_generator_darkfish.rb @@ -0,0 +1,119 @@ +require 'minitest/autorun' +require 'rdoc/rdoc' +require 'rdoc/generator/darkfish' +require 'tmpdir' +require 'fileutils' + +class TestRDocGeneratorDarkfish < MiniTest::Unit::TestCase + + def setup + @pwd = Dir.pwd + @lib_dir = "#{@pwd}/lib" + $LOAD_PATH.unshift @lib_dir # ensure we load from this RDoc + RDoc::TopLevel.reset + + @options = RDoc::Options.new + @options.option_parser = OptionParser.new + + @tmpdir = File.join Dir.tmpdir, "test_rdoc_generator_darkfish_#{$$}" + FileUtils.mkdir_p @tmpdir + Dir.chdir @tmpdir + @options.op_dir = @tmpdir + @options.generator = RDoc::Generator::Darkfish + + $LOAD_PATH.each do |path| + darkfish_dir = File.join path, 'rdoc/generator/template/darkfish' + next unless File.directory? darkfish_dir + @options.template_dir = darkfish_dir + break + end + + rd = RDoc::RDoc.new + rd.options = @options + RDoc::RDoc.current = rd + + @g = @options.generator.new @options + + rd.generator = @g + + @top_level = RDoc::TopLevel.new 'file.rb' + @klass = @top_level.add_class RDoc::NormalClass, 'Object' + @meth = RDoc::AnyMethod.new nil, 'method' + @meth_bang = RDoc::AnyMethod.new nil, 'method!' + @attr = RDoc::Attr.new nil, 'attr', 'RW', '' + + @klass.add_method @meth + @klass.add_method @meth_bang + @klass.add_attribute @attr + end + + def teardown + $LOAD_PATH.shift + Dir.chdir @pwd + FileUtils.rm_rf @tmpdir + end + + def assert_file path + assert File.file?(path), "#{path} is not a file" + end + + def refute_file path + refute File.exist?(path), "#{path} exists" + end + + def test_generate + top_level = RDoc::TopLevel.new 'file.rb' + top_level.add_class @klass.class, @klass.name + + @g.generate [top_level] + + assert_file 'index.html' + assert_file 'Object.html' + assert_file 'file_rb.html' + + encoding = if Object.const_defined? :Encoding then + Regexp.escape Encoding.default_external.name + else + Regexp.escape 'UTF-8' + end + + assert_match(/regexp
    s) are patterns which describe the contents of a string. EXPECTED + # FIXME 1.9 fix on windoze + # preprocessor uses binread, so line endings are \r\n + expected.gsub!("\n", "\r\n") if + RUBY_VERSION =~ /^1.9/ && RUBY_PLATFORM =~ /mswin|mingw/ + assert_equal expected, content end diff --git a/test/rdoc/test_rdoc_markup_to_ansi.rb b/test/rdoc/test_rdoc_markup_to_ansi.rb index a8fab98d19..1334ac71c4 100644 --- a/test/rdoc/test_rdoc_markup_to_ansi.rb +++ b/test/rdoc/test_rdoc_markup_to_ansi.rb @@ -1,11 +1,12 @@ require 'rubygems' -require 'rdoc/markup/formatter_test_case' +require 'rdoc/markup/text_formatter_test_case' require 'rdoc/markup/to_ansi' require 'minitest/autorun' -class TestRDocMarkupToAnsi < RDoc::Markup::FormatterTestCase +class TestRDocMarkupToAnsi < RDoc::Markup::TextFormatterTestCase add_visitor_tests + add_text_tests def setup super @@ -62,7 +63,7 @@ class TestRDocMarkupToAnsi < RDoc::Markup::FormatterTestCase end def accept_list_item_end_label - assert_equal "\e[0m\n", @to.res.join + assert_equal "\e[0m", @to.res.join assert_equal 0, @to.indent, 'indent' end @@ -72,7 +73,7 @@ class TestRDocMarkupToAnsi < RDoc::Markup::FormatterTestCase end def accept_list_item_end_note - assert_equal "\e[0m\n", @to.res.join + assert_equal "\e[0m", @to.res.join assert_equal 0, @to.indent, 'indent' end @@ -191,8 +192,8 @@ class TestRDocMarkupToAnsi < RDoc::Markup::FormatterTestCase assert_equal "\e[0m#{'-' * 78}\n", @to.res.join end - def accept_verbatim # FormatterTestCase doesn't set indent for ToAnsi - assert_equal "\e[0m hi\n world\n\n", @to.res.join + def accept_verbatim + assert_equal "\e[0m hi\n world\n\n", @to.res.join end def end_accepting @@ -207,214 +208,103 @@ class TestRDocMarkupToAnsi < RDoc::Markup::FormatterTestCase assert_empty @to.list_width end - def test_accept_heading_1 - @to.start_accepting - @to.accept_heading @RM::Heading.new(1, 'Hello') - + def accept_heading_1 assert_equal "\e[0m\e[1;32mHello\e[m\n", @to.end_accepting end - def test_accept_heading_2 - @to.start_accepting - @to.accept_heading @RM::Heading.new(2, 'Hello') - + def accept_heading_2 assert_equal "\e[0m\e[4;32mHello\e[m\n", @to.end_accepting end - def test_accept_heading_3 - @to.start_accepting - @to.accept_heading @RM::Heading.new(3, 'Hello') - + def accept_heading_3 assert_equal "\e[0m\e[32mHello\e[m\n", @to.end_accepting end - def test_accept_heading_4 - @to.start_accepting - @to.accept_heading @RM::Heading.new(4, 'Hello') - + def accept_heading_4 assert_equal "\e[0mHello\n", @to.end_accepting end - def test_accept_heading_indent - @to.start_accepting - @to.indent = 3 - @to.accept_heading @RM::Heading.new(1, 'Hello') - + def accept_heading_indent assert_equal "\e[0m \e[1;32mHello\e[m\n", @to.end_accepting end - def test_accept_heading_b - @to.start_accepting - @to.indent = 3 - @to.accept_heading @RM::Heading.new(1, '*Hello*') - - assert_equal "\e[0m \e[1;32m\e[1mHello\e[m\e[m\n", @to.end_accepting + def accept_heading_b + assert_equal "\e[0m\e[1;32m\e[1mHello\e[m\e[m\n", @to.end_accepting end - def test_accept_list_item_start_note_2 - list = @RM::List.new(:NOTE, - @RM::ListItem.new('teletype', - @RM::Paragraph.new('teletype description'))) - - @to.start_accepting - - list.accept @to - - expected = "\e[0m\e[7mteletype\e[m:\n teletype description\n\n" - - assert_equal expected, @to.end_accepting + def accept_heading_suppressed_crossref + assert_equal "\e[0m\e[1;32mHello\e[m\n", @to.end_accepting end - def test_accept_paragraph_b - @to.start_accepting - @to.accept_paragraph @RM::Paragraph.new('reg bold words reg') - - expected = "\e[0mreg \e[1mbold words\e[m reg\n" - - assert_equal expected, @to.end_accepting + def accept_list_item_start_note_2 + assert_equal "\e[0m\e[7mteletype\e[m:\n teletype description\n\n", + @to.res.join end - def test_accept_paragraph_i - @to.start_accepting - @to.accept_paragraph @RM::Paragraph.new('reg italic words reg') - - expected = "\e[0mreg \e[4mitalic words\e[m reg\n" - - assert_equal expected, @to.end_accepting + def accept_paragraph_b + assert_equal "\e[0mreg \e[1mbold words\e[m reg\n", @to.end_accepting end - def test_accept_paragraph_indent - @to.start_accepting - @to.indent = 3 - @to.accept_paragraph @RM::Paragraph.new('words ' * 30) + def accept_paragraph_i + assert_equal "\e[0mreg \e[4mitalic words\e[m reg\n", @to.end_accepting + end + def accept_paragraph_indent expected = <<-EXPECTED \e[0m words words words words words words words words words words words words words words words words words words words words words words words words - words words words words words words + words words words words words words EXPECTED assert_equal expected, @to.end_accepting end - def test_accept_paragraph_plus - @to.start_accepting - @to.accept_paragraph @RM::Paragraph.new('regular +teletype+ regular') - - expected = "\e[0mregular \e[7mteletype\e[m regular\n" - - assert_equal expected, @to.end_accepting + def accept_paragraph_plus + assert_equal "\e[0mreg \e[7mteletype\e[m reg\n", @to.end_accepting end - def test_accept_paragraph_star - @to.start_accepting - @to.accept_paragraph @RM::Paragraph.new('regular *bold* regular') - - expected = "\e[0mregular \e[1mbold\e[m regular\n" - - assert_equal expected, @to.end_accepting + def accept_paragraph_star + assert_equal "\e[0mreg \e[1mbold\e[m reg\n", @to.end_accepting end - def test_accept_paragraph_underscore - @to.start_accepting - @to.accept_paragraph @RM::Paragraph.new('regular _italic_ regular') - - expected = "\e[0mregular \e[4mitalic\e[m regular\n" - - assert_equal expected, @to.end_accepting + def accept_paragraph_underscore + assert_equal "\e[0mreg \e[4mitalic\e[m reg\n", @to.end_accepting end - def test_accept_paragraph_wrap - @to.start_accepting - @to.accept_paragraph @RM::Paragraph.new('words ' * 30) - + def accept_paragraph_wrap expected = <<-EXPECTED \e[0mwords words words words words words words words words words words words words words words words words words words words words words words words words words -words words words words +words words words words EXPECTED assert_equal expected, @to.end_accepting end - def test_accept_rule_indent - @to.start_accepting - @to.indent = 3 - - @to.accept_rule @RM::Rule.new(1) - + def accept_rule_indent assert_equal "\e[0m #{'-' * 75}\n", @to.end_accepting end - def test_accept_verbatim_indent - @to.start_accepting - - @to.indent = 2 - - @to.accept_verbatim @RM::Verbatim.new(' ', 'hi', "\n", - ' ', 'world', "\n") - + def accept_verbatim_indent assert_equal "\e[0m hi\n world\n\n", @to.end_accepting end - def test_accept_verbatim_big_indent - @to.start_accepting - - @to.indent = 2 - - @to.accept_verbatim @RM::Verbatim.new(' ', 'hi', "\n", - ' ', 'world', "\n") - + def accept_verbatim_big_indent assert_equal "\e[0m hi\n world\n\n", @to.end_accepting end - def test_attributes - assert_equal 'Dog', @to.attributes("\\Dog") - end - - def test_list_nested - doc = @RM::Document.new( - @RM::List.new(:BULLET, - @RM::ListItem.new(nil, - @RM::Paragraph.new('l1'), - @RM::List.new(:BULLET, - @RM::ListItem.new(nil, - @RM::Paragraph.new('l1.1')))), - @RM::ListItem.new(nil, - @RM::Paragraph.new('l2')))) - - output = doc.accept @to - + def list_nested expected = <<-EXPECTED \e[0m* l1 * l1.1 * l2 EXPECTED - assert_equal expected, output + assert_equal expected, @to.end_accepting end - def test_list_verbatim # HACK overblown - doc = @RM::Document.new( - @RM::List.new(:BULLET, - @RM::ListItem.new(nil, - @RM::Paragraph.new('list', 'stuff'), - @RM::BlankLine.new(), - @RM::Verbatim.new(' ', '*', ' ', 'list', "\n", - ' ', 'with', "\n", - "\n", - ' ', 'second', "\n", - "\n", - ' ', '1.', ' ', 'indented', "\n", - ' ', '2.', ' ', 'numbered', "\n", - "\n", - ' ', 'third', "\n", - "\n", - ' ', '*', ' ', 'second', "\n")))) - - output = doc.accept @to - - expected = <<-EXPECTED + def list_verbatim + expected = <<-EXPECTED # HACK overblown \e[0m* list stuff * list @@ -431,7 +321,7 @@ words words words words EXPECTED - assert_equal expected, output + assert_equal expected, @to.end_accepting end end diff --git a/test/rdoc/test_rdoc_markup_to_bs.rb b/test/rdoc/test_rdoc_markup_to_bs.rb index c042452637..3d2e4da8de 100644 --- a/test/rdoc/test_rdoc_markup_to_bs.rb +++ b/test/rdoc/test_rdoc_markup_to_bs.rb @@ -1,11 +1,12 @@ require 'rubygems' -require 'rdoc/markup/formatter_test_case' +require 'rdoc/markup/text_formatter_test_case' require 'rdoc/markup/to_bs' require 'minitest/autorun' -class TestRDocMarkupToBs < RDoc::Markup::FormatterTestCase +class TestRDocMarkupToBs < RDoc::Markup::TextFormatterTestCase add_visitor_tests + add_text_tests def setup super @@ -63,6 +64,7 @@ class TestRDocMarkupToBs < RDoc::Markup::FormatterTestCase end def accept_list_item_end_label + assert_equal "\n", @to.res.join assert_equal 0, @to.indent, 'indent' end @@ -72,6 +74,7 @@ class TestRDocMarkupToBs < RDoc::Markup::FormatterTestCase end def accept_list_item_end_note + assert_equal "\n", @to.res.join assert_equal 0, @to.indent, 'indent' end @@ -190,8 +193,8 @@ class TestRDocMarkupToBs < RDoc::Markup::FormatterTestCase assert_equal "#{'-' * 78}\n", @to.res.join end - def accept_verbatim # FormatterTestCase doesn't set indent for ToAnsi - assert_equal " hi\n world\n\n", @to.res.join + def accept_verbatim + assert_equal " hi\n world\n\n", @to.res.join end def end_accepting @@ -206,232 +209,115 @@ class TestRDocMarkupToBs < RDoc::Markup::FormatterTestCase assert_empty @to.list_width end - def test_accept_heading_1 + def accept_heading_1 skip "No String#chars, upgrade your ruby" unless ''.respond_to? :chars - @to.start_accepting - @to.accept_heading @RM::Heading.new(1, 'Hello') - assert_equal "= H\bHe\bel\bll\blo\bo\n", @to.end_accepting end - def test_accept_heading_2 + def accept_heading_2 skip "No String#chars, upgrade your ruby" unless ''.respond_to? :chars - @to.start_accepting - @to.accept_heading @RM::Heading.new(2, 'Hello') - assert_equal "== H\bHe\bel\bll\blo\bo\n", @to.end_accepting end - def test_accept_heading_3 + def accept_heading_3 skip "No String#chars, upgrade your ruby" unless ''.respond_to? :chars - @to.start_accepting - @to.accept_heading @RM::Heading.new(3, 'Hello') - assert_equal "=== H\bHe\bel\bll\blo\bo\n", @to.end_accepting end - def test_accept_heading_4 + def accept_heading_4 skip "No String#chars, upgrade your ruby" unless ''.respond_to? :chars - @to.start_accepting - @to.accept_heading @RM::Heading.new(4, 'Hello') - assert_equal "==== H\bHe\bel\bll\blo\bo\n", @to.end_accepting end - def test_accept_heading_indent + def accept_heading_indent skip "No String#chars, upgrade your ruby" unless ''.respond_to? :chars - @to.start_accepting - @to.indent = 3 - @to.accept_heading @RM::Heading.new(1, 'Hello') - assert_equal " = H\bHe\bel\bll\blo\bo\n", @to.end_accepting end - def test_accept_heading_b + def accept_heading_b skip "No String#chars, upgrade your ruby" unless ''.respond_to? :chars - @to.start_accepting - @to.indent = 3 - @to.accept_heading @RM::Heading.new(1, '*Hello*') - - assert_equal " = H\bHe\bel\bll\blo\bo\n", @to.end_accepting - end - - def test_accept_heading_suppressed_crossref - skip "No String#chars, upgrade your ruby" unless ''.respond_to? :chars - @to.start_accepting - @to.accept_heading @RM::Heading.new(1, '\\Hello') - assert_equal "= H\bHe\bel\bll\blo\bo\n", @to.end_accepting end - def test_accept_list_item_start_note_2 - list = @RM::List.new(:NOTE, - @RM::ListItem.new('teletype', - @RM::Paragraph.new('teletype description'))) - - @to.start_accepting - - list.accept @to - - expected = "teletype:\n teletype description\n\n" - - assert_equal expected, @to.end_accepting - end - - def test_accept_paragraph_b + def accept_heading_suppressed_crossref skip "No String#chars, upgrade your ruby" unless ''.respond_to? :chars - @to.start_accepting - @to.accept_paragraph @RM::Paragraph.new('reg bold words reg') - - expected = "reg b\bbo\bol\bld\bd \b w\bwo\bor\brd\bds\bs reg\n" - - assert_equal expected, @to.end_accepting + assert_equal "= H\bHe\bel\bll\blo\bo\n", @to.end_accepting end - def test_accept_paragraph_i + def accept_list_item_start_note_2 + assert_equal "teletype:\n teletype description\n\n", @to.res.join + end + + def accept_paragraph_b skip "No String#chars, upgrade your ruby" unless ''.respond_to? :chars - @to.start_accepting - @to.accept_paragraph @RM::Paragraph.new('reg italic words reg') - - expected = "reg _\bi_\bt_\ba_\bl_\bi_\bc_\b _\bw_\bo_\br_\bd_\bs reg\n" - - assert_equal expected, @to.end_accepting + assert_equal "reg b\bbo\bol\bld\bd \b w\bwo\bor\brd\bds\bs reg\n", + @to.end_accepting end - def test_accept_paragraph_indent - @to.start_accepting - @to.indent = 3 - @to.accept_paragraph @RM::Paragraph.new('words ' * 30) + def accept_paragraph_i + skip "No String#chars, upgrade your ruby" unless ''.respond_to? :chars + assert_equal "reg _\bi_\bt_\ba_\bl_\bi_\bc_\b _\bw_\bo_\br_\bd_\bs reg\n", + @to.end_accepting + end + def accept_paragraph_indent expected = <<-EXPECTED words words words words words words words words words words words words words words words words words words words words words words words words - words words words words words words + words words words words words words EXPECTED assert_equal expected, @to.end_accepting end - def test_accept_paragraph_plus - @to.start_accepting - @to.accept_paragraph @RM::Paragraph.new('regular +teletype+ regular') - - expected = "regular teletype regular\n" - - assert_equal expected, @to.end_accepting + def accept_paragraph_plus + assert_equal "reg teletype reg\n", @to.end_accepting end - def test_accept_paragraph_star + def accept_paragraph_star skip "No String#chars, upgrade your ruby" unless ''.respond_to? :chars - @to.start_accepting - @to.accept_paragraph @RM::Paragraph.new('regular *bold* regular') - - expected = "regular b\bbo\bol\bld\bd regular\n" - - assert_equal expected, @to.end_accepting + assert_equal "reg b\bbo\bol\bld\bd reg\n", @to.end_accepting end - def test_accept_paragraph_underscore + def accept_paragraph_underscore skip "No String#chars, upgrade your ruby" unless ''.respond_to? :chars - @to.start_accepting - @to.accept_paragraph @RM::Paragraph.new('regular _italic_ regular') - - expected = "regular _\bi_\bt_\ba_\bl_\bi_\bc regular\n" - - assert_equal expected, @to.end_accepting + assert_equal "reg _\bi_\bt_\ba_\bl_\bi_\bc reg\n", @to.end_accepting end - def test_accept_paragraph_wrap - @to.start_accepting - @to.accept_paragraph @RM::Paragraph.new('words ' * 30) - + def accept_paragraph_wrap expected = <<-EXPECTED words words words words words words words words words words words words words words words words words words words words words words words words words words -words words words words +words words words words EXPECTED assert_equal expected, @to.end_accepting end - def test_accept_rule_indent - @to.start_accepting - @to.indent = 3 - - @to.accept_rule @RM::Rule.new(1) - + def accept_rule_indent assert_equal " #{'-' * 75}\n", @to.end_accepting end - def test_accept_verbatim_indent - @to.start_accepting - - @to.indent = 2 - - @to.accept_verbatim @RM::Verbatim.new(' ', 'hi', "\n", - ' ', 'world', "\n") - + def accept_verbatim_indent assert_equal " hi\n world\n\n", @to.end_accepting end - def test_accept_verbatim_big_indent - @to.start_accepting - - @to.indent = 2 - - @to.accept_verbatim @RM::Verbatim.new(' ', 'hi', "\n", - ' ', 'world', "\n") - + def accept_verbatim_big_indent assert_equal " hi\n world\n\n", @to.end_accepting end - def test_attributes - assert_equal 'Dog', @to.attributes("\\Dog") - end - - def test_list_nested - doc = @RM::Document.new( - @RM::List.new(:BULLET, - @RM::ListItem.new(nil, - @RM::Paragraph.new('l1'), - @RM::List.new(:BULLET, - @RM::ListItem.new(nil, - @RM::Paragraph.new('l1.1')))), - @RM::ListItem.new(nil, - @RM::Paragraph.new('l2')))) - - output = doc.accept @to - + def list_nested expected = <<-EXPECTED * l1 * l1.1 * l2 EXPECTED - assert_equal expected, output + assert_equal expected, @to.end_accepting end - def test_list_verbatim # HACK overblown - doc = @RM::Document.new( - @RM::List.new(:BULLET, - @RM::ListItem.new(nil, - @RM::Paragraph.new('list', 'stuff'), - @RM::BlankLine.new(), - @RM::Verbatim.new(' ', '*', ' ', 'list', "\n", - ' ', 'with', "\n", - "\n", - ' ', 'second', "\n", - "\n", - ' ', '1.', ' ', 'indented', "\n", - ' ', '2.', ' ', 'numbered', "\n", - "\n", - ' ', 'third', "\n", - "\n", - ' ', '*', ' ', 'second', "\n")))) - - output = doc.accept @to - - expected = <<-EXPECTED + def list_verbatim + expected = <<-EXPECTED # HACK overblown * list stuff * list @@ -448,7 +334,7 @@ words words words words EXPECTED - assert_equal expected, output + assert_equal expected, @to.end_accepting end end diff --git a/test/rdoc/test_rdoc_markup_to_html.rb b/test/rdoc/test_rdoc_markup_to_html.rb index f6014391c8..8a5860fe25 100644 --- a/test/rdoc/test_rdoc_markup_to_html.rb +++ b/test/rdoc/test_rdoc_markup_to_html.rb @@ -31,49 +31,73 @@ class TestRDocMarkupToHtml < RDoc::Markup::FormatterTestCase end def accept_heading - assert_equal "
    Hello
    \n", @to.res.join + assert_equal "\n
    Hello
    \n", @to.res.join + end + + def accept_heading_1 + assert_equal "\n

    Hello

    \n", @to.res.join + end + + def accept_heading_2 + assert_equal "\n

    Hello

    \n", @to.res.join + end + + def accept_heading_3 + assert_equal "\n

    Hello

    \n", @to.res.join + end + + def accept_heading_4 + assert_equal "\n

    Hello

    \n", @to.res.join + end + + def accept_heading_b + assert_equal "\n

    Hello

    \n", @to.res.join + end + + def accept_heading_suppressed_crossref + assert_equal "\n

    Hello

    \n", @to.res.join end def accept_list_end_bullet assert_equal [], @to.list assert_equal [], @to.in_list_entry - assert_equal "
      \n
    \n", @to.res.join + assert_equal "
      \n", @to.res.join end def accept_list_end_label assert_equal [], @to.list assert_equal [], @to.in_list_entry - assert_equal "
      \n
      \n", @to.res.join + assert_equal "
      \n", @to.res.join end def accept_list_end_lalpha assert_equal [], @to.list assert_equal [], @to.in_list_entry - assert_equal "
        \n
      \n", @to.res.join + assert_equal "
        \n", @to.res.join end def accept_list_end_number assert_equal [], @to.list assert_equal [], @to.in_list_entry - assert_equal "
          \n
        \n", @to.res.join + assert_equal "
          \n", @to.res.join end def accept_list_end_note assert_equal [], @to.list assert_equal [], @to.in_list_entry - assert_equal "\n
          \n", @to.res.join + assert_equal "
          \n", @to.res.join end def accept_list_end_ualpha assert_equal [], @to.list assert_equal [], @to.in_list_entry - assert_equal "
            \n
          \n", @to.res.join + assert_equal "
            \n", @to.res.join end def accept_list_item_end_bullet @@ -101,73 +125,105 @@ class TestRDocMarkupToHtml < RDoc::Markup::FormatterTestCase end def accept_list_item_start_bullet - assert_equal "
              \n
            • ", @to.res.join + assert_equal "
              • ", @to.res.join end def accept_list_item_start_label - assert_equal "
                \n
                cat
                ", @to.res.join + assert_equal "
                cat
                \n
                ", @to.res.join end def accept_list_item_start_lalpha - assert_equal "
                  \n
                1. ", @to.res.join + assert_equal "
                  1. ", @to.res.join end def accept_list_item_start_note - assert_equal "\n
                    cat", @to.res.join + assert_equal "\n

                    cat

                    ", + @to.res.join + end + + def accept_list_item_start_note_2 + expected = <<-EXPECTED + +

                    teletype

                    +

                    teletype description

                    +
                    + EXPECTED + + assert_equal expected, @to.res.join end def accept_list_item_start_number - assert_equal "
                      \n
                    1. ", @to.res.join + assert_equal "
                      1. ", @to.res.join end def accept_list_item_start_ualpha - assert_equal "
                          \n
                        1. ", @to.res.join + assert_equal "
                          1. ", @to.res.join end def accept_list_start_bullet assert_equal [:BULLET], @to.list assert_equal [false], @to.in_list_entry - assert_equal "
                              \n", @to.res.join + assert_equal "
                                ", @to.res.join end def accept_list_start_label assert_equal [:LABEL], @to.list assert_equal [false], @to.in_list_entry - assert_equal "
                                \n", @to.res.join + assert_equal "
                                ", @to.res.join end def accept_list_start_lalpha assert_equal [:LALPHA], @to.list assert_equal [false], @to.in_list_entry - assert_equal "
                                  \n", @to.res.join + assert_equal "
                                    ", @to.res.join end def accept_list_start_note assert_equal [:NOTE], @to.list assert_equal [false], @to.in_list_entry - assert_equal "\n", @to.res.join + assert_equal "
                                    ", @to.res.join end def accept_list_start_number assert_equal [:NUMBER], @to.list assert_equal [false], @to.in_list_entry - assert_equal "
                                      \n", @to.res.join + assert_equal "
                                        ", @to.res.join end def accept_list_start_ualpha assert_equal [:UALPHA], @to.list assert_equal [false], @to.in_list_entry - assert_equal "
                                          \n", @to.res.join + assert_equal "
                                            ", @to.res.join end def accept_paragraph - assert_equal "

                                            \nhi\n

                                            \n", @to.res.join + assert_equal "\n

                                            hi

                                            \n", @to.res.join + end + + def accept_paragraph_b + assert_equal "\n

                                            reg bold words reg

                                            \n", @to.res.join + end + + def accept_paragraph_i + assert_equal "\n

                                            reg italic words reg

                                            \n", @to.res.join + end + + def accept_paragraph_plus + assert_equal "\n

                                            reg teletype reg

                                            \n", @to.res.join + end + + def accept_paragraph_star + assert_equal "\n

                                            reg bold reg

                                            \n", @to.res.join + end + + def accept_paragraph_underscore + assert_equal "\n

                                            reg italic reg

                                            \n", @to.res.join end def accept_raw @@ -183,11 +239,11 @@ class TestRDocMarkupToHtml < RDoc::Markup::FormatterTestCase end def accept_rule - assert_equal '
                                            ', @to.res.join + assert_equal "
                                            \n", @to.res.join end def accept_verbatim - assert_equal "
                                            \n  hi\n  world\n
                                            \n", @to.res.join + assert_equal "\n
                                            hi\n  world
                                            \n", @to.res.join end def end_accepting @@ -200,54 +256,70 @@ class TestRDocMarkupToHtml < RDoc::Markup::FormatterTestCase assert_equal [], @to.list end - def test_list_verbatim + def list_nested + expected = <<-EXPECTED +
                                            • +

                                              l1

                                              +
                                              • +

                                                l1.1

                                                +
                                              +
                                            • +

                                              l2

                                              +
                                            + EXPECTED + + assert_equal expected, @to.res.join + end + + def list_verbatim + expected = <<-EXPECTED +
                                            • +

                                              list stuff

                                              + +
                                              * list
                                              +  with
                                              +
                                              +  second
                                              +
                                              +  1. indented
                                              +  2. numbered
                                              +
                                              +  third
                                              +
                                              +* second
                                              +
                                            + EXPECTED + + assert_equal expected, @to.end_accepting + end + + def test_convert_string + assert_equal '<>', @to.convert_string('<>') + end + + def test_list_verbatim_2 str = "* one\n verb1\n verb2\n* two\n" expected = <<-EXPECTED -
                                              -
                                            • -one -

                                              -
                                              -  verb1
                                              -  verb2
                                              -
                                              -
                                            • -
                                            • -two -

                                              -
                                            • -
                                            +
                                            • +

                                              one

                                              + +
                                              verb1
                                              +verb2
                                              +
                                            • +

                                              two

                                              +
                                            EXPECTED assert_equal expected, @m.convert(str, @to) end - def test_tt_formatting - assert_equal "

                                            \n--cats' cats’\n

                                            \n", - util_format("-- -- cats' cats'") - - assert_equal "

                                            \n\n

                                            \n", util_format("--") + def test_to_html + assert_equal "\n

                                            --

                                            \n", util_format("--") end - def test_convert_string_fancy - # - # The HTML typesetting is broken in a number of ways, but I have fixed - # the most glaring issues for single and double quotes. Note that - # "strange" symbols (periods or dashes) need to be at the end of the - # test case strings in order to suppress cross-references. - # - assert_equal "

                                            \n“cats”.\n

                                            \n", util_format("\"cats\".") - assert_equal "

                                            \n‘cats’.\n

                                            \n", util_format("\'cats\'.") - assert_equal "

                                            \ncat’s-\n

                                            \n", util_format("cat\'s-") - end - - def util_paragraph(text) - RDoc::Markup::Paragraph.new text - end - - def util_format(text) - paragraph = util_paragraph text + def util_format text + paragraph = RDoc::Markup::Paragraph.new text @to.start_accepting @to.accept_paragraph paragraph diff --git a/test/rdoc/test_rdoc_markup_to_html_crossref.rb b/test/rdoc/test_rdoc_markup_to_html_crossref.rb index 67dfc0cbc9..8c97941727 100644 --- a/test/rdoc/test_rdoc_markup_to_html_crossref.rb +++ b/test/rdoc/test_rdoc_markup_to_html_crossref.rb @@ -14,12 +14,11 @@ class TestRDocMarkupToHtmlCrossref < XrefTestCase end def assert_ref(path, ref) - assert_equal "

                                            \n#{ref}\n

                                            \n", - @xref.convert(ref) + assert_equal "\n

                                            #{ref}

                                            \n", @xref.convert(ref) end def refute_ref(body, ref) - assert_equal "

                                            \n#{body}\n

                                            \n", @xref.convert(ref) + assert_equal "\n

                                            #{body}

                                            \n", @xref.convert(ref) end def test_handle_special_CROSSREF_C2 @@ -108,16 +107,16 @@ class TestRDocMarkupToHtmlCrossref < XrefTestCase assert_ref 'C1.html#method-c-m', '::m' assert_ref 'C1.html#method-i-m', 'C1#m' - assert_ref 'C1.html#method-i-m', 'C1.m' + assert_ref 'C1.html#method-c-m', 'C1.m' assert_ref 'C1.html#method-c-m', 'C1::m' assert_ref 'C1.html#method-i-m', 'C1#m' assert_ref 'C1.html#method-i-m', 'C1#m()' assert_ref 'C1.html#method-i-m', 'C1#m(*)' - assert_ref 'C1.html#method-i-m', 'C1.m' - assert_ref 'C1.html#method-i-m', 'C1.m()' - assert_ref 'C1.html#method-i-m', 'C1.m(*)' + assert_ref 'C1.html#method-c-m', 'C1.m' + assert_ref 'C1.html#method-c-m', 'C1.m()' + assert_ref 'C1.html#method-c-m', 'C1.m(*)' assert_ref 'C1.html#method-c-m', 'C1::m' assert_ref 'C1.html#method-c-m', 'C1::m()' @@ -127,7 +126,8 @@ class TestRDocMarkupToHtmlCrossref < XrefTestCase assert_ref 'C2/C3.html#method-i-m', 'C2::C3.m' - assert_ref 'C2/C3/H1.html#method-i-m%3F', 'C2::C3::H1#m?' + # TODO stop escaping - HTML5 allows anything but space + assert_ref 'C2/C3/H1.html#method-i-m-3F', 'C2::C3::H1#m?' assert_ref 'C2/C3.html#method-i-m', '::C2::C3#m' assert_ref 'C2/C3.html#method-i-m', '::C2::C3#m()' @@ -153,8 +153,15 @@ class TestRDocMarkupToHtmlCrossref < XrefTestCase refute_ref '::C3::H1#n', '\::C3::H1#n' end + def test_handle_special_CROSSREF_show_hash_false + @xref.show_hash = false + + assert_equal "\n

                                            m

                                            \n", + @xref.convert('#m') + end + def test_handle_special_CROSSREF_special - assert_equal "

                                            \nC2::C3;method(*)\n

                                            \n", + assert_equal "\n

                                            C2::C3;method(*)

                                            \n", @xref.convert('C2::C3;method(*)') end diff --git a/test/rdoc/test_rdoc_markup_to_rdoc.rb b/test/rdoc/test_rdoc_markup_to_rdoc.rb index ac6884ba58..20ff937c5a 100644 --- a/test/rdoc/test_rdoc_markup_to_rdoc.rb +++ b/test/rdoc/test_rdoc_markup_to_rdoc.rb @@ -1,11 +1,12 @@ require 'rubygems' -require 'rdoc/markup/formatter_test_case' +require 'rdoc/markup/text_formatter_test_case' require 'rdoc/markup/to_rdoc' require 'minitest/autorun' -class TestRDocMarkupToRdoc < RDoc::Markup::FormatterTestCase +class TestRDocMarkupToRDoc < RDoc::Markup::TextFormatterTestCase add_visitor_tests + add_text_tests def setup super @@ -191,8 +192,8 @@ class TestRDocMarkupToRdoc < RDoc::Markup::FormatterTestCase assert_equal "#{'-' * 78}\n", @to.res.join end - def accept_verbatim # FormatterTestCase doesn't set indent for ToAnsi - assert_equal " hi\n world\n\n", @to.res.join + def accept_verbatim + assert_equal " hi\n world\n\n", @to.res.join end def end_accepting @@ -207,214 +208,102 @@ class TestRDocMarkupToRdoc < RDoc::Markup::FormatterTestCase assert_empty @to.list_width end - def test_accept_heading_1 - @to.start_accepting - @to.accept_heading @RM::Heading.new(1, 'Hello') - + def accept_heading_1 assert_equal "= Hello\n", @to.end_accepting end - def test_accept_heading_2 - @to.start_accepting - @to.accept_heading @RM::Heading.new(2, 'Hello') - + def accept_heading_2 assert_equal "== Hello\n", @to.end_accepting end - def test_accept_heading_3 - @to.start_accepting - @to.accept_heading @RM::Heading.new(3, 'Hello') - + def accept_heading_3 assert_equal "=== Hello\n", @to.end_accepting end - def test_accept_heading_4 - @to.start_accepting - @to.accept_heading @RM::Heading.new(4, 'Hello') - + def accept_heading_4 assert_equal "==== Hello\n", @to.end_accepting end - def test_accept_heading_indent - @to.start_accepting - @to.indent = 3 - @to.accept_heading @RM::Heading.new(1, 'Hello') - + def accept_heading_indent assert_equal " = Hello\n", @to.end_accepting end - def test_accept_heading_b - @to.start_accepting - @to.indent = 3 - @to.accept_heading @RM::Heading.new(1, '*Hello*') - - assert_equal " = Hello\n", @to.end_accepting + def accept_heading_b + assert_equal "= Hello\n", @to.end_accepting end - def test_accept_list_item_start_note_2 - list = @RM::List.new(:NOTE, - @RM::ListItem.new('teletype', - @RM::Paragraph.new('teletype description'))) - - @to.start_accepting - - list.accept @to - - expected = "teletype:\n teletype description\n\n" - - assert_equal expected, @to.end_accepting + def accept_heading_suppressed_crossref + assert_equal "= Hello\n", @to.end_accepting end - def test_accept_paragraph_b - @to.start_accepting - @to.accept_paragraph @RM::Paragraph.new('reg bold words reg') - - expected = "reg bold words reg\n" - - assert_equal expected, @to.end_accepting + def accept_list_item_start_note_2 + assert_equal "teletype:\n teletype description\n\n", @to.res.join end - def test_accept_paragraph_i - @to.start_accepting - @to.accept_paragraph @RM::Paragraph.new('reg italic words reg') - - expected = "reg italic words reg\n" - - assert_equal expected, @to.end_accepting + def accept_paragraph_b + assert_equal "reg bold words reg\n", @to.end_accepting end - def test_accept_paragraph_indent - @to.start_accepting - @to.indent = 3 - @to.accept_paragraph @RM::Paragraph.new('words ' * 30) + def accept_paragraph_i + assert_equal "reg italic words reg\n", @to.end_accepting + end + def accept_paragraph_indent expected = <<-EXPECTED words words words words words words words words words words words words words words words words words words words words words words words words - words words words words words words + words words words words words words EXPECTED assert_equal expected, @to.end_accepting end - def test_accept_paragraph_plus - @to.start_accepting - @to.accept_paragraph @RM::Paragraph.new('regular +teletype+ regular') - - expected = "regular teletype regular\n" - - assert_equal expected, @to.end_accepting + def accept_paragraph_plus + assert_equal "reg teletype reg\n", @to.end_accepting end - def test_accept_paragraph_star - @to.start_accepting - @to.accept_paragraph @RM::Paragraph.new('regular *bold* regular') - - expected = "regular bold regular\n" - - assert_equal expected, @to.end_accepting + def accept_paragraph_star + assert_equal "reg bold reg\n", @to.end_accepting end - def test_accept_paragraph_underscore - @to.start_accepting - @to.accept_paragraph @RM::Paragraph.new('regular _italic_ regular') - - expected = "regular italic regular\n" - - assert_equal expected, @to.end_accepting + def accept_paragraph_underscore + assert_equal "reg italic reg\n", @to.end_accepting end - def test_accept_paragraph_wrap - @to.start_accepting - @to.accept_paragraph @RM::Paragraph.new('words ' * 30) - + def accept_paragraph_wrap expected = <<-EXPECTED words words words words words words words words words words words words words words words words words words words words words words words words words words -words words words words +words words words words EXPECTED assert_equal expected, @to.end_accepting end - def test_accept_rule_indent - @to.start_accepting - @to.indent = 3 - - @to.accept_rule @RM::Rule.new(1) - + def accept_rule_indent assert_equal " #{'-' * 75}\n", @to.end_accepting end - def test_accept_verbatim_indent - @to.start_accepting - - @to.indent = 2 - - @to.accept_verbatim @RM::Verbatim.new(' ', 'hi', "\n", - ' ', 'world', "\n") - + def accept_verbatim_indent assert_equal " hi\n world\n\n", @to.end_accepting end - def test_accept_verbatim_big_indent - @to.start_accepting - - @to.indent = 2 - - @to.accept_verbatim @RM::Verbatim.new(' ', 'hi', "\n", - ' ', 'world', "\n") - + def accept_verbatim_big_indent assert_equal " hi\n world\n\n", @to.end_accepting end - def test_attributes - assert_equal 'Dog', @to.attributes("\\Dog") - end - - def test_list_nested - doc = @RM::Document.new( - @RM::List.new(:BULLET, - @RM::ListItem.new(nil, - @RM::Paragraph.new('l1'), - @RM::List.new(:BULLET, - @RM::ListItem.new(nil, - @RM::Paragraph.new('l1.1')))), - @RM::ListItem.new(nil, - @RM::Paragraph.new('l2')))) - - output = doc.accept @to - + def list_nested expected = <<-EXPECTED * l1 * l1.1 * l2 EXPECTED - assert_equal expected, output + assert_equal expected, @to.end_accepting end - def test_list_verbatim # HACK overblown - doc = @RM::Document.new( - @RM::List.new(:BULLET, - @RM::ListItem.new(nil, - @RM::Paragraph.new('list', 'stuff'), - @RM::BlankLine.new(), - @RM::Verbatim.new(' ', '*', ' ', 'list', "\n", - ' ', 'with', "\n", - "\n", - ' ', 'second', "\n", - "\n", - ' ', '1.', ' ', 'indented', "\n", - ' ', '2.', ' ', 'numbered', "\n", - "\n", - ' ', 'third', "\n", - "\n", - ' ', '*', ' ', 'second', "\n")))) - - output = doc.accept @to - - expected = <<-EXPECTED + def list_verbatim + expected = <<-EXPECTED # HACK overblown * list stuff * list @@ -431,7 +320,7 @@ words words words words EXPECTED - assert_equal expected, output + assert_equal expected, @to.end_accepting end end diff --git a/test/rdoc/test_rdoc_method_attr.rb b/test/rdoc/test_rdoc_method_attr.rb new file mode 100644 index 0000000000..007a3f6b35 --- /dev/null +++ b/test/rdoc/test_rdoc_method_attr.rb @@ -0,0 +1,122 @@ +require File.expand_path '../xref_test_case', __FILE__ + +class TestRDocMethodAttr < XrefTestCase + + def test_block_params_equal + + m = RDoc::MethodAttr.new(nil, 'foo') + + m.block_params = '' + assert_equal '', m.block_params + + m.block_params = 'a_var' + assert_equal 'a_var', m.block_params + + m.block_params = '()' + assert_equal '', m.block_params + + m.block_params = '(a_var, b_var)' + assert_equal 'a_var, b_var', m.block_params + + m.block_params = '.to_s + ""' + assert_equal '', m.block_params + + m.block_params = 'i.name' + assert_equal 'name', m.block_params + + m.block_params = 'attr.expanded_name, attr.value' + assert_equal 'expanded_name, value', m.block_params + + m.block_params = 'expanded_name, attr.value' + assert_equal 'expanded_name, value', m.block_params + + m.block_params = 'attr.expanded_name, value' + assert_equal 'expanded_name, value', m.block_params + + m.block_params = '(@base_notifier)' + assert_equal 'base_notifier', m.block_params + + m.block_params = 'if @signal_status == :IN_LOAD' + assert_equal '', m.block_params + + m.block_params = 'e if e.kind_of? Element' + assert_equal 'e', m.block_params + + m.block_params = '(e, f) if e.kind_of? Element' + assert_equal 'e, f', m.block_params + + m.block_params = 'back_path, back_name' + assert_equal 'back_path, back_name', m.block_params + + m.block_params = '(*a[1..-1])' + assert_equal '*a', m.block_params + + m.block_params = '@@context[:node] if defined? @@context[:node].namespace' + assert_equal 'context', m.block_params + + m.block_params = '(result, klass.const_get(constant_name))' + assert_equal 'result, const', m.block_params + + m.block_params = 'name.to_s if (bitmap & bit) != 0' + assert_equal 'name', m.block_params + + m.block_params = 'line unless line.deleted' + assert_equal 'line', m.block_params + + m.block_params = 'str + rs' + assert_equal 'str', m.block_params + + m.block_params = 'f+rs' + assert_equal 'f', m.block_params + + m.block_params = '[user, realm, hash[user]]' + assert_equal 'user, realm, hash', m.block_params + + m.block_params = 'proc{|rc| rc == "rc" ? irbrc : irbrc+rc| ... }' + assert_equal 'proc', m.block_params + + m.block_params = 'lambda { |x| x.to_i }' + assert_equal 'lambda', m.block_params + + m.block_params = '$&' + assert_equal 'str', m.block_params + + m.block_params = 'Inflections.instance' + assert_equal 'instance', m.block_params + + m.block_params = 'self.class::STARTED' + assert_equal 'STARTED', m.block_params + + m.block_params = 'Test::Unit::TestCase::STARTED' + assert_equal 'STARTED', m.block_params + + m.block_params = 'ActiveSupport::OptionMerger.new(self, options)' + assert_equal 'option_merger', m.block_params + + m.block_params = ', msg' + assert_equal '', m.block_params + + m.block_params = '[size.to_s(16), term, chunk, term].join' + assert_equal '[size, term, chunk, term].join', m.block_params + + m.block_params = 'YPath.new( path )' + assert_equal 'y_path', m.block_params + + end + + def test_find_method_or_attribute_recursive + inc = RDoc::Include.new 'M1', nil + @m1.add_include inc # M1 now includes itself + + assert_nil @m1_m.find_method_or_attribute 'm' + end + + def test_to_s + assert_equal 'RDoc::AnyMethod: C1#m', @c1_m.to_s + assert_equal 'RDoc::AnyMethod: C2#b', @c2_b.to_s + assert_equal 'RDoc::AnyMethod: C1::m', @c1__m.to_s + end + + +end + diff --git a/test/rdoc/test_rdoc_normal_class.rb b/test/rdoc/test_rdoc_normal_class.rb index b7471f654f..db07ecb9c7 100644 --- a/test/rdoc/test_rdoc_normal_class.rb +++ b/test/rdoc/test_rdoc_normal_class.rb @@ -10,7 +10,7 @@ class TestRDocNormalClass < XrefTestCase sub_klass = klass.add_class RDoc::NormalClass, 'SubClass', 'Klass' sub_klass.add_include incl - assert_equal [incl, klass], sub_klass.ancestors + assert_equal [incl.name, klass], sub_klass.ancestors end end diff --git a/test/rdoc/test_rdoc_normal_module.rb b/test/rdoc/test_rdoc_normal_module.rb index da48d33f55..570b2765c6 100644 --- a/test/rdoc/test_rdoc_normal_module.rb +++ b/test/rdoc/test_rdoc_normal_module.rb @@ -15,7 +15,12 @@ class TestRDocNormalModule < XrefTestCase mod.add_include incl - assert_equal [incl], mod.ancestors + assert_equal [incl.name], mod.ancestors + + mod2 = top_level.add_module RDoc::NormalModule, 'Inc2' + inc2 = RDoc::Include.new 'Inc2', '' + mod.add_include inc2 + assert_equal [mod2, incl.name], mod.ancestors end def test_module_eh diff --git a/test/rdoc/test_rdoc_options.rb b/test/rdoc/test_rdoc_options.rb index f4a8d51c8e..3d07353c5f 100644 --- a/test/rdoc/test_rdoc_options.rb +++ b/test/rdoc/test_rdoc_options.rb @@ -2,12 +2,182 @@ require 'rubygems' require 'minitest/autorun' require 'rdoc/options' +require 'fileutils' +require 'tmpdir' + class TestRDocOptions < MiniTest::Unit::TestCase def setup @options = RDoc::Options.new end + def test_check_files + out, err = capture_io do + Dir.mktmpdir do |dir| + Dir.chdir dir do + FileUtils.touch 'unreadable' + FileUtils.chmod 0, 'unreadable' + + @options.files = %w[nonexistent unreadable] + + @options.check_files + end + end + end + + assert_empty @options.files + + assert_equal '', out + + expected = <<-EXPECTED +file 'nonexistent' not found +file 'unreadable' not readable + EXPECTED + + assert_equal expected, err + end + + def test_dry_run_default + refute @options.dry_run + end + + def test_encoding_default + skip "Encoding not implemented" unless Object.const_defined? :Encoding + + assert_equal Encoding.default_external, @options.encoding + end + + def test_parse_dash_p + out, err = capture_io do + @options.parse %w[-p] + end + + assert @options.pipe + refute_match %r%^Usage: %, err + refute_match %r%^invalid options%, err + + assert_empty out + end + + def test_parse_dash_p_files + out, err = capture_io do + @options.parse ['-p', File.expand_path(__FILE__)] + end + + refute @options.pipe + refute_match %r%^Usage: %, err + assert_match %r%^invalid options: -p .with files.%, err + + assert_empty out + end + + def test_parse_default + @options.parse [] + + assert_equal RDoc::Generator::Darkfish, @options.generator + assert_equal 'darkfish', @options.template + assert_match %r%rdoc/generator/template/darkfish$%, @options.template_dir + end + + def test_parse_deprecated + dep_hash = RDoc::Options::DEPRECATED + options = dep_hash.keys.sort + + out, err = capture_io do + @options.parse options + end + + dep_hash.each_pair do |opt, message| + assert_match %r%.*#{opt}.+#{message}%, err + end + + assert_empty out + end + + def test_parse_dry_run + @options.parse %w[--dry-run] + + assert @options.dry_run + end + + def test_parse_encoding + skip "Encoding not implemented" unless Object.const_defined? :Encoding + + @options.parse %w[--encoding Big5] + + assert_equal Encoding::Big5, @options.encoding + assert_equal 'Big5', @options.charset + end + + def test_parse_encoding_invalid + skip "Encoding not implemented" unless Object.const_defined? :Encoding + + out, err = capture_io do + @options.parse %w[--encoding invalid] + end + + assert_match %r%^invalid options: --encoding invalid%, err + + assert_empty out + end + + def test_parse_formatter + e = assert_raises OptionParser::InvalidOption do + @options.parse %w[--format darkfish --format ri] + end + + assert_equal 'invalid option: --format generator already set to darkfish', + e.message + end + + def test_parse_formatter_ri + e = assert_raises OptionParser::InvalidOption do + @options.parse %w[--format darkfish --ri] + end + + assert_equal 'invalid option: --ri generator already set to darkfish', + e.message + + @options = RDoc::Options.new + + e = assert_raises OptionParser::InvalidOption do + @options.parse %w[--format darkfish -r] + end + + assert_equal 'invalid option: -r generator already set to darkfish', + e.message + end + + def test_parse_formatter_ri_site + e = assert_raises OptionParser::InvalidOption do + @options.parse %w[--format darkfish --ri-site] + end + + assert_equal 'invalid option: --ri-site generator already set to darkfish', + e.message + + @options = RDoc::Options.new + + e = assert_raises OptionParser::InvalidOption do + @options.parse %w[--format darkfish -R] + end + + assert_equal 'invalid option: -R generator already set to darkfish', + e.message + end + + def test_parse_help + out, = capture_io do + begin + @options.parse %w[--help] + rescue SystemExit + end + end + + assert_equal 1, out.scan(/HTML generator options:/).length + assert_equal 1, out.scan(/ri generator options:/). length + end + def test_parse_ignore_invalid out, err = capture_io do @options.parse %w[--ignore-invalid --bogus] @@ -15,6 +185,8 @@ class TestRDocOptions < MiniTest::Unit::TestCase refute_match %r%^Usage: %, err assert_match %r%^invalid options: --bogus%, err + + assert_empty out end def test_parse_ignore_invalid_default @@ -26,17 +198,21 @@ class TestRDocOptions < MiniTest::Unit::TestCase assert_match %r%^invalid options: --bogus%, err assert_equal 'BLAH', @options.main_page + + assert_empty out end def test_parse_ignore_invalid_no out, err = capture_io do assert_raises SystemExit do - @options.parse %w[--no-ignore-invalid --bogus] + @options.parse %w[--no-ignore-invalid --bogus=arg --bobogus --visibility=extended] end end assert_match %r%^Usage: %, err - assert_match %r%^invalid option: --bogus%, err + assert_match %r%^invalid options: --bogus=arg, --bobogus, --visibility=extended%, err + + assert_empty out end def test_parse_main @@ -50,24 +226,73 @@ class TestRDocOptions < MiniTest::Unit::TestCase assert_equal 'MAIN', @options.main_page end - def test_parse_dash_p + def test_parse_template out, err = capture_io do - @options.parse %w[-p] + @options.parse %w[--template darkfish] end - assert @options.pipe - refute_match %r%^Usage: %, err - refute_match %r%^invalid options%, err + assert_empty out + assert_empty err + + assert_equal 'darkfish', @options.template + + assert_match %r%rdoc/generator/template/darkfish$%, @options.template_dir end - def test_parse_dash_p_files + def test_parse_template_nonexistent out, err = capture_io do - @options.parse %w[-p README] + @options.parse %w[--template NONEXISTENT] end - refute @options.pipe - refute_match %r%^Usage: %, err - assert_match %r%^invalid options: -p .with files.%, err + assert_empty out + assert_equal "could not find template NONEXISTENT\n", err + + assert_equal 'darkfish', @options.template + assert_match %r%rdoc/generator/template/darkfish$%, @options.template_dir + end + + def test_parse_template_load_path + orig_LOAD_PATH = $LOAD_PATH.dup + + template_dir = nil + + Dir.mktmpdir do |dir| + $LOAD_PATH << dir + + template_dir = File.join dir, 'rdoc', 'generator', 'template', 'load_path' + + FileUtils.mkdir_p template_dir + + out, err = capture_io do + @options.parse %w[--template load_path] + end + + assert_empty out + assert_empty err + end + + assert_equal 'load_path', @options.template + assert_equal template_dir, @options.template_dir + ensure + $LOAD_PATH.replace orig_LOAD_PATH + end + + def test_setup_generator + test_generator = Object.new + def test_generator.setup_options(op) + @op = op + end + + def test_generator.op() @op end + + RDoc::RDoc::GENERATORS['TestGenerator'] = test_generator + + @options.setup_generator 'TestGenerator' + + assert_equal test_generator, @options.generator + assert_equal [test_generator], @options.generator_options + + assert_equal @options, test_generator.op end end diff --git a/test/rdoc/test_rdoc_parser.rb b/test/rdoc/test_rdoc_parser.rb index ebb520032d..2eb1ac1a67 100644 --- a/test/rdoc/test_rdoc_parser.rb +++ b/test/rdoc/test_rdoc_parser.rb @@ -11,25 +11,6 @@ class TestRDocParser < MiniTest::Unit::TestCase @binary_dat = File.expand_path '../binary.dat', __FILE__ end - def test_class_binary_eh_erb - erb = File.join Dir.tmpdir, "test_rdoc_parser_#{$$}.erb" - open erb, 'wb' do |io| - io.write 'blah blah <%= stuff %> <% more stuff %>' - end - - assert @RP.binary?(erb) - - erb_rb = File.join Dir.tmpdir, "test_rdoc_parser_#{$$}.erb.rb" - open erb_rb, 'wb' do |io| - io.write 'blah blah <%= stuff %>' - end - - refute @RP.binary?(erb_rb) - ensure - File.unlink erb - File.unlink erb_rb if erb_rb - end - def test_class_binary_eh_marshal marshal = File.join Dir.tmpdir, "test_rdoc_parser_#{$$}.marshal" open marshal, 'wb' do |io| @@ -42,6 +23,18 @@ class TestRDocParser < MiniTest::Unit::TestCase File.unlink marshal end + def test_class_binary_japanese_text + file_name = File.expand_path '../test.ja.txt', __FILE__ + refute @RP.binary?(file_name) + end + + def test_class_binary_japanese_rdoc + skip "Encoding not implemented" unless Object.const_defined? :Encoding + + file_name = File.expand_path '../test.ja.rdoc', __FILE__ + refute @RP.binary?(file_name) + end + def test_class_can_parse assert_equal @RP.can_parse(__FILE__), @RP::Ruby @@ -72,6 +65,10 @@ class TestRDocParser < MiniTest::Unit::TestCase def test_class_for_binary rp = @RP.dup + class << rp + alias old_can_parse can_parse + end + def rp.can_parse(*args) nil end assert_nil @RP.for(nil, @binary_dat, nil, nil, nil) diff --git a/test/rdoc/test_rdoc_parser_c.rb b/test/rdoc/test_rdoc_parser_c.rb index 4f5d9e3171..c6cc42081c 100644 --- a/test/rdoc/test_rdoc_parser_c.rb +++ b/test/rdoc/test_rdoc_parser_c.rb @@ -1,12 +1,52 @@ require 'stringio' require 'tempfile' require 'rubygems' -require 'minitest/unit' +require 'minitest/autorun' require 'rdoc/options' require 'rdoc/parser/c' +=begin + TODO: test call-seq parsing + +/* + * 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 + * + * Reads +ARGF+'s current file in its entirety, returning an +Array+ of its + * lines, one line per element. Lines are assumed to be separated by _sep_. + * + * lines = ARGF.readlines + * lines[0] #=> "This is line one\n" + */ + +assert call-seq did not stop at first empty line + +/* + * call-seq: + * + * flt ** other -> float + * + * Raises float the other power. + * + * 2.0**3 #=> 8.0 + */ + +assert call-seq correct (bug: was empty) + +/* call-seq: flt ** other -> float */ + +assert call-seq correct + +=end + class RDoc::Parser::C - attr_accessor :classes + attr_accessor :classes, :singleton_classes public :do_classes, :do_constants end @@ -30,6 +70,129 @@ class TestRDocParserC < MiniTest::Unit::TestCase @tempfile.close end + def test_do_attr_rb_attr + content = <<-EOF +void Init_Blah(void) { + cBlah = rb_define_class("Blah", rb_cObject); + + /* + * This is an accessor + */ + rb_attr(cBlah, rb_intern("accessor"), 1, 1, Qfalse); + + /* + * This is a reader + */ + rb_attr(cBlah, rb_intern("reader"), 1, 0, Qfalse); + + /* + * This is a writer + */ + rb_attr(cBlah, rb_intern("writer"), 0, 1, Qfalse); +} + EOF + + klass = util_get_class content, 'cBlah' + + attrs = klass.attributes + assert_equal 3, attrs.length, attrs.inspect + + accessor = attrs.shift + assert_equal 'accessor', accessor.name + assert_equal 'RW', accessor.rw + assert_equal 'This is an accessor', accessor.comment + + reader = attrs.shift + assert_equal 'reader', reader.name + assert_equal 'R', reader.rw + assert_equal 'This is a reader', reader.comment + + writer = attrs.shift + assert_equal 'writer', writer.name + assert_equal 'W', writer.rw + assert_equal 'This is a writer', writer.comment + end + + def test_do_attr_rb_define_attr + content = <<-EOF +void Init_Blah(void) { + cBlah = rb_define_class("Blah", rb_cObject); + + /* + * This is an accessor + */ + rb_define_attr(cBlah, "accessor", 1, 1); +} + EOF + + klass = util_get_class content, 'cBlah' + + attrs = klass.attributes + assert_equal 1, attrs.length, attrs.inspect + + accessor = attrs.shift + assert_equal 'accessor', accessor.name + assert_equal 'RW', accessor.rw + assert_equal 'This is an accessor', accessor.comment + end + + def test_do_aliases + content = <<-EOF +/* + * This should show up as an alias with documentation + */ +VALUE blah(VALUE klass, VALUE year) { +} + +void Init_Blah(void) { + cDate = rb_define_class("Date", rb_cObject); + + rb_define_method(cDate, "blah", blah, 1); + + rb_define_alias(cDate, "bleh", "blah"); +} + EOF + + klass = util_get_class content, 'cDate' + + methods = klass.method_list + assert_equal 2, methods.length + assert_equal 'bleh', methods.last.name + assert_equal 'blah', methods.last.is_alias_for.name + end + + def test_do_aliases_singleton + content = <<-EOF +/* + * This should show up as a method with documentation + */ +VALUE blah(VALUE klass, VALUE year) { +} + +void Init_Blah(void) { + cDate = rb_define_class("Date", rb_cObject); + sDate = rb_singleton_class(cDate); + + rb_define_method(sDate, "blah", blah, 1); + + /* + * This should show up as an alias + */ + rb_define_alias(sDate, "bleh", "blah"); +} + EOF + + klass = util_get_class content, 'cDate' + + methods = klass.method_list + + assert_equal 2, methods.length + assert_equal 'bleh', methods.last.name + assert methods.last.singleton + assert_equal 'blah', methods.last.is_alias_for.name + assert_equal 'This should show up as an alias', methods.last.comment + end + def test_do_classes_boot_class content = <<-EOF /* Document-class: Foo @@ -68,6 +231,17 @@ VALUE cFoo = rb_define_class("Foo", rb_cObject); assert_equal "this is the Foo class", klass.comment end + def test_do_classes_singleton + content = <<-EOF +VALUE cFoo = rb_define_class("Foo", rb_cObject); +VALUE cFooS = rb_singleton_class(cFoo); + EOF + + util_get_class content, 'cFooS' + + assert_equal 'Foo', @parser.singleton_classes['cFooS'] + end + def test_do_classes_class_under content = <<-EOF /* Document-class: Kernel::Foo @@ -201,6 +375,26 @@ Multiline comment goes here because this comment spans multiple lines. assert constants.empty?, constants.inspect end + def test_find_alias_comment + parser = util_parser '' + + comment = parser.find_alias_comment 'C', '[]', 'index' + + assert_equal '', comment + + parser = util_parser <<-C +/* + * comment + */ + +rb_define_alias(C, "[]", "index"); + C + + comment = parser.find_alias_comment 'C', '[]', 'index' + + assert_equal "/*\n * comment\n */\n\n", comment + end + def test_find_class_comment_include @options.rdoc_include << File.dirname(__FILE__) @@ -406,6 +600,113 @@ Init_Foo(void) { assert_equal "a comment for bar", bar.comment end + def test_find_modifiers_call_seq + comment = <<-COMMENT +/* call-seq: + * commercial() -> Date
                                            + * commercial(cwyear, cweek=41, cwday=5, sg=nil) -> Date [ruby 1.8]
                                            + * commercial(cwyear, cweek=1, cwday=1, sg=nil) -> Date [ruby 1.9] + * + * If no arguments are given: + * * ruby 1.8: returns a +Date+ for 1582-10-15 (the Day of Calendar Reform in + * Italy) + * * ruby 1.9: returns a +Date+ for julian day 0 + * + * Otherwise, returns a +Date+ for the commercial week year, commercial week, + * and commercial week day given. Ignores the 4th argument. + */ + + COMMENT + + parser = util_parser '' + method_obj = RDoc::AnyMethod.new nil, 'blah' + + parser.find_modifiers comment, method_obj + + expected = <<-CALL_SEQ.chomp +commercial() -> Date
                                            +commercial(cwyear, cweek=41, cwday=5, sg=nil) -> Date [ruby 1.8]
                                            +commercial(cwyear, cweek=1, cwday=1, sg=nil) -> Date [ruby 1.9] + + CALL_SEQ + + assert_equal expected, method_obj.call_seq + end + + def test_find_modifiers_nodoc + comment = <<-COMMENT +/* :nodoc: + * + * Blah + */ + + COMMENT + + parser = util_parser '' + method_obj = RDoc::AnyMethod.new nil, 'blah' + + parser.find_modifiers comment, method_obj + + assert_equal nil, method_obj.document_self + end + + def test_find_modifiers_yields + comment = <<-COMMENT +/* :yields: a, b + * + * Blah + */ + + COMMENT + + parser = util_parser '' + method_obj = RDoc::AnyMethod.new nil, 'blah' + + parser.find_modifiers comment, method_obj + + assert_equal 'a, b', method_obj.block_params + + expected = <<-EXPECTED +/* + * + * Blah + */ + + EXPECTED + + assert_equal expected, comment + end + + def test_handle_method + parser = util_parser "Document-method: BasicObject#==\n blah */" + + parser.handle_method 'method', 'rb_cBasicObject', '==', 'rb_obj_equal', 1 + + bo = @top_level.find_module_named 'BasicObject' + + assert_equal 1, bo.method_list.length + + equals2 = bo.method_list.first + + assert_equal '==', equals2.name + end + + def test_handle_method_initialize + parser = util_parser "Document-method: BasicObject::new\n blah */" + + parser.handle_method('private_method', 'rb_cBasicObject', + 'initialize', 'rb_obj_dummy', -1) + + bo = @top_level.find_module_named 'BasicObject' + + assert_equal 1, bo.method_list.length + + new = bo.method_list.first + + assert_equal 'new', new.name + assert_equal :public, new.visibility + end + def test_look_for_directives_in parser = util_parser '' @@ -442,6 +743,7 @@ Init_IO(void) { read_method = klass.method_list.first assert_equal "read", read_method.name assert_equal "Method Comment! ", read_method.comment + assert read_method.singleton end def test_define_method_private @@ -472,6 +774,65 @@ Init_IO(void) { assert_equal "Method Comment! ", read_method.comment end + def test_define_method_private_singleton + content = <<-EOF +/*Method Comment! */ +static VALUE +rb_io_s_read(argc, argv, io) + int argc; + VALUE *argv; + VALUE io; +{ +} + +void +Init_IO(void) { + /* + * a comment for class Foo on rb_define_class + */ + VALUE rb_cIO = rb_define_class("IO", rb_cObject); + VALUE rb_cIO_s = rb_singleton_class(rb_cIO); + rb_define_private_method(rb_cIO_s, "read", rb_io_s_read, -1); +} + EOF + + klass = util_get_class content, 'rb_cIO' + read_method = klass.method_list.first + assert_equal "read", read_method.name + assert_equal "Method Comment! ", read_method.comment + assert_equal :private, read_method.visibility + assert read_method.singleton + end + + def test_define_method_singleton + content = <<-EOF +/*Method Comment! */ +static VALUE +rb_io_s_read(argc, argv, io) + int argc; + VALUE *argv; + VALUE io; +{ +} + +void +Init_IO(void) { + /* + * a comment for class Foo on rb_define_class + */ + VALUE rb_cIO = rb_define_class("IO", rb_cObject); + VALUE rb_cIO_s = rb_singleton_class(rb_cIO); + rb_define_method(rb_cIO_s, "read", rb_io_s_read, -1); +} + EOF + + klass = util_get_class content, 'rb_cIO' + read_method = klass.method_list.first + assert_equal "read", read_method.name + assert_equal "Method Comment! ", read_method.comment + assert read_method.singleton + end + def util_get_class(content, name) @parser = util_parser content @parser.scan @@ -484,4 +845,3 @@ Init_IO(void) { end -MiniTest::Unit.autorun diff --git a/test/rdoc/test_rdoc_parser_perl.rb b/test/rdoc/test_rdoc_parser_perl.rb deleted file mode 100644 index ce4e7d2b65..0000000000 --- a/test/rdoc/test_rdoc_parser_perl.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'stringio' -require 'tempfile' -require 'rubygems' -require 'minitest/autorun' -require 'rdoc/options' -require 'rdoc/parser/perl' - -class TestRdocParserPerlPOD < MiniTest::Unit::TestCase - - def setup - @tempfile = Tempfile.new self.class.name - filename = @tempfile.path - - @top_level = RDoc::TopLevel.new filename - @fn = filename - @options = RDoc::Options.new - @stats = RDoc::Stats.new 0 - end - - def teardown - @tempfile.close - end - - def test_uncommented_perl - content = <<-EOF -while (<>) { - tr/a-z/A-Z; - print -} - EOF - - comment = util_get_comment content - assert_equal "", comment - end - - def test_perl_without_pod - content = <<-EOF -#!/usr/local/bin/perl -# -#This is a pointless perl program because it does -p. -# -while(<>) {print;}: - EOF - - comment = util_get_comment content - assert_equal "", comment - end - - def test_simple_pod_no_structure - content = <<-EOF -=begin pod - -This just contains plain old documentation - -=end - EOF - comment = util_get_comment content - assert_equal 'This just contains plain old documentation', comment - end - - # Get the comment of the @top_level when it has processed the input. - def util_get_comment(content) - parser = util_parser content - parser.scan.comment - end - - # create a new parser with the supplied content. - def util_parser(content) - RDoc::Parser::PerlPOD.new @top_level, @fn, content, @options, @stats - end - -end - diff --git a/test/rdoc/test_rdoc_parser_ruby.rb b/test/rdoc/test_rdoc_parser_ruby.rb index 33ffded723..f2fa18c67b 100644 --- a/test/rdoc/test_rdoc_parser_ruby.rb +++ b/test/rdoc/test_rdoc_parser_ruby.rb @@ -22,6 +22,8 @@ class TestRDocParserRuby < MiniTest::Unit::TestCase util_top_level @options = RDoc::Options.new @options.quiet = true + @options.option_parser = OptionParser.new + @stats = RDoc::Stats.new 0 end @@ -30,6 +32,30 @@ class TestRDocParserRuby < MiniTest::Unit::TestCase @tempfile2.close end + def test_get_symbol_or_name + util_parser "* & | + 5 / 4" + + assert_equal '*', @parser.get_symbol_or_name + + @parser.skip_tkspace + + assert_equal '&', @parser.get_symbol_or_name + + @parser.skip_tkspace + + assert_equal '|', @parser.get_symbol_or_name + + @parser.skip_tkspace + + assert_equal '+', @parser.get_symbol_or_name + + @parser.skip_tkspace + @parser.get_tk + @parser.skip_tkspace + + assert_equal '/', @parser.get_symbol_or_name + end + def test_look_for_directives_in_attr util_parser "" @@ -52,6 +78,61 @@ class TestRDocParserRuby < MiniTest::Unit::TestCase assert_equal "# :attr_writer: my_method\n", comment end + def test_remove_private_comments + util_parser '' + + comment = <<-EOS +# This is text +#-- +# this is private + EOS + + expected = <<-EOS +# This is text + EOS + + @parser.remove_private_comments(comment) + + assert_equal expected, comment + end + + def test_remove_private_comments_rule + util_parser '' + + comment = <<-EOS +# This is text with a rule: +# --- +# this is also text + EOS + + expected = comment.dup + + @parser.remove_private_comments(comment) + + assert_equal expected, comment + end + + def test_remove_private_comments_toggle + util_parser '' + + comment = <<-EOS +# This is text +#-- +# this is private +#++ +# This is text again. + EOS + + expected = <<-EOS +# This is text +# This is text again. + EOS + + @parser.remove_private_comments(comment) + + assert_equal expected, comment + end + def test_look_for_directives_in_commented util_parser "" @@ -70,9 +151,9 @@ class TestRDocParserRuby < MiniTest::Unit::TestCase def test_look_for_directives_in_enddoc util_parser "" - assert_throws :enddoc do - @parser.look_for_directives_in @top_level, "# :enddoc:\n" - end + @parser.look_for_directives_in @top_level, "# :enddoc:\n" + + assert @top_level.done_documenting end def test_look_for_directives_in_main @@ -105,13 +186,11 @@ class TestRDocParserRuby < MiniTest::Unit::TestCase @top_level.stop_doc assert !@top_level.document_self assert !@top_level.document_children - assert !@top_level.force_documentation @parser.look_for_directives_in @top_level, "# :startdoc:\n" assert @top_level.document_self assert @top_level.document_children - assert @top_level.force_documentation end def test_look_for_directives_in_stopdoc @@ -166,10 +245,29 @@ class TestRDocParserRuby < MiniTest::Unit::TestCase alas = @parser.parse_alias klass, RDoc::Parser::Ruby::NORMAL, tk, 'comment' - assert_equal 'bar', alas.old_name - assert_equal 'next=', alas.new_name - assert_equal klass, alas.parent - assert_equal 'comment', alas.comment + assert_equal 'bar', alas.old_name + assert_equal 'next=', alas.new_name + assert_equal klass, alas.parent + assert_equal 'comment', alas.comment + assert_equal @top_level, alas.file + end + + def test_parse_alias_singleton + klass = RDoc::NormalClass.new 'Foo' + klass.parent = @top_level + + util_parser "alias :next= :bar" + + tk = @parser.get_tk + + alas = @parser.parse_alias klass, RDoc::Parser::Ruby::SINGLE, tk, 'comment' + + assert_equal 'bar', alas.old_name + assert_equal 'next=', alas.new_name + assert_equal klass, alas.parent + assert_equal 'comment', alas.comment + assert_equal @top_level, alas.file + assert alas.singleton end def test_parse_alias_meta @@ -202,6 +300,7 @@ class TestRDocParserRuby < MiniTest::Unit::TestCase foo = klass.attributes.first assert_equal 'foo', foo.name assert_equal 'my attr', foo.comment + assert_equal @top_level, foo.file end def test_parse_attr_accessor @@ -222,6 +321,7 @@ class TestRDocParserRuby < MiniTest::Unit::TestCase assert_equal 'foo', foo.name assert_equal 'RW', foo.rw assert_equal 'my attr', foo.comment + assert_equal @top_level, foo.file bar = klass.attributes.last assert_equal 'bar', bar.name @@ -229,6 +329,21 @@ class TestRDocParserRuby < MiniTest::Unit::TestCase assert_equal 'my attr', bar.comment end + def test_parse_attr_accessor_nodoc + klass = RDoc::NormalClass.new 'Foo' + klass.parent = @top_level + + comment = "##\n# my attr\n" + + util_parser "attr_accessor :foo, :bar # :nodoc:" + + tk = @parser.get_tk + + @parser.parse_attr_accessor klass, RDoc::Parser::Ruby::NORMAL, tk, comment + + assert_equal 0, klass.attributes.length + end + def test_parse_attr_accessor_writer klass = RDoc::NormalClass.new 'Foo' klass.parent = @top_level @@ -247,6 +362,7 @@ class TestRDocParserRuby < MiniTest::Unit::TestCase assert_equal 'foo', foo.name assert_equal 'W', foo.rw assert_equal "my attr", foo.comment + assert_equal @top_level, foo.file bar = klass.attributes.last assert_equal 'bar', bar.name @@ -271,6 +387,7 @@ class TestRDocParserRuby < MiniTest::Unit::TestCase assert_equal 'foo', foo.name assert_equal 'RW', foo.rw assert_equal "my method", foo.comment + assert_equal @top_level, foo.file end def test_parse_meta_attr_accessor @@ -290,6 +407,7 @@ class TestRDocParserRuby < MiniTest::Unit::TestCase assert_equal 'foo', foo.name assert_equal 'RW', foo.rw assert_equal 'my method', foo.comment + assert_equal @top_level, foo.file end def test_parse_meta_attr_named @@ -309,6 +427,7 @@ class TestRDocParserRuby < MiniTest::Unit::TestCase assert_equal 'foo', foo.name assert_equal 'RW', foo.rw assert_equal 'my method', foo.comment + assert_equal @top_level, foo.file end def test_parse_meta_attr_reader @@ -327,6 +446,7 @@ class TestRDocParserRuby < MiniTest::Unit::TestCase assert_equal 'foo', foo.name assert_equal 'R', foo.rw assert_equal 'my method', foo.comment + assert_equal @top_level, foo.file end def test_parse_meta_attr_writer @@ -345,12 +465,13 @@ class TestRDocParserRuby < MiniTest::Unit::TestCase assert_equal 'foo', foo.name assert_equal 'W', foo.rw assert_equal "my method", foo.comment + assert_equal @top_level, foo.file end def test_parse_class comment = "##\n# my method\n" - util_parser 'class Foo; end' + util_parser "class Foo\nend" tk = @parser.get_tk @@ -379,11 +500,12 @@ end blah = foo.method_list.first assert_equal 'Foo#blah', blah.full_name + assert_equal @top_level, blah.file end def test_parse_class_nested_superclass - foo = RDoc::NormalModule.new 'Foo' - foo.parent = @top_level + util_top_level + foo = @top_level.add_module RDoc::NormalModule, 'Foo' util_parser "class Bar < Super\nend" @@ -398,7 +520,7 @@ end def test_parse_module comment = "##\n# my module\n" - util_parser 'module Foo; end' + util_parser "module Foo\nend" tk = @parser.get_tk @@ -430,6 +552,8 @@ class A class << B end class << d = Object.new + def foo; end + alias bar foo end end CODE @@ -439,7 +563,40 @@ end @parser.parse_class @top_level, false, @parser.get_tk, '' assert_equal %w[A], RDoc::TopLevel.classes.map { |c| c.full_name } - assert_equal %w[A::B], RDoc::TopLevel.modules.map { |c| c.full_name } + assert_equal %w[A::B A::d], RDoc::TopLevel.modules.map { |c| c.full_name } + + # make sure method/alias was not added to enclosing class/module + a = RDoc::TopLevel.all_classes_hash['A'] + assert_empty a.method_list + + # make sure non-constant-named module will be removed from documentation + d = RDoc::TopLevel.all_modules_hash['A::d'] + assert d.remove_from_documentation? + + end + + # TODO this is really a Context#add_class test + def test_parse_class_object + code = <<-CODE +module A + class B + end + class Object + end + class C < Object + end +end + CODE + + util_parser code + + @parser.parse_module @top_level, false, @parser.get_tk, '' + + assert_equal %w[A], RDoc::TopLevel.modules.map { |c| c.full_name } + assert_equal %w[A::B A::C A::Object], RDoc::TopLevel.classes.map { |c| c.full_name }.sort + assert_equal 'Object', RDoc::TopLevel.classes_hash['A::B'].superclass + assert_equal 'Object', RDoc::TopLevel.classes_hash['A::Object'].superclass + assert_equal 'A::Object', RDoc::TopLevel.classes_hash['A::C'].superclass.full_name end def test_parse_class_mistaken_for_module @@ -447,7 +604,7 @@ end # before Foo::Bar is encountered), but RDoc might encounter Foo::Bar # before Foo if they live in different files. - code = <<-EOF + code = <<-RUBY class Foo::Bar end @@ -456,7 +613,7 @@ end class Foo end - EOF + RUBY util_parser code @@ -542,18 +699,20 @@ EOF @parser.parse_comment klass, tk, comment foo = klass.attributes.first - assert_equal 'foo', foo.name - assert_equal 'RW', foo.rw - assert_equal 'my attr', foo.comment + assert_equal 'foo', foo.name + assert_equal 'RW', foo.rw + assert_equal 'my attr', foo.comment + assert_equal @top_level, foo.file + + assert_equal nil, foo.viewer + assert_equal true, foo.document_children + assert_equal true, foo.document_self + assert_equal false, foo.done_documenting + assert_equal false, foo.force_documentation + assert_equal klass, foo.parent + assert_equal :public, foo.visibility + assert_equal "\n", foo.text - assert_equal nil, foo.viewer - assert_equal true, foo.document_children - assert_equal true, foo.document_self - assert_equal false, foo.done_documenting - assert_equal false, foo.force_documentation - assert_equal klass, foo.parent - assert_equal :public, foo.visibility - assert_equal "\n", foo.text assert_equal klass.current_section, foo.section end @@ -570,8 +729,9 @@ EOF @parser.parse_comment klass, tk, comment foo = klass.method_list.first - assert_equal 'foo', foo.name - assert_equal 'my method', foo.comment + assert_equal 'foo', foo.name + assert_equal 'my method', foo.comment + assert_equal @top_level, foo.file assert_equal [], foo.aliases assert_equal nil, foo.block_params @@ -599,9 +759,23 @@ EOF assert_equal stream, foo.token_stream end + def test_parse_constant_attrasgn + util_top_level + + klass = @top_level.add_class RDoc::NormalClass, 'Foo' + + util_parser "A[k] = v" + + tk = @parser.get_tk + + @parser.parse_constant klass, tk, '' + + assert klass.constants.empty? + end + def test_parse_constant_alias - klass = RDoc::NormalClass.new 'Foo' - klass.parent = @top_level + util_top_level + klass = @top_level.add_class RDoc::NormalClass, 'Foo' cB = klass.add_class RDoc::NormalClass, 'B' util_parser "A = B" @@ -642,8 +816,9 @@ EOF @parser.parse_meta_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment foo = klass.method_list.first - assert_equal 'foo', foo.name + assert_equal 'foo', foo.name assert_equal 'my method', foo.comment + assert_equal @top_level, foo.file assert_equal [], foo.aliases assert_equal nil, foo.block_params @@ -691,8 +866,9 @@ EOF @parser.parse_meta_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment foo = klass.method_list.first - assert_equal 'woo_hoo!', foo.name + assert_equal 'woo_hoo!', foo.name assert_equal 'my method', foo.comment + assert_equal @top_level, foo.file end def test_parse_meta_method_singleton @@ -711,6 +887,7 @@ EOF assert_equal 'foo', foo.name assert_equal true, foo.singleton, 'singleton method' assert_equal 'my method', foo.comment + assert_equal @top_level, foo.file end def test_parse_meta_method_singleton_name @@ -729,6 +906,7 @@ EOF assert_equal 'woo_hoo!', foo.name assert_equal true, foo.singleton, 'singleton method' assert_equal 'my method', foo.comment + assert_equal @top_level, foo.file end def test_parse_meta_method_string_name @@ -744,6 +922,7 @@ EOF foo = klass.method_list.first assert_equal 'foo', foo.name assert_equal 'my method', foo.comment + assert_equal @top_level, foo.file end def test_parse_meta_method_unknown @@ -759,6 +938,7 @@ EOF foo = klass.method_list.first assert_equal 'unknown', foo.name assert_equal 'my method', foo.comment + assert_equal @top_level, foo.file end def test_parse_method @@ -774,8 +954,9 @@ EOF @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment foo = klass.method_list.first - assert_equal 'foo', foo.name - assert_equal 'my method', foo.comment + assert_equal 'foo', foo.name + assert_equal 'my method', foo.comment + assert_equal @top_level, foo.file assert_equal [], foo.aliases assert_equal nil, foo.block_params @@ -826,41 +1007,55 @@ EOF assert klass.aliases.empty? end - def test_parse_method_utf8 - klass = RDoc::NormalClass.new 'Foo' - klass.parent = @top_level - - comment = "##\n# my method\n" - - method = "def ω() end" - - assert_equal Encoding::UTF_8, method.encoding if defined? ::Encoding - - util_parser method + def test_parse_method_false + util_parser "def false.foo() :bar end" tk = @parser.get_tk - @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment + @parser.parse_method @top_level, RDoc::Parser::Ruby::NORMAL, tk, '' - omega = klass.method_list.first - assert_equal "def \317\211", omega.text + klass = RDoc::TopLevel.find_class_named 'FalseClass' + + foo = klass.method_list.first + assert_equal 'foo', foo.name end def test_parse_method_funky klass = RDoc::NormalClass.new 'Foo' klass.parent = @top_level - comment = "##\n# my method\n" - util_parser "def (blah).foo() :bar end" tk = @parser.get_tk - @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment + @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, '' assert klass.method_list.empty? end + def test_parse_method_gvar + util_parser "def $stdout.foo() :bar end" + + tk = @parser.get_tk + + @parser.parse_method @top_level, RDoc::Parser::Ruby::NORMAL, tk, '' + + assert @top_level.method_list.empty? + end + + def test_parse_method_internal_gvar + klass = RDoc::NormalClass.new 'Foo' + klass.parent = @top_level + + util_parser "def foo() def $blah.bar() end end" + + tk = @parser.get_tk + + @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, '' + + assert_equal 1, klass.method_list.length + end + def test_parse_method_internal_ivar klass = RDoc::NormalClass.new 'Foo' klass.parent = @top_level @@ -887,33 +1082,43 @@ EOF assert_equal 1, klass.method_list.length end + def test_parse_method_nil + util_parser "def nil.foo() :bar end" + + tk = @parser.get_tk + + @parser.parse_method @top_level, RDoc::Parser::Ruby::NORMAL, tk, '' + + klass = RDoc::TopLevel.find_class_named 'NilClass' + + foo = klass.method_list.first + assert_equal 'foo', foo.name + end + def test_parse_method_no_parens klass = RDoc::NormalClass.new 'Foo' klass.parent = @top_level - comment = "##\n# my method\n" - - util_parser "def foo arg1, arg2\nend" + util_parser "def foo arg1, arg2 = {}\nend" tk = @parser.get_tk - @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment + @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, '' foo = klass.method_list.first - assert_equal '(arg1, arg2)', foo.params + assert_equal '(arg1, arg2 = {})', foo.params + assert_equal @top_level, foo.file end def test_parse_method_parameters_comment klass = RDoc::NormalClass.new 'Foo' klass.parent = @top_level - comment = "##\n# my method\n" - util_parser "def foo arg1, arg2 # some useful comment\nend" tk = @parser.get_tk - @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment + @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, '' foo = klass.method_list.first assert_equal '(arg1, arg2)', foo.params @@ -923,13 +1128,11 @@ EOF klass = RDoc::NormalClass.new 'Foo' klass.parent = @top_level - comment = "##\n# my method\n" - util_parser "def foo arg1, arg2, # some useful comment\narg3\nend" tk = @parser.get_tk - @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment + @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, '' foo = klass.method_list.first assert_equal '(arg1, arg2, arg3)', foo.params @@ -938,18 +1141,17 @@ EOF def test_parse_method_toplevel klass = @top_level - comment = "##\n# my method\n" - util_parser "def foo arg1, arg2\nend" tk = @parser.get_tk - @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, comment + @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, '' object = RDoc::TopLevel.find_class_named 'Object' foo = object.method_list.first assert_equal 'Object#foo', foo.full_name + assert_equal @top_level, foo.file end def test_parse_method_toplevel_class @@ -967,9 +1169,39 @@ EOF assert_equal 'Object::foo', foo.full_name end - def test_parse_statements_class_if - comment = "##\n# my method\n" + def test_parse_method_true + util_parser "def true.foo() :bar end" + tk = @parser.get_tk + + @parser.parse_method @top_level, RDoc::Parser::Ruby::NORMAL, tk, '' + + klass = RDoc::TopLevel.find_class_named 'TrueClass' + + foo = klass.method_list.first + assert_equal 'foo', foo.name + end + + def test_parse_method_utf8 + klass = RDoc::NormalClass.new 'Foo' + klass.parent = @top_level + + method = "def ω() end" + + assert_equal Encoding::UTF_8, method.encoding if + Object.const_defined? :Encoding + + util_parser method + + tk = @parser.get_tk + + @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, '' + + omega = klass.method_list.first + assert_equal "def \317\211", omega.text + end + + def test_parse_statements_class_if util_parser <<-CODE module Foo X = if TRUE then @@ -1024,7 +1256,12 @@ end end def test_parse_statements_identifier_alias_method - content = "class Foo def foo() end; alias_method :foo2, :foo end" + content = <<-RUBY +class Foo + def foo() end + alias_method :foo2, :foo +end + RUBY util_parser content @@ -1078,10 +1315,25 @@ EOF assert_equal 'foo4', foo4.name assert_equal 'foo', foo4.is_alias_for.name - assert_equal 'unknown', @top_level.classes.first.aliases[0].old_name + assert_equal 'unknown', @top_level.classes.first.external_aliases[0].old_name end def test_parse_statements_identifier_constant + + sixth_constant = <<-EOF +Class.new do + rule :file do + all(x, y, z) { + def value + find(:require).each {|r| require r.value } + find(:grammar).map {|g| g.value } + end + def min; end + } + end +end + EOF + content = <<-EOF class Foo FIRST_CONSTANT = 5 @@ -1103,6 +1355,10 @@ class Foo end FIFTH_CONSTANT = SECOND_CONSTANT.map { |element| element + 1 } + + SIXTH_CONSTANT = #{sixth_constant} + + SEVENTH_CONSTANT = proc { |i| begin i end } end EOF @@ -1115,26 +1371,43 @@ EOF constant = constants[0] assert_equal 'FIRST_CONSTANT', constant.name assert_equal '5', constant.value + assert_equal @top_level, constant.file constant = constants[1] assert_equal 'SECOND_CONSTANT', constant.name - assert_equal '[ 1, 2, 3 ]', constant.value + assert_equal "[\n1,\n2,\n3\n]", constant.value + assert_equal @top_level, constant.file constant = constants[2] assert_equal 'THIRD_CONSTANT', constant.name - assert_equal "{ :foo => 'bar', :x => 'y' }", constant.value + assert_equal "{\n:foo => 'bar',\n:x => 'y'\n}", constant.value + assert_equal @top_level, constant.file constant = constants[3] assert_equal 'FOURTH_CONSTANT', constant.name - assert_equal 'SECOND_CONSTANT.map do |element| element + 1 element + 2 end', constant.value + assert_equal "SECOND_CONSTANT.map do |element|\nelement + 1\nelement + 2\nend", constant.value + assert_equal @top_level, constant.file - constant = constants.last + constant = constants[4] assert_equal 'FIFTH_CONSTANT', constant.name assert_equal 'SECOND_CONSTANT.map { |element| element + 1 }', constant.value + assert_equal @top_level, constant.file + + # TODO: parse as class + constant = constants[5] + assert_equal 'SIXTH_CONSTANT', constant.name + assert_equal sixth_constant.lines.map(&:strip).join("\n"), constant.value + assert_equal @top_level, constant.file + + # TODO: parse as method + constant = constants[6] + assert_equal 'SEVENTH_CONSTANT', constant.name + assert_equal "proc { |i| begin i end }", constant.value + assert_equal @top_level, constant.file end def test_parse_statements_identifier_attr - content = "class Foo; attr :foo; end" + content = "class Foo\nattr :foo\nend" util_parser content @@ -1146,7 +1419,7 @@ EOF end def test_parse_statements_identifier_attr_accessor - content = "class Foo; attr_accessor :foo; end" + content = "class Foo\nattr_accessor :foo\nend" util_parser content @@ -1158,7 +1431,7 @@ EOF end def test_parse_statements_identifier_include - content = "class Foo; include Bar; end" + content = "class Foo\ninclude Bar\nend" util_parser content @@ -1170,24 +1443,24 @@ EOF end def test_parse_statements_identifier_module_function - content = "module Foo def foo() end; module_function :foo; end" + content = "module Foo\ndef foo() end\nmodule_function :foo\nend" util_parser content @parser.parse_statements @top_level, RDoc::Parser::Ruby::NORMAL, nil, '' foo, s_foo = @top_level.modules.first.method_list - assert_equal 'foo', foo.name, 'instance method name' + assert_equal 'foo', foo.name, 'instance method name' assert_equal :private, foo.visibility, 'instance method visibility' - assert_equal false, foo.singleton, 'instance method singleton' + assert_equal false, foo.singleton, 'instance method singleton' - assert_equal 'foo', s_foo.name, 'module function name' + assert_equal 'foo', s_foo.name, 'module function name' assert_equal :public, s_foo.visibility, 'module function visibility' - assert_equal true, s_foo.singleton, 'module function singleton' + assert_equal true, s_foo.singleton, 'module function singleton' end def test_parse_statements_identifier_private - content = "class Foo private; def foo() end end" + content = "class Foo\nprivate\ndef foo() end\nend" util_parser content @@ -1235,6 +1508,24 @@ end assert_equal 'A#b', m_b.full_name end + def test_parse_symbol_in_arg + util_parser ':blah "blah" "#{blah}" blah' + + assert_equal 'blah', @parser.parse_symbol_in_arg + + @parser.skip_tkspace + + assert_equal 'blah', @parser.parse_symbol_in_arg + + @parser.skip_tkspace + + assert_equal nil, @parser.parse_symbol_in_arg + + @parser.skip_tkspace + + assert_equal nil, @parser.parse_symbol_in_arg + end + def test_parse_top_level_statements_alias_method content = <<-CONTENT class A @@ -1252,6 +1543,20 @@ end @parser.parse_statements @top_level end + def test_parse_yield_in_braces_with_parens + klass = RDoc::NormalClass.new 'Foo' + klass.parent = @top_level + + util_parser "def foo\nn.times { |i| yield nth(i) }\nend" + + tk = @parser.get_tk + + @parser.parse_method klass, RDoc::Parser::Ruby::NORMAL, tk, '' + + foo = klass.method_list.first + assert_equal 'nth(i)', foo.block_params + end + def test_sanity_integer util_parser '1' assert_equal '1', @parser.get_tk.text @@ -1272,7 +1577,6 @@ end # If you're writing code like this you're doing it wrong def test_sanity_interpolation_crazy - last_tk = nil util_parser '"#{"#{"a")}" if b}"' assert_equal '"#{"#{"a")}" if b}"', @parser.get_tk.text @@ -1280,7 +1584,6 @@ end end def test_sanity_interpolation_curly - last_tk = nil util_parser '%{ #{} }' assert_equal '%{ #{} }', @parser.get_tk.text @@ -1290,13 +1593,39 @@ end def test_sanity_interpolation_format util_parser '"#{stftime("%m-%d")}"' - while tk = @parser.get_tk do end + while @parser.get_tk do end end def test_sanity_symbol_interpolation util_parser ':"#{bar}="' - while tk = @parser.get_tk do end + while @parser.get_tk do end + end + + def test_stopdoc_after_comment + + util_parser <<-EOS + module Bar + # hello + module Foo + # :stopdoc: + end + # there + class Baz + # :stopdoc: + end + end + EOS + + @parser.parse_statements @top_level, RDoc::Parser::Ruby::NORMAL, nil, '' + + foo = @top_level.modules.first.modules.first + assert_equal 'Foo', foo.name + assert_equal 'hello', foo.comment + + baz = @top_level.modules.first.classes.first + assert_equal 'Baz', baz.name + assert_equal 'there', baz.comment end def tk(klass, line, char, name, text) diff --git a/test/rdoc/test_rdoc_parser_simple.rb b/test/rdoc/test_rdoc_parser_simple.rb index d09cced5a4..8cedfaa2fc 100644 --- a/test/rdoc/test_rdoc_parser_simple.rb +++ b/test/rdoc/test_rdoc_parser_simple.rb @@ -41,7 +41,6 @@ contents of a string. parser.scan expected = <<-TEXT.strip - Regular expressions (regexps) are patterns which describe the contents of a string. TEXT @@ -49,6 +48,31 @@ contents of a string. assert_equal expected, @top_level.comment end + # RDoc stops processing comments if it finds a comment line CONTAINING + # '#--'. 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 '#++'. + # + # I've seen guys that comment their code like this: + # # This method.... + # #----------------- + # def method + # + # => either we do it only in ruby code, or we require the leading # + # (to avoid conflict with rules). + # + # TODO: require the leading #, to provide the feature in simple text files. + # Note: in ruby & C code, we require '#--' & '#++' or '*--' & '*++', + # to allow rules: + # + # # this is a comment + # #--- + # # private text + # #+++ + # # this is a rule: + # # --- + def test_remove_private_comments parser = util_parser '' text = "foo\n\n--\nbar\n++\n\nbaz\n" diff --git a/test/rdoc/test_rdoc_rdoc.rb b/test/rdoc/test_rdoc_rdoc.rb index b02ef2ae1a..d65e2f3427 100644 --- a/test/rdoc/test_rdoc_rdoc.rb +++ b/test/rdoc/test_rdoc_rdoc.rb @@ -1,15 +1,21 @@ -require 'tempfile' -require 'tmpdir' require 'rubygems' require 'minitest/autorun' require 'rdoc/rdoc' +require 'fileutils' +require 'tempfile' +require 'tmpdir' + class TestRDocRDoc < MiniTest::Unit::TestCase def setup @rdoc = RDoc::RDoc.new + @rdoc.options = RDoc::Options.new + + @stats = RDoc::Stats.new 0, 0 + @rdoc.instance_variable_set :@stats, @stats + @tempfile = Tempfile.new 'test_rdoc_rdoc' - @tempfile.binmode end def teardown @@ -39,48 +45,6 @@ class TestRDocRDoc < MiniTest::Unit::TestCase assert_empty files end - def test_read_file_contents - @tempfile.write "hi everybody" - @tempfile.flush - - assert_equal "hi everybody", @rdoc.read_file_contents(@tempfile.path) - end - - def test_read_file_contents_encoding - skip "Encoding not implemented" unless defined? ::Encoding - - @tempfile.write "# coding: utf-8\nhi everybody" - @tempfile.flush - - contents = @rdoc.read_file_contents @tempfile.path - assert_equal "# coding: utf-8\nhi everybody", contents - assert_equal Encoding::UTF_8, contents.encoding - end - - def test_read_file_contents_encoding_fancy - skip "Encoding not implemented" unless defined? ::Encoding - - @tempfile.write "# -*- coding: utf-8; fill-column: 74 -*-\nhi everybody" - @tempfile.flush - - contents = @rdoc.read_file_contents @tempfile.path - assert_equal("# -*- coding: utf-8; fill-column: 74 -*-\nhi everybody", - contents) - assert_equal Encoding::UTF_8, contents.encoding - end - - def test_read_file_contents_encoding_with_signature - skip "Encoding not implemented" unless defined? ::Encoding - - @tempfile.write "\xEF\xBB\xBF""hi everybody" - @tempfile.flush - - bug3360 = '[ruby-dev:41452]' - contents = @rdoc.read_file_contents @tempfile.path - assert_equal "hi everybody", contents, bug3360 - assert_equal Encoding::UTF_8, contents.encoding, bug3360 - end - def test_remove_unparsable file_list = %w[ blah.class @@ -108,6 +72,20 @@ class TestRDocRDoc < MiniTest::Unit::TestCase } end + def test_setup_output_dir_dry_run + skip "No Dir::mktmpdir, upgrade your ruby" unless Dir.respond_to? :mktmpdir + + @rdoc.options.dry_run = true + + Dir.mktmpdir do |d| + path = File.join d, 'testdir' + + @rdoc.setup_output_dir path, false + + refute File.exist? path + end + end + def test_setup_output_dir_exists skip "No Dir::mktmpdir, upgrade your ruby" unless Dir.respond_to? :mktmpdir @@ -161,5 +139,26 @@ class TestRDocRDoc < MiniTest::Unit::TestCase end end + def test_update_output_dir + skip "No Dir::mktmpdir, upgrade your ruby" unless Dir.respond_to? :mktmpdir + + Dir.mktmpdir do |d| + @rdoc.update_output_dir d, Time.now, {} + + assert File.exist? "#{d}/created.rid" + end + end + + def test_update_output_dir_dry_run + skip "No Dir::mktmpdir, upgrade your ruby" unless Dir.respond_to? :mktmpdir + + Dir.mktmpdir do |d| + @rdoc.options.dry_run = true + @rdoc.update_output_dir d, Time.now, {} + + refute File.exist? "#{d}/created.rid" + end + end + end diff --git a/test/rdoc/test_rdoc_ri_driver.rb b/test/rdoc/test_rdoc_ri_driver.rb index 9ff89077b6..75ecfefe8e 100644 --- a/test/rdoc/test_rdoc_ri_driver.rb +++ b/test/rdoc/test_rdoc_ri_driver.rb @@ -53,7 +53,7 @@ class TestRDocRIDriver < MiniTest::Unit::TestCase def test_self_dump util_store - out, err = capture_io do + out, = capture_io do RDoc::RI::Driver.dump @store.cache_path end @@ -83,8 +83,8 @@ class TestRDocRIDriver < MiniTest::Unit::TestCase expected = @RM::Document.new( @RM::Rule.new(1), @RM::Paragraph.new('Also found in:'), - @RM::Verbatim.new(' ', 'ruby core', "\n", - ' ', '~/.ri', "\n")) + @RM::Verbatim.new("ruby core\n", + "~/.ri\n")) assert_equal expected, out end @@ -143,7 +143,7 @@ class TestRDocRIDriver < MiniTest::Unit::TestCase @RM::BlankLine.new, @RM::Paragraph.new("Include thingy"), @RM::BlankLine.new, - @RM::Verbatim.new(' ', 'Enumerable', "\n")) + @RM::Verbatim.new("Enumerable\n")) assert_equal expected, out end @@ -163,8 +163,8 @@ class TestRDocRIDriver < MiniTest::Unit::TestCase @RM::Rule.new(1), @RM::Heading.new(1, "Includes:"), @RM::Paragraph.new("(from #{@store.friendly_path})"), - @RM::Verbatim.new(' ', 'Inc', "\n", - ' ', 'Enumerable', "\n")) + @RM::Verbatim.new("Inc\n", + "Enumerable\n")) assert_equal expected, out end @@ -195,7 +195,7 @@ class TestRDocRIDriver < MiniTest::Unit::TestCase expected = @RM::Document.new( @RM::Heading.new(1, 'Class methods:'), @RM::BlankLine.new, - @RM::Verbatim.new(' ', 'new'), + @RM::Verbatim.new('new'), @RM::BlankLine.new) assert_equal expected, out @@ -285,7 +285,7 @@ class TestRDocRIDriver < MiniTest::Unit::TestCase doc = @RM::Document.new( @RM::Paragraph.new('hi')) - out, err = capture_io do + out, = capture_io do @driver.display doc end @@ -295,12 +295,12 @@ class TestRDocRIDriver < MiniTest::Unit::TestCase def test_display_class util_store - out, err = capture_io do + out, = capture_io do @driver.display_class 'Foo::Bar' end assert_match %r%^= Foo::Bar%, out - assert_match %r%^\(from%, out # ) + assert_match %r%^\(from%, out assert_match %r%^= Class methods:%, out assert_match %r%^ new%, out @@ -315,7 +315,7 @@ class TestRDocRIDriver < MiniTest::Unit::TestCase def test_display_class_ambiguous util_multi_store - out, err = capture_io do + out, = capture_io do @driver.display_class 'Ambiguous' end @@ -325,7 +325,7 @@ class TestRDocRIDriver < MiniTest::Unit::TestCase def test_display_class_multi_no_doc util_multi_store - out, err = capture_io do + out, = capture_io do @driver.display_class 'Foo::Baz' end @@ -339,7 +339,7 @@ class TestRDocRIDriver < MiniTest::Unit::TestCase def test_display_class_superclass util_multi_store - out, err = capture_io do + out, = capture_io do @driver.display_class 'Bar' end @@ -349,7 +349,7 @@ class TestRDocRIDriver < MiniTest::Unit::TestCase def test_display_class_module util_store - out, err = capture_io do + out, = capture_io do @driver.display_class 'Inc' end @@ -359,7 +359,7 @@ class TestRDocRIDriver < MiniTest::Unit::TestCase def test_display_method util_store - out, err = capture_io do + out, = capture_io do @driver.display_method 'Foo::Bar#blah' end @@ -371,7 +371,7 @@ class TestRDocRIDriver < MiniTest::Unit::TestCase def test_display_method_attribute util_store - out, err = capture_io do + out, = capture_io do @driver.display_method 'Foo::Bar#attr' end @@ -382,7 +382,7 @@ class TestRDocRIDriver < MiniTest::Unit::TestCase def test_display_method_inherited util_multi_store - out, err = capture_io do + out, = capture_io do @driver.display_method 'Bar#inherit' end @@ -393,7 +393,7 @@ class TestRDocRIDriver < MiniTest::Unit::TestCase def test_display_name_not_found_class util_store - out, err = capture_io do + out, = capture_io do assert_equal false, @driver.display_name('Foo::B') end @@ -410,7 +410,7 @@ Foo::Baz def test_display_name_not_found_method util_store - out, err = capture_io do + out, = capture_io do assert_equal false, @driver.display_name('Foo::Bar#b') end @@ -427,7 +427,7 @@ Foo::Bar#bother def test_display_method_params util_store - out, err = capture_io do + out, = capture_io do @driver.display_method 'Foo::Bar#bother' end @@ -496,24 +496,34 @@ Foo::Bar#bother end def test_formatter + tty = Object.new + def tty.tty?() true; end + driver = RDoc::RI::Driver.new - io = Object.new - def io.tty?; false; end + assert_instance_of @RM::ToAnsi, driver.formatter(tty) - assert_instance_of @RM::ToBs, driver.formatter(io) - - def io.tty?; true; end - - assert_instance_of @RM::ToAnsi, driver.formatter(io) + assert_instance_of @RM::ToBs, driver.formatter(StringIO.new) driver.instance_variable_set :@paging, true - assert_instance_of @RM::ToBs, driver.formatter(io) + assert_instance_of @RM::ToBs, driver.formatter(StringIO.new) driver.instance_variable_set :@formatter_klass, @RM::ToHtml - assert_instance_of @RM::ToHtml, driver.formatter(io) + assert_instance_of @RM::ToHtml, driver.formatter(tty) + end + + def test_in_path_eh + path = ENV['PATH'] + + refute @driver.in_path?('/nonexistent') + + ENV['PATH'] = File.expand_path '..', __FILE__ + + assert @driver.in_path?(File.basename(__FILE__)) + ensure + ENV['PATH'] = path end def test_method_type @@ -526,8 +536,8 @@ Foo::Bar#bother def test_list_known_classes util_store - out, err = capture_io do - @driver.list_known_classes + out, = capture_io do + @driver.list_known_classes end assert_equal "Ambiguous\nFoo\nFoo::Bar\nFoo::Baz\nInc\n", out diff --git a/test/rdoc/test_rdoc_ri_store.rb b/test/rdoc/test_rdoc_ri_store.rb index 4a52ded989..7c9f4ec80b 100644 --- a/test/rdoc/test_rdoc_ri_store.rb +++ b/test/rdoc/test_rdoc_ri_store.rb @@ -65,6 +65,10 @@ class TestRDocRIStore < MiniTest::Unit::TestCase assert File.file?(path), "#{path} is not a file" end + def refute_file path + refute File.exist?(path), "#{path} exists" + end + def test_attributes @s.cache[:attributes]['Object'] = %w[attr] @@ -94,6 +98,14 @@ class TestRDocRIStore < MiniTest::Unit::TestCase @s.class_path('Object::SubClass') end + def test_dry_run + refute @s.dry_run + + @s.dry_run = true + + assert @s.dry_run + end + def test_friendly_path @s.path = @tmpdir @s.type = nil @@ -198,7 +210,7 @@ class TestRDocRIStore < MiniTest::Unit::TestCase :instance_methods => { 'Object' => %w[method] }, :modules => %w[Object Object::SubClass], :ancestors => { - 'Object' => %w[Object], + 'Object' => %w[], 'Object::SubClass' => %w[Incl Object], }, } @@ -210,6 +222,19 @@ class TestRDocRIStore < MiniTest::Unit::TestCase end end + def test_save_cache_dry_run + @s.dry_run = true + + @s.save_class @klass + @s.save_method @klass, @meth + @s.save_method @klass, @cmeth + @s.save_class @nest_klass + + @s.save_cache + + refute_file File.join(@tmpdir, 'cache.ri') + end + def test_save_cache_duplicate_methods @s.save_method @klass, @meth @s.save_method @klass, @meth @@ -226,7 +251,7 @@ class TestRDocRIStore < MiniTest::Unit::TestCase assert_file File.join(@tmpdir, 'Object', 'cdesc-Object.ri') assert_cache({}, {}, { 'Object' => ['attr_accessor attr'] }, %w[Object], - 'Object' => %w[Object]) + 'Object' => %w[]) assert_equal @klass, @s.load_class('Object') end @@ -245,6 +270,15 @@ class TestRDocRIStore < MiniTest::Unit::TestCase assert_equal @klass, @s.load_class('Object') end + def test_save_class_dry_run + @s.dry_run = true + + @s.save_class @klass + + refute_file File.join(@tmpdir, 'Object') + refute_file File.join(@tmpdir, 'Object', 'cdesc-Object.ri') + end + def test_save_class_merge @s.save_class @klass @@ -270,7 +304,7 @@ class TestRDocRIStore < MiniTest::Unit::TestCase assert_file File.join(@tmpdir, 'Object', 'cdesc-Object.ri') assert_cache({}, {}, { 'Object' => ['attr_accessor attr'] }, %w[Object], - 'Object' => %w[Object]) + 'Object' => %w[]) assert_equal @klass, @s.load_class('Object') end @@ -296,6 +330,15 @@ class TestRDocRIStore < MiniTest::Unit::TestCase assert_equal @meth, @s.load_method('Object', '#method') end + def test_save_method_dry_run + @s.dry_run = true + + @s.save_method @klass, @meth + + refute_file File.join(@tmpdir, 'Object') + refute_file File.join(@tmpdir, 'Object', 'method-i.ri') + end + def test_save_method_nested @s.save_method @nest_klass, @nest_meth diff --git a/test/rdoc/test_rdoc_task.rb b/test/rdoc/test_rdoc_task.rb index e95ac46d4c..8eaff1f25b 100644 --- a/test/rdoc/test_rdoc_task.rb +++ b/test/rdoc/test_rdoc_task.rb @@ -42,7 +42,7 @@ class TestRDocTask < MiniTest::Unit::TestCase end def test_tasks_creation_with_custom_name_hash_will_use_default_if_an_option_isnt_given - rd = RDoc::Task.new(:clobber_rdoc => "rdoc:clean") + RDoc::Task.new(:clobber_rdoc => "rdoc:clean") assert Rake::Task[:rdoc] assert Rake::Task[:"rdoc:clean"] assert Rake::Task[:rerdoc] diff --git a/test/rdoc/test_rdoc_text.rb b/test/rdoc/test_rdoc_text.rb index 7e0f2cf0ba..600de30b0b 100644 --- a/test/rdoc/test_rdoc_text.rb +++ b/test/rdoc/test_rdoc_text.rb @@ -1,3 +1,5 @@ +# coding: utf-8 + require 'rubygems' require 'minitest/autorun' require 'rdoc' @@ -9,6 +11,15 @@ class TestRDocText < MiniTest::Unit::TestCase include RDoc::Text + def test_self_encode_fallback + skip "Encoding not implemented" unless Object.const_defined? :Encoding + + assert_equal '…', + RDoc::Text::encode_fallback('…', Encoding::UTF_8, '...') + assert_equal '...', + RDoc::Text::encode_fallback('…', Encoding::US_ASCII, '...') + end + def test_expand_tabs assert_equal("hello\n dave", expand_tabs("hello\n dave"), 'spaces') @@ -46,9 +57,9 @@ class TestRDocText < MiniTest::Unit::TestCase def test_flush_left text = <<-TEXT - + we don't worry too much. - + The comments associated with TEXT @@ -65,7 +76,7 @@ The comments associated with def test_markup def formatter() RDoc::Markup::ToHtml.new end - assert_equal "

                                            \nhi\n

                                            \n", markup('hi') + assert_equal "

                                            hi

                                            ", markup('hi').gsub("\n", '') end def test_normalize_comment @@ -114,9 +125,9 @@ The comments associated with TEXT expected = <<-EXPECTED - + we don't worry too much. - + The comments associated with EXPECTED @@ -143,15 +154,98 @@ The comments associated with TEXT expected = <<-EXPECTED - + * we don't worry too much. - + The comments associated with - EXPECTED assert_equal expected, strip_stars(text) end + def test_to_html_apostrophe + assert_equal '‘a', to_html("'a") + assert_equal 'a’', to_html("a'") + + assert_equal '‘a’ ‘', to_html("'a' '") + end + + def test_to_html_backslash + assert_equal 'S', to_html('\\S') + end + + def test_to_html_copyright + assert_equal '©', to_html('(c)') + end + + def test_to_html_dash + assert_equal '-', to_html('-') + assert_equal '–', to_html('--') + assert_equal '—', to_html('---') + assert_equal '—-', to_html('----') + end + + def test_to_html_double_backtick + assert_equal '“a', to_html('``a') + assert_equal '“a“', to_html('``a``') + end + + def test_to_html_double_quote + assert_equal '“a', to_html('"a') + assert_equal '“a”', to_html('"a"') + end + + def test_to_html_double_quote_quot + assert_equal '“a', to_html('"a') + assert_equal '“a”', to_html('"a"') + end + + def test_to_html_double_tick + assert_equal '”a', to_html("''a") + assert_equal '”a”', to_html("''a''") + end + + def test_to_html_ellipsis + assert_equal '..', to_html('..') + assert_equal '…', to_html('...') + assert_equal '.…', to_html('....') + end + + def test_to_html_encoding + skip "Encoding not implemented" unless Object.const_defined? :Encoding + + s = '...(c)'.encode Encoding::Shift_JIS + + html = to_html s + + assert_equal Encoding::Shift_JIS, html.encoding + + expected = '…(c)'.encode Encoding::Shift_JIS + + assert_equal expected, html + end + + def test_to_html_html_tag + assert_equal 'hi’s', + to_html('hi\'s') + end + + def test_to_html_registered_trademark + assert_equal '®', to_html('(r)') + end + + def test_to_html_tt_tag + assert_equal 'hi\'s', to_html('hi\'s') + assert_equal 'hi\\\'s', to_html('hi\\\\\'s') + end + + def test_to_html_tt_tag_mismatch + _, err = capture_io do + assert_equal 'hi', to_html('hi') + end + + assert_equal "mismatched tag\n", err + end + end diff --git a/test/rdoc/test_rdoc_top_level.rb b/test/rdoc/test_rdoc_top_level.rb index b96b74c182..f40a42b3f6 100644 --- a/test/rdoc/test_rdoc_top_level.rb +++ b/test/rdoc/test_rdoc_top_level.rb @@ -27,6 +27,17 @@ class TestRDocTopLevel < XrefTestCase assert_equal expected, RDoc::TopLevel.classes.map { |m| m.full_name }.sort end + def test_class_complete + @c2.add_module_alias @c2_c3, 'A1' + + RDoc::TopLevel.complete :public + + a1 = @xref_data.find_class_or_module 'C2::A1' + + assert_equal 'C2::A1', a1.full_name + refute_empty a1.aliases + end + def test_class_files assert_equal %w[path/top_level.rb xref_data.rb], RDoc::TopLevel.files.map { |m| m.full_name }.sort @@ -94,13 +105,11 @@ class TestRDocTopLevel < XrefTestCase end def test_last_modified - assert_equal 'Unknown', @top_level.last_modified - + assert_equal nil, @top_level.last_modified stat = Object.new def stat.mtime() 0 end @top_level.file_stat = stat - - assert_equal '0', @top_level.last_modified + assert_equal 0, @top_level.last_modified end def test_name diff --git a/test/rdoc/xref_data.rb b/test/rdoc/xref_data.rb index ac1c39a915..3afb06bd13 100644 --- a/test/rdoc/xref_data.rb +++ b/test/rdoc/xref_data.rb @@ -13,9 +13,15 @@ class C1 def m foo end + end class C2 + def b + end + + alias a b + class C3 def m end @@ -46,6 +52,8 @@ class C5 end module M1 + def m + end end module M1::M2 diff --git a/test/rdoc/xref_test_case.rb b/test/rdoc/xref_test_case.rb index b40956684b..307e8f3350 100644 --- a/test/rdoc/xref_test_case.rb +++ b/test/rdoc/xref_test_case.rb @@ -38,7 +38,11 @@ class XrefTestCase < MiniTest::Unit::TestCase @c1_m = @c1.method_list.last # C1#m @c1__m = @c1.method_list.first # C1::m + @c2 = @xref_data.find_module_named 'C2' + @c2_a = @c2.method_list.last + @c2_b = @c2.method_list.first + @c2_c3 = @xref_data.find_module_named 'C2::C3' @c3 = @xref_data.find_module_named 'C3' @c4 = @xref_data.find_module_named 'C4' @@ -48,6 +52,8 @@ class XrefTestCase < MiniTest::Unit::TestCase @c3_h2 = @xref_data.find_module_named 'C3::H2' @m1 = @xref_data.find_module_named 'M1' + @m1_m = @m1.method_list.first + @m1_m2 = @xref_data.find_module_named 'M1::M2' end