summaryrefslogtreecommitdiff
path: root/lib/rdoc
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rdoc')
-rw-r--r--lib/rdoc/README232
-rw-r--r--lib/rdoc/alias.rb54
-rw-r--r--lib/rdoc/anon_class.rb10
-rw-r--r--lib/rdoc/any_method.rb282
-rw-r--r--lib/rdoc/attr.rb165
-rw-r--r--lib/rdoc/class_module.rb213
-rw-r--r--lib/rdoc/code_object.rb175
-rw-r--r--lib/rdoc/code_objects.rb1074
-rw-r--r--lib/rdoc/constant.rb58
-rw-r--r--lib/rdoc/context.rb755
-rw-r--r--lib/rdoc/diagram.rb340
-rw-r--r--lib/rdoc/dot.rb249
-rw-r--r--lib/rdoc/generator.rb1080
-rw-r--r--lib/rdoc/generator/chm.rb113
-rw-r--r--lib/rdoc/generator/chm/chm.rb100
-rw-r--r--lib/rdoc/generator/darkfish.rb459
-rw-r--r--lib/rdoc/generator/html.rb449
-rw-r--r--lib/rdoc/generator/html/common.rb24
-rw-r--r--lib/rdoc/generator/html/frameless.rb92
-rw-r--r--lib/rdoc/generator/html/hefss.rb150
-rw-r--r--lib/rdoc/generator/html/html.rb769
-rw-r--r--lib/rdoc/generator/html/kilmer.rb151
-rw-r--r--lib/rdoc/generator/html/kilmerfactory.rb427
-rw-r--r--lib/rdoc/generator/html/one_page_html.rb122
-rw-r--r--lib/rdoc/generator/markup.rb171
-rw-r--r--lib/rdoc/generator/ri.rb226
-rw-r--r--lib/rdoc/generator/template/darkfish/.document0
-rw-r--r--lib/rdoc/generator/template/darkfish/classpage.rhtml297
-rw-r--r--lib/rdoc/generator/template/darkfish/filepage.rhtml124
-rw-r--r--lib/rdoc/generator/template/darkfish/images/brick.pngbin0 -> 452 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/images/brick_link.pngbin0 -> 764 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/images/bug.pngbin0 -> 774 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/images/bullet_black.pngbin0 -> 211 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/images/bullet_toggle_minus.pngbin0 -> 207 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/images/bullet_toggle_plus.pngbin0 -> 209 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/images/date.pngbin0 -> 626 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/images/find.pngbin0 -> 659 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/images/loadingAnimation.gifbin0 -> 5886 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/images/macFFBgHack.pngbin0 -> 207 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/images/package.pngbin0 -> 853 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/images/page_green.pngbin0 -> 621 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/images/page_white_text.pngbin0 -> 342 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/images/page_white_width.pngbin0 -> 309 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/images/plugin.pngbin0 -> 591 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/images/ruby.pngbin0 -> 592 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/images/tag_green.pngbin0 -> 613 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/images/wrench.pngbin0 -> 610 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/images/wrench_orange.pngbin0 -> 584 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/images/zoom.pngbin0 -> 692 bytes
-rw-r--r--lib/rdoc/generator/template/darkfish/index.rhtml64
-rw-r--r--lib/rdoc/generator/template/darkfish/js/darkfish.js116
-rw-r--r--lib/rdoc/generator/template/darkfish/js/jquery.js32
-rw-r--r--lib/rdoc/generator/template/darkfish/js/quicksearch.js114
-rw-r--r--lib/rdoc/generator/template/darkfish/js/thickbox-compressed.js10
-rw-r--r--lib/rdoc/generator/template/darkfish/rdoc.css701
-rw-r--r--lib/rdoc/generator/texinfo.rb81
-rw-r--r--lib/rdoc/generator/texinfo/class.texinfo.erb44
-rw-r--r--lib/rdoc/generator/texinfo/file.texinfo.erb6
-rw-r--r--lib/rdoc/generator/texinfo/method.texinfo.erb6
-rw-r--r--lib/rdoc/generator/texinfo/texinfo.erb28
-rw-r--r--lib/rdoc/generator/xml.rb117
-rw-r--r--lib/rdoc/generator/xml/rdf.rb113
-rw-r--r--lib/rdoc/generator/xml/xml.rb123
-rw-r--r--lib/rdoc/ghost_method.rb8
-rw-r--r--lib/rdoc/include.rb61
-rw-r--r--lib/rdoc/markup.rb286
-rw-r--r--lib/rdoc/markup/attribute_manager.rb141
-rw-r--r--lib/rdoc/markup/blank_line.rb19
-rw-r--r--lib/rdoc/markup/document.rb72
-rw-r--r--lib/rdoc/markup/formatter.rb129
-rw-r--r--lib/rdoc/markup/formatter_test_case.rb341
-rw-r--r--lib/rdoc/markup/fragments.rb337
-rw-r--r--lib/rdoc/markup/heading.rb17
-rw-r--r--lib/rdoc/markup/inline.rb42
-rw-r--r--lib/rdoc/markup/lines.rb152
-rw-r--r--lib/rdoc/markup/list.rb78
-rw-r--r--lib/rdoc/markup/list_item.rb83
-rw-r--r--lib/rdoc/markup/paragraph.rb66
-rw-r--r--lib/rdoc/markup/parser.rb528
-rw-r--r--lib/rdoc/markup/preprocess.rb11
-rw-r--r--lib/rdoc/markup/rule.rb17
-rw-r--r--lib/rdoc/markup/to_ansi.rb72
-rw-r--r--lib/rdoc/markup/to_bs.rb74
-rw-r--r--lib/rdoc/markup/to_flow.rb185
-rw-r--r--lib/rdoc/markup/to_html.rb327
-rw-r--r--lib/rdoc/markup/to_html_crossref.rb84
-rw-r--r--lib/rdoc/markup/to_latex.rb328
-rw-r--r--lib/rdoc/markup/to_rdoc.rb243
-rw-r--r--lib/rdoc/markup/to_test.rb46
-rw-r--r--lib/rdoc/markup/to_texinfo.rb69
-rw-r--r--lib/rdoc/markup/verbatim.rb42
-rw-r--r--lib/rdoc/meta_method.rb8
-rw-r--r--lib/rdoc/normal_class.rb55
-rw-r--r--lib/rdoc/normal_module.rb64
-rw-r--r--lib/rdoc/options.rb422
-rw-r--r--lib/rdoc/parser.rb64
-rw-r--r--lib/rdoc/parser/c.rb288
-rw-r--r--lib/rdoc/parser/f95.rb1835
-rw-r--r--lib/rdoc/parser/perl.rb16
-rw-r--r--lib/rdoc/parser/ruby.rb2240
-rw-r--r--lib/rdoc/parser/ruby_tools.rb157
-rw-r--r--lib/rdoc/parser/simple.rb5
-rw-r--r--lib/rdoc/rdoc.rb543
-rw-r--r--lib/rdoc/require.rb32
-rw-r--r--lib/rdoc/ri.rb10
-rw-r--r--lib/rdoc/ri/cache.rb187
-rw-r--r--lib/rdoc/ri/descriptions.rb156
-rw-r--r--lib/rdoc/ri/display.rb392
-rw-r--r--lib/rdoc/ri/driver.rb1155
-rw-r--r--lib/rdoc/ri/formatter.rb615
-rw-r--r--lib/rdoc/ri/gemdirs.rb28
-rw-r--r--lib/rdoc/ri/paths.rb148
-rw-r--r--lib/rdoc/ri/reader.rb106
-rw-r--r--lib/rdoc/ri/store.rb248
-rw-r--r--lib/rdoc/ri/util.rb79
-rw-r--r--lib/rdoc/ri/writer.rb68
-rw-r--r--lib/rdoc/ruby_lex.rb1284
-rw-r--r--lib/rdoc/ruby_token.rb416
-rw-r--r--lib/rdoc/single_class.rb13
-rw-r--r--lib/rdoc/stats.rb216
-rw-r--r--lib/rdoc/task.rb254
-rw-r--r--lib/rdoc/template.rb64
-rw-r--r--lib/rdoc/text.rb130
-rw-r--r--lib/rdoc/tokenstream.rb37
-rw-r--r--lib/rdoc/top_level.rb248
125 files changed, 11217 insertions, 14140 deletions
diff --git a/lib/rdoc/README b/lib/rdoc/README
deleted file mode 100644
index f183c61f8d..0000000000
--- a/lib/rdoc/README
+++ /dev/null
@@ -1,232 +0,0 @@
-= RDOC - Ruby Documentation System
-
-This package contains RDoc and RDoc::Markup. RDoc is an application that
-produces documentation for one or more Ruby source files. We work similarly to
-JavaDoc, parsing the source, and extracting the definition for classes,
-modules, and methods (along with includes and requires). We associate with
-these optional documentation contained in the immediately preceding comment
-block, and then render the result using a pluggable output formatter.
-RDoc::Markup is a library that converts plain text into various output formats.
-The markup library is used to interpret the comment blocks that RDoc uses to
-document methods, classes, and so on.
-
-== Roadmap
-
-* If you want to use RDoc to create documentation for your Ruby source files,
- read on.
-* If you want to include extensions written in C, see RDoc::C_Parser
-* For information on the various markups available in comment blocks, see
- RDoc::Markup.
-* 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
-
-== Summary
-
-Once installed, you can create documentation using the 'rdoc' command
-(the command is 'rdoc.bat' under Windows)
-
- % rdoc [options] [names...]
-
-Type "rdoc --help" for an up-to-date option summary.
-
-A typical use might be to generate documentation for a package of Ruby
-source (such as rdoc itself).
-
- % rdoc
-
-This command generates documentation for all the Ruby and C source
-files in and below the current directory. These will be stored in a
-documentation tree starting in the subdirectory 'doc'.
-
-You can make this slightly more useful for your readers by having the
-index page contain the documentation for the primary file. In our
-case, we could type
-
- % rdoc --main rdoc.rb
-
-You'll find information on the various formatting tricks you can use
-in comment blocks in the documentation this generates.
-
-RDoc uses file extensions to determine how to process each file. File names
-ending +.rb+ and <tt>.rbw</tt> are assumed to be Ruby source. Files
-ending +.c+ are parsed as C files. All other files are assumed to
-contain just Markup-style markup (with or without leading '#' comment markers).
-If directory names are passed to RDoc, they are scanned recursively for C and
-Ruby source files only.
-
-= Markup
-
-For information on how to make lists, hyperlinks, & etc. with RDoc, see
-RDoc::Markup.
-
-Comment blocks can be written fairly naturally, either using '#' on successive
-lines of the comment, or by including the comment in an =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 '+#--+'.
-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 '+#+++'.
-
- ##
- # 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, source 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.
-
-== Directives
-
-[+:nodoc:+ / +:nodoc:+ all]
- Don't include this element in the documentation. For classes
- and modules, the methods, aliases, constants, and attributes
- directly within the affected class or module will also 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.
-
-[+:doc:+]
- Force a method or attribute to be documented even if it wouldn't otherwise
- be. 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 protected, 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.
-
-= Other stuff
-
-Author:: Dave Thomas <dave@pragmaticprogrammer.com>
-
-== Credits
-
-* 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.
-
-* Code to diagram classes and modules was written by Sergey A Yanovitsky
- (Jah) of Enticla.
-
-* 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.
-
diff --git a/lib/rdoc/alias.rb b/lib/rdoc/alias.rb
new file mode 100644
index 0000000000..6cd9af09d4
--- /dev/null
+++ b/lib/rdoc/alias.rb
@@ -0,0 +1,54 @@
+require 'rdoc/code_object'
+
+##
+# Represent an alias, which is an old_name/new_name pair associated with a
+# particular context
+
+class RDoc::Alias < RDoc::CodeObject
+
+ ##
+ # Allow comments to be overridden
+
+ attr_writer :comment
+
+ ##
+ # Aliased name
+
+ attr_accessor :new_name
+
+ ##
+ # Aliasee's name
+
+ attr_accessor :old_name
+
+ ##
+ # Source file token stream
+
+ attr_accessor :text
+
+ ##
+ # Creates a new Alias with a token stream of +text+ that aliases +old_name+
+ # to +new_name+ and has +comment+
+
+ def initialize(text, old_name, new_name, comment)
+ super()
+ @text = text
+ @old_name = old_name
+ @new_name = new_name
+ self.comment = comment
+ end
+
+ def inspect # :nodoc:
+ parent_name = parent ? parent.name : '(unknown)'
+ "#<%s:0x%x %s.alias_method %s, %s>" % [
+ self.class, object_id,
+ parent_name, @old_name, @new_name,
+ ]
+ end
+
+ def to_s # :nodoc:
+ "alias: #{self.old_name} -> #{self.new_name}\n#{self.comment}"
+ end
+
+end
+
diff --git a/lib/rdoc/anon_class.rb b/lib/rdoc/anon_class.rb
new file mode 100644
index 0000000000..0a684f2dfb
--- /dev/null
+++ b/lib/rdoc/anon_class.rb
@@ -0,0 +1,10 @@
+require 'rdoc/class_module'
+
+##
+# An anonymous class like:
+#
+# c = Class.new do end
+
+class RDoc::AnonClass < RDoc::ClassModule
+end
+
diff --git a/lib/rdoc/any_method.rb b/lib/rdoc/any_method.rb
new file mode 100644
index 0000000000..5d291e82c8
--- /dev/null
+++ b/lib/rdoc/any_method.rb
@@ -0,0 +1,282 @@
+require 'rdoc/code_object'
+require 'rdoc/tokenstream'
+
+##
+# AnyMethod is the base class for objects representing methods
+
+class RDoc::AnyMethod < RDoc::CodeObject
+
+ MARSHAL_VERSION = 0 # :nodoc:
+
+ include Comparable
+
+ ##
+ # Method name
+
+ attr_writer :name
+
+ ##
+ # public, protected, private
+
+ attr_accessor :visibility
+
+ ##
+ # Parameters yielded by the called block
+
+ attr_accessor :block_params
+
+ ##
+ # Don't rename \#initialize to \::new
+
+ attr_accessor :dont_rename_initialize
+
+ ##
+ # Is this a singleton method?
+
+ attr_accessor :singleton
+
+ ##
+ # Source file token stream
+
+ attr_reader :text
+
+ ##
+ # Array of other names for this method
+
+ attr_reader :aliases
+
+ ##
+ # Fragment reference for this method
+
+ attr_reader :aref
+
+ ##
+ # The method we're aliasing
+
+ attr_accessor :is_alias_for
+
+ ##
+ # Parameters for this method
+
+ attr_overridable :params, :param, :parameters, :parameter
+
+ ##
+ # Different ways to call this method
+
+ attr_accessor :call_seq
+
+ include RDoc::TokenStream
+
+ ##
+ # Resets method fragment reference counter
+
+ def self.reset
+ @@aref = 'M000000'
+ end
+
+ reset
+
+ def initialize(text, name)
+ super()
+
+ @text = text
+ @name = name
+
+ @aliases = []
+ @block_params = nil
+ @call_seq = nil
+ @dont_rename_initialize = false
+ @is_alias_for = nil
+ @parent_name = nil
+ @singleton = nil
+ @token_stream = nil
+ @visibility = :public
+
+ @aref = @@aref
+ @@aref = @@aref.succ
+ end
+
+ ##
+ # Order by #singleton then #name
+
+ def <=>(other)
+ [@singleton ? 0 : 1, @name] <=> [other.singleton ? 0 : 1, other.name]
+ end
+
+ ##
+ # Adds +method+ as an alias for this method
+
+ def add_alias(method)
+ @aliases << method
+ 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
+
+ def marshal_dump
+ aliases = @aliases.map do |a|
+ [a.full_name, parse(a.comment)]
+ end
+
+ [ MARSHAL_VERSION,
+ @name,
+ full_name,
+ @singleton,
+ @visibility,
+ parse(@comment),
+ @call_seq,
+ @block_params,
+ aliases,
+ ]
+ end
+
+ ##
+ # Loads this AnyMethod from +array+. For a loaded AnyMethod the following
+ # methods will return cached values:
+ #
+ # * #full_name
+ # * #parent_name
+
+ def marshal_load(array)
+ @aliases = []
+ @dont_rename_initialize = nil
+ @is_alias_for = nil
+ @token_stream = nil
+
+ @name = array[1]
+ @full_name = array[2]
+ @singleton = array[3]
+ @visibility = array[4]
+ @comment = array[5]
+ @call_seq = array[6]
+ @block_params = array[7]
+
+ @parent_name = if @full_name =~ /#/ then
+ $`
+ else
+ name = @full_name.split('::')
+ name.pop
+ name.join '::'
+ end
+
+ array[8].each do |old_name, new_name, comment|
+ add_alias RDoc::Alias.new(nil, old_name, new_name, comment)
+ end
+ end
+
+ ##
+ # Method name
+
+ def name
+ return @name if @name
+
+ @name = @call_seq[/^.*?\.(\w+)/, 1] || @call_seq if @call_seq
+ end
+
+ ##
+ # Pretty parameter list for this method
+
+ def param_seq
+ params = params.gsub(/\s*\#.*/, '')
+ params = params.tr("\n", " ").squeeze(" ")
+ params = "(#{params})" unless p[0] == ?(
+
+ if block = block_params then # yes, =
+ # If this method has explicit block parameters, remove any explicit
+ # &block
+ params.sub!(/,?\s*&\w+/)
+
+ block.gsub!(/\s*\#.*/, '')
+ block = block.tr("\n", " ").squeeze(" ")
+ if block[0] == ?(
+ block.sub!(/^\(/, '').sub!(/\)/, '')
+ end
+ params << " { |#{block}| ... }"
+ end
+
+ 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
new file mode 100644
index 0000000000..43ded8e610
--- /dev/null
+++ b/lib/rdoc/attr.rb
@@ -0,0 +1,165 @@
+require 'rdoc/code_object'
+
+##
+# An attribute created by \#attr, \#attr_reader, \#attr_writer or
+# \#attr_accessor
+
+class RDoc::Attr < RDoc::CodeObject
+
+ MARSHAL_VERSION = 0 # :nodoc:
+
+ ##
+ # Name of the attribute
+
+ attr_accessor :name
+
+ ##
+ # Is the attribute readable, writable or both?
+
+ attr_accessor :rw
+
+ ##
+ # Source file token stream
+
+ attr_accessor :text
+
+ ##
+ # public, protected, private
+
+ attr_accessor :visibility
+
+ def initialize(text, name, rw, comment)
+ super()
+ @text = text
+ @name = name
+ @rw = rw
+ @visibility = :public
+ 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
+
+ def == other
+ self.class == other.class and
+ self.name == other.name and
+ self.rw == other.rw
+ end
+
+ ##
+ # Returns nil, for duck typing with RDoc::AnyMethod
+
+ def block_params
+ end
+
+ ##
+ # Returns nil, for duck typing with RDoc::AnyMethod
+
+ 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,
+ ]
+ end
+
+ ##
+ # Dumps this Attr for use by ri. See also #marshal_load
+
+ def marshal_dump
+ [ MARSHAL_VERSION,
+ @name,
+ full_name,
+ @rw,
+ @visibility,
+ parse(@comment),
+ ]
+ end
+
+ ##
+ # Loads this AnyMethod from +array+. For a loaded AnyMethod the following
+ # methods will return cached values:
+ #
+ # * #full_name
+ # * #parent_name
+
+ def marshal_load array
+ @name = array[1]
+ @full_name = array[2]
+ @rw = array[3]
+ @visibility = array[4]
+ @comment = array[5]
+
+ @parent_name = @full_name
+ end
+
+ ##
+ # Name of our parent with special handling for un-marshaled methods
+
+ def parent_name
+ @parent_name || super
+ 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
+ end
+
+end
+
diff --git a/lib/rdoc/class_module.rb b/lib/rdoc/class_module.rb
new file mode 100644
index 0000000000..c3029bfe9b
--- /dev/null
+++ b/lib/rdoc/class_module.rb
@@ -0,0 +1,213 @@
+require 'rdoc/context'
+
+##
+# ClassModule is the base class for objects representing either a class or a
+# module.
+
+class RDoc::ClassModule < RDoc::Context
+
+ MARSHAL_VERSION = 0 # :nodoc:
+
+ attr_accessor :diagram
+
+ ##
+ # Creates a new ClassModule with +name+ with optional +superclass+
+
+ def initialize(name, superclass = 'Object')
+ @diagram = nil
+ @full_name = nil
+ @name = name
+ @superclass = superclass
+ super()
+ end
+
+ ##
+ # Ancestors list for this ClassModule (abstract)
+
+ def ancestors
+ raise NotImplementedError
+ end
+
+ ##
+ # Appends +comment+ to the current comment, but separated by a rule. Works
+ # more like <tt>+=</tt>.
+
+ def comment=(comment)
+ return if comment.empty?
+
+ comment = "#{@comment}\n---\n#{normalize_comment comment}" unless
+ @comment.empty?
+
+ super
+ end
+
+ ##
+ # Finds a class or module with +name+ in this namespace or its descendents
+
+ def find_class_named(name)
+ return self if full_name == name
+ return self if @name == name
+
+ @classes.values.find do |klass|
+ next if klass == self
+ klass.find_class_named name
+ end
+ end
+
+ ##
+ # Return the fully qualified name of this class or module
+
+ def full_name
+ @full_name ||= if RDoc::ClassModule === @parent then
+ "#{@parent.full_name}::#{@name}"
+ else
+ @name
+ end
+ end
+
+ ##
+ # 'module' or 'class'
+
+ def type
+ module? ? 'module' : 'class'
+ end
+
+ def marshal_dump # :nodoc:
+ attrs = attributes.sort.map do |attr|
+ [attr.name, attr.rw]
+ end
+
+ method_types = methods_by_type.map do |type, visibilities|
+ visibilities = visibilities.map do |visibility, methods|
+ method_names = methods.map do |method|
+ method.name
+ end
+
+ [visibility, method_names.uniq]
+ end
+
+ [type, visibilities]
+ end
+
+ [ MARSHAL_VERSION,
+ @name,
+ full_name,
+ @superclass,
+ parse(@comment),
+ attrs,
+ constants.map do |const|
+ [const.name, parse(const.comment)]
+ end,
+ includes.map do |incl|
+ [incl.name, parse(incl.comment)]
+ end,
+ method_types,
+ ]
+ end
+
+ def marshal_load array # :nodoc:
+ initialize_methods_etc
+ @document_self = true
+ @done_documenting = false
+ @current_section = nil
+
+ @name = array[1]
+ @full_name = array[2]
+ @superclass = array[3]
+ @comment = array[4]
+
+ array[5].each do |name, rw|
+ add_attribute RDoc::Attr.new(nil, name, rw, nil)
+ end
+
+ array[6].each do |name, comment|
+ add_constant RDoc::Constant.new(name, nil, comment)
+ end
+
+ array[7].each do |name, comment|
+ add_include RDoc::Include.new(name, comment)
+ end
+
+ array[8].each do |type, visibilities|
+ visibilities.each do |visibility, methods|
+ @visibility = visibility
+
+ methods.each do |name|
+ method = RDoc::AnyMethod.new nil, name
+ method.singleton = true if type == 'class'
+ add_method method
+ end
+ end
+ end
+ end
+
+ ##
+ # Merges +class_module+ into this ClassModule
+
+ def merge class_module
+ if class_module.comment then
+ document = parse @comment
+
+ class_module.comment.parts.push(*document.parts)
+
+ @comment = class_module.comment
+ end
+
+ class_module.each_attribute do |attr|
+ if match = attributes.find { |a| a.name == attr.name } then
+ match.rw = [match.rw, attr.rw].compact.join
+ else
+ add_attribute attr
+ end
+ end
+
+ class_module.each_constant do |const|
+ add_constant const
+ end
+
+ class_module.each_include do |incl|
+ add_include incl
+ end
+
+ class_module.each_method do |meth|
+ add_method meth
+ end
+ end
+
+ ##
+ # Does this object represent a module?
+
+ def module?
+ false
+ end
+
+ ##
+ # Path to this class or module
+
+ def path
+ http_url RDoc::RDoc.current.generator.class_dir
+ 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
+ end
+
+ ##
+ # Set the superclass of this class to +superclass+
+
+ def superclass=(superclass)
+ raise NoMethodError, "#{full_name} is a module" if module?
+
+ @superclass = superclass if @superclass.nil? or @superclass == 'Object'
+ end
+
+ def to_s # :nodoc:
+ "#{self.class}: #{full_name} #{@comment} #{super}"
+ end
+
+end
+
diff --git a/lib/rdoc/code_object.rb b/lib/rdoc/code_object.rb
new file mode 100644
index 0000000000..2fcc92e8b5
--- /dev/null
+++ b/lib/rdoc/code_object.rb
@@ -0,0 +1,175 @@
+require 'rdoc'
+require 'rdoc/text'
+
+##
+# Base class for the RDoc code tree.
+#
+# We contain the common stuff for contexts (which are containers) and other
+# elements (methods, attributes and so on)
+
+class RDoc::CodeObject
+
+ include RDoc::Text
+
+ ##
+ # Our comment
+
+ attr_reader :comment
+
+ ##
+ # Do we document our children?
+
+ attr_reader :document_children
+
+ ##
+ # Do we document ourselves?
+
+ attr_reader :document_self
+
+ ##
+ # Are we done documenting (ie, did we come across a :enddoc:)?
+
+ attr_accessor :done_documenting
+
+ ##
+ # Force documentation of this CodeObject
+
+ attr_accessor :force_documentation
+
+ ##
+ # Our parent CodeObject
+
+ attr_accessor :parent
+
+ ##
+ # Which section are we in
+
+ attr_accessor :section
+
+ ##
+ # We are the model of the code, but we know that at some point we will be
+ # worked on by viewers. By implementing the Viewable protocol, viewers can
+ # associated themselves with these objects.
+
+ attr_accessor :viewer
+
+ ##
+ # There's a wee trick we pull. Comment blocks can have directives that
+ # override the stuff we extract during the parse. So, we have a special
+ # class method, attr_overridable, that lets code objects list those
+ # directives. When a comment is assigned, we then extract out any matching
+ # directives and update our object
+
+ def self.attr_overridable(name, *aliases)
+ @overridables ||= {}
+
+ attr_accessor name
+
+ aliases.unshift name
+
+ aliases.each do |directive_name|
+ @overridables[directive_name.to_s] = name
+ end
+ end
+
+ ##
+ # Creates a new CodeObject that will document itself and its children
+
+ def initialize
+ @comment = ''
+
+ @document_children = true
+ @document_self = true
+ @done_documenting = false
+ @force_documentation = false
+
+ @parent = nil
+ end
+
+ ##
+ # Replaces our comment with +comment+, unless it is empty.
+
+ def comment=(comment)
+ @comment = case comment
+ when NilClass then ''
+ when RDoc::Markup::Document then comment
+ else
+ if comment and not comment.empty? then
+ normalize_comment comment
+ else
+ @comment
+ end
+ end
+ end
+
+ ##
+ # Enables or disables documentation of this CodeObject's children. Calls
+ # remove_classes_and_modules when disabling.
+
+ def document_children=(document_children)
+ @document_children = document_children
+ remove_classes_and_modules unless document_children
+ end
+
+ ##
+ # Enables or disables documentation of this CodeObject. Calls
+ # remove_methods_etc when disabling.
+
+ def document_self=(document_self)
+ @document_self = document_self
+ remove_methods_etc unless document_self
+ end
+
+ ##
+ # Does this class have a comment with content or is document_self false.
+
+ def documented?
+ !(@document_self and @comment.empty?)
+ end
+
+ ##
+ # File name of our parent
+
+ def parent_file_name
+ @parent ? @parent.base_name : '(unknown)'
+ end
+
+ ##
+ # Name of our parent
+
+ def parent_name
+ @parent ? @parent.full_name : '(unknown)'
+ end
+
+ ##
+ # Callback called upon disabling documentation of children. See
+ # #document_children=
+
+ def remove_classes_and_modules
+ end
+
+ ##
+ # Callback called upon disabling documentation of ourself. See
+ # #document_self=
+
+ def remove_methods_etc
+ end
+
+ ##
+ # Enable capture of documentation
+
+ def start_doc
+ @document_self = true
+ @document_children = true
+ end
+
+ ##
+ # Disable capture of documentation
+
+ def stop_doc
+ @document_self = false
+ @document_children = false
+ end
+
+end
+
diff --git a/lib/rdoc/code_objects.rb b/lib/rdoc/code_objects.rb
index 3fdd86314d..c60dad92df 100644
--- a/lib/rdoc/code_objects.rb
+++ b/lib/rdoc/code_objects.rb
@@ -1,1061 +1,23 @@
-# We represent the various high-level code constructs that appear
-# in Ruby programs: classes, modules, methods, and so on.
+# We represent the various high-level code constructs that appear in Ruby
+# programs: classes, modules, methods, and so on.
-require 'rdoc/tokenstream'
+require 'rdoc/code_object'
+require 'rdoc/context'
+require 'rdoc/top_level'
-module RDoc
+require 'rdoc/class_module'
+require 'rdoc/normal_class'
+require 'rdoc/normal_module'
+require 'rdoc/anon_class'
+require 'rdoc/single_class'
- ##
- # We contain the common stuff for contexts (which are containers) and other
- # elements (methods, attributes and so on)
+require 'rdoc/any_method'
+require 'rdoc/alias'
+require 'rdoc/ghost_method'
+require 'rdoc/meta_method'
- class CodeObject
+require 'rdoc/attr'
+require 'rdoc/constant'
+require 'rdoc/require'
+require 'rdoc/include'
- attr_accessor :parent
-
- # We are the model of the code, but we know that at some point
- # we will be worked on by viewers. By implementing the Viewable
- # protocol, viewers can associated themselves with these objects.
-
- attr_accessor :viewer
-
- # are we done documenting (ie, did we come across a :enddoc:)?
-
- attr_accessor :done_documenting
-
- # Which section are we in
-
- attr_accessor :section
-
- # do we document ourselves?
-
- attr_reader :document_self
-
- def initialize
- @document_self = true
- @document_children = true
- @force_documentation = false
- @done_documenting = false
- end
-
- def document_self=(val)
- @document_self = val
- if !val
- remove_methods_etc
- end
- end
-
- # set and cleared by :startdoc: and :enddoc:, this is used to toggle
- # the capturing of documentation
- def start_doc
- @document_self = true
- @document_children = true
- end
-
- def stop_doc
- @document_self = false
- @document_children = false
- end
-
- # do we document ourselves and our children
-
- attr_reader :document_children
-
- def document_children=(val)
- @document_children = val
- if !val
- remove_classes_and_modules
- end
- end
-
- # Do we _force_ documentation, even is we wouldn't normally show the entity
- attr_accessor :force_documentation
-
- def parent_file_name
- @parent ? @parent.file_base_name : '(unknown)'
- end
-
- def parent_name
- @parent ? @parent.name : '(unknown)'
- end
-
- # Default callbacks to nothing, but this is overridden for classes
- # and modules
- def remove_classes_and_modules
- end
-
- def remove_methods_etc
- end
-
- # Access the code object's comment
- attr_reader :comment
-
- # Update the comment, but don't overwrite a real comment with an empty one
- def comment=(comment)
- @comment = comment unless comment.empty?
- end
-
- # There's a wee trick we pull. Comment blocks can have directives that
- # override the stuff we extract during the parse. So, we have a special
- # class method, attr_overridable, that lets code objects list
- # those directives. Wehn a comment is assigned, we then extract
- # out any matching directives and update our object
-
- def self.attr_overridable(name, *aliases)
- @overridables ||= {}
-
- attr_accessor name
-
- aliases.unshift name
- aliases.each do |directive_name|
- @overridables[directive_name.to_s] = name
- end
- end
-
- end
-
- ##
- # A Context is something that can hold modules, classes, methods,
- # attributes, aliases, requires, and includes. Classes, modules, and files
- # are all Contexts.
-
- class Context < CodeObject
-
- attr_reader :aliases
- attr_reader :attributes
- attr_reader :constants
- attr_reader :current_section
- attr_reader :in_files
- attr_reader :includes
- attr_reader :method_list
- attr_reader :name
- attr_reader :requires
- attr_reader :sections
- attr_reader :visibility
-
- class Section
- attr_reader :title, :comment, :sequence
-
- @@sequence = "SEC00000"
-
- def initialize(title, comment)
- @title = title
- @@sequence.succ!
- @sequence = @@sequence.dup
- @comment = nil
- set_comment(comment)
- end
-
- def ==(other)
- self.class === other and @sequence == other.sequence
- end
-
- def inspect
- "#<%s:0x%x %s %p>" % [
- self.class, object_id,
- @sequence, title
- ]
- end
-
- ##
- # 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
- #
- # # ---------------------
- # # :SECTION: The title
- # # The body
- # # ---------------------
-
- def set_comment(comment)
- return unless comment
-
- if comment =~ /^#[ \t]*:section:.*\n/
- start = $`
- rest = $'
-
- if start.empty?
- @comment = rest
- else
- @comment = rest.sub(/#{start.chomp}\Z/, '')
- end
- else
- @comment = comment
- end
- @comment = nil if @comment.empty?
- end
-
- end
-
- def initialize
- super
-
- @in_files = []
-
- @name ||= "unknown"
- @comment ||= ""
- @parent = nil
- @visibility = :public
-
- @current_section = Section.new(nil, nil)
- @sections = [ @current_section ]
-
- initialize_methods_etc
- initialize_classes_and_modules
- end
-
- ##
- # map the class hash to an array externally
-
- def classes
- @classes.values
- end
-
- ##
- # map the module hash to an array externally
-
- def modules
- @modules.values
- end
-
- ##
- # return the classes Hash (only to be used internally)
-
- def classes_hash
- @classes
- end
- protected :classes_hash
-
- ##
- # return the modules Hash (only to be used internally)
-
- def modules_hash
- @modules
- end
- protected :modules_hash
-
- ##
- # Change the default visibility for new methods
-
- def ongoing_visibility=(vis)
- @visibility = vis
- end
-
- ##
- # Yields Method and Attr entries matching the list of names in +methods+.
- # Attributes are only returned when +singleton+ is false.
-
- def methods_matching(methods, singleton = false)
- count = 0
-
- @method_list.each do |m|
- if methods.include? m.name and m.singleton == singleton then
- yield m
- count += 1
- end
- end
-
- return if count == methods.size || singleton
-
- # perhaps we need to look at attributes
-
- @attributes.each do |a|
- yield a if methods.include? a.name
- end
- end
-
- ##
- # Given an array +methods+ of method names, set the visibility of the
- # corresponding AnyMethod object
-
- def set_visibility_for(methods, vis, singleton = false)
- methods_matching methods, singleton do |m|
- m.visibility = vis
- end
- end
-
- ##
- # Record the file that we happen to find it in
-
- def record_location(toplevel)
- @in_files << toplevel unless @in_files.include?(toplevel)
- end
-
- # Return true if at least part of this thing was defined in +file+
- def defined_in?(file)
- @in_files.include?(file)
- end
-
- def add_class(class_type, name, superclass)
- klass = add_class_or_module @classes, class_type, name, superclass
-
- #
- # 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.
- #
- mod = @modules.delete(name)
-
- if mod then
- klass.classes_hash.update(mod.classes_hash)
- klass.modules_hash.update(mod.modules_hash)
- klass.method_list.concat(mod.method_list)
- end
-
- return klass
- end
-
- def add_module(class_type, name)
- add_class_or_module(@modules, class_type, name, nil)
- end
-
- def add_method(a_method)
- a_method.visibility = @visibility
- add_to(@method_list, a_method)
-
- unmatched_alias_list = @unmatched_alias_lists[a_method.name]
- if unmatched_alias_list then
- unmatched_alias_list.each do |unmatched_alias|
- add_alias_impl unmatched_alias, a_method
- @aliases.delete unmatched_alias
- end
-
- @unmatched_alias_lists.delete a_method.name
- end
- end
-
- def add_attribute(an_attribute)
- add_to(@attributes, an_attribute)
- end
-
- def add_alias_impl(an_alias, meth)
- new_meth = 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
- new_meth.comment = "Alias for \##{meth.name}"
- meth.add_alias(new_meth)
- add_method(new_meth)
- end
-
- def add_alias(an_alias)
- meth = find_instance_method_named(an_alias.old_name)
-
- if meth then
- add_alias_impl(an_alias, meth)
- else
- add_to(@aliases, an_alias)
- unmatched_alias_list = @unmatched_alias_lists[an_alias.old_name] ||= []
- unmatched_alias_list.push(an_alias)
- end
-
- an_alias
- end
-
- def add_include(an_include)
- add_to(@includes, an_include)
- end
-
- def add_constant(const)
- add_to(@constants, const)
- end
-
- # Requires always get added to the top-level (file) context
- def add_require(a_require)
- if TopLevel === self then
- add_to @requires, a_require
- else
- parent.add_require a_require
- end
- end
-
- def add_class_or_module(collection, class_type, name, superclass=nil)
- cls = collection[name]
-
- if cls then
- cls.superclass = superclass unless cls.module?
- puts "Reusing class/module #{name}" if $DEBUG_RDOC
- else
- cls = class_type.new(name, superclass)
-# collection[name] = cls if @document_self && !@done_documenting
- collection[name] = cls if !@done_documenting
- cls.parent = self
- cls.section = @current_section
- end
- cls
- end
-
- def add_to(array, thing)
- array << thing if @document_self and not @done_documenting
- thing.parent = self
- thing.section = @current_section
- end
-
- # If a class's documentation is turned off after we've started
- # collecting methods etc., we need to remove the ones
- # we have
-
- def remove_methods_etc
- initialize_methods_etc
- end
-
- def initialize_methods_etc
- @method_list = []
- @attributes = []
- @aliases = []
- @requires = []
- @includes = []
- @constants = []
-
- # This Hash maps a method name to a list of unmatched
- # aliases (aliases of a method not yet encountered).
- @unmatched_alias_lists = {}
- end
-
- # and remove classes and modules when we see a :nodoc: all
- def remove_classes_and_modules
- initialize_classes_and_modules
- end
-
- def initialize_classes_and_modules
- @classes = {}
- @modules = {}
- end
-
- # Find a named module
- def find_module_named(name)
- # First check the enclosed modules, then check the module itself,
- # then check the enclosing modules (this mirrors the check done by
- # the Ruby parser)
- res = @modules[name] || @classes[name]
- return res if res
- return self if self.name == name
- find_enclosing_module_named(name)
- end
-
- # find a module at a higher scope
- def find_enclosing_module_named(name)
- parent && parent.find_module_named(name)
- end
-
- # Iterate over all the classes and modules in
- # this object
-
- def each_classmodule
- @modules.each_value {|m| yield m}
- @classes.each_value {|c| yield c}
- end
-
- def each_method
- @method_list.each {|m| yield m}
- end
-
- def each_attribute
- @attributes.each {|a| yield a}
- end
-
- def each_constant
- @constants.each {|c| yield c}
- end
-
- # Return the toplevel that owns us
-
- def toplevel
- return @toplevel if defined? @toplevel
- @toplevel = self
- @toplevel = @toplevel.parent until TopLevel === @toplevel
- @toplevel
- end
-
- # allow us to sort modules by name
- def <=>(other)
- name <=> other.name
- end
-
- ##
- # Look up +symbol+. If +method+ is non-nil, then we assume the symbol
- # references a module that contains that method.
-
- def find_symbol(symbol, method = nil)
- result = nil
-
- case symbol
- when /^::(.*)/ then
- result = toplevel.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
- end
-
- else
- # if a method is specified, then we're definitely looking for
- # a module, otherwise it could be any symbol
- if method
- result = find_module_named(symbol)
- else
- result = find_local_symbol(symbol)
- if result.nil?
- if symbol =~ /^[A-Z]/
- result = parent
- while result && result.name != symbol
- result = result.parent
- end
- end
- end
- end
- end
-
- if result and method then
- fail unless result.respond_to? :find_local_symbol
- result = result.find_local_symbol(method)
- end
-
- result
- end
-
- def find_local_symbol(symbol)
- res = find_method_named(symbol) ||
- find_constant_named(symbol) ||
- find_attribute_named(symbol) ||
- find_module_named(symbol) ||
- find_file_named(symbol)
- end
-
- # Handle sections
-
- def set_current_section(title, comment)
- @current_section = Section.new(title, comment)
- @sections << @current_section
- end
-
- private
-
- # Find a named method, or return nil
- def find_method_named(name)
- @method_list.find {|meth| meth.name == name}
- end
-
- # Find a named instance method, or return nil
- def find_instance_method_named(name)
- @method_list.find {|meth| meth.name == name && !meth.singleton}
- end
-
- # Find a named constant, or return nil
- def find_constant_named(name)
- @constants.find {|m| m.name == name}
- end
-
- # Find a named attribute, or return nil
- def find_attribute_named(name)
- @attributes.find {|m| m.name == name}
- end
-
- ##
- # Find a named file, or return nil
-
- def find_file_named(name)
- toplevel.class.find_file_named(name)
- end
-
- end
-
- ##
- # A TopLevel context is a source file
-
- class TopLevel < Context
- attr_accessor :file_stat
- attr_accessor :file_relative_name
- attr_accessor :file_absolute_name
- attr_accessor :diagram
-
- @@all_classes = {}
- @@all_modules = {}
- @@all_files = {}
-
- def self.reset
- @@all_classes = {}
- @@all_modules = {}
- @@all_files = {}
- end
-
- def initialize(file_name)
- super()
- @name = "TopLevel"
- @file_relative_name = file_name
- @file_absolute_name = file_name
- @file_stat = File.stat(file_name)
- @diagram = nil
- @@all_files[file_name] = self
- end
-
- def file_base_name
- File.basename @file_absolute_name
- end
-
- def full_name
- nil
- end
-
- ##
- # Adding a class or module to a TopLevel is special, as we only want one
- # copy of a particular top-level class. For example, if both file A and
- # file B implement class C, we only want one ClassModule object for C.
- # This code arranges to share classes and modules between files.
-
- def add_class_or_module(collection, class_type, name, superclass)
- cls = collection[name]
-
- if cls then
- cls.superclass = superclass unless cls.module?
- puts "Reusing class/module #{cls.full_name}" if $DEBUG_RDOC
- else
- if class_type == NormalModule then
- all = @@all_modules
- else
- all = @@all_classes
- end
-
- cls = all[name]
-
- if !cls then
- cls = class_type.new name, superclass
- all[name] = cls unless @done_documenting
- 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 == NormalClass
- if !cls.superclass then
- cls.superclass = superclass
- end
- end
- end
-
- collection[name] = cls unless @done_documenting
-
- cls.parent = self
- end
-
- cls
- end
-
- def self.all_classes_and_modules
- @@all_classes.values + @@all_modules.values
- end
-
- def self.find_class_named(name)
- @@all_classes.each_value do |c|
- res = c.find_class_named(name)
- return res if res
- end
- nil
- end
-
- def self.find_file_named(name)
- @@all_files[name]
- end
-
- def find_local_symbol(symbol)
- find_class_or_module_named(symbol) || super
- end
-
- def find_class_or_module_named(symbol)
- @@all_classes.each_value {|c| return c if c.name == symbol}
- @@all_modules.each_value {|m| return m if m.name == symbol}
- nil
- end
-
- ##
- # Find a named module
-
- def find_module_named(name)
- find_class_or_module_named(name) || find_enclosing_module_named(name)
- end
-
- def inspect
- "#<%s:0x%x %p modules: %p classes: %p>" % [
- self.class, object_id,
- file_base_name,
- @modules.map { |n,m| m },
- @classes.map { |n,c| c }
- ]
- end
-
- end
-
- ##
- # ClassModule is the base class for objects representing either a class or a
- # module.
-
- class ClassModule < Context
-
- attr_accessor :diagram
-
- def initialize(name, superclass = nil)
- @name = name
- @diagram = nil
- @superclass = superclass
- @comment = ""
- super()
- end
-
- def find_class_named(name)
- return self if full_name == name
- @classes.each_value {|c| return c if c.find_class_named(name) }
- nil
- end
-
- ##
- # Return the fully qualified name of this class or module
-
- def full_name
- if @parent && @parent.full_name
- @parent.full_name + "::" + @name
- else
- @name
- end
- end
-
- def http_url(prefix)
- path = full_name.split("::")
- File.join(prefix, *path) + ".html"
- end
-
- ##
- # Does this object represent a module?
-
- def module?
- false
- end
-
- ##
- # Get the superclass of this class. Attempts to retrieve the superclass'
- # real name by following module nesting.
-
- def superclass
- raise NoMethodError, "#{full_name} is a module" if module?
-
- scope = self
-
- begin
- superclass = scope.classes.find { |c| c.name == @superclass }
-
- return superclass.full_name if superclass
- scope = scope.parent
- end until scope.nil? or TopLevel === scope
-
- @superclass
- end
-
- ##
- # Set the superclass of this class
-
- def superclass=(superclass)
- raise NoMethodError, "#{full_name} is a module" if module?
-
- if @superclass.nil? or @superclass == 'Object' then
- @superclass = superclass
- end
- end
-
- def to_s
- "#{self.class}: #{@name} #{@comment} #{super}"
- end
-
- end
-
- ##
- # Anonymous classes
-
- class AnonClass < ClassModule
- end
-
- ##
- # Normal classes
-
- class NormalClass < ClassModule
-
- def inspect
- superclass = @superclass ? " < #{@superclass}" : nil
- "<%s:0x%x class %s%s includes: %p attributes: %p methods: %p aliases: %p>" % [
- self.class, object_id,
- @name, superclass, @includes, @attributes, @method_list, @aliases
- ]
- end
-
- end
-
- ##
- # Singleton classes
-
- class SingleClass < ClassModule
- end
-
- ##
- # Module
-
- class NormalModule < ClassModule
-
- def comment=(comment)
- return if comment.empty?
- comment = @comment << "# ---\n" << comment unless @comment.empty?
-
- super
- end
-
- def inspect
- "#<%s:0x%x module %s includes: %p attributes: %p methods: %p aliases: %p>" % [
- self.class, object_id,
- @name, @includes, @attributes, @method_list, @aliases
- ]
- end
-
- def module?
- true
- end
-
- end
-
- ##
- # AnyMethod is the base class for objects representing methods
-
- class AnyMethod < CodeObject
-
- attr_accessor :name
- attr_accessor :visibility
- attr_accessor :block_params
- attr_accessor :dont_rename_initialize
- attr_accessor :singleton
- attr_reader :text
-
- # list of other names for this method
- attr_reader :aliases
-
- # method we're aliasing
- attr_accessor :is_alias_for
-
- attr_overridable :params, :param, :parameters, :parameter
-
- attr_accessor :call_seq
-
- include TokenStream
-
- def initialize(text, name)
- super()
- @text = text
- @name = name
- @token_stream = nil
- @visibility = :public
- @dont_rename_initialize = false
- @block_params = nil
- @aliases = []
- @is_alias_for = nil
- @comment = ""
- @call_seq = nil
- end
-
- def <=>(other)
- @name <=> other.name
- end
-
- def add_alias(method)
- @aliases << method
- end
-
- def inspect
- alias_for = @is_alias_for ? " (alias for #{@is_alias_for.name})" : nil
- "#<%s:0x%x %s%s%s (%s)%s>" % [
- self.class, object_id,
- parent_name,
- singleton ? '::' : '#',
- name,
- visibility,
- alias_for,
- ]
- end
-
- def param_seq
- params = params.gsub(/\s*\#.*/, '')
- params = params.tr("\n", " ").squeeze(" ")
- params = "(#{params})" unless p[0] == ?(
-
- if block = block_params then # yes, =
- # If this method has explicit block parameters, remove any explicit
- # &block
- params.sub!(/,?\s*&\w+/)
-
- block.gsub!(/\s*\#.*/, '')
- block = block.tr("\n", " ").squeeze(" ")
- if block[0] == ?(
- block.sub!(/^\(/, '').sub!(/\)/, '')
- end
- params << " { |#{block}| ... }"
- end
-
- params
- end
-
- def to_s
- res = self.class.name + ": " + @name + " (" + @text + ")\n"
- res << @comment.to_s
- res
- end
-
- end
-
- ##
- # GhostMethod represents a method referenced only by a comment
-
- class GhostMethod < AnyMethod
- end
-
- ##
- # MetaMethod represents a meta-programmed method
-
- class MetaMethod < AnyMethod
- end
-
- ##
- # Represent an alias, which is an old_name/ new_name pair associated with a
- # particular context
-
- class Alias < CodeObject
-
- attr_accessor :text, :old_name, :new_name, :comment
-
- def initialize(text, old_name, new_name, comment)
- super()
- @text = text
- @old_name = old_name
- @new_name = new_name
- self.comment = comment
- end
-
- def inspect
- "#<%s:0x%x %s.alias_method %s, %s>" % [
- self.class, object_id,
- parent.name, @old_name, @new_name,
- ]
- end
-
- def to_s
- "alias: #{self.old_name} -> #{self.new_name}\n#{self.comment}"
- end
-
- end
-
- ##
- # Represent a constant
-
- class Constant < CodeObject
- attr_accessor :name, :value
-
- def initialize(name, value, comment)
- super()
- @name = name
- @value = value
- self.comment = comment
- end
- end
-
- ##
- # Represent attributes
-
- class Attr < CodeObject
- attr_accessor :text, :name, :rw, :visibility
-
- def initialize(text, name, rw, comment)
- super()
- @text = text
- @name = name
- @rw = rw
- @visibility = :public
- self.comment = comment
- end
-
- def <=>(other)
- self.name <=> other.name
- end
-
- def inspect
- 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,
- ]
- end
-
- def to_s
- "attr: #{self.name} #{self.rw}\n#{self.comment}"
- end
-
- end
-
- ##
- # A required file
-
- class Require < CodeObject
- attr_accessor :name
-
- def initialize(name, comment)
- super()
- @name = name.gsub(/'|"/, "") #'
- self.comment = comment
- end
-
- def inspect
- "#<%s:0x%x require '%s' in %s>" % [
- self.class,
- object_id,
- @name,
- parent_file_name,
- ]
- end
-
- end
-
- ##
- # An included module
-
- class Include < CodeObject
-
- attr_accessor :name
-
- def initialize(name, comment)
- super()
- @name = name
- self.comment = comment
-
- end
-
- def inspect
- "#<%s:0x%x %s.include %s>" % [
- self.class,
- object_id,
- parent_name, @name,
- ]
- end
-
- end
-
-end
diff --git a/lib/rdoc/constant.rb b/lib/rdoc/constant.rb
new file mode 100644
index 0000000000..908990855d
--- /dev/null
+++ b/lib/rdoc/constant.rb
@@ -0,0 +1,58 @@
+require 'rdoc/code_object'
+
+##
+# A constant
+
+class RDoc::Constant < RDoc::CodeObject
+
+ ##
+ # The constant's name
+
+ attr_accessor :name
+
+ ##
+ # The constant's value
+
+ attr_accessor :value
+
+ ##
+ # Creates a new constant with +name+, +value+ and +comment+
+
+ def initialize(name, value, comment)
+ super()
+ @name = name
+ @value = value
+ self.comment = comment
+ end
+
+ ##
+ # Constants are ordered by name
+
+ def <=> other
+ return unless self.class === other
+
+ [parent_name, name] <=> [other.parent_name, other.name]
+ end
+
+ def == other
+ self.class == other.class and
+ @parent == other.parent and
+ @name == other.name
+ end
+
+ def inspect # :nodoc:
+ "#<%s:0x%x %s::%s>" % [
+ self.class, object_id,
+ parent_name, @name,
+ ]
+ end
+
+ ##
+ # Path to this constant
+
+ def path
+ "#{@parent.path}##{@name}"
+ end
+
+end
+
diff --git a/lib/rdoc/context.rb b/lib/rdoc/context.rb
new file mode 100644
index 0000000000..4cf0c1914f
--- /dev/null
+++ b/lib/rdoc/context.rb
@@ -0,0 +1,755 @@
+require 'rdoc/code_object'
+
+##
+# A Context is something that can hold modules, classes, methods, attributes,
+# aliases, requires, and includes. Classes, modules, and files are all
+# Contexts.
+
+class RDoc::Context < RDoc::CodeObject
+
+ include Comparable
+
+ ##
+ # Types of methods
+
+ TYPES = %w[class instance]
+
+ ##
+ # Method visibilities
+
+ VISIBILITIES = [:public, :protected, :private]
+
+ ##
+ # Aliased methods
+
+ attr_reader :aliases
+
+ ##
+ # attr* methods
+
+ attr_reader :attributes
+
+ ##
+ # Constants defined
+
+ attr_reader :constants
+
+ ##
+ # Current section of documentation
+
+ attr_reader :current_section
+
+ ##
+ # Files this context is found in
+
+ attr_reader :in_files
+
+ ##
+ # Modules this context includes
+
+ attr_reader :includes
+
+ ##
+ # Methods defined in this context
+
+ attr_reader :method_list
+
+ ##
+ # Name of this class excluding namespace. See also full_name
+
+ attr_reader :name
+
+ ##
+ # Files this context requires
+
+ attr_reader :requires
+
+ ##
+ # Sections in this context
+
+ attr_reader :sections
+
+ ##
+ # Aliases that haven't been resolved to a method
+
+ attr_accessor :unmatched_alias_lists
+
+ ##
+ # Current visibility of this context
+
+ attr_reader :visibility
+
+ ##
+ # A per-comment section of documentation like:
+ #
+ # # :SECTION: The title
+ # # The body
+
+ class Section
+
+ ##
+ # Section comment
+
+ attr_reader :comment
+
+ ##
+ # Context this Section lives in
+
+ attr_reader :parent
+
+ ##
+ # Section sequence number (for linking)
+
+ attr_reader :sequence
+
+ ##
+ # Section title
+
+ attr_reader :title
+
+ @@sequence = "SEC00000"
+
+ ##
+ # Creates a new section with +title+ and +comment+
+
+ def initialize(parent, title, comment)
+ @parent = parent
+ @title = title
+
+ @@sequence.succ!
+ @sequence = @@sequence.dup
+
+ set_comment comment
+ end
+
+ ##
+ # Sections are equal when they have the same #sequence
+
+ def ==(other)
+ self.class === other and @sequence == other.sequence
+ end
+
+ def inspect # :nodoc:
+ "#<%s:0x%x %s %p>" % [
+ self.class, object_id,
+ @sequence, title
+ ]
+ end
+
+ ##
+ # 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
+ # # The body
+
+ def set_comment(comment)
+ return unless comment
+
+ if comment =~ /^#[ \t]*:section:.*\n/ then
+ start = $`
+ rest = $'
+
+ if start.empty?
+ @comment = rest
+ else
+ @comment = rest.sub(/#{start.chomp}\Z/, '')
+ end
+ else
+ @comment = comment
+ end
+
+ @comment = nil if @comment.empty?
+ end
+
+ end
+
+ ##
+ # Creates an unnamed empty context with public visibility
+
+ def initialize
+ super
+
+ @in_files = []
+
+ @name ||= "unknown"
+ @comment ||= ""
+ @parent = nil
+ @visibility = :public
+
+ @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 = {}
+ end
+
+ ##
+ # Sets the defaults for methods and so-forth
+
+ def initialize_methods_etc
+ @method_list = []
+ @attributes = []
+ @aliases = []
+ @requires = []
+ @includes = []
+ @constants = []
+
+ # This Hash maps a method name to a list of unmatched aliases (aliases of
+ # a method not yet encountered).
+ @unmatched_alias_lists = {}
+ end
+
+ ##
+ # Contexts are sorted by full_name
+
+ def <=>(other)
+ full_name <=> other.full_name
+ end
+
+ ##
+ # Adds +an_alias+ that is automatically resolved
+
+ def add_alias(an_alias)
+ meth = find_instance_method_named(an_alias.old_name)
+
+ if meth then
+ add_alias_impl an_alias, meth
+ else
+ add_to @aliases, an_alias
+ unmatched_alias_list = @unmatched_alias_lists[an_alias.old_name] ||= []
+ unmatched_alias_list.push an_alias
+ end
+
+ an_alias
+ end
+
+ ##
+ # Turns +an_alias+ into an AnyMethod that points to +meth+
+
+ 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
+
+ new_meth.comment = an_alias.comment
+
+ meth.add_alias new_meth
+
+ add_method new_meth
+
+ # aliases don't use ongoing visibility
+ new_meth.visibility = meth.visibility
+
+ new_meth
+ end
+
+ ##
+ # Adds +attribute+
+
+ def add_attribute(attribute)
+ add_to @attributes, attribute
+ end
+
+ ##
+ # Adds a class named +name+ with +superclass+.
+ #
+ # Given <tt>class Container::Item</tt> RDoc assumes +Container+ is a module
+ # unless it later sees <tt>class Container</tt>. add_class automatically
+ # upgrades +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
+
+ existing = klass.superclass
+ existing = existing.name if existing and not String === existing
+
+ if superclass != existing and superclass != 'Object' then
+ klass.superclass = superclass
+ 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.
+
+ mod = RDoc::TopLevel.modules_hash.delete klass.full_name
+
+ if mod then
+ klass.classes_hash.update mod.classes_hash
+ klass.modules_hash.update mod.modules_hash
+ klass.method_list.concat mod.method_list
+
+ @modules.delete klass.name
+ end
+
+ RDoc::TopLevel.classes_hash[klass.full_name] = klass
+
+ klass
+ end
+
+ ##
+ # Instantiates a +class_type+ named +name+ and adds it the modules or
+ # classes Hash +collection+.
+
+ def add_class_or_module(collection, class_type, name, superclass = nil)
+ full_name = child_name name
+
+ 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
+ end
+
+ mod
+ end
+
+ ##
+ # Adds +constant+
+
+ def add_constant(constant)
+ add_to @constants, constant
+ end
+
+ ##
+ # Adds included module +include+
+
+ def add_include(include)
+ add_to @includes, include
+ end
+
+ ##
+ # Adds +method+
+
+ def add_method(method)
+ method.visibility = @visibility
+ add_to @method_list, method
+
+ 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
+ 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
+
+ def add_module(class_type, name)
+ return @classes[name] if @classes.key? name
+
+ add_class_or_module @modules, class_type, name, nil
+ end
+
+ ##
+ # Adds an alias from +from+ to +name+
+
+ def add_module_alias from, name
+ to_name = child_name name
+
+ unless @done_documenting then
+ if from.module? then
+ RDoc::TopLevel.modules_hash
+ else
+ RDoc::TopLevel.classes_hash
+ end[to_name] = from
+
+ if from.module? then
+ @modules
+ else
+ @classes
+ end[name] = from
+ end
+
+ from
+ end
+
+ ##
+ # Adds +require+ to this context's top level
+
+ def add_require(require)
+ if RDoc::TopLevel === self then
+ add_to @requires, require
+ else
+ parent.add_require require
+ end
+ end
+
+ ##
+ # Adds +thing+ to the collection +array+
+
+ def add_to(array, thing)
+ array << thing if @document_self and not @done_documenting
+ thing.parent = self
+ thing.section = @current_section
+ end
+
+ ##
+ # Creates the full name for a child with +name+
+
+ def child_name name
+ if RDoc::TopLevel === self then
+ name
+ else
+ "#{self.full_name}::#{name}"
+ end
+ end
+
+ ##
+ # Array of classes in this context
+
+ def classes
+ @classes.values
+ end
+
+ ##
+ # All classes and modules in this namespace
+
+ def classes_and_modules
+ classes + modules
+ end
+
+ ##
+ # Hash of classes keyed by class name
+
+ def classes_hash
+ @classes
+ end
+
+ ##
+ # Is part of this thing was defined in +file+?
+
+ def defined_in?(file)
+ @in_files.include?(file)
+ end
+
+ ##
+ # Iterator for attributes
+
+ def each_attribute # :yields: attribute
+ @attributes.each { |a| yield a }
+ end
+
+ ##
+ # Iterator for classes and modules
+
+ def each_classmodule(&block) # :yields: module
+ classes_and_modules.sort.each(&block)
+ end
+
+ ##
+ # Iterator for constants
+
+ def each_constant # :yields: constant
+ @constants.each {|c| yield c}
+ end
+
+ ##
+ # Iterator for included modules
+
+ def each_include # :yields: include
+ @includes.each do |i| yield i end
+ end
+
+ ##
+ # Iterator for methods
+
+ def each_method # :yields: method
+ @method_list.sort.each {|m| yield m}
+ end
+
+ ##
+ # Finds an attribute with +name+ in this context
+
+ def find_attribute_named(name)
+ @attributes.find { |m| m.name == name }
+ end
+
+ ##
+ # Finds a constant with +name+ in this context
+
+ def find_constant_named(name)
+ @constants.find {|m| m.name == name}
+ end
+
+ ##
+ # Find a module at a higher scope
+
+ def find_enclosing_module_named(name)
+ parent && parent.find_module_named(name)
+ end
+
+ ##
+ # Finds a file with +name+ in this context
+
+ def find_file_named(name)
+ top_level.class.find_file_named(name)
+ end
+
+ ##
+ # Finds an instance method with +name+ in this context
+
+ def find_instance_method_named(name)
+ @method_list.find { |meth| meth.name == name && !meth.singleton }
+ end
+
+ ##
+ # Finds a method, constant, attribute, module or files 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_module_named(symbol) or
+ find_file_named(symbol)
+ end
+
+ ##
+ # Finds a instance or module method with +name+ in this context
+
+ def find_method_named(name)
+ @method_list.find { |meth| meth.name == name }
+ end
+
+ ##
+ # Find a module with +name+ using ruby's scoping rules
+
+ def find_module_named(name)
+ res = @modules[name] || @classes[name]
+ return res if res
+ return self if self.name == name
+ find_enclosing_module_named name
+ end
+
+ ##
+ # Look up +symbol+. If +method+ is non-nil, then we assume the symbol
+ # references a module that contains that method.
+
+ def find_symbol(symbol, method = nil)
+ result = nil
+
+ case symbol
+ when /^::(.*)/ 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
+ end
+
+ else
+ # 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
+
+ if result and method then
+ fail unless result.respond_to? :find_local_symbol
+ result = result.find_local_symbol(method)
+ end
+
+ result
+ end
+
+ ##
+ # The full name for this context. This method is overridden by subclasses.
+
+ def full_name
+ '(unknown)'
+ end
+
+ ##
+ # URL for this with a +prefix+
+
+ def http_url(prefix)
+ path = full_name
+ path = path.gsub(/<<\s*(\w*)/, 'from-\1') if path =~ /<</
+ path = [prefix] + path.split('::')
+
+ File.join(*path.compact) + '.html'
+ end
+
+ ##
+ # Breaks method_list into a nested hash by type (class or instance) and
+ # visibility (public, protected private)
+
+ def methods_by_type
+ methods = {}
+
+ TYPES.each do |type|
+ visibilities = {}
+ VISIBILITIES.each do |vis|
+ visibilities[vis] = []
+ end
+
+ methods[type] = visibilities
+ end
+
+ each_method do |method|
+ methods[method.type][method.visibility] << method
+ end
+
+ methods
+ end
+
+ ##
+ # Yields Method and Attr entries matching the list of names in +methods+.
+ # Attributes are only returned when +singleton+ is false.
+
+ def methods_matching(methods, singleton = false)
+ count = 0
+
+ @method_list.each do |m|
+ if methods.include? m.name and m.singleton == singleton then
+ yield m
+ count += 1
+ end
+ end
+
+ return if count == methods.size || singleton
+
+ @attributes.each do |a|
+ yield a if methods.include? a.name
+ end
+ end
+
+ ##
+ # Array of modules in this context
+
+ def modules
+ @modules.values
+ end
+
+ ##
+ # Hash of modules keyed by module name
+
+ def modules_hash
+ @modules
+ end
+
+ ##
+ # Changes the visibility for new methods to +visibility+
+
+ def ongoing_visibility=(visibility)
+ @visibility = visibility
+ end
+
+ ##
+ # Record which file +top_level+ 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
+
+ def remove_methods_etc
+ initialize_methods_etc
+ end
+
+ ##
+ # Given an array +methods+ of method names, set the visibility of each to
+ # +visibility+
+
+ def set_visibility_for(methods, visibility, singleton = false)
+ methods_matching methods, singleton do |m|
+ m.visibility = visibility
+ end
+ end
+
+ ##
+ # 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
+ end
+
+ ##
+ # Return the TopLevel that owns us
+
+ def top_level
+ return @top_level if defined? @top_level
+ @top_level = self
+ @top_level = @top_level.parent until RDoc::TopLevel === @top_level
+ @top_level
+ end
+
+end
+
diff --git a/lib/rdoc/diagram.rb b/lib/rdoc/diagram.rb
deleted file mode 100644
index d308d36dee..0000000000
--- a/lib/rdoc/diagram.rb
+++ /dev/null
@@ -1,340 +0,0 @@
-# A wonderful hack by to draw package diagrams using the dot package.
-# Originally written by Jah, team Enticla.
-#
-# You must have the V1.7 or later in your path
-# http://www.research.att.com/sw/tools/graphviz/
-
-require 'rdoc/dot'
-
-module RDoc
-
- ##
- # Draw a set of diagrams representing the modules and classes in the
- # system. We draw one diagram for each file, and one for each toplevel
- # class or module. This means there will be overlap. However, it also
- # means that you'll get better context for objects.
- #
- # To use, simply
- #
- # d = Diagram.new(info) # pass in collection of top level infos
- # d.draw
- #
- # The results will be written to the +dot+ subdirectory. The process
- # also sets the +diagram+ attribute in each object it graphs to
- # the name of the file containing the image. This can be used
- # by output generators to insert images.
-
- class Diagram
-
- FONT = "Arial"
-
- DOT_PATH = "dot"
-
- ##
- # Pass in the set of top level objects. The method also creates the
- # subdirectory to hold the images
-
- def initialize(info, options)
- @info = info
- @options = options
- @counter = 0
- FileUtils.mkdir_p(DOT_PATH)
- @diagram_cache = {}
- end
-
- ##
- # Draw the diagrams. We traverse the files, drawing a diagram for each. We
- # also traverse each top-level class and module in that file drawing a
- # diagram for these too.
-
- def draw
- unless @options.quiet
- $stderr.print "Diagrams: "
- $stderr.flush
- end
-
- @info.each_with_index do |i, file_count|
- @done_modules = {}
- @local_names = find_names(i)
- @global_names = []
- @global_graph = graph = DOT::Digraph.new('name' => 'TopLevel',
- 'fontname' => FONT,
- 'fontsize' => '8',
- 'bgcolor' => 'lightcyan1',
- 'compound' => 'true')
-
- # it's a little hack %) i'm too lazy to create a separate class
- # for default node
- graph << DOT::Node.new('name' => 'node',
- 'fontname' => FONT,
- 'color' => 'black',
- 'fontsize' => 8)
-
- i.modules.each do |mod|
- draw_module(mod, graph, true, i.file_relative_name)
- end
- add_classes(i, graph, i.file_relative_name)
-
- i.diagram = convert_to_png("f_#{file_count}", graph)
-
- # now go through and document each top level class and
- # module independently
- i.modules.each_with_index do |mod, count|
- @done_modules = {}
- @local_names = find_names(mod)
- @global_names = []
-
- @global_graph = graph = DOT::Digraph.new('name' => 'TopLevel',
- 'fontname' => FONT,
- 'fontsize' => '8',
- 'bgcolor' => 'lightcyan1',
- 'compound' => 'true')
-
- graph << DOT::Node.new('name' => 'node',
- 'fontname' => FONT,
- 'color' => 'black',
- 'fontsize' => 8)
- draw_module(mod, graph, true)
- mod.diagram = convert_to_png("m_#{file_count}_#{count}",
- graph)
- end
- end
- $stderr.puts unless @options.quiet
- end
-
- private
-
- def find_names(mod)
- return [mod.full_name] + mod.classes.collect{|cl| cl.full_name} +
- mod.modules.collect{|m| find_names(m)}.flatten
- end
-
- def find_full_name(name, mod)
- full_name = name.dup
- return full_name if @local_names.include?(full_name)
- mod_path = mod.full_name.split('::')[0..-2]
- unless mod_path.nil?
- until mod_path.empty?
- full_name = mod_path.pop + '::' + full_name
- return full_name if @local_names.include?(full_name)
- end
- end
- return name
- end
-
- def draw_module(mod, graph, toplevel = false, file = nil)
- return if @done_modules[mod.full_name] and not toplevel
-
- @counter += 1
- url = mod.http_url("classes")
- m = DOT::Subgraph.new('name' => "cluster_#{mod.full_name.gsub( /:/,'_' )}",
- 'label' => mod.name,
- 'fontname' => FONT,
- 'color' => 'blue',
- 'style' => 'filled',
- 'URL' => %{"#{url}"},
- 'fillcolor' => toplevel ? 'palegreen1' : 'palegreen3')
-
- @done_modules[mod.full_name] = m
- add_classes(mod, m, file)
- graph << m
-
- unless mod.includes.empty?
- mod.includes.each do |inc|
- m_full_name = find_full_name(inc.name, mod)
- if @local_names.include?(m_full_name)
- @global_graph << DOT::Edge.new('from' => "#{m_full_name.gsub( /:/,'_' )}",
- 'to' => "#{mod.full_name.gsub( /:/,'_' )}",
- 'ltail' => "cluster_#{m_full_name.gsub( /:/,'_' )}",
- 'lhead' => "cluster_#{mod.full_name.gsub( /:/,'_' )}")
- else
- unless @global_names.include?(m_full_name)
- path = m_full_name.split("::")
- url = File.join('classes', *path) + ".html"
- @global_graph << DOT::Node.new('name' => "#{m_full_name.gsub( /:/,'_' )}",
- 'shape' => 'box',
- 'label' => "#{m_full_name}",
- 'URL' => %{"#{url}"})
- @global_names << m_full_name
- end
- @global_graph << DOT::Edge.new('from' => "#{m_full_name.gsub( /:/,'_' )}",
- 'to' => "#{mod.full_name.gsub( /:/,'_' )}",
- 'lhead' => "cluster_#{mod.full_name.gsub( /:/,'_' )}")
- end
- end
- end
- end
-
- def add_classes(container, graph, file = nil )
-
- use_fileboxes = @options.fileboxes
-
- files = {}
-
- # create dummy node (needed if empty and for module includes)
- if container.full_name
- graph << DOT::Node.new('name' => "#{container.full_name.gsub( /:/,'_' )}",
- 'label' => "",
- 'width' => (container.classes.empty? and
- container.modules.empty?) ?
- '0.75' : '0.01',
- 'height' => '0.01',
- 'shape' => 'plaintext')
- end
-
- container.classes.each_with_index do |cl, cl_index|
- last_file = cl.in_files[-1].file_relative_name
-
- if use_fileboxes && !files.include?(last_file)
- @counter += 1
- files[last_file] =
- DOT::Subgraph.new('name' => "cluster_#{@counter}",
- 'label' => "#{last_file}",
- 'fontname' => FONT,
- 'color'=>
- last_file == file ? 'red' : 'black')
- end
-
- next if cl.name == 'Object' || cl.name[0,2] == "<<"
-
- url = cl.http_url("classes")
-
- label = cl.name.dup
- if use_fileboxes && cl.in_files.length > 1
- label << '\n[' +
- cl.in_files.collect {|i|
- i.file_relative_name
- }.sort.join( '\n' ) +
- ']'
- end
-
- attrs = {
- 'name' => "#{cl.full_name.gsub( /:/, '_' )}",
- 'fontcolor' => 'black',
- 'style'=>'filled',
- 'color'=>'palegoldenrod',
- 'label' => label,
- 'shape' => 'ellipse',
- 'URL' => %{"#{url}"}
- }
-
- c = DOT::Node.new(attrs)
-
- if use_fileboxes
- files[last_file].push c
- else
- graph << c
- end
- end
-
- if use_fileboxes
- files.each_value do |val|
- graph << val
- end
- end
-
- unless container.classes.empty?
- container.classes.each_with_index do |cl, cl_index|
- cl.includes.each do |m|
- m_full_name = find_full_name(m.name, cl)
- if @local_names.include?(m_full_name)
- @global_graph << DOT::Edge.new('from' => "#{m_full_name.gsub( /:/,'_' )}",
- 'to' => "#{cl.full_name.gsub( /:/,'_' )}",
- 'ltail' => "cluster_#{m_full_name.gsub( /:/,'_' )}")
- else
- unless @global_names.include?(m_full_name)
- path = m_full_name.split("::")
- url = File.join('classes', *path) + ".html"
- @global_graph << DOT::Node.new('name' => "#{m_full_name.gsub( /:/,'_' )}",
- 'shape' => 'box',
- 'label' => "#{m_full_name}",
- 'URL' => %{"#{url}"})
- @global_names << m_full_name
- end
- @global_graph << DOT::Edge.new('from' => "#{m_full_name.gsub( /:/,'_' )}",
- 'to' => "#{cl.full_name.gsub( /:/, '_')}")
- end
- end
-
- sclass = cl.superclass
- next if sclass.nil? || sclass == 'Object'
- sclass_full_name = find_full_name(sclass,cl)
- unless @local_names.include?(sclass_full_name) or @global_names.include?(sclass_full_name)
- path = sclass_full_name.split("::")
- url = File.join('classes', *path) + ".html"
- @global_graph << DOT::Node.new('name' => "#{sclass_full_name.gsub( /:/, '_' )}",
- 'label' => sclass_full_name,
- 'URL' => %{"#{url}"})
- @global_names << sclass_full_name
- end
- @global_graph << DOT::Edge.new('from' => "#{sclass_full_name.gsub( /:/,'_' )}",
- 'to' => "#{cl.full_name.gsub( /:/, '_')}")
- end
- end
-
- container.modules.each do |submod|
- draw_module(submod, graph)
- end
-
- end
-
- def convert_to_png(file_base, graph)
- str = graph.to_s
- return @diagram_cache[str] if @diagram_cache[str]
- op_type = @options.image_format
- dotfile = File.join(DOT_PATH, file_base)
- src = dotfile + ".dot"
- dot = dotfile + "." + op_type
-
- unless @options.quiet
- $stderr.print "."
- $stderr.flush
- end
-
- File.open(src, 'w+' ) do |f|
- f << str << "\n"
- end
-
- system "dot", "-T#{op_type}", src, "-o", dot
-
- # Now construct the imagemap wrapper around
- # that png
-
- ret = wrap_in_image_map(src, dot)
- @diagram_cache[str] = ret
- return ret
- end
-
- ##
- # Extract the client-side image map from dot, and use it to generate the
- # imagemap proper. Return the whole <map>..<img> combination, suitable for
- # inclusion on the page
-
- def wrap_in_image_map(src, dot)
- res = ""
- dot_map = `dot -Tismap #{src}`
-
- if(!dot_map.empty?)
- res << %{<map id="map" name="map">\n}
- dot_map.split($/).each do |area|
- unless area =~ /^rectangle \((\d+),(\d+)\) \((\d+),(\d+)\) ([\/\w.]+)\s*(.*)/
- $stderr.puts "Unexpected output from dot:\n#{area}"
- return nil
- end
-
- xs, ys = [$1.to_i, $3.to_i], [$2.to_i, $4.to_i]
- url, area_name = $5, $6
-
- res << %{ <area shape="rect" coords="#{xs.min},#{ys.min},#{xs.max},#{ys.max}" }
- res << %{ href="#{url}" alt="#{area_name}" />\n}
- end
- res << "</map>\n"
- end
-
- res << %{<img src="#{dot}" usemap="#map" alt="#{dot}" />}
- return res
- end
-
- end
-
-end
diff --git a/lib/rdoc/dot.rb b/lib/rdoc/dot.rb
deleted file mode 100644
index fbd2cfba02..0000000000
--- a/lib/rdoc/dot.rb
+++ /dev/null
@@ -1,249 +0,0 @@
-module RDoc; end
-
-module RDoc::DOT
-
- TAB = ' '
- TAB2 = TAB * 2
-
- # options for node declaration
- NODE_OPTS = [
- 'bgcolor',
- 'color',
- 'fontcolor',
- 'fontname',
- 'fontsize',
- 'height',
- 'width',
- 'label',
- 'layer',
- 'rank',
- 'shape',
- 'shapefile',
- 'style',
- 'URL',
- ]
-
- # options for edge declaration
- EDGE_OPTS = [
- 'color',
- 'decorate',
- 'dir',
- 'fontcolor',
- 'fontname',
- 'fontsize',
- 'id',
- 'label',
- 'layer',
- 'lhead',
- 'ltail',
- 'minlen',
- 'style',
- 'weight'
- ]
-
- # options for graph declaration
- GRAPH_OPTS = [
- 'bgcolor',
- 'center',
- 'clusterrank',
- 'color',
- 'compound',
- 'concentrate',
- 'fillcolor',
- 'fontcolor',
- 'fontname',
- 'fontsize',
- 'label',
- 'layerseq',
- 'margin',
- 'mclimit',
- 'nodesep',
- 'nslimit',
- 'ordering',
- 'orientation',
- 'page',
- 'rank',
- 'rankdir',
- 'ranksep',
- 'ratio',
- 'size',
- 'style',
- 'URL'
- ]
-
- # a root class for any element in dot notation
- class SimpleElement
- attr_accessor :name
-
- def initialize( params = {} )
- @label = params['name'] ? params['name'] : ''
- end
-
- def to_s
- @name
- end
- end
-
- # an element that has options ( node, edge or graph )
- class Element < SimpleElement
- #attr_reader :parent
- attr_accessor :name, :options
-
- def initialize( params = {}, option_list = [] )
- super( params )
- @name = params['name'] ? params['name'] : nil
- @parent = params['parent'] ? params['parent'] : nil
- @options = {}
- option_list.each{ |i|
- @options[i] = params[i] if params[i]
- }
- @options['label'] ||= @name if @name != 'node'
- end
-
- def each_option
- @options.each{ |i| yield i }
- end
-
- def each_option_pair
- @options.each_pair{ |key, val| yield key, val }
- end
-
- #def parent=( thing )
- # @parent.delete( self ) if defined?( @parent ) and @parent
- # @parent = thing
- #end
- end
-
-
- # this is used when we build nodes that have shape=record
- # ports don't have options :)
- class Port < SimpleElement
- attr_accessor :label
-
- def initialize( params = {} )
- super( params )
- @name = params['label'] ? params['label'] : ''
- end
- def to_s
- ( @name && @name != "" ? "<#{@name}>" : "" ) + "#{@label}"
- end
- end
-
- # node element
- class Node < Element
-
- def initialize( params = {}, option_list = NODE_OPTS )
- super( params, option_list )
- @ports = params['ports'] ? params['ports'] : []
- end
-
- def each_port
- @ports.each{ |i| yield i }
- end
-
- def << ( thing )
- @ports << thing
- end
-
- def push ( thing )
- @ports.push( thing )
- end
-
- def pop
- @ports.pop
- end
-
- def to_s( t = '' )
-
- label = @options['shape'] != 'record' && @ports.length == 0 ?
- @options['label'] ?
- t + TAB + "label = \"#{@options['label']}\"\n" :
- '' :
- t + TAB + 'label = "' + " \\\n" +
- t + TAB2 + "#{@options['label']}| \\\n" +
- @ports.collect{ |i|
- t + TAB2 + i.to_s
- }.join( "| \\\n" ) + " \\\n" +
- t + TAB + '"' + "\n"
-
- t + "#{@name} [\n" +
- @options.to_a.collect{ |i|
- i[1] && i[0] != 'label' ?
- t + TAB + "#{i[0]} = #{i[1]}" : nil
- }.compact.join( ",\n" ) + ( label != '' ? ",\n" : "\n" ) +
- label +
- t + "]\n"
- end
- end
-
- # subgraph element is the same to graph, but has another header in dot
- # notation
- class Subgraph < Element
-
- def initialize( params = {}, option_list = GRAPH_OPTS )
- super( params, option_list )
- @nodes = params['nodes'] ? params['nodes'] : []
- @dot_string = 'subgraph'
- end
-
- def each_node
- @nodes.each{ |i| yield i }
- end
-
- def << ( thing )
- @nodes << thing
- end
-
- def push( thing )
- @nodes.push( thing )
- end
-
- def pop
- @nodes.pop
- end
-
- def to_s( t = '' )
- hdr = t + "#{@dot_string} #{@name} {\n"
-
- options = @options.to_a.collect{ |name, val|
- val && name != 'label' ?
- t + TAB + "#{name} = #{val}" :
- name ? t + TAB + "#{name} = \"#{val}\"" : nil
- }.compact.join( "\n" ) + "\n"
-
- nodes = @nodes.collect{ |i|
- i.to_s( t + TAB )
- }.join( "\n" ) + "\n"
- hdr + options + nodes + t + "}\n"
- end
- end
-
- # this is graph
- class Digraph < Subgraph
- def initialize( params = {}, option_list = GRAPH_OPTS )
- super( params, option_list )
- @dot_string = 'digraph'
- end
- end
-
- # this is edge
- class Edge < Element
- attr_accessor :from, :to
- def initialize( params = {}, option_list = EDGE_OPTS )
- super( params, option_list )
- @from = params['from'] ? params['from'] : nil
- @to = params['to'] ? params['to'] : nil
- end
-
- def to_s( t = '' )
- t + "#{@from} -> #{to} [\n" +
- @options.to_a.collect{ |i|
- i[1] && i[0] != 'label' ?
- t + TAB + "#{i[0]} = #{i[1]}" :
- i[1] ? t + TAB + "#{i[0]} = \"#{i[1]}\"" : nil
- }.compact.join( "\n" ) + "\n" + t + "]\n"
- end
- end
-
-end
-
diff --git a/lib/rdoc/generator.rb b/lib/rdoc/generator.rb
index 86196b4cf6..b65002977a 100644
--- a/lib/rdoc/generator.rb
+++ b/lib/rdoc/generator.rb
@@ -1,1082 +1,8 @@
-require 'cgi'
require 'rdoc'
-require 'rdoc/options'
-require 'rdoc/markup/to_html_crossref'
-require 'rdoc/template'
-module RDoc::Generator
-
- ##
- # Name of sub-directory that holds file descriptions
-
- FILE_DIR = "files"
-
- ##
- # Name of sub-directory that holds class descriptions
-
- CLASS_DIR = "classes"
-
- ##
- # Name of the RDoc CSS file
-
- CSS_NAME = "rdoc-style.css"
-
- ##
- # Build a hash of all items that can be cross-referenced. This is used when
- # we output required and included names: if the names appear in this hash,
- # we can generate an html cross reference to the appropriate description.
- # We also use this when parsing comment blocks: any decorated words matching
- # an entry in this list are hyperlinked.
-
- class AllReferences
- @@refs = {}
-
- def AllReferences::reset
- @@refs = {}
- end
-
- def AllReferences.add(name, html_class)
- @@refs[name] = html_class
- end
-
- def AllReferences.[](name)
- @@refs[name]
- end
-
- def AllReferences.keys
- @@refs.keys
- end
- end
-
- ##
- # Handle common markup tasks for the various Context subclasses
-
- module MarkUp
-
- ##
- # Convert a string in markup format into HTML.
-
- def markup(str, remove_para = false)
- return '' unless str
-
- # Convert leading comment markers to spaces, but only if all non-blank
- # lines have them
- if str =~ /^(?>\s*)[^\#]/ then
- content = str
- else
- content = str.gsub(/^\s*(#+)/) { $1.tr '#', ' ' }
- end
-
- res = formatter.convert content
-
- if remove_para then
- res.sub!(/^<p>/, '')
- res.sub!(/<\/p>$/, '')
- end
-
- res
- end
-
- ##
- # Qualify a stylesheet URL; if if +css_name+ does not begin with '/' or
- # 'http[s]://', prepend a prefix relative to +path+. Otherwise, return it
- # unmodified.
-
- def style_url(path, css_name=nil)
-# $stderr.puts "style_url( #{path.inspect}, #{css_name.inspect} )"
- css_name ||= CSS_NAME
- if %r{^(https?:/)?/} =~ css_name
- css_name
- else
- RDoc::Markup::ToHtml.gen_relative_url path, css_name
- end
- end
-
- ##
- # Build a webcvs URL with the given 'url' argument. URLs with a '%s' in them
- # get the file's path sprintfed into them; otherwise they're just catenated
- # together.
-
- def cvs_url(url, full_path)
- if /%s/ =~ url
- return sprintf( url, full_path )
- else
- return url + full_path
- end
- end
-
- end
-
- ##
- # A Context is built by the parser to represent a container: contexts hold
- # classes, modules, methods, require lists and include lists. ClassModule
- # and TopLevel are the context objects we process here
-
- class Context
-
- include MarkUp
-
- attr_reader :context
-
- ##
- # Generate:
- #
- # * a list of RDoc::Generator::File objects for each TopLevel object
- # * a list of RDoc::Generator::Class objects for each first level class or
- # module in the TopLevel objects
- # * a complete list of all hyperlinkable terms (file, class, module, and
- # method names)
-
- def self.build_indices(toplevels, options)
- files = []
- classes = []
-
- toplevels.each do |toplevel|
- files << RDoc::Generator::File.new(toplevel, options,
- RDoc::Generator::FILE_DIR)
- end
-
- RDoc::TopLevel.all_classes_and_modules.each do |cls|
- build_class_list(classes, options, cls, files[0],
- RDoc::Generator::CLASS_DIR)
- end
-
- return files, classes
- end
-
- def self.build_class_list(classes, options, from, html_file, class_dir)
- classes << RDoc::Generator::Class.new(from, html_file, class_dir, options)
-
- from.each_classmodule do |mod|
- build_class_list(classes, options, mod, html_file, class_dir)
- end
- end
-
- def initialize(context, options)
- @context = context
- @options = options
-
- # HACK ugly
- @template = options.template_class
- end
-
- def formatter
- @formatter ||= @options.formatter ||
- RDoc::Markup::ToHtmlCrossref.new(path, self, @options.show_hash)
- end
-
- ##
- # convenience method to build a hyperlink
-
- def href(link, cls, name)
- %{<a href="#{link}" class="#{cls}">#{name}</a>} #"
- end
-
- ##
- # Returns a reference to outselves to be used as an href= the form depends
- # on whether we're all in one file or in multiple files
-
- def as_href(from_path)
- if @options.all_one_file
- "#" + path
- else
- RDoc::Markup::ToHtml.gen_relative_url from_path, path
- end
- end
-
- ##
- # Create a list of Method objects for each method in the corresponding
- # context object. If the @options.show_all variable is set (corresponding
- # to the <tt>--all</tt> option, we include all methods, otherwise just the
- # public ones.
-
- def collect_methods
- list = @context.method_list
-
- unless @options.show_all then
- list = list.select do |m|
- m.visibility == :public or
- m.visibility == :protected or
- m.force_documentation
- end
- end
-
- @methods = list.collect do |m|
- RDoc::Generator::Method.new m, self, @options
- end
- end
-
- ##
- # Build a summary list of all the methods in this context
-
- def build_method_summary_list(path_prefix = "")
- collect_methods unless @methods
-
- @methods.sort.map do |meth|
- {
- "name" => CGI.escapeHTML(meth.name),
- "aref" => "##{meth.aref}"
- }
- end
- end
-
- ##
- # Build a list of aliases for which we couldn't find a
- # corresponding method
-
- def build_alias_summary_list(section)
- @context.aliases.map do |al|
- next unless al.section == section
-
- res = {
- 'old_name' => al.old_name,
- 'new_name' => al.new_name,
- }
-
- if al.comment and not al.comment.empty? then
- res['desc'] = markup al.comment, true
- end
-
- res
- end.compact
- end
-
- ##
- # Build a list of constants
-
- def build_constants_summary_list(section)
- @context.constants.map do |co|
- next unless co.section == section
-
- res = {
- 'name' => co.name,
- 'value' => CGI.escapeHTML(co.value)
- }
-
- if co.comment and not co.comment.empty? then
- res['desc'] = markup co.comment, true
- end
-
- res
- end.compact
- end
-
- def build_requires_list(context)
- potentially_referenced_list(context.requires) {|fn| [fn + ".rb"] }
- end
-
- def build_include_list(context)
- potentially_referenced_list(context.includes)
- end
-
- ##
- # Build a list from an array of Context items. Look up each in the
- # AllReferences hash: if we find a corresponding entry, we generate a
- # hyperlink to it, otherwise just output the name. However, some names
- # potentially need massaging. For example, you may require a Ruby file
- # without the .rb extension, but the file names we know about may have it.
- # To deal with this, we pass in a block which performs the massaging,
- # returning an array of alternative names to match
-
- def potentially_referenced_list(array)
- res = []
- array.each do |i|
- ref = AllReferences[i.name]
-# if !ref
-# container = @context.parent
-# while !ref && container
-# name = container.name + "::" + i.name
-# ref = AllReferences[name]
-# container = container.parent
-# end
-# end
-
- ref = @context.find_symbol(i.name)
- ref = ref.viewer if ref
-
- if !ref && block_given?
- possibles = yield(i.name)
- while !ref and !possibles.empty?
- ref = AllReferences[possibles.shift]
- end
- end
- h_name = CGI.escapeHTML(i.name)
- if ref and ref.document_self
- path = url(ref.path)
- res << { "name" => h_name, "aref" => path }
- else
- res << { "name" => h_name }
- end
- end
- res
- end
-
- ##
- # Build an array of arrays of method details. The outer array has up
- # to six entries, public, private, and protected for both class
- # methods, the other for instance methods. The inner arrays contain
- # a hash for each method
-
- def build_method_detail_list(section)
- outer = []
-
- methods = @methods.sort.select do |m|
- m.document_self and m.section == section
- end
-
- for singleton in [true, false]
- for vis in [ :public, :protected, :private ]
- res = []
- methods.each do |m|
- next unless m.visibility == vis and m.singleton == singleton
-
- row = {}
-
- if m.call_seq then
- row["callseq"] = m.call_seq.gsub(/->/, '&rarr;')
- else
- row["name"] = CGI.escapeHTML(m.name)
- row["params"] = m.params
- end
-
- desc = m.description.strip
- row["m_desc"] = desc unless desc.empty?
- row["aref"] = m.aref
- row["visibility"] = m.visibility.to_s
-
- alias_names = []
-
- m.aliases.each do |other|
- if other.viewer then # won't be if the alias is private
- alias_names << {
- 'name' => other.name,
- 'aref' => other.viewer.as_href(path)
- }
- end
- end
-
- row["aka"] = alias_names unless alias_names.empty?
-
- if @options.inline_source then
- code = m.source_code
- row["sourcecode"] = code if code
- else
- code = m.src_url
- if code then
- row["codeurl"] = code
- row["imgurl"] = m.img_url
- end
- end
-
- res << row
- end
-
- if res.size > 0 then
- outer << {
- "type" => vis.to_s.capitalize,
- "category" => singleton ? "Class" : "Instance",
- "methods" => res
- }
- end
- end
- end
-
- outer
- end
-
- ##
- # Build the structured list of classes and modules contained
- # in this context.
-
- def build_class_list(level, from, section, infile=nil)
- prefix = '&nbsp;&nbsp;::' * level;
- res = ''
-
- from.modules.sort.each do |mod|
- next unless mod.section == section
- next if infile && !mod.defined_in?(infile)
- if mod.document_self
- res <<
- prefix <<
- 'Module ' <<
- href(url(mod.viewer.path), 'link', mod.full_name) <<
- "<br />\n" <<
- build_class_list(level + 1, mod, section, infile)
- end
- end
-
- from.classes.sort.each do |cls|
- next unless cls.section == section
- next if infile and not cls.defined_in?(infile)
-
- if cls.document_self
- res <<
- prefix <<
- 'Class ' <<
- href(url(cls.viewer.path), 'link', cls.full_name) <<
- "<br />\n" <<
- build_class_list(level + 1, cls, section, infile)
- end
- end
-
- res
- end
-
- def url(target)
- RDoc::Markup::ToHtml.gen_relative_url path, target
- end
-
- def aref_to(target)
- if @options.all_one_file
- "#" + target
- else
- url(target)
- end
- end
-
- def document_self
- @context.document_self
- end
-
- def diagram_reference(diagram)
- res = diagram.gsub(/((?:src|href)=")(.*?)"/) {
- $1 + url($2) + '"'
- }
- res
- end
-
- ##
- # Find a symbol in ourselves or our parent
-
- def find_symbol(symbol, method=nil)
- res = @context.find_symbol(symbol, method)
- if res
- res = res.viewer
- end
- res
- end
-
- ##
- # create table of contents if we contain sections
-
- def add_table_of_sections
- toc = []
- @context.sections.each do |section|
- if section.title then
- toc << {
- 'secname' => section.title,
- 'href' => section.sequence
- }
- end
- end
-
- @values['toc'] = toc unless toc.empty?
- end
-
- end
-
- ##
- # Wrap a ClassModule context
-
- class Class < Context
-
- attr_reader :methods
- attr_reader :path
- attr_reader :values
-
- def initialize(context, html_file, prefix, options)
- super context, options
-
- @html_file = html_file
- @html_class = self
- @is_module = context.module?
- @values = {}
-
- context.viewer = self
-
- if options.all_one_file
- @path = context.full_name
- else
- @path = http_url(context.full_name, prefix)
- end
-
- collect_methods
-
- AllReferences.add(name, self)
- end
-
- ##
- # Returns the relative file name to store this class in, which is also its
- # url
-
- def http_url(full_name, prefix)
- path = full_name.dup
-
- path.gsub!(/<<\s*(\w*)/, 'from-\1') if path['<<']
-
- ::File.join(prefix, path.split("::")) + ".html"
- end
-
- def name
- @context.full_name
- end
-
- def parent_name
- @context.parent.full_name
- end
-
- def index_name
- name
- end
-
- def write_on(f, file_list, class_list, method_list, overrides = {})
- value_hash
-
- @values['file_list'] = file_list
- @values['class_list'] = class_list
- @values['method_list'] = method_list
-
- @values.update overrides
-
- template = RDoc::TemplatePage.new(@template::BODY,
- @template::CLASS_PAGE,
- @template::METHOD_LIST)
-
- template.write_html_on(f, @values)
- end
-
- def value_hash
- class_attribute_values
- add_table_of_sections
-
- @values["charset"] = @options.charset
- @values["style_url"] = style_url(path, @options.css)
-
- d = markup(@context.comment)
- @values["description"] = d unless d.empty?
-
- ml = build_method_summary_list @path
- @values["methods"] = ml unless ml.empty?
-
- il = build_include_list @context
- @values["includes"] = il unless il.empty?
-
- @values["sections"] = @context.sections.map do |section|
- secdata = {
- "sectitle" => section.title,
- "secsequence" => section.sequence,
- "seccomment" => markup(section.comment),
- }
-
- al = build_alias_summary_list section
- secdata["aliases"] = al unless al.empty?
-
- co = build_constants_summary_list section
- secdata["constants"] = co unless co.empty?
-
- al = build_attribute_list section
- secdata["attributes"] = al unless al.empty?
-
- cl = build_class_list 0, @context, section
- secdata["classlist"] = cl unless cl.empty?
-
- mdl = build_method_detail_list section
- secdata["method_list"] = mdl unless mdl.empty?
-
- secdata
- end
-
- @values
- end
-
- def build_attribute_list(section)
- @context.attributes.sort.map do |att|
- next unless att.section == section
-
- if att.visibility == :public or att.visibility == :protected or
- @options.show_all then
-
- entry = {
- "name" => CGI.escapeHTML(att.name),
- "rw" => att.rw,
- "a_desc" => markup(att.comment, true)
- }
-
- unless att.visibility == :public or att.visibility == :protected then
- entry["rw"] << "-"
- end
-
- entry
- end
- end.compact
- end
-
- def class_attribute_values
- h_name = CGI.escapeHTML(name)
-
- @values["href"] = @path
- @values["classmod"] = @is_module ? "Module" : "Class"
- @values["title"] = "#{@values['classmod']}: #{h_name} [#{@options.title}]"
-
- c = @context
- c = c.parent while c and not c.diagram
-
- if c and c.diagram then
- @values["diagram"] = diagram_reference(c.diagram)
- end
-
- @values["full_name"] = h_name
-
- if not @context.module? and @context.superclass then
- parent_class = @context.superclass
- @values["parent"] = CGI.escapeHTML(parent_class)
-
- if parent_name
- lookup = parent_name + "::" + parent_class
- else
- lookup = parent_class
- end
-
- parent_url = AllReferences[lookup] || AllReferences[parent_class]
-
- if parent_url and parent_url.document_self
- @values["par_url"] = aref_to(parent_url.path)
- end
- end
-
- files = []
- @context.in_files.each do |f|
- res = {}
- full_path = CGI.escapeHTML(f.file_absolute_name)
-
- res["full_path"] = full_path
- res["full_path_url"] = aref_to(f.viewer.path) if f.document_self
-
- if @options.webcvs
- res["cvsurl"] = cvs_url( @options.webcvs, full_path )
- end
-
- files << res
- end
-
- @values['infiles'] = files
- end
-
- def <=>(other)
- self.name <=> other.name
- end
-
- end
-
- ##
- # Handles the mapping of a file's information to HTML. In reality, a file
- # corresponds to a +TopLevel+ object, containing modules, classes, and
- # top-level methods. In theory it _could_ contain attributes and aliases,
- # but we ignore these for now.
-
- class File < Context
-
- attr_reader :path
- attr_reader :name
- attr_reader :values
-
- def initialize(context, options, file_dir)
- super context, options
-
- @values = {}
-
- if options.all_one_file
- @path = filename_to_label
- else
- @path = http_url(file_dir)
- end
-
- @name = @context.file_relative_name
-
- collect_methods
- AllReferences.add(name, self)
- context.viewer = self
- end
-
- def http_url(file_dir)
- ::File.join file_dir, "#{@context.file_relative_name.tr '.', '_'}.html"
- end
-
- def filename_to_label
- @context.file_relative_name.gsub(/%|\/|\?|\#/) do
- ('%%%x' % $&[0]).unpack('C')
- end
- end
-
- def index_name
- name
- end
-
- def parent_name
- nil
- end
-
- def value_hash
- file_attribute_values
- add_table_of_sections
-
- @values["charset"] = @options.charset
- @values["href"] = path
- @values["style_url"] = style_url(path, @options.css)
-
- if @context.comment
- d = markup(@context.comment)
- @values["description"] = d if d.size > 0
- end
-
- ml = build_method_summary_list
- @values["methods"] = ml unless ml.empty?
-
- il = build_include_list(@context)
- @values["includes"] = il unless il.empty?
-
- rl = build_requires_list(@context)
- @values["requires"] = rl unless rl.empty?
-
- if @options.promiscuous
- file_context = nil
- else
- file_context = @context
- end
-
-
- @values["sections"] = @context.sections.map do |section|
-
- secdata = {
- "sectitle" => section.title,
- "secsequence" => section.sequence,
- "seccomment" => markup(section.comment)
- }
-
- cl = build_class_list(0, @context, section, file_context)
- secdata["classlist"] = cl unless cl.empty?
-
- mdl = build_method_detail_list(section)
- secdata["method_list"] = mdl unless mdl.empty?
-
- al = build_alias_summary_list(section)
- secdata["aliases"] = al unless al.empty?
-
- co = build_constants_summary_list(section)
- secdata["constants"] = co unless co.empty?
-
- secdata
- end
-
- @values
- end
-
- def write_on(f, file_list, class_list, method_list, overrides = {})
- value_hash
-
- @values['file_list'] = file_list
- @values['class_list'] = class_list
- @values['method_list'] = method_list
-
- @values.update overrides
-
- template = RDoc::TemplatePage.new(@template::BODY,
- @template::FILE_PAGE,
- @template::METHOD_LIST)
-
- template.write_html_on(f, @values)
- end
-
- def file_attribute_values
- full_path = @context.file_absolute_name
- short_name = ::File.basename full_path
-
- @values["title"] = CGI.escapeHTML("File: #{short_name} [#{@options.title}]")
-
- if @context.diagram then
- @values["diagram"] = diagram_reference(@context.diagram)
- end
-
- @values["short_name"] = CGI.escapeHTML(short_name)
- @values["full_path"] = CGI.escapeHTML(full_path)
- @values["dtm_modified"] = @context.file_stat.mtime.to_s
-
- if @options.webcvs then
- @values["cvsurl"] = cvs_url @options.webcvs, @values["full_path"]
- end
- end
-
- def <=>(other)
- self.name <=> other.name
- end
-
- end
-
- class Method
-
- include MarkUp
-
- attr_reader :context
- attr_reader :src_url
- attr_reader :img_url
- attr_reader :source_code
-
- def self.all_methods
- @@all_methods
- end
-
- def self.reset
- @@all_methods = []
- @@seq = "M000000"
- end
-
- # Initialize the class variables.
- self.reset
-
- def initialize(context, html_class, options)
- # TODO: rethink the class hierarchy here...
- @context = context
- @html_class = html_class
- @options = options
-
- @@seq = @@seq.succ
- @seq = @@seq
-
- # HACK ugly
- @template = options.template_class
-
- @@all_methods << self
-
- context.viewer = self
-
- if (ts = @context.token_stream)
- @source_code = markup_code(ts)
- unless @options.inline_source
- @src_url = create_source_code_file(@source_code)
- @img_url = RDoc::Markup::ToHtml.gen_relative_url path, 'source.png'
- end
- end
-
- AllReferences.add(name, self)
- end
-
- ##
- # Returns a reference to outselves to be used as an href= the form depends
- # on whether we're all in one file or in multiple files
-
- def as_href(from_path)
- if @options.all_one_file
- "#" + path
- else
- RDoc::Markup::ToHtml.gen_relative_url from_path, path
- end
- end
-
- def formatter
- @formatter ||= @options.formatter ||
- RDoc::Markup::ToHtmlCrossref.new(path, self, @options.show_hash)
- end
-
- def inspect
- alias_for = if @context.is_alias_for then
- " (alias_for #{@context.is_alias_for})"
- else
- nil
- end
-
- "#<%s:0x%x %s%s%s (%s)%s>" % [
- self.class, object_id,
- @context.parent.name,
- @context.singleton ? '::' : '#',
- name,
- @context.visibility,
- alias_for
- ]
- end
-
- def name
- @context.name
- end
-
- def section
- @context.section
- end
-
- def index_name
- "#{@context.name} (#{@html_class.name})"
- end
-
- def parent_name
- if @context.parent.parent
- @context.parent.parent.full_name
- else
- nil
- end
- end
-
- def aref
- @seq
- end
-
- def path
- if @options.all_one_file
- aref
- else
- @html_class.path + "#" + aref
- end
- end
-
- def description
- markup(@context.comment)
- end
-
- def visibility
- @context.visibility
- end
-
- def singleton
- @context.singleton
- end
-
- def call_seq
- cs = @context.call_seq
- if cs
- cs.gsub(/\n/, "<br />\n")
- else
- nil
- end
- end
-
- def params
- # params coming from a call-seq in 'C' will start with the
- # method name
- params = @context.params
- if params !~ /^\w/
- params = @context.params.gsub(/\s*\#.*/, '')
- params = params.tr("\n", " ").squeeze(" ")
- params = "(" + params + ")" unless params[0] == ?(
-
- if (block = @context.block_params)
- # If this method has explicit block parameters, remove any
- # explicit &block
-
- params.sub!(/,?\s*&\w+/, '')
-
- block.gsub!(/\s*\#.*/, '')
- block = block.tr("\n", " ").squeeze(" ")
- if block[0] == ?(
- block.sub!(/^\(/, '').sub!(/\)/, '')
- end
- params << " {|#{block.strip}| ...}"
- end
- end
- CGI.escapeHTML(params)
- end
-
- def create_source_code_file(code_body)
- meth_path = @html_class.path.sub(/\.html$/, '.src')
- FileUtils.mkdir_p(meth_path)
- file_path = ::File.join meth_path, "#{@seq}.html"
-
- template = RDoc::TemplatePage.new(@template::SRC_PAGE)
-
- open file_path, 'w' do |f|
- values = {
- 'title' => CGI.escapeHTML(index_name),
- 'code' => code_body,
- 'style_url' => style_url(file_path, @options.css),
- 'charset' => @options.charset
- }
- template.write_html_on(f, values)
- end
-
- RDoc::Markup::ToHtml.gen_relative_url path, file_path
- end
-
- def <=>(other)
- @context <=> other.context
- end
-
- ##
- # Given a sequence of source tokens, mark up the source code
- # to make it look purty.
-
- def markup_code(tokens)
- src = ""
- tokens.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
-
- text = CGI.escapeHTML(t.text)
-
- if style
- src << "<span class=\"#{style}\">#{text}</span>"
- else
- src << text
- end
- end
-
- add_line_numbers(src) if @options.include_line_numbers
- src
- end
-
- ##
- # We rely on the fact that the first line of a source code listing has
- # # File xxxxx, line dddd
-
- def add_line_numbers(src)
- if src =~ /\A.*, line (\d+)/
- first = $1.to_i - 1
- last = first + src.count("\n")
- size = last.to_s.length
- fmt = "%#{size}d: "
- is_first_line = true
- line_num = first
- src.gsub!(/^/) do
- if is_first_line then
- is_first_line = false
- res = " " * (size+2)
- else
- res = sprintf(fmt, line_num)
- end
-
- line_num += 1
- res
- end
- end
- end
-
- def document_self
- @context.document_self
- end
-
- def aliases
- @context.aliases
- end
-
- def find_symbol(symbol, method=nil)
- res = @context.parent.find_symbol(symbol, method)
- if res
- res = res.viewer
- end
- res
- end
-
- end
+##
+# Namespace for generators
+module RDoc::Generator
end
diff --git a/lib/rdoc/generator/chm.rb b/lib/rdoc/generator/chm.rb
deleted file mode 100644
index 7537365842..0000000000
--- a/lib/rdoc/generator/chm.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-require 'rdoc/generator/html'
-
-class RDoc::Generator::CHM < RDoc::Generator::HTML
-
- HHC_PATH = "c:/Program Files/HTML Help Workshop/hhc.exe"
-
- ##
- # Standard generator factory
-
- def self.for(options)
- new(options)
- end
-
- def initialize(*args)
- super
- @op_name = @options.op_name || "rdoc"
- check_for_html_help_workshop
- end
-
- def check_for_html_help_workshop
- stat = File.stat(HHC_PATH)
- rescue
- $stderr <<
- "\n.chm output generation requires that Microsoft's Html Help\n" <<
- "Workshop is installed. RDoc looks for it in:\n\n " <<
- HHC_PATH <<
- "\n\nYou can download a copy for free from:\n\n" <<
- " http://msdn.microsoft.com/library/default.asp?" <<
- "url=/library/en-us/htmlhelp/html/hwMicrosoftHTMLHelpDownloads.asp\n\n"
- end
-
- ##
- # Generate the html as normal, then wrap it in a help project
-
- def generate(info)
- super
- @project_name = @op_name + ".hhp"
- create_help_project
- end
-
- ##
- # The project contains the project file, a table of contents and an index
-
- def create_help_project
- create_project_file
- create_contents_and_index
- compile_project
- end
-
- ##
- # The project file links together all the various
- # files that go to make up the help.
-
- def create_project_file
- template = RDoc::TemplatePage.new @template::HPP_FILE
- values = { "title" => @options.title, "opname" => @op_name }
- files = []
- @files.each do |f|
- files << { "html_file_name" => f.path }
- end
-
- values['all_html_files'] = files
-
- File.open(@project_name, "w") do |f|
- template.write_html_on(f, values)
- end
- end
-
- ##
- # The contents is a list of all files and modules.
- # For each we include as sub-entries the list
- # of methods they contain. As we build the contents
- # we also build an index file
-
- def create_contents_and_index
- contents = []
- index = []
-
- (@files+@classes).sort.each do |entry|
- content_entry = { "c_name" => entry.name, "ref" => entry.path }
- index << { "name" => entry.name, "aref" => entry.path }
-
- internals = []
-
- methods = entry.build_method_summary_list(entry.path)
-
- content_entry["methods"] = methods unless methods.empty?
- contents << content_entry
- index.concat methods
- end
-
- values = { "contents" => contents }
- template = RDoc::TemplatePage.new @template::CONTENTS
- File.open("contents.hhc", "w") do |f|
- template.write_html_on(f, values)
- end
-
- values = { "index" => index }
- template = RDoc::TemplatePage.new @template::CHM_INDEX
- File.open("index.hhk", "w") do |f|
- template.write_html_on(f, values)
- end
- end
-
- ##
- # Invoke the windows help compiler to compiler the project
-
- def compile_project
- system(HHC_PATH, @project_name)
- end
-
-end
-
diff --git a/lib/rdoc/generator/chm/chm.rb b/lib/rdoc/generator/chm/chm.rb
deleted file mode 100644
index c362318d91..0000000000
--- a/lib/rdoc/generator/chm/chm.rb
+++ /dev/null
@@ -1,100 +0,0 @@
-require 'rdoc/generator/chm'
-require 'rdoc/generator/html/html'
-
-module RDoc::Generator::CHM::CHM
-
- HTML = RDoc::Generator::HTML::HTML
-
- INDEX = HTML::INDEX
-
- STYLE = HTML::STYLE
-
- CLASS_INDEX = HTML::CLASS_INDEX
- CLASS_PAGE = HTML::CLASS_PAGE
- FILE_INDEX = HTML::FILE_INDEX
- FILE_PAGE = HTML::FILE_PAGE
- METHOD_INDEX = HTML::METHOD_INDEX
- METHOD_LIST = HTML::METHOD_LIST
-
- FR_INDEX_BODY = HTML::FR_INDEX_BODY
-
- # This is a nasty little hack, but hhc doesn't support the <?xml tag, so...
- BODY = HTML::BODY.sub!(/<\?xml.*\?>/, '')
- SRC_PAGE = HTML::SRC_PAGE.sub!(/<\?xml.*\?>/, '')
-
- HPP_FILE = <<-EOF
-[OPTIONS]
-Auto Index = Yes
-Compatibility=1.1 or later
-Compiled file=<%= values["opname"] %>.chm
-Contents file=contents.hhc
-Full-text search=Yes
-Index file=index.hhk
-Language=0x409 English(United States)
-Title=<%= values["title"] %>
-
-[FILES]
-<% values["all_html_files"].each do |all_html_files| %>
-<%= all_html_files["html_file_name"] %>
-<% end # values["all_html_files"] %>
- EOF
-
- CONTENTS = <<-EOF
-<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
-<HTML>
-<HEAD>
-<meta name="GENERATOR" content="Microsoft&reg; HTML Help Workshop 4.1">
-<!-- Sitemap 1.0 -->
-</HEAD><BODY>
-<OBJECT type="text/site properties">
- <param name="Foreground" value="0x80">
- <param name="Window Styles" value="0x800025">
- <param name="ImageType" value="Folder">
-</OBJECT>
-<UL>
-<% values["contents"].each do |contents| %>
- <LI> <OBJECT type="text/sitemap">
- <param name="Name" value="<%= contents["c_name"] %>">
- <param name="Local" value="<%= contents["ref"] %>">
- </OBJECT>
-<% if contents["methods"] then %>
-<ul>
-<% contents["methods"].each do |methods| %>
- <LI> <OBJECT type="text/sitemap">
- <param name="Name" value="<%= methods["name"] %>">
- <param name="Local" value="<%= methods["aref"] %>">
- </OBJECT>
-<% end # contents["methods"] %>
-</ul>
-<% end %>
- </LI>
-<% end # values["contents"] %>
-</UL>
-</BODY></HTML>
- EOF
-
- CHM_INDEX = <<-EOF
-<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
-<HTML>
-<HEAD>
-<meta name="GENERATOR" content="Microsoft&reg; HTML Help Workshop 4.1">
-<!-- Sitemap 1.0 -->
-</HEAD><BODY>
-<OBJECT type="text/site properties">
- <param name="Foreground" value="0x80">
- <param name="Window Styles" value="0x800025">
- <param name="ImageType" value="Folder">
-</OBJECT>
-<UL>
-<% values["index"].each do |index| %>
- <LI> <OBJECT type="text/sitemap">
- <param name="Name" value="<%= index["name"] %>">
- <param name="Local" value="<%= index["aref"] %>">
- </OBJECT>
-<% end # values["index"] %>
-</UL>
-</BODY></HTML>
- EOF
-
-end
-
diff --git a/lib/rdoc/generator/darkfish.rb b/lib/rdoc/generator/darkfish.rb
new file mode 100644
index 0000000000..a25475d39f
--- /dev/null
+++ b/lib/rdoc/generator/darkfish.rb
@@ -0,0 +1,459 @@
+#!ruby
+# vim: noet ts=2 sts=8 sw=2
+
+unless File.exist? File.expand_path('../.svn', __FILE__) then
+ require 'rubygems'
+ gem 'rdoc', '>= 2.4'
+end
+
+require 'pp'
+require 'pathname'
+require 'fileutils'
+require 'erb'
+
+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 $
+#
+# == Author/s
+# * Michael Granger (ged@FaerieMUD.org)
+#
+# == Contributors
+# * Mahlon E. Smith (mahlon@martini.nu)
+# * Eric Hodel (drbrain@segment7.net)
+#
+# == License
+#
+# Copyright (c) 2007, 2008, Michael Granger. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# * Neither the name of the author/s, nor the names of the project's
+# contributors may be used to endorse or promote products derived from this
+# software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+class RDoc::Generator::Darkfish
+
+ RDoc::RDoc.add_generator( self )
+
+ include ERB::Util
+
+ # Subversion rev
+ SVNRev = %$Rev: 52 $
+
+ # Subversion ID
+ SVNId = %$Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $
+
+ # Path to this file's parent directory. Used to find templates and other
+ # resources.
+ GENERATOR_DIR = File.join 'rdoc', 'generator'
+
+ # Release Version
+ VERSION = '1.1.6'
+
+ # Directory where generated classes live relative to the root
+ CLASS_DIR = nil
+
+ # Directory where generated files live relative to the root
+ FILE_DIR = nil
+
+
+ #################################################################
+ ### C L A S S M E T H O D S
+ #################################################################
+
+ ### Standard generator factory method
+ def self::for( options )
+ new( options )
+ end
+
+
+ #################################################################
+ ### I N S T A N C E M E T H O D S
+ #################################################################
+
+ ### Initialize a few instance variables before we start
+ def initialize( options )
+ @options = options
+
+ template = @options.template || 'darkfish'
+
+ 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
+
+ raise RDoc::Error, "could not find template #{template.inspect}" unless
+ template_dir
+
+ @template_dir = Pathname.new File.expand_path(template_dir)
+
+ @files = nil
+ @classes = nil
+
+ @basedir = Pathname.pwd.expand_path
+ end
+
+ ######
+ public
+ ######
+
+ # The output directory
+ attr_reader :outputdir
+
+
+ ### Output progress information if debugging is enabled
+ def debug_msg( *msg )
+ return unless $DEBUG_RDOC
+ $stderr.puts( *msg )
+ end
+
+ def class_dir
+ CLASS_DIR
+ end
+
+ def file_dir
+ FILE_DIR
+ 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 )
+ context = binding()
+
+ 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 <receiver> minutes
+ def minutes
+ return self * 60
+ end
+ alias_method :minute, :minutes
+
+ ### Returns the number of seconds in <receiver> hours
+ def hours
+ return self * 60.minutes
+ end
+ alias_method :hour, :hours
+
+ ### Returns the number of seconds in <receiver> days
+ def days
+ return self * 24.hours
+ end
+ alias_method :day, :days
+
+ ### Return the number of seconds in <receiver> weeks
+ def weeks
+ return self * 7.days
+ end
+ alias_method :week, :weeks
+
+ ### Returns the number of seconds in <receiver> fortnights
+ def fortnights
+ return self * 2.weeks
+ end
+ alias_method :fortnight, :fortnights
+
+ ### Returns the number of seconds in <receiver> months (approximate)
+ def months
+ return self * 30.days
+ end
+ alias_method :month, :months
+
+ ### Returns the number of seconds in <receiver> years (approximate)
+ def years
+ return (self * 365.25.days).to_i
+ end
+ alias_method :year, :years
+
+
+ ### Returns the Time <receiver> number of seconds before the
+ ### specified +time+. E.g., 2.hours.before( header.expiration )
+ def before( time )
+ return time - self
+ end
+
+
+ ### Returns the Time <receiver> number of seconds ago. (e.g.,
+ ### expiration > 2.hours.ago )
+ def ago
+ return self.before( ::Time.now )
+ end
+
+
+ ### Returns the Time <receiver> 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/html.rb b/lib/rdoc/generator/html.rb
deleted file mode 100644
index 3bce7b21aa..0000000000
--- a/lib/rdoc/generator/html.rb
+++ /dev/null
@@ -1,449 +0,0 @@
-require 'fileutils'
-
-require 'rdoc/generator'
-require 'rdoc/markup/to_html'
-
-##
-# We're responsible for generating all the HTML files from the object tree
-# defined in code_objects.rb. We generate:
-#
-# [files] an html file for each input file given. These
-# input files appear as objects of class
-# TopLevel
-#
-# [classes] an html file for each class or module encountered.
-# These classes are not grouped by file: if a file
-# contains four classes, we'll generate an html
-# file for the file itself, and four html files
-# for the individual classes.
-#
-# [indices] we generate three indices for files, classes,
-# and methods. These are displayed in a browser
-# like window with three index panes across the
-# top and the selected description below
-#
-# Method descriptions appear in whatever entity (file, class, or module) that
-# contains them.
-#
-# We generate files in a structure below a specified subdirectory, normally
-# +doc+.
-#
-# opdir
-# |
-# |___ files
-# | |__ per file summaries
-# |
-# |___ classes
-# |__ per class/module descriptions
-#
-# HTML is generated using the Template class.
-
-class RDoc::Generator::HTML
-
- include RDoc::Generator::MarkUp
-
- ##
- # Generator may need to return specific subclasses depending on the
- # options they are passed. Because of this we create them using a factory
-
- def self.for(options)
- RDoc::Generator::AllReferences.reset
- RDoc::Generator::Method.reset
-
- if options.all_one_file
- RDoc::Generator::HTMLInOne.new options
- else
- new options
- end
- end
-
- class << self
- protected :new
- end
-
- ##
- # Set up a new HTML generator. Basically all we do here is load up the
- # correct output temlate
-
- def initialize(options) #:not-new:
- @options = options
- load_html_template
- end
-
- ##
- # Build the initial indices and output objects
- # based on an array of TopLevel objects containing
- # the extracted information.
-
- def generate(toplevels)
- @toplevels = toplevels
- @files = []
- @classes = []
-
- write_style_sheet
- gen_sub_directories
- build_indices
- generate_html
- end
-
- private
-
- ##
- # Load up the HTML template specified in the options.
- # If the template name contains a slash, use it literally
-
- def load_html_template
- #
- # If the template is not a path, first look for it
- # in rdoc's HTML template directory. Perhaps this behavior should
- # be reversed (first try to include the template and, only if that
- # fails, try to include it in the default template directory).
- # One danger with reversing the behavior, however, is that
- # if something like require 'html' could load up an
- # unrelated file in the standard library or in a gem.
- #
- template = @options.template
-
- unless template =~ %r{/|\\} then
- template = File.join('rdoc', 'generator', @options.generator.key,
- template)
- end
-
- begin
- require template
-
- @template = self.class.const_get @options.template.upcase
- @options.template_class = @template
- rescue LoadError => e
- #
- # The template did not exist in the default template directory, so
- # see if require can find the template elsewhere (in a gem, for
- # instance).
- #
- if(e.message[template] && template != @options.template)
- template = @options.template
- retry
- end
-
- $stderr.puts "Could not find HTML template '#{template}': #{e.message}"
- exit 99
- end
- end
-
- ##
- # Write out the style sheet used by the main frames
-
- def write_style_sheet
- return unless @template.constants.include? :STYLE or
- @template.constants.include? 'STYLE'
-
- template = RDoc::TemplatePage.new @template::STYLE
-
- unless @options.css then
- open RDoc::Generator::CSS_NAME, 'w' do |f|
- values = {}
-
- if @template.constants.include? :FONTS or
- @template.constants.include? 'FONTS' then
- values["fonts"] = @template::FONTS
- end
-
- template.write_html_on(f, values)
- end
- end
- end
-
- ##
- # See the comments at the top for a description of the directory structure
-
- def gen_sub_directories
- FileUtils.mkdir_p RDoc::Generator::FILE_DIR
- FileUtils.mkdir_p RDoc::Generator::CLASS_DIR
- rescue
- $stderr.puts $!.message
- exit 1
- end
-
- def build_indices
- @files, @classes = RDoc::Generator::Context.build_indices(@toplevels,
- @options)
- end
-
- ##
- # Generate all the HTML
-
- def generate_html
- @main_url = main_url
-
- # the individual descriptions for files and classes
- gen_into(@files)
- gen_into(@classes)
-
- # and the index files
- gen_file_index
- gen_class_index
- gen_method_index
- gen_main_index
-
- # this method is defined in the template file
- values = {
- 'title_suffix' => CGI.escapeHTML("[#{@options.title}]"),
- 'charset' => @options.charset,
- 'style_url' => style_url('', @options.css),
- }
-
- @template.write_extra_pages(values) if @template.respond_to?(:write_extra_pages)
- end
-
- def gen_into(list)
- #
- # The file, class, and method lists technically should be regenerated
- # for every output file, in order that the relative links be correct
- # (we are worried here about frameless templates, which need this
- # information for every generated page). Doing this is a bit slow,
- # however. For a medium-sized gem, this increased rdoc's runtime by
- # about 5% (using the 'time' command-line utility). While this is not
- # necessarily a problem, I do not want to pessimize rdoc for large
- # projects, however, and so we only regenerate the lists when the
- # directory of the output file changes, which seems like a reasonable
- # optimization.
- #
- file_list = {}
- class_list = {}
- method_list = {}
- prev_op_dir = nil
-
- list.each do |item|
- next unless item.document_self
-
- op_file = item.path
- op_dir = File.dirname(op_file)
-
- if(op_dir != prev_op_dir)
- file_list = index_to_links op_file, @files
- class_list = index_to_links op_file, @classes
- method_list = index_to_links op_file, RDoc::Generator::Method.all_methods
- end
- prev_op_dir = op_dir
-
- FileUtils.mkdir_p op_dir
-
- open op_file, 'w' do |io|
- item.write_on io, file_list, class_list, method_list
- end
-
- file_list.clear
- class_list.clear
- method_list.clear
- end
- end
-
- def gen_file_index
- gen_an_index @files, 'Files', @template::FILE_INDEX, "fr_file_index.html"
- end
-
- def gen_class_index
- gen_an_index(@classes, 'Classes', @template::CLASS_INDEX,
- "fr_class_index.html")
- end
-
- def gen_method_index
- gen_an_index(RDoc::Generator::Method.all_methods, 'Methods',
- @template::METHOD_INDEX, "fr_method_index.html")
- end
-
- def gen_an_index(collection, title, template, filename)
- template = RDoc::TemplatePage.new @template::FR_INDEX_BODY, template
- res = []
- collection.sort.each do |f|
- if f.document_self
- res << { "href" => f.path, "name" => f.index_name }
- end
- end
-
- values = {
- "entries" => res,
- 'title' => CGI.escapeHTML("#{title} [#{@options.title}]"),
- 'list_title' => CGI.escapeHTML(title),
- 'index_url' => @main_url,
- 'charset' => @options.charset,
- 'style_url' => style_url('', @options.css),
- }
-
- open filename, 'w' do |f|
- template.write_html_on(f, values)
- end
- end
-
- ##
- # The main index page is mostly a template frameset, but includes the
- # initial page. If the <tt>--main</tt> option was given, we use this as
- # our main page, otherwise we use the first file specified on the command
- # line.
-
- def gen_main_index
- if @template.const_defined? :FRAMELESS then
- #
- # If we're using a template without frames, then just redirect
- # to it from index.html.
- #
- # One alternative to this, expanding the main page's template into
- # index.html, is tricky because the relative URLs will be different
- # (since index.html is located in at the site's root,
- # rather than within a files or a classes subdirectory).
- #
- open 'index.html', 'w' do |f|
- f.puts(%{<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">})
- f.puts(%{<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
- lang="en">})
- f.puts(%{<head>})
- f.puts(%{<title>#{CGI.escapeHTML(@options.title)}</title>})
- f.puts(%{<meta http-equiv="refresh" content="0; url=#{@main_url}" />})
- f.puts(%{</head>})
- f.puts(%{<body></body>})
- f.puts(%{</html>})
- end
- else
- main = RDoc::TemplatePage.new @template::INDEX
-
- open 'index.html', 'w' do |f|
- style_url = style_url '', @options.css
-
- classes = @classes.sort.map { |klass| klass.value_hash }
-
- values = {
- 'initial_page' => @main_url,
- 'style_url' => style_url('', @options.css),
- 'title' => CGI.escapeHTML(@options.title),
- 'charset' => @options.charset,
- 'classes' => classes,
- }
-
- values['inline_source'] = @options.inline_source
-
- main.write_html_on f, values
- end
- end
- end
-
- def index_to_links(output_path, collection)
- collection.sort.map do |f|
- next unless f.document_self
- { "href" => RDoc::Markup::ToHtml.gen_relative_url(output_path, f.path),
- "name" => f.index_name }
- end.compact
- end
-
- ##
- # Returns the url of the main page
-
- def main_url
- main_page = @options.main_page
-
- #
- # If a main page has been specified (--main), then search for it
- # in the AllReferences array. This allows either files or classes
- # to be used for the main page.
- #
- if main_page then
- main_page_ref = RDoc::Generator::AllReferences[main_page]
-
- if main_page_ref then
- return main_page_ref.path
- else
- $stderr.puts "Could not find main page #{main_page}"
- end
- end
-
- #
- # No main page has been specified, so just use the README.
- #
- @files.each do |file|
- if file.name =~ /^README/ then
- return file.path
- end
- end
-
- #
- # There's no README (shame! shame!). Just use the first file
- # that will be documented.
- #
- @files.each do |file|
- if file.document_self then
- return file.path
- end
- end
-
- #
- # There are no files to be documented... Something seems very wrong.
- #
- raise RDoc::Error, "Couldn't find anything to document (perhaps :stopdoc: has been used in all classes)!"
- end
- private :main_url
-
-end
-
-class RDoc::Generator::HTMLInOne < RDoc::Generator::HTML
-
- def initialize(*args)
- super
- end
-
- ##
- # Build the initial indices and output objects
- # based on an array of TopLevel objects containing
- # the extracted information.
-
- def generate(info)
- @toplevels = info
- @hyperlinks = {}
-
- build_indices
- generate_xml
- end
-
- ##
- # Generate:
- #
- # * a list of RDoc::Generator::File objects for each TopLevel object.
- # * a list of RDoc::Generator::Class objects for each first level
- # class or module in the TopLevel objects
- # * a complete list of all hyperlinkable terms (file,
- # class, module, and method names)
-
- def build_indices
- @files, @classes = RDoc::Generator::Context.build_indices(@toplevels,
- @options)
- end
-
- ##
- # Generate all the HTML. For the one-file case, we generate
- # all the information in to one big hash
-
- def generate_xml
- values = {
- 'charset' => @options.charset,
- 'files' => gen_into(@files),
- 'classes' => gen_into(@classes),
- 'title' => CGI.escapeHTML(@options.title),
- }
-
- template = RDoc::TemplatePage.new @template::ONE_PAGE
-
- if @options.op_name
- opfile = open @options.op_name, 'w'
- else
- opfile = $stdout
- end
- template.write_html_on(opfile, values)
- end
-
- def gen_into(list)
- res = []
- list.each do |item|
- res << item.value_hash
- end
- res
- end
-end
diff --git a/lib/rdoc/generator/html/common.rb b/lib/rdoc/generator/html/common.rb
deleted file mode 100644
index b25f009a72..0000000000
--- a/lib/rdoc/generator/html/common.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# The templates require further refactoring. In particular,
-# * Some kind of HTML generation library should be used.
-#
-# Also, all of the templates require some TLC from a designer.
-#
-# Right now, this file contains some constants that are used by all
-# of the templates.
-#
-module RDoc::Generator::HTML::Common
- XHTML_STRICT_PREAMBLE = <<-EOF
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-EOF
-
- XHTML_FRAME_PREAMBLE = <<-EOF
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
-EOF
-
- HTML_ELEMENT = <<-EOF
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-EOF
-end
diff --git a/lib/rdoc/generator/html/frameless.rb b/lib/rdoc/generator/html/frameless.rb
deleted file mode 100644
index 0375fee313..0000000000
--- a/lib/rdoc/generator/html/frameless.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-require 'rdoc/generator/html/html'
-
-##
-# = CSS2 RDoc HTML template
-#
-# This is a template for RDoc that uses XHTML 1.0 Strict and dictates a
-# bit more of the appearance of the output to cascading stylesheets than the
-# default. It was designed for clean inline code display, and uses DHTMl to
-# toggle the visbility of each method's source with each click on the '[source]'
-# link.
-#
-# Frameless basically is the html template without frames.
-#
-# == Authors
-#
-# * Michael Granger <ged@FaerieMUD.org>
-#
-# Copyright (c) 2002, 2003 The FaerieMUD Consortium. Some rights reserved.
-#
-# This work is licensed under the Creative Commons Attribution License. To view
-# a copy of this license, visit http://creativecommons.org/licenses/by/1.0/ or
-# send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California
-# 94305, USA.
-
-module RDoc::Generator::HTML::FRAMELESS
-
- FRAMELESS = true
-
- FONTS = RDoc::Generator::HTML::HTML::FONTS
-
- STYLE = RDoc::Generator::HTML::HTML::STYLE
-
- HEADER = RDoc::Generator::HTML::HTML::HEADER
-
- FOOTER = <<-EOF
- <div id="popupmenu" class="index">
- <br />
- <h1 class="index-entries section-bar">Files</h1>
- <ul>
-<% values["file_list"].each do |file| %>
- <li><a href="<%= file["href"] %>"><%= file["name"] %></a></li>
-<% end %>
- </ul>
-
- <br />
- <h1 class="index-entries section-bar">Classes</h1>
- <ul>
-<% values["class_list"].each do |klass| %>
- <li><a href="<%= klass["href"] %>"><%= klass["name"] %></a></li>
-<% end %>
- </ul>
-
- <br />
- <h1 class="index-entries section-bar">Methods</h1>
- <ul>
-<% values["method_list"].each do |method| %>
- <li><a href="<%= method["href"] %>"><%= method["name"] %></a></li>
-<% end %>
- </ul>
- </div>
-</body>
-</html>
- EOF
-
- FILE_PAGE = RDoc::Generator::HTML::HTML::FILE_PAGE
-
- CLASS_PAGE = RDoc::Generator::HTML::HTML::CLASS_PAGE
-
- METHOD_LIST = RDoc::Generator::HTML::HTML::METHOD_LIST
-
- BODY = HEADER + %{
-
-<%= template_include %> <!-- banner header -->
-
- <div id="bodyContent">
-
-} + METHOD_LIST + %{
-
- </div>
-
-} + FOOTER
-
- SRC_PAGE = RDoc::Generator::HTML::HTML::SRC_PAGE
-
- FR_INDEX_BODY = RDoc::Generator::HTML::HTML::FR_INDEX_BODY
-
- FILE_INDEX = RDoc::Generator::HTML::HTML::FILE_INDEX
-
- CLASS_INDEX = RDoc::Generator::HTML::HTML::CLASS_INDEX
-
- METHOD_INDEX = RDoc::Generator::HTML::HTML::METHOD_INDEX
-end
diff --git a/lib/rdoc/generator/html/hefss.rb b/lib/rdoc/generator/html/hefss.rb
deleted file mode 100644
index 01425a4556..0000000000
--- a/lib/rdoc/generator/html/hefss.rb
+++ /dev/null
@@ -1,150 +0,0 @@
-require 'rdoc/generator/html'
-require 'rdoc/generator/html/kilmerfactory'
-
-module RDoc::Generator::HTML::HEFSS
-
- FONTS = "Verdana, Arial, Helvetica, sans-serif"
-
- CENTRAL_STYLE = <<-EOF
-body,p { font-family: <%= values["fonts"] %>;
- color: #000040; background: #BBBBBB;
-}
-
-td { font-family: <%= values["fonts"] %>;
- color: #000040;
-}
-
-.attr-rw { font-size: small; color: #444488 }
-
-.title-row {color: #eeeeff;
- background: #BBBBDD;
-}
-
-.big-title-font { color: white;
- font-family: <%= values["fonts"] %>;
- font-size: large;
- height: 50px}
-
-.small-title-font { color: purple;
- font-family: <%= values["fonts"] %>;
- font-size: small; }
-
-.aqua { color: purple }
-
-#diagram img {
- border: 0;
-}
-
-.method-name, attr-name {
- font-family: monospace; font-weight: bold;
-}
-
-.tablesubtitle {
- width: 100%;
- margin-top: 1ex;
- margin-bottom: .5ex;
- padding: 5px 0px 5px 20px;
- font-size: large;
- color: purple;
- background: #BBBBCC;
-}
-
-.tablesubsubtitle {
- width: 100%;
- margin-top: 1ex;
- margin-bottom: .5ex;
- padding: 5px 0px 5px 20px;
- font-size: medium;
- color: white;
- background: #BBBBCC;
-}
-
-.name-list {
- font-family: monospace;
- margin-left: 40px;
- margin-bottom: 2ex;
- line-height: 140%;
-}
-
-.description {
- margin-left: 40px;
- margin-bottom: 2ex;
- line-height: 140%;
-}
-
-.methodtitle {
- font-size: medium;
- text_decoration: none;
- padding: 3px 3px 3px 20px;
- color: #0000AA;
-}
-
-.ruby-comment { color: green; font-style: italic }
-.ruby-constant { color: #4433aa; font-weight: bold; }
-.ruby-identifier { color: #222222; }
-.ruby-ivar { color: #2233dd; }
-.ruby-keyword { color: #3333FF; font-weight: bold }
-.ruby-node { color: #777777; }
-.ruby-operator { color: #111111; }
-.ruby-regexp { color: #662222; }
-.ruby-value { color: #662222; font-style: italic }
-
-.srcbut { float: right }
- EOF
-
- INDEX_STYLE = <<-EOF
-body {
- background-color: #bbbbbb;
- font-family: #{FONTS};
- font-size: 11px;
- font-style: normal;
- line-height: 14px;
- color: #000040;
-}
-
-div.banner {
- background: #bbbbcc;
- color: white;
- padding: 1;
- margin: 0;
- font-size: 90%;
- font-weight: bold;
- line-height: 1.1;
- text-align: center;
- width: 100%;
-}
-EOF
-
- FACTORY = RDoc::Generator::HTML::
- KilmerFactory.new(:central_css => CENTRAL_STYLE,
- :index_css => INDEX_STYLE,
- :method_list_heading => "Subroutines and Functions",
- :class_and_module_list_heading => "Classes and Modules",
- :attribute_list_heading => "Arguments")
-
- STYLE = FACTORY.get_STYLE()
-
- METHOD_LIST = FACTORY.get_METHOD_LIST()
-
- BODY = FACTORY.get_BODY()
-
- FILE_PAGE = FACTORY.get_FILE_PAGE()
-
- CLASS_PAGE = FACTORY.get_CLASS_PAGE()
-
- SRC_PAGE = FACTORY.get_SRC_PAGE()
-
- FR_INDEX_BODY = FACTORY.get_FR_INDEX_BODY()
-
- FILE_INDEX = FACTORY.get_FILE_INDEX()
-
- CLASS_INDEX = FACTORY.get_CLASS_INDEX()
-
- METHOD_INDEX = FACTORY.get_METHOD_INDEX()
-
- INDEX = FACTORY.get_INDEX()
-
- def self.write_extra_pages(values)
- FACTORY.write_extra_pages(values)
- end
-end
diff --git a/lib/rdoc/generator/html/html.rb b/lib/rdoc/generator/html/html.rb
deleted file mode 100644
index 5e5b4d5531..0000000000
--- a/lib/rdoc/generator/html/html.rb
+++ /dev/null
@@ -1,769 +0,0 @@
-require 'rdoc/generator/html'
-require 'rdoc/generator/html/common'
-
-##
-# = CSS2 RDoc HTML template
-#
-# This is a template for RDoc that uses XHTML 1.0 Strict and dictates a
-# bit more of the appearance of the output to cascading stylesheets than the
-# default. It was designed for clean inline code display, and uses DHTMl to
-# toggle the visibility of each method's source with each click on the
-# '[source]' link.
-#
-# This template *also* forms the basis of the frameless template.
-#
-# == Authors
-#
-# * Michael Granger <ged@FaerieMUD.org>
-#
-# Copyright (c) 2002, 2003 The FaerieMUD Consortium. Some rights reserved.
-#
-# This work is licensed under the Creative Commons Attribution License. To
-# view a copy of this license, visit
-# http://creativecommons.org/licenses/by/1.0/ or send a letter to Creative
-# Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
-
-module RDoc::Generator::HTML::HTML
-
- include RDoc::Generator::HTML::Common
-
- FONTS = "Verdana,Arial,Helvetica,sans-serif"
-
- STYLE = <<-EOF
-body {
- font-family: #{FONTS};
- font-size: 90%;
- margin: 0;
- margin-left: 40px;
- padding: 0;
- background: white;
- color: black;
-}
-
-h1, h2, h3, h4 {
- margin: 0;
- background: transparent;
-}
-
-h1 {
- font-size: 150%;
-}
-
-h2,h3,h4 {
- margin-top: 1em;
-}
-
-:link, :visited {
- background: #eef;
- color: #039;
- text-decoration: none;
-}
-
-:link:hover, :visited:hover {
- background: #039;
- color: #eef;
-}
-
-/* Override the base stylesheet's Anchor inside a table cell */
-td > :link, td > :visited {
- background: transparent;
- color: #039;
- text-decoration: none;
-}
-
-/* and inside a section title */
-.section-title > :link, .section-title > :visited {
- background: transparent;
- color: #eee;
- text-decoration: none;
-}
-
-/* === Structural elements =================================== */
-
-.index {
- margin: 0;
- margin-left: -40px;
- padding: 0;
- font-size: 90%;
-}
-
-.index :link, .index :visited {
- margin-left: 0.7em;
-}
-
-.index .section-bar {
- margin-left: 0px;
- padding-left: 0.7em;
- background: #ccc;
- font-size: small;
-}
-
-#classHeader, #fileHeader {
- width: auto;
- color: white;
- padding: 0.5em 1.5em 0.5em 1.5em;
- margin: 0;
- margin-left: -40px;
- border-bottom: 3px solid #006;
-}
-
-#classHeader :link, #fileHeader :link,
-#classHeader :visited, #fileHeader :visited {
- background: inherit;
- color: white;
-}
-
-#classHeader td, #fileHeader td {
- background: inherit;
- color: white;
-}
-
-#fileHeader {
- background: #057;
-}
-
-#classHeader {
- background: #048;
-}
-
-.class-name-in-header {
- font-size: 180%;
- font-weight: bold;
-}
-
-#bodyContent {
- padding: 0 1.5em 0 1.5em;
-}
-
-#description {
- padding: 0.5em 1.5em;
- background: #efefef;
- border: 1px dotted #999;
-}
-
-#description h1, #description h2, #description h3,
-#description h4, #description h5, #description h6 {
- color: #125;
- background: transparent;
-}
-
-#validator-badges {
- text-align: center;
-}
-
-#validator-badges img {
- border: 0;
-}
-
-#copyright {
- color: #333;
- background: #efefef;
- font: 0.75em sans-serif;
- margin-top: 5em;
- margin-bottom: 0;
- padding: 0.5em 2em;
-}
-
-/* === Classes =================================== */
-
-table.header-table {
- color: white;
- font-size: small;
-}
-
-.type-note {
- font-size: small;
- color: #dedede;
-}
-
-.section-bar {
- color: #333;
- border-bottom: 1px solid #999;
- margin-left: -20px;
-}
-
-.section-title {
- background: #79a;
- color: #eee;
- padding: 3px;
- margin-top: 2em;
- margin-left: -30px;
- border: 1px solid #999;
-}
-
-.top-aligned-row {
- vertical-align: top
-}
-
-.bottom-aligned-row {
- vertical-align: bottom
-}
-
-#diagram img {
- border: 0;
-}
-
-/* --- Context section classes ----------------------- */
-
-.context-row { }
-
-.context-item-name {
- font-family: monospace;
- font-weight: bold;
- color: black;
-}
-
-.context-item-value {
- font-size: small;
- color: #448;
-}
-
-.context-item-desc {
- color: #333;
- padding-left: 2em;
-}
-
-/* --- Method classes -------------------------- */
-
-.method-detail {
- background: #efefef;
- padding: 0;
- margin-top: 0.5em;
- margin-bottom: 1em;
- border: 1px dotted #ccc;
-}
-
-.method-heading {
- color: black;
- background: #ccc;
- border-bottom: 1px solid #666;
- padding: 0.2em 0.5em 0 0.5em;
-}
-
-.method-signature {
- color: black;
- background: inherit;
-}
-
-.method-name {
- font-weight: bold;
-}
-
-.method-args {
- font-style: italic;
-}
-
-.method-description {
- padding: 0 0.5em 0 0.5em;
-}
-
-/* --- Source code sections -------------------- */
-
-:link.source-toggle, :visited.source-toggle {
- font-size: 90%;
-}
-
-div.method-source-code {
- background: #262626;
- color: #ffdead;
- margin: 1em;
- padding: 0.5em;
- border: 1px dashed #999;
- overflow: auto;
-}
-
-div.method-source-code pre {
- color: #ffdead;
-}
-
-/* --- Ruby keyword styles --------------------- */
-
-.standalone-code {
- background: #221111;
- color: #ffdead;
- overflow: auto;
-}
-
-.ruby-constant {
- color: #7fffd4;
- background: transparent;
-}
-
-.ruby-keyword {
- color: #00ffff;
- background: transparent;
-}
-
-.ruby-ivar {
- color: #eedd82;
- background: transparent;
-}
-
-.ruby-operator {
- color: #00ffee;
- background: transparent;
-}
-
-.ruby-identifier {
- color: #ffdead;
- background: transparent;
-}
-
-.ruby-node {
- color: #ffa07a;
- background: transparent;
-}
-
-.ruby-comment {
- color: #b22222;
- font-weight: bold;
- background: transparent;
-}
-
-.ruby-regexp {
- color: #ffa07a;
- background: transparent;
-}
-
-.ruby-value {
- color: #7fffd4;
- background: transparent;
-}
-EOF
-
-
-#####################################################################
-### H E A D E R T E M P L A T E
-#####################################################################
-
- HEADER = XHTML_STRICT_PREAMBLE + HTML_ELEMENT + <<-EOF
-<head>
- <title><%= values["title"] %></title>
- <meta http-equiv="Content-Type" content="text/html; charset=<%= values["charset"] %>" />
- <meta http-equiv="Content-Script-Type" content="text/javascript" />
- <link rel="stylesheet" href="<%= values["style_url"] %>" type="text/css" media="screen" />
- <script type="text/javascript">
- // <![CDATA[
-
- function popupCode( url ) {
- window.open(url, "Code", "resizable=yes,scrollbars=yes,toolbar=no,status=no,height=150,width=400")
- }
-
- function toggleCode( id ) {
- if ( document.getElementById )
- elem = document.getElementById( id );
- else if ( document.all )
- elem = eval( "document.all." + id );
- else
- return false;
-
- elemStyle = elem.style;
-
- if ( elemStyle.display != "block" ) {
- elemStyle.display = "block"
- } else {
- elemStyle.display = "none"
- }
-
- return true;
- }
-
- // Make codeblocks hidden by default
- document.writeln( "<style type=\\"text/css\\">div.method-source-code { display: none }<\\/style>" )
-
- // ]]>
- </script>
-
-</head>
-<body>
-EOF
-
-#####################################################################
-### F O O T E R T E M P L A T E
-#####################################################################
-
- FOOTER = <<-EOF
-<div id="validator-badges">
- <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
-</div>
-
-</body>
-</html>
- EOF
-
-
-#####################################################################
-### F I L E P A G E H E A D E R T E M P L A T E
-#####################################################################
-
- FILE_PAGE = <<-EOF
- <div id="fileHeader">
- <h1><%= values["short_name"] %></h1>
- <table class="header-table">
- <tr class="top-aligned-row">
- <td><strong>Path:</strong></td>
- <td><%= values["full_path"] %>
-<% if values["cvsurl"] then %>
- &nbsp;(<a href="<%= values["cvsurl"] %>"><acronym title="Concurrent Versioning System">CVS</acronym></a>)
-<% end %>
- </td>
- </tr>
- <tr class="top-aligned-row">
- <td><strong>Last Update:</strong></td>
- <td><%= values["dtm_modified"] %></td>
- </tr>
- </table>
- </div>
- EOF
-
-#####################################################################
-### C L A S S P A G E H E A D E R T E M P L A T E
-#####################################################################
-
- CLASS_PAGE = <<-EOF
- <div id="classHeader">
- <table class="header-table">
- <tr class="top-aligned-row">
- <td><strong><%= values["classmod"] %></strong></td>
- <td class="class-name-in-header"><%= values["full_name"] %></td>
- </tr>
- <tr class="top-aligned-row">
- <td><strong>In:</strong></td>
- <td>
-<% values["infiles"].each do |infiles| %>
-<% if infiles["full_path_url"] then %>
- <a href="<%= infiles["full_path_url"] %>">
-<% end %>
- <%= infiles["full_path"] %>
-<% if infiles["full_path_url"] then %>
- </a>
-<% end %>
-<% if infiles["cvsurl"] then %>
- &nbsp;(<a href="<%= infiles["cvsurl"] %>"><acronym title="Concurrent Versioning System">CVS</acronym></a>)
-<% end %>
- <br />
-<% end %><%# values["infiles"] %>
- </td>
- </tr>
-
-<% if values["parent"] then %>
- <tr class="top-aligned-row">
- <td><strong>Parent:</strong></td>
- <td>
-<% if values["par_url"] then %>
- <a href="<%= values["par_url"] %>">
-<% end %>
- <%= values["parent"] %>
-<% if values["par_url"] then %>
- </a>
-<% end %>
- </td>
- </tr>
-<% end %>
- </table>
- </div>
- EOF
-
-#####################################################################
-### M E T H O D L I S T T E M P L A T E
-#####################################################################
-
- METHOD_LIST = <<-EOF
- <div id="contextContent">
-<% if values["diagram"] then %>
- <div id="diagram">
- <%= values["diagram"] %>
- </div>
-<% end
-
- if values["description"] then %>
- <div id="description">
- <%= values["description"] %>
- </div>
-<% end
-
- if values["requires"] then %>
- <div id="requires-list">
- <h3 class="section-bar">Required files</h3>
-
- <div class="name-list">
-<% values["requires"].each do |requires| %>
- <%= href requires["aref"], requires["name"] %>&nbsp;&nbsp;
-<% end %><%# values["requires"] %>
- </div>
- </div>
-<% end
-
- if values["toc"] then %>
- <div id="contents-list">
- <h3 class="section-bar">Contents</h3>
- <ul>
-<% values["toc"].each do |toc| %>
- <li><a href="#<%= toc["href"] %>"><%= toc["secname"] %></a></li>
-<% end %><%# values["toc"] %>
- </ul>
-<% end %>
- </div>
-
-<% if values["methods"] then %>
- <div id="method-list">
- <h3 class="section-bar">Methods</h3>
-
- <div class="name-list">
-<% values["methods"].each do |methods| %>
- <%= href methods["aref"], methods["name"] %>&nbsp;&nbsp;
-<% end %><%# values["methods"] %>
- </div>
- </div>
-<% end %>
- </div>
-
- <!-- if includes -->
-<% if values["includes"] then %>
- <div id="includes">
- <h3 class="section-bar">Included Modules</h3>
-
- <div id="includes-list">
-<% values["includes"].each do |includes| %>
- <span class="include-name"><%= href includes["aref"], includes["name"] %></span>
-<% end %><%# values["includes"] %>
- </div>
- </div>
-<% end
-
- values["sections"].each do |sections| %>
- <div id="section">
-<% if sections["sectitle"] then %>
- <h2 class="section-title"><a name="<%= sections["secsequence"] %>"><%= sections["sectitle"] %></a></h2>
-<% if sections["seccomment"] then %>
- <div class="section-comment">
- <%= sections["seccomment"] %>
- </div>
-<% end
- end
-
- if sections["classlist"] then %>
- <div id="class-list">
- <h3 class="section-bar">Classes and Modules</h3>
-
- <%= sections["classlist"] %>
- </div>
-<% end
-
- if sections["constants"] then %>
- <div id="constants-list">
- <h3 class="section-bar">Constants</h3>
-
- <div class="name-list">
- <table summary="Constants">
-<% sections["constants"].each do |constants| %>
- <tr class="top-aligned-row context-row">
- <td class="context-item-name"><%= constants["name"] %></td>
- <td>=</td>
- <td class="context-item-value"><%= constants["value"] %></td>
-<% if constants["desc"] then %>
- <td>&nbsp;</td>
- <td class="context-item-desc"><%= constants["desc"] %></td>
-<% end %>
- </tr>
-<% end %><%# sections["constants"] %>
- </table>
- </div>
- </div>
-<% end
-
- if sections["aliases"] then %>
- <div id="aliases-list">
- <h3 class="section-bar">External Aliases</h3>
-
- <div class="name-list">
- <table summary="aliases">
-<% sections["aliases"].each do |aliases| %>
- <tr class="top-aligned-row context-row">
- <td class="context-item-name"><%= aliases["old_name"] %></td>
- <td>-&gt;</td>
- <td class="context-item-value"><%= aliases["new_name"] %></td>
- </tr>
-<% if aliases["desc"] then %>
- <tr class="top-aligned-row context-row">
- <td>&nbsp;</td>
- <td colspan="2" class="context-item-desc"><%= aliases["desc"] %></td>
- </tr>
-<% end
- end %><%# sections["aliases"] %>
- </table>
- </div>
- </div>
-<% end %>
-
-<% if sections["attributes"] then %>
- <div id="attribute-list">
- <h3 class="section-bar">Attributes</h3>
-
- <div class="name-list">
- <table>
-<% sections["attributes"].each do |attribute| %>
- <tr class="top-aligned-row context-row">
- <td class="context-item-name"><%= attribute["name"] %></td>
-<% if attribute["rw"] then %>
- <td class="context-item-value">&nbsp;[<%= attribute["rw"] %>]&nbsp;</td>
-<% end
- unless attribute["rw"] then %>
- <td class="context-item-value">&nbsp;&nbsp;</td>
-<% end %>
- <td class="context-item-desc"><%= attribute["a_desc"] %></td>
- </tr>
-<% end %><%# sections["attributes"] %>
- </table>
- </div>
- </div>
-<% end %>
-
- <!-- if method_list -->
-<% if sections["method_list"] then %>
- <div id="methods">
-<% sections["method_list"].each do |method_list|
- if method_list["methods"] then %>
- <h3 class="section-bar"><%= method_list["type"] %> <%= method_list["category"] %> methods</h3>
-
-<% method_list["methods"].each do |methods| %>
- <div id="method-<%= methods["aref"] %>" class="method-detail">
- <a name="<%= methods["aref"] %>"></a>
-
- <div class="method-heading">
-<% if methods["codeurl"] then %>
- <a href="<%= methods["codeurl"] %>" target="Code" class="method-signature"
- onclick="popupCode('<%= methods["codeurl"] %>');return false;">
-<% end
- if methods["sourcecode"] then %>
- <a href="#<%= methods["aref"] %>" class="method-signature">
-<% end
- if methods["callseq"] then %>
- <span class="method-name"><%= methods["callseq"] %></span>
-<% end
- unless methods["callseq"] then %>
- <span class="method-name"><%= methods["name"] %></span><span class="method-args"><%= methods["params"] %></span>
-<% end
- if methods["codeurl"] then %>
- </a>
-<% end
- if methods["sourcecode"] then %>
- </a>
-<% end %>
- </div>
-
- <div class="method-description">
-<% if methods["m_desc"] then %>
- <%= methods["m_desc"] %>
-<% end
- if methods["sourcecode"] then %>
- <p><a class="source-toggle" href="#"
- onclick="toggleCode('<%= methods["aref"] %>-source');return false;">[Source]</a></p>
- <div class="method-source-code" id="<%= methods["aref"] %>-source">
-<pre>
-<%= methods["sourcecode"] %>
-</pre>
- </div>
-<% end %>
- </div>
- </div>
-
-<% end %><%# method_list["methods"] %><%
- end
- end %><%# sections["method_list"] %>
-
- </div>
-<% end %>
-<% end %><%# values["sections"] %>
- EOF
-
-#####################################################################
-### B O D Y T E M P L A T E
-#####################################################################
-
- BODY = HEADER + %{
-
-<%= template_include %> <!-- banner header -->
-
- <div id="bodyContent">
-
-} + METHOD_LIST + %{
-
- </div>
-
-} + FOOTER
-
-#####################################################################
-### S O U R C E C O D E T E M P L A T E
-#####################################################################
-
- SRC_PAGE = XHTML_STRICT_PREAMBLE + HTML_ELEMENT + <<-EOF
-<head>
- <title><%= values["title"] %></title>
- <meta http-equiv="Content-Type" content="text/html; charset=<%= values["charset"] %>" />
- <link rel="stylesheet" href="<%= values["style_url"] %>" type="text/css" media="screen" />
-</head>
-<body class="standalone-code">
- <pre><%= values["code"] %></pre>
-</body>
-</html>
- EOF
-
-
-#####################################################################
-### I N D E X F I L E T E M P L A T E S
-#####################################################################
-
- FR_INDEX_BODY = %{<%= template_include %>}
-
- FILE_INDEX = XHTML_STRICT_PREAMBLE + HTML_ELEMENT + <<-EOF
-<!--
-
- <%= values["title"] %>
-
- -->
-<head>
- <title><%= values["title"] %></title>
- <meta http-equiv="Content-Type" content="text/html; charset=<%= values["charset"] %>" />
- <link rel="stylesheet" href="<%= values["style_url"] %>" type="text/css" />
- <base target="docwin" />
-</head>
-<body>
-<div class="index">
- <h1 class="section-bar"><%= values["list_title"] %></h1>
- <div id="index-entries">
-<% values["entries"].each do |entries| %>
- <a href="<%= entries["href"] %>"><%= entries["name"] %></a><br />
-<% end %><%# values["entries"] %>
- </div>
-</div>
-</body>
-</html>
- EOF
-
- CLASS_INDEX = FILE_INDEX
- METHOD_INDEX = FILE_INDEX
-
- INDEX = XHTML_FRAME_PREAMBLE + HTML_ELEMENT + <<-EOF
-<!--
-
- <%= values["title"] %>
-
- -->
-<head>
- <title><%= values["title"] %></title>
- <meta http-equiv="Content-Type" content="text/html; charset=<%= values["charset"] %>" />
-</head>
-<frameset rows="20%, 80%">
- <frameset cols="25%,35%,45%">
- <frame src="fr_file_index.html" title="Files" name="Files" />
- <frame src="fr_class_index.html" name="Classes" />
- <frame src="fr_method_index.html" name="Methods" />
- </frameset>
- <frame src="<%= values["initial_page"] %>" name="docwin" />
-</frameset>
-</html>
- EOF
-
-end
-
diff --git a/lib/rdoc/generator/html/kilmer.rb b/lib/rdoc/generator/html/kilmer.rb
deleted file mode 100644
index 233a9259a2..0000000000
--- a/lib/rdoc/generator/html/kilmer.rb
+++ /dev/null
@@ -1,151 +0,0 @@
-require 'rdoc/generator/html'
-require 'rdoc/generator/html/kilmerfactory'
-
-module RDoc::Generator::HTML::KILMER
-
- FONTS = "Verdana, Arial, Helvetica, sans-serif"
-
- CENTRAL_STYLE = <<-EOF
-body,td,p { font-family: <%= values["fonts"] %>;
- color: #000040;
-}
-
-.attr-rw { font-size: xx-small; color: #444488 }
-
-.title-row { background-color: #CCCCFF;
- color: #000010;
-}
-
-.big-title-font {
- color: black;
- font-weight: bold;
- font-family: <%= values["fonts"] %>;
- font-size: large;
- height: 60px;
- padding: 10px 3px 10px 3px;
-}
-
-.small-title-font { color: black;
- font-family: <%= values["fonts"] %>;
- font-size:10; }
-
-.aqua { color: black }
-
-#diagram img {
- border: 0;
-}
-
-.method-name, .attr-name {
- font-family: font-family: <%= values["fonts"] %>;
- font-weight: bold;
- font-size: small;
- margin-left: 20px;
- color: #000033;
-}
-
-.tablesubtitle, .tablesubsubtitle {
- width: 100%;
- margin-top: 1ex;
- margin-bottom: .5ex;
- padding: 5px 0px 5px 3px;
- font-size: large;
- color: black;
- background-color: #CCCCFF;
- border: thin;
-}
-
-.name-list {
- margin-left: 5px;
- margin-bottom: 2ex;
- line-height: 105%;
-}
-
-.description {
- margin-left: 5px;
- margin-bottom: 2ex;
- line-height: 105%;
- font-size: small;
-}
-
-.methodtitle {
- font-size: small;
- font-weight: bold;
- text-decoration: none;
- color: #000033;
- background: #ccc;
-}
-
-.srclink {
- font-size: small;
- font-weight: bold;
- text-decoration: none;
- color: #0000DD;
- background-color: white;
-}
-
-.srcbut { float: right }
-
-.ruby-comment { color: green; font-style: italic }
-.ruby-constant { color: #4433aa; font-weight: bold; }
-.ruby-identifier { color: #222222; }
-.ruby-ivar { color: #2233dd; }
-.ruby-keyword { color: #3333FF; font-weight: bold }
-.ruby-node { color: #777777; }
-.ruby-operator { color: #111111; }
-.ruby-regexp { color: #662222; }
-.ruby-value { color: #662222; font-style: italic }
- EOF
-
- INDEX_STYLE = <<-EOF
-body {
- background-color: #ddddff;
- font-family: #{FONTS};
- font-size: 11px;
- font-style: normal;
- line-height: 14px;
- color: #000040;
-}
-
-div.banner {
- background: #0000aa;
- color: white;
- padding: 1;
- margin: 0;
- font-size: 90%;
- font-weight: bold;
- line-height: 1.1;
- text-align: center;
- width: 100%;
-}
-EOF
-
- FACTORY = RDoc::Generator::HTML::
- KilmerFactory.new(:central_css => CENTRAL_STYLE,
- :index_css => INDEX_STYLE)
-
- STYLE = FACTORY.get_STYLE()
-
- METHOD_LIST = FACTORY.get_METHOD_LIST()
-
- BODY = FACTORY.get_BODY()
-
- FILE_PAGE = FACTORY.get_FILE_PAGE()
-
- CLASS_PAGE = FACTORY.get_CLASS_PAGE()
-
- SRC_PAGE = FACTORY.get_SRC_PAGE()
-
- FR_INDEX_BODY = FACTORY.get_FR_INDEX_BODY()
-
- FILE_INDEX = FACTORY.get_FILE_INDEX()
-
- CLASS_INDEX = FACTORY.get_CLASS_INDEX()
-
- METHOD_INDEX = FACTORY.get_METHOD_INDEX()
-
- INDEX = FACTORY.get_INDEX()
-
- def self.write_extra_pages(values)
- FACTORY.write_extra_pages(values)
- end
-end
diff --git a/lib/rdoc/generator/html/kilmerfactory.rb b/lib/rdoc/generator/html/kilmerfactory.rb
deleted file mode 100644
index 1407840839..0000000000
--- a/lib/rdoc/generator/html/kilmerfactory.rb
+++ /dev/null
@@ -1,427 +0,0 @@
-require 'rdoc/generator/html'
-require 'rdoc/generator/html/common'
-
-#
-# This class generates Kilmer-style templates. Right now,
-# rdoc is shipped with two such templates:
-# * kilmer
-# * hefss
-#
-# Kilmer-style templates use frames. The left side of the page has
-# three frames stacked on top of each other: one lists
-# files, one lists classes, and one lists methods. If source code
-# is not inlined, an additional frame runs across the bottom of
-# the page and will be used to display method source code.
-# The central (and largest frame) display class and file
-# pages.
-#
-# The constructor of this class accepts a Hash containing stylistic
-# attributes. Then, a get_BLAH instance method of this class returns a
-# value for the template's BLAH constant. get_BODY, for instance, returns
-# the value of the template's BODY constant.
-#
-class RDoc::Generator::HTML::KilmerFactory
-
- include RDoc::Generator::HTML::Common
-
- #
- # The contents of the stylesheet that should be used for the
- # central frame (for the class and file pages).
- #
- # This must be specified in the Hash passed to the constructor.
- #
- attr_reader :central_css
-
- #
- # The contents of the stylesheet that should be used for the
- # index pages.
- #
- # This must be specified in the Hash passed to the constructor.
- #
- attr_reader :index_css
-
- #
- # The heading that should be displayed before listing methods.
- #
- # If not supplied, this defaults to "Methods".
- #
- attr_reader :method_list_heading
-
- #
- # The heading that should be displayed before listing classes and
- # modules.
- #
- # If not supplied, this defaults to "Classes and Modules".
- #
- attr_reader :class_and_module_list_heading
-
- #
- # The heading that should be displayed before listing attributes.
- #
- # If not supplied, this defaults to "Attributes".
- #
- attr_reader :attribute_list_heading
-
- #
- # ====Description:
- # This method constructs a KilmerFactory instance, which
- # can be used to build Kilmer-style template classes.
- # The +style_attributes+ argument is a Hash that contains the
- # values of the classes attributes (Symbols mapped to Strings).
- #
- # ====Parameters:
- # [style_attributes]
- # A Hash describing the appearance of the Kilmer-style.
- #
- def initialize(style_attributes)
- @central_css = style_attributes[:central_css]
- if(!@central_css)
- raise ArgumentError, "did not specify a value for :central_css"
- end
-
- @index_css = style_attributes[:index_css]
- if(!@index_css)
- raise ArgumentError, "did not specify a value for :index_css"
- end
-
- @method_list_heading = style_attributes[:method_list_heading]
- if(!@method_list_heading)
- @method_list_heading = "Methods"
- end
-
- @class_and_module_list_heading = style_attributes[:class_and_module_list_heading]
- if(!@class_and_module_list_heading)
- @class_and_module_list_heading = "Classes and Modules"
- end
-
- @attribute_list_heading = style_attributes[:attribute_list_heading]
- if(!@attribute_list_heading)
- @attribute_list_heading = "Attributes"
- end
- end
-
- def get_STYLE
- return @central_css
- end
-
- def get_METHOD_LIST
- return %{
-<% if values["diagram"] then %>
-<div id="diagram">
-<table width="100%"><tr><td align="center">
-<%= values["diagram"] %>
-</td></tr></table>
-</div>
-<% end %>
-
-<% if values["description"] then %>
-<div class="description"><%= values["description"] %></div>
-<% end %>
-
-<% if values["requires"] then %>
-<table cellpadding="5" width="100%">
-<tr><td class="tablesubtitle">Required files</td></tr>
-</table><br />
-<div class="name-list">
-<% values["requires"].each do |requires| %>
-<%= href requires["aref"], requires["name"] %>
-<% end %><%# values["requires"] %>
-</div>
-<% end %>
-
-<% if values["methods"] then %>
-<table cellpadding="5" width="100%">
-<tr><td class="tablesubtitle">#{@method_list_heading}</td></tr>
-</table><br />
-<div class="name-list">
-<% values["methods"].each do |methods| %>
-<%= href methods["aref"], methods["name"] %>,
-<% end %><%# values["methods"] %>
-</div>
-<% end %>
-
-<% if values["includes"] then %>
-<div class="tablesubsubtitle">Included modules</div><br />
-<div class="name-list">
-<% values["includes"].each do |includes| %>
- <span class="method-name"><%= href includes["aref"], includes["name"] %></span>
-<% end %><%# values["includes"] %>
-</div>
-<% end %>
-
-<% values["sections"].each do |sections| %>
- <div id="section">
-<% if sections["sectitle"] then %>
- <h2 class="section-title"><a name="<%= sections["secsequence"] %>"><%= sections["sectitle"] %></a></h2>
-<% if sections["seccomment"] then %>
- <div class="section-comment">
- <%= sections["seccomment"] %>
- </div>
-<% end %>
-<% end %>
-<% if sections["attributes"] then %>
-<table cellpadding="5" width="100%">
-<tr><td class="tablesubtitle">#{@attribute_list_heading}</td></tr>
-</table><br />
-<table cellspacing="5">
-<% sections["attributes"].each do |attributes| %>
- <tr valign="top">
-<% if attributes["rw"] then %>
- <td align="center" class="attr-rw">&nbsp;[<%= attributes["rw"] %>]&nbsp;</td>
-<% end %>
-<% unless attributes["rw"] then %>
- <td></td>
-<% end %>
- <td class="attr-name"><%= attributes["name"] %></td>
- <td><%= attributes["a_desc"] %></td>
- </tr>
-<% end %><%# sections["attributes"] %>
-</table>
-<% end %>
-
-<% if sections["classlist"] then %>
-<table cellpadding="5" width="100%">
-<tr><td class="tablesubtitle">#{@class_and_module_list_heading}</td></tr>
-</table><br />
-<%= sections["classlist"] %><br />
-<% end %>
-
-<% if sections["method_list"] then %>
-<% sections["method_list"].each do |method_list| %>
-<% if method_list["methods"] then %>
-<table cellpadding="5" width="100%">
-<tr><td class="tablesubtitle"><%= method_list["type"] %> <%= method_list["category"] %> methods</td></tr>
-</table>
-<% method_list["methods"].each do |methods| %>
-<table width="100%" cellspacing="0" cellpadding="5" border="0">
-<tr><td class="methodtitle">
-<a name="<%= methods["aref"] %>">
-<% if methods["callseq"] then %>
-<b><%= methods["callseq"] %></b>
-<% end %>
-<% unless methods["callseq"] then %>
- <b><%= methods["name"] %></b><%= methods["params"] %>
-<% end %>
-</a>
-<% if methods["codeurl"] then %>
-<a href="<%= methods["codeurl"] %>" target="source" class="srclink">src</a>
-<% end %>
-</td></tr>
-</table>
-<% if methods["m_desc"] then %>
-<div class="description">
-<%= methods["m_desc"] %>
-</div>
-<% end %>
-<% if methods["aka"] then %>
-<div class="aka">
-This method is also aliased as
-<% methods["aka"].each do |aka| %>
-<a href="<%= methods["aref"] %>"><%= methods["name"] %></a>
-<% end %><%# methods["aka"] %>
-</div>
-<% end %>
-<% if methods["sourcecode"] then %>
-<pre class="source">
-<%= methods["sourcecode"] %>
-</pre>
-<% end %>
-<% end %><%# method_list["methods"] %>
-<% end %>
-<% end %><%# sections["method_list"] %>
-<% end %>
-
-<% end %><%# values["sections"] %>
-</div>
-}
- end
-
- def get_BODY
- return XHTML_STRICT_PREAMBLE + HTML_ELEMENT + %{
-<head>
- <title><%= values["title"] %></title>
- <meta http-equiv="Content-Type" content="text/html; charset=<%= values["charset"] %>" />
- <link rel="stylesheet" href="<%= values["style_url"] %>" type="text/css" media="screen" />
- <script type="text/javascript">
- <!--
- function popCode(url) {
- parent.frames.source.location = url
- }
- //-->
- </script>
-</head>
-<body>
-<div class="bodyContent">
-<%= template_include %> <!-- banner header -->
-
-#{get_METHOD_LIST()}
-</div>
-</body>
-</html>
-}
- end
-
-def get_FILE_PAGE
- return %{
-<table width="100%">
- <tr class="title-row">
- <td><table width="100%"><tr>
- <td class="big-title-font" colspan="2">File<br /><%= values["short_name"] %></td>
- <td align="right"><table cellspacing="0" cellpadding="2">
- <tr>
- <td class="small-title-font">Path:</td>
- <td class="small-title-font"><%= values["full_path"] %>
-<% if values["cvsurl"] then %>
- &nbsp;(<a href="<%= values["cvsurl"] %>"><acronym title="Concurrent Versioning System">CVS</acronym></a>)
-<% end %>
- </td>
- </tr>
- <tr>
- <td class="small-title-font">Modified:</td>
- <td class="small-title-font"><%= values["dtm_modified"] %></td>
- </tr>
- </table>
- </td></tr></table></td>
- </tr>
-</table><br />
-}
-end
-
-def get_CLASS_PAGE
- return %{
-<table width="100%" border="0" cellspacing="0">
- <tr class="title-row">
- <td class="big-title-font">
- <%= values["classmod"] %><br /><%= values["full_name"] %>
- </td>
- <td align="right">
- <table cellspacing="0" cellpadding="2">
- <tr valign="top">
- <td class="small-title-font">In:</td>
- <td class="small-title-font">
-<% values["infiles"].each do |infiles| %>
-<%= href infiles["full_path_url"], infiles["full_path"] %>
-<% if infiles["cvsurl"] then %>
-&nbsp;(<a href="<%= infiles["cvsurl"] %>"><acronym title="Concurrent Versioning System">CVS</acronym></a>)
-<% end %>
-<% end %><%# values["infiles"] %>
- </td>
- </tr>
-<% if values["parent"] then %>
- <tr>
- <td class="small-title-font">Parent:</td>
- <td class="small-title-font">
-<% if values["par_url"] then %>
- <a href="<%= values["par_url"] %>" class="cyan">
-<% end %>
-<%= values["parent"] %>
-<% if values["par_url"] then %>
- </a>
-<% end %>
- </td>
- </tr>
-<% end %>
- </table>
- </td>
- </tr>
-</table><br />
-}
-end
-
-def get_SRC_PAGE
- return XHTML_STRICT_PREAMBLE + HTML_ELEMENT + %{
-<head><title><%= values["title"] %></title>
-<meta http-equiv="Content-Type" content="text/html; charset=<%= values["charset"] %>" />
-<link rel="stylesheet" href="<%= values["style_url"] %>" type="text/css" media="screen" />
-</head>
-<body>
-<pre><%= values["code"] %></pre>
-</body>
-</html>
-}
-end
-
-def get_FR_INDEX_BODY
- return %{<%= template_include %>}
-end
-
-def get_FILE_INDEX
- return XHTML_STRICT_PREAMBLE + HTML_ELEMENT + %{
-<head>
-<title><%= values["title"] %></title>
-<meta http-equiv="Content-Type" content="text/html; charset=<%= values["charset"] %>" />
-<style type="text/css">
-<!--
-#{@index_css}
--->
-</style>
-<base target="docwin" />
-</head>
-<body>
-<div class="index">
-<div class="banner"><%= values["list_title"] %></div>
-<% values["entries"].each do |entries| %>
-<a href="<%= entries["href"] %>"><%= entries["name"] %></a><br />
-<% end %><%# values["entries"] %>
-</div>
-</body></html>
-}
-end
-
-def get_CLASS_INDEX
- return get_FILE_INDEX
-end
-
-def get_METHOD_INDEX
- return get_FILE_INDEX
-end
-
-def get_INDEX
- return XHTML_FRAME_PREAMBLE + HTML_ELEMENT + %{
-<head>
- <title><%= values["title"] %></title>
- <meta http-equiv="Content-Type" content="text/html; charset=<%= values["charset"] %>" />
-</head>
-
-<frameset cols="20%,*">
- <frameset rows="15%,35%,50%">
- <frame src="fr_file_index.html" title="Files" name="Files" />
- <frame src="fr_class_index.html" name="Classes" />
- <frame src="fr_method_index.html" name="Methods" />
- </frameset>
-<% if values["inline_source"] then %>
- <frame src="<%= values["initial_page"] %>" name="docwin" />
-<% end %>
-<% unless values["inline_source"] then %>
- <frameset rows="80%,20%">
- <frame src="<%= values["initial_page"] %>" name="docwin" />
- <frame src="blank.html" name="source" />
- </frameset>
-<% end %>
-</frameset>
-
-</html>
-}
-end
-
-def get_BLANK
- # This will be displayed in the source code frame before
- # any source code has been selected.
- return XHTML_STRICT_PREAMBLE + HTML_ELEMENT + %{
-<head>
- <title>Source Code Frame <%= values["title_suffix"] %></title>
- <meta http-equiv="Content-Type" content="text/html; charset=<%= values["charset"] %>" />
- <link rel="stylesheet" href="<%= values["style_url"] %>" type="text/css" media="screen" />
-</head>
-<body>
-</body>
-</html>
-}
-end
-
-def write_extra_pages(values)
- template = RDoc::TemplatePage.new(get_BLANK())
- File.open("blank.html", "w") { |f| template.write_html_on(f, values) }
-end
-
-end
diff --git a/lib/rdoc/generator/html/one_page_html.rb b/lib/rdoc/generator/html/one_page_html.rb
deleted file mode 100644
index 5bae2f34f7..0000000000
--- a/lib/rdoc/generator/html/one_page_html.rb
+++ /dev/null
@@ -1,122 +0,0 @@
-require 'rdoc/generator/html'
-require 'rdoc/generator/html/common'
-
-module RDoc::Generator::HTML::ONE_PAGE_HTML
-
- include RDoc::Generator::HTML::Common
-
- CONTENTS_XML = <<-EOF
-<% if defined? classes and classes["description"] then %>
-<%= classes["description"] %>
-<% end %>
-
-<% if defined? files and files["requires"] then %>
-<h4>Requires:</h4>
-<ul>
-<% files["requires"].each do |requires| %>
-<% if requires["aref"] then %>
-<li><a href="<%= requires["aref"] %>"><%= requires["name"] %></a></li>
-<% end %>
-<% unless requires["aref"] then %>
-<li><%= requires["name"] %></li>
-<% end %>
-<% end %><%# files["requires"] %>
-</ul>
-<% end %>
-
-<% if defined? classes and classes["includes"] then %>
-<h4>Includes</h4>
-<ul>
-<% classes["includes"].each do |includes| %>
-<% if includes["aref"] then %>
-<li><a href="<%= includes["aref"] %>"><%= includes["name"] %></a></li>
-<% end %>
-<% unless includes["aref"] then %>
-<li><%= includes["name"] %></li>
-<% end %>
-<% end %><%# classes["includes"] %>
-</ul>
-<% end %>
-
-<% if defined? classes and classes["sections"] then %>
-<% classes["sections"].each do |sections| %>
-<% if sections["attributes"] then %>
-<h4>Attributes</h4>
-<table>
-<% sections["attributes"].each do |attributes| %>
-<tr><td><%= attributes["name"] %></td><td><%= attributes["rw"] %></td><td><%= attributes["a_desc"] %></td></tr>
-<% end %><%# sections["attributes"] %>
-</table>
-<% end %>
-
-<% if sections["method_list"] then %>
-<h3>Methods</h3>
-<% sections["method_list"].each do |method_list| %>
-<% if method_list["methods"] then %>
-<% method_list["methods"].each do |methods| %>
-<h4><%= methods["type"] %> <%= methods["category"] %> method:
-<% if methods["callseq"] then %>
-<a name="<%= methods["aref"] %>"><%= methods["callseq"] %></a>
-<% end %>
-<% unless methods["callseq"] then %>
-<a name="<%= methods["aref"] %>"><%= methods["name"] %><%= methods["params"] %></a></h4>
-<% end %>
-
-<% if methods["m_desc"] then %>
-<%= methods["m_desc"] %>
-<% end %>
-
-<% if methods["sourcecode"] then %>
-<blockquote><pre>
-<%= methods["sourcecode"] %>
-</pre></blockquote>
-<% end %>
-<% end %><%# method_list["methods"] %>
-<% end %>
-<% end %><%# sections["method_list"] %>
-<% end %>
-<% end %><%# classes["sections"] %>
-<% end %>
- EOF
-
- ONE_PAGE = XHTML_STRICT_PREAMBLE + HTML_ELEMENT + %{
-<head>
- <title><%= values["title"] %></title>
- <meta http-equiv="Content-Type" content="text/html; charset=<%= values["charset"] %>" />
-</head>
-<body>
-<% values["files"].each do |files| %>
-<h2>File: <a name="<%= files["href"] %>"><%= files["short_name"] %></a></h2>
-<table>
- <tr><td>Path:</td><td><%= files["full_path"] %></td></tr>
- <tr><td>Modified:</td><td><%= files["dtm_modified"] %></td></tr>
-</table>
-} + CONTENTS_XML + %{
-<% end %><%# values["files"] %>
-
-<% if values["classes"] then %>
-<h2>Classes</h2>
-<% values["classes"].each do |classes| %>
-<% if classes["parent"] then %>
-<h3><%= classes["classmod"] %> <a name="<%= classes["href"] %>"><%= classes["full_name"] %></a> &lt; <%= href classes["par_url"], classes["parent"] %></h3>
-<% end %>
-<% unless classes["parent"] then %>
-<h3><%= classes["classmod"] %> <%= classes["full_name"] %></h3>
-<% end %>
-
-<% if classes["infiles"] then %>
-(in files
-<% classes["infiles"].each do |infiles| %>
-<%= href infiles["full_path_url"], infiles["full_path"] %>
-<% end %><%# classes["infiles"] %>
-)
-<% end %>
-} + CONTENTS_XML + %{
-<% end %><%# values["classes"] %>
-<% end %>
-</body>
-</html>
-}
-
-end
-
diff --git a/lib/rdoc/generator/markup.rb b/lib/rdoc/generator/markup.rb
new file mode 100644
index 0000000000..a90b15a1e7
--- /dev/null
+++ b/lib/rdoc/generator/markup.rb
@@ -0,0 +1,171 @@
+require 'rdoc/text'
+require 'rdoc/code_objects'
+require 'rdoc/generator'
+require 'rdoc/markup/to_html_crossref'
+
+##
+# Handle common RDoc::Markup tasks for various CodeObjects
+
+module RDoc::Generator::Markup
+
+ ##
+ # Generates a relative URL from this object's path to +target_path+
+
+ def aref_to(target_path)
+ RDoc::Markup::ToHtml.gen_relative_url path, target_path
+ end
+
+ ##
+ # Generates a relative URL from +from_path+ to this object's path
+
+ def as_href(from_path)
+ RDoc::Markup::ToHtml.gen_relative_url from_path, path
+ end
+
+ ##
+ # Handy wrapper for marking up this object's comment
+
+ def description
+ markup @comment
+ end
+
+ ##
+ # Creates an RDoc::Markup::ToHtmlCrossref formatter
+
+ def formatter
+ return @formatter if defined? @formatter
+
+ show_hash = RDoc::RDoc.current.options.show_hash
+ this = RDoc::Context === self ? self : @parent
+ @formatter = RDoc::Markup::ToHtmlCrossref.new this.path, this, show_hash
+ end
+
+ ##
+ # Build a webcvs URL starting for the given +url+ with +full_path+ appended
+ # as the destination path. If +url+ contains '%s' +full_path+ will be
+ # sprintf'd into +url+ instead.
+
+ def cvs_url(url, full_path)
+ if /%s/ =~ url then
+ sprintf url, full_path
+ else
+ url + full_path
+ end
+ end
+
+end
+
+class RDoc::AnyMethod
+
+ include RDoc::Generator::Markup
+
+ ##
+ # Prepend +src+ with line numbers. Relies on the first line of a source
+ # code listing having:
+ #
+ # # File xxxxx, line dddd
+
+ 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
+
+ line = first
+ src.gsub!(/^/) do
+ res = if line == first then
+ " " * (size + 2)
+ else
+ "%2$*1$d: " % [size, line]
+ end
+
+ line += 1
+ res
+ end
+ end
+ end
+
+ ##
+ # Turns the method's token stream into HTML
+
+ def markup_code
+ return '' unless @token_stream
+
+ src = ""
+
+ @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
+
+ text = CGI.escapeHTML t.text
+
+ if style
+ src << "<span class=\"#{style}\">#{text}</span>"
+ else
+ src << text
+ end
+ end
+
+ add_line_numbers src
+
+ src
+ end
+
+end
+
+class RDoc::Attr
+
+ include RDoc::Generator::Markup
+
+end
+
+class RDoc::Constant
+
+ include RDoc::Generator::Markup
+
+end
+
+class RDoc::Context
+
+ include RDoc::Generator::Markup
+
+end
+
+class RDoc::Context::Section
+
+ include RDoc::Generator::Markup
+
+end
+
+class RDoc::TopLevel
+
+ ##
+ # Returns a URL for this source file on some web repository. Use the -W
+ # command line option to set.
+
+ def cvs_url
+ url = RDoc::RDoc.current.options.webcvs
+
+ if /%s/ =~ url then
+ url % @absolute_name
+ else
+ url + @absolute_name
+ end
+ end
+
+end
+
diff --git a/lib/rdoc/generator/ri.rb b/lib/rdoc/generator/ri.rb
index 6b7a5932f8..ad9932a02a 100644
--- a/lib/rdoc/generator/ri.rb
+++ b/lib/rdoc/generator/ri.rb
@@ -1,225 +1,79 @@
require 'rdoc/generator'
-require 'rdoc/markup/to_flow'
+require 'rdoc/ri'
-require 'rdoc/ri/cache'
-require 'rdoc/ri/reader'
-require 'rdoc/ri/writer'
-require 'rdoc/ri/descriptions'
+##
+# Generates ri data files
class RDoc::Generator::RI
- ##
- # Generator may need to return specific subclasses depending on the
- # options they are passed. Because of this we create them using a factory
+ RDoc::RDoc.add_generator self
- def self.for(options)
- new(options)
+ def self.for options
+ new options
end
##
# Set up a new ri generator
- def initialize(options) #:not-new:
- @options = options
- @ri_writer = RDoc::RI::Writer.new "."
- @markup = RDoc::Markup.new
- @to_flow = RDoc::Markup::ToFlow.new
-
- @generated = {}
+ def initialize options #:not-new:
+ @options = options
+ @store = RDoc::RI::Store.new '.'
+ @old_siginfo = nil
+ @current = nil
end
##
- # Build the initial indices and output objects based on an array of
- # TopLevel objects containing the extracted information.
-
- def generate(toplevels)
- RDoc::TopLevel.all_classes_and_modules.each do |cls|
- process_class cls
- end
- end
-
- def process_class(from_class)
- generate_class_info(from_class)
+ # Build the initial indices and output objects based on an array of TopLevel
+ # objects containing the extracted information.
- # now recurse into this class' constituent classes
- from_class.each_classmodule do |mod|
- process_class(mod)
- end
- end
-
- def generate_class_info(cls)
- case cls
- when RDoc::NormalModule then
- cls_desc = RDoc::RI::ModuleDescription.new
- else
- cls_desc = RDoc::RI::ClassDescription.new
- cls_desc.superclass = cls.superclass
- end
+ def generate top_levels
+ install_siginfo_handler
- cls_desc.name = cls.name
- cls_desc.full_name = cls.full_name
- cls_desc.comment = markup(cls.comment)
+ RDoc::TopLevel.all_classes_and_modules.each do |klass|
+ @current = "#{klass.class}: #{klass.full_name}"
- cls_desc.attributes = cls.attributes.sort.map do |a|
- RDoc::RI::Attribute.new(a.name, a.rw, markup(a.comment))
- end
+ @store.save_class klass
- cls_desc.constants = cls.constants.map do |c|
- RDoc::RI::Constant.new(c.name, c.value, markup(c.comment))
- end
-
- cls_desc.includes = cls.includes.map do |i|
- RDoc::RI::IncludedModule.new(i.name)
- end
-
- class_methods, instance_methods = method_list(cls)
-
- cls_desc.class_methods = class_methods.map do |m|
- RDoc::RI::MethodSummary.new(m.name)
- end
-
- cls_desc.instance_methods = instance_methods.map do |m|
- RDoc::RI::MethodSummary.new(m.name)
- end
-
- update_or_replace(cls_desc)
-
- class_methods.each do |m|
- generate_method_info(cls_desc, m)
- end
+ klass.each_method do |method|
+ @current = "#{method.class}: #{method.full_name}"
+ @store.save_method klass, method
+ end
- instance_methods.each do |m|
- generate_method_info(cls_desc, m)
+ klass.each_attribute do |attribute|
+ @store.save_method klass, attribute
+ end
end
- end
- def generate_method_info(cls_desc, method)
- meth_desc = RDoc::RI::MethodDescription.new
- meth_desc.name = method.name
- meth_desc.full_name = cls_desc.full_name
- if method.singleton
- meth_desc.full_name += "::"
- else
- meth_desc.full_name += "#"
- end
- meth_desc.full_name << method.name
+ @current = 'saving cache'
- meth_desc.comment = markup(method.comment)
- meth_desc.params = params_of(method)
- meth_desc.visibility = method.visibility.to_s
- meth_desc.is_singleton = method.singleton
- meth_desc.block_params = method.block_params
+ @store.save_cache
- meth_desc.aliases = method.aliases.map do |a|
- RDoc::RI::AliasName.new(a.name)
- end
+ ensure
+ @current = nil
- @ri_writer.add_method(cls_desc, meth_desc)
+ remove_siginfo_handler
end
- private
-
##
- # Returns a list of class and instance methods that we'll be documenting
-
- def method_list(cls)
- list = cls.method_list
- unless @options.show_all
- list = list.find_all do |m|
- m.visibility == :public || m.visibility == :protected || m.force_documentation
- end
- end
+ # Installs a siginfo handler that prints the current filename.
- c = []
- i = []
- list.sort.each do |m|
- if m.singleton
- c << m
- else
- i << m
- end
- end
- return c,i
- end
+ def install_siginfo_handler
+ return unless Signal.list.key? 'INFO'
- def params_of(method)
- if method.call_seq
- method.call_seq
- else
- params = method.params || ""
-
- p = params.gsub(/\s*\#.*/, '')
- p = p.tr("\n", " ").squeeze(" ")
- p = "(" + p + ")" unless p[0] == ?(
-
- if (block = method.block_params)
- block.gsub!(/\s*\#.*/, '')
- block = block.tr("\n", " ").squeeze(" ")
- if block[0] == ?(
- block.sub!(/^\(/, '').sub!(/\)/, '')
- end
- p << " {|#{block.strip}| ...}"
- end
- p
+ @old_siginfo = trap 'INFO' do
+ puts @current if @current
end
end
- def markup(comment)
- return nil if !comment || comment.empty?
-
- # Convert leading comment markers to spaces, but only
- # if all non-blank lines have them
-
- if comment =~ /^(?>\s*)[^\#]/
- content = comment
- else
- content = comment.gsub(/^\s*(#+)/) { $1.tr('#',' ') }
- end
- @markup.convert(content, @to_flow)
- end
-
##
- # By default we replace existing classes with the same name. If the
- # --merge option was given, we instead merge this definition into an
- # existing class. We add our methods, aliases, etc to that class, but do
- # not change the class's description.
-
- def update_or_replace(cls_desc)
- old_cls = nil
-
- if @options.merge
- rdr = RDoc::RI::Reader.new RDoc::RI::Cache.new(@options.op_dir)
-
- namespace = rdr.top_level_namespace
- namespace = rdr.lookup_namespace_in(cls_desc.name, namespace)
- if namespace.empty?
- $stderr.puts "You asked me to merge this source into existing "
- $stderr.puts "documentation. This file references a class or "
- $stderr.puts "module called #{cls_desc.name} which I don't"
- $stderr.puts "have existing documentation for."
- $stderr.puts
- $stderr.puts "Perhaps you need to generate its documentation first"
- exit 1
- else
- old_cls = namespace[0]
- end
- end
-
- prev_cls = @generated[cls_desc.full_name]
+ # Removes a siginfo handler and replaces the previous
- if old_cls and not prev_cls then
- old_desc = rdr.get_class old_cls
- cls_desc.merge_in old_desc
- end
-
- if prev_cls then
- cls_desc.merge_in prev_cls
- end
+ def remove_siginfo_handler
+ return unless Signal.list.key? 'INFO'
- @generated[cls_desc.full_name] = cls_desc
+ handler = @old_siginfo || 'DEFAULT'
- @ri_writer.remove_class cls_desc
- @ri_writer.add_class cls_desc
+ trap 'INFO', handler
end
end
diff --git a/lib/rdoc/generator/template/darkfish/.document b/lib/rdoc/generator/template/darkfish/.document
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/.document
diff --git a/lib/rdoc/generator/template/darkfish/classpage.rhtml b/lib/rdoc/generator/template/darkfish/classpage.rhtml
new file mode 100644
index 0000000000..58e87e60ba
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/classpage.rhtml
@@ -0,0 +1,297 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <meta content="text/html; charset=<%= @options.charset %>" http-equiv="Content-Type" />
+
+ <title><%= klass.type.capitalize %>: <%= klass.full_name %></title>
+
+ <link rel="stylesheet" href="<%= rel_prefix %>/rdoc.css" type="text/css" media="screen" />
+
+ <script src="<%= rel_prefix %>/js/jquery.js" type="text/javascript"
+ charset="utf-8"></script>
+ <script src="<%= rel_prefix %>/js/thickbox-compressed.js" type="text/javascript"
+ charset="utf-8"></script>
+ <script src="<%= rel_prefix %>/js/quicksearch.js" type="text/javascript"
+ charset="utf-8"></script>
+ <script src="<%= rel_prefix %>/js/darkfish.js" type="text/javascript"
+ charset="utf-8"></script>
+
+</head>
+<body class="<%= klass.type %>">
+
+ <div id="metadata">
+ <div id="home-metadata">
+ <div id="home-section" class="section">
+ <h3 class="section-header">
+ <a href="<%= rel_prefix %>/index.html">Home</a>
+ <a href="<%= rel_prefix %>/index.html#classes">Classes</a>
+ <a href="<%= rel_prefix %>/index.html#methods">Methods</a>
+ </h3>
+ </div>
+ </div>
+
+ <div id="file-metadata">
+ <div id="file-list-section" class="section">
+ <h3 class="section-header">In Files</h3>
+ <div class="section-body">
+ <ul>
+ <% klass.in_files.each do |tl| %>
+ <li><a href="<%= rel_prefix %>/<%= h tl.path %>?TB_iframe=true&amp;height=550&amp;width=785"
+ class="thickbox" title="<%= h tl.absolute_name %>"><%= h tl.absolute_name %></a></li>
+ <% end %>
+ </ul>
+ </div>
+ </div>
+
+ <% if !svninfo.empty? %>
+ <div id="file-svninfo-section" class="section">
+ <h3 class="section-header">Subversion Info</h3>
+ <div class="section-body">
+ <dl class="svninfo">
+ <dt>Rev</dt>
+ <dd><%= svninfo[:rev] %></dd>
+
+ <dt>Last Checked In</dt>
+ <dd><%= svninfo[:commitdate].strftime('%Y-%m-%d %H:%M:%S') %>
+ (<%= svninfo[:commitdelta] %> ago)</dd>
+
+ <dt>Checked in by</dt>
+ <dd><%= svninfo[:committer] %></dd>
+ </dl>
+ </div>
+ </div>
+ <% end %>
+ </div>
+
+ <div id="class-metadata">
+
+ <!-- Parent Class -->
+ <% if klass.type == 'class' %>
+ <div id="parent-class-section" class="section">
+ <h3 class="section-header">Parent</h3>
+ <% unless String === klass.superclass %>
+ <p class="link"><a href="<%= klass.aref_to klass.superclass.path %>"><%= klass.superclass.full_name %></a></p>
+ <% else %>
+ <p class="link"><%= klass.superclass %></p>
+ <% end %>
+ </div>
+ <% end %>
+
+ <!-- Namespace Contents -->
+ <% unless klass.classes_and_modules.empty? %>
+ <div id="namespace-list-section" class="section">
+ <h3 class="section-header">Namespace</h3>
+ <ul class="link-list">
+ <% (klass.modules.sort + klass.classes.sort).each do |mod| %>
+ <li><span class="type"><%= mod.type.upcase %></span> <a href="<%= klass.aref_to mod.path %>"><%= mod.full_name %></a></li>
+ <% end %>
+ </ul>
+ </div>
+ <% end %>
+
+ <!-- Method Quickref -->
+ <% unless klass.method_list.empty? %>
+ <div id="method-list-section" class="section">
+ <h3 class="section-header">Methods</h3>
+ <ul class="link-list">
+ <% klass.each_method do |meth| %>
+ <li><a href="#<%= meth.aref %>"><%= meth.singleton ? '::' : '#' %><%= meth.name %></a></li>
+ <% end %>
+ </ul>
+ </div>
+ <% end %>
+
+ <!-- Included Modules -->
+ <% unless klass.includes.empty? %>
+ <div id="includes-section" class="section">
+ <h3 class="section-header">Included Modules</h3>
+ <ul class="link-list">
+ <% klass.each_include do |inc| %>
+ <% unless String === inc.module %>
+ <li><a class="include" href="<%= klass.aref_to inc.module.path %>"><%= inc.module.full_name %></a></li>
+ <% else %>
+ <li><span class="include"><%= inc.name %></span></li>
+ <% end %>
+ <% end %>
+ </ul>
+ </div>
+ <% end %>
+ </div>
+
+ <div id="project-metadata">
+ <% simple_files = @files.select {|tl| tl.parser == RDoc::Parser::Simple } %>
+ <% unless simple_files.empty? then %>
+ <div id="fileindex-section" class="section project-section">
+ <h3 class="section-header">Files</h3>
+ <ul>
+ <% simple_files.each do |file| %>
+ <li class="file"><a href="<%= rel_prefix %>/<%= file.path %>"><%= h file.base_name %></a></li>
+ <% end %>
+ </ul>
+ </div>
+ <% end %>
+
+ <div id="classindex-section" class="section project-section">
+ <h3 class="section-header">Class Index
+ <span class="search-toggle"><img src="<%= rel_prefix %>/images/find.png"
+ height="16" width="16" alt="[+]"
+ title="show/hide quicksearch" /></span></h3>
+ <form action="#" method="get" accept-charset="utf-8" class="initially-hidden">
+ <fieldset>
+ <legend>Quicksearch</legend>
+ <input type="text" name="quicksearch" value=""
+ class="quicksearch-field" />
+ </fieldset>
+ </form>
+
+ <ul class="link-list">
+ <% @modsort.each do |index_klass| %>
+ <li><a href="<%= rel_prefix %>/<%= index_klass.path %>"><%= index_klass.full_name %></a></li>
+ <% end %>
+ </ul>
+ <div id="no-class-search-results" style="display: none;">No matching classes.</div>
+ </div>
+
+ <% if $DEBUG_RDOC %>
+ <div id="debugging-toggle"><img src="<%= rel_prefix %>/images/bug.png"
+ alt="toggle debugging" height="16" width="16" /></div>
+ <% end %>
+ </div>
+ </div>
+
+ <div id="documentation">
+ <h1 class="<%= klass.type %>"><%= klass.full_name %></h1>
+
+ <div id="description">
+ <%= klass.description %>
+ </div>
+
+ <!-- Constants -->
+ <% unless klass.constants.empty? %>
+ <div id="constants-list" class="section">
+ <h3 class="section-header">Constants</h3>
+ <dl>
+ <% klass.each_constant do |const| %>
+ <dt><a name="<%= const.name %>"><%= const.name %></a></dt>
+ <% if const.comment %>
+ <dd class="description"><%= const.description.strip %></dd>
+ <% else %>
+ <dd class="description missing-docs">(Not documented)</dd>
+ <% end %>
+ <% end %>
+ </dl>
+ </div>
+ <% end %>
+
+ <!-- Attributes -->
+ <% unless klass.attributes.empty? %>
+ <div id="attribute-method-details" class="method-section section">
+ <h3 class="section-header">Attributes</h3>
+
+ <% klass.each_attribute do |attrib| %>
+ <div id="<%= attrib.html_name %>-attribute-method" class="method-detail">
+ <a name="<%= h attrib.name %>"></a>
+ <% if attrib.rw =~ /w/i %>
+ <a name="<%= h attrib.name %>="></a>
+ <% end %>
+ <div class="method-heading attribute-method-heading">
+ <span class="method-name"><%= h attrib.name %></span><span
+ class="attribute-access-type">[<%= attrib.rw %>]</span>
+ </div>
+
+ <div class="method-description">
+ <% if attrib.comment %>
+ <%= attrib.description.strip %>
+ <% else %>
+ <p class="missing-docs">(Not documented)</p>
+ <% end %>
+ </div>
+ </div>
+ <% end %>
+ </div>
+ <% end %>
+
+ <!-- Methods -->
+ <% klass.methods_by_type.each do |type, visibilities|
+ next if visibilities.empty?
+ visibilities.each do |visibility, methods|
+ next if methods.empty? %>
+ <div id="<%= visibility %>-<%= type %>-method-details" class="method-section section">
+ <h3 class="section-header"><%= visibility.to_s.capitalize %> <%= type.capitalize %> Methods</h3>
+
+ <% methods.each do |method| %>
+ <div id="<%= method.html_name %>-method" class="method-detail <%= method.is_alias_for ? "method-alias" : '' %>">
+ <a name="<%= h method.aref %>"></a>
+
+ <div class="method-heading">
+ <% if method.call_seq %>
+ <span class="method-callseq"><%= method.call_seq.strip.gsub(/->/, '&rarr;').gsub( /^\w.+\./m, '') %></span>
+ <span class="method-click-advice">click to toggle source</span>
+ <% else %>
+ <span class="method-name"><%= h method.name %></span><span
+ class="method-args"><%= method.params %></span>
+ <span class="method-click-advice">click to toggle source</span>
+ <% end %>
+ </div>
+
+ <div class="method-description">
+ <% if method.comment %>
+ <%= method.description.strip %>
+ <% else %>
+ <p class="missing-docs">(Not documented)</p>
+ <% end %>
+
+ <% if method.token_stream %>
+ <div class="method-source-code"
+ id="<%= method.html_name %>-source">
+<pre>
+<%= method.markup_code %>
+</pre>
+ </div>
+ <% end %>
+ </div>
+
+ <% unless method.aliases.empty? %>
+ <div class="aliases">
+ Also aliased as: <%= method.aliases.map do |aka|
+ %{<a href="#{ klass.aref_to aka.path}">#{h aka.name}</a>}
+ end.join(", ") %>
+ </div>
+ <% end %>
+
+ <% if method.is_alias_for then %>
+ <div class="aliases">
+ Alias for: <a href="<%= klass.aref_to method.is_alias_for.path %>"><%= h method.is_alias_for.name %></a>
+ </div>
+ <% end %>
+ </div>
+
+ <% end %>
+ </div>
+ <% end
+ end %>
+
+ </div>
+
+
+ <div id="rdoc-debugging-section-dump" class="debugging-section">
+ <% if $DEBUG_RDOC
+ require 'pp' %>
+<pre><%= h PP.pp(klass, _erbout) %></pre>
+ </div>
+ <% else %>
+ <p>Disabled; run with --debug to generate this.</p>
+ <% end %>
+ </div>
+
+ <div id="validator-badges">
+ <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
+ <p><small>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish
+ Rdoc Generator</a> <%= RDoc::Generator::Darkfish::VERSION %></small>.</p>
+ </div>
+
+</body>
+</html>
+
diff --git a/lib/rdoc/generator/template/darkfish/filepage.rhtml b/lib/rdoc/generator/template/darkfish/filepage.rhtml
new file mode 100644
index 0000000000..33216dc8f1
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/filepage.rhtml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <meta content="text/html; charset=<%= @options.charset %>" http-equiv="Content-Type" />
+
+ <title>File: <%= file.base_name %> [<%= @options.title %>]</title>
+
+ <link type="text/css" media="screen" href="<%= rel_prefix %>/rdoc.css" rel="stylesheet" />
+
+ <script src="<%= rel_prefix %>/js/jquery.js" type="text/javascript"
+ charset="utf-8"></script>
+ <script src="<%= rel_prefix %>/js/thickbox-compressed.js" type="text/javascript"
+ charset="utf-8"></script>
+ <script src="<%= rel_prefix %>/js/quicksearch.js" type="text/javascript"
+ charset="utf-8"></script>
+ <script src="<%= rel_prefix %>/js/darkfish.js" type="text/javascript"
+ charset="utf-8"></script>
+</head>
+
+<% if file.parser == RDoc::Parser::Simple %>
+<body class="file">
+ <div id="metadata">
+ <div id="home-metadata">
+ <div id="home-section" class="section">
+ <h3 class="section-header">
+ <a href="<%= rel_prefix %>/index.html">Home</a>
+ <a href="<%= rel_prefix %>/index.html#classes">Classes</a>
+ <a href="<%= rel_prefix %>/index.html#methods">Methods</a>
+ </h3>
+ </div>
+ </div>
+
+ <div id="project-metadata">
+ <% simple_files = @files.select { |f| f.parser == RDoc::Parser::Simple } %>
+ <% unless simple_files.empty? then %>
+ <div id="fileindex-section" class="section project-section">
+ <h3 class="section-header">Files</h3>
+ <ul>
+ <% simple_files.each do |f| %>
+ <li class="file"><a href="<%= rel_prefix %>/<%= f.path %>"><%= h f.base_name %></a></li>
+ <% end %>
+ </ul>
+ </div>
+ <% end %>
+
+ <div id="classindex-section" class="section project-section">
+ <h3 class="section-header">Class Index
+ <span class="search-toggle"><img src="<%= rel_prefix %>/images/find.png"
+ height="16" width="16" alt="[+]"
+ title="show/hide quicksearch" /></span></h3>
+ <form action="#" method="get" accept-charset="utf-8" class="initially-hidden">
+ <fieldset>
+ <legend>Quicksearch</legend>
+ <input type="text" name="quicksearch" value=""
+ class="quicksearch-field" />
+ </fieldset>
+ </form>
+
+ <ul class="link-list">
+ <% @modsort.each do |index_klass| %>
+ <li><a href="<%= rel_prefix %>/<%= index_klass.path %>"><%= index_klass.full_name %></a></li>
+ <% end %>
+ </ul>
+ <div id="no-class-search-results" style="display: none;">No matching classes.</div>
+ </div>
+
+ <% if $DEBUG_RDOC %>
+ <div id="debugging-toggle"><img src="<%= rel_prefix %>/images/bug.png"
+ alt="toggle debugging" height="16" width="16" /></div>
+ <% end %>
+ </div>
+ </div>
+
+ <div id="documentation">
+ <%= file.description %>
+ </div>
+
+ <div id="validator-badges">
+ <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
+ <p><small>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish
+ Rdoc Generator</a> <%= RDoc::Generator::Darkfish::VERSION %></small>.</p>
+ </div>
+</body>
+<% else %>
+<body class="file file-popup">
+ <div id="metadata">
+ <dl>
+ <dt class="modified-date">Last Modified</dt>
+ <dd class="modified-date"><%= file.last_modified %></dd>
+
+ <% if file.requires %>
+ <dt class="requires">Requires</dt>
+ <dd class="requires">
+ <ul>
+ <% file.requires.each do |require| %>
+ <li><%= require.name %></li>
+ <% end %>
+ </ul>
+ </dd>
+ <% end %>
+
+ <% if @options.webcvs %>
+ <dt class="scs-url">Trac URL</dt>
+ <dd class="scs-url"><a target="_top"
+ href="<%= file.cvs_url %>"><%= file.cvs_url %></a></dd>
+ <% end %>
+ </dl>
+ </div>
+
+ <div id="documentation">
+ <% if file.comment %>
+ <div class="description">
+ <h2>Description</h2>
+ <%= file.description %>
+ </div>
+ <% end %>
+ </div>
+</body>
+<% end %>
+</html>
+
diff --git a/lib/rdoc/generator/template/darkfish/images/brick.png b/lib/rdoc/generator/template/darkfish/images/brick.png
new file mode 100644
index 0000000000..7851cf34c9
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/brick.png
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/images/brick_link.png b/lib/rdoc/generator/template/darkfish/images/brick_link.png
new file mode 100644
index 0000000000..9ebf013a23
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/brick_link.png
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/images/bug.png b/lib/rdoc/generator/template/darkfish/images/bug.png
new file mode 100644
index 0000000000..2d5fb90ec6
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/bug.png
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/images/bullet_black.png b/lib/rdoc/generator/template/darkfish/images/bullet_black.png
new file mode 100644
index 0000000000..57619706d1
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/bullet_black.png
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/images/bullet_toggle_minus.png b/lib/rdoc/generator/template/darkfish/images/bullet_toggle_minus.png
new file mode 100644
index 0000000000..b47ce55f68
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/bullet_toggle_minus.png
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/images/bullet_toggle_plus.png b/lib/rdoc/generator/template/darkfish/images/bullet_toggle_plus.png
new file mode 100644
index 0000000000..9ab4a89664
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/bullet_toggle_plus.png
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/images/date.png b/lib/rdoc/generator/template/darkfish/images/date.png
new file mode 100644
index 0000000000..783c83357f
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/date.png
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/images/find.png b/lib/rdoc/generator/template/darkfish/images/find.png
new file mode 100644
index 0000000000..1547479646
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/find.png
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/images/loadingAnimation.gif b/lib/rdoc/generator/template/darkfish/images/loadingAnimation.gif
new file mode 100644
index 0000000000..82290f4833
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/loadingAnimation.gif
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/images/macFFBgHack.png b/lib/rdoc/generator/template/darkfish/images/macFFBgHack.png
new file mode 100644
index 0000000000..c6473b324e
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/macFFBgHack.png
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/images/package.png b/lib/rdoc/generator/template/darkfish/images/package.png
new file mode 100644
index 0000000000..da3c2a2d74
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/package.png
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/images/page_green.png b/lib/rdoc/generator/template/darkfish/images/page_green.png
new file mode 100644
index 0000000000..de8e003f9f
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/page_green.png
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/images/page_white_text.png b/lib/rdoc/generator/template/darkfish/images/page_white_text.png
new file mode 100644
index 0000000000..813f712f72
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/page_white_text.png
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/images/page_white_width.png b/lib/rdoc/generator/template/darkfish/images/page_white_width.png
new file mode 100644
index 0000000000..1eb880947d
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/page_white_width.png
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/images/plugin.png b/lib/rdoc/generator/template/darkfish/images/plugin.png
new file mode 100644
index 0000000000..6187b15aec
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/plugin.png
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/images/ruby.png b/lib/rdoc/generator/template/darkfish/images/ruby.png
new file mode 100644
index 0000000000..f763a16880
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/ruby.png
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/images/tag_green.png b/lib/rdoc/generator/template/darkfish/images/tag_green.png
new file mode 100644
index 0000000000..83ec984bd7
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/tag_green.png
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/images/wrench.png b/lib/rdoc/generator/template/darkfish/images/wrench.png
new file mode 100644
index 0000000000..5c8213fef5
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/wrench.png
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/images/wrench_orange.png b/lib/rdoc/generator/template/darkfish/images/wrench_orange.png
new file mode 100644
index 0000000000..565a9330e0
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/wrench_orange.png
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/images/zoom.png b/lib/rdoc/generator/template/darkfish/images/zoom.png
new file mode 100644
index 0000000000..908612e394
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/images/zoom.png
Binary files differ
diff --git a/lib/rdoc/generator/template/darkfish/index.rhtml b/lib/rdoc/generator/template/darkfish/index.rhtml
new file mode 100644
index 0000000000..e853235ddb
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/index.rhtml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+ <meta content="text/html; charset=<%= @options.charset %>" http-equiv="Content-Type" />
+
+ <title><%= h @options.title %></title>
+
+ <link type="text/css" media="screen" href="rdoc.css" rel="stylesheet" />
+
+ <script src="js/jquery.js" type="text/javascript" charset="utf-8"></script>
+ <script src="js/thickbox-compressed.js" type="text/javascript" charset="utf-8"></script>
+ <script src="js/quicksearch.js" type="text/javascript" charset="utf-8"></script>
+ <script src="js/darkfish.js" type="text/javascript" charset="utf-8"></script>
+
+</head>
+<body class="indexpage">
+
+ <% $stderr.sync = true %>
+ <h1><%= h @options.title %></h1>
+
+ <% if @options.main_page && main_page = @files.find { |f| f.full_name == @options.main_page } %>
+ <div id="main">
+ <%= main_page.description.sub(%r{^\s*<h1.*?/h1>}i, '') %>
+ </div>
+ <% else %>
+ <p>This is the API documentation for '<%= @options.title %>'.</p>
+ <% end %>
+
+ <% simple_files = @files.select {|tl| tl.parser == RDoc::Parser::Simple } %>
+ <% unless simple_files.empty? then %>
+ <h2>Files</h2>
+ <ul>
+ <% simple_files.sort.each do |file| %>
+ <li class="file"><a href="<%= file.path %>"><%= h file.base_name %></a></li>
+ <% end %>
+ </ul>
+ <% end %>
+
+ <h2 id="classes">Classes/Modules</h2>
+ <ul>
+ <% @modsort.each do |klass| %>
+ <li class="<%= klass.type %>"><a href="<%= klass.path %>"><%= klass.full_name %></a></li>
+ <% end %>
+ </ul>
+
+ <h2 id="methods">Methods</h2>
+ <ul>
+ <% RDoc::TopLevel.all_classes_and_modules.map do |mod|
+ mod.method_list
+ end.flatten.sort.each do |method| %>
+ <li><a href="<%= method.path %>"><%= method.pretty_name %> &mdash; <%= method.parent.full_name %></a></li>
+ <% end %>
+ </ul>
+
+ <div id="validator-badges">
+ <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
+ <p><small>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish
+ Rdoc Generator</a> <%= RDoc::Generator::Darkfish::VERSION %></small>.</p>
+ </div>
+</body>
+</html>
diff --git a/lib/rdoc/generator/template/darkfish/js/darkfish.js b/lib/rdoc/generator/template/darkfish/js/darkfish.js
new file mode 100644
index 0000000000..43528fdde4
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/js/darkfish.js
@@ -0,0 +1,116 @@
+/**
+ *
+ * Darkfish Page Functions
+ * $Id: darkfish.js 53 2009-01-07 02:52:03Z deveiant $
+ *
+ * Author: Michael Granger <mgranger@laika.com>
+ *
+ */
+
+/* Provide console simulation for firebug-less environments */
+if (!("console" in window) || !("firebug" in console)) {
+ var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
+ "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
+
+ window.console = {};
+ for (var i = 0; i < names.length; ++i)
+ window.console[names[i]] = function() {};
+};
+
+
+/**
+ * Unwrap the first element that matches the given @expr@ from the targets and return them.
+ */
+$.fn.unwrap = function( expr ) {
+ return this.each( function() {
+ $(this).parents( expr ).eq( 0 ).after( this ).remove();
+ });
+};
+
+
+function showSource( e ) {
+ var target = e.target;
+ var codeSections = $(target).
+ parents('.method-detail').
+ find('.method-source-code');
+
+ $(target).
+ parents('.method-detail').
+ find('.method-source-code').
+ slideToggle();
+};
+
+function hookSourceViews() {
+ $('.method-description,.method-heading').click( showSource );
+};
+
+function toggleDebuggingSection() {
+ $('.debugging-section').slideToggle();
+};
+
+function hookDebuggingToggle() {
+ $('#debugging-toggle img').click( toggleDebuggingSection );
+};
+
+function hookQuickSearch() {
+ $('.quicksearch-field').each( function() {
+ var searchElems = $(this).parents('.section').find( 'li' );
+ var toggle = $(this).parents('.section').find('h3 .search-toggle');
+ // console.debug( "Toggle is: %o", toggle );
+ var qsbox = $(this).parents('form').get( 0 );
+
+ $(this).quicksearch( this, searchElems, {
+ noSearchResultsIndicator: 'no-class-search-results',
+ focusOnLoad: false
+ });
+ $(toggle).click( function() {
+ // console.debug( "Toggling qsbox: %o", qsbox );
+ $(qsbox).toggle();
+ });
+ });
+};
+
+function highlightTarget( anchor ) {
+ console.debug( "Highlighting target '%s'.", anchor );
+
+ $("a[name=" + anchor + "]").each( function() {
+ if ( !$(this).parent().parent().hasClass('target-section') ) {
+ console.debug( "Wrapping the target-section" );
+ $('div.method-detail').unwrap( 'div.target-section' );
+ $(this).parent().wrap( '<div class="target-section"></div>' );
+ } else {
+ console.debug( "Already wrapped." );
+ }
+ });
+};
+
+function highlightLocationTarget() {
+ console.debug( "Location hash: %s", window.location.hash );
+ if ( ! window.location.hash || window.location.hash.length == 0 ) return;
+
+ var anchor = window.location.hash.substring(1);
+ console.debug( "Found anchor: %s; matching %s", anchor, "a[name=" + anchor + "]" );
+
+ highlightTarget( anchor );
+};
+
+function highlightClickTarget( event ) {
+ console.debug( "Highlighting click target for event %o", event.target );
+ try {
+ var anchor = $(event.target).attr( 'href' ).substring(1);
+ console.debug( "Found target anchor: %s", anchor );
+ highlightTarget( anchor );
+ } catch ( err ) {
+ console.error( "Exception while highlighting: %o", err );
+ };
+};
+
+
+$(document).ready( function() {
+ hookSourceViews();
+ hookDebuggingToggle();
+ hookQuickSearch();
+ highlightLocationTarget();
+
+ $('ul.link-list a').bind( "click", highlightClickTarget );
+});
diff --git a/lib/rdoc/generator/template/darkfish/js/jquery.js b/lib/rdoc/generator/template/darkfish/js/jquery.js
new file mode 100644
index 0000000000..afe9e74c90
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/js/jquery.js
@@ -0,0 +1,32 @@
+/*
+ * jQuery 1.2.6 - New Wave Javascript
+ *
+ * Copyright (c) 2008 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2008-09-25 09:50:52 -0700 (Thu, 25 Sep 2008) $
+ * $Rev: 38 $
+ */
+(function(){var _jQuery=window.jQuery,_$=window.$;var jQuery=window.jQuery=window.$=function(selector,context){return new jQuery.fn.init(selector,context);};var quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,isSimple=/^.[^:#\[\.]*$/,undefined;jQuery.fn=jQuery.prototype={init:function(selector,context){selector=selector||document;if(selector.nodeType){this[0]=selector;this.length=1;return this;}if(typeof selector=="string"){var match=quickExpr.exec(selector);if(match&&(match[1]||!context)){if(match[1])selector=jQuery.clean([match[1]],context);else{var elem=document.getElementById(match[3]);if(elem){if(elem.id!=match[3])return jQuery().find(selector);return jQuery(elem);}selector=[];}}else
+return jQuery(context).find(selector);}else if(jQuery.isFunction(selector))return jQuery(document)[jQuery.fn.ready?"ready":"load"](selector);return this.setArray(jQuery.makeArray(selector));},jquery:"1.2.6",size:function(){return this.length;},length:0,get:function(num){return num==undefined?jQuery.makeArray(this):this[num];},pushStack:function(elems){var ret=jQuery(elems);ret.prevObject=this;return ret;},setArray:function(elems){this.length=0;Array.prototype.push.apply(this,elems);return this;},each:function(callback,args){return jQuery.each(this,callback,args);},index:function(elem){var ret=-1;return jQuery.inArray(elem&&elem.jquery?elem[0]:elem,this);},attr:function(name,value,type){var options=name;if(name.constructor==String)if(value===undefined)return this[0]&&jQuery[type||"attr"](this[0],name);else{options={};options[name]=value;}return this.each(function(i){for(name in options)jQuery.attr(type?this.style:this,name,jQuery.prop(this,options[name],type,i,name));});},css:function(key,value){if((key=='width'||key=='height')&&parseFloat(value)<0)value=undefined;return this.attr(key,value,"curCSS");},text:function(text){if(typeof text!="object"&&text!=null)return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(text));var ret="";jQuery.each(text||this,function(){jQuery.each(this.childNodes,function(){if(this.nodeType!=8)ret+=this.nodeType!=1?this.nodeValue:jQuery.fn.text([this]);});});return ret;},wrapAll:function(html){if(this[0])jQuery(html,this[0].ownerDocument).clone().insertBefore(this[0]).map(function(){var elem=this;while(elem.firstChild)elem=elem.firstChild;return elem;}).append(this);return this;},wrapInner:function(html){return this.each(function(){jQuery(this).contents().wrapAll(html);});},wrap:function(html){return this.each(function(){jQuery(this).wrapAll(html);});},append:function(){return this.domManip(arguments,true,false,function(elem){if(this.nodeType==1)this.appendChild(elem);});},prepend:function(){return this.domManip(arguments,true,true,function(elem){if(this.nodeType==1)this.insertBefore(elem,this.firstChild);});},before:function(){return this.domManip(arguments,false,false,function(elem){this.parentNode.insertBefore(elem,this);});},after:function(){return this.domManip(arguments,false,true,function(elem){this.parentNode.insertBefore(elem,this.nextSibling);});},end:function(){return this.prevObject||jQuery([]);},find:function(selector){var elems=jQuery.map(this,function(elem){return jQuery.find(selector,elem);});return this.pushStack(/[^+>] [^+>]/.test(selector)||selector.indexOf("..")>-1?jQuery.unique(elems):elems);},clone:function(events){var ret=this.map(function(){if(jQuery.browser.msie&&!jQuery.isXMLDoc(this)){var clone=this.cloneNode(true),container=document.createElement("div");container.appendChild(clone);return jQuery.clean([container.innerHTML])[0];}else
+return this.cloneNode(true);});var clone=ret.find("*").andSelf().each(function(){if(this[expando]!=undefined)this[expando]=null;});if(events===true)this.find("*").andSelf().each(function(i){if(this.nodeType==3)return;var events=jQuery.data(this,"events");for(var type in events)for(var handler in events[type])jQuery.event.add(clone[i],type,events[type][handler],events[type][handler].data);});return ret;},filter:function(selector){return this.pushStack(jQuery.isFunction(selector)&&jQuery.grep(this,function(elem,i){return selector.call(elem,i);})||jQuery.multiFilter(selector,this));},not:function(selector){if(selector.constructor==String)if(isSimple.test(selector))return this.pushStack(jQuery.multiFilter(selector,this,true));else
+selector=jQuery.multiFilter(selector,this);var isArrayLike=selector.length&&selector[selector.length-1]!==undefined&&!selector.nodeType;return this.filter(function(){return isArrayLike?jQuery.inArray(this,selector)<0:this!=selector;});},add:function(selector){return this.pushStack(jQuery.unique(jQuery.merge(this.get(),typeof selector=='string'?jQuery(selector):jQuery.makeArray(selector))));},is:function(selector){return!!selector&&jQuery.multiFilter(selector,this).length>0;},hasClass:function(selector){return this.is("."+selector);},val:function(value){if(value==undefined){if(this.length){var elem=this[0];if(jQuery.nodeName(elem,"select")){var index=elem.selectedIndex,values=[],options=elem.options,one=elem.type=="select-one";if(index<0)return null;for(var i=one?index:0,max=one?index+1:options.length;i<max;i++){var option=options[i];if(option.selected){value=jQuery.browser.msie&&!option.attributes.value.specified?option.text:option.value;if(one)return value;values.push(value);}}return values;}else
+return(this[0].value||"").replace(/\r/g,"");}return undefined;}if(value.constructor==Number)value+='';return this.each(function(){if(this.nodeType!=1)return;if(value.constructor==Array&&/radio|checkbox/.test(this.type))this.checked=(jQuery.inArray(this.value,value)>=0||jQuery.inArray(this.name,value)>=0);else if(jQuery.nodeName(this,"select")){var values=jQuery.makeArray(value);jQuery("option",this).each(function(){this.selected=(jQuery.inArray(this.value,values)>=0||jQuery.inArray(this.text,values)>=0);});if(!values.length)this.selectedIndex=-1;}else
+this.value=value;});},html:function(value){return value==undefined?(this[0]?this[0].innerHTML:null):this.empty().append(value);},replaceWith:function(value){return this.after(value).remove();},eq:function(i){return this.slice(i,i+1);},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments));},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem);}));},andSelf:function(){return this.add(this.prevObject);},data:function(key,value){var parts=key.split(".");parts[1]=parts[1]?"."+parts[1]:"";if(value===undefined){var data=this.triggerHandler("getData"+parts[1]+"!",[parts[0]]);if(data===undefined&&this.length)data=jQuery.data(this[0],key);return data===undefined&&parts[1]?this.data(parts[0]):data;}else
+return this.trigger("setData"+parts[1]+"!",[parts[0],value]).each(function(){jQuery.data(this,key,value);});},removeData:function(key){return this.each(function(){jQuery.removeData(this,key);});},domManip:function(args,table,reverse,callback){var clone=this.length>1,elems;return this.each(function(){if(!elems){elems=jQuery.clean(args,this.ownerDocument);if(reverse)elems.reverse();}var obj=this;if(table&&jQuery.nodeName(this,"table")&&jQuery.nodeName(elems[0],"tr"))obj=this.getElementsByTagName("tbody")[0]||this.appendChild(this.ownerDocument.createElement("tbody"));var scripts=jQuery([]);jQuery.each(elems,function(){var elem=clone?jQuery(this).clone(true)[0]:this;if(jQuery.nodeName(elem,"script"))scripts=scripts.add(elem);else{if(elem.nodeType==1)scripts=scripts.add(jQuery("script",elem).remove());callback.call(obj,elem);}});scripts.each(evalScript);});}};jQuery.fn.init.prototype=jQuery.fn;function evalScript(i,elem){if(elem.src)jQuery.ajax({url:elem.src,async:false,dataType:"script"});else
+jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");if(elem.parentNode)elem.parentNode.removeChild(elem);}function now(){return+new Date;}jQuery.extend=jQuery.fn.extend=function(){var target=arguments[0]||{},i=1,length=arguments.length,deep=false,options;if(target.constructor==Boolean){deep=target;target=arguments[1]||{};i=2;}if(typeof target!="object"&&typeof target!="function")target={};if(length==i){target=this;--i;}for(;i<length;i++)if((options=arguments[i])!=null)for(var name in options){var src=target[name],copy=options[name];if(target===copy)continue;if(deep&&copy&&typeof copy=="object"&&!copy.nodeType)target[name]=jQuery.extend(deep,src||(copy.length!=null?[]:{}),copy);else if(copy!==undefined)target[name]=copy;}return target;};var expando="jQuery"+now(),uuid=0,windowData={},exclude=/z-?index|font-?weight|opacity|zoom|line-?height/i,defaultView=document.defaultView||{};jQuery.extend({noConflict:function(deep){window.$=_$;if(deep)window.jQuery=_jQuery;return jQuery;},isFunction:function(fn){return!!fn&&typeof fn!="string"&&!fn.nodeName&&fn.constructor!=Array&&/^[\s[]?function/.test(fn+"");},isXMLDoc:function(elem){return elem.documentElement&&!elem.body||elem.tagName&&elem.ownerDocument&&!elem.ownerDocument.body;},globalEval:function(data){data=jQuery.trim(data);if(data){var head=document.getElementsByTagName("head")[0]||document.documentElement,script=document.createElement("script");script.type="text/javascript";if(jQuery.browser.msie)script.text=data;else
+script.appendChild(document.createTextNode(data));head.insertBefore(script,head.firstChild);head.removeChild(script);}},nodeName:function(elem,name){return elem.nodeName&&elem.nodeName.toUpperCase()==name.toUpperCase();},cache:{},data:function(elem,name,data){elem=elem==window?windowData:elem;var id=elem[expando];if(!id)id=elem[expando]=++uuid;if(name&&!jQuery.cache[id])jQuery.cache[id]={};if(data!==undefined)jQuery.cache[id][name]=data;return name?jQuery.cache[id][name]:id;},removeData:function(elem,name){elem=elem==window?windowData:elem;var id=elem[expando];if(name){if(jQuery.cache[id]){delete jQuery.cache[id][name];name="";for(name in jQuery.cache[id])break;if(!name)jQuery.removeData(elem);}}else{try{delete elem[expando];}catch(e){if(elem.removeAttribute)elem.removeAttribute(expando);}delete jQuery.cache[id];}},each:function(object,callback,args){var name,i=0,length=object.length;if(args){if(length==undefined){for(name in object)if(callback.apply(object[name],args)===false)break;}else
+for(;i<length;)if(callback.apply(object[i++],args)===false)break;}else{if(length==undefined){for(name in object)if(callback.call(object[name],name,object[name])===false)break;}else
+for(var value=object[0];i<length&&callback.call(value,i,value)!==false;value=object[++i]){}}return object;},prop:function(elem,value,type,i,name){if(jQuery.isFunction(value))value=value.call(elem,i);return value&&value.constructor==Number&&type=="curCSS"&&!exclude.test(name)?value+"px":value;},className:{add:function(elem,classNames){jQuery.each((classNames||"").split(/\s+/),function(i,className){if(elem.nodeType==1&&!jQuery.className.has(elem.className,className))elem.className+=(elem.className?" ":"")+className;});},remove:function(elem,classNames){if(elem.nodeType==1)elem.className=classNames!=undefined?jQuery.grep(elem.className.split(/\s+/),function(className){return!jQuery.className.has(classNames,className);}).join(" "):"";},has:function(elem,className){return jQuery.inArray(className,(elem.className||elem).toString().split(/\s+/))>-1;}},swap:function(elem,options,callback){var old={};for(var name in options){old[name]=elem.style[name];elem.style[name]=options[name];}callback.call(elem);for(var name in options)elem.style[name]=old[name];},css:function(elem,name,force){if(name=="width"||name=="height"){var val,props={position:"absolute",visibility:"hidden",display:"block"},which=name=="width"?["Left","Right"]:["Top","Bottom"];function getWH(){val=name=="width"?elem.offsetWidth:elem.offsetHeight;var padding=0,border=0;jQuery.each(which,function(){padding+=parseFloat(jQuery.curCSS(elem,"padding"+this,true))||0;border+=parseFloat(jQuery.curCSS(elem,"border"+this+"Width",true))||0;});val-=Math.round(padding+border);}if(jQuery(elem).is(":visible"))getWH();else
+jQuery.swap(elem,props,getWH);return Math.max(0,val);}return jQuery.curCSS(elem,name,force);},curCSS:function(elem,name,force){var ret,style=elem.style;function color(elem){if(!jQuery.browser.safari)return false;var ret=defaultView.getComputedStyle(elem,null);return!ret||ret.getPropertyValue("color")=="";}if(name=="opacity"&&jQuery.browser.msie){ret=jQuery.attr(style,"opacity");return ret==""?"1":ret;}if(jQuery.browser.opera&&name=="display"){var save=style.outline;style.outline="0 solid black";style.outline=save;}if(name.match(/float/i))name=styleFloat;if(!force&&style&&style[name])ret=style[name];else if(defaultView.getComputedStyle){if(name.match(/float/i))name="float";name=name.replace(/([A-Z])/g,"-$1").toLowerCase();var computedStyle=defaultView.getComputedStyle(elem,null);if(computedStyle&&!color(elem))ret=computedStyle.getPropertyValue(name);else{var swap=[],stack=[],a=elem,i=0;for(;a&&color(a);a=a.parentNode)stack.unshift(a);for(;i<stack.length;i++)if(color(stack[i])){swap[i]=stack[i].style.display;stack[i].style.display="block";}ret=name=="display"&&swap[stack.length-1]!=null?"none":(computedStyle&&computedStyle.getPropertyValue(name))||"";for(i=0;i<swap.length;i++)if(swap[i]!=null)stack[i].style.display=swap[i];}if(name=="opacity"&&ret=="")ret="1";}else if(elem.currentStyle){var camelCase=name.replace(/\-(\w)/g,function(all,letter){return letter.toUpperCase();});ret=elem.currentStyle[name]||elem.currentStyle[camelCase];if(!/^\d+(px)?$/i.test(ret)&&/^\d/.test(ret)){var left=style.left,rsLeft=elem.runtimeStyle.left;elem.runtimeStyle.left=elem.currentStyle.left;style.left=ret||0;ret=style.pixelLeft+"px";style.left=left;elem.runtimeStyle.left=rsLeft;}}return ret;},clean:function(elems,context){var ret=[];context=context||document;if(typeof context.createElement=='undefined')context=context.ownerDocument||context[0]&&context[0].ownerDocument||document;jQuery.each(elems,function(i,elem){if(!elem)return;if(elem.constructor==Number)elem+='';if(typeof elem=="string"){elem=elem.replace(/(<(\w+)[^>]*?)\/>/g,function(all,front,tag){return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?all:front+"></"+tag+">";});var tags=jQuery.trim(elem).toLowerCase(),div=context.createElement("div");var wrap=!tags.indexOf("<opt")&&[1,"<select multiple='multiple'>","</select>"]||!tags.indexOf("<leg")&&[1,"<fieldset>","</fieldset>"]||tags.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"<table>","</table>"]||!tags.indexOf("<tr")&&[2,"<table><tbody>","</tbody></table>"]||(!tags.indexOf("<td")||!tags.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||!tags.indexOf("<col")&&[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"]||jQuery.browser.msie&&[1,"div<div>","</div>"]||[0,"",""];div.innerHTML=wrap[1]+elem+wrap[2];while(wrap[0]--)div=div.lastChild;if(jQuery.browser.msie){var tbody=!tags.indexOf("<table")&&tags.indexOf("<tbody")<0?div.firstChild&&div.firstChild.childNodes:wrap[1]=="<table>"&&tags.indexOf("<tbody")<0?div.childNodes:[];for(var j=tbody.length-1;j>=0;--j)if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length)tbody[j].parentNode.removeChild(tbody[j]);if(/^\s/.test(elem))div.insertBefore(context.createTextNode(elem.match(/^\s*/)[0]),div.firstChild);}elem=jQuery.makeArray(div.childNodes);}if(elem.length===0&&(!jQuery.nodeName(elem,"form")&&!jQuery.nodeName(elem,"select")))return;if(elem[0]==undefined||jQuery.nodeName(elem,"form")||elem.options)ret.push(elem);else
+ret=jQuery.merge(ret,elem);});return ret;},attr:function(elem,name,value){if(!elem||elem.nodeType==3||elem.nodeType==8)return undefined;var notxml=!jQuery.isXMLDoc(elem),set=value!==undefined,msie=jQuery.browser.msie;name=notxml&&jQuery.props[name]||name;if(elem.tagName){var special=/href|src|style/.test(name);if(name=="selected"&&jQuery.browser.safari)elem.parentNode.selectedIndex;if(name in elem&&notxml&&!special){if(set){if(name=="type"&&jQuery.nodeName(elem,"input")&&elem.parentNode)throw"type property can't be changed";elem[name]=value;}if(jQuery.nodeName(elem,"form")&&elem.getAttributeNode(name))return elem.getAttributeNode(name).nodeValue;return elem[name];}if(msie&&notxml&&name=="style")return jQuery.attr(elem.style,"cssText",value);if(set)elem.setAttribute(name,""+value);var attr=msie&&notxml&&special?elem.getAttribute(name,2):elem.getAttribute(name);return attr===null?undefined:attr;}if(msie&&name=="opacity"){if(set){elem.zoom=1;elem.filter=(elem.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(value)+''=="NaN"?"":"alpha(opacity="+value*100+")");}return elem.filter&&elem.filter.indexOf("opacity=")>=0?(parseFloat(elem.filter.match(/opacity=([^)]*)/)[1])/100)+'':"";}name=name.replace(/-([a-z])/ig,function(all,letter){return letter.toUpperCase();});if(set)elem[name]=value;return elem[name];},trim:function(text){return(text||"").replace(/^\s+|\s+$/g,"");},makeArray:function(array){var ret=[];if(array!=null){var i=array.length;if(i==null||array.split||array.setInterval||array.call)ret[0]=array;else
+while(i)ret[--i]=array[i];}return ret;},inArray:function(elem,array){for(var i=0,length=array.length;i<length;i++)if(array[i]===elem)return i;return-1;},merge:function(first,second){var i=0,elem,pos=first.length;if(jQuery.browser.msie){while(elem=second[i++])if(elem.nodeType!=8)first[pos++]=elem;}else
+while(elem=second[i++])first[pos++]=elem;return first;},unique:function(array){var ret=[],done={};try{for(var i=0,length=array.length;i<length;i++){var id=jQuery.data(array[i]);if(!done[id]){done[id]=true;ret.push(array[i]);}}}catch(e){ret=array;}return ret;},grep:function(elems,callback,inv){var ret=[];for(var i=0,length=elems.length;i<length;i++)if(!inv!=!callback(elems[i],i))ret.push(elems[i]);return ret;},map:function(elems,callback){var ret=[];for(var i=0,length=elems.length;i<length;i++){var value=callback(elems[i],i);if(value!=null)ret[ret.length]=value;}return ret.concat.apply([],ret);}});var userAgent=navigator.userAgent.toLowerCase();jQuery.browser={version:(userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[])[1],safari:/webkit/.test(userAgent),opera:/opera/.test(userAgent),msie:/msie/.test(userAgent)&&!/opera/.test(userAgent),mozilla:/mozilla/.test(userAgent)&&!/(compatible|webkit)/.test(userAgent)};var styleFloat=jQuery.browser.msie?"styleFloat":"cssFloat";jQuery.extend({boxModel:!jQuery.browser.msie||document.compatMode=="CSS1Compat",props:{"for":"htmlFor","class":"className","float":styleFloat,cssFloat:styleFloat,styleFloat:styleFloat,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing"}});jQuery.each({parent:function(elem){return elem.parentNode;},parents:function(elem){return jQuery.dir(elem,"parentNode");},next:function(elem){return jQuery.nth(elem,2,"nextSibling");},prev:function(elem){return jQuery.nth(elem,2,"previousSibling");},nextAll:function(elem){return jQuery.dir(elem,"nextSibling");},prevAll:function(elem){return jQuery.dir(elem,"previousSibling");},siblings:function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},children:function(elem){return jQuery.sibling(elem.firstChild);},contents:function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}},function(name,fn){jQuery.fn[name]=function(selector){var ret=jQuery.map(this,fn);if(selector&&typeof selector=="string")ret=jQuery.multiFilter(selector,ret);return this.pushStack(jQuery.unique(ret));};});jQuery.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(name,original){jQuery.fn[name]=function(){var args=arguments;return this.each(function(){for(var i=0,length=args.length;i<length;i++)jQuery(args[i])[original](this);});};});jQuery.each({removeAttr:function(name){jQuery.attr(this,name,"");if(this.nodeType==1)this.removeAttribute(name);},addClass:function(classNames){jQuery.className.add(this,classNames);},removeClass:function(classNames){jQuery.className.remove(this,classNames);},toggleClass:function(classNames){jQuery.className[jQuery.className.has(this,classNames)?"remove":"add"](this,classNames);},remove:function(selector){if(!selector||jQuery.filter(selector,[this]).r.length){jQuery("*",this).add(this).each(function(){jQuery.event.remove(this);jQuery.removeData(this);});if(this.parentNode)this.parentNode.removeChild(this);}},empty:function(){jQuery(">*",this).remove();while(this.firstChild)this.removeChild(this.firstChild);}},function(name,fn){jQuery.fn[name]=function(){return this.each(fn,arguments);};});jQuery.each(["Height","Width"],function(i,name){var type=name.toLowerCase();jQuery.fn[type]=function(size){return this[0]==window?jQuery.browser.opera&&document.body["client"+name]||jQuery.browser.safari&&window["inner"+name]||document.compatMode=="CSS1Compat"&&document.documentElement["client"+name]||document.body["client"+name]:this[0]==document?Math.max(Math.max(document.body["scroll"+name],document.documentElement["scroll"+name]),Math.max(document.body["offset"+name],document.documentElement["offset"+name])):size==undefined?(this.length?jQuery.css(this[0],type):null):this.css(type,size.constructor==String?size:size+"px");};});function num(elem,prop){return elem[0]&&parseInt(jQuery.curCSS(elem[0],prop,true),10)||0;}var chars=jQuery.browser.safari&&parseInt(jQuery.browser.version)<417?"(?:[\\w*_-]|\\\\.)":"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",quickChild=new RegExp("^>\\s*("+chars+"+)"),quickID=new RegExp("^("+chars+"+)(#)("+chars+"+)"),quickClass=new RegExp("^([#.]?)("+chars+"*)");jQuery.extend({expr:{"":function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},"#":function(a,i,m){return a.getAttribute("id")==m[2];},":":{lt:function(a,i,m){return i<m[3]-0;},gt:function(a,i,m){return i>m[3]-0;},nth:function(a,i,m){return m[3]-0==i;},eq:function(a,i,m){return m[3]-0==i;},first:function(a,i){return i==0;},last:function(a,i,m,r){return i==r.length-1;},even:function(a,i){return i%2==0;},odd:function(a,i){return i%2;},"first-child":function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},"last-child":function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},"only-child":function(a){return!jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},parent:function(a){return a.firstChild;},empty:function(a){return!a.firstChild;},contains:function(a,i,m){return(a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},visible:function(a){return"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},hidden:function(a){return"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},enabled:function(a){return!a.disabled;},disabled:function(a){return a.disabled;},checked:function(a){return a.checked;},selected:function(a){return a.selected||jQuery.attr(a,"selected");},text:function(a){return"text"==a.type;},radio:function(a){return"radio"==a.type;},checkbox:function(a){return"checkbox"==a.type;},file:function(a){return"file"==a.type;},password:function(a){return"password"==a.type;},submit:function(a){return"submit"==a.type;},image:function(a){return"image"==a.type;},reset:function(a){return"reset"==a.type;},button:function(a){return"button"==a.type||jQuery.nodeName(a,"button");},input:function(a){return/input|select|textarea|button/i.test(a.nodeName);},has:function(a,i,m){return jQuery.find(m[3],a).length;},header:function(a){return/h\d/i.test(a.nodeName);},animated:function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}}},parse:[/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,new RegExp("^([:.#]*)("+chars+"+)")],multiFilter:function(expr,elems,not){var old,cur=[];while(expr&&expr!=old){old=expr;var f=jQuery.filter(expr,elems,not);expr=f.t.replace(/^\s*,\s*/,"");cur=not?elems=f.r:jQuery.merge(cur,f.r);}return cur;},find:function(t,context){if(typeof t!="string")return[t];if(context&&context.nodeType!=1&&context.nodeType!=9)return[];context=context||document;var ret=[context],done=[],last,nodeName;while(t&&last!=t){var r=[];last=t;t=jQuery.trim(t);var foundToken=false,re=quickChild,m=re.exec(t);if(m){nodeName=m[1].toUpperCase();for(var i=0;ret[i];i++)for(var c=ret[i].firstChild;c;c=c.nextSibling)if(c.nodeType==1&&(nodeName=="*"||c.nodeName.toUpperCase()==nodeName))r.push(c);ret=r;t=t.replace(re,"");if(t.indexOf(" ")==0)continue;foundToken=true;}else{re=/^([>+~])\s*(\w*)/i;if((m=re.exec(t))!=null){r=[];var merge={};nodeName=m[2].toUpperCase();m=m[1];for(var j=0,rl=ret.length;j<rl;j++){var n=m=="~"||m=="+"?ret[j].nextSibling:ret[j].firstChild;for(;n;n=n.nextSibling)if(n.nodeType==1){var id=jQuery.data(n);if(m=="~"&&merge[id])break;if(!nodeName||n.nodeName.toUpperCase()==nodeName){if(m=="~")merge[id]=true;r.push(n);}if(m=="+")break;}}ret=r;t=jQuery.trim(t.replace(re,""));foundToken=true;}}if(t&&!foundToken){if(!t.indexOf(",")){if(context==ret[0])ret.shift();done=jQuery.merge(done,ret);r=ret=[context];t=" "+t.substr(1,t.length);}else{var re2=quickID;var m=re2.exec(t);if(m){m=[0,m[2],m[3],m[1]];}else{re2=quickClass;m=re2.exec(t);}m[2]=m[2].replace(/\\/g,"");var elem=ret[ret.length-1];if(m[1]=="#"&&elem&&elem.getElementById&&!jQuery.isXMLDoc(elem)){var oid=elem.getElementById(m[2]);if((jQuery.browser.msie||jQuery.browser.opera)&&oid&&typeof oid.id=="string"&&oid.id!=m[2])oid=jQuery('[@id="'+m[2]+'"]',elem)[0];ret=r=oid&&(!m[3]||jQuery.nodeName(oid,m[3]))?[oid]:[];}else{for(var i=0;ret[i];i++){var tag=m[1]=="#"&&m[3]?m[3]:m[1]!=""||m[0]==""?"*":m[2];if(tag=="*"&&ret[i].nodeName.toLowerCase()=="object")tag="param";r=jQuery.merge(r,ret[i].getElementsByTagName(tag));}if(m[1]==".")r=jQuery.classFilter(r,m[2]);if(m[1]=="#"){var tmp=[];for(var i=0;r[i];i++)if(r[i].getAttribute("id")==m[2]){tmp=[r[i]];break;}r=tmp;}ret=r;}t=t.replace(re2,"");}}if(t){var val=jQuery.filter(t,r);ret=r=val.r;t=jQuery.trim(val.t);}}if(t)ret=[];if(ret&&context==ret[0])ret.shift();done=jQuery.merge(done,ret);return done;},classFilter:function(r,m,not){m=" "+m+" ";var tmp=[];for(var i=0;r[i];i++){var pass=(" "+r[i].className+" ").indexOf(m)>=0;if(!not&&pass||not&&!pass)tmp.push(r[i]);}return tmp;},filter:function(t,r,not){var last;while(t&&t!=last){last=t;var p=jQuery.parse,m;for(var i=0;p[i];i++){m=p[i].exec(t);if(m){t=t.substring(m[0].length);m[2]=m[2].replace(/\\/g,"");break;}}if(!m)break;if(m[1]==":"&&m[2]=="not")r=isSimple.test(m[3])?jQuery.filter(m[3],r,true).r:jQuery(r).not(m[3]);else if(m[1]==".")r=jQuery.classFilter(r,m[2],not);else if(m[1]=="["){var tmp=[],type=m[3];for(var i=0,rl=r.length;i<rl;i++){var a=r[i],z=a[jQuery.props[m[2]]||m[2]];if(z==null||/href|src|selected/.test(m[2]))z=jQuery.attr(a,m[2])||'';if((type==""&&!!z||type=="="&&z==m[5]||type=="!="&&z!=m[5]||type=="^="&&z&&!z.indexOf(m[5])||type=="$="&&z.substr(z.length-m[5].length)==m[5]||(type=="*="||type=="~=")&&z.indexOf(m[5])>=0)^not)tmp.push(a);}r=tmp;}else if(m[1]==":"&&m[2]=="nth-child"){var merge={},tmp=[],test=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(m[3]=="even"&&"2n"||m[3]=="odd"&&"2n+1"||!/\D/.test(m[3])&&"0n+"+m[3]||m[3]),first=(test[1]+(test[2]||1))-0,last=test[3]-0;for(var i=0,rl=r.length;i<rl;i++){var node=r[i],parentNode=node.parentNode,id=jQuery.data(parentNode);if(!merge[id]){var c=1;for(var n=parentNode.firstChild;n;n=n.nextSibling)if(n.nodeType==1)n.nodeIndex=c++;merge[id]=true;}var add=false;if(first==0){if(node.nodeIndex==last)add=true;}else if((node.nodeIndex-last)%first==0&&(node.nodeIndex-last)/first>=0)add=true;if(add^not)tmp.push(node);}r=tmp;}else{var fn=jQuery.expr[m[1]];if(typeof fn=="object")fn=fn[m[2]];if(typeof fn=="string")fn=eval("false||function(a,i){return "+fn+";}");r=jQuery.grep(r,function(elem,i){return fn(elem,i,m,r);},not);}}return{r:r,t:t};},dir:function(elem,dir){var matched=[],cur=elem[dir];while(cur&&cur!=document){if(cur.nodeType==1)matched.push(cur);cur=cur[dir];}return matched;},nth:function(cur,result,dir,elem){result=result||1;var num=0;for(;cur;cur=cur[dir])if(cur.nodeType==1&&++num==result)break;return cur;},sibling:function(n,elem){var r=[];for(;n;n=n.nextSibling){if(n.nodeType==1&&n!=elem)r.push(n);}return r;}});jQuery.event={add:function(elem,types,handler,data){if(elem.nodeType==3||elem.nodeType==8)return;if(jQuery.browser.msie&&elem.setInterval)elem=window;if(!handler.guid)handler.guid=this.guid++;if(data!=undefined){var fn=handler;handler=this.proxy(fn,function(){return fn.apply(this,arguments);});handler.data=data;}var events=jQuery.data(elem,"events")||jQuery.data(elem,"events",{}),handle=jQuery.data(elem,"handle")||jQuery.data(elem,"handle",function(){if(typeof jQuery!="undefined"&&!jQuery.event.triggered)return jQuery.event.handle.apply(arguments.callee.elem,arguments);});handle.elem=elem;jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];handler.type=parts[1];var handlers=events[type];if(!handlers){handlers=events[type]={};if(!jQuery.event.special[type]||jQuery.event.special[type].setup.call(elem)===false){if(elem.addEventListener)elem.addEventListener(type,handle,false);else if(elem.attachEvent)elem.attachEvent("on"+type,handle);}}handlers[handler.guid]=handler;jQuery.event.global[type]=true;});elem=null;},guid:1,global:{},remove:function(elem,types,handler){if(elem.nodeType==3||elem.nodeType==8)return;var events=jQuery.data(elem,"events"),ret,index;if(events){if(types==undefined||(typeof types=="string"&&types.charAt(0)=="."))for(var type in events)this.remove(elem,type+(types||""));else{if(types.type){handler=types.handler;types=types.type;}jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];if(events[type]){if(handler)delete events[type][handler.guid];else
+for(handler in events[type])if(!parts[1]||events[type][handler].type==parts[1])delete events[type][handler];for(ret in events[type])break;if(!ret){if(!jQuery.event.special[type]||jQuery.event.special[type].teardown.call(elem)===false){if(elem.removeEventListener)elem.removeEventListener(type,jQuery.data(elem,"handle"),false);else if(elem.detachEvent)elem.detachEvent("on"+type,jQuery.data(elem,"handle"));}ret=null;delete events[type];}}});}for(ret in events)break;if(!ret){var handle=jQuery.data(elem,"handle");if(handle)handle.elem=null;jQuery.removeData(elem,"events");jQuery.removeData(elem,"handle");}}},trigger:function(type,data,elem,donative,extra){data=jQuery.makeArray(data);if(type.indexOf("!")>=0){type=type.slice(0,-1);var exclusive=true;}if(!elem){if(this.global[type])jQuery("*").add([window,document]).trigger(type,data);}else{if(elem.nodeType==3||elem.nodeType==8)return undefined;var val,ret,fn=jQuery.isFunction(elem[type]||null),event=!data[0]||!data[0].preventDefault;if(event){data.unshift({type:type,target:elem,preventDefault:function(){},stopPropagation:function(){},timeStamp:now()});data[0][expando]=true;}data[0].type=type;if(exclusive)data[0].exclusive=true;var handle=jQuery.data(elem,"handle");if(handle)val=handle.apply(elem,data);if((!fn||(jQuery.nodeName(elem,'a')&&type=="click"))&&elem["on"+type]&&elem["on"+type].apply(elem,data)===false)val=false;if(event)data.shift();if(extra&&jQuery.isFunction(extra)){ret=extra.apply(elem,val==null?data:data.concat(val));if(ret!==undefined)val=ret;}if(fn&&donative!==false&&val!==false&&!(jQuery.nodeName(elem,'a')&&type=="click")){this.triggered=true;try{elem[type]();}catch(e){}}this.triggered=false;}return val;},handle:function(event){var val,ret,namespace,all,handlers;event=arguments[0]=jQuery.event.fix(event||window.event);namespace=event.type.split(".");event.type=namespace[0];namespace=namespace[1];all=!namespace&&!event.exclusive;handlers=(jQuery.data(this,"events")||{})[event.type];for(var j in handlers){var handler=handlers[j];if(all||handler.type==namespace){event.handler=handler;event.data=handler.data;ret=handler.apply(this,arguments);if(val!==false)val=ret;if(ret===false){event.preventDefault();event.stopPropagation();}}}return val;},fix:function(event){if(event[expando]==true)return event;var originalEvent=event;event={originalEvent:originalEvent};var props="altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which".split(" ");for(var i=props.length;i;i--)event[props[i]]=originalEvent[props[i]];event[expando]=true;event.preventDefault=function(){if(originalEvent.preventDefault)originalEvent.preventDefault();originalEvent.returnValue=false;};event.stopPropagation=function(){if(originalEvent.stopPropagation)originalEvent.stopPropagation();originalEvent.cancelBubble=true;};event.timeStamp=event.timeStamp||now();if(!event.target)event.target=event.srcElement||document;if(event.target.nodeType==3)event.target=event.target.parentNode;if(!event.relatedTarget&&event.fromElement)event.relatedTarget=event.fromElement==event.target?event.toElement:event.fromElement;if(event.pageX==null&&event.clientX!=null){var doc=document.documentElement,body=document.body;event.pageX=event.clientX+(doc&&doc.scrollLeft||body&&body.scrollLeft||0)-(doc.clientLeft||0);event.pageY=event.clientY+(doc&&doc.scrollTop||body&&body.scrollTop||0)-(doc.clientTop||0);}if(!event.which&&((event.charCode||event.charCode===0)?event.charCode:event.keyCode))event.which=event.charCode||event.keyCode;if(!event.metaKey&&event.ctrlKey)event.metaKey=event.ctrlKey;if(!event.which&&event.button)event.which=(event.button&1?1:(event.button&2?3:(event.button&4?2:0)));return event;},proxy:function(fn,proxy){proxy.guid=fn.guid=fn.guid||proxy.guid||this.guid++;return proxy;},special:{ready:{setup:function(){bindReady();return;},teardown:function(){return;}},mouseenter:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseover",jQuery.event.special.mouseenter.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseover",jQuery.event.special.mouseenter.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseenter";return jQuery.event.handle.apply(this,arguments);}},mouseleave:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseout",jQuery.event.special.mouseleave.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseout",jQuery.event.special.mouseleave.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseleave";return jQuery.event.handle.apply(this,arguments);}}}};jQuery.fn.extend({bind:function(type,data,fn){return type=="unload"?this.one(type,data,fn):this.each(function(){jQuery.event.add(this,type,fn||data,fn&&data);});},one:function(type,data,fn){var one=jQuery.event.proxy(fn||data,function(event){jQuery(this).unbind(event,one);return(fn||data).apply(this,arguments);});return this.each(function(){jQuery.event.add(this,type,one,fn&&data);});},unbind:function(type,fn){return this.each(function(){jQuery.event.remove(this,type,fn);});},trigger:function(type,data,fn){return this.each(function(){jQuery.event.trigger(type,data,this,true,fn);});},triggerHandler:function(type,data,fn){return this[0]&&jQuery.event.trigger(type,data,this[0],false,fn);},toggle:function(fn){var args=arguments,i=1;while(i<args.length)jQuery.event.proxy(fn,args[i++]);return this.click(jQuery.event.proxy(fn,function(event){this.lastToggle=(this.lastToggle||0)%i;event.preventDefault();return args[this.lastToggle++].apply(this,arguments)||false;}));},hover:function(fnOver,fnOut){return this.bind('mouseenter',fnOver).bind('mouseleave',fnOut);},ready:function(fn){bindReady();if(jQuery.isReady)fn.call(document,jQuery);else
+jQuery.readyList.push(function(){return fn.call(this,jQuery);});return this;}});jQuery.extend({isReady:false,readyList:[],ready:function(){if(!jQuery.isReady){jQuery.isReady=true;if(jQuery.readyList){jQuery.each(jQuery.readyList,function(){this.call(document);});jQuery.readyList=null;}jQuery(document).triggerHandler("ready");}}});var readyBound=false;function bindReady(){if(readyBound)return;readyBound=true;if(document.addEventListener&&!jQuery.browser.opera)document.addEventListener("DOMContentLoaded",jQuery.ready,false);if(jQuery.browser.msie&&window==top)(function(){if(jQuery.isReady)return;try{document.documentElement.doScroll("left");}catch(error){setTimeout(arguments.callee,0);return;}jQuery.ready();})();if(jQuery.browser.opera)document.addEventListener("DOMContentLoaded",function(){if(jQuery.isReady)return;for(var i=0;i<document.styleSheets.length;i++)if(document.styleSheets[i].disabled){setTimeout(arguments.callee,0);return;}jQuery.ready();},false);if(jQuery.browser.safari){var numStyles;(function(){if(jQuery.isReady)return;if(document.readyState!="loaded"&&document.readyState!="complete"){setTimeout(arguments.callee,0);return;}if(numStyles===undefined)numStyles=jQuery("style, link[rel=stylesheet]").length;if(document.styleSheets.length!=numStyles){setTimeout(arguments.callee,0);return;}jQuery.ready();})();}jQuery.event.add(window,"load",jQuery.ready);}jQuery.each(("blur,focus,load,resize,scroll,unload,click,dblclick,"+"mousedown,mouseup,mousemove,mouseover,mouseout,change,select,"+"submit,keydown,keypress,keyup,error").split(","),function(i,name){jQuery.fn[name]=function(fn){return fn?this.bind(name,fn):this.trigger(name);};});var withinElement=function(event,elem){var parent=event.relatedTarget;while(parent&&parent!=elem)try{parent=parent.parentNode;}catch(error){parent=elem;}return parent==elem;};jQuery(window).bind("unload",function(){jQuery("*").add(document).unbind();});jQuery.fn.extend({_load:jQuery.fn.load,load:function(url,params,callback){if(typeof url!='string')return this._load(url);var off=url.indexOf(" ");if(off>=0){var selector=url.slice(off,url.length);url=url.slice(0,off);}callback=callback||function(){};var type="GET";if(params)if(jQuery.isFunction(params)){callback=params;params=null;}else{params=jQuery.param(params);type="POST";}var self=this;jQuery.ajax({url:url,type:type,dataType:"html",data:params,complete:function(res,status){if(status=="success"||status=="notmodified")self.html(selector?jQuery("<div/>").append(res.responseText.replace(/<script(.|\s)*?\/script>/g,"")).find(selector):res.responseText);self.each(callback,[res.responseText,status,res]);}});return this;},serialize:function(){return jQuery.param(this.serializeArray());},serializeArray:function(){return this.map(function(){return jQuery.nodeName(this,"form")?jQuery.makeArray(this.elements):this;}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type));}).map(function(i,elem){var val=jQuery(this).val();return val==null?null:val.constructor==Array?jQuery.map(val,function(val,i){return{name:elem.name,value:val};}):{name:elem.name,value:val};}).get();}});jQuery.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(i,o){jQuery.fn[o]=function(f){return this.bind(o,f);};});var jsc=now();jQuery.extend({get:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data=null;}return jQuery.ajax({type:"GET",url:url,data:data,success:callback,dataType:type});},getScript:function(url,callback){return jQuery.get(url,null,callback,"script");},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json");},post:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data={};}return jQuery.ajax({type:"POST",url:url,data:data,success:callback,dataType:type});},ajaxSetup:function(settings){jQuery.extend(jQuery.ajaxSettings,settings);},ajaxSettings:{url:location.href,global:true,type:"GET",timeout:0,contentType:"application/x-www-form-urlencoded",processData:true,async:true,data:null,username:null,password:null,accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(s){s=jQuery.extend(true,s,jQuery.extend(true,{},jQuery.ajaxSettings,s));var jsonp,jsre=/=\?(&|$)/g,status,data,type=s.type.toUpperCase();if(s.data&&s.processData&&typeof s.data!="string")s.data=jQuery.param(s.data);if(s.dataType=="jsonp"){if(type=="GET"){if(!s.url.match(jsre))s.url+=(s.url.match(/\?/)?"&":"?")+(s.jsonp||"callback")+"=?";}else if(!s.data||!s.data.match(jsre))s.data=(s.data?s.data+"&":"")+(s.jsonp||"callback")+"=?";s.dataType="json";}if(s.dataType=="json"&&(s.data&&s.data.match(jsre)||s.url.match(jsre))){jsonp="jsonp"+jsc++;if(s.data)s.data=(s.data+"").replace(jsre,"="+jsonp+"$1");s.url=s.url.replace(jsre,"="+jsonp+"$1");s.dataType="script";window[jsonp]=function(tmp){data=tmp;success();complete();window[jsonp]=undefined;try{delete window[jsonp];}catch(e){}if(head)head.removeChild(script);};}if(s.dataType=="script"&&s.cache==null)s.cache=false;if(s.cache===false&&type=="GET"){var ts=now();var ret=s.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+ts+"$2");s.url=ret+((ret==s.url)?(s.url.match(/\?/)?"&":"?")+"_="+ts:"");}if(s.data&&type=="GET"){s.url+=(s.url.match(/\?/)?"&":"?")+s.data;s.data=null;}if(s.global&&!jQuery.active++)jQuery.event.trigger("ajaxStart");var remote=/^(?:\w+:)?\/\/([^\/?#]+)/;if(s.dataType=="script"&&type=="GET"&&remote.test(s.url)&&remote.exec(s.url)[1]!=location.host){var head=document.getElementsByTagName("head")[0];var script=document.createElement("script");script.src=s.url;if(s.scriptCharset)script.charset=s.scriptCharset;if(!jsonp){var done=false;script.onload=script.onreadystatechange=function(){if(!done&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){done=true;success();complete();head.removeChild(script);}};}head.appendChild(script);return undefined;}var requestDone=false;var xhr=window.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest();if(s.username)xhr.open(type,s.url,s.async,s.username,s.password);else
+xhr.open(type,s.url,s.async);try{if(s.data)xhr.setRequestHeader("Content-Type",s.contentType);if(s.ifModified)xhr.setRequestHeader("If-Modified-Since",jQuery.lastModified[s.url]||"Thu, 01 Jan 1970 00:00:00 GMT");xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Accept",s.dataType&&s.accepts[s.dataType]?s.accepts[s.dataType]+", */*":s.accepts._default);}catch(e){}if(s.beforeSend&&s.beforeSend(xhr,s)===false){s.global&&jQuery.active--;xhr.abort();return false;}if(s.global)jQuery.event.trigger("ajaxSend",[xhr,s]);var onreadystatechange=function(isTimeout){if(!requestDone&&xhr&&(xhr.readyState==4||isTimeout=="timeout")){requestDone=true;if(ival){clearInterval(ival);ival=null;}status=isTimeout=="timeout"&&"timeout"||!jQuery.httpSuccess(xhr)&&"error"||s.ifModified&&jQuery.httpNotModified(xhr,s.url)&&"notmodified"||"success";if(status=="success"){try{data=jQuery.httpData(xhr,s.dataType,s.dataFilter);}catch(e){status="parsererror";}}if(status=="success"){var modRes;try{modRes=xhr.getResponseHeader("Last-Modified");}catch(e){}if(s.ifModified&&modRes)jQuery.lastModified[s.url]=modRes;if(!jsonp)success();}else
+jQuery.handleError(s,xhr,status);complete();if(s.async)xhr=null;}};if(s.async){var ival=setInterval(onreadystatechange,13);if(s.timeout>0)setTimeout(function(){if(xhr){xhr.abort();if(!requestDone)onreadystatechange("timeout");}},s.timeout);}try{xhr.send(s.data);}catch(e){jQuery.handleError(s,xhr,null,e);}if(!s.async)onreadystatechange();function success(){if(s.success)s.success(data,status);if(s.global)jQuery.event.trigger("ajaxSuccess",[xhr,s]);}function complete(){if(s.complete)s.complete(xhr,status);if(s.global)jQuery.event.trigger("ajaxComplete",[xhr,s]);if(s.global&&!--jQuery.active)jQuery.event.trigger("ajaxStop");}return xhr;},handleError:function(s,xhr,status,e){if(s.error)s.error(xhr,status,e);if(s.global)jQuery.event.trigger("ajaxError",[xhr,s,e]);},active:0,httpSuccess:function(xhr){try{return!xhr.status&&location.protocol=="file:"||(xhr.status>=200&&xhr.status<300)||xhr.status==304||xhr.status==1223||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpNotModified:function(xhr,url){try{var xhrRes=xhr.getResponseHeader("Last-Modified");return xhr.status==304||xhrRes==jQuery.lastModified[url]||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpData:function(xhr,type,filter){var ct=xhr.getResponseHeader("content-type"),xml=type=="xml"||!type&&ct&&ct.indexOf("xml")>=0,data=xml?xhr.responseXML:xhr.responseText;if(xml&&data.documentElement.tagName=="parsererror")throw"parsererror";if(filter)data=filter(data,type);if(type=="script")jQuery.globalEval(data);if(type=="json")data=eval("("+data+")");return data;},param:function(a){var s=[];if(a.constructor==Array||a.jquery)jQuery.each(a,function(){s.push(encodeURIComponent(this.name)+"="+encodeURIComponent(this.value));});else
+for(var j in a)if(a[j]&&a[j].constructor==Array)jQuery.each(a[j],function(){s.push(encodeURIComponent(j)+"="+encodeURIComponent(this));});else
+s.push(encodeURIComponent(j)+"="+encodeURIComponent(jQuery.isFunction(a[j])?a[j]():a[j]));return s.join("&").replace(/%20/g,"+");}});jQuery.fn.extend({show:function(speed,callback){return speed?this.animate({height:"show",width:"show",opacity:"show"},speed,callback):this.filter(":hidden").each(function(){this.style.display=this.oldblock||"";if(jQuery.css(this,"display")=="none"){var elem=jQuery("<"+this.tagName+" />").appendTo("body");this.style.display=elem.css("display");if(this.style.display=="none")this.style.display="block";elem.remove();}}).end();},hide:function(speed,callback){return speed?this.animate({height:"hide",width:"hide",opacity:"hide"},speed,callback):this.filter(":visible").each(function(){this.oldblock=this.oldblock||jQuery.css(this,"display");this.style.display="none";}).end();},_toggle:jQuery.fn.toggle,toggle:function(fn,fn2){return jQuery.isFunction(fn)&&jQuery.isFunction(fn2)?this._toggle.apply(this,arguments):fn?this.animate({height:"toggle",width:"toggle",opacity:"toggle"},fn,fn2):this.each(function(){jQuery(this)[jQuery(this).is(":hidden")?"show":"hide"]();});},slideDown:function(speed,callback){return this.animate({height:"show"},speed,callback);},slideUp:function(speed,callback){return this.animate({height:"hide"},speed,callback);},slideToggle:function(speed,callback){return this.animate({height:"toggle"},speed,callback);},fadeIn:function(speed,callback){return this.animate({opacity:"show"},speed,callback);},fadeOut:function(speed,callback){return this.animate({opacity:"hide"},speed,callback);},fadeTo:function(speed,to,callback){return this.animate({opacity:to},speed,callback);},animate:function(prop,speed,easing,callback){var optall=jQuery.speed(speed,easing,callback);return this[optall.queue===false?"each":"queue"](function(){if(this.nodeType!=1)return false;var opt=jQuery.extend({},optall),p,hidden=jQuery(this).is(":hidden"),self=this;for(p in prop){if(prop[p]=="hide"&&hidden||prop[p]=="show"&&!hidden)return opt.complete.call(this);if(p=="height"||p=="width"){opt.display=jQuery.css(this,"display");opt.overflow=this.style.overflow;}}if(opt.overflow!=null)this.style.overflow="hidden";opt.curAnim=jQuery.extend({},prop);jQuery.each(prop,function(name,val){var e=new jQuery.fx(self,opt,name);if(/toggle|show|hide/.test(val))e[val=="toggle"?hidden?"show":"hide":val](prop);else{var parts=val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),start=e.cur(true)||0;if(parts){var end=parseFloat(parts[2]),unit=parts[3]||"px";if(unit!="px"){self.style[name]=(end||1)+unit;start=((end||1)/e.cur(true))*start;self.style[name]=start+unit;}if(parts[1])end=((parts[1]=="-="?-1:1)*end)+start;e.custom(start,end,unit);}else
+e.custom(start,val,"");}});return true;});},queue:function(type,fn){if(jQuery.isFunction(type)||(type&&type.constructor==Array)){fn=type;type="fx";}if(!type||(typeof type=="string"&&!fn))return queue(this[0],type);return this.each(function(){if(fn.constructor==Array)queue(this,type,fn);else{queue(this,type).push(fn);if(queue(this,type).length==1)fn.call(this);}});},stop:function(clearQueue,gotoEnd){var timers=jQuery.timers;if(clearQueue)this.queue([]);this.each(function(){for(var i=timers.length-1;i>=0;i--)if(timers[i].elem==this){if(gotoEnd)timers[i](true);timers.splice(i,1);}});if(!gotoEnd)this.dequeue();return this;}});var queue=function(elem,type,array){if(elem){type=type||"fx";var q=jQuery.data(elem,type+"queue");if(!q||array)q=jQuery.data(elem,type+"queue",jQuery.makeArray(array));}return q;};jQuery.fn.dequeue=function(type){type=type||"fx";return this.each(function(){var q=queue(this,type);q.shift();if(q.length)q[0].call(this);});};jQuery.extend({speed:function(speed,easing,fn){var opt=speed&&speed.constructor==Object?speed:{complete:fn||!fn&&easing||jQuery.isFunction(speed)&&speed,duration:speed,easing:fn&&easing||easing&&easing.constructor!=Function&&easing};opt.duration=(opt.duration&&opt.duration.constructor==Number?opt.duration:jQuery.fx.speeds[opt.duration])||jQuery.fx.speeds.def;opt.old=opt.complete;opt.complete=function(){if(opt.queue!==false)jQuery(this).dequeue();if(jQuery.isFunction(opt.old))opt.old.call(this);};return opt;},easing:{linear:function(p,n,firstNum,diff){return firstNum+diff*p;},swing:function(p,n,firstNum,diff){return((-Math.cos(p*Math.PI)/2)+0.5)*diff+firstNum;}},timers:[],timerId:null,fx:function(elem,options,prop){this.options=options;this.elem=elem;this.prop=prop;if(!options.orig)options.orig={};}});jQuery.fx.prototype={update:function(){if(this.options.step)this.options.step.call(this.elem,this.now,this);(jQuery.fx.step[this.prop]||jQuery.fx.step._default)(this);if(this.prop=="height"||this.prop=="width")this.elem.style.display="block";},cur:function(force){if(this.elem[this.prop]!=null&&this.elem.style[this.prop]==null)return this.elem[this.prop];var r=parseFloat(jQuery.css(this.elem,this.prop,force));return r&&r>-10000?r:parseFloat(jQuery.curCSS(this.elem,this.prop))||0;},custom:function(from,to,unit){this.startTime=now();this.start=from;this.end=to;this.unit=unit||this.unit||"px";this.now=this.start;this.pos=this.state=0;this.update();var self=this;function t(gotoEnd){return self.step(gotoEnd);}t.elem=this.elem;jQuery.timers.push(t);if(jQuery.timerId==null){jQuery.timerId=setInterval(function(){var timers=jQuery.timers;for(var i=0;i<timers.length;i++)if(!timers[i]())timers.splice(i--,1);if(!timers.length){clearInterval(jQuery.timerId);jQuery.timerId=null;}},13);}},show:function(){this.options.orig[this.prop]=jQuery.attr(this.elem.style,this.prop);this.options.show=true;this.custom(0,this.cur());if(this.prop=="width"||this.prop=="height")this.elem.style[this.prop]="1px";jQuery(this.elem).show();},hide:function(){this.options.orig[this.prop]=jQuery.attr(this.elem.style,this.prop);this.options.hide=true;this.custom(this.cur(),0);},step:function(gotoEnd){var t=now();if(gotoEnd||t>this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var done=true;for(var i in this.options.curAnim)if(this.options.curAnim[i]!==true)done=false;if(done){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(jQuery.css(this.elem,"display")=="none")this.elem.style.display="block";}if(this.options.hide)this.elem.style.display="none";if(this.options.hide||this.options.show)for(var p in this.options.curAnim)jQuery.attr(this.elem.style,p,this.options.orig[p]);}if(done)this.options.complete.call(this.elem);return false;}else{var n=t-this.startTime;this.state=n/this.options.duration;this.pos=jQuery.easing[this.options.easing||(jQuery.easing.swing?"swing":"linear")](this.state,n,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update();}return true;}};jQuery.extend(jQuery.fx,{speeds:{slow:600,fast:200,def:400},step:{scrollLeft:function(fx){fx.elem.scrollLeft=fx.now;},scrollTop:function(fx){fx.elem.scrollTop=fx.now;},opacity:function(fx){jQuery.attr(fx.elem.style,"opacity",fx.now);},_default:function(fx){fx.elem.style[fx.prop]=fx.now+fx.unit;}}});jQuery.fn.offset=function(){var left=0,top=0,elem=this[0],results;if(elem)with(jQuery.browser){var parent=elem.parentNode,offsetChild=elem,offsetParent=elem.offsetParent,doc=elem.ownerDocument,safari2=safari&&parseInt(version)<522&&!/adobeair/i.test(userAgent),css=jQuery.curCSS,fixed=css(elem,"position")=="fixed";if(elem.getBoundingClientRect){var box=elem.getBoundingClientRect();add(box.left+Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),box.top+Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));add(-doc.documentElement.clientLeft,-doc.documentElement.clientTop);}else{add(elem.offsetLeft,elem.offsetTop);while(offsetParent){add(offsetParent.offsetLeft,offsetParent.offsetTop);if(mozilla&&!/^t(able|d|h)$/i.test(offsetParent.tagName)||safari&&!safari2)border(offsetParent);if(!fixed&&css(offsetParent,"position")=="fixed")fixed=true;offsetChild=/^body$/i.test(offsetParent.tagName)?offsetChild:offsetParent;offsetParent=offsetParent.offsetParent;}while(parent&&parent.tagName&&!/^body|html$/i.test(parent.tagName)){if(!/^inline|table.*$/i.test(css(parent,"display")))add(-parent.scrollLeft,-parent.scrollTop);if(mozilla&&css(parent,"overflow")!="visible")border(parent);parent=parent.parentNode;}if((safari2&&(fixed||css(offsetChild,"position")=="absolute"))||(mozilla&&css(offsetChild,"position")!="absolute"))add(-doc.body.offsetLeft,-doc.body.offsetTop);if(fixed)add(Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));}results={top:top,left:left};}function border(elem){add(jQuery.curCSS(elem,"borderLeftWidth",true),jQuery.curCSS(elem,"borderTopWidth",true));}function add(l,t){left+=parseInt(l,10)||0;top+=parseInt(t,10)||0;}return results;};jQuery.fn.extend({position:function(){var left=0,top=0,results;if(this[0]){var offsetParent=this.offsetParent(),offset=this.offset(),parentOffset=/^body|html$/i.test(offsetParent[0].tagName)?{top:0,left:0}:offsetParent.offset();offset.top-=num(this,'marginTop');offset.left-=num(this,'marginLeft');parentOffset.top+=num(offsetParent,'borderTopWidth');parentOffset.left+=num(offsetParent,'borderLeftWidth');results={top:offset.top-parentOffset.top,left:offset.left-parentOffset.left};}return results;},offsetParent:function(){var offsetParent=this[0].offsetParent;while(offsetParent&&(!/^body|html$/i.test(offsetParent.tagName)&&jQuery.css(offsetParent,'position')=='static'))offsetParent=offsetParent.offsetParent;return jQuery(offsetParent);}});jQuery.each(['Left','Top'],function(i,name){var method='scroll'+name;jQuery.fn[method]=function(val){if(!this[0])return;return val!=undefined?this.each(function(){this==window||this==document?window.scrollTo(!i?val:jQuery(window).scrollLeft(),i?val:jQuery(window).scrollTop()):this[method]=val;}):this[0]==window||this[0]==document?self[i?'pageYOffset':'pageXOffset']||jQuery.boxModel&&document.documentElement[method]||document.body[method]:this[0][method];};});jQuery.each(["Height","Width"],function(i,name){var tl=i?"Left":"Top",br=i?"Right":"Bottom";jQuery.fn["inner"+name]=function(){return this[name.toLowerCase()]()+num(this,"padding"+tl)+num(this,"padding"+br);};jQuery.fn["outer"+name]=function(margin){return this["inner"+name]()+num(this,"border"+tl+"Width")+num(this,"border"+br+"Width")+(margin?num(this,"margin"+tl)+num(this,"margin"+br):0);};});})(); \ No newline at end of file
diff --git a/lib/rdoc/generator/template/darkfish/js/quicksearch.js b/lib/rdoc/generator/template/darkfish/js/quicksearch.js
new file mode 100644
index 0000000000..332772ac71
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/js/quicksearch.js
@@ -0,0 +1,114 @@
+/**
+ *
+ * JQuery QuickSearch - Hook up a form field to hide non-matching elements.
+ * $Id: quicksearch.js 53 2009-01-07 02:52:03Z deveiant $
+ *
+ * Author: Michael Granger <mgranger@laika.com>
+ *
+ */
+jQuery.fn.quicksearch = function( target, searchElems, options ) {
+ // console.debug( "Quicksearch fn" );
+
+ var settings = {
+ delay: 250,
+ clearButton: false,
+ highlightMatches: false,
+ focusOnLoad: false,
+ noSearchResultsIndicator: null
+ };
+ if ( options ) $.extend( settings, options );
+
+ return jQuery(this).each( function() {
+ // console.debug( "Creating a new quicksearch on %o for %o", this, searchElems );
+ new jQuery.quicksearch( this, searchElems, settings );
+ });
+};
+
+
+jQuery.quicksearch = function( searchBox, searchElems, settings ) {
+ var timeout;
+ var boxdiv = $(searchBox).parents('div').eq(0);
+
+ function init() {
+ setupKeyEventHandlers();
+ focusOnLoad();
+ };
+
+ function setupKeyEventHandlers() {
+ // console.debug( "Hooking up the 'keypress' event to %o", searchBox );
+ $(searchBox).
+ unbind( 'keyup' ).
+ keyup( function(e) { return onSearchKey( e.keyCode ); });
+ $(searchBox).
+ unbind( 'keypress' ).
+ keypress( function(e) {
+ switch( e.which ) {
+ // Execute the search on Enter, Tab, or Newline
+ case 9:
+ case 13:
+ case 10:
+ clearTimeout( timeout );
+ e.preventDefault();
+ doQuickSearch();
+ break;
+
+ // Allow backspace
+ case 8:
+ return true;
+ break;
+
+ // Only allow valid search characters
+ default:
+ return validQSChar( e.charCode );
+ }
+ });
+ };
+
+ function focusOnLoad() {
+ if ( !settings.focusOnLoad ) return false;
+ $(searchBox).focus();
+ };
+
+ function onSearchKey ( code ) {
+ clearTimeout( timeout );
+ // console.debug( "...scheduling search." );
+ timeout = setTimeout( doQuickSearch, settings.delay );
+ };
+
+ function validQSChar( code ) {
+ var c = String.fromCharCode( code );
+ return (
+ (c == ':') ||
+ (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z')
+ );
+ };
+
+ function doQuickSearch() {
+ var searchText = searchBox.value;
+ var pat = new RegExp( searchText, "im" );
+ var shownCount = 0;
+
+ if ( settings.noSearchResultsIndicator ) {
+ $('#' + settings.noSearchResultsIndicator).hide();
+ }
+
+ // All elements start out hidden
+ $(searchElems).each( function(index) {
+ var str = $(this).text();
+
+ if ( pat.test(str) ) {
+ shownCount += 1;
+ $(this).fadeIn();
+ } else {
+ $(this).hide();
+ }
+ });
+
+ if ( shownCount == 0 && settings.noSearchResultsIndicator ) {
+ $('#' + settings.noSearchResultsIndicator).slideDown();
+ }
+ };
+
+ init();
+};
diff --git a/lib/rdoc/generator/template/darkfish/js/thickbox-compressed.js b/lib/rdoc/generator/template/darkfish/js/thickbox-compressed.js
new file mode 100644
index 0000000000..3a3fdae1fb
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/js/thickbox-compressed.js
@@ -0,0 +1,10 @@
+/*
+ * Thickbox 3 - One Box To Rule Them All.
+ * By Cody Lindley (http://www.codylindley.com)
+ * Copyright (c) 2007 cody lindley
+ * Licensed under the MIT License: http://www.opensource.org/licenses/mit-license.php
+*/
+
+var tb_pathToImage = "../images/loadingAnimation.gif";
+
+eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('$(o).2S(9(){1u(\'a.18, 3n.18, 3i.18\');1w=1p 1t();1w.L=2H});9 1u(b){$(b).s(9(){6 t=X.Q||X.1v||M;6 a=X.u||X.23;6 g=X.1N||P;19(t,a,g);X.2E();H P})}9 19(d,f,g){3m{3(2t o.v.J.2i==="2g"){$("v","11").r({A:"28%",z:"28%"});$("11").r("22","2Z");3(o.1Y("1F")===M){$("v").q("<U 5=\'1F\'></U><4 5=\'B\'></4><4 5=\'8\'></4>");$("#B").s(G)}}n{3(o.1Y("B")===M){$("v").q("<4 5=\'B\'></4><4 5=\'8\'></4>");$("#B").s(G)}}3(1K()){$("#B").1J("2B")}n{$("#B").1J("2z")}3(d===M){d=""}$("v").q("<4 5=\'K\'><1I L=\'"+1w.L+"\' /></4>");$(\'#K\').2y();6 h;3(f.O("?")!==-1){h=f.3l(0,f.O("?"))}n{h=f}6 i=/\\.2s$|\\.2q$|\\.2m$|\\.2l$|\\.2k$/;6 j=h.1C().2h(i);3(j==\'.2s\'||j==\'.2q\'||j==\'.2m\'||j==\'.2l\'||j==\'.2k\'){1D="";1G="";14="";1z="";1x="";R="";1n="";1r=P;3(g){E=$("a[@1N="+g+"]").36();25(D=0;((D<E.1c)&&(R===""));D++){6 k=E[D].u.1C().2h(i);3(!(E[D].u==f)){3(1r){1z=E[D].Q;1x=E[D].u;R="<1e 5=\'1X\'>&1d;&1d;<a u=\'#\'>2T &2R;</a></1e>"}n{1D=E[D].Q;1G=E[D].u;14="<1e 5=\'1U\'>&1d;&1d;<a u=\'#\'>&2O; 2N</a></1e>"}}n{1r=1b;1n="1t "+(D+1)+" 2L "+(E.1c)}}}S=1p 1t();S.1g=9(){S.1g=M;6 a=2x();6 x=a[0]-1M;6 y=a[1]-1M;6 b=S.z;6 c=S.A;3(b>x){c=c*(x/b);b=x;3(c>y){b=b*(y/c);c=y}}n 3(c>y){b=b*(y/c);c=y;3(b>x){c=c*(x/b);b=x}}13=b+30;1a=c+2G;$("#8").q("<a u=\'\' 5=\'1L\' Q=\'1o\'><1I 5=\'2F\' L=\'"+f+"\' z=\'"+b+"\' A=\'"+c+"\' 23=\'"+d+"\'/></a>"+"<4 5=\'2D\'>"+d+"<4 5=\'2C\'>"+1n+14+R+"</4></4><4 5=\'2A\'><a u=\'#\' 5=\'Z\' Q=\'1o\'>1l</a> 1k 1j 1s</4>");$("#Z").s(G);3(!(14==="")){9 12(){3($(o).N("s",12)){$(o).N("s",12)}$("#8").C();$("v").q("<4 5=\'8\'></4>");19(1D,1G,g);H P}$("#1U").s(12)}3(!(R==="")){9 1i(){$("#8").C();$("v").q("<4 5=\'8\'></4>");19(1z,1x,g);H P}$("#1X").s(1i)}o.1h=9(e){3(e==M){I=2w.2v}n{I=e.2u}3(I==27){G()}n 3(I==3k){3(!(R=="")){o.1h="";1i()}}n 3(I==3j){3(!(14=="")){o.1h="";12()}}};16();$("#K").C();$("#1L").s(G);$("#8").r({Y:"T"})};S.L=f}n{6 l=f.2r(/^[^\\?]+\\??/,\'\');6 m=2p(l);13=(m[\'z\']*1)+30||3h;1a=(m[\'A\']*1)+3g||3f;W=13-30;V=1a-3e;3(f.O(\'2j\')!=-1){1E=f.1B(\'3d\');$("#15").C();3(m[\'1A\']!="1b"){$("#8").q("<4 5=\'2f\'><4 5=\'1H\'>"+d+"</4><4 5=\'2e\'><a u=\'#\' 5=\'Z\' Q=\'1o\'>1l</a> 1k 1j 1s</4></4><U 1W=\'0\' 2d=\'0\' L=\'"+1E[0]+"\' 5=\'15\' 1v=\'15"+1f.2c(1f.1y()*2b)+"\' 1g=\'1m()\' J=\'z:"+(W+29)+"p;A:"+(V+17)+"p;\' > </U>")}n{$("#B").N();$("#8").q("<U 1W=\'0\' 2d=\'0\' L=\'"+1E[0]+"\' 5=\'15\' 1v=\'15"+1f.2c(1f.1y()*2b)+"\' 1g=\'1m()\' J=\'z:"+(W+29)+"p;A:"+(V+17)+"p;\'> </U>")}}n{3($("#8").r("Y")!="T"){3(m[\'1A\']!="1b"){$("#8").q("<4 5=\'2f\'><4 5=\'1H\'>"+d+"</4><4 5=\'2e\'><a u=\'#\' 5=\'Z\'>1l</a> 1k 1j 1s</4></4><4 5=\'F\' J=\'z:"+W+"p;A:"+V+"p\'></4>")}n{$("#B").N();$("#8").q("<4 5=\'F\' 3c=\'3b\' J=\'z:"+W+"p;A:"+V+"p;\'></4>")}}n{$("#F")[0].J.z=W+"p";$("#F")[0].J.A=V+"p";$("#F")[0].3a=0;$("#1H").11(d)}}$("#Z").s(G);3(f.O(\'37\')!=-1){$("#F").q($(\'#\'+m[\'26\']).1T());$("#8").24(9(){$(\'#\'+m[\'26\']).q($("#F").1T())});16();$("#K").C();$("#8").r({Y:"T"})}n 3(f.O(\'2j\')!=-1){16();3($.1q.35){$("#K").C();$("#8").r({Y:"T"})}}n{$("#F").34(f+="&1y="+(1p 33().32()),9(){16();$("#K").C();1u("#F a.18");$("#8").r({Y:"T"})})}}3(!m[\'1A\']){o.21=9(e){3(e==M){I=2w.2v}n{I=e.2u}3(I==27){G()}}}}31(e){}}9 1m(){$("#K").C();$("#8").r({Y:"T"})}9 G(){$("#2Y").N("s");$("#Z").N("s");$("#8").2X("2W",9(){$(\'#8,#B,#1F\').2V("24").N().C()});$("#K").C();3(2t o.v.J.2i=="2g"){$("v","11").r({A:"1Z",z:"1Z"});$("11").r("22","")}o.1h="";o.21="";H P}9 16(){$("#8").r({2U:\'-\'+20((13/2),10)+\'p\',z:13+\'p\'});3(!(1V.1q.2Q&&1V.1q.2P<7)){$("#8").r({38:\'-\'+20((1a/2),10)+\'p\'})}}9 2p(a){6 b={};3(!a){H b}6 c=a.1B(/[;&]/);25(6 i=0;i<c.1c;i++){6 d=c[i].1B(\'=\');3(!d||d.1c!=2){39}6 e=2a(d[0]);6 f=2a(d[1]);f=f.2r(/\\+/g,\' \');b[e]=f}H b}9 2x(){6 a=o.2M;6 w=1S.2o||1R.2o||(a&&a.1Q)||o.v.1Q;6 h=1S.1P||1R.1P||(a&&a.2n)||o.v.2n;1O=[w,h];H 1O}9 1K(){6 a=2K.2J.1C();3(a.O(\'2I\')!=-1&&a.O(\'3o\')!=-1){H 1b}}',62,211,'|||if|div|id|var||TB_window|function||||||||||||||else|document|px|append|css|click||href|body||||width|height|TB_overlay|remove|TB_Counter|TB_TempArray|TB_ajaxContent|tb_remove|return|keycode|style|TB_load|src|null|unbind|indexOf|false|title|TB_NextHTML|imgPreloader|block|iframe|ajaxContentH|ajaxContentW|this|display|TB_closeWindowButton||html|goPrev|TB_WIDTH|TB_PrevHTML|TB_iframeContent|tb_position||thickbox|tb_show|TB_HEIGHT|true|length|nbsp|span|Math|onload|onkeydown|goNext|Esc|or|close|tb_showIframe|TB_imageCount|Close|new|browser|TB_FoundURL|Key|Image|tb_init|name|imgLoader|TB_NextURL|random|TB_NextCaption|modal|split|toLowerCase|TB_PrevCaption|urlNoQuery|TB_HideSelect|TB_PrevURL|TB_ajaxWindowTitle|img|addClass|tb_detectMacXFF|TB_ImageOff|150|rel|arrayPageSize|innerHeight|clientWidth|self|window|children|TB_prev|jQuery|frameborder|TB_next|getElementById|auto|parseInt|onkeyup|overflow|alt|unload|for|inlineId||100||unescape|1000|round|hspace|TB_closeAjaxWindow|TB_title|undefined|match|maxHeight|TB_iframe|bmp|gif|png|clientHeight|innerWidth|tb_parseQuery|jpeg|replace|jpg|typeof|which|keyCode|event|tb_getPageSize|show|TB_overlayBG|TB_closeWindow|TB_overlayMacFFBGHack|TB_secondLine|TB_caption|blur|TB_Image|60|tb_pathToImage|mac|userAgent|navigator|of|documentElement|Prev|lt|version|msie|gt|ready|Next|marginLeft|trigger|fast|fadeOut|TB_imageOff|hidden||catch|getTime|Date|load|safari|get|TB_inline|marginTop|continue|scrollTop|TB_modal|class|TB_|45|440|40|630|input|188|190|substr|try|area|firefox'.split('|'),0,{})) \ No newline at end of file
diff --git a/lib/rdoc/generator/template/darkfish/rdoc.css b/lib/rdoc/generator/template/darkfish/rdoc.css
new file mode 100644
index 0000000000..e836c66339
--- /dev/null
+++ b/lib/rdoc/generator/template/darkfish/rdoc.css
@@ -0,0 +1,701 @@
+/*
+ * "Darkfish" Rdoc CSS
+ * $Id: rdoc.css 54 2009-01-27 01:09:48Z deveiant $
+ *
+ * Author: Michael Granger <ged@FaerieMUD.org>
+ *
+ */
+
+/* Base Green is: #6C8C22 */
+
+*{ padding: 0; margin: 0; }
+
+body {
+ background: #efefef;
+ font: 14px "Helvetica Neue", Helvetica, Tahoma, sans-serif;
+}
+body.class, body.module, body.file {
+ margin-left: 40px;
+}
+body.file-popup {
+ font-size: 90%;
+ margin-left: 0;
+}
+
+h1 {
+ 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;
+}
+:link:hover,
+:visited:hover {
+ border-bottom: 1px dotted #6C8C22;
+}
+
+pre {
+ background: #ddd;
+ padding: 0.5em 0;
+}
+
+
+/* @group Generic Classes */
+
+.initially-hidden {
+ display: none;
+}
+
+.quicksearch-field {
+ width: 98%;
+ background: #ddd;
+ border: 1px solid #aaa;
+ height: 1.5em;
+ -webkit-border-radius: 4px;
+}
+.quicksearch-field:focus {
+ 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;
+}
+
+.target-section {
+ border: 2px solid #dcce90;
+ border-left-width: 8px;
+ padding: 0 1em;
+ background: #fff3c2;
+}
+
+/* @end */
+
+
+/* @group Index Page, Standalone file pages */
+body.indexpage {
+ margin: 1em 3em;
+}
+body.indexpage p,
+body.indexpage div,
+body.file p {
+ margin: 1em 0;
+}
+
+.indexpage ul,
+.file #documentation ul {
+ line-height: 160%;
+ list-style: none;
+}
+.indexpage ul :link,
+.indexpage ul :visited,
+.file #documentation ul :link,
+.file #documentation ul :visited {
+ font-size: 16px;
+}
+
+.indexpage li,
+.file #documentation li {
+ 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;
+}
+.indexpage li.class {
+ background: url(images/ruby.png) no-repeat left 4px;
+}
+.indexpage li.file {
+ background: url(images/page_white_text.png) no-repeat left 4px;
+}
+.file li p,
+.indexpage li p {
+ margin: 0 0;
+}
+
+/* @end */
+
+/* @group Top-Level Structure */
+
+.class #metadata,
+.file #metadata,
+.module #metadata {
+ float: left;
+ width: 260px;
+}
+
+.class #documentation,
+.file #documentation,
+.module #documentation {
+ margin: 2em 1em 5em 300px;
+ min-width: 340px;
+}
+
+.file #metadata {
+ margin: 0.8em;
+}
+
+#validator-badges {
+ 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;
+}
+#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;
+}
+#metadata #home-section h3.section-header {
+ border-bottom: 0;
+}
+
+#metadata ul,
+#metadata dl,
+#metadata p {
+ padding: 8px;
+ list-style: none;
+}
+
+#file-metadata ul {
+ padding-left: 28px;
+ list-style-image: url(images/page_green.png);
+}
+
+dl.svninfo {
+ color: #666;
+ margin: 0;
+}
+dl.svninfo dt {
+ font-weight: bold;
+}
+
+ul.link-list li {
+ white-space: nowrap;
+}
+ul.link-list .type {
+ font-size: 8px;
+ text-transform: uppercase;
+ color: white;
+ background: #969696;
+ padding: 2px 4px;
+ -webkit-border-radius: 5px;
+}
+
+/* @end */
+
+
+/* @group Project Metadata Section */
+#project-metadata {
+ margin-top: 3em;
+}
+
+.file #project-metadata {
+ margin-top: 0em;
+}
+
+#project-metadata .section {
+ border: 1px solid #aaa;
+}
+#project-metadata h3.section-header {
+ border-bottom: 1px solid #aaa;
+ position: relative;
+}
+#project-metadata h3.section-header .search-toggle {
+ position: absolute;
+ right: 5px;
+}
+
+
+#project-metadata form {
+ color: #777;
+ background: #ccc;
+ padding: 8px 8px 16px;
+ border-bottom: 1px solid #bbb;
+}
+#project-metadata fieldset {
+ border: 0;
+}
+
+#no-class-search-results {
+ margin: 0 auto 1em;
+ text-align: center;
+ font-size: 14px;
+ font-weight: bold;
+ color: #aaa;
+}
+
+/* @end */
+
+
+/* @group Documentation Section */
+#description {
+ font-size: 100%;
+ color: #333;
+}
+
+#description p {
+ margin: 1em 0.4em;
+}
+
+#description ul {
+ margin-left: 2em;
+}
+#description ul li {
+ line-height: 1.4em;
+}
+
+#description dl,
+#documentation dl {
+ margin: 8px 1.5em;
+ border: 1px solid #ccc;
+}
+#description dl {
+ font-size: 14px;
+}
+
+#description dt,
+#documentation dt {
+ padding: 2px 4px;
+ font-weight: bold;
+ background: #ddd;
+}
+#description dd,
+#documentation dd {
+ padding: 2px 12px;
+}
+#description dd + dt,
+#documentation dd + dt {
+ margin-top: 0.7em;
+}
+
+#documentation .section {
+ 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;
+}
+
+#constants-list > dl,
+#attributes-list > dl {
+ 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;
+}
+#constants-list > dl dt a,
+#attributes-list > dl dt a {
+ color: inherit;
+}
+#constants-list > dl dd,
+#attributes-list > dl dd {
+ margin: 0 0 1em 0;
+ padding: 0;
+ color: #666;
+}
+
+/* @group Method Details */
+
+#documentation .method-source-code {
+ display: none;
+}
+
+#documentation .method-detail {
+ margin: 0.5em 0;
+ padding: 0.5em 0;
+ cursor: pointer;
+}
+#documentation .method-detail:hover {
+ 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;
+}
+#documentation .method-heading :link,
+#documentation .method-heading :visited {
+ 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;
+}
+#documentation .method-detail:hover .method-click-advice {
+ visibility: visible;
+}
+
+#documentation .method-alias .method-heading {
+ 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;
+}
+#documentation .aliases {
+ padding-top: 4px;
+ font-style: italic;
+ cursor: default;
+}
+#documentation .method-description p {
+ padding: 0;
+}
+#documentation .method-description p + p {
+ margin-bottom: 0.5em;
+}
+
+#documentation .attribute-method-heading {
+ background: url(images/tag_green.png) no-repeat left bottom;
+}
+#documentation #attribute-method-details .method-detail:hover {
+ background-color: transparent;
+ cursor: default;
+}
+#documentation .attribute-access-type {
+ font-size: 60%;
+ text-transform: uppercase;
+ vertical-align: super;
+ padding: 0 2px;
+}
+/* @end */
+
+/* @end */
+
+
+
+/* @group Source Code */
+
+div.method-source-code {
+ 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;
+}
+
+/* @group Ruby keyword styles */
+
+.ruby-constant { color: #7fffd4; background: transparent; }
+.ruby-keyword { color: #00ffff; background: transparent; }
+.ruby-ivar { color: #eedd82; background: transparent; }
+.ruby-operator { color: #00ffee; background: transparent; }
+.ruby-identifier { color: #ffdead; background: transparent; }
+.ruby-node { color: #ffa07a; background: transparent; }
+.ruby-comment { color: #b22222; font-weight: bold; background: transparent; }
+.ruby-regexp { color: #ffa07a; background: transparent; }
+.ruby-value { color: #7fffd4; background: transparent; }
+
+/* @end */
+/* @end */
+
+
+/* @group File Popup Contents */
+
+.file #metadata,
+.file-popup #metadata {
+}
+
+.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;
+}
+.file dt {
+ 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;
+}
+.file dt.requires {
+ background: url(images/plugin.png) no-repeat left top;
+}
+.file dt.scs-url {
+ background: url(images/wrench.png) no-repeat left top;
+}
+
+.file dl dd {
+ margin: 0 0 1em 0;
+}
+.file #metadata dl dd ul {
+ 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;
+}
+
+/* @end */
+
+
+
+
+/* @group ThickBox Styles */
+#TB_window {
+ font: 12px Arial, Helvetica, sans-serif;
+ color: #333333;
+}
+
+#TB_secondLine {
+ font: 10px Arial, Helvetica, sans-serif;
+ color:#666666;
+}
+
+#TB_window :link,
+#TB_window :visited { color: #666666; }
+#TB_window :link:hover,
+#TB_window :visited:hover { color: #000; }
+#TB_window :link:active,
+#TB_window :visited:active { color: #666666; }
+#TB_window :link:focus,
+#TB_window :visited:focus { color: #666666; }
+
+#TB_overlay {
+ 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;
+}
+
+* html #TB_overlay { /* ie6 hack */
+ 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%;
+}
+
+* 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');
+}
+
+#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;
+}
+
+#TB_caption{
+ height:25px;
+ padding:7px 30px 10px 25px;
+ float:left;
+}
+
+#TB_closeWindow{
+ height:25px;
+ padding:11px 25px 10px 0;
+ float:right;
+}
+
+#TB_closeAjaxWindow{
+ 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;
+}
+
+#TB_title{
+ background-color: #6C8C22;
+ color: #dedede;
+ height:40px;
+}
+#TB_title :link,
+#TB_title :visited {
+ 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;
+}
+
+#TB_ajaxContent.TB_modal{
+ padding:15px;
+}
+
+#TB_ajaxContent p{
+ 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 */
+}
+
+* 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');
+}
+
+#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%;
+}
+
+* html #TB_HideSelect { /* ie6 hack */
+ 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;
+}
+
+/* @end */
+
+/* @group Debugging Section */
+
+#debugging-toggle {
+ text-align: center;
+}
+#debugging-toggle img {
+ cursor: pointer;
+}
+
+#rdoc-debugging-section-dump {
+ display: none;
+ margin: 0 2em 2em;
+ background: #ccc;
+ border: 1px solid #999;
+}
+
+
+
+/* @end */
diff --git a/lib/rdoc/generator/texinfo.rb b/lib/rdoc/generator/texinfo.rb
deleted file mode 100644
index 99a1452f21..0000000000
--- a/lib/rdoc/generator/texinfo.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-require 'rdoc/rdoc'
-require 'rdoc/generator'
-require 'rdoc/markup/to_texinfo'
-
-module RDoc
- module Generator
- # This generates Texinfo files for viewing with GNU Info or Emacs
- # from RDoc extracted from Ruby source files.
- class TEXINFO
- # What should the .info file be named by default?
- DEFAULT_INFO_FILENAME = 'rdoc.info'
-
- include Generator::MarkUp
-
- # Accept some options
- def initialize(options)
- @options = options
- @options.inline_source = true
- @options.op_name ||= 'rdoc.texinfo'
- @options.formatter = ::RDoc::Markup::ToTexInfo.new
- end
-
- # Generate the +texinfo+ files
- def generate(toplevels)
- @toplevels = toplevels
- @files, @classes = ::RDoc::Generator::Context.build_indices(@toplevels,
- @options)
-
- (@files + @classes).each { |x| x.value_hash }
-
- open(@options.op_name, 'w') do |f|
- f.puts TexinfoTemplate.new('files' => @files,
- 'classes' => @classes,
- 'filename' => @options.op_name.gsub(/texinfo/, 'info'),
- 'title' => @options.title).render
- end
- # TODO: create info files and install?
- end
-
- class << self
- # Factory? We don't need no stinkin' factory!
- alias_method :for, :new
- end
- end
-
- # Basically just a wrapper around ERB.
- # Should probably use RDoc::TemplatePage instead
- class TexinfoTemplate
- BASE_DIR = ::File.expand_path(::File.dirname(__FILE__)) # have to calculate this when the file's loaded.
-
- def initialize(values, file = 'texinfo.erb')
- @v, @file = [values, file]
- end
-
- def template
- ::File.read(::File.join(BASE_DIR, 'texinfo', @file))
- end
-
- # Go!
- def render
- ERB.new(template).result binding
- end
-
- def href(location, text)
- text # TODO: how does texinfo do hyperlinks?
- end
-
- def target(name, text)
- text # TODO: how do hyperlink targets work?
- end
-
- # TODO: this is probably implemented elsewhere?
- def method_prefix(section)
- { 'Class' => '.',
- 'Module' => '::',
- 'Instance' => '#',
- }[section['category']]
- end
- end
- end
-end
diff --git a/lib/rdoc/generator/texinfo/class.texinfo.erb b/lib/rdoc/generator/texinfo/class.texinfo.erb
deleted file mode 100644
index 74ecc59f7d..0000000000
--- a/lib/rdoc/generator/texinfo/class.texinfo.erb
+++ /dev/null
@@ -1,44 +0,0 @@
-@node <%= @v['class']['full_name'].gsub(/::/, '-') %>
-@chapter <%= @v['class']["classmod"] %> <%= @v['class']['full_name'] %>
-
-<% if @v['class']["parent"] and @v['class']['par_url'] %>
-Inherits <%= href @v['class']["par_url"], @v['class']["parent"] %><% end %>
-
-<%= @v['class']["description"] %>
-
-<% if @v['class']["includes"] %>
-Includes
-<% @v['class']["includes"].each do |include| %>
-* <%= href include["aref"], include["name"] %>
-<% end # @v['class']["includes"] %>
-<% end %>
-
-<% if @v['class']["sections"] %>
-<% @v['class']["sections"].each do |section| %>
-<% if section["attributes"] %>
-Attributes
-<% section["attributes"].each do |attributes| %>
-* <%= attributes["name"] %> <%= attributes["rw"] %> <%= attributes["a_desc"] %>
-<% end # section["attributes"] %>
-<% end %>
-<% end %>
-
-<% @v['class']["sections"].each do |section| %>
-<% if section["method_list"] %>
-Methods
-@menu
-<% section["method_list"].each_with_index do |method_list, i| %>
-<%= i %>
-<% (method_list["methods"] || []).each do |method| %>
-* <%= @v['class']['full_name'].gsub(/::/, '-') %><%= method_prefix method_list %><%= method['name'] %>::<% end %>
-<% end %>
-@end menu
-
-<% section["method_list"].each do |method_list| %>
-<% (method_list["methods"] || []).uniq.each do |method| %>
-<%= TexinfoTemplate.new(@v.merge({'method' => method, 'list' => method_list}),
- 'method.texinfo.erb').render %><% end %>
-<% end %>
-<% end # if section["method_list"] %>
-<% end # @v['class']["sections"] %>
-<% end %>
diff --git a/lib/rdoc/generator/texinfo/file.texinfo.erb b/lib/rdoc/generator/texinfo/file.texinfo.erb
deleted file mode 100644
index b619b94bd2..0000000000
--- a/lib/rdoc/generator/texinfo/file.texinfo.erb
+++ /dev/null
@@ -1,6 +0,0 @@
-<% if false %>
-<h2>File: <%= @v['file']["short_name"] %></h2>
-Path: <%= @v['file']["full_path"] %>
-
-<%= TexinfoTemplate.new(@v, 'content.texinfo.erb').render %>
-<% end %>
diff --git a/lib/rdoc/generator/texinfo/method.texinfo.erb b/lib/rdoc/generator/texinfo/method.texinfo.erb
deleted file mode 100644
index f5c2b73a4b..0000000000
--- a/lib/rdoc/generator/texinfo/method.texinfo.erb
+++ /dev/null
@@ -1,6 +0,0 @@
-@node <%= @v['class']['full_name'].gsub(/::/, '-') %><%= method_prefix @v['list'] %><%= @v['method']['name'] %>
-@section <%= @v['class']["classmod"] %> <%= @v['class']['full_name'] %><%= method_prefix @v['list'] %><%= @v['method']['name'] %>
-<%= @v['method']["type"] %> <%= @v['method']["category"] %> method:
-<%= target @v['method']["aref"], @v['method']['callseq'] ||
- @v['method']["name"] + @v['method']["params"] %>
-<%= @v['method']["m_desc"] %>
diff --git a/lib/rdoc/generator/texinfo/texinfo.erb b/lib/rdoc/generator/texinfo/texinfo.erb
deleted file mode 100644
index 235f63d73c..0000000000
--- a/lib/rdoc/generator/texinfo/texinfo.erb
+++ /dev/null
@@ -1,28 +0,0 @@
-\input texinfo @c -*-texinfo-*-
-@c %**start of header
-@setfilename <%= @v['filename'] %>
-@settitle <%= @v['title'] %>
-@c %**end of header
-
-@contents @c TODO: whitespace is a mess... =\
-
-@ifnottex
-@node Top
-
-@top <%= @v['title'] %>
-@end ifnottex
-
-<% if @f = @v['files'].detect { |f| f.name =~ /Readme/i } %>
-<%= @f.values['description'] %><% end %>
-
-@menu
-<% @v['classes'].each do |klass| %>
-* <%= klass.name.gsub(/::/, '-') %>::<% end %>
-@c TODO: add files
-@end menu
-
-<% (@v['classes'] || []).each_with_index do |klass, i| %>
-<%= TexinfoTemplate.new(@v.merge('class' => klass.values),
- 'class.texinfo.erb').render %><% end %>
-
-@bye
diff --git a/lib/rdoc/generator/xml.rb b/lib/rdoc/generator/xml.rb
deleted file mode 100644
index 0d4c5a7ea1..0000000000
--- a/lib/rdoc/generator/xml.rb
+++ /dev/null
@@ -1,117 +0,0 @@
-require 'rdoc/generator/html'
-
-##
-# Generate XML output as one big file
-
-class RDoc::Generator::XML < RDoc::Generator::HTML
-
- ##
- # Standard generator factory
-
- def self.for(options)
- new(options)
- end
-
- def initialize(*args)
- super
- end
-
- ##
- # Build the initial indices and output objects
- # based on an array of TopLevel objects containing
- # the extracted information.
-
- def generate(info)
- @info = info
- @files = []
- @classes = []
- @hyperlinks = {}
-
- build_indices
- generate_xml
- end
-
- ##
- # Generate:
- #
- # * a list of File objects for each TopLevel object.
- # * a list of Class objects for each first level
- # class or module in the TopLevel objects
- # * a complete list of all hyperlinkable terms (file,
- # class, module, and method names)
-
- def build_indices
- @info.each do |toplevel|
- @files << RDoc::Generator::File.new(toplevel, @options, RDoc::Generator::FILE_DIR)
- end
-
- RDoc::TopLevel.all_classes_and_modules.each do |cls|
- build_class_list(cls, @files[0], RDoc::Generator::CLASS_DIR)
- end
- end
-
- def build_class_list(from, html_file, class_dir)
- @classes << RDoc::Generator::Class.new(from, html_file, class_dir, @options)
- from.each_classmodule do |mod|
- build_class_list(mod, html_file, class_dir)
- end
- end
-
- ##
- # Generate all the HTML. For the one-file case, we generate
- # all the information in to one big hash
-
- def generate_xml
- values = {
- 'charset' => @options.charset,
- 'files' => gen_into(@files),
- 'classes' => gen_into(@classes)
- }
-
- template = RDoc::TemplatePage.new @template::ONE_PAGE
-
- if @options.op_name
- opfile = File.open(@options.op_name, "w")
- else
- opfile = $stdout
- end
- template.write_html_on(opfile, values)
- end
-
- def gen_into(list)
- res = []
- list.each do |item|
- res << item.value_hash
- end
- res
- end
-
- def gen_file_index
- gen_an_index(@files, 'Files')
- end
-
- def gen_class_index
- gen_an_index(@classes, 'Classes')
- end
-
- def gen_method_index
- gen_an_index(RDoc::Generator::HtmlMethod.all_methods, 'Methods')
- end
-
- def gen_an_index(collection, title)
- res = []
- collection.sort.each do |f|
- if f.document_self
- res << { "href" => f.path, "name" => f.index_name }
- end
- end
-
- return {
- "entries" => res,
- 'list_title' => title,
- 'index_url' => main_url,
- }
- end
-
-end
-
diff --git a/lib/rdoc/generator/xml/rdf.rb b/lib/rdoc/generator/xml/rdf.rb
deleted file mode 100644
index 7b15c69a18..0000000000
--- a/lib/rdoc/generator/xml/rdf.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-require 'rdoc/generator/xml'
-
-module RDoc::Generator::XML::RDF
-
- CONTENTS_RDF = <<-EOF
-<% if defined? classes and classes["description"] then %>
- <description rd:parseType="Literal">
-<%= classes["description"] %>
- </description>
-<% end %>
-
-<% if defined? files and files["requires"] then %>
-<% files["requires"].each do |requires| %>
- <rd:required-file rd:name="<%= requires["name"] %>" />
-<% end # files["requires"] %>
-<% end %>
-
-<% if defined? classes and classes["includes"] then %>
- <IncludedModuleList>
-<% classes["includes"].each do |includes| %>
- <included-module rd:name="<%= includes["name"] %>" />
-<% end # includes["includes"] %>
- </IncludedModuleList>
-<% end %>
-
-<% if defined? classes and classes["sections"] then %>
-<% classes["sections"].each do |sections| %>
-<% if sections["attributes"] then %>
-<% sections["attributes"].each do |attributes| %>
- <contents>
- <Attribute rd:name="<%= attributes["name"] %>">
-<% if attributes["rw"] then %>
- <attribute-rw><%= attributes["rw"] %></attribute-rw>
-<% end %>
- <description rdf:parseType="Literal"><%= attributes["a_desc"] %></description>
- </Attribute>
- </contents>
-<% end # sections["attributes"] %>
-<% end %>
-
-<% if sections["method_list"] then %>
-<% sections["method_list"].each do |method_list| %>
-<% if method_list["methods"] then %>
-<% method_list["methods"].each do |methods| %>
- <contents>
- <Method rd:name="<%= methods["name"] %>" rd:visibility="<%= methods["type"] %>"
- rd:category="<%= methods["category"] %>" rd:id="<%= methods["aref"] %>">
- <parameters><%= methods["params"] %></parameters>
-<% if methods["m_desc"] then %>
- <description rdf:parseType="Literal">
-<%= methods["m_desc"] %>
- </description>
-<% end %>
-<% if methods["sourcecode"] then %>
- <source-code-listing rdf:parseType="Literal">
-<%= methods["sourcecode"] %>
- </source-code-listing>
-<% end %>
- </Method>
- </contents>
-<% end # method_list["methods"] %>
-<% end %>
-<% end # sections["method_list"] %>
-<% end %>
- <!-- end method list -->
-<% end # classes["sections"] %>
-<% end %>
- EOF
-
-########################################################################
-
- ONE_PAGE = %{<?xml version="1.0" encoding="utf-8"?>
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns="http://pragprog.com/rdoc/rdoc.rdf#"
- xmlns:rd="http://pragprog.com/rdoc/rdoc.rdf#">
-
-<!-- RDoc -->
-<% values["files"].each do |files| %>
- <rd:File rd:name="<%= files["short_name"] %>" rd:id="<%= files["href"] %>">
- <path><%= files["full_path"] %></path>
- <dtm-modified><%= files["dtm_modified"] %></dtm-modified>
-} + CONTENTS_RDF + %{
- </rd:File>
-<% end # values["files"] %>
-<% values["classes"].each do |classes| %>
- <<%= values["classmod"] %> rd:name="<%= classes["full_name"] %>" rd:id="<%= classes["full_name"] %>">
- <classmod-info>
-<% if classes["infiles"] then %>
- <InFiles>
-<% classes["infiles"].each do |infiles| %>
- <infile>
- <File rd:name="<%= infiles["full_path"] %>"
-<% if infiles["full_path_url"] then %>
- rdf:about="<%= infiles["full_path_url"] %>"
-<% end %>
- />
- </infile>
-<% end # classes["infiles"] %>
- </InFiles>
-<% end %>
-<% if classes["parent"] then %>
- <superclass><%= href classes["par_url"], classes["parent"] %></superclass>
-<% end %>
- </classmod-info>
-} + CONTENTS_RDF + %{
- </<%= classes["classmod"] %>>
-<% end # values["classes"] %>
-<!-- /RDoc -->
-</rdf:RDF>
-}
-
-end
-
diff --git a/lib/rdoc/generator/xml/xml.rb b/lib/rdoc/generator/xml/xml.rb
deleted file mode 100644
index 4b54e7350f..0000000000
--- a/lib/rdoc/generator/xml/xml.rb
+++ /dev/null
@@ -1,123 +0,0 @@
-require 'rdoc/generator/xml'
-
-module RDoc::Generator::XML::XML
-
- CONTENTS_XML = <<-EOF
-<% if defined? classes and classes["description"] then %>
- <description>
-<%= classes["description"] %>
- </description>
-<% end %>
- <contents>
-<% if defined? files and files["requires"] then %>
- <required-file-list>
-<% files["requires"].each do |requires| %>
- <required-file name="<%= requires["name"] %>"
-<% if requires["aref"] then %>
- href="<%= requires["aref"] %>"
-<% end %>
- />
-<% end %><%# files["requires"] %>
- </required-file-list>
-<% end %>
-<% if defined? classes and classes["sections"] then %>
-<% classes["sections"].each do |sections| %>
-<% if sections["constants"] then %>
- <constant-list>
-<% sections["constants"].each do |constant| %>
- <constant name="<%= constant["name"] %>">
-<% if constant["value"] then %>
- <value><%= constant["value"] %></value>
-<% end %>
- <description><%= constant["a_desc"] %></description>
- </constant>
-<% end %><%# sections["constants"] %>
- </constant-list>
-<% end %>
-<% if sections["attributes"] then %>
- <attribute-list>
-<% sections["attributes"].each do |attributes| %>
- <attribute name="<%= attributes["name"] %>">
-<% if attributes["rw"] then %>
- <attribute-rw><%= attributes["rw"] %></attribute-rw>
-<% end %>
- <description><%= attributes["a_desc"] %></description>
- </attribute>
-<% end %><%# sections["attributes"] %>
- </attribute-list>
-<% end %>
-<% if sections["method_list"] then %>
- <method-list>
-<% sections["method_list"].each do |method_list| %>
-<% if method_list["methods"] then %>
-<% method_list["methods"].each do |methods| %>
- <method name="<%= methods["name"] %>" type="<%= methods["type"] %>" category="<%= methods["category"] %>" id="<%= methods["aref"] %>">
- <parameters><%= methods["params"] %></parameters>
-<% if methods["m_desc"] then %>
- <description>
-<%= methods["m_desc"] %>
- </description>
-<% end %>
-<% if methods["sourcecode"] then %>
- <source-code-listing>
-<%= methods["sourcecode"] %>
- </source-code-listing>
-<% end %>
- </method>
-<% end %><%# method_list["methods"] %>
-<% end %>
-<% end %><%# sections["method_list"] %>
- </method-list>
-<% end %>
-<% end %><%# classes["sections"] %>
-<% end %>
-<% if defined? classes and classes["includes"] then %>
- <included-module-list>
-<% classes["includes"].each do |includes| %>
- <included-module name="<%= includes["name"] %>"
-<% if includes["aref"] then %>
- href="<%= includes["aref"] %>"
-<% end %>
- />
-<% end %><%# classes["includes"] %>
- </included-module-list>
-<% end %>
- </contents>
- EOF
-
- ONE_PAGE = %{<?xml version="1.0" encoding="utf-8"?>
-<rdoc>
-<file-list>
-<% values["files"].each do |files| %>
- <file name="<%= files["short_name"] %>" id="<%= files["href"] %>">
- <file-info>
- <path><%= files["full_path"] %></path>
- <dtm-modified><%= files["dtm_modified"] %></dtm-modified>
- </file-info>
-} + CONTENTS_XML + %{
- </file>
-<% end %><%# values["files"] %>
-</file-list>
-<class-module-list>
-<% values["classes"].each do |classes| %>
- <<%= classes["classmod"] %> name="<%= classes["full_name"] %>" id="<%= classes["full_name"] %>">
- <classmod-info>
-<% if classes["infiles"] then %>
- <infiles>
-<% classes["infiles"].each do |infiles| %>
- <infile><%= href infiles["full_path_url"], infiles["full_path"] %></infile>
-<% end %><%# classes["infiles"] %>
- </infiles>
-<% end %>
-<% if classes["parent"] then %>
- <superclass><%= href classes["par_url"], classes["parent"] %></superclass>
-<% end %>
- </classmod-info>
-} + CONTENTS_XML + %{
- </<%= classes["classmod"] %>>
-<% end %><%# values["classes"] %>
-</class-module-list>
-</rdoc>
-}
-
-end
diff --git a/lib/rdoc/ghost_method.rb b/lib/rdoc/ghost_method.rb
new file mode 100644
index 0000000000..192b46f51f
--- /dev/null
+++ b/lib/rdoc/ghost_method.rb
@@ -0,0 +1,8 @@
+require 'rdoc/any_method'
+
+##
+# GhostMethod represents a method referenced only by a comment
+
+class RDoc::GhostMethod < RDoc::AnyMethod
+end
+
diff --git a/lib/rdoc/include.rb b/lib/rdoc/include.rb
new file mode 100644
index 0000000000..11a9bdc7ef
--- /dev/null
+++ b/lib/rdoc/include.rb
@@ -0,0 +1,61 @@
+require 'rdoc/code_object'
+
+##
+# A Module include in a class with \#include
+
+class RDoc::Include < RDoc::CodeObject
+
+ ##
+ # Name of included module
+
+ attr_accessor :name
+
+ ##
+ # Creates a new Include for +name+ with +comment+
+
+ def initialize(name, comment)
+ super()
+ @name = name
+ self.comment = comment
+ end
+
+ ##
+ # Includes are sorted by name
+
+ def <=> other
+ return unless self.class === other
+
+ name <=> other.name
+ end
+
+ def == other # :nodoc:
+ self.class == other.class and
+ self.name == other.name
+ end
+
+ ##
+ # Full name based on #module
+
+ def full_name
+ m = self.module
+ RDoc::ClassModule === m ? m.full_name : @name
+ end
+
+ def inspect # :nodoc:
+ "#<%s:0x%x %s.include %s>" % [
+ self.class,
+ object_id,
+ parent_name, @name,
+ ]
+ end
+
+ ##
+ # Attempts to locate the included module object. Returns the name if not
+ # known.
+
+ def module
+ RDoc::TopLevel.find_module_named(@name) || @name
+ end
+
+end
+
diff --git a/lib/rdoc/markup.rb b/lib/rdoc/markup.rb
index a584a79b75..c3c5213943 100644
--- a/lib/rdoc/markup.rb
+++ b/lib/rdoc/markup.rb
@@ -27,9 +27,9 @@ require 'rdoc'
# convert multiple input strings.
#
# require 'rdoc/markup/to_html'
-#
+#
# h = RDoc::Markup::ToHtml.new
-#
+#
# puts h.convert(input_string)
#
# You can extend the RDoc::Markup parser to recognise new markup
@@ -41,22 +41,22 @@ require 'rdoc'
#
# require 'rdoc/markup'
# require 'rdoc/markup/to_html'
-#
+#
# class WikiHtml < RDoc::Markup::ToHtml
# def handle_special_WIKIWORD(special)
# "<font color=red>" + special.text + "</font>"
# end
# end
-#
+#
# m = RDoc::Markup.new
# m.add_word_pair("{", "}", :STRIKE)
# m.add_html("no", :STRIKE)
-#
+#
# m.add_special(/\b([A-Z][a-z]+[A-Z]\w+)/, :WIKIWORD)
-#
+#
# wh = WikiHtml.new
# wh.add_tag(:STRIKE, "<strike>", "</strike>")
-#
+#
# puts "<body>#{wh.convert ARGF.read}</body>"
#
#--
@@ -65,30 +65,7 @@ require 'rdoc'
class RDoc::Markup
- SPACE = ?\s
-
- # List entries look like:
- # * text
- # 1. text
- # [label] text
- # label:: text
- #
- # Flag it as a list entry, and work out the indent for subsequent lines
-
- SIMPLE_LIST_RE = /^(
- ( \* (?# bullet)
- |- (?# bullet)
- |\d+\. (?# numbered )
- |[A-Za-z]\. (?# alphabetically numbered )
- )
- \s+
- )\S/x
-
- LABEL_LIST_RE = /^(
- ( \[.*?\] (?# labeled )
- |\S.*:: (?# note )
- )(?:\s+|$)
- )/x
+ attr_reader :attribute_manager
##
# Take a block of text and use various heuristics to determine it's
@@ -96,7 +73,7 @@ class RDoc::Markup
# identify significant chunks.
def initialize
- @am = RDoc::Markup::AttributeManager.new
+ @attribute_manager = RDoc::Markup::AttributeManager.new
@output = nil
end
@@ -106,14 +83,14 @@ class RDoc::Markup
# formatters can recognize by their +name+.
def add_word_pair(start, stop, name)
- @am.add_word_pair(start, stop, name)
+ @attribute_manager.add_word_pair(start, stop, name)
end
##
# Add to the sequences recognized as general markup.
def add_html(tag, name)
- @am.add_html(tag, name)
+ @attribute_manager.add_html(tag, name)
end
##
@@ -126,7 +103,7 @@ class RDoc::Markup
# accept_special method.
def add_special(pattern, name)
- @am.add_special(pattern, name)
+ @attribute_manager.add_special(pattern, name)
end
##
@@ -136,243 +113,14 @@ class RDoc::Markup
# display the result.
def convert(str, op)
- lines = str.split(/\r?\n/).map { |line| Line.new line }
- @lines = Lines.new lines
-
- return "" if @lines.empty?
- @lines.normalize
- assign_types_to_lines
- group = group_lines
- # call the output formatter to handle the result
- #group.each { |line| p line }
- group.accept @am, op
- end
-
- private
-
- ##
- # Look through the text at line indentation. We flag each line as being
- # Blank, a paragraph, a list element, or verbatim text.
-
- def assign_types_to_lines(margin = 0, level = 0)
- while line = @lines.next
- if line.blank? then
- line.stamp :BLANK, level
- next
- end
-
- # if a line contains non-blanks before the margin, then it must belong
- # to an outer level
-
- text = line.text
-
- for i in 0...margin
- if text[i] != SPACE
- @lines.unget
- return
- end
- end
-
- active_line = text[margin..-1]
-
- # Rules (horizontal lines) look like
- #
- # --- (three or more hyphens)
- #
- # The more hyphens, the thicker the rule
- #
-
- if /^(---+)\s*$/ =~ active_line
- line.stamp :RULE, level, $1.length-2
- next
- end
-
- # Then look for list entries. First the ones that have to have
- # text following them (* xxx, - xxx, and dd. xxx)
-
- if SIMPLE_LIST_RE =~ active_line
- offset = margin + $1.length
- prefix = $2
- prefix_length = prefix.length
-
- flag = case prefix
- when "*","-" then :BULLET
- when /^\d/ then :NUMBER
- when /^[A-Z]/ then :UPPERALPHA
- when /^[a-z]/ then :LOWERALPHA
- else raise "Invalid List Type: #{self.inspect}"
- end
-
- line.stamp :LIST, level+1, prefix, flag
- text[margin, prefix_length] = " " * prefix_length
- assign_types_to_lines(offset, level + 1)
- next
- end
-
- if LABEL_LIST_RE =~ active_line
- offset = margin + $1.length
- prefix = $2
- prefix_length = prefix.length
-
- next if handled_labeled_list(line, level, margin, offset, prefix)
- end
-
- # Headings look like
- # = Main heading
- # == Second level
- # === Third
- #
- # Headings reset the level to 0
-
- if active_line[0] == ?= and active_line =~ /^(=+)\s*(.*)/
- prefix_length = $1.length
- prefix_length = 6 if prefix_length > 6
- line.stamp :HEADING, 0, prefix_length
- line.strip_leading(margin + prefix_length)
- next
- end
-
- # If the character's a space, then we have verbatim text,
- # otherwise
-
- if active_line[0] == SPACE
- line.strip_leading(margin) if margin > 0
- line.stamp :VERBATIM, level
- else
- line.stamp :PARAGRAPH, level
- end
- end
- end
-
- ##
- # Handle labeled list entries, We have a special case to deal with.
- # Because the labels can be long, they force the remaining block of text
- # over the to right:
- #
- # this is a long label that I wrote:: and here is the
- # block of text with
- # a silly margin
- #
- # So we allow the special case. If the label is followed by nothing, and
- # if the following line is indented, then we take the indent of that line
- # as the new margin.
- #
- # this is a long label that I wrote::
- # here is a more reasonably indented block which
- # will be attached to the label.
- #
-
- def handled_labeled_list(line, level, margin, offset, prefix)
- prefix_length = prefix.length
- text = line.text
- flag = nil
-
- case prefix
- when /^\[/ then
- flag = :LABELED
- prefix = prefix[1, prefix.length-2]
- when /:$/ then
- flag = :NOTE
- prefix.chop!
- else
- raise "Invalid List Type: #{self.inspect}"
- end
+ document = RDoc::Markup::Parser.parse str
- # body is on the next line
- if text.length <= offset then
- original_line = line
- line = @lines.next
- return false unless line
- text = line.text
-
- for i in 0..margin
- if text[i] != SPACE
- @lines.unget
- return false
- end
- end
-
- i = margin
- i += 1 while text[i] == SPACE
-
- if i >= text.length then
- @lines.unget
- return false
- else
- offset = i
- prefix_length = 0
-
- if text[offset..-1] =~ SIMPLE_LIST_RE then
- @lines.unget
- line = original_line
- line.text = ''
- else
- @lines.delete original_line
- end
- end
- end
-
- line.stamp :LIST, level+1, prefix, flag
- text[margin, prefix_length] = " " * prefix_length
- assign_types_to_lines(offset, level + 1)
- return true
+ document.accept op
end
- ##
- # Return a block consisting of fragments which are paragraphs, list
- # entries or verbatim text. We merge consecutive lines of the same type
- # and level together. We are also slightly tricky with lists: the lines
- # following a list introduction look like paragraph lines at the next
- # level, and we remap them into list entries instead.
-
- def group_lines
- @lines.rewind
-
- in_list = false
- wanted_type = wanted_level = nil
-
- block = LineCollection.new
- group = nil
-
- while line = @lines.next
- if line.level == wanted_level and line.type == wanted_type
- group.add_text(line.text)
- else
- group = block.fragment_for(line)
- block.add(group)
-
- if line.type == :LIST
- wanted_type = :PARAGRAPH
- else
- wanted_type = line.type
- end
-
- wanted_level = line.type == :HEADING ? line.param : line.level
- end
- end
-
- block.normalize
- block
- end
-
- ##
- # For debugging, we allow access to our line contents as text.
-
- def content
- @lines.as_text
- end
- public :content
-
- ##
- # For debugging, return the list of line types.
-
- def get_line_types
- @lines.line_types
- end
- public :get_line_types
-
end
-require 'rdoc/markup/fragments'
+require 'rdoc/markup/parser'
+require 'rdoc/markup/attribute_manager'
require 'rdoc/markup/inline'
-require 'rdoc/markup/lines'
+
diff --git a/lib/rdoc/markup/attribute_manager.rb b/lib/rdoc/markup/attribute_manager.rb
index d13b79376c..5b9e070efb 100644
--- a/lib/rdoc/markup/attribute_manager.rb
+++ b/lib/rdoc/markup/attribute_manager.rb
@@ -1,41 +1,76 @@
-require 'rdoc/markup/inline'
+##
+# Manages changes of attributes in a block of text
class RDoc::Markup::AttributeManager
+ ##
+ # The NUL character
+
NULL = "\000".freeze
- ##
+ #--
# We work by substituting non-printing characters in to the text. For now
# I'm assuming that I can substitute a character in the range 0..8 for a 7
# bit character without damaging the encoded string, but this might be
# optimistic
+ #++
- A_PROTECT = 004
- PROTECT_ATTR = A_PROTECT.chr
+ A_PROTECT = 004 # :nodoc:
+
+ PROTECT_ATTR = A_PROTECT.chr # :nodoc:
##
# This maps delimiters that occur around words (such as *bold* or +tt+)
# where the start and end delimiters and the same. This lets us optimize
# the regexp
- MATCHING_WORD_PAIRS = {}
+ attr_reader :matching_word_pairs
##
# And this is used when the delimiters aren't the same. In this case the
# hash maps a pattern to the attribute character
- WORD_PAIR_MAP = {}
+ attr_reader :word_pair_map
##
# This maps HTML tags to the corresponding attribute char
- HTML_TAGS = {}
+ attr_reader :html_tags
+
+ ##
+ # A \ in front of a character that would normally be processed turns off
+ # processing. We do this by turning \< into <#{PROTECT}
+
+ attr_reader :protectable
##
# And this maps _special_ sequences to a name. A special sequence is
# something like a WikiWord
- SPECIAL = {}
+ attr_reader :special
+
+ ##
+ # Creates a new attribute manager that understands bold, emphasized and
+ # teletype text.
+
+ def initialize
+ @html_tags = {}
+ @matching_word_pairs = {}
+ @protectable = %w[<\\]
+ @special = {}
+ @word_pair_map = {}
+
+ add_word_pair "*", "*", :BOLD
+ add_word_pair "_", "_", :EM
+ add_word_pair "+", "+", :TT
+
+ add_html "em", :EM
+ add_html "i", :EM
+ add_html "b", :BOLD
+ add_html "tt", :TT
+ add_html "code", :TT
+ end
+
##
# Return an attribute object with the given turn_on and turn_off bits set
@@ -75,19 +110,19 @@ class RDoc::Markup::AttributeManager
def convert_attrs(str, attrs)
# first do matching ones
- tags = MATCHING_WORD_PAIRS.keys.join("")
+ tags = @matching_word_pairs.keys.join("")
re = /(^|\W)([#{tags}])([#:\\]?[\w.\/-]+?\S?)\2(\W|$)/
1 while str.gsub!(re) do
- attr = MATCHING_WORD_PAIRS[$2]
+ attr = @matching_word_pairs[$2]
attrs.set_attrs($`.length + $1.length + $2.length, $3.length, attr)
$1 + NULL * $2.length + $3 + NULL * $2.length + $4
end
# then non-matching
- unless WORD_PAIR_MAP.empty? then
- WORD_PAIR_MAP.each do |regexp, attr|
+ unless @word_pair_map.empty? then
+ @word_pair_map.each do |regexp, attr|
str.gsub!(regexp) {
attrs.set_attrs($`.length + $1.length, $2.length, attr)
NULL * $1.length + $2 + NULL * $3.length
@@ -96,11 +131,14 @@ class RDoc::Markup::AttributeManager
end
end
+ ##
+ # Converts HTML tags to RDoc attributes
+
def convert_html(str, attrs)
- tags = HTML_TAGS.keys.join '|'
+ tags = @html_tags.keys.join '|'
1 while str.gsub!(/<(#{tags})>(.*?)<\/\1>/i) {
- attr = HTML_TAGS[$1.downcase]
+ attr = @html_tags[$1.downcase]
html_length = $1.length + 2
seq = NULL * html_length
attrs.set_attrs($`.length + html_length, $2.length, attr)
@@ -108,9 +146,12 @@ class RDoc::Markup::AttributeManager
}
end
+ ##
+ # Converts special sequences to RDoc attributes
+
def convert_specials(str, attrs)
- unless SPECIAL.empty?
- SPECIAL.each do |regexp, attr|
+ unless @special.empty?
+ @special.each do |regexp, attr|
str.scan(regexp) do
attrs.set_attrs($`.length, $&.length,
attr | RDoc::Markup::Attribute::SPECIAL)
@@ -120,31 +161,25 @@ class RDoc::Markup::AttributeManager
end
##
- # A \ in front of a character that would normally be processed turns off
- # processing. We do this by turning \< into <#{PROTECT}
-
- PROTECTABLE = %w[<\\]
+ # Escapes special sequences of text to prevent conversion to RDoc
def mask_protected_sequences
- protect_pattern = Regexp.new("\\\\([#{Regexp.escape(PROTECTABLE.join(''))}])")
- @str.gsub!(protect_pattern, "\\1#{PROTECT_ATTR}")
+ @str.gsub!(/\\([#{Regexp.escape @protectable.join('')}])/,
+ "\\1#{PROTECT_ATTR}")
end
+ ##
+ # Unescapes special sequences of text
+
def unmask_protected_sequences
@str.gsub!(/(.)#{PROTECT_ATTR}/, "\\1\000")
end
- def initialize
- add_word_pair("*", "*", :BOLD)
- add_word_pair("_", "_", :EM)
- add_word_pair("+", "+", :TT)
-
- add_html("em", :EM)
- add_html("i", :EM)
- add_html("b", :BOLD)
- add_html("tt", :TT)
- add_html("code", :TT)
- end
+ ##
+ # Adds a markup class with +name+ for words wrapped in the +start+ and
+ # +stop+ character. To make words wrapped with "*" bold:
+ #
+ # am.add_word_pair '*', '*', :BOLD
def add_word_pair(start, stop, name)
raise ArgumentError, "Word flags may not start with '<'" if
@@ -153,24 +188,39 @@ class RDoc::Markup::AttributeManager
bitmap = RDoc::Markup::Attribute.bitmap_for name
if start == stop then
- MATCHING_WORD_PAIRS[start] = bitmap
+ @matching_word_pairs[start] = bitmap
else
pattern = /(#{Regexp.escape start})(\S+)(#{Regexp.escape stop})/
- WORD_PAIR_MAP[pattern] = bitmap
+ @word_pair_map[pattern] = bitmap
end
- PROTECTABLE << start[0,1]
- PROTECTABLE.uniq!
+ @protectable << start[0,1]
+ @protectable.uniq!
end
+ ##
+ # Adds a markup class with +name+ for words surrounded by HTML tag +tag+.
+ # To process emphasis tags:
+ #
+ # am.add_html 'em', :EM
+
def add_html(tag, name)
- HTML_TAGS[tag.downcase] = RDoc::Markup::Attribute.bitmap_for name
+ @html_tags[tag.downcase] = RDoc::Markup::Attribute.bitmap_for name
end
+ ##
+ # Adds a special handler for +pattern+ with +name+. A simple URL handler
+ # would be:
+ #
+ # @am.add_special(/((https?:)\S+\w)/, :HYPERLINK)
+
def add_special(pattern, name)
- SPECIAL[pattern] = RDoc::Markup::Attribute.bitmap_for name
+ @special[pattern] = RDoc::Markup::Attribute.bitmap_for name
end
+ ##
+ # Processes +str+ converting attributes, HTML and specials
+
def flow(str)
@str = str
@@ -178,15 +228,18 @@ class RDoc::Markup::AttributeManager
@attrs = RDoc::Markup::AttrSpan.new @str.length
- convert_attrs(@str, @attrs)
- convert_html(@str, @attrs)
- convert_specials(str, @attrs)
+ convert_attrs @str, @attrs
+ convert_html @str, @attrs
+ convert_specials @str, @attrs
unmask_protected_sequences
- return split_into_flow
+ split_into_flow
end
+ ##
+ # Debug method that prints a string along with its attributes
+
def display_attributes
puts
puts @str.tr(NULL, "!")
@@ -258,7 +311,7 @@ class RDoc::Markup::AttributeManager
# and reset to all attributes off
res << change_attribute(current_attr, 0) if current_attr != 0
- return res
+ res
end
end
diff --git a/lib/rdoc/markup/blank_line.rb b/lib/rdoc/markup/blank_line.rb
new file mode 100644
index 0000000000..a8c07c8e57
--- /dev/null
+++ b/lib/rdoc/markup/blank_line.rb
@@ -0,0 +1,19 @@
+##
+# An empty line
+
+class RDoc::Markup::BlankLine
+
+ def == other # :nodoc:
+ self.class == other.class
+ end
+
+ def accept visitor
+ visitor.accept_blank_line self
+ end
+
+ def pretty_print q # :nodoc:
+ q.text 'blankline'
+ end
+
+end
+
diff --git a/lib/rdoc/markup/document.rb b/lib/rdoc/markup/document.rb
new file mode 100644
index 0000000000..7963e9afe1
--- /dev/null
+++ b/lib/rdoc/markup/document.rb
@@ -0,0 +1,72 @@
+##
+# A Document containing lists, headings, paragraphs, etc.
+
+class RDoc::Markup::Document
+
+ ##
+ # The parts of the Document
+
+ attr_reader :parts
+
+ ##
+ # Creates a new Document with +parts+
+
+ def initialize *parts
+ @parts = []
+ @parts.push(*parts)
+ end
+
+ ##
+ # Appends +part+ to the document
+
+ def << part
+ case part
+ when RDoc::Markup::Document then
+ unless part.empty? then
+ parts.push(*part.parts)
+ parts << RDoc::Markup::BlankLine.new
+ end
+ when String then
+ raise ArgumentError,
+ "expected RDoc::Markup::Document and friends, got String" unless
+ part.empty?
+ else
+ parts << part
+ end
+ end
+
+ def == other # :nodoc:
+ self.class == other.class and @parts == other.parts
+ end
+
+ def accept visitor
+ visitor.start_accepting
+
+ @parts.each do |item|
+ item.accept visitor
+ end
+
+ visitor.end_accepting
+ end
+
+ def empty?
+ @parts.empty?
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 2, '[doc: ', ']' do
+ q.seplist @parts do |part|
+ q.pp part
+ end
+ end
+ end
+
+ ##
+ # Appends +parts+ to the document
+
+ def push *parts
+ self.parts.push(*parts)
+ end
+
+end
+
diff --git a/lib/rdoc/markup/formatter.rb b/lib/rdoc/markup/formatter.rb
index 14cbae59f9..993e523f0c 100644
--- a/lib/rdoc/markup/formatter.rb
+++ b/lib/rdoc/markup/formatter.rb
@@ -1,14 +1,143 @@
require 'rdoc/markup'
+##
+# Base class for RDoc markup formatters
+#
+# Formatters use a visitor pattern to convert content into output.
+
class RDoc::Markup::Formatter
+ InlineTag = Struct.new(:bit, :on, :off)
+
+ ##
+ # Creates a new Formatter
+
def initialize
@markup = RDoc::Markup.new
+ @am = @markup.attribute_manager
+ @attr_tags = []
+
+ @in_tt = 0
+ @tt_bit = RDoc::Markup::Attribute.bitmap_for :TT
+ end
+
+ ##
+ # Add a new set of tags for an attribute. We allow separate start and end
+ # tags for flexibility
+
+ def add_tag(name, start, stop)
+ attr = RDoc::Markup::Attribute.bitmap_for name
+ @attr_tags << InlineTag.new(attr, start, stop)
+ end
+
+ ##
+ # Allows +tag+ to be decorated with additional information.
+
+ def annotate(tag)
+ tag
end
+ ##
+ # Marks up +content+
+
def convert(content)
@markup.convert content, self
end
+ ##
+ # Converts flow items +flow+
+
+ def convert_flow(flow)
+ res = []
+
+ flow.each do |item|
+ case item
+ when String then
+ res << convert_string(item)
+ when RDoc::Markup::AttrChanger then
+ off_tags res, item
+ on_tags res, item
+ when RDoc::Markup::Special then
+ res << convert_special(item)
+ else
+ raise "Unknown flow element: #{item.inspect}"
+ end
+ end
+
+ res.join
+ end
+
+ ##
+ # Converts added specials. See RDoc::Markup#add_special
+
+ def convert_special(special)
+ handled = false
+
+ RDoc::Markup::Attribute.each_name_of special.type do |name|
+ method_name = "handle_special_#{name}"
+
+ if respond_to? method_name then
+ special.text = send method_name, special
+ handled = true
+ end
+ end
+
+ raise "Unhandled special: #{special}" unless handled
+
+ special.text
+ end
+
+ ##
+ # Converts a string to be fancier if desired
+
+ def convert_string string
+ string
+ end
+
+ ##
+ # Are we currently inside tt tags?
+
+ def in_tt?
+ @in_tt > 0
+ end
+
+ def on_tags res, item
+ attr_mask = item.turn_on
+ return if attr_mask.zero?
+
+ @attr_tags.each do |tag|
+ if attr_mask & tag.bit != 0 then
+ res << annotate(tag.on)
+ @in_tt += 1 if tt? tag
+ end
+ end
+ end
+
+ def off_tags res, item
+ attr_mask = item.turn_off
+ return if attr_mask.zero?
+
+ @attr_tags.reverse_each do |tag|
+ if attr_mask & tag.bit != 0 then
+ @in_tt -= 1 if tt? tag
+ res << annotate(tag.off)
+ end
+ end
+ end
+
+ ##
+ # Is +tag+ a tt tag?
+
+ def tt? tag
+ tag.bit == @tt_bit
+ end
+
end
+class RDoc::Markup
+ autoload :ToAnsi, 'rdoc/markup/to_ansi'
+ autoload :ToBs, 'rdoc/markup/to_bs'
+ autoload :ToHtml, 'rdoc/markup/to_html'
+ autoload :ToHtmlCrossref, 'rdoc/markup/to_html_crossref'
+ autoload :ToRdoc, 'rdoc/markup/to_rdoc'
+end
diff --git a/lib/rdoc/markup/formatter_test_case.rb b/lib/rdoc/markup/formatter_test_case.rb
new file mode 100644
index 0000000000..9b9d7cf000
--- /dev/null
+++ b/lib/rdoc/markup/formatter_test_case.rb
@@ -0,0 +1,341 @@
+require 'minitest/unit'
+require 'rdoc/markup/formatter'
+
+##
+# Test case for creating new RDoc::Markup formatters. See
+# test/test_rdoc_markup_to_*.rb for examples.
+
+class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase
+
+ def setup
+ super
+
+ @m = RDoc::Markup.new
+ @am = RDoc::Markup::AttributeManager.new
+ @RM = RDoc::Markup
+
+ @bullet_list = @RM::List.new(:BULLET,
+ @RM::ListItem.new(nil, @RM::Paragraph.new('l1')),
+ @RM::ListItem.new(nil, @RM::Paragraph.new('l2')))
+
+ @label_list = @RM::List.new(:LABEL,
+ @RM::ListItem.new('cat', @RM::Paragraph.new('cats are cool')),
+ @RM::ListItem.new('dog', @RM::Paragraph.new('dogs are cool too')))
+
+ @lalpha_list = @RM::List.new(:LALPHA,
+ @RM::ListItem.new(nil, @RM::Paragraph.new('l1')),
+ @RM::ListItem.new(nil, @RM::Paragraph.new('l2')))
+
+ @note_list = @RM::List.new(:NOTE,
+ @RM::ListItem.new('cat', @RM::Paragraph.new('cats are cool')),
+ @RM::ListItem.new('dog', @RM::Paragraph.new('dogs are cool too')))
+
+ @number_list = @RM::List.new(:NUMBER,
+ @RM::ListItem.new(nil, @RM::Paragraph.new('l1')),
+ @RM::ListItem.new(nil, @RM::Paragraph.new('l2')))
+
+ @ualpha_list = @RM::List.new(:UALPHA,
+ @RM::ListItem.new(nil, @RM::Paragraph.new('l1')),
+ @RM::ListItem.new(nil, @RM::Paragraph.new('l2')))
+ end
+
+ def self.add_visitor_tests
+ self.class_eval do
+ def test_start_accepting
+ @to.start_accepting
+
+ start_accepting
+ end
+
+ def test_end_accepting
+ @to.start_accepting
+ @to.res << 'hi'
+
+ end_accepting
+ end
+
+ def test_accept_blank_line
+ @to.start_accepting
+
+ @to.accept_blank_line @RM::BlankLine.new
+
+ accept_blank_line
+ end
+
+ def test_accept_heading
+ @to.start_accepting
+
+ @to.accept_heading @RM::Heading.new(5, 'Hello')
+
+ accept_heading
+ end
+
+ def test_accept_paragraph
+ @to.start_accepting
+
+ @to.accept_paragraph @RM::Paragraph.new('hi')
+
+ accept_paragraph
+ end
+
+ def test_accept_verbatim
+ @to.start_accepting
+
+ @to.accept_verbatim @RM::Verbatim.new(' ', 'hi', "\n",
+ ' ', 'world', "\n")
+
+ accept_verbatim
+ end
+
+ def test_accept_rule
+ @to.start_accepting
+
+ @to.accept_rule @RM::Rule.new(4)
+
+ accept_rule
+ end
+
+ def test_accept_list_item_start_bullet
+ @to.start_accepting
+
+ @to.accept_list_start @bullet_list
+
+ @to.accept_list_item_start @bullet_list.items.first
+
+ accept_list_item_start_bullet
+ end
+
+ def test_accept_list_item_start_label
+ @to.start_accepting
+
+ @to.accept_list_start @label_list
+
+ @to.accept_list_item_start @label_list.items.first
+
+ accept_list_item_start_label
+ end
+
+ def test_accept_list_item_start_lalpha
+ @to.start_accepting
+
+ @to.accept_list_start @lalpha_list
+
+ @to.accept_list_item_start @lalpha_list.items.first
+
+ accept_list_item_start_lalpha
+ end
+
+ def test_accept_list_item_start_note
+ @to.start_accepting
+
+ @to.accept_list_start @note_list
+
+ @to.accept_list_item_start @note_list.items.first
+
+ accept_list_item_start_note
+ end
+
+ def test_accept_list_item_start_number
+ @to.start_accepting
+
+ @to.accept_list_start @number_list
+
+ @to.accept_list_item_start @number_list.items.first
+
+ accept_list_item_start_number
+ end
+
+ def test_accept_list_item_start_ualpha
+ @to.start_accepting
+
+ @to.accept_list_start @ualpha_list
+
+ @to.accept_list_item_start @ualpha_list.items.first
+
+ accept_list_item_start_ualpha
+ end
+
+ def test_accept_list_item_end_bullet
+ @to.start_accepting
+
+ @to.accept_list_start @bullet_list
+
+ @to.accept_list_item_start @bullet_list.items.first
+
+ @to.accept_list_item_end @bullet_list.items.first
+
+ accept_list_item_end_bullet
+ end
+
+ def test_accept_list_item_end_label
+ @to.start_accepting
+
+ @to.accept_list_start @label_list
+
+ @to.accept_list_item_start @label_list.items.first
+
+ @to.accept_list_item_end @label_list.items.first
+
+ accept_list_item_end_label
+ end
+
+ def test_accept_list_item_end_lalpha
+ @to.start_accepting
+
+ @to.accept_list_start @lalpha_list
+
+ @to.accept_list_item_start @lalpha_list.items.first
+
+ @to.accept_list_item_end @lalpha_list.items.first
+
+ accept_list_item_end_lalpha
+ end
+
+ def test_accept_list_item_end_note
+ @to.start_accepting
+
+ @to.accept_list_start @note_list
+
+ @to.accept_list_item_start @note_list.items.first
+
+ @to.accept_list_item_end @note_list.items.first
+
+ accept_list_item_end_note
+ end
+
+ def test_accept_list_item_end_number
+ @to.start_accepting
+
+ @to.accept_list_start @number_list
+
+ @to.accept_list_item_start @number_list.items.first
+
+ @to.accept_list_item_end @number_list.items.first
+
+ accept_list_item_end_number
+ end
+
+ def test_accept_list_item_end_ualpha
+ @to.start_accepting
+
+ @to.accept_list_start @ualpha_list
+
+ @to.accept_list_item_start @ualpha_list.items.first
+
+ @to.accept_list_item_end @ualpha_list.items.first
+
+ accept_list_item_end_ualpha
+ end
+
+ def test_accept_list_start_bullet
+ @to.start_accepting
+
+ @to.accept_list_start @bullet_list
+
+ accept_list_start_bullet
+ end
+
+ def test_accept_list_start_label
+ @to.start_accepting
+
+ @to.accept_list_start @label_list
+
+ accept_list_start_label
+ end
+
+ def test_accept_list_start_lalpha
+ @to.start_accepting
+
+ @to.accept_list_start @lalpha_list
+
+ accept_list_start_lalpha
+ end
+
+ def test_accept_list_start_note
+ @to.start_accepting
+
+ @to.accept_list_start @note_list
+
+ accept_list_start_note
+ end
+
+ def test_accept_list_start_number
+ @to.start_accepting
+
+ @to.accept_list_start @number_list
+
+ accept_list_start_number
+ end
+
+ def test_accept_list_start_ualpha
+ @to.start_accepting
+
+ @to.accept_list_start @ualpha_list
+
+ accept_list_start_ualpha
+ end
+
+ def test_accept_list_end_bullet
+ @to.start_accepting
+
+ @to.accept_list_start @bullet_list
+
+ @to.accept_list_end @bullet_list
+
+ accept_list_end_bullet
+ end
+
+ def test_accept_list_end_label
+ @to.start_accepting
+
+ @to.accept_list_start @label_list
+
+ @to.accept_list_end @label_list
+
+ accept_list_end_label
+ end
+
+ def test_accept_list_end_lalpha
+ @to.start_accepting
+
+ @to.accept_list_start @lalpha_list
+
+ @to.accept_list_end @lalpha_list
+
+ accept_list_end_lalpha
+ end
+
+ def test_accept_list_end_number
+ @to.start_accepting
+
+ @to.accept_list_start @number_list
+
+ @to.accept_list_end @number_list
+
+ accept_list_end_number
+ end
+
+ def test_accept_list_end_note
+ @to.start_accepting
+
+ @to.accept_list_start @note_list
+
+ @to.accept_list_end @note_list
+
+ accept_list_end_note
+ end
+
+ def test_accept_list_end_ualpha
+ @to.start_accepting
+
+ @to.accept_list_start @ualpha_list
+
+ @to.accept_list_end @ualpha_list
+
+ accept_list_end_ualpha
+ end
+ end
+ end
+
+end
+
diff --git a/lib/rdoc/markup/fragments.rb b/lib/rdoc/markup/fragments.rb
deleted file mode 100644
index 0031b809b4..0000000000
--- a/lib/rdoc/markup/fragments.rb
+++ /dev/null
@@ -1,337 +0,0 @@
-require 'rdoc/markup'
-require 'rdoc/markup/lines'
-
-class RDoc::Markup
-
- ##
- # A Fragment is a chunk of text, subclassed as a paragraph, a list
- # entry, or verbatim text.
-
- class Fragment
- attr_reader :level, :param, :txt
- attr_accessor :type
-
- ##
- # This is a simple factory system that lets us associate fragement
- # types (a string) with a subclass of fragment
-
- TYPE_MAP = {}
-
- def self.type_name(name)
- TYPE_MAP[name] = self
- end
-
- def self.for(line)
- klass = TYPE_MAP[line.type] ||
- raise("Unknown line type: '#{line.type.inspect}:' '#{line.text}'")
- return klass.new(line.level, line.param, line.flag, line.text)
- end
-
- def initialize(level, param, type, txt)
- @level = level
- @param = param
- @type = type
- @txt = ""
- add_text(txt) if txt
- end
-
- def add_text(txt)
- @txt << " " if @txt.length > 0
- @txt << txt.tr_s("\n ", " ").strip
- end
-
- def to_s
- "L#@level: #{self.class.name.split('::')[-1]}\n#@txt"
- end
-
- end
-
- ##
- # A paragraph is a fragment which gets wrapped to fit. We remove all
- # newlines when we're created, and have them put back on output.
-
- class Paragraph < Fragment
- type_name :PARAGRAPH
- end
-
- class BlankLine < Paragraph
- type_name :BLANK
- end
-
- class Heading < Paragraph
- type_name :HEADING
-
- def head_level
- @param.to_i
- end
- end
-
- ##
- # A List is a fragment with some kind of label
-
- class ListBase < Paragraph
- LIST_TYPES = [
- :BULLET,
- :NUMBER,
- :UPPERALPHA,
- :LOWERALPHA,
- :LABELED,
- :NOTE,
- ]
- end
-
- class ListItem < ListBase
- type_name :LIST
-
- def to_s
- text = if [:NOTE, :LABELED].include? type then
- "#{@param}: #{@txt}"
- else
- @txt
- end
-
- "L#@level: #{type} #{self.class.name.split('::')[-1]}\n#{text}"
- end
-
- end
-
- class ListStart < ListBase
- def initialize(level, param, type)
- super(level, param, type, nil)
- end
- end
-
- class ListEnd < ListBase
- def initialize(level, type)
- super(level, "", type, nil)
- end
- end
-
- ##
- # Verbatim code contains lines that don't get wrapped.
-
- class Verbatim < Fragment
- type_name :VERBATIM
-
- def add_text(txt)
- @txt << txt.chomp << "\n"
- end
-
- end
-
- ##
- # A horizontal rule
-
- class Rule < Fragment
- type_name :RULE
- end
-
- ##
- # Collect groups of lines together. Each group will end up containing a flow
- # of text.
-
- class LineCollection
-
- def initialize
- @fragments = []
- end
-
- def add(fragment)
- @fragments << fragment
- end
-
- def each(&b)
- @fragments.each(&b)
- end
-
- def to_a # :nodoc:
- @fragments.map {|fragment| fragment.to_s}
- end
-
- ##
- # Factory for different fragment types
-
- def fragment_for(*args)
- Fragment.for(*args)
- end
-
- ##
- # Tidy up at the end
-
- def normalize
- change_verbatim_blank_lines
- add_list_start_and_ends
- add_list_breaks
- tidy_blank_lines
- end
-
- def to_s
- @fragments.join("\n----\n")
- end
-
- def accept(am, visitor)
- visitor.start_accepting
-
- @fragments.each do |fragment|
- case fragment
- when Verbatim
- visitor.accept_verbatim(am, fragment)
- when Rule
- visitor.accept_rule(am, fragment)
- when ListStart
- visitor.accept_list_start(am, fragment)
- when ListEnd
- visitor.accept_list_end(am, fragment)
- when ListItem
- visitor.accept_list_item(am, fragment)
- when BlankLine
- visitor.accept_blank_line(am, fragment)
- when Heading
- visitor.accept_heading(am, fragment)
- when Paragraph
- visitor.accept_paragraph(am, fragment)
- end
- end
-
- visitor.end_accepting
- end
-
- private
-
- # If you have:
- #
- # normal paragraph text.
- #
- # this is code
- #
- # and more code
- #
- # You'll end up with the fragments Paragraph, BlankLine, Verbatim,
- # BlankLine, Verbatim, BlankLine, etc.
- #
- # The BlankLine in the middle of the verbatim chunk needs to be changed to
- # a real verbatim newline, and the two verbatim blocks merged
-
- def change_verbatim_blank_lines
- frag_block = nil
- blank_count = 0
- @fragments.each_with_index do |frag, i|
- if frag_block.nil?
- frag_block = frag if Verbatim === frag
- else
- case frag
- when Verbatim
- blank_count.times { frag_block.add_text("\n") }
- blank_count = 0
- frag_block.add_text(frag.txt)
- @fragments[i] = nil # remove out current fragment
- when BlankLine
- if frag_block
- blank_count += 1
- @fragments[i] = nil
- end
- else
- frag_block = nil
- blank_count = 0
- end
- end
- end
- @fragments.compact!
- end
-
- ##
- # List nesting is implicit given the level of indentation. Make it
- # explicit, just to make life a tad easier for the output processors
-
- def add_list_start_and_ends
- level = 0
- res = []
- type_stack = []
-
- @fragments.each do |fragment|
- # $stderr.puts "#{level} : #{fragment.class.name} : #{fragment.level}"
- new_level = fragment.level
- while (level < new_level)
- level += 1
- type = fragment.type
- res << ListStart.new(level, fragment.param, type) if type
- type_stack.push type
- # $stderr.puts "Start: #{level}"
- end
-
- while level > new_level
- type = type_stack.pop
- res << ListEnd.new(level, type) if type
- level -= 1
- # $stderr.puts "End: #{level}, #{type}"
- end
-
- res << fragment
- level = fragment.level
- end
- level.downto(1) do |i|
- type = type_stack.pop
- res << ListEnd.new(i, type) if type
- end
-
- @fragments = res
- end
-
- ##
- # Inserts start/ends between list entries at the same level that have
- # different element types
-
- def add_list_breaks
- res = @fragments
-
- @fragments = []
- list_stack = []
-
- res.each do |fragment|
- case fragment
- when ListStart
- list_stack.push fragment
- when ListEnd
- start = list_stack.pop
- fragment.type = start.type
- when ListItem
- l = list_stack.last
- if fragment.type != l.type
- @fragments << ListEnd.new(l.level, l.type)
- start = ListStart.new(l.level, fragment.param, fragment.type)
- @fragments << start
- list_stack.pop
- list_stack.push start
- end
- else
- ;
- end
- @fragments << fragment
- end
- end
-
- ##
- # Tidy up the blank lines:
- # * change Blank/ListEnd into ListEnd/Blank
- # * remove blank lines at the front
-
- def tidy_blank_lines
- (@fragments.size - 1).times do |i|
- if BlankLine === @fragments[i] and ListEnd === @fragments[i+1] then
- @fragments[i], @fragments[i+1] = @fragments[i+1], @fragments[i]
- end
- end
-
- # remove leading blanks
- @fragments.each_with_index do |f, i|
- break unless f.kind_of? BlankLine
- @fragments[i] = nil
- end
-
- @fragments.compact!
- end
-
- end
-
-end
-
diff --git a/lib/rdoc/markup/heading.rb b/lib/rdoc/markup/heading.rb
new file mode 100644
index 0000000000..21e2574d68
--- /dev/null
+++ b/lib/rdoc/markup/heading.rb
@@ -0,0 +1,17 @@
+##
+# A heading with a level (1-6) and text
+
+class RDoc::Markup::Heading < Struct.new :level, :text
+
+ def accept visitor
+ visitor.accept_heading self
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 2, "[head: #{level} ", ']' do
+ q.pp text
+ end
+ end
+
+end
+
diff --git a/lib/rdoc/markup/inline.rb b/lib/rdoc/markup/inline.rb
index 46c9b5822c..1b5eac45ae 100644
--- a/lib/rdoc/markup/inline.rb
+++ b/lib/rdoc/markup/inline.rb
@@ -1,5 +1,3 @@
-require 'rdoc/markup'
-
class RDoc::Markup
##
@@ -7,6 +5,10 @@ class RDoc::Markup
# value.
class Attribute
+
+ ##
+ # Special attribute type. See RDoc::Markup#add_special
+
SPECIAL = 1
@@name_to_bitmap = { :_SPECIAL_ => SPECIAL }
@@ -37,17 +39,18 @@ class RDoc::Markup
yield name.to_s if (bitmap & bit) != 0
end
end
+
end
- AttrChanger = Struct.new(:turn_on, :turn_off)
+ AttrChanger = Struct.new :turn_on, :turn_off # :nodoc:
##
# An AttrChanger records a change in attributes. It contains a bitmap of the
# attributes to turn on, and a bitmap of those to turn off.
class AttrChanger
- def to_s
- "Attr: +#{Attribute.as_string(turn_on)}/-#{Attribute.as_string(turn_on)}"
+ def to_s # :nodoc:
+ "Attr: +#{Attribute.as_string turn_on}/-#{Attribute.as_string turn_on}"
end
end
@@ -55,42 +58,66 @@ class RDoc::Markup
# An array of attributes which parallels the characters in a string.
class AttrSpan
+
+ ##
+ # Creates a new AttrSpan for +length+ characters
+
def initialize(length)
@attrs = Array.new(length, 0)
end
+ ##
+ # Toggles +bits+ from +start+ to +length+
def set_attrs(start, length, bits)
for i in start ... (start+length)
@attrs[i] |= bits
end
end
+ ##
+ # Acccesses flags for character +n+
+
def [](n)
@attrs[n]
end
+
end
##
# Hold details of a special sequence
class Special
+
+ ##
+ # Special type
+
attr_reader :type
+
+ ##
+ # Special text
+
attr_accessor :text
+ ##
+ # Creates a new special sequence of +type+ with +text+
+
def initialize(type, text)
@type, @text = type, text
end
+ ##
+ # Specials are equal when the have the same text and type
+
def ==(o)
self.text == o.text && self.type == o.type
end
- def inspect
+ def inspect # :nodoc:
"#<RDoc::Markup::Special:0x%x @type=%p, name=%p @text=%p>" % [
object_id, @type, RDoc::Markup::Attribute.as_string(type), text.dump]
end
- def to_s
+ def to_s # :nodoc:
"Special: type=#{type}, name=#{RDoc::Markup::Attribute.as_string type}, text=#{text.dump}"
end
@@ -98,4 +125,3 @@ class RDoc::Markup
end
-require 'rdoc/markup/attribute_manager'
diff --git a/lib/rdoc/markup/lines.rb b/lib/rdoc/markup/lines.rb
deleted file mode 100644
index 069492122f..0000000000
--- a/lib/rdoc/markup/lines.rb
+++ /dev/null
@@ -1,152 +0,0 @@
-class RDoc::Markup
-
- ##
- # We store the lines we're working on as objects of class Line. These
- # contain the text of the line, along with a flag indicating the line type,
- # and an indentation level.
-
- class Line
- INFINITY = 9999
-
- LINE_TYPES = [
- :BLANK,
- :HEADING,
- :LIST,
- :PARAGRAPH,
- :RULE,
- :VERBATIM,
- ]
-
- # line type
- attr_accessor :type
-
- # The indentation nesting level
- attr_accessor :level
-
- # The contents
- attr_accessor :text
-
- # A prefix or parameter. For LIST lines, this is
- # the text that introduced the list item (the label)
- attr_accessor :param
-
- # A flag. For list lines, this is the type of the list
- attr_accessor :flag
-
- # the number of leading spaces
- attr_accessor :leading_spaces
-
- # true if this line has been deleted from the list of lines
- attr_accessor :deleted
-
- def initialize(text)
- @text = text.dup
- @deleted = false
-
- # expand tabs
- 1 while @text.gsub!(/\t+/) { ' ' * (8*$&.length - $`.length % 8)} && $~ #`
-
- # Strip trailing whitespace
- @text.sub!(/\s+$/, '')
-
- # and look for leading whitespace
- if @text.length > 0
- @text =~ /^(\s*)/
- @leading_spaces = $1.length
- else
- @leading_spaces = INFINITY
- end
- end
-
- # Return true if this line is blank
- def blank?
- @text.empty?
- end
-
- # stamp a line with a type, a level, a prefix, and a flag
- def stamp(type, level, param="", flag=nil)
- @type, @level, @param, @flag = type, level, param, flag
- end
-
- ##
- # Strip off the leading margin
-
- def strip_leading(size)
- if @text.size > size
- @text[0,size] = ""
- else
- @text = ""
- end
- end
-
- def to_s
- "#@type#@level: #@text"
- end
- end
-
- ##
- # A container for all the lines.
-
- class Lines
-
- include Enumerable
-
- attr_reader :lines # :nodoc:
-
- def initialize(lines)
- @lines = lines
- rewind
- end
-
- def empty?
- @lines.size.zero?
- end
-
- def each
- @lines.each do |line|
- yield line unless line.deleted
- end
- end
-
-# def [](index)
-# @lines[index]
-# end
-
- def rewind
- @nextline = 0
- end
-
- def next
- begin
- res = @lines[@nextline]
- @nextline += 1 if @nextline < @lines.size
- end while res and res.deleted and @nextline < @lines.size
- res
- end
-
- def unget
- @nextline -= 1
- end
-
- def delete(a_line)
- a_line.deleted = true
- end
-
- def normalize
- margin = @lines.collect{|l| l.leading_spaces}.min
- margin = 0 if margin == :INFINITY
- @lines.each {|line| line.strip_leading(margin) } if margin > 0
- end
-
- def as_text
- @lines.map {|l| l.text}.join("\n")
- end
-
- def line_types
- @lines.map {|l| l.type }
- end
-
- end
-
-end
-
diff --git a/lib/rdoc/markup/list.rb b/lib/rdoc/markup/list.rb
new file mode 100644
index 0000000000..75326ed836
--- /dev/null
+++ b/lib/rdoc/markup/list.rb
@@ -0,0 +1,78 @@
+##
+# A List of ListItems
+
+class RDoc::Markup::List
+
+ ##
+ # The list's type
+
+ attr_accessor :type
+
+ ##
+ # Items in the list
+
+ attr_reader :items
+
+ ##
+ # Creates a new list of +type+ with +items+
+
+ def initialize type = nil, *items
+ @type = type
+ @items = []
+ @items.push(*items)
+ end
+
+ ##
+ # Appends +item+ to the list
+
+ def << item
+ @items << item
+ end
+
+ def == other # :nodoc:
+ self.class == other.class and
+ @type == other.type and
+ @items == other.items
+ end
+
+ def accept visitor
+ visitor.accept_list_start self
+
+ @items.each do |item|
+ item.accept visitor
+ end
+
+ visitor.accept_list_end self
+ end
+
+ ##
+ # Is the list empty?
+
+ def empty?
+ @items.empty?
+ end
+
+ ##
+ # Returns the last item in the list
+
+ def last
+ @items.last
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 2, "[list: #{@type} ", ']' do
+ q.seplist @items do |item|
+ q.pp item
+ end
+ end
+ end
+
+ ##
+ # Appends +items+ to the list
+
+ def push *items
+ @items.push(*items)
+ end
+
+end
+
diff --git a/lib/rdoc/markup/list_item.rb b/lib/rdoc/markup/list_item.rb
new file mode 100644
index 0000000000..500e814fe1
--- /dev/null
+++ b/lib/rdoc/markup/list_item.rb
@@ -0,0 +1,83 @@
+##
+# An item within a List that contains paragraphs, headings, etc.
+
+class RDoc::Markup::ListItem
+
+ ##
+ # The label for the ListItem
+
+ attr_accessor :label
+
+ ##
+ # Parts of the ListItem
+
+ attr_reader :parts
+
+ ##
+ # Creates a new ListItem with an optional +label+ containing +parts+
+
+ def initialize label = nil, *parts
+ @label = label
+ @parts = []
+ @parts.push(*parts)
+ end
+
+ ##
+ # Appends +part+ to the ListItem
+
+ def << part
+ @parts << part
+ end
+
+ def == other # :nodoc:
+ self.class == other.class and
+ @label == other.label and
+ @parts == other.parts
+ end
+
+ def accept visitor
+ visitor.accept_list_item_start self
+
+ @parts.each do |part|
+ part.accept visitor
+ end
+
+ visitor.accept_list_item_end self
+ end
+
+ ##
+ # Is the ListItem empty?
+
+ def empty?
+ @parts.empty?
+ end
+
+ ##
+ # Length of parts in the ListItem
+
+ def length
+ @parts.length
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 2, '[item: ', ']' do
+ if @label then
+ q.text @label
+ q.breakable
+ end
+
+ q.seplist @parts do |part|
+ q.pp part
+ end
+ end
+ end
+
+ ##
+ # Adds +parts+ to the ListItem
+
+ def push *parts
+ @parts.push(*parts)
+ end
+
+end
+
diff --git a/lib/rdoc/markup/paragraph.rb b/lib/rdoc/markup/paragraph.rb
new file mode 100644
index 0000000000..bc23423dfc
--- /dev/null
+++ b/lib/rdoc/markup/paragraph.rb
@@ -0,0 +1,66 @@
+##
+# A Paragraph of text
+
+class RDoc::Markup::Paragraph
+
+ ##
+ # The component parts of the list
+
+ attr_reader :parts
+
+ ##
+ # Creates a new Paragraph containing +parts+
+
+ def initialize *parts
+ @parts = []
+ @parts.push(*parts)
+ end
+
+ ##
+ # Appends +text+ to the Paragraph
+
+ def << text
+ @parts << text
+ end
+
+ def == other # :nodoc:
+ self.class == other.class and text == other.text
+ end
+
+ def accept visitor
+ visitor.accept_paragraph self
+ end
+
+ ##
+ # Appends +other+'s parts into this Paragraph
+
+ def merge other
+ @parts.push(*other.parts)
+ end
+
+ def pretty_print q # :nodoc:
+ self.class.name =~ /.*::(\w{4})/i
+
+ q.group 2, "[#{$1.downcase}: ", ']' do
+ q.seplist @parts do |part|
+ q.pp part
+ end
+ end
+ end
+
+ ##
+ # Appends +texts+ onto this Paragraph
+
+ def push *texts
+ self.parts.push(*texts)
+ end
+
+ ##
+ # The text of this paragraph
+
+ def text
+ @parts.join ' '
+ end
+
+end
+
diff --git a/lib/rdoc/markup/parser.rb b/lib/rdoc/markup/parser.rb
new file mode 100644
index 0000000000..c0d6519fd5
--- /dev/null
+++ b/lib/rdoc/markup/parser.rb
@@ -0,0 +1,528 @@
+require 'strscan'
+require 'rdoc/text'
+
+##
+# A recursive-descent parser for RDoc markup.
+#
+# The parser tokenizes an input string then parses the tokens into a Document.
+# Documents can be converted into output formats by writing a visitor like
+# RDoc::Markup::ToHTML.
+#
+# The parser only handles the block-level constructs Paragraph, List,
+# ListItem, Heading, Verbatim, BlankLine and Rule. Inline markup such as
+# <tt>\+blah\+</tt> is handled separately by RDoc::Markup::AttributeManager.
+#
+# To see what markup the Parser implements read RDoc. To see how to use
+# RDoc markup to format text in your program read RDoc::Markup.
+
+class RDoc::Markup::Parser
+
+ include RDoc::Text
+
+ ##
+ # List token types
+
+ LIST_TOKENS = [
+ :BULLET,
+ :LABEL,
+ :LALPHA,
+ :NOTE,
+ :NUMBER,
+ :UALPHA,
+ ]
+
+ ##
+ # Parser error subclass
+
+ class Error < RuntimeError; end
+
+ ##
+ # Raised when the parser is unable to handle the given markup
+
+ class ParseError < Error; end
+
+ ##
+ # Enables display of debugging information
+
+ attr_accessor :debug
+
+ ##
+ # Token accessor
+
+ attr_reader :tokens
+
+ ##
+ # Parsers +str+ into a Document
+
+ def self.parse str
+ parser = new
+ #parser.debug = true
+ parser.tokenize str
+ RDoc::Markup::Document.new(*parser.parse)
+ end
+
+ ##
+ # Returns a token stream for +str+, for testing
+
+ def self.tokenize str
+ parser = new
+ parser.tokenize str
+ parser.tokens
+ end
+
+ ##
+ # Creates a new Parser. See also ::parse
+
+ def initialize
+ @tokens = []
+ @current_token = nil
+ @debug = false
+
+ @line = 0
+ @line_pos = 0
+ end
+
+ ##
+ # Builds a Heading of +level+
+
+ def build_heading level
+ heading = RDoc::Markup::Heading.new level, text
+ skip :NEWLINE
+
+ heading
+ end
+
+ ##
+ # Builds a List flush to +margin+
+
+ def build_list margin
+ p :list_start => margin if @debug
+
+ list = RDoc::Markup::List.new
+
+ until @tokens.empty? do
+ type, data, column, = get
+
+ case type
+ when :BULLET, :LABEL, :LALPHA, :NOTE, :NUMBER, :UALPHA then
+ list_type = type
+
+ if column < margin then
+ unget
+ break
+ end
+
+ if list.type and list.type != list_type then
+ unget
+ break
+ end
+
+ list.type = list_type
+
+ 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
+ end
+ else
+ data = nil
+ _, indent, = get
+ end
+
+ list_item = build_list_item(margin + indent, data)
+
+ list << list_item if list_item
+ else
+ unget
+ break
+ end
+ end
+
+ p :list_end => margin if @debug
+
+ return nil if list.empty?
+
+ 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+
+
+ def build_paragraph margin
+ p :paragraph_start => margin if @debug
+
+ paragraph = RDoc::Markup::Paragraph.new
+
+ 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
+
+ paragraph << data
+ skip :NEWLINE
+ else
+ unget
+ break
+ end
+ end
+
+ p :paragraph_end => margin if @debug
+
+ paragraph
+ end
+
+ ##
+ # Builds a Verbatim that is flush to +margin+
+
+ def build_verbatim margin
+ p :verbatim_begin => margin if @debug
+ verbatim = RDoc::Markup::Verbatim.new
+
+ until @tokens.empty? do
+ type, data, column, = get
+
+ case type
+ when :INDENT then
+ if margin >= data then
+ unget
+ break
+ 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
+ unget
+ break
+ end
+ end
+
+ verbatim.normalize
+
+ p :verbatim_end => margin if @debug
+
+ verbatim
+ end
+
+ ##
+ # Pulls the next token from the stream.
+
+ def get
+ @current_token = @tokens.shift
+ p :get => @current_token if @debug
+ @current_token
+ end
+
+ ##
+ # Parses the tokens into a Document
+
+ def parse 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
+ end
+
+ 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
+ when :RULE then
+ document << 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
+ 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
+ else
+ type, data, column, line = @current_token
+ raise ParseError,
+ "Unhandled token #{type} (#{data.inspect}) at #{line}:#{column}"
+ end
+ end
+
+ p :parse_end => indent if @debug
+
+ document
+ end
+
+ ##
+ # Returns the next token on the stream without modifying the stream
+
+ def peek_token
+ token = @tokens.first || []
+ p :peek => token if @debug
+ token
+ end
+
+ ##
+ # Skips a token of +token_type+, optionally raising an error.
+
+ def skip token_type, error = true
+ type, data, = 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]
+ end
+
+ ##
+ # Turns text +input+ into a stream of tokens
+
+ def tokenize input
+ s = StringScanner.new input
+
+ @line = 0
+ @line_pos = 0
+
+ until s.eos? do
+ pos = s.pos
+
+ @tokens << case
+ 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)]
+ 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
+ [: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
+ 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)]
+ when s.scan(/\[(.*?)\]( +|$)/) then
+ @tokens << [:LABEL, s[1], *token_pos(pos)]
+ [:SPACE, s.matched_size, *token_pos(pos)]
+ when s.scan(/(.*?)::( +|$)/) then
+ @tokens << [:NOTE, s[1], *token_pos(pos)]
+ [:SPACE, s.matched_size, *token_pos(pos)]
+ else s.scan(/.*/)
+ [:TEXT, s.matched, *token_pos(pos)]
+ end
+ end
+
+ self
+ end
+
+ ##
+ # Returns the current token or +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
+ end
+
+end
+
+require 'rdoc/markup/blank_line'
+require 'rdoc/markup/document'
+require 'rdoc/markup/heading'
+require 'rdoc/markup/list'
+require 'rdoc/markup/list_item'
+require 'rdoc/markup/paragraph'
+require 'rdoc/markup/rule'
+require 'rdoc/markup/verbatim'
+
diff --git a/lib/rdoc/markup/preprocess.rb b/lib/rdoc/markup/preprocess.rb
index 00dd4be4ad..a175d179cf 100644
--- a/lib/rdoc/markup/preprocess.rb
+++ b/lib/rdoc/markup/preprocess.rb
@@ -7,6 +7,10 @@ require 'rdoc/markup'
class RDoc::Markup::PreProcess
+ ##
+ # Creates a new pre-processor for +input_file_name+ that will look for
+ # included files in +include_path+
+
def initialize(input_file_name, include_path)
@input_file_name = input_file_name
@include_path = include_path
@@ -44,15 +48,16 @@ class RDoc::Markup::PreProcess
def include_file(name, indent)
if full_name = find_include_file(name) then
- content = File.open(full_name) {|f| f.read}
+ content = File.read full_name
+
# strip leading '#'s, but only if all lines start with them
- if content =~ /^[^#]/
+ if content =~ /^[^#]/ then
content.gsub(/^/, indent)
else
content.gsub(/^#?/, indent)
end
else
- $stderr.puts "Couldn't find file to include: '#{name}'"
+ $stderr.puts "Couldn't find file to include '#{name}' from #{@input_file_name}"
''
end
end
diff --git a/lib/rdoc/markup/rule.rb b/lib/rdoc/markup/rule.rb
new file mode 100644
index 0000000000..4fcd040d2b
--- /dev/null
+++ b/lib/rdoc/markup/rule.rb
@@ -0,0 +1,17 @@
+##
+# A horizontal rule with a weight
+
+class RDoc::Markup::Rule < Struct.new :weight
+
+ def accept visitor
+ visitor.accept_rule self
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 2, '[rule:', ']' do
+ q.pp weight
+ end
+ end
+
+end
+
diff --git a/lib/rdoc/markup/to_ansi.rb b/lib/rdoc/markup/to_ansi.rb
new file mode 100644
index 0000000000..9a5be8babb
--- /dev/null
+++ b/lib/rdoc/markup/to_ansi.rb
@@ -0,0 +1,72 @@
+require 'rdoc/markup/inline'
+
+##
+# Outputs RDoc markup with vibrant ANSI color!
+
+class RDoc::Markup::ToAnsi < RDoc::Markup::ToRdoc
+
+ def initialize
+ super
+
+ @headings.clear
+ @headings[1] = ["\e[1;32m", "\e[m"]
+ @headings[2] = ["\e[4;32m", "\e[m"]
+ @headings[3] = ["\e[32m", "\e[m"]
+ end
+
+ ##
+ # Maps attributes to ANSI sequences
+
+ def init_tags
+ add_tag :BOLD, "\e[1m", "\e[m"
+ add_tag :TT, "\e[7m", "\e[m"
+ add_tag :EM, "\e[4m", "\e[m"
+ end
+
+ def accept_list_item_end list_item
+ width = case @list_type.last
+ when :BULLET then
+ 2
+ when :NOTE, :LABEL then
+ @res << "\n"
+ 2
+ else
+ bullet = @list_index.last.to_s
+ @list_index[-1] = @list_index.last.succ
+ bullet.length + 2
+ end
+
+ @indent -= width
+ end
+
+ 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
+
+ case @list_type.last
+ when :NOTE, :LABEL then
+ @indent += 2
+ @prefix = bullet + (' ' * @indent)
+ else
+ @prefix = (' ' * @indent) + bullet.ljust(bullet.length + 1)
+
+ width = bullet.gsub(/\e\[[\d;]*m/, '').length + 1
+
+ @indent += width
+ end
+ end
+
+ def start_accepting
+ super
+
+ @res = ["\e[0m"]
+ end
+
+end
+
diff --git a/lib/rdoc/markup/to_bs.rb b/lib/rdoc/markup/to_bs.rb
new file mode 100644
index 0000000000..e7af129824
--- /dev/null
+++ b/lib/rdoc/markup/to_bs.rb
@@ -0,0 +1,74 @@
+require 'rdoc/markup/inline'
+
+##
+# Outputs RDoc markup with hot backspace action! You will probably need a
+# pager to use this output format.
+#
+# This formatter won't work on 1.8.6 because it lacks String#chars.
+
+class RDoc::Markup::ToBs < RDoc::Markup::ToRdoc
+
+ def initialize
+ super
+
+ @in_b = false
+ @in_em = false
+ end
+
+ ##
+ # Sets a flag that is picked up by #annotate to do the right thing in
+ # #convert_string
+
+ def init_tags
+ add_tag :BOLD, '+b', '-b'
+ add_tag :EM, '+_', '-_'
+ end
+
+ def accept_heading heading
+ use_prefix or @res << ' ' * @indent
+ @res << @headings[heading.level][0]
+ @in_b = true
+ @res << attributes(heading.text)
+ @in_b = false
+ @res << @headings[heading.level][1]
+ @res << "\n"
+ end
+
+ ##
+ # Turns on or off special handling for +convert_string+
+
+ def annotate tag
+ case tag
+ when '+b' then @in_b = true
+ when '-b' then @in_b = false
+ when '+_' then @in_em = true
+ when '-_' then @in_em = false
+ end
+
+ ''
+ end
+
+ ##
+ # Calls convert_string on the result of convert_special
+
+ def convert_special special
+ convert_string super
+ end
+
+ ##
+ # Adds bold or underline mixed with backspaces
+
+ def convert_string string
+ return string unless string.respond_to? :chars # your ruby is lame
+ return string unless @in_b or @in_em
+ chars = if @in_b then
+ string.chars.map do |char| "#{char}\b#{char}" end
+ elsif @in_em then
+ string.chars.map do |char| "_\b#{char}" end
+ end
+
+ chars.join
+ end
+
+end
+
diff --git a/lib/rdoc/markup/to_flow.rb b/lib/rdoc/markup/to_flow.rb
deleted file mode 100644
index 3d87b3e9c3..0000000000
--- a/lib/rdoc/markup/to_flow.rb
+++ /dev/null
@@ -1,185 +0,0 @@
-require 'rdoc/markup/formatter'
-require 'rdoc/markup/fragments'
-require 'rdoc/markup/inline'
-require 'cgi'
-
-class RDoc::Markup
-
- module Flow
- P = Struct.new(:body)
- VERB = Struct.new(:body)
- RULE = Struct.new(:width)
- class LIST
- attr_reader :type, :contents
- def initialize(type)
- @type = type
- @contents = []
- end
- def <<(stuff)
- @contents << stuff
- end
- end
- LI = Struct.new(:label, :body)
- H = Struct.new(:level, :text)
- end
-
- class ToFlow < RDoc::Markup::Formatter
- LIST_TYPE_TO_HTML = {
- :BULLET => [ "<ul>", "</ul>" ],
- :NUMBER => [ "<ol>", "</ol>" ],
- :UPPERALPHA => [ "<ol>", "</ol>" ],
- :LOWERALPHA => [ "<ol>", "</ol>" ],
- :LABELED => [ "<dl>", "</dl>" ],
- :NOTE => [ "<table>", "</table>" ],
- }
-
- InlineTag = Struct.new(:bit, :on, :off)
-
- def initialize
- super
-
- init_tags
- end
-
- ##
- # Set up the standard mapping of attributes to HTML tags
-
- def init_tags
- @attr_tags = [
- InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:BOLD), "<b>", "</b>"),
- InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:TT), "<tt>", "</tt>"),
- InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:EM), "<em>", "</em>"),
- ]
- end
-
- ##
- # Add a new set of HTML tags for an attribute. We allow separate start and
- # end tags for flexibility
-
- def add_tag(name, start, stop)
- @attr_tags << InlineTag.new(RDoc::Markup::Attribute.bitmap_for(name), start, stop)
- end
-
- ##
- # Given an HTML tag, decorate it with class information and the like if
- # required. This is a no-op in the base class, but is overridden in HTML
- # output classes that implement style sheets
-
- def annotate(tag)
- tag
- end
-
- ##
- # Here's the client side of the visitor pattern
-
- def start_accepting
- @res = []
- @list_stack = []
- end
-
- def end_accepting
- @res
- end
-
- def accept_paragraph(am, fragment)
- @res << Flow::P.new((convert_flow(am.flow(fragment.txt))))
- end
-
- def accept_verbatim(am, fragment)
- @res << Flow::VERB.new((convert_flow(am.flow(fragment.txt))))
- end
-
- def accept_rule(am, fragment)
- size = fragment.param
- size = 10 if size > 10
- @res << Flow::RULE.new(size)
- end
-
- def accept_list_start(am, fragment)
- @list_stack.push(@res)
- list = Flow::LIST.new(fragment.type)
- @res << list
- @res = list
- end
-
- def accept_list_end(am, fragment)
- @res = @list_stack.pop
- end
-
- def accept_list_item(am, fragment)
- @res << Flow::LI.new(fragment.param, convert_flow(am.flow(fragment.txt)))
- end
-
- def accept_blank_line(am, fragment)
- # @res << annotate("<p />") << "\n"
- end
-
- def accept_heading(am, fragment)
- @res << Flow::H.new(fragment.head_level, convert_flow(am.flow(fragment.txt)))
- end
-
- private
-
- def on_tags(res, item)
- attr_mask = item.turn_on
- return if attr_mask.zero?
-
- @attr_tags.each do |tag|
- if attr_mask & tag.bit != 0
- res << annotate(tag.on)
- end
- end
- end
-
- def off_tags(res, item)
- attr_mask = item.turn_off
- return if attr_mask.zero?
-
- @attr_tags.reverse_each do |tag|
- if attr_mask & tag.bit != 0
- res << annotate(tag.off)
- end
- end
- end
-
- def convert_flow(flow)
- res = ""
- flow.each do |item|
- case item
- when String
- res << convert_string(item)
- when AttrChanger
- off_tags(res, item)
- on_tags(res, item)
- when Special
- res << convert_special(item)
- else
- raise "Unknown flow element: #{item.inspect}"
- end
- end
- res
- end
-
- def convert_string(item)
- CGI.escapeHTML(item)
- end
-
- def convert_special(special)
- handled = false
- Attribute.each_name_of(special.type) do |name|
- method_name = "handle_special_#{name}"
- if self.respond_to? method_name
- special.text = send(method_name, special)
- handled = true
- end
- end
-
- raise "Unhandled special: #{special}" unless handled
-
- special.text
- end
-
- end
-
-end
-
diff --git a/lib/rdoc/markup/to_html.rb b/lib/rdoc/markup/to_html.rb
index 0165042d84..f060fd1c72 100644
--- a/lib/rdoc/markup/to_html.rb
+++ b/lib/rdoc/markup/to_html.rb
@@ -1,38 +1,28 @@
require 'rdoc/markup/formatter'
-require 'rdoc/markup/fragments'
require 'rdoc/markup/inline'
require 'cgi'
+##
+# Outputs RDoc markup as HTML
+
class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
+ ##
+ # Maps RDoc::Markup::Parser::LIST_TOKENS types to HTML tags
+
LIST_TYPE_TO_HTML = {
- :BULLET => %w[<ul> </ul>],
- :NUMBER => %w[<ol> </ol>],
- :UPPERALPHA => %w[<ol> </ol>],
- :LOWERALPHA => %w[<ol> </ol>],
- :LABELED => %w[<dl> </dl>],
- :NOTE => %w[<table> </table>],
+ :BULLET => ['<ul>', '</ul>'],
+ :LABEL => ['<dl>', '</dl>'],
+ :LALPHA => ['<ol style="display: lower-alpha">', '</ol>'],
+ :NOTE => ['<table>', '</table>'],
+ :NUMBER => ['<ol>', '</ol>'],
+ :UALPHA => ['<ol style="display: upper-alpha">', '</ol>'],
}
- InlineTag = Struct.new(:bit, :on, :off)
-
- def initialize
- super
-
- # @in_tt - tt nested levels count
- # @tt_bit - cache
- @in_tt = 0
- @tt_bit = RDoc::Markup::Attribute.bitmap_for :TT
-
- # external hyperlinks
- @markup.add_special(/((link:|https?:|mailto:|ftp:|www\.)\S+\w)/, :HYPERLINK)
-
- # and links of the form <text>[<url>]
- @markup.add_special(/(((\{.*?\})|\b\S+?)\[\S+?\.\S+?\])/, :TIDYLINK)
-
- init_tags
- end
+ attr_reader :res # :nodoc:
+ attr_reader :in_list_entry # :nodoc:
+ attr_reader :list # :nodoc:
##
# Converts a target url to one that is relative to a given path
@@ -44,6 +34,9 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
from = from.split "/"
to = to.split "/"
+ from.delete '.'
+ to.delete '.'
+
while from.size > 0 and to.size > 0 and from[0] == to[0] do
from.shift
to.shift
@@ -55,6 +48,31 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
File.join(*from)
end
+ def initialize
+ super
+
+ @th = nil
+ @in_list_entry = nil
+ @list = nil
+
+ # external hyperlinks
+ @markup.add_special(/((link:|https?:|mailto:|ftp:|www\.)\S+\w)/, :HYPERLINK)
+
+ # and links of the form <text>[<url>]
+ @markup.add_special(/(((\{.*?\})|\b\S+?)\[\S+?\.\S+?\])/, :TIDYLINK)
+
+ init_tags
+ end
+
+ ##
+ # Maps attributes to HTML tags
+
+ def init_tags
+ add_tag :BOLD, "<b>", "</b>"
+ add_tag :TT, "<tt>", "</tt>"
+ add_tag :EM, "<em>", "</em>"
+ end
+
##
# Generate a hyperlink for url, labeled with text. Handle the
# special cases for img: and link: described under handle_special_HYPERLINK
@@ -113,195 +131,121 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
end
##
- # are we currently inside <tt> tags?
-
- def in_tt?
- @in_tt > 0
- end
-
- ##
- # is +tag+ a <tt> tag?
-
- def tt?(tag)
- tag.bit == @tt_bit
- end
-
- ##
- # Set up the standard mapping of attributes to HTML tags
-
- def init_tags
- @attr_tags = [
- InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:BOLD), "<b>", "</b>"),
- InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:TT), "<tt>", "</tt>"),
- InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:EM), "<em>", "</em>"),
- ]
- end
-
- ##
- # Add a new set of HTML tags for an attribute. We allow separate start and
- # end tags for flexibility.
+ # This is a higher speed (if messier) version of wrap
- def add_tag(name, start, stop)
- @attr_tags << InlineTag.new(RDoc::Markup::Attribute.bitmap_for(name), start, stop)
- end
+ def wrap(txt, line_len = 76)
+ res = []
+ sp = 0
+ ep = txt.length
- ##
- # Given an HTML tag, decorate it with class information and the like if
- # required. This is a no-op in the base class, but is overridden in HTML
- # output classes that implement style sheets.
+ while sp < ep
+ # scan back for a space
+ p = sp + line_len - 1
+ if p >= ep
+ p = ep
+ else
+ while p > sp and txt[p] != ?\s
+ p -= 1
+ end
+ if p <= sp
+ p = sp + line_len
+ while p < ep and txt[p] != ?\s
+ p += 1
+ end
+ end
+ end
+ res << txt[sp...p] << "\n"
+ sp = p
+ sp += 1 while sp < ep and txt[sp] == ?\s
+ end
- def annotate(tag)
- tag
+ res.join
end
##
- # Here's the client side of the visitor pattern
+ # :section: Visitor
def start_accepting
- @res = ""
+ @res = []
@in_list_entry = []
+ @list = []
end
def end_accepting
- @res
+ @res.join
end
- def accept_paragraph(am, fragment)
+ def accept_paragraph(paragraph)
@res << annotate("<p>") + "\n"
- @res << wrap(convert_flow(am.flow(fragment.txt)))
+ @res << wrap(convert_flow(@am.flow(paragraph.text)))
@res << annotate("</p>") + "\n"
end
- def accept_verbatim(am, fragment)
- @res << annotate("<pre>") + "\n"
- @res << CGI.escapeHTML(fragment.txt)
+ def accept_verbatim(verbatim)
+ @res << annotate("<pre>") << "\n"
+ @res << CGI.escapeHTML(verbatim.text)
@res << annotate("</pre>") << "\n"
end
- def accept_rule(am, fragment)
- size = fragment.param
+ def accept_rule(rule)
+ size = rule.weight
size = 10 if size > 10
- @res << "<hr size=\"#{size}\"></hr>"
+ @res << "<hr style=\"height: #{size}px\"></hr>"
end
- def accept_list_start(am, fragment)
- @res << html_list_name(fragment.type, true) << "\n"
+ def accept_list_start(list)
+ @list << list.type
+ @res << html_list_name(list.type, true) << "\n"
@in_list_entry.push false
end
- def accept_list_end(am, fragment)
+ def accept_list_end(list)
+ @list.pop
if tag = @in_list_entry.pop
@res << annotate(tag) << "\n"
end
- @res << html_list_name(fragment.type, false) << "\n"
+ @res << html_list_name(list.type, false) << "\n"
end
- def accept_list_item(am, fragment)
+ def accept_list_item_start(list_item)
if tag = @in_list_entry.last
@res << annotate(tag) << "\n"
end
- @res << list_item_start(am, fragment)
-
- @res << wrap(convert_flow(am.flow(fragment.txt))) << "\n"
+ @res << list_item_start(list_item, @list.last)
+ end
- @in_list_entry[-1] = list_end_for(fragment.type)
+ def accept_list_item_end(list_item)
+ @in_list_entry[-1] = list_end_for(@list.last)
end
- def accept_blank_line(am, fragment)
+ def accept_blank_line(blank_line)
# @res << annotate("<p />") << "\n"
end
- def accept_heading(am, fragment)
- @res << convert_heading(fragment.head_level, am.flow(fragment.txt))
- end
-
- ##
- # This is a higher speed (if messier) version of wrap
-
- def wrap(txt, line_len = 76)
- res = ""
- sp = 0
- ep = txt.length
- while sp < ep
- # scan back for a space
- p = sp + line_len - 1
- if p >= ep
- p = ep
- else
- while p > sp and txt[p] != ?\s
- p -= 1
- end
- if p <= sp
- p = sp + line_len
- while p < ep and txt[p] != ?\s
- p += 1
- end
- end
- end
- res << txt[sp...p] << "\n"
- sp = p
- sp += 1 while sp < ep and txt[sp] == ?\s
- end
- res
+ def accept_heading(heading)
+ @res << convert_heading(heading.level, @am.flow(heading.text))
end
private
- def on_tags(res, item)
- attr_mask = item.turn_on
- return if attr_mask.zero?
-
- @attr_tags.each do |tag|
- if attr_mask & tag.bit != 0
- res << annotate(tag.on)
- @in_tt += 1 if tt?(tag)
- end
- end
- end
-
- def off_tags(res, item)
- attr_mask = item.turn_off
- return if attr_mask.zero?
-
- @attr_tags.reverse_each do |tag|
- if attr_mask & tag.bit != 0
- @in_tt -= 1 if tt?(tag)
- res << annotate(tag.off)
- end
- end
- end
-
- def convert_flow(flow)
- res = ""
-
- flow.each do |item|
- case item
- when String
- res << convert_string(item)
- when RDoc::Markup::AttrChanger
- off_tags(res, item)
- on_tags(res, item)
- when RDoc::Markup::Special
- res << convert_special(item)
- else
- raise "Unknown flow element: #{item.inspect}"
- end
- end
-
- res
- end
+ ##
+ # Converts string +item+
def convert_string(item)
in_tt? ? convert_string_simple(item) : convert_string_fancy(item)
end
+ ##
+ # Escapes HTML in +item+
+
def convert_string_simple(item)
CGI.escapeHTML item
end
##
- # some of these patterns are taken from SmartyPants...
+ # Converts ampersand, dashes, elipsis, quotes, copyright and registered
+ # trademark symbols to HTML escaped Unicode.
def convert_string_fancy(item)
# convert ampersand before doing anything else
@@ -333,69 +277,62 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
gsub(/\(r\)/, '&#174;')
end
- def convert_special(special)
- handled = false
- RDoc::Markup::Attribute.each_name_of(special.type) do |name|
- method_name = "handle_special_#{name}"
- if self.respond_to? method_name
- special.text = send(method_name, special)
- handled = true
- end
- end
- raise "Unhandled special: #{special}" unless handled
- special.text
- end
+ ##
+ # Converts headings to hN elements
def convert_heading(level, flow)
- res =
- annotate("<h#{level}>") +
- convert_flow(flow) +
- annotate("</h#{level}>\n")
+ [annotate("<h#{level}>"),
+ convert_flow(flow),
+ annotate("</h#{level}>\n")].join
end
- def html_list_name(list_type, is_open_tag)
- tags = LIST_TYPE_TO_HTML[list_type] || raise("Invalid list type: #{list_type.inspect}")
- annotate(tags[ is_open_tag ? 0 : 1])
- end
+ ##
+ # Determins the HTML list element for +list_type+ and +open_tag+
- def list_item_start(am, fragment)
- case fragment.type
- when :BULLET, :NUMBER then
- annotate("<li>")
+ def html_list_name(list_type, open_tag)
+ tags = LIST_TYPE_TO_HTML[list_type]
+ raise RDoc::Error, "Invalid list type: #{list_type.inspect}" unless tags
+ annotate tags[open_tag ? 0 : 1]
+ end
- when :UPPERALPHA then
- annotate("<li type=\"A\">")
+ ##
+ # Starts a list item
- when :LOWERALPHA then
- annotate("<li type=\"a\">")
+ def list_item_start(list_item, list_type)
+ case list_type
+ when :BULLET, :LALPHA, :NUMBER, :UALPHA then
+ annotate("<li>")
- when :LABELED then
+ when :LABEL then
annotate("<dt>") +
- convert_flow(am.flow(fragment.param)) +
+ convert_flow(@am.flow(list_item.label)) +
annotate("</dt>") +
annotate("<dd>")
when :NOTE then
annotate("<tr>") +
annotate("<td valign=\"top\">") +
- convert_flow(am.flow(fragment.param)) +
+ convert_flow(@am.flow(list_item.label)) +
annotate("</td>") +
annotate("<td>")
else
- raise "Invalid list type"
+ raise RDoc::Error, "Invalid list type: #{list_type.inspect}"
end
end
- def list_end_for(fragment_type)
- case fragment_type
- when :BULLET, :NUMBER, :UPPERALPHA, :LOWERALPHA then
+ ##
+ # Ends a list item
+
+ def list_end_for(list_type)
+ case list_type
+ when :BULLET, :LALPHA, :NUMBER, :UALPHA then
"</li>"
- when :LABELED then
+ when :LABEL then
"</dd>"
when :NOTE then
"</td></tr>"
else
- raise "Invalid list type"
+ raise RDoc::Error, "Invalid list type: #{list_type.inspect}"
end
end
diff --git a/lib/rdoc/markup/to_html_crossref.rb b/lib/rdoc/markup/to_html_crossref.rb
index 930218bb7e..1f62ee04f9 100644
--- a/lib/rdoc/markup/to_html_crossref.rb
+++ b/lib/rdoc/markup/to_html_crossref.rb
@@ -1,44 +1,35 @@
require 'rdoc/markup/to_html'
##
-# Subclass of the RDoc::Markup::ToHtml class that supports looking up words in
-# the AllReferences list. Those that are found (like AllReferences in this
-# comment) will be hyperlinked
+# Subclass of the RDoc::Markup::ToHtml class that supports looking up words
+# from a context. Those that are found will be hyperlinked.
class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
- attr_accessor :context
-
- # Regular expressions to match class and method references.
- #
- # 1.) There can be a '\' in front of text to suppress
- # any cross-references (note, however, that the single '\'
- # is written as '\\\\' in order to escape it twice, once
- # in the Ruby String literal and once in the regexp).
- # 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,
- # which may or may not have things inside (this
- # apparently is allowed for Fortran 95, but I also think that this
- # is a good idea for Ruby, as it is very reasonable to want to
- # reference a call with arguments).
- #
- # NOTE: In order to support Fortran 95 properly, the [A-Z] below
- # should be changed to [A-Za-z]. This slows down rdoc significantly,
- # however, and the Fortran 95 support is broken in any case due to
- # the return in handle_special_CROSSREF if the token consists
- # entirely of lowercase letters.
+ ##
+ # Regular expression to match class references
#
- # The markup/cross-referencing engine needs a rewrite for
- # Fortran 95 to be supported properly.
+ # 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
+ # top-level namespace.
+ # 3) The method can be followed by parenthesis
+
CLASS_REGEXP_STR = '\\\\?((?:\:{2})?[A-Z]\w*(?:\:\:\w+)*)'
- METHOD_REGEXP_STR = '(\w+[!?=]?)(?:\([\.\w+\*\/\+\-\=\<\>]*\))?'
+ ##
+ # Regular expression to match method references.
+ #
+ # See CLASS_REGEXP_STR
+
+ METHOD_REGEXP_STR = '(\w+[!?=]?)(?:\([\w.+*/=<>-]*\))?'
+
+ ##
# Regular expressions matching text that should potentially have
- # cross-reference links generated are passed to add_special.
- # Note that these expressions are meant to pick up text for which
- # cross-references have been suppressed, since the suppression
- # characters are removed by the code that is triggered.
+ # cross-reference links generated are passed to add_special. Note that
+ # these expressions are meant to pick up text for which cross-references
+ # have been suppressed, since the suppression characters are removed by the
+ # code that is triggered.
+
CROSSREF_REGEXP = /(
# A::B::C.meth
#{CLASS_REGEXP_STR}[\.\#]#{METHOD_REGEXP_STR}
@@ -72,8 +63,14 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
)/x
##
- # We need to record the html path of our caller so we can generate
- # correct relative paths for any hyperlinks that we find
+ # RDoc::CodeObject for generating references
+
+ attr_accessor :context
+
+ ##
+ # 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.
def initialize(from_path, context, show_hash)
raise ArgumentError, 'from_path cannot be nil' if from_path.nil?
@@ -89,19 +86,18 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
end
##
- # We're invoked when any text matches the CROSSREF pattern
- # (defined in MarkUp). If we fine 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.
+ # We're invoked when any text matches the CROSSREF pattern (defined in
+ # MarkUp). 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.
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)
+ # 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/
return @seen[name] if @seen.include? name
@@ -113,7 +109,6 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
lookup = name
end
-
# Find class, module, or method in class or module.
#
# Do not, however, use an if/elsif/else chain to do so. Instead, test
@@ -132,7 +127,9 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
ref = @context.find_symbol lookup unless ref
- out = if lookup =~ /^\\/ then
+ out = if lookup == '\\' then
+ lookup
+ elsif lookup =~ /^\\/ then
$'
elsif ref and ref.document_self then
"<a href=\"#{ref.as_href(@from_path)}\">#{name}</a>"
@@ -146,3 +143,4 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
end
end
+
diff --git a/lib/rdoc/markup/to_latex.rb b/lib/rdoc/markup/to_latex.rb
deleted file mode 100644
index bbf958f2ed..0000000000
--- a/lib/rdoc/markup/to_latex.rb
+++ /dev/null
@@ -1,328 +0,0 @@
-require 'rdoc/markup/formatter'
-require 'rdoc/markup/fragments'
-require 'rdoc/markup/inline'
-
-require 'cgi'
-
-##
-# Convert SimpleMarkup to basic LaTeX report format.
-
-class RDoc::Markup::ToLaTeX < RDoc::Markup::Formatter
-
- BS = "\020" # \
- OB = "\021" # {
- CB = "\022" # }
- DL = "\023" # Dollar
-
- BACKSLASH = "#{BS}symbol#{OB}92#{CB}"
- HAT = "#{BS}symbol#{OB}94#{CB}"
- BACKQUOTE = "#{BS}symbol#{OB}0#{CB}"
- TILDE = "#{DL}#{BS}sim#{DL}"
- LESSTHAN = "#{DL}<#{DL}"
- GREATERTHAN = "#{DL}>#{DL}"
-
- def self.l(str)
- str.tr('\\', BS).tr('{', OB).tr('}', CB).tr('$', DL)
- end
-
- def l(arg)
- RDoc::Markup::ToLaTeX.l(arg)
- end
-
- LIST_TYPE_TO_LATEX = {
- :BULLET => [ l("\\begin{itemize}"), l("\\end{itemize}") ],
- :NUMBER => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\arabic" ],
- :UPPERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\Alph" ],
- :LOWERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\alph" ],
- :LABELED => [ l("\\begin{description}"), l("\\end{description}") ],
- :NOTE => [
- l("\\begin{tabularx}{\\linewidth}{@{} l X @{}}"),
- l("\\end{tabularx}") ],
- }
-
- InlineTag = Struct.new(:bit, :on, :off)
-
- def initialize
- init_tags
- @list_depth = 0
- @prev_list_types = []
- end
-
- ##
- # Set up the standard mapping of attributes to LaTeX
-
- def init_tags
- @attr_tags = [
- InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:BOLD), l("\\textbf{"), l("}")),
- InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:TT), l("\\texttt{"), l("}")),
- InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:EM), l("\\emph{"), l("}")),
- ]
- end
-
- ##
- # Escape a LaTeX string
-
- def escape(str)
- $stderr.print "FE: ", str if $DEBUG_RDOC
- s = str.
- sub(/\s+$/, '').
- gsub(/([_\${}&%#])/, "#{BS}\\1").
- gsub(/\\/, BACKSLASH).
- gsub(/\^/, HAT).
- gsub(/~/, TILDE).
- gsub(/</, LESSTHAN).
- gsub(/>/, GREATERTHAN).
- gsub(/,,/, ",{},").
- gsub(/\`/, BACKQUOTE)
- $stderr.print "-> ", s, "\n" if $DEBUG_RDOC
- s
- end
-
- ##
- # Add a new set of LaTeX tags for an attribute. We allow
- # separate start and end tags for flexibility
-
- def add_tag(name, start, stop)
- @attr_tags << InlineTag.new(RDoc::Markup::Attribute.bitmap_for(name), start, stop)
- end
-
- ##
- # Here's the client side of the visitor pattern
-
- def start_accepting
- @res = ""
- @in_list_entry = []
- end
-
- def end_accepting
- @res.tr(BS, '\\').tr(OB, '{').tr(CB, '}').tr(DL, '$')
- end
-
- def accept_paragraph(am, fragment)
- @res << wrap(convert_flow(am.flow(fragment.txt)))
- @res << "\n"
- end
-
- def accept_verbatim(am, fragment)
- @res << "\n\\begin{code}\n"
- @res << fragment.txt.sub(/[\n\s]+\Z/, '')
- @res << "\n\\end{code}\n\n"
- end
-
- def accept_rule(am, fragment)
- size = fragment.param
- size = 10 if size > 10
- @res << "\n\n\\rule{\\linewidth}{#{size}pt}\n\n"
- end
-
- def accept_list_start(am, fragment)
- @res << list_name(fragment.type, true) << "\n"
- @in_list_entry.push false
- end
-
- def accept_list_end(am, fragment)
- if tag = @in_list_entry.pop
- @res << tag << "\n"
- end
- @res << list_name(fragment.type, false) << "\n"
- end
-
- def accept_list_item(am, fragment)
- if tag = @in_list_entry.last
- @res << tag << "\n"
- end
- @res << list_item_start(am, fragment)
- @res << wrap(convert_flow(am.flow(fragment.txt))) << "\n"
- @in_list_entry[-1] = list_end_for(fragment.type)
- end
-
- def accept_blank_line(am, fragment)
- # @res << "\n"
- end
-
- def accept_heading(am, fragment)
- @res << convert_heading(fragment.head_level, am.flow(fragment.txt))
- end
-
- ##
- # This is a higher speed (if messier) version of wrap
-
- def wrap(txt, line_len = 76)
- res = ""
- sp = 0
- ep = txt.length
- while sp < ep
- # scan back for a space
- p = sp + line_len - 1
- if p >= ep
- p = ep
- else
- while p > sp and txt[p] != ?\s
- p -= 1
- end
- if p <= sp
- p = sp + line_len
- while p < ep and txt[p] != ?\s
- p += 1
- end
- end
- end
- res << txt[sp...p] << "\n"
- sp = p
- sp += 1 while sp < ep and txt[sp] == ?\s
- end
- res
- end
-
- private
-
- def on_tags(res, item)
- attr_mask = item.turn_on
- return if attr_mask.zero?
-
- @attr_tags.each do |tag|
- if attr_mask & tag.bit != 0
- res << tag.on
- end
- end
- end
-
- def off_tags(res, item)
- attr_mask = item.turn_off
- return if attr_mask.zero?
-
- @attr_tags.reverse_each do |tag|
- if attr_mask & tag.bit != 0
- res << tag.off
- end
- end
- end
-
- def convert_flow(flow)
- res = ""
- flow.each do |item|
- case item
- when String
- $stderr.puts "Converting '#{item}'" if $DEBUG_RDOC
- res << convert_string(item)
- when AttrChanger
- off_tags(res, item)
- on_tags(res, item)
- when Special
- res << convert_special(item)
- else
- raise "Unknown flow element: #{item.inspect}"
- end
- end
- res
- end
-
- ##
- # some of these patterns are taken from SmartyPants...
-
- def convert_string(item)
- escape(item).
-
- # convert ... to elipsis (and make sure .... becomes .<elipsis>)
- gsub(/\.\.\.\./, '.\ldots{}').gsub(/\.\.\./, '\ldots{}').
-
- # convert single closing quote
- gsub(%r{([^ \t\r\n\[\{\(])\'}, '\1\'').
- gsub(%r{\'(?=\W|s\b)}, "'" ).
-
- # convert single opening quote
- gsub(/'/, '`').
-
- # convert double closing quote
- gsub(%r{([^ \t\r\n\[\{\(])\"(?=\W)}, "\\1''").
-
- # convert double opening quote
- gsub(/"/, "``").
-
- # convert copyright
- gsub(/\(c\)/, '\copyright{}')
-
- end
-
- def convert_special(special)
- handled = false
- Attribute.each_name_of(special.type) do |name|
- method_name = "handle_special_#{name}"
- if self.respond_to? method_name
- special.text = send(method_name, special)
- handled = true
- end
- end
- raise "Unhandled special: #{special}" unless handled
- special.text
- end
-
- def convert_heading(level, flow)
- res =
- case level
- when 1 then "\\chapter{"
- when 2 then "\\section{"
- when 3 then "\\subsection{"
- when 4 then "\\subsubsection{"
- else "\\paragraph{"
- end +
- convert_flow(flow) +
- "}\n"
- end
-
- def list_name(list_type, is_open_tag)
- tags = LIST_TYPE_TO_LATEX[list_type] || raise("Invalid list type: #{list_type.inspect}")
- if tags[2] # enumerate
- if is_open_tag
- @list_depth += 1
- if @prev_list_types[@list_depth] != tags[2]
- case @list_depth
- when 1
- roman = "i"
- when 2
- roman = "ii"
- when 3
- roman = "iii"
- when 4
- roman = "iv"
- else
- raise("Too deep list: level #{@list_depth}")
- end
- @prev_list_types[@list_depth] = tags[2]
- return l("\\renewcommand{\\labelenum#{roman}}{#{tags[2]}{enum#{roman}}}") + "\n" + tags[0]
- end
- else
- @list_depth -= 1
- end
- end
- tags[ is_open_tag ? 0 : 1]
- end
-
- def list_item_start(am, fragment)
- case fragment.type
- when :BULLET, :NUMBER, :UPPERALPHA, :LOWERALPHA then
- "\\item "
-
- when :LABELED then
- "\\item[" + convert_flow(am.flow(fragment.param)) + "] "
-
- when :NOTE then
- convert_flow(am.flow(fragment.param)) + " & "
- else
- raise "Invalid list type"
- end
- end
-
- def list_end_for(fragment_type)
- case fragment_type
- when :BULLET, :NUMBER, :UPPERALPHA, :LOWERALPHA, :LABELED then
- ""
- when :NOTE
- "\\\\\n"
- else
- raise "Invalid list type"
- end
- end
-
-end
-
diff --git a/lib/rdoc/markup/to_rdoc.rb b/lib/rdoc/markup/to_rdoc.rb
new file mode 100644
index 0000000000..97c7d0c984
--- /dev/null
+++ b/lib/rdoc/markup/to_rdoc.rb
@@ -0,0 +1,243 @@
+require 'rdoc/markup/inline'
+
+##
+# Outputs RDoc markup as RDoc markup! (mostly)
+
+class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter
+
+ attr_accessor :indent
+ attr_reader :list_index
+ attr_reader :list_type
+ attr_reader :list_width
+ attr_reader :prefix
+ attr_reader :res
+
+ def initialize
+ super
+
+ @markup.add_special(/\\[^\s]/, :SUPPRESSED_CROSSREF)
+
+ @width = 78
+ @prefix = ''
+
+ init_tags
+
+ @headings = {}
+ @headings.default = []
+
+ @headings[1] = ['= ', '']
+ @headings[2] = ['== ', '']
+ @headings[3] = ['=== ', '']
+ @headings[4] = ['==== ', '']
+ @headings[5] = ['===== ', '']
+ @headings[6] = ['====== ', '']
+ end
+
+ ##
+ # Maps attributes to ANSI sequences
+
+ def init_tags
+ add_tag :BOLD, "<b>", "</b>"
+ add_tag :TT, "<tt>", "</tt>"
+ add_tag :EM, "<em>", "</em>"
+ end
+
+ def accept_blank_line blank_line
+ @res << "\n"
+ end
+
+ def accept_heading heading
+ use_prefix or @res << ' ' * @indent
+ @res << @headings[heading.level][0]
+ @res << attributes(heading.text)
+ @res << @headings[heading.level][1]
+ @res << "\n"
+ end
+
+ def accept_list_end list
+ @list_index.pop
+ @list_type.pop
+ @list_width.pop
+ end
+
+ def accept_list_item_end list_item
+ width = case @list_type.last
+ when :BULLET then
+ 2
+ when :NOTE, :LABEL then
+ @res << "\n"
+ 2
+ else
+ bullet = @list_index.last.to_s
+ @list_index[-1] = @list_index.last.succ
+ bullet.length + 2
+ end
+
+ @indent -= width
+ end
+
+ 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
+
+ case @list_type.last
+ when :NOTE, :LABEL then
+ @indent += 2
+ @prefix = bullet + (' ' * @indent)
+ else
+ @prefix = (' ' * @indent) + bullet.ljust(bullet.length + 1)
+
+ width = bullet.length + 1
+
+ @indent += width
+ end
+ end
+
+ def accept_list_start list
+ case list.type
+ when :BULLET then
+ @list_index << nil
+ @list_width << 1
+ when :LABEL, :NOTE then
+ @list_index << nil
+ @list_width << 2
+ when :LALPHA then
+ @list_index << 'a'
+ @list_width << list.items.length.to_s.length
+ when :NUMBER then
+ @list_index << 1
+ @list_width << list.items.length.to_s.length
+ when :UALPHA then
+ @list_index << 'A'
+ @list_width << list.items.length.to_s.length
+ else
+ raise RDoc::Error, "invalid list type #{list.type}"
+ end
+
+ @list_type << list.type
+ end
+
+ def accept_paragraph paragraph
+ wrap attributes(paragraph.text)
+ end
+
+ def accept_rule rule
+ use_prefix or @res << ' ' * @indent
+ @res << '-' * (@width - @indent)
+ @res << "\n"
+ end
+
+ ##
+ # Outputs +verbatim+ flush left and 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
+ 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"
+ end
+
+ def attributes text
+ flow = @am.flow text.dup
+ convert_flow flow
+ end
+
+ def end_accepting
+ @res.join
+ end
+
+ def handle_special_SUPPRESSED_CROSSREF special
+ special.text.sub(/\\/, '')
+ end
+
+ def start_accepting
+ @res = [""]
+ @indent = 0
+ @prefix = nil
+
+ @list_index = []
+ @list_type = []
+ @list_width = []
+ end
+
+ def use_prefix
+ prefix = @prefix
+ @prefix = nil
+ @res << prefix if prefix
+
+ prefix
+ end
+
+ def wrap text
+ return unless text && !text.empty?
+
+ text_len = @width - @indent
+
+ text_len = 20 if text_len < 20
+
+ re = /^(.{0,#{text_len}})[ \n]/
+ next_prefix = ' ' * @indent
+
+ prefix = @prefix || next_prefix
+ @prefix = nil
+
+ @res << prefix
+
+ while text.length > text_len
+ if text =~ re then
+ @res << $1
+ text.slice!(0, $&.length)
+ else
+ @res << text.slice!(0, text_len)
+ end
+
+ @res << "\n" << next_prefix
+ end
+
+ if text.empty? then
+ @res.pop
+ @res.pop
+ else
+ @res << text
+ @res << "\n"
+ end
+ end
+
+end
+
diff --git a/lib/rdoc/markup/to_test.rb b/lib/rdoc/markup/to_test.rb
index ce6aff6e9a..0afdb96a18 100644
--- a/lib/rdoc/markup/to_test.rb
+++ b/lib/rdoc/markup/to_test.rb
@@ -6,44 +6,58 @@ require 'rdoc/markup/formatter'
class RDoc::Markup::ToTest < RDoc::Markup::Formatter
+ ##
+ # :section: Visitor
+
def start_accepting
@res = []
+ @list = []
end
def end_accepting
@res
end
- def accept_paragraph(am, fragment)
- @res << fragment.to_s
+ def accept_paragraph(paragraph)
+ @res << paragraph.text
+ end
+
+ def accept_verbatim(verbatim)
+ @res << verbatim.text
end
- def accept_verbatim(am, fragment)
- @res << fragment.to_s
+ def accept_list_start(list)
+ @list << case list.type
+ when :BULLET then
+ '*'
+ when :NUMBER then
+ '1'
+ else
+ list.type
+ end
end
- def accept_list_start(am, fragment)
- @res << fragment.to_s
+ def accept_list_end(list)
+ @list.pop
end
- def accept_list_end(am, fragment)
- @res << fragment.to_s
+ def accept_list_item_start(list_item)
+ @res << "#{' ' * (@list.size - 1)}#{@list.last}: "
end
- def accept_list_item(am, fragment)
- @res << fragment.to_s
+ def accept_list_item_end(list_item)
end
- def accept_blank_line(am, fragment)
- @res << fragment.to_s
+ def accept_blank_line(blank_line)
+ @res << "\n"
end
- def accept_heading(am, fragment)
- @res << fragment.to_s
+ def accept_heading(heading)
+ @res << "#{'=' * heading.level} #{heading.text}"
end
- def accept_rule(am, fragment)
- @res << fragment.to_s
+ def accept_rule(rule)
+ @res << '-' * rule.weight
end
end
diff --git a/lib/rdoc/markup/to_texinfo.rb b/lib/rdoc/markup/to_texinfo.rb
deleted file mode 100644
index 65a1608c4d..0000000000
--- a/lib/rdoc/markup/to_texinfo.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-require 'rdoc/markup/formatter'
-require 'rdoc/markup/fragments'
-require 'rdoc/markup/inline'
-
-require 'rdoc/markup'
-require 'rdoc/markup/formatter'
-
-##
-# Convert SimpleMarkup to basic TexInfo format
-#
-# TODO: WTF is AttributeManager for?
-#
-class RDoc::Markup::ToTexInfo < RDoc::Markup::Formatter
-
- def start_accepting
- @text = []
- end
-
- def end_accepting
- @text.join("\n")
- end
-
- def accept_paragraph(attributes, text)
- @text << format(text)
- end
-
- def accept_verbatim(attributes, text)
- @text << "@verb{|#{format(text)}|}"
- end
-
- def accept_heading(attributes, text)
- heading = ['@majorheading', '@chapheading'][text.head_level - 1] || '@heading'
- @text << "#{heading} #{format(text)}"
- end
-
- def accept_list_start(attributes, text)
- @text << '@itemize @bullet'
- end
-
- def accept_list_end(attributes, text)
- @text << '@end itemize'
- end
-
- def accept_list_item(attributes, text)
- @text << "@item\n#{format(text)}"
- end
-
- def accept_blank_line(attributes, text)
- @text << "\n"
- end
-
- def accept_rule(attributes, text)
- @text << '-----'
- end
-
- def format(text)
- text.txt.
- gsub(/@/, "@@").
- gsub(/\{/, "@{").
- gsub(/\}/, "@}").
- # gsub(/,/, "@,"). # technically only required in cross-refs
- gsub(/\+([\w]+)\+/, "@code{\\1}").
- gsub(/\<tt\>([^<]+)\<\/tt\>/, "@code{\\1}").
- gsub(/\*([\w]+)\*/, "@strong{\\1}").
- gsub(/\<b\>([^<]+)\<\/b\>/, "@strong{\\1}").
- gsub(/_([\w]+)_/, "@emph{\\1}").
- gsub(/\<em\>([^<]+)\<\/em\>/, "@emph{\\1}")
- end
-end
diff --git a/lib/rdoc/markup/verbatim.rb b/lib/rdoc/markup/verbatim.rb
new file mode 100644
index 0000000000..faf539a723
--- /dev/null
+++ b/lib/rdoc/markup/verbatim.rb
@@ -0,0 +1,42 @@
+##
+# A section of verbatim text
+
+class RDoc::Markup::Verbatim < RDoc::Markup::Paragraph
+
+ def accept visitor
+ visitor.accept_verbatim self
+ end
+
+ ##
+ # Collapses 3+ newlines into two newlines
+
+ def normalize
+ parts = []
+
+ newlines = 0
+
+ @parts.each do |part|
+ case part
+ when /\n/ then
+ newlines += 1
+ parts << part if newlines <= 2
+ else
+ newlines = 0
+ parts << part
+ end
+ end
+
+ parts.slice!(-1) if parts[-2..-1] == ["\n", "\n"]
+
+ @parts = parts
+ end
+
+ ##
+ # The text of the section
+
+ def text
+ @parts.join
+ end
+
+end
+
diff --git a/lib/rdoc/meta_method.rb b/lib/rdoc/meta_method.rb
new file mode 100644
index 0000000000..e0c065c2ba
--- /dev/null
+++ b/lib/rdoc/meta_method.rb
@@ -0,0 +1,8 @@
+require 'rdoc/any_method'
+
+##
+# MetaMethod represents a meta-programmed method
+
+class RDoc::MetaMethod < RDoc::AnyMethod
+end
+
diff --git a/lib/rdoc/normal_class.rb b/lib/rdoc/normal_class.rb
new file mode 100644
index 0000000000..e7ca6fffde
--- /dev/null
+++ b/lib/rdoc/normal_class.rb
@@ -0,0 +1,55 @@
+require 'rdoc/class_module'
+
+##
+# A normal class, neither singleton nor anonymous
+
+class RDoc::NormalClass < RDoc::ClassModule
+
+ ##
+ # Ancestor ClassModules
+
+ def ancestors
+ includes + [superclass]
+ end
+
+ def inspect # :nodoc:
+ superclass = @superclass ? " < #{@superclass}" : nil
+ "<%s:0x%x class %s%s includes: %p attributes: %p methods: %p aliases: %p>" % [
+ self.class, object_id,
+ full_name, superclass, @includes, @attributes, @method_list, @aliases
+ ]
+ end
+
+ def pretty_print q # :nodoc:
+ superclass = @superclass ? " < #{@superclass}" : nil
+
+ q.group 2, "[class #{full_name}#{superclass} ", "]" do
+ q.breakable
+ q.text "includes:"
+ q.breakable
+ q.seplist @includes do |inc| q.pp inc end
+
+ q.breakable
+ q.text "attributes:"
+ q.breakable
+ q.seplist @attributes do |inc| q.pp inc end
+
+ q.breakable
+ q.text "methods:"
+ q.breakable
+ q.seplist @method_list do |inc| q.pp inc end
+
+ q.breakable
+ q.text "aliases:"
+ q.breakable
+ q.seplist @aliases do |inc| q.pp inc end
+
+ q.breakable
+ q.text "comment:"
+ q.breakable
+ q.pp comment
+ end
+ end
+
+end
+
diff --git a/lib/rdoc/normal_module.rb b/lib/rdoc/normal_module.rb
new file mode 100644
index 0000000000..92abe6b440
--- /dev/null
+++ b/lib/rdoc/normal_module.rb
@@ -0,0 +1,64 @@
+require 'rdoc/class_module'
+
+##
+# A normal module, like NormalClass
+
+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,
+ full_name, @includes, @attributes, @method_list, @aliases
+ ]
+ end
+
+ ##
+ # This is a module, returns true
+
+ def module?
+ true
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 2, "[module #{full_name}: ", "]" do
+ q.breakable
+ q.text "includes:"
+ q.breakable
+ q.seplist @includes do |inc| q.pp inc end
+ q.breakable
+
+ q.text "attributes:"
+ q.breakable
+ q.seplist @attributes do |inc| q.pp inc end
+ q.breakable
+
+ q.text "methods:"
+ q.breakable
+ q.seplist @method_list do |inc| q.pp inc end
+ q.breakable
+
+ q.text "aliases:"
+ q.breakable
+ q.seplist @aliases do |inc| q.pp inc end
+ q.breakable
+
+ q.text "comment:"
+ q.breakable
+ q.pp comment
+ end
+ end
+
+ ##
+ # Modules don't have one, raises NoMethodError
+
+ def superclass
+ raise NoMethodError, "#{full_name} is a module"
+ end
+
+end
+
diff --git a/lib/rdoc/options.rb b/lib/rdoc/options.rb
index 2b069f4265..e4616f2aed 100644
--- a/lib/rdoc/options.rb
+++ b/lib/rdoc/options.rb
@@ -1,15 +1,11 @@
-# We handle the parsing of options, and subsequently as a singleton
-# object to be queried for option values
-
-require "rdoc/ri/paths"
require 'optparse'
-class RDoc::Options
+require 'rdoc/ri/paths'
- ##
- # Should the output be placed into a single file
+##
+# RDoc::Options handles the parsing and storage of options
- attr_reader :all_one_file
+class RDoc::Options
##
# Character-set
@@ -17,36 +13,11 @@ class RDoc::Options
attr_reader :charset
##
- # URL of stylesheet
-
- attr_reader :css
-
- ##
- # Should diagrams be drawn
-
- attr_reader :diagram
-
- ##
# Files matching this pattern will be excluded
attr_accessor :exclude
##
- # Additional attr_... style method flags
-
- attr_reader :extra_accessor_flags
-
- ##
- # Pattern for additional attr_... style methods
-
- attr_accessor :extra_accessors
-
- ##
- # Should we draw fileboxes in diagrams
-
- attr_reader :fileboxes
-
- ##
# The list of files to be processed
attr_accessor :files
@@ -67,45 +38,20 @@ class RDoc::Options
attr_accessor :formatter
##
- # image format for diagrams
-
- attr_reader :image_format
-
- ##
- # Include line numbers in the source listings
-
- attr_reader :include_line_numbers
-
- ##
- # Should source code be included inline, or displayed in a popup
-
- attr_accessor :inline_source
-
- ##
# Name of the file, class or module to display in the initial index page (if
# not specified the first file we encounter is used)
attr_accessor :main_page
##
- # Merge into classes of the same name when generating ri
-
- attr_reader :merge
-
- ##
# The name of the output directory
attr_accessor :op_dir
##
- # The name to use for the output
-
- attr_accessor :op_name
-
- ##
- # Are we promiscuous about showing module contents across multiple files
+ # Is RDoc in pipe mode?
- attr_reader :promiscuous
+ attr_accessor :pipe
##
# Array of directories to search for files to satisfy an :include:
@@ -113,7 +59,7 @@ class RDoc::Options
attr_reader :rdoc_include
##
- # Include private and protected methods in the output
+ # Include private and protected methods in the output?
attr_accessor :show_all
@@ -128,18 +74,11 @@ class RDoc::Options
attr_reader :tab_width
##
- # template to be used when generating output
+ # Template to be used when generating output
attr_reader :template
##
- # Template class for file generation
- #--
- # HACK around dependencies in lib/rdoc/generator/html.rb
-
- attr_accessor :template_class # :nodoc:
-
- ##
# Documentation title
attr_reader :title
@@ -154,34 +93,24 @@ class RDoc::Options
attr_reader :webcvs
- def initialize(generators = {}) # :nodoc:
- @op_dir = "doc"
- @op_name = nil
+ def initialize # :nodoc:
+ require 'rdoc/rdoc'
+ @op_dir = nil
@show_all = false
@main_page = nil
- @merge = false
@exclude = []
- @generators = generators
- @generator_name = 'html'
- @generator = @generators[@generator_name]
+ @generators = RDoc::RDoc::GENERATORS
+ @generator = RDoc::Generator::Darkfish
+ @generator_name = nil
@rdoc_include = []
@title = nil
@template = nil
- @template_class = nil
- @diagram = false
- @fileboxes = false
@show_hash = false
- @image_format = 'png'
- @inline_source = false
- @all_one_file = false
@tab_width = 8
- @include_line_numbers = false
- @extra_accessor_flags = {}
- @promiscuous = false
- @force_update = false
+ @force_update = true
@verbosity = 1
+ @pipe = false
- @css = nil
@webcvs = nil
@charset = 'utf-8'
@@ -191,7 +120,7 @@ class RDoc::Options
# Parse command line options.
def parse(argv)
- accessors = []
+ ignore_invalid = true
opts = OptionParser.new do |opt|
opt.program_name = File.basename $0
@@ -210,44 +139,26 @@ Usage: #{opt.program_name} [options] [names...]
How RDoc generates output depends on the output formatter being used, and on
the options you give.
- - HTML output is normally produced into a number of separate files
- (one per class, module, and file, along with various indices).
- These files will appear in the directory given by the --op
- option (doc/ by default).
+ - Darkfish creates frameless HTML output by Michael Granger.
+ - ri creates ri data files
- - XML output by default is written to standard output. If a
- --opname option is given, the output will instead be written
- to a file with that name in the output directory.
+ RDoc understands the following file formats:
- - .chm files (Windows help files) are written in the --op directory.
- If an --opname parameter is present, that name is used, otherwise
- the file will be called rdoc.chm.
EOF
- opt.separator nil
- opt.separator "Options:"
- opt.separator nil
+ parsers = Hash.new { |h,parser| h[parser] = [] }
- opt.on("--accessor=ACCESSORS", "-A", Array,
- "A comma separated list of additional class",
- "methods that should be treated like",
- "'attr_reader' and friends.",
- " ",
- "Option may be repeated.",
- " ",
- "Each accessorname may have '=text'",
- "appended, in which case that text appears",
- "where the r/w/rw appears for normal.",
- "accessors") do |value|
- value.each do |accessor|
- if accessor =~ /^(\w+)(=(.*))?$/
- accessors << $1
- @extra_accessor_flags[$1] = $3
- end
- end
+ RDoc::Parser.parsers.each do |regexp, parser|
+ parsers[parser.name.sub('RDoc::Parser::', '')] << regexp.source
+ end
+
+ parsers.sort.each do |parser, regexp|
+ opt.banner << " - #{parser}: #{regexp.join ', '}\n"
end
opt.separator nil
+ opt.separator "Parsing Options:"
+ opt.separator nil
opt.on("--all", "-a",
"Include all methods (not just public) in",
@@ -257,31 +168,6 @@ Usage: #{opt.program_name} [options] [names...]
opt.separator nil
- opt.on("--charset=CHARSET", "-c",
- "Specifies the output HTML character-set.") do |value|
- @charset = value
- end
-
- opt.separator nil
-
- opt.on("--debug", "-D",
- "Displays lots on internal stuff.") do |value|
- $DEBUG_RDOC = value
- end
-
- opt.separator nil
-
- opt.on("--diagram", "-d",
- "Generate diagrams showing modules and",
- "classes. You need dot V1.8.6 or later to",
- "use the --diagram option correctly. Dot is",
- "available from http://graphviz.org") do |value|
- check_diagram
- @diagram = true
- end
-
- opt.separator nil
-
opt.on("--exclude=PATTERN", "-x", Regexp,
"Do not process files or directories",
"matching PATTERN.") do |value|
@@ -300,25 +186,13 @@ Usage: #{opt.program_name} [options] [names...]
raise OptionParser::InvalidArgument, "Invalid parameter to '-E'"
end
- unless RDoc::ParserFactory.alias_extension old, new then
+ unless RDoc::Parser.alias_extension old, new then
raise OptionParser::InvalidArgument, "Unknown extension .#{old} to -E"
end
end
opt.separator nil
- opt.on("--fileboxes", "-F",
- "Classes are put in boxes which represents",
- "files, where these classes reside. Classes",
- "shared between more than one file are",
- "shown with list of files that are sharing",
- "them. Silently discarded if --diagram is",
- "not given.") do |value|
- @fileboxes = value
- end
-
- opt.separator nil
-
opt.on("--force-update", "-U",
"Forces rdoc to scan all sources even if",
"newer than the flag file.") do |value|
@@ -327,45 +201,37 @@ Usage: #{opt.program_name} [options] [names...]
opt.separator nil
- opt.on("--fmt=FORMAT", "--format=FORMAT", "-f", @generators.keys,
- "Set the output formatter.") do |value|
- @generator_name = value.downcase
- setup_generator
+ opt.on("--pipe",
+ "Convert RDoc on stdin to HTML") do
+ @pipe = true
end
opt.separator nil
-
- image_formats = %w[gif png jpg jpeg]
- opt.on("--image-format=FORMAT", "-I", image_formats,
- "Sets output image format for diagrams. Can",
- "be #{image_formats.join ', '}. If this option",
- "is omitted, png is used. Requires",
- "diagrams.") do |value|
- @image_format = value
- end
-
+ opt.separator "Generator Options:"
opt.separator nil
- opt.on("--include=DIRECTORIES", "-i", Array,
- "set (or add to) the list of directories to",
- "be searched when satisfying :include:",
- "requests. Can be used more than once.") do |value|
- @rdoc_include.concat value.map { |dir| dir.strip }
+ opt.on("--charset=CHARSET", "-c",
+ "Specifies the output HTML character-set.") do |value|
+ @charset = value
end
opt.separator nil
- opt.on("--inline-source", "-S",
- "Show method source code inline, rather than",
- "via a popup link.") do |value|
- @inline_source = value
+ generator_text = @generators.keys.map { |name| " #{name}" }.sort
+
+ opt.on("--fmt=FORMAT", "--format=FORMAT", "-f", @generators.keys,
+ "Set the output formatter. One of:", *generator_text) do |value|
+ @generator_name = value.downcase
+ setup_generator
end
opt.separator nil
- opt.on("--line-numbers", "-N",
- "Include line numbers in the source code.") do |value|
- @include_line_numbers = value
+ opt.on("--include=DIRECTORIES", "-i", Array,
+ "Set (or add to) the list of directories to",
+ "be searched when satisfying :include:",
+ "requests. Can be used more than once.") do |value|
+ @rdoc_include.concat value.map { |dir| dir.strip }
end
opt.separator nil
@@ -377,62 +243,61 @@ Usage: #{opt.program_name} [options] [names...]
opt.separator nil
- opt.on("--merge", "-M",
- "When creating ri output, merge previously",
- "processed classes into previously",
- "documented classes of the same name.") do |value|
- @merge = value
+ opt.on("--output=DIR", "--op", "-o",
+ "Set the output directory.") do |value|
+ @op_dir = value
end
opt.separator nil
- opt.on("--one-file", "-1",
- "Put all the output into a single file.") do |value|
- @all_one_file = value
- @inline_source = value if value
- @template = 'one_page_html'
+ opt.on("--show-hash", "-H",
+ "A name of the form #name in a comment is a",
+ "possible hyperlink to an instance method",
+ "name. When displayed, the '#' is removed",
+ "unless this option is specified.") do |value|
+ @show_hash = value
end
opt.separator nil
- opt.on("--op=DIR", "-o",
- "Set the output directory.") do |value|
- @op_dir = value
+ 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("--opname=NAME", "-n",
- "Set the NAME of the output. Has no effect",
- "for HTML.") do |value|
- @op_name = value
+ opt.on("--template=NAME", "-T",
+ "Set the template used when generating",
+ "output.") do |value|
+ @template = value
end
opt.separator nil
- opt.on("--promiscuous", "-p",
- "When documenting a file that contains a",
- "module or class also defined in other",
- "files, show all stuff for that module or",
- "class in each files page. By default, only",
- "show stuff defined in that particular file.") do |value|
- @promiscuous = value
+ opt.on("--title=TITLE", "-t",
+ "Set TITLE as the title for HTML output.") do |value|
+ @title = value
end
opt.separator nil
- opt.on("--quiet", "-q",
- "Don't show progress as we parse.") do |value|
- @verbosity = 0
+ opt.on("--webcvs=URL", "-W",
+ "Specify a URL for linking to a web frontend",
+ "to CVS. If the URL contains a '\%s', the",
+ "name of the current file will be",
+ "substituted; if the URL doesn't contain a",
+ "'\%s', the filename will be appended to it.") do |value|
+ @webcvs = value
end
- opt.on("--verbose", "-v",
- "Display extra progress as we parse.") do |value|
- @verbosity = 2
- end
+ opt.separator nil
+ opt.on("-d", "--diagram", "Prevents -d from tripping --debug")
opt.separator nil
+ opt.separator "ri Generator Options:"
+ opt.separator nil
opt.on("--ri", "-r",
"Generate output for use by `ri`. The files",
@@ -441,7 +306,7 @@ Usage: #{opt.program_name} [options] [names...]
"subsequent --op parameter, so no special",
"privileges are needed.") do |value|
@generator_name = "ri"
- @op_dir = RDoc::RI::Paths::HOMEDIR
+ @op_dir ||= RDoc::RI::Paths::HOMEDIR
setup_generator
end
@@ -458,74 +323,49 @@ Usage: #{opt.program_name} [options] [names...]
end
opt.separator nil
-
- opt.on("--ri-system", "-Y",
- "Generate output for use by `ri`. The files",
- "are stored in a site-wide directory,",
- "making them accessible to others, so",
- "special privileges are needed. This",
- "option is intended to be used during Ruby",
- "installation.") do |value|
- @generator_name = "ri"
- @op_dir = RDoc::RI::Paths::SYSDIR
- setup_generator
- end
-
- opt.separator nil
-
- opt.on("--show-hash", "-H",
- "A name of the form #name in a comment is a",
- "possible hyperlink to an instance method",
- "name. When displayed, the '#' is removed",
- "unless this option is specified.") do |value|
- @show_hash = value
- end
-
+ opt.separator "Generic Options:"
opt.separator nil
- opt.on("--style=URL", "-s",
- "Specifies the URL of a separate stylesheet.") do |value|
- @css = value
+ opt.on("-D", "--[no-]debug",
+ "Displays lots on internal stuff.") do |value|
+ $DEBUG_RDOC = value
end
- opt.separator nil
-
- opt.on("--tab-width=WIDTH", "-w", OptionParser::DecimalInteger,
- "Set the width of tab characters.") do |value|
- @tab_width = value
+ opt.on("--[no-]ignore-invalid",
+ "Ignore invalid options and continue.") do |value|
+ ignore_invalid = value
end
- opt.separator nil
-
- opt.on("--template=NAME", "-T",
- "Set the template used when generating",
- "output.") do |value|
- @template = value
+ opt.on("--quiet", "-q",
+ "Don't show progress as we parse.") do |value|
+ @verbosity = 0
end
- opt.separator nil
-
- opt.on("--title=TITLE", "-t",
- "Set TITLE as the title for HTML output.") do |value|
- @title = value
+ opt.on("--verbose", "-v",
+ "Display extra progress as we parse.") do |value|
+ @verbosity = 2
end
opt.separator nil
-
- opt.on("--webcvs=URL", "-W",
- "Specify a URL for linking to a web frontend",
- "to CVS. If the URL contains a '\%s', the",
- "name of the current file will be",
- "substituted; if the URL doesn't contain a",
- "'\%s', the filename will be appended to it.") do |value|
- @webcvs = value
- end
end
argv.insert(0, *ENV['RDOCOPT'].split) if ENV['RDOCOPT']
- opts.parse! argv
+ begin
+ opts.parse! argv
+ rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e
+ if ignore_invalid and not quiet then
+ $stderr.puts e
+ $stderr.puts '(invalid options are ignored)'
+ else
+ $stderr.puts opts
+ $stderr.puts
+ $stderr.puts e
+ exit 1
+ end
+ end
+ @op_dir ||= 'doc'
@files = argv.dup
@rdoc_include << "." if @rdoc_include.empty?
@@ -542,18 +382,6 @@ Usage: #{opt.program_name} [options] [names...]
# formatter
@template ||= @generator_name
-
- # Generate a regexp from the accessors
- unless accessors.empty? then
- re = '^(' + accessors.map { |a| Regexp.quote a }.join('|') + ')$'
- @extra_accessors = Regexp.new re
- end
-
- rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e
- puts opts
- puts
- puts e
- exit 1
end
##
@@ -586,42 +414,6 @@ Usage: #{opt.program_name} [options] [names...]
unless @generator then
raise OptionParser::InvalidArgument, "Invalid output formatter"
end
-
- if @generator_name == "xml" then
- @all_one_file = true
- @inline_source = true
- end
- end
-
- # Check that the right version of 'dot' is available. Unfortunately this
- # doesn't work correctly under Windows NT, so we'll bypass the test under
- # Windows.
-
- def check_diagram
- return if RUBY_PLATFORM =~ /mswin|cygwin|mingw|bccwin/
-
- ok = false
- ver = nil
-
- IO.popen "dot -V 2>&1" do |io|
- ver = io.read
- if ver =~ /dot.+version(?:\s+gviz)?\s+(\d+)\.(\d+)/ then
- ok = ($1.to_i > 1) || ($1.to_i == 1 && $2.to_i >= 8)
- end
- end
-
- unless ok then
- if ver =~ /^dot.+version/ then
- $stderr.puts "Warning: You may need dot V1.8.6 or later to use\n",
- "the --diagram option correctly. You have:\n\n ",
- ver,
- "\nDiagrams might have strange background colors.\n\n"
- else
- $stderr.puts "You need the 'dot' program to produce diagrams.",
- "(see http://www.research.att.com/sw/tools/graphviz/)\n\n"
- exit
- end
- end
end
##
@@ -629,7 +421,7 @@ Usage: #{opt.program_name} [options] [names...]
def check_files
@files.each do |f|
- stat = File.stat f
+ stat = File.stat f rescue next
raise RDoc::Error, "file '#{f}' not readable" unless stat.readable?
end
end
diff --git a/lib/rdoc/parser.rb b/lib/rdoc/parser.rb
index 3798734d5e..d29599c879 100644
--- a/lib/rdoc/parser.rb
+++ b/lib/rdoc/parser.rb
@@ -22,14 +22,14 @@ require 'rdoc/stats'
# following incantation
#
# require "rdoc/parser"
-#
+#
# class RDoc::Parser::Xyz < RDoc::Parser
# parse_files_matching /\.xyz$/ # <<<<
-#
+#
# def initialize(file_name, body, options)
# ...
# end
-#
+#
# def scan
# ...
# end
@@ -63,13 +63,37 @@ class RDoc::Parser
end
##
- # Return _true_ if the +file+ seems like binary.
+ # Determines if the file is a "binary" file which basically means it has
+ # content that an RDoc parser shouldn't try to consume.
def self.binary?(file)
- s = File.read(file, 1024) or return false
- s.count("^ -~\t\r\n").fdiv(s.size) > 0.3 || s.index("\x00")
+ s = File.read(file, File.stat(file).blksize) || ""
+
+ if s[0, 2] == Marshal.dump('')[0, 2] then
+ true
+ elsif file =~ /erb\.rb$/ then
+ false
+ elsif s.scan(/<%|%>/).length >= 4 then
+ true
+ else
+ # From ptools under the Artistic License 2.0, (c) Daniel Berger.
+ s = s.split(//)
+
+ ((s.size - s.grep(" ".."~").size) / s.size.to_f) > 0.30
+ end
+ end
+
+ ##
+ # Checks if +file+ is a zip file in disguise. Signatures from
+ # http://www.garykessler.net/library/file_sigs.html
+
+ def self.zip? file
+ zip_signature = File.read file, 4
+
+ zip_signature == "PK\x03\x04" or
+ zip_signature == "PK\x05\x06" or
+ zip_signature == "PK\x07\x08"
end
- private_class_method :binary?
##
# Return a parser that can handle a particular extension
@@ -77,16 +101,13 @@ class RDoc::Parser
def self.can_parse(file_name)
parser = RDoc::Parser.parsers.find { |regexp,| regexp =~ file_name }.last
- #
- # The default parser should *NOT* parse binary files.
- #
- if parser == RDoc::Parser::Simple then
- if binary? file_name then
- return nil
- end
- end
+ # HACK Selenium hides a jar file using a .txt extension
+ return if parser == RDoc::Parser::Simple and zip? file_name
- return parser
+ # The default parser must not parse binary files
+ return if parser == RDoc::Parser::Simple and file_name !~ /\.(txt|rdoc)$/
+
+ parser
end
##
@@ -94,6 +115,8 @@ class RDoc::Parser
# for ones that we don't know
def self.for(top_level, file_name, body, options, stats)
+ return if binary? file_name
+
# If no extension, look for shebang
if file_name !~ /\.\w+$/ && body =~ %r{\A#!(.+)} then
shebang = $1
@@ -105,18 +128,15 @@ class RDoc::Parser
parser = can_parse file_name
- #
- # This method must return a parser.
- #
- if !parser then
- parser = RDoc::Parser::Simple
- end
+ return unless parser
parser.new top_level, file_name, body, options, stats
end
##
# Record which file types this parser can understand.
+ #
+ # It is ok to call this multiple times.
def self.parse_files_matching(regexp)
RDoc::Parser.parsers.unshift [regexp, self]
diff --git a/lib/rdoc/parser/c.rb b/lib/rdoc/parser/c.rb
index a2c3eefbde..f2d460b62f 100644
--- a/lib/rdoc/parser/c.rb
+++ b/lib/rdoc/parser/c.rb
@@ -14,32 +14,32 @@ require 'rdoc/known_classes'
# method, that is to say the method whose name is given in the
# <tt>rb_define_method</tt> call. For example, you might write:
#
-# /*
-# * Returns a new array that is a one-dimensional flattening of this
-# * array (recursively). That is, for every element that is an array,
-# * extract its elements into the new array.
-# *
-# * s = [ 1, 2, 3 ] #=> [1, 2, 3]
-# * t = [ 4, 5, 6, [7, 8] ] #=> [4, 5, 6, [7, 8]]
-# * a = [ s, t, 9, 10 ] #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10]
-# * a.flatten #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
-# */
-# static VALUE
-# rb_ary_flatten(ary)
-# VALUE ary;
-# {
-# ary = rb_obj_dup(ary);
-# rb_ary_flatten_bang(ary);
-# return ary;
-# }
+# /*
+# * Returns a new array that is a one-dimensional flattening of this
+# * array (recursively). That is, for every element that is an array,
+# * extract its elements into the new array.
+# *
+# * s = [ 1, 2, 3 ] #=> [1, 2, 3]
+# * t = [ 4, 5, 6, [7, 8] ] #=> [4, 5, 6, [7, 8]]
+# * a = [ s, t, 9, 10 ] #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10]
+# * a.flatten #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+# */
+# static VALUE
+# rb_ary_flatten(ary)
+# VALUE ary;
+# {
+# ary = rb_obj_dup(ary);
+# rb_ary_flatten_bang(ary);
+# return ary;
+# }
#
-# ...
+# ...
#
-# void
-# Init_Array()
-# {
-# ...
-# rb_define_method(rb_cArray, "flatten", rb_ary_flatten, 0);
+# void
+# Init_Array()
+# {
+# ...
+# rb_define_method(rb_cArray, "flatten", rb_ary_flatten, 0);
#
# Here RDoc will determine from the rb_define_method line that there's a
# method called "flatten" in class Array, and will look for the implementation
@@ -47,9 +47,6 @@ require 'rdoc/known_classes'
# method in the HTML output. This method must be in the same source file
# as the rb_define_method.
#
-# C classes can be diagrammed (see /tc/dl/ruby/ruby/error.c), and RDoc
-# integrates C and Ruby source into one tree
-#
# The comment blocks may include special directives:
#
# [Document-class: <i>name</i>]
@@ -68,7 +65,7 @@ require 'rdoc/known_classes'
# Ruby function is in the same source file as the rb_define_method call.
# If this isn't the case, add the comment:
#
-# rb_define_method(....); // in filename
+# rb_define_method(....); // in: filename
#
# As an example, we might have an extension that defines multiple classes
# in its Init_xxx method. We could document them using
@@ -79,7 +76,7 @@ require 'rdoc/known_classes'
# * Encapsulate the writing and reading of the configuration
# * file. ...
# */
-#
+#
# /*
# * Document-method: read_value
# *
@@ -96,8 +93,23 @@ class RDoc::Parser::C < RDoc::Parser
parse_files_matching(/\.(?:([CcHh])\1?|c([+xp])\2|y)\z/)
- @@enclosure_classes = {}
- @@known_bodies = {}
+ include RDoc::Text
+
+ ##
+ # C file the parser is parsing
+
+ attr_accessor :content
+
+ ##
+ # Resets cross-file state. Call when parsing different projects that need
+ # separate documentation.
+
+ def self.reset
+ @@enclosure_classes = {}
+ @@known_bodies = {}
+ end
+
+ reset
##
# Prepare to parse a C file
@@ -164,19 +176,17 @@ class RDoc::Parser::C < RDoc::Parser
def do_constants
@content.scan(%r{\Wrb_define_
- (
- variable |
- readonly_variable |
- const |
- global_const |
- )
+ ( variable |
+ readonly_variable |
+ const |
+ global_const | )
\s*\(
(?:\s*(\w+),)?
\s*"(\w+)",
\s*(.*?)\s*\)\s*;
}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)
+ handle_constants type, var_name, const_name, definition
end
end
@@ -270,12 +280,13 @@ 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
- comment, params = $1, $2
- body_text = $&
+ when %r"((?>/\*.*?\*/\s*))((?:(?:static|SWIGINTERN)\s+)?(?:intern\s+)?VALUE\s+#{meth_name}
+ \s*(\([^)]*\))([^;]|$))"xm
+ comment = $1
+ body_text = $2
+ params = $3
- remove_private_comments(comment) if comment
+ remove_private_comments comment if comment
# see if we can find the whole body
@@ -288,33 +299,40 @@ class RDoc::Parser::C < RDoc::Parser
# distinct (for example Kernel.hash and Kernel.object_id share the same
# implementation
- override_comment = find_override_comment(class_name, meth_obj.name)
+ override_comment = find_override_comment class_name, meth_obj.name
comment = override_comment if override_comment
- find_modifiers(comment, meth_obj) if comment
+ find_modifiers comment, meth_obj if comment
# meth_obj.params = params
meth_obj.start_collecting_tokens
- meth_obj.add_token(RDoc::RubyToken::Token.new(1,1).set_text(body_text))
- meth_obj.comment = mangle_comment(comment)
- when %r{((?>/\*.*?\*/\s*))^\s*\#\s*define\s+#{meth_name}\s+(\w+)}m
+ 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
comment = $1
- find_body(class_name, $2, meth_obj, body, true)
- find_modifiers(comment, meth_obj)
- meth_obj.comment = mangle_comment(comment) + meth_obj.comment
+ body_text = $2
+ find_body class_name, $3, meth_obj, body, true
+ find_modifiers comment, meth_obj
+
+ 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) + meth_obj.comment.to_s
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
return false
end
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)
- meth_obj.comment = mangle_comment(comment)
+ meth_obj.comment = strip_stars comment
else
warn "No definition for #{meth_name}" unless @options.quiet
return false
@@ -328,7 +346,7 @@ class RDoc::Parser::C < RDoc::Parser
if raw_name =~ /^rb_m/
container = @top_level.add_module RDoc::NormalModule, name
else
- container = @top_level.add_class RDoc::NormalClass, name, nil
+ container = @top_level.add_class RDoc::NormalClass, name
end
container.record_location @top_level
@@ -363,27 +381,23 @@ class RDoc::Parser::C < RDoc::Parser
# */
# VALUE cFoo = rb_define_class("Foo", rb_cObject);
- def find_class_comment(class_name, class_meth)
+ def find_class_comment(class_name, class_mod)
comment = nil
- if @content =~ %r{((?>/\*.*?\*/\s+))
- (static\s+)?void\s+Init_#{class_name}\s*(?:_\(\s*)?\(\s*(?:void\s*)\)}xmi then
+
+ if @content =~ %r{
+ ((?>/\*.*?\*/\s+))
+ (static\s+)?
+ void\s+
+ Init_#{class_name}\s*(?:_\(\s*)?\(\s*(?:void\s*)?\)}xmi then # )
comment = $1
- elsif @content =~ %r{Document-(?:class|module):\s#{class_name}\s*?(?:<\s+[:,\w]+)?\n((?>.*?\*/))}m
+ 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
comment = $1
- else
- if @content =~ /rb_define_(class|module)/m then
- class_name = class_name.split("::").last
- comments = []
- @content.split(/(\/\*.*?\*\/)\s*?\n/m).each_with_index do |chunk, index|
- comments[index] = chunk
- if chunk =~ /rb_define_(class|module).*?"(#{class_name})"/m then
- comment = comments[index-1]
- break
- end
- end
- end
end
- class_meth.comment = mangle_comment(comment) if comment
+
+ class_mod.comment = strip_stars comment if comment
end
##
@@ -434,67 +448,67 @@ class RDoc::Parser::C < RDoc::Parser
def handle_attr(var_name, attr_name, reader, writer)
rw = ''
- if reader
- #@stats.num_methods += 1
- rw << 'R'
- end
- if writer
- #@stats.num_methods += 1
- rw << 'W'
- end
+ rw << 'R' if reader
+ rw << 'W' if writer
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)
- unless comment.empty?
- comment = mangle_comment(comment)
- end
+ comment = strip_stars comment
att = RDoc::Attr.new '', attr_name, rw, comment
+ @stats.add_method att
class_obj.add_attribute(att)
end
end
- def handle_class_module(var_name, class_mod, class_name, parent, in_module)
+ def handle_class_module(var_name, type, class_name, parent, in_module)
parent_name = @known_classes[parent] || parent
- if in_module
+ if in_module then
enclosure = @classes[in_module] || @@enclosure_classes[in_module]
- unless enclosure
- if enclosure = @known_classes[in_module]
- handle_class_module(in_module, (/^rb_m/ =~ in_module ? "module" : "class"),
- enclosure, nil, nil)
- enclosure = @classes[in_module]
- end
+
+ if enclosure.nil? and enclosure = @known_classes[in_module] then
+ type = /^rb_m/ =~ in_module ? "module" : "class"
+ handle_class_module in_module, type, enclosure, nil, nil
+ enclosure = @classes[in_module]
end
- unless enclosure
- warn("Enclosing class/module '#{in_module}' for " +
- "#{class_mod} #{class_name} not known")
+
+ unless enclosure then
+ warn("Enclosing class/module '#{in_module}' for #{type} #{class_name} not known")
return
end
else
enclosure = @top_level
end
- if class_mod == "class" then
- full_name = enclosure.full_name.to_s + "::#{class_name}"
+ if type == "class" then
+ full_name = if RDoc::ClassModule === enclosure then
+ enclosure.full_name + "::#{class_name}"
+ else
+ class_name
+ end
+
if @content =~ %r{Document-class:\s+#{full_name}\s*<\s+([:,\w]+)} then
parent_name = $1
end
+
cm = enclosure.add_class RDoc::NormalClass, class_name, parent_name
+
@stats.add_class cm
else
cm = enclosure.add_module RDoc::NormalModule, class_name
@stats.add_module cm
end
- cm.record_location(enclosure.toplevel)
+ cm.record_location enclosure.top_level
+
+ find_class_comment cm.full_name, cm
- find_class_comment(cm.full_name, cm)
@classes[var_name] = cm
@@enclosure_classes[var_name] = cm
@known_classes[var_name] = cm.full_name
@@ -512,46 +526,55 @@ class RDoc::Parser::C < RDoc::Parser
# Values may include quotes and escaped colons (\:).
def handle_constants(type, var_name, const_name, definition)
- #@stats.num_constants += 1
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
- unless class_obj
- warn("Enclosing class/module '#{const_name}' for not known")
+ unless class_obj then
+ warn "Enclosing class/module #{const_name.inspect} not known"
return
end
- comment = find_const_comment(type, const_name)
+ comment = find_const_comment type, const_name
+ comment = strip_stars comment
+ comment = normalize_comment comment
# In the case of rb_define_const, the definition and comment are in
# "/* definition: comment */" form. The literal ':' and '\' characters
# can be escaped with a backslash.
if type.downcase == 'const' then
- elements = mangle_comment(comment).split(':')
- if elements.nil? or elements.empty? then
- con = RDoc::Constant.new(const_name, definition,
- mangle_comment(comment))
- else
- new_definition = elements[0..-2].join(':')
- if new_definition.empty? then # Default to literal C definition
- new_definition = definition
- else
- new_definition.gsub!("\:", ":")
- new_definition.gsub!("\\", '\\')
- end
- new_definition.sub!(/\A(\s+)/, '')
- new_comment = $1.nil? ? elements.last : "#{$1}#{elements.last.lstrip}"
- con = RDoc::Constant.new(const_name, new_definition,
- mangle_comment(new_comment))
- end
+ elements = comment.split ':'
+
+ if elements.nil? or elements.empty? then
+ con = RDoc::Constant.new const_name, definition, comment
+ else
+ new_definition = elements[0..-2].join(':')
+
+ if new_definition.empty? then # Default to literal C definition
+ new_definition = definition
+ else
+ new_definition.gsub!("\:", ":")
+ new_definition.gsub!("\\", '\\')
+ end
+
+ new_definition.sub!(/\A(\s+)/, '')
+
+ new_comment = if $1.nil? then
+ elements.last.lstrip
+ else
+ "#{$1}#{elements.last.lstrip}"
+ end
+
+ con = RDoc::Constant.new const_name, new_definition, new_comment
+ end
else
- con = RDoc::Constant.new const_name, definition, mangle_comment(comment)
+ con = RDoc::Constant.new const_name, definition, comment
end
- class_obj.add_constant(con)
+ @stats.add_constant con
+ class_obj.add_constant con
end
##
@@ -588,9 +611,11 @@ class RDoc::Parser::C < RDoc::Parser
meth_obj.params = "(" + (1..p_count).map{|i| "p#{i}"}.join(", ") + ")"
end
- if source_file then
+ if source_file and File.exist? source_file then
file_name = File.join(@file_dir, source_file)
body = (@@known_bodies[source_file] ||= File.read(file_name))
+ elsif source_file then
+ warn "unknown source file #{source_file}"
else
body = @content
end
@@ -598,6 +623,7 @@ class RDoc::Parser::C < RDoc::Parser
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
end
end
end
@@ -615,16 +641,6 @@ class RDoc::Parser::C < RDoc::Parser
end
##
- # Remove the /*'s and leading asterisks from C comments
-
- def mangle_comment(comment)
- comment.sub!(%r{/\*+}) { " " * $&.length }
- comment.sub!(%r{\*+/}) { " " * $&.length }
- comment.gsub!(/^[ \t]*\*/m) { " " * $&.length }
- comment
- end
-
- ##
# Removes lines that are commented out that might otherwise get picked up
# when scanning for classes and methods
@@ -651,11 +667,5 @@ class RDoc::Parser::C < RDoc::Parser
@top_level
end
- def warn(msg)
- $stderr.puts
- $stderr.puts msg
- $stderr.flush
- end
-
end
diff --git a/lib/rdoc/parser/f95.rb b/lib/rdoc/parser/f95.rb
deleted file mode 100644
index 0959db4936..0000000000
--- a/lib/rdoc/parser/f95.rb
+++ /dev/null
@@ -1,1835 +0,0 @@
-require 'rdoc/parser'
-
-##
-# = Fortran95 RDoc Parser
-#
-# == Overview
-#
-# This parser parses Fortran95 files with suffixes "f90", "F90", "f95" and
-# "F95". Fortran95 files are expected to be conformed to Fortran95 standards.
-#
-# == Rules
-#
-# Fundamental rules are same as that of the Ruby parser. But comment markers
-# are '!' not '#'.
-#
-# === Correspondence between RDoc documentation and Fortran95 programs
-#
-# F95 parses main programs, modules, subroutines, functions, derived-types,
-# public variables, public constants, defined operators and defined
-# assignments. These components are described in items of RDoc documentation,
-# as follows.
-#
-# Files :: Files (same as Ruby)
-# Classes:: Modules
-# Methods:: Subroutines, functions, variables, constants, derived-types,
-# defined operators, defined assignments
-# Required files:: Files in which imported modules, external subroutines and
-# external functions are defined.
-# Included Modules:: List of imported modules
-# Attributes:: List of derived-types, List of imported modules all of whose
-# components are published again
-#
-# Components listed in 'Methods' (subroutines, functions, ...) defined in
-# modules are described in the item of 'Classes'. On the other hand,
-# components defined in main programs or as external procedures are described
-# in the item of 'Files'.
-#
-# === Components parsed by default
-#
-# By default, documentation on public components (subroutines, functions,
-# variables, constants, derived-types, defined operators, defined assignments)
-# are generated.
-#
-# With "--all" option, documentation on all components are generated (almost
-# same as the Ruby parser).
-#
-# === Information parsed automatically
-#
-# The following information is automatically parsed.
-#
-# * Types of arguments
-# * Types of variables and constants
-# * Types of variables in the derived types, and initial values
-# * NAMELISTs and types of variables in them, and initial values
-#
-# Aliases by interface statement are described in the item of 'Methods'.
-#
-# Components which are imported from other modules and published again are
-# described in the item of 'Methods'.
-#
-# === Format of comment blocks
-#
-# Comment blocks should be written as follows.
-#
-# Comment blocks are considered to be ended when the line without '!' appears.
-#
-# The indentation is not necessary.
-#
-# ! (Top of file)
-# !
-# ! Comment blocks for the files.
-# !
-# !--
-# ! The comment described in the part enclosed by
-# ! "!--" and "!++" is ignored.
-# !++
-# !
-# module hogehoge
-# !
-# ! Comment blocks for the modules (or the programs).
-# !
-#
-# private
-#
-# logical :: a ! a private variable
-# real, public :: b ! a public variable
-# integer, parameter :: c = 0 ! a public constant
-#
-# public :: c
-# public :: MULTI_ARRAY
-# public :: hoge, foo
-#
-# type MULTI_ARRAY
-# !
-# ! Comment blocks for the derived-types.
-# !
-# real, pointer :: var(:) =>null() ! Comments block for the variables.
-# integer :: num = 0
-# end type MULTI_ARRAY
-#
-# contains
-#
-# subroutine hoge( in, & ! Comment blocks between continuation lines are ignored.
-# & out )
-# !
-# ! Comment blocks for the subroutines or functions
-# !
-# character(*),intent(in):: in ! Comment blocks for the arguments.
-# character(*),intent(out),allocatable,target :: in
-# ! Comment blocks can be
-# ! written under Fortran statements.
-#
-# character(32) :: file ! This comment parsed as a variable in below NAMELIST.
-# integer :: id
-#
-# namelist /varinfo_nml/ file, id
-# !
-# ! Comment blocks for the NAMELISTs.
-# ! Information about variables are described above.
-# !
-#
-# ....
-#
-# end subroutine hoge
-#
-# integer function foo( in )
-# !
-# ! This part is considered as comment block.
-#
-# ! Comment blocks under blank lines are ignored.
-# !
-# integer, intent(in):: inA ! This part is considered as comment block.
-#
-# ! This part is ignored.
-#
-# end function foo
-#
-# subroutine hide( in, &
-# & out ) !:nodoc:
-# !
-# ! If "!:nodoc:" is described at end-of-line in subroutine
-# ! statement as above, the subroutine is ignored.
-# ! This assignment can be used to modules, subroutines,
-# ! functions, variables, constants, derived-types,
-# ! defined operators, defined assignments,
-# ! list of imported modules ("use" statement).
-# !
-#
-# ....
-#
-# end subroutine hide
-#
-# end module hogehoge
-
-class RDoc::Parser::F95 < RDoc::Parser
-
- parse_files_matching(/\.((f|F)9(0|5)|F)$/)
-
- class Token
-
- NO_TEXT = "??".freeze
-
- def initialize(line_no, char_no)
- @line_no = line_no
- @char_no = char_no
- @text = NO_TEXT
- end
- # Because we're used in contexts that expect to return a token,
- # we set the text string and then return ourselves
- def set_text(text)
- @text = text
- self
- end
-
- attr_reader :line_no, :char_no, :text
-
- end
-
- @@external_aliases = []
- @@public_methods = []
-
- ##
- # "false":: Comments are below source code
- # "true" :: Comments are upper source code
-
- COMMENTS_ARE_UPPER = false
-
- ##
- # Internal alias message
-
- INTERNAL_ALIAS_MES = "Alias for"
-
- ##
- # External alias message
-
- EXTERNAL_ALIAS_MES = "The entity is"
-
- ##
- # Define code constructs
-
- def scan
- # remove private comment
- remaining_code = remove_private_comments(@content)
-
- # continuation lines are united to one line
- remaining_code = united_to_one_line(remaining_code)
-
- # semicolons are replaced to line feed
- remaining_code = semicolon_to_linefeed(remaining_code)
-
- # collect comment for file entity
- whole_comment, remaining_code = collect_first_comment(remaining_code)
- @top_level.comment = whole_comment
-
- # String "remaining_code" is converted to Array "remaining_lines"
- remaining_lines = remaining_code.split("\n")
-
- # "module" or "program" parts are parsed (new)
- #
- level_depth = 0
- block_searching_flag = nil
- block_searching_lines = []
- pre_comment = []
- module_program_trailing = ""
- module_program_name = ""
- other_block_level_depth = 0
- other_block_searching_flag = nil
- remaining_lines.collect!{|line|
- if !block_searching_flag && !other_block_searching_flag
- if line =~ /^\s*?module\s+(\w+)\s*?(!.*?)?$/i
- block_searching_flag = :module
- block_searching_lines << line
- module_program_name = $1
- module_program_trailing = find_comments($2)
- next false
- elsif line =~ /^\s*?program\s+(\w+)\s*?(!.*?)?$/i ||
- line =~ /^\s*?\w/ && !block_start?(line)
- block_searching_flag = :program
- block_searching_lines << line
- module_program_name = $1 || ""
- module_program_trailing = find_comments($2)
- next false
-
- elsif block_start?(line)
- other_block_searching_flag = true
- next line
-
- elsif line =~ /^\s*?!\s?(.*)/
- pre_comment << line
- next line
- else
- pre_comment = []
- next line
- end
- elsif other_block_searching_flag
- other_block_level_depth += 1 if block_start?(line)
- other_block_level_depth -= 1 if block_end?(line)
- if other_block_level_depth < 0
- other_block_level_depth = 0
- other_block_searching_flag = nil
- end
- next line
- end
-
- block_searching_lines << line
- level_depth += 1 if block_start?(line)
- level_depth -= 1 if block_end?(line)
- if level_depth >= 0
- next false
- end
-
- # "module_program_code" is formatted.
- # ":nodoc:" flag is checked.
- #
- module_program_code = block_searching_lines.join("\n")
- module_program_code = remove_empty_head_lines(module_program_code)
- if module_program_trailing =~ /^:nodoc:/
- # next loop to search next block
- level_depth = 0
- block_searching_flag = false
- block_searching_lines = []
- pre_comment = []
- next false
- end
-
- # NormalClass is created, and added to @top_level
- #
- if block_searching_flag == :module
- module_name = module_program_name
- module_code = module_program_code
- module_trailing = module_program_trailing
-
- f9x_module = @top_level.add_module NormalClass, module_name
- f9x_module.record_location @top_level
-
- @stats.add_module f9x_module
-
- f9x_comment = COMMENTS_ARE_UPPER ?
- find_comments(pre_comment.join("\n")) + "\n" + module_trailing :
- module_trailing + "\n" + find_comments(module_code.sub(/^.*$\n/i, ''))
- f9x_module.comment = f9x_comment
- parse_program_or_module(f9x_module, module_code)
-
- TopLevel.all_files.each do |name, toplevel|
- if toplevel.include_includes?(module_name, @options.ignore_case)
- if !toplevel.include_requires?(@file_name, @options.ignore_case)
- toplevel.add_require(Require.new(@file_name, ""))
- end
- end
- toplevel.each_classmodule{|m|
- if m.include_includes?(module_name, @options.ignore_case)
- if !m.include_requires?(@file_name, @options.ignore_case)
- m.add_require(Require.new(@file_name, ""))
- end
- end
- }
- end
- elsif block_searching_flag == :program
- program_name = module_program_name
- program_code = module_program_code
- program_trailing = module_program_trailing
- # progress "p" # HACK what stats thingy does this correspond to?
- program_comment = COMMENTS_ARE_UPPER ?
- find_comments(pre_comment.join("\n")) + "\n" + program_trailing :
- program_trailing + "\n" + find_comments(program_code.sub(/^.*$\n/i, ''))
- program_comment = "\n\n= <i>Program</i> <tt>#{program_name}</tt>\n\n" \
- + program_comment
- @top_level.comment << program_comment
- parse_program_or_module(@top_level, program_code, :private)
- end
-
- # next loop to search next block
- level_depth = 0
- block_searching_flag = false
- block_searching_lines = []
- pre_comment = []
- next false
- }
-
- remaining_lines.delete_if{ |line|
- line == false
- }
-
- # External subprograms and functions are parsed
- #
- parse_program_or_module(@top_level, remaining_lines.join("\n"),
- :public, true)
-
- @top_level
- end # End of scan
-
- private
-
- def parse_program_or_module(container, code,
- visibility=:public, external=nil)
- return unless container
- return unless code
- remaining_lines = code.split("\n")
- remaining_code = "#{code}"
-
- #
- # Parse variables before "contains" in module
- #
- level_depth = 0
- before_contains_lines = []
- before_contains_code = nil
- before_contains_flag = nil
- remaining_lines.each{ |line|
- if !before_contains_flag
- if line =~ /^\s*?module\s+\w+\s*?(!.*?)?$/i
- before_contains_flag = true
- end
- else
- break if line =~ /^\s*?contains\s*?(!.*?)?$/i
- level_depth += 1 if block_start?(line)
- level_depth -= 1 if block_end?(line)
- break if level_depth < 0
- before_contains_lines << line
- end
- }
- before_contains_code = before_contains_lines.join("\n")
- if before_contains_code
- before_contains_code.gsub!(/^\s*?interface\s+.*?\s+end\s+interface.*?$/im, "")
- before_contains_code.gsub!(/^\s*?type[\s\,]+.*?\s+end\s+type.*?$/im, "")
- end
-
- #
- # Parse global "use"
- #
- use_check_code = "#{before_contains_code}"
- cascaded_modules_list = []
- while use_check_code =~ /^\s*?use\s+(\w+)(.*?)(!.*?)?$/i
- use_check_code = $~.pre_match
- use_check_code << $~.post_match
- used_mod_name = $1.strip.chomp
- used_list = $2 || ""
- used_trailing = $3 || ""
- next if used_trailing =~ /!:nodoc:/
- if !container.include_includes?(used_mod_name, @options.ignore_case)
- # progress "." # HACK what stats thingy does this correspond to?
- container.add_include Include.new(used_mod_name, "")
- end
- if ! (used_list =~ /\,\s*?only\s*?:/i )
- cascaded_modules_list << "\#" + used_mod_name
- end
- end
-
- #
- # Parse public and private, and store information.
- # This information is used when "add_method" and
- # "set_visibility_for" are called.
- #
- visibility_default, visibility_info =
- parse_visibility(remaining_lines.join("\n"), visibility, container)
- @@public_methods.concat visibility_info
- if visibility_default == :public
- if !cascaded_modules_list.empty?
- cascaded_modules =
- Attr.new("Cascaded Modules",
- "Imported modules all of whose components are published again",
- "",
- cascaded_modules_list.join(", "))
- container.add_attribute(cascaded_modules)
- end
- end
-
- #
- # Check rename elements
- #
- use_check_code = "#{before_contains_code}"
- while use_check_code =~ /^\s*?use\s+(\w+)\s*?\,(.+)$/i
- use_check_code = $~.pre_match
- use_check_code << $~.post_match
- used_mod_name = $1.strip.chomp
- used_elements = $2.sub(/\s*?only\s*?:\s*?/i, '')
- used_elements.split(",").each{ |used|
- if /\s*?(\w+)\s*?=>\s*?(\w+)\s*?/ =~ used
- local = $1
- org = $2
- @@public_methods.collect!{ |pub_meth|
- if local == pub_meth["name"] ||
- local.upcase == pub_meth["name"].upcase &&
- @options.ignore_case
- pub_meth["name"] = org
- pub_meth["local_name"] = local
- end
- pub_meth
- }
- end
- }
- end
-
- #
- # Parse private "use"
- #
- use_check_code = remaining_lines.join("\n")
- while use_check_code =~ /^\s*?use\s+(\w+)(.*?)(!.*?)?$/i
- use_check_code = $~.pre_match
- use_check_code << $~.post_match
- used_mod_name = $1.strip.chomp
- used_trailing = $3 || ""
- next if used_trailing =~ /!:nodoc:/
- if !container.include_includes?(used_mod_name, @options.ignore_case)
- # progress "." # HACK what stats thingy does this correspond to?
- container.add_include Include.new(used_mod_name, "")
- end
- end
-
- container.each_includes{ |inc|
- TopLevel.all_files.each do |name, toplevel|
- indicated_mod = toplevel.find_symbol(inc.name,
- nil, @options.ignore_case)
- if indicated_mod
- indicated_name = indicated_mod.parent.file_relative_name
- if !container.include_requires?(indicated_name, @options.ignore_case)
- container.add_require(Require.new(indicated_name, ""))
- end
- break
- end
- end
- }
-
- #
- # Parse derived-types definitions
- #
- derived_types_comment = ""
- remaining_code = remaining_lines.join("\n")
- while remaining_code =~ /^\s*?
- type[\s\,]+(public|private)?\s*?(::)?\s*?
- (\w+)\s*?(!.*?)?$
- (.*?)
- ^\s*?end\s+type.*?$
- /imx
- remaining_code = $~.pre_match
- remaining_code << $~.post_match
- typename = $3.chomp.strip
- type_elements = $5 || ""
- type_code = remove_empty_head_lines($&)
- type_trailing = find_comments($4)
- next if type_trailing =~ /^:nodoc:/
- type_visibility = $1
- type_comment = COMMENTS_ARE_UPPER ?
- find_comments($~.pre_match) + "\n" + type_trailing :
- type_trailing + "\n" + find_comments(type_code.sub(/^.*$\n/i, ''))
- type_element_visibility_public = true
- type_code.split("\n").each{ |line|
- if /^\s*?private\s*?$/ =~ line
- type_element_visibility_public = nil
- break
- end
- } if type_code
-
- args_comment = ""
- type_args_info = nil
-
- if @options.show_all
- args_comment = find_arguments(nil, type_code, true)
- else
- type_public_args_list = []
- type_args_info = definition_info(type_code)
- type_args_info.each{ |arg|
- arg_is_public = type_element_visibility_public
- arg_is_public = true if arg.include_attr?("public")
- arg_is_public = nil if arg.include_attr?("private")
- type_public_args_list << arg.varname if arg_is_public
- }
- args_comment = find_arguments(type_public_args_list, type_code)
- end
-
- type = AnyMethod.new("type #{typename}", typename)
- type.singleton = false
- type.params = ""
- type.comment = "<b><em> Derived Type </em></b> :: <tt></tt>\n"
- type.comment << args_comment if args_comment
- type.comment << type_comment if type_comment
-
- @stats.add_method type
-
- container.add_method type
-
- set_visibility(container, typename, visibility_default, @@public_methods)
-
- if type_visibility
- type_visibility.gsub!(/\s/,'')
- type_visibility.gsub!(/\,/,'')
- type_visibility.gsub!(/:/,'')
- type_visibility.downcase!
- if type_visibility == "public"
- container.set_visibility_for([typename], :public)
- elsif type_visibility == "private"
- container.set_visibility_for([typename], :private)
- end
- end
-
- check_public_methods(type, container.name)
-
- if @options.show_all
- derived_types_comment << ", " unless derived_types_comment.empty?
- derived_types_comment << typename
- else
- if type.visibility == :public
- derived_types_comment << ", " unless derived_types_comment.empty?
- derived_types_comment << typename
- end
- end
-
- end
-
- if !derived_types_comment.empty?
- derived_types_table =
- Attr.new("Derived Types", "Derived_Types", "",
- derived_types_comment)
- container.add_attribute(derived_types_table)
- end
-
- #
- # move interface scope
- #
- interface_code = ""
- while remaining_code =~ /^\s*?
- interface(
- \s+\w+ |
- \s+operator\s*?\(.*?\) |
- \s+assignment\s*?\(\s*?=\s*?\)
- )?\s*?$
- (.*?)
- ^\s*?end\s+interface.*?$
- /imx
- interface_code << remove_empty_head_lines($&) + "\n"
- remaining_code = $~.pre_match
- remaining_code << $~.post_match
- end
-
- #
- # Parse global constants or variables in modules
- #
- const_var_defs = definition_info(before_contains_code)
- const_var_defs.each{|defitem|
- next if defitem.nodoc
- const_or_var_type = "Variable"
- const_or_var_progress = "v"
- if defitem.include_attr?("parameter")
- const_or_var_type = "Constant"
- const_or_var_progress = "c"
- end
- const_or_var = AnyMethod.new(const_or_var_type, defitem.varname)
- const_or_var.singleton = false
- const_or_var.params = ""
- self_comment = find_arguments([defitem.varname], before_contains_code)
- const_or_var.comment = "<b><em>" + const_or_var_type + "</em></b> :: <tt></tt>\n"
- const_or_var.comment << self_comment if self_comment
-
- @stats.add_method const_or_var_progress
-
- container.add_method const_or_var
-
- set_visibility(container, defitem.varname, visibility_default, @@public_methods)
-
- if defitem.include_attr?("public")
- container.set_visibility_for([defitem.varname], :public)
- elsif defitem.include_attr?("private")
- container.set_visibility_for([defitem.varname], :private)
- end
-
- check_public_methods(const_or_var, container.name)
-
- } if const_var_defs
-
- remaining_lines = remaining_code.split("\n")
-
- # "subroutine" or "function" parts are parsed (new)
- #
- level_depth = 0
- block_searching_flag = nil
- block_searching_lines = []
- pre_comment = []
- procedure_trailing = ""
- procedure_name = ""
- procedure_params = ""
- procedure_prefix = ""
- procedure_result_arg = ""
- procedure_type = ""
- contains_lines = []
- contains_flag = nil
- remaining_lines.collect!{|line|
- if !block_searching_flag
- # subroutine
- if line =~ /^\s*?
- (recursive|pure|elemental)?\s*?
- subroutine\s+(\w+)\s*?(\(.*?\))?\s*?(!.*?)?$
- /ix
- block_searching_flag = :subroutine
- block_searching_lines << line
-
- procedure_name = $2.chomp.strip
- procedure_params = $3 || ""
- procedure_prefix = $1 || ""
- procedure_trailing = $4 || "!"
- next false
-
- # function
- elsif line =~ /^\s*?
- (recursive|pure|elemental)?\s*?
- (
- character\s*?(\([\w\s\=\(\)\*]+?\))?\s+
- | type\s*?\([\w\s]+?\)\s+
- | integer\s*?(\([\w\s\=\(\)\*]+?\))?\s+
- | real\s*?(\([\w\s\=\(\)\*]+?\))?\s+
- | double\s+precision\s+
- | logical\s*?(\([\w\s\=\(\)\*]+?\))?\s+
- | complex\s*?(\([\w\s\=\(\)\*]+?\))?\s+
- )?
- function\s+(\w+)\s*?
- (\(.*?\))?(\s+result\((.*?)\))?\s*?(!.*?)?$
- /ix
- block_searching_flag = :function
- block_searching_lines << line
-
- procedure_prefix = $1 || ""
- procedure_type = $2 ? $2.chomp.strip : nil
- procedure_name = $8.chomp.strip
- procedure_params = $9 || ""
- procedure_result_arg = $11 ? $11.chomp.strip : procedure_name
- procedure_trailing = $12 || "!"
- next false
- elsif line =~ /^\s*?!\s?(.*)/
- pre_comment << line
- next line
- else
- pre_comment = []
- next line
- end
- end
- contains_flag = true if line =~ /^\s*?contains\s*?(!.*?)?$/
- block_searching_lines << line
- contains_lines << line if contains_flag
-
- level_depth += 1 if block_start?(line)
- level_depth -= 1 if block_end?(line)
- if level_depth >= 0
- next false
- end
-
- # "procedure_code" is formatted.
- # ":nodoc:" flag is checked.
- #
- procedure_code = block_searching_lines.join("\n")
- procedure_code = remove_empty_head_lines(procedure_code)
- if procedure_trailing =~ /^!:nodoc:/
- # next loop to search next block
- level_depth = 0
- block_searching_flag = nil
- block_searching_lines = []
- pre_comment = []
- procedure_trailing = ""
- procedure_name = ""
- procedure_params = ""
- procedure_prefix = ""
- procedure_result_arg = ""
- procedure_type = ""
- contains_lines = []
- contains_flag = nil
- next false
- end
-
- # AnyMethod is created, and added to container
- #
- subroutine_function = nil
- if block_searching_flag == :subroutine
- subroutine_prefix = procedure_prefix
- subroutine_name = procedure_name
- subroutine_params = procedure_params
- subroutine_trailing = procedure_trailing
- subroutine_code = procedure_code
-
- subroutine_comment = COMMENTS_ARE_UPPER ?
- pre_comment.join("\n") + "\n" + subroutine_trailing :
- subroutine_trailing + "\n" + subroutine_code.sub(/^.*$\n/i, '')
- subroutine = AnyMethod.new("subroutine", subroutine_name)
- parse_subprogram(subroutine, subroutine_params,
- subroutine_comment, subroutine_code,
- before_contains_code, nil, subroutine_prefix)
-
- @stats.add_method subroutine
-
- container.add_method subroutine
- subroutine_function = subroutine
-
- elsif block_searching_flag == :function
- function_prefix = procedure_prefix
- function_type = procedure_type
- function_name = procedure_name
- function_params_org = procedure_params
- function_result_arg = procedure_result_arg
- function_trailing = procedure_trailing
- function_code_org = procedure_code
-
- function_comment = COMMENTS_ARE_UPPER ?
- pre_comment.join("\n") + "\n" + function_trailing :
- function_trailing + "\n " + function_code_org.sub(/^.*$\n/i, '')
-
- function_code = "#{function_code_org}"
- if function_type
- function_code << "\n" + function_type + " :: " + function_result_arg
- end
-
- function_params =
- function_params_org.sub(/^\(/, "\(#{function_result_arg}, ")
-
- function = AnyMethod.new("function", function_name)
- parse_subprogram(function, function_params,
- function_comment, function_code,
- before_contains_code, true, function_prefix)
-
- # Specific modification due to function
- function.params.sub!(/\(\s*?#{function_result_arg}\s*?,\s*?/, "\( ")
- function.params << " result(" + function_result_arg + ")"
- function.start_collecting_tokens
- function.add_token Token.new(1,1).set_text(function_code_org)
-
- @stats.add_method function
-
- container.add_method function
- subroutine_function = function
-
- end
-
- # The visibility of procedure is specified
- #
- set_visibility(container, procedure_name,
- visibility_default, @@public_methods)
-
- # The alias for this procedure from external modules
- #
- check_external_aliases(procedure_name,
- subroutine_function.params,
- subroutine_function.comment, subroutine_function) if external
- check_public_methods(subroutine_function, container.name)
-
-
- # contains_lines are parsed as private procedures
- if contains_flag
- parse_program_or_module(container,
- contains_lines.join("\n"), :private)
- end
-
- # next loop to search next block
- level_depth = 0
- block_searching_flag = nil
- block_searching_lines = []
- pre_comment = []
- procedure_trailing = ""
- procedure_name = ""
- procedure_params = ""
- procedure_prefix = ""
- procedure_result_arg = ""
- contains_lines = []
- contains_flag = nil
- next false
- } # End of remaining_lines.collect!{|line|
-
- # Array remains_lines is converted to String remains_code again
- #
- remaining_code = remaining_lines.join("\n")
-
- #
- # Parse interface
- #
- interface_scope = false
- generic_name = ""
- interface_code.split("\n").each{ |line|
- if /^\s*?
- interface(
- \s+\w+|
- \s+operator\s*?\(.*?\)|
- \s+assignment\s*?\(\s*?=\s*?\)
- )?
- \s*?(!.*?)?$
- /ix =~ line
- generic_name = $1 ? $1.strip.chomp : nil
- interface_trailing = $2 || "!"
- interface_scope = true
- interface_scope = false if interface_trailing =~ /!:nodoc:/
-# if generic_name =~ /operator\s*?\((.*?)\)/i
-# operator_name = $1
-# if operator_name && !operator_name.empty?
-# generic_name = "#{operator_name}"
-# end
-# end
-# if generic_name =~ /assignment\s*?\((.*?)\)/i
-# assignment_name = $1
-# if assignment_name && !assignment_name.empty?
-# generic_name = "#{assignment_name}"
-# end
-# end
- end
- if /^\s*?end\s+interface/i =~ line
- interface_scope = false
- generic_name = nil
- end
- # internal alias
- if interface_scope && /^\s*?module\s+procedure\s+(.*?)(!.*?)?$/i =~ line
- procedures = $1.strip.chomp
- procedures_trailing = $2 || "!"
- next if procedures_trailing =~ /!:nodoc:/
- procedures.split(",").each{ |proc|
- proc.strip!
- proc.chomp!
- next if generic_name == proc || !generic_name
- old_meth = container.find_symbol(proc, nil, @options.ignore_case)
- next if !old_meth
- nolink = old_meth.visibility == :private ? true : nil
- nolink = nil if @options.show_all
- new_meth =
- initialize_external_method(generic_name, proc,
- old_meth.params, nil,
- old_meth.comment,
- old_meth.clone.token_stream[0].text,
- true, nolink)
- new_meth.singleton = old_meth.singleton
-
- @stats.add_method new_meth
-
- container.add_method new_meth
-
- set_visibility(container, generic_name, visibility_default, @@public_methods)
-
- check_public_methods(new_meth, container.name)
-
- }
- end
-
- # external aliases
- if interface_scope
- # subroutine
- proc = nil
- params = nil
- procedures_trailing = nil
- if line =~ /^\s*?
- (recursive|pure|elemental)?\s*?
- subroutine\s+(\w+)\s*?(\(.*?\))?\s*?(!.*?)?$
- /ix
- proc = $2.chomp.strip
- generic_name = proc unless generic_name
- params = $3 || ""
- procedures_trailing = $4 || "!"
-
- # function
- elsif line =~ /^\s*?
- (recursive|pure|elemental)?\s*?
- (
- character\s*?(\([\w\s\=\(\)\*]+?\))?\s+
- | type\s*?\([\w\s]+?\)\s+
- | integer\s*?(\([\w\s\=\(\)\*]+?\))?\s+
- | real\s*?(\([\w\s\=\(\)\*]+?\))?\s+
- | double\s+precision\s+
- | logical\s*?(\([\w\s\=\(\)\*]+?\))?\s+
- | complex\s*?(\([\w\s\=\(\)\*]+?\))?\s+
- )?
- function\s+(\w+)\s*?
- (\(.*?\))?(\s+result\((.*?)\))?\s*?(!.*?)?$
- /ix
- proc = $8.chomp.strip
- generic_name = proc unless generic_name
- params = $9 || ""
- procedures_trailing = $12 || "!"
- else
- next
- end
- next if procedures_trailing =~ /!:nodoc:/
- indicated_method = nil
- indicated_file = nil
- TopLevel.all_files.each do |name, toplevel|
- indicated_method = toplevel.find_local_symbol(proc, @options.ignore_case)
- indicated_file = name
- break if indicated_method
- end
-
- if indicated_method
- external_method =
- initialize_external_method(generic_name, proc,
- indicated_method.params,
- indicated_file,
- indicated_method.comment)
-
- @stats.add_method external_method
-
- container.add_method external_method
- set_visibility(container, generic_name, visibility_default, @@public_methods)
- if !container.include_requires?(indicated_file, @options.ignore_case)
- container.add_require(Require.new(indicated_file, ""))
- end
- check_public_methods(external_method, container.name)
-
- else
- @@external_aliases << {
- "new_name" => generic_name,
- "old_name" => proc,
- "file_or_module" => container,
- "visibility" => find_visibility(container, generic_name, @@public_methods) || visibility_default
- }
- end
- end
-
- } if interface_code # End of interface_code.split("\n").each ...
-
- #
- # Already imported methods are removed from @@public_methods.
- # Remainders are assumed to be imported from other modules.
- #
- @@public_methods.delete_if{ |method| method["entity_is_discovered"]}
-
- @@public_methods.each{ |pub_meth|
- next unless pub_meth["file_or_module"].name == container.name
- pub_meth["used_modules"].each{ |used_mod|
- TopLevel.all_classes_and_modules.each{ |modules|
- if modules.name == used_mod ||
- modules.name.upcase == used_mod.upcase &&
- @options.ignore_case
- modules.method_list.each{ |meth|
- if meth.name == pub_meth["name"] ||
- meth.name.upcase == pub_meth["name"].upcase &&
- @options.ignore_case
- new_meth = initialize_public_method(meth,
- modules.name)
- if pub_meth["local_name"]
- new_meth.name = pub_meth["local_name"]
- end
-
- @stats.add_method new_meth
-
- container.add_method new_meth
- end
- }
- end
- }
- }
- }
-
- container
- end # End of parse_program_or_module
-
- ##
- # Parse arguments, comment, code of subroutine and function. Return
- # AnyMethod object.
-
- def parse_subprogram(subprogram, params, comment, code,
- before_contains=nil, function=nil, prefix=nil)
- subprogram.singleton = false
- prefix = "" if !prefix
- arguments = params.sub(/\(/, "").sub(/\)/, "").split(",") if params
- args_comment, params_opt =
- find_arguments(arguments, code.sub(/^s*?contains\s*?(!.*?)?$.*/im, ""),
- nil, nil, true)
- params_opt = "( " + params_opt + " ) " if params_opt
- subprogram.params = params_opt || ""
- namelist_comment = find_namelists(code, before_contains)
-
- block_comment = find_comments comment
- if function
- subprogram.comment = "<b><em> Function </em></b> :: <em>#{prefix}</em>\n"
- else
- subprogram.comment = "<b><em> Subroutine </em></b> :: <em>#{prefix}</em>\n"
- end
- subprogram.comment << args_comment if args_comment
- subprogram.comment << block_comment if block_comment
- subprogram.comment << namelist_comment if namelist_comment
-
- # For output source code
- subprogram.start_collecting_tokens
- subprogram.add_token Token.new(1,1).set_text(code)
-
- subprogram
- end
-
- ##
- # Collect comment for file entity
-
- def collect_first_comment(body)
- comment = ""
- not_comment = ""
- comment_start = false
- comment_end = false
- body.split("\n").each{ |line|
- if comment_end
- not_comment << line
- not_comment << "\n"
- elsif /^\s*?!\s?(.*)$/i =~ line
- comment_start = true
- comment << $1
- comment << "\n"
- elsif /^\s*?$/i =~ line
- comment_end = true if comment_start && COMMENTS_ARE_UPPER
- else
- comment_end = true
- not_comment << line
- not_comment << "\n"
- end
- }
- return comment, not_comment
- end
-
-
- ##
- # Return comments of definitions of arguments
- #
- # If "all" argument is true, information of all arguments are returned.
- #
- # If "modified_params" is true, list of arguments are decorated, for
- # example, optional arguments are parenthetic as "[arg]".
-
- def find_arguments(args, text, all=nil, indent=nil, modified_params=nil)
- return unless args || all
- indent = "" unless indent
- args = ["all"] if all
- params = "" if modified_params
- comma = ""
- return unless text
- args_rdocforms = "\n"
- remaining_lines = "#{text}"
- definitions = definition_info(remaining_lines)
- args.each{ |arg|
- arg.strip!
- arg.chomp!
- definitions.each { |defitem|
- if arg == defitem.varname.strip.chomp || all
- args_rdocforms << <<-"EOF"
-
-#{indent}<tt><b>#{defitem.varname.chomp.strip}#{defitem.arraysuffix}</b> #{defitem.inivalue}</tt> ::
-#{indent} <tt>#{defitem.types.chomp.strip}</tt>
-EOF
- if !defitem.comment.chomp.strip.empty?
- comment = ""
- defitem.comment.split("\n").each{ |line|
- comment << " " + line + "\n"
- }
- args_rdocforms << <<-"EOF"
-
-#{indent} <tt></tt> ::
-#{indent} <tt></tt>
-#{indent} #{comment.chomp.strip}
-EOF
- end
-
- if modified_params
- if defitem.include_attr?("optional")
- params << "#{comma}[#{arg}]"
- else
- params << "#{comma}#{arg}"
- end
- comma = ", "
- end
- end
- }
- }
- if modified_params
- return args_rdocforms, params
- else
- return args_rdocforms
- end
- end
-
- ##
- # Return comments of definitions of namelists
-
- def find_namelists(text, before_contains=nil)
- return nil if !text
- result = ""
- lines = "#{text}"
- before_contains = "" if !before_contains
- while lines =~ /^\s*?namelist\s+\/\s*?(\w+)\s*?\/([\s\w\,]+)$/i
- lines = $~.post_match
- nml_comment = COMMENTS_ARE_UPPER ?
- find_comments($~.pre_match) : find_comments($~.post_match)
- nml_name = $1
- nml_args = $2.split(",")
- result << "\n\n=== NAMELIST <tt><b>" + nml_name + "</tt></b>\n\n"
- result << nml_comment + "\n" if nml_comment
- if lines.split("\n")[0] =~ /^\//i
- lines = "namelist " + lines
- end
- result << find_arguments(nml_args, "#{text}" + "\n" + before_contains)
- end
- return result
- end
-
- ##
- # Comments just after module or subprogram, or arguments are returned. If
- # "COMMENTS_ARE_UPPER" is true, comments just before modules or subprograms
- # are returnd
-
- def find_comments text
- return "" unless text
- lines = text.split("\n")
- lines.reverse! if COMMENTS_ARE_UPPER
- comment_block = Array.new
- lines.each do |line|
- break if line =~ /^\s*?\w/ || line =~ /^\s*?$/
- if COMMENTS_ARE_UPPER
- comment_block.unshift line.sub(/^\s*?!\s?/,"")
- else
- comment_block.push line.sub(/^\s*?!\s?/,"")
- end
- end
- nice_lines = comment_block.join("\n").split "\n\s*?\n"
- nice_lines[0] ||= ""
- nice_lines.shift
- end
-
- ##
- # Create method for internal alias
-
- def initialize_public_method(method, parent)
- return if !method || !parent
-
- new_meth = AnyMethod.new("External Alias for module", method.name)
- new_meth.singleton = method.singleton
- new_meth.params = method.params.clone
- new_meth.comment = remove_trailing_alias(method.comment.clone)
- new_meth.comment << "\n\n#{EXTERNAL_ALIAS_MES} #{parent.strip.chomp}\##{method.name}"
-
- return new_meth
- end
-
- ##
- # Create method for external alias
- #
- # If argument "internal" is true, file is ignored.
-
- def initialize_external_method(new, old, params, file, comment, token=nil,
- internal=nil, nolink=nil)
- return nil unless new || old
-
- if internal
- external_alias_header = "#{INTERNAL_ALIAS_MES} "
- external_alias_text = external_alias_header + old
- elsif file
- external_alias_header = "#{EXTERNAL_ALIAS_MES} "
- external_alias_text = external_alias_header + file + "#" + old
- else
- return nil
- end
- external_meth = AnyMethod.new(external_alias_text, new)
- external_meth.singleton = false
- external_meth.params = params
- external_comment = remove_trailing_alias(comment) + "\n\n" if comment
- external_meth.comment = external_comment || ""
- if nolink && token
- external_meth.start_collecting_tokens
- external_meth.add_token Token.new(1,1).set_text(token)
- else
- external_meth.comment << external_alias_text
- end
-
- return external_meth
- end
-
- ##
- # Parse visibility
-
- def parse_visibility(code, default, container)
- result = []
- visibility_default = default || :public
-
- used_modules = []
- container.includes.each{|i| used_modules << i.name} if container
-
- remaining_code = code.gsub(/^\s*?type[\s\,]+.*?\s+end\s+type.*?$/im, "")
- remaining_code.split("\n").each{ |line|
- if /^\s*?private\s*?$/ =~ line
- visibility_default = :private
- break
- end
- } if remaining_code
-
- remaining_code.split("\n").each{ |line|
- if /^\s*?private\s*?(::)?\s+(.*)\s*?(!.*?)?/i =~ line
- methods = $2.sub(/!.*$/, '')
- methods.split(",").each{ |meth|
- meth.sub!(/!.*$/, '')
- meth.gsub!(/:/, '')
- result << {
- "name" => meth.chomp.strip,
- "visibility" => :private,
- "used_modules" => used_modules.clone,
- "file_or_module" => container,
- "entity_is_discovered" => nil,
- "local_name" => nil
- }
- }
- elsif /^\s*?public\s*?(::)?\s+(.*)\s*?(!.*?)?/i =~ line
- methods = $2.sub(/!.*$/, '')
- methods.split(",").each{ |meth|
- meth.sub!(/!.*$/, '')
- meth.gsub!(/:/, '')
- result << {
- "name" => meth.chomp.strip,
- "visibility" => :public,
- "used_modules" => used_modules.clone,
- "file_or_module" => container,
- "entity_is_discovered" => nil,
- "local_name" => nil
- }
- }
- end
- } if remaining_code
-
- if container
- result.each{ |vis_info|
- vis_info["parent"] = container.name
- }
- end
-
- return visibility_default, result
- end
-
- ##
- # Set visibility
- #
- # "subname" element of "visibility_info" is deleted.
-
- def set_visibility(container, subname, visibility_default, visibility_info)
- return unless container || subname || visibility_default || visibility_info
- not_found = true
- visibility_info.collect!{ |info|
- if info["name"] == subname ||
- @options.ignore_case && info["name"].upcase == subname.upcase
- if info["file_or_module"].name == container.name
- container.set_visibility_for([subname], info["visibility"])
- info["entity_is_discovered"] = true
- not_found = false
- end
- end
- info
- }
- if not_found
- return container.set_visibility_for([subname], visibility_default)
- else
- return container
- end
- end
-
- ##
- # Find visibility
-
- def find_visibility(container, subname, visibility_info)
- return nil if !subname || !visibility_info
- visibility_info.each{ |info|
- if info["name"] == subname ||
- @options.ignore_case && info["name"].upcase == subname.upcase
- if info["parent"] == container.name
- return info["visibility"]
- end
- end
- }
- return nil
- end
-
- ##
- # Check external aliases
-
- def check_external_aliases(subname, params, comment, test=nil)
- @@external_aliases.each{ |alias_item|
- if subname == alias_item["old_name"] ||
- subname.upcase == alias_item["old_name"].upcase &&
- @options.ignore_case
-
- new_meth = initialize_external_method(alias_item["new_name"],
- subname, params, @file_name,
- comment)
- new_meth.visibility = alias_item["visibility"]
-
- @stats.add_method new_meth
-
- alias_item["file_or_module"].add_method(new_meth)
-
- if !alias_item["file_or_module"].include_requires?(@file_name, @options.ignore_case)
- alias_item["file_or_module"].add_require(Require.new(@file_name, ""))
- end
- end
- }
- end
-
- ##
- # Check public_methods
-
- def check_public_methods(method, parent)
- return if !method || !parent
- @@public_methods.each{ |alias_item|
- parent_is_used_module = nil
- alias_item["used_modules"].each{ |used_module|
- if used_module == parent ||
- used_module.upcase == parent.upcase &&
- @options.ignore_case
- parent_is_used_module = true
- end
- }
- next if !parent_is_used_module
-
- if method.name == alias_item["name"] ||
- method.name.upcase == alias_item["name"].upcase &&
- @options.ignore_case
-
- new_meth = initialize_public_method(method, parent)
- if alias_item["local_name"]
- new_meth.name = alias_item["local_name"]
- end
-
- @stats.add_method new_meth
-
- alias_item["file_or_module"].add_method new_meth
- end
- }
- end
-
- ##
- # Continuous lines are united.
- #
- # Comments in continuous lines are removed.
-
- def united_to_one_line(f90src)
- return "" unless f90src
- lines = f90src.split("\n")
- previous_continuing = false
- now_continuing = false
- body = ""
- lines.each{ |line|
- words = line.split("")
- next if words.empty? && previous_continuing
- commentout = false
- brank_flag = true ; brank_char = ""
- squote = false ; dquote = false
- ignore = false
- words.collect! { |char|
- if previous_continuing && brank_flag
- now_continuing = true
- ignore = true
- case char
- when "!" ; break
- when " " ; brank_char << char ; next ""
- when "&"
- brank_flag = false
- now_continuing = false
- next ""
- else
- brank_flag = false
- now_continuing = false
- ignore = false
- next brank_char + char
- end
- end
- ignore = false
-
- if now_continuing
- next ""
- elsif !(squote) && !(dquote) && !(commentout)
- case char
- when "!" ; commentout = true ; next char
- when "\""; dquote = true ; next char
- when "\'"; squote = true ; next char
- when "&" ; now_continuing = true ; next ""
- else next char
- end
- elsif commentout
- next char
- elsif squote
- case char
- when "\'"; squote = false ; next char
- else next char
- end
- elsif dquote
- case char
- when "\""; dquote = false ; next char
- else next char
- end
- end
- }
- if !ignore && !previous_continuing || !brank_flag
- if previous_continuing
- body << words.join("")
- else
- body << "\n" + words.join("")
- end
- end
- previous_continuing = now_continuing ? true : nil
- now_continuing = nil
- }
- return body
- end
-
-
- ##
- # Continuous line checker
-
- def continuous_line?(line)
- continuous = false
- if /&\s*?(!.*)?$/ =~ line
- continuous = true
- if comment_out?($~.pre_match)
- continuous = false
- end
- end
- return continuous
- end
-
- ##
- # Comment out checker
-
- def comment_out?(line)
- return nil unless line
- commentout = false
- squote = false ; dquote = false
- line.split("").each { |char|
- if !(squote) && !(dquote)
- case char
- when "!" ; commentout = true ; break
- when "\""; dquote = true
- when "\'"; squote = true
- else next
- end
- elsif squote
- case char
- when "\'"; squote = false
- else next
- end
- elsif dquote
- case char
- when "\""; dquote = false
- else next
- end
- end
- }
- return commentout
- end
-
- ##
- # Semicolons are replaced to line feed.
-
- def semicolon_to_linefeed(text)
- return "" unless text
- lines = text.split("\n")
- lines.collect!{ |line|
- words = line.split("")
- commentout = false
- squote = false ; dquote = false
- words.collect! { |char|
- if !(squote) && !(dquote) && !(commentout)
- case char
- when "!" ; commentout = true ; next char
- when "\""; dquote = true ; next char
- when "\'"; squote = true ; next char
- when ";" ; "\n"
- else next char
- end
- elsif commentout
- next char
- elsif squote
- case char
- when "\'"; squote = false ; next char
- else next char
- end
- elsif dquote
- case char
- when "\""; dquote = false ; next char
- else next char
- end
- end
- }
- words.join("")
- }
- return lines.join("\n")
- end
-
- ##
- # Which "line" is start of block (module, program, block data, subroutine,
- # function) statement ?
-
- def block_start?(line)
- return nil if !line
-
- if line =~ /^\s*?module\s+(\w+)\s*?(!.*?)?$/i ||
- line =~ /^\s*?program\s+(\w+)\s*?(!.*?)?$/i ||
- line =~ /^\s*?block\s+data(\s+\w+)?\s*?(!.*?)?$/i ||
- line =~ \
- /^\s*?
- (recursive|pure|elemental)?\s*?
- subroutine\s+(\w+)\s*?(\(.*?\))?\s*?(!.*?)?$
- /ix ||
- line =~ \
- /^\s*?
- (recursive|pure|elemental)?\s*?
- (
- character\s*?(\([\w\s\=\(\)\*]+?\))?\s+
- | type\s*?\([\w\s]+?\)\s+
- | integer\s*?(\([\w\s\=\(\)\*]+?\))?\s+
- | real\s*?(\([\w\s\=\(\)\*]+?\))?\s+
- | double\s+precision\s+
- | logical\s*?(\([\w\s\=\(\)\*]+?\))?\s+
- | complex\s*?(\([\w\s\=\(\)\*]+?\))?\s+
- )?
- function\s+(\w+)\s*?
- (\(.*?\))?(\s+result\((.*?)\))?\s*?(!.*?)?$
- /ix
- return true
- end
-
- return nil
- end
-
- ##
- # Which "line" is end of block (module, program, block data, subroutine,
- # function) statement ?
-
- def block_end?(line)
- return nil if !line
-
- if line =~ /^\s*?end\s*?(!.*?)?$/i ||
- line =~ /^\s*?end\s+module(\s+\w+)?\s*?(!.*?)?$/i ||
- line =~ /^\s*?end\s+program(\s+\w+)?\s*?(!.*?)?$/i ||
- line =~ /^\s*?end\s+block\s+data(\s+\w+)?\s*?(!.*?)?$/i ||
- line =~ /^\s*?end\s+subroutine(\s+\w+)?\s*?(!.*?)?$/i ||
- line =~ /^\s*?end\s+function(\s+\w+)?\s*?(!.*?)?$/i
- return true
- end
-
- return nil
- end
-
- ##
- # Remove "Alias for" in end of comments
-
- def remove_trailing_alias(text)
- return "" if !text
- lines = text.split("\n").reverse
- comment_block = Array.new
- checked = false
- lines.each do |line|
- if !checked
- if /^\s?#{INTERNAL_ALIAS_MES}/ =~ line ||
- /^\s?#{EXTERNAL_ALIAS_MES}/ =~ line
- checked = true
- next
- end
- end
- comment_block.unshift line
- end
- nice_lines = comment_block.join("\n")
- nice_lines ||= ""
- return nice_lines
- end
-
- ##
- # Empty lines in header are removed
-
- def remove_empty_head_lines(text)
- return "" unless text
- lines = text.split("\n")
- header = true
- lines.delete_if{ |line|
- header = false if /\S/ =~ line
- header && /^\s*?$/ =~ line
- }
- lines.join("\n")
- end
-
- ##
- # header marker "=", "==", ... are removed
-
- def remove_header_marker(text)
- return text.gsub(/^\s?(=+)/, '<tt></tt>\1')
- end
-
- def remove_private_comments(body)
- body.gsub!(/^\s*!--\s*?$.*?^\s*!\+\+\s*?$/m, '')
- return body
- end
-
- ##
- # Information of arguments of subroutines and functions in Fortran95
-
- class Fortran95Definition
-
- # Name of variable
- #
- attr_reader :varname
-
- # Types of variable
- #
- attr_reader :types
-
- # Initial Value
- #
- attr_reader :inivalue
-
- # Suffix of array
- #
- attr_reader :arraysuffix
-
- # Comments
- #
- attr_accessor :comment
-
- # Flag of non documentation
- #
- attr_accessor :nodoc
-
- def initialize(varname, types, inivalue, arraysuffix, comment,
- nodoc=false)
- @varname = varname
- @types = types
- @inivalue = inivalue
- @arraysuffix = arraysuffix
- @comment = comment
- @nodoc = nodoc
- end
-
- def to_s
- return <<-EOF
-<Fortran95Definition:
-varname=#{@varname}, types=#{types},
-inivalue=#{@inivalue}, arraysuffix=#{@arraysuffix}, nodoc=#{@nodoc},
-comment=
-#{@comment}
->
-EOF
- end
-
- #
- # If attr is included, true is returned
- #
- def include_attr?(attr)
- return if !attr
- @types.split(",").each{ |type|
- return true if type.strip.chomp.upcase == attr.strip.chomp.upcase
- }
- return nil
- end
-
- end # End of Fortran95Definition
-
- ##
- # Parse string argument "text", and Return Array of Fortran95Definition
- # object
-
- def definition_info(text)
- return nil unless text
- lines = "#{text}"
- defs = Array.new
- comment = ""
- trailing_comment = ""
- under_comment_valid = false
- lines.split("\n").each{ |line|
- if /^\s*?!\s?(.*)/ =~ line
- if COMMENTS_ARE_UPPER
- comment << remove_header_marker($1)
- comment << "\n"
- elsif defs[-1] && under_comment_valid
- defs[-1].comment << "\n"
- defs[-1].comment << remove_header_marker($1)
- end
- next
- elsif /^\s*?$/ =~ line
- comment = ""
- under_comment_valid = false
- next
- end
- type = ""
- characters = ""
- if line =~ /^\s*?
- (
- character\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]*
- | type\s*?\([\w\s]+?\)[\s\,]*
- | integer\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]*
- | real\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]*
- | double\s+precision[\s\,]*
- | logical\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]*
- | complex\s*?(\([\w\s\=\(\)\*]+?\))?[\s\,]*
- )
- (.*?::)?
- (.+)$
- /ix
- characters = $8
- type = $1
- type << $7.gsub(/::/, '').gsub(/^\s*?\,/, '') if $7
- else
- under_comment_valid = false
- next
- end
- squote = false ; dquote = false ; bracket = 0
- iniflag = false; commentflag = false
- varname = "" ; arraysuffix = "" ; inivalue = ""
- start_pos = defs.size
- characters.split("").each { |char|
- if !(squote) && !(dquote) && bracket <= 0 && !(iniflag) && !(commentflag)
- case char
- when "!" ; commentflag = true
- when "(" ; bracket += 1 ; arraysuffix = char
- when "\""; dquote = true
- when "\'"; squote = true
- when "=" ; iniflag = true ; inivalue << char
- when ","
- defs << Fortran95Definition.new(varname, type, inivalue, arraysuffix, comment)
- varname = "" ; arraysuffix = "" ; inivalue = ""
- under_comment_valid = true
- when " " ; next
- else ; varname << char
- end
- elsif commentflag
- comment << remove_header_marker(char)
- trailing_comment << remove_header_marker(char)
- elsif iniflag
- if dquote
- case char
- when "\"" ; dquote = false ; inivalue << char
- else ; inivalue << char
- end
- elsif squote
- case char
- when "\'" ; squote = false ; inivalue << char
- else ; inivalue << char
- end
- elsif bracket > 0
- case char
- when "(" ; bracket += 1 ; inivalue << char
- when ")" ; bracket -= 1 ; inivalue << char
- else ; inivalue << char
- end
- else
- case char
- when ","
- defs << Fortran95Definition.new(varname, type, inivalue, arraysuffix, comment)
- varname = "" ; arraysuffix = "" ; inivalue = ""
- iniflag = false
- under_comment_valid = true
- when "(" ; bracket += 1 ; inivalue << char
- when "\""; dquote = true ; inivalue << char
- when "\'"; squote = true ; inivalue << char
- when "!" ; commentflag = true
- else ; inivalue << char
- end
- end
- elsif !(squote) && !(dquote) && bracket > 0
- case char
- when "(" ; bracket += 1 ; arraysuffix << char
- when ")" ; bracket -= 1 ; arraysuffix << char
- else ; arraysuffix << char
- end
- elsif squote
- case char
- when "\'"; squote = false ; inivalue << char
- else ; inivalue << char
- end
- elsif dquote
- case char
- when "\""; dquote = false ; inivalue << char
- else ; inivalue << char
- end
- end
- }
- defs << Fortran95Definition.new(varname, type, inivalue, arraysuffix, comment)
- if trailing_comment =~ /^:nodoc:/
- defs[start_pos..-1].collect!{ |defitem|
- defitem.nodoc = true
- }
- end
- varname = "" ; arraysuffix = "" ; inivalue = ""
- comment = ""
- under_comment_valid = true
- trailing_comment = ""
- }
- return defs
- end
-
-end
-
diff --git a/lib/rdoc/parser/perl.rb b/lib/rdoc/parser/perl.rb
index 0023a013a6..43d1e9ff69 100644
--- a/lib/rdoc/parser/perl.rb
+++ b/lib/rdoc/parser/perl.rb
@@ -15,7 +15,7 @@ require 'rdoc/parser'
#
# 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.
+# I don't think I can do that: time constraints.
#
class RDoc::Parser::PerlPOD < RDoc::Parser
@@ -45,39 +45,39 @@ class RDoc::Parser::PerlPOD < RDoc::Parser
# 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,
+ # 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
+ 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
+ code_noop = lambda do
if line =~ /^\s+$/
state = :code_blank
end
end
- pod_noop = lambda do
+ pod_noop = lambda do
if line =~ /^\s+$/
state = :pod_blank
end
@top_level.comment += filter(line)
end
- begin_noop = lambda do
+ begin_noop = lambda do
if line =~ /^\s+$/
state = :begin_blank
end
@@ -151,7 +151,7 @@ class RDoc::Parser::PerlPOD < RDoc::Parser
def filter(comment)
return '' if comment =~ /^=pod\s*$/
comment.gsub!(/^=pod/, '==')
- comment.gsub!(/^=head(\d+)/) do
+ comment.gsub!(/^=head(\d+)/) do
"=" * $1.to_i
end
comment.gsub!(/=item/, '');
diff --git a/lib/rdoc/parser/ruby.rb b/lib/rdoc/parser/ruby.rb
index 1697ab85aa..f78dcf07d6 100644
--- a/lib/rdoc/parser/ruby.rb
+++ b/lib/rdoc/parser/ruby.rb
@@ -7,1339 +7,16 @@
# by Keiju ISHITSUKA (Nippon Rational Inc.)
#
-require 'e2mmap'
-require 'irb/slex'
+require 'rdoc/ruby_token'
+require 'rdoc/ruby_lex'
require 'rdoc/code_objects'
require 'rdoc/tokenstream'
require 'rdoc/markup/preprocess'
require 'rdoc/parser'
+require 'rdoc/parser/ruby_tools'
$TOKEN_DEBUG ||= nil
-#$TOKEN_DEBUG = $DEBUG_RDOC
-
-##
-# Definitions of all tokens involved in the lexical analysis
-
-module RDoc::RubyToken
-
- EXPR_BEG = :EXPR_BEG
- EXPR_MID = :EXPR_MID
- EXPR_END = :EXPR_END
- EXPR_ARG = :EXPR_ARG
- EXPR_FNAME = :EXPR_FNAME
- EXPR_DOT = :EXPR_DOT
- EXPR_CLASS = :EXPR_CLASS
-
- class Token
- NO_TEXT = "??".freeze
-
- attr_accessor :text
- attr_reader :line_no
- attr_reader :char_no
-
- def initialize(line_no, char_no)
- @line_no = line_no
- @char_no = char_no
- @text = NO_TEXT
- end
-
- def ==(other)
- self.class == other.class and
- other.line_no == @line_no and
- other.char_no == @char_no and
- other.text == @text
- end
-
- ##
- # Because we're used in contexts that expect to return a token, we set the
- # text string and then return ourselves
-
- def set_text(text)
- @text = text
- self
- end
-
- end
-
- class TkNode < Token
- attr :node
- end
-
- class TkId < Token
- def initialize(line_no, char_no, name)
- super(line_no, char_no)
- @name = name
- end
- attr :name
- end
-
- class TkKW < TkId
- end
-
- class TkVal < Token
- def initialize(line_no, char_no, value = nil)
- super(line_no, char_no)
- set_text(value)
- end
- end
-
- class TkOp < Token
- def name
- self.class.op_name
- end
- end
-
- class TkOPASGN < TkOp
- def initialize(line_no, char_no, op)
- super(line_no, char_no)
- op = TkReading2Token[op] unless Symbol === op
- @op = op
- end
- attr :op
- end
-
- class TkUnknownChar < Token
- def initialize(line_no, char_no, id)
- super(line_no, char_no)
- @name = char_no.chr
- end
- attr :name
- end
-
- class TkError < Token
- end
-
- def set_token_position(line, char)
- @prev_line_no = line
- @prev_char_no = char
- end
-
- def Token(token, value = nil)
- tk = nil
- case token
- when String, Symbol
- source = String === token ? TkReading2Token : TkSymbol2Token
- raise TkReading2TokenNoKey, token if (tk = source[token]).nil?
- tk = Token(tk[0], value)
- else
- tk = if (token.ancestors & [TkId, TkVal, TkOPASGN, TkUnknownChar]).empty?
- token.new(@prev_line_no, @prev_char_no)
- else
- token.new(@prev_line_no, @prev_char_no, value)
- end
- end
- tk
- end
-
- TokenDefinitions = [
- [:TkCLASS, TkKW, "class", EXPR_CLASS],
- [:TkMODULE, TkKW, "module", EXPR_CLASS],
- [:TkDEF, TkKW, "def", EXPR_FNAME],
- [:TkUNDEF, TkKW, "undef", EXPR_FNAME],
- [:TkBEGIN, TkKW, "begin", EXPR_BEG],
- [:TkRESCUE, TkKW, "rescue", EXPR_MID],
- [:TkENSURE, TkKW, "ensure", EXPR_BEG],
- [:TkEND, TkKW, "end", EXPR_END],
- [:TkIF, TkKW, "if", EXPR_BEG, :TkIF_MOD],
- [:TkUNLESS, TkKW, "unless", EXPR_BEG, :TkUNLESS_MOD],
- [:TkTHEN, TkKW, "then", EXPR_BEG],
- [:TkELSIF, TkKW, "elsif", EXPR_BEG],
- [:TkELSE, TkKW, "else", EXPR_BEG],
- [:TkCASE, TkKW, "case", EXPR_BEG],
- [:TkWHEN, TkKW, "when", EXPR_BEG],
- [:TkWHILE, TkKW, "while", EXPR_BEG, :TkWHILE_MOD],
- [:TkUNTIL, TkKW, "until", EXPR_BEG, :TkUNTIL_MOD],
- [:TkFOR, TkKW, "for", EXPR_BEG],
- [:TkBREAK, TkKW, "break", EXPR_END],
- [:TkNEXT, TkKW, "next", EXPR_END],
- [:TkREDO, TkKW, "redo", EXPR_END],
- [:TkRETRY, TkKW, "retry", EXPR_END],
- [:TkIN, TkKW, "in", EXPR_BEG],
- [:TkDO, TkKW, "do", EXPR_BEG],
- [:TkRETURN, TkKW, "return", EXPR_MID],
- [:TkYIELD, TkKW, "yield", EXPR_END],
- [:TkSUPER, TkKW, "super", EXPR_END],
- [:TkSELF, TkKW, "self", EXPR_END],
- [:TkNIL, TkKW, "nil", EXPR_END],
- [:TkTRUE, TkKW, "true", EXPR_END],
- [:TkFALSE, TkKW, "false", EXPR_END],
- [:TkAND, TkKW, "and", EXPR_BEG],
- [:TkOR, TkKW, "or", EXPR_BEG],
- [:TkNOT, TkKW, "not", EXPR_BEG],
- [:TkIF_MOD, TkKW],
- [:TkUNLESS_MOD, TkKW],
- [:TkWHILE_MOD, TkKW],
- [:TkUNTIL_MOD, TkKW],
- [:TkALIAS, TkKW, "alias", EXPR_FNAME],
- [:TkDEFINED, TkKW, "defined?", EXPR_END],
- [:TklBEGIN, TkKW, "BEGIN", EXPR_END],
- [:TklEND, TkKW, "END", EXPR_END],
- [:Tk__LINE__, TkKW, "__LINE__", EXPR_END],
- [:Tk__FILE__, TkKW, "__FILE__", EXPR_END],
-
- [:TkIDENTIFIER, TkId],
- [:TkFID, TkId],
- [:TkGVAR, TkId],
- [:TkIVAR, TkId],
- [:TkCONSTANT, TkId],
-
- [:TkINTEGER, TkVal],
- [:TkFLOAT, TkVal],
- [:TkSTRING, TkVal],
- [:TkXSTRING, TkVal],
- [:TkREGEXP, TkVal],
- [:TkCOMMENT, TkVal],
-
- [:TkDSTRING, TkNode],
- [:TkDXSTRING, TkNode],
- [:TkDREGEXP, TkNode],
- [:TkNTH_REF, TkId],
- [:TkBACK_REF, TkId],
-
- [:TkUPLUS, TkOp, "+@"],
- [:TkUMINUS, TkOp, "-@"],
- [:TkPOW, TkOp, "**"],
- [:TkCMP, TkOp, "<=>"],
- [:TkEQ, TkOp, "=="],
- [:TkEQQ, TkOp, "==="],
- [:TkNEQ, TkOp, "!="],
- [:TkGEQ, TkOp, ">="],
- [:TkLEQ, TkOp, "<="],
- [:TkANDOP, TkOp, "&&"],
- [:TkOROP, TkOp, "||"],
- [:TkMATCH, TkOp, "=~"],
- [:TkNMATCH, TkOp, "!~"],
- [:TkDOT2, TkOp, ".."],
- [:TkDOT3, TkOp, "..."],
- [:TkAREF, TkOp, "[]"],
- [:TkASET, TkOp, "[]="],
- [:TkLSHFT, TkOp, "<<"],
- [:TkRSHFT, TkOp, ">>"],
- [:TkCOLON2, TkOp],
- [:TkCOLON3, TkOp],
-# [:OPASGN, TkOp], # +=, -= etc. #
- [:TkASSOC, TkOp, "=>"],
- [:TkQUESTION, TkOp, "?"], #?
- [:TkCOLON, TkOp, ":"], #:
-
- [:TkfLPAREN], # func( #
- [:TkfLBRACK], # func[ #
- [:TkfLBRACE], # func{ #
- [:TkSTAR], # *arg
- [:TkAMPER], # &arg #
- [:TkSYMBOL, TkId], # :SYMBOL
- [:TkSYMBEG, TkId],
- [:TkGT, TkOp, ">"],
- [:TkLT, TkOp, "<"],
- [:TkPLUS, TkOp, "+"],
- [:TkMINUS, TkOp, "-"],
- [:TkMULT, TkOp, "*"],
- [:TkDIV, TkOp, "/"],
- [:TkMOD, TkOp, "%"],
- [:TkBITOR, TkOp, "|"],
- [:TkBITXOR, TkOp, "^"],
- [:TkBITAND, TkOp, "&"],
- [:TkBITNOT, TkOp, "~"],
- [:TkNOTOP, TkOp, "!"],
-
- [:TkBACKQUOTE, TkOp, "`"],
-
- [:TkASSIGN, Token, "="],
- [:TkDOT, Token, "."],
- [:TkLPAREN, Token, "("], #(exp)
- [:TkLBRACK, Token, "["], #[arry]
- [:TkLBRACE, Token, "{"], #{hash}
- [:TkRPAREN, Token, ")"],
- [:TkRBRACK, Token, "]"],
- [:TkRBRACE, Token, "}"],
- [:TkCOMMA, Token, ","],
- [:TkSEMICOLON, Token, ";"],
-
- [:TkRD_COMMENT],
- [:TkSPACE],
- [:TkNL],
- [:TkEND_OF_SCRIPT],
-
- [:TkBACKSLASH, TkUnknownChar, "\\"],
- [:TkAT, TkUnknownChar, "@"],
- [:TkDOLLAR, TkUnknownChar, "\$"], #"
- ]
-
- # {reading => token_class}
- # {reading => [token_class, *opt]}
- TkReading2Token = {}
- TkSymbol2Token = {}
-
- def self.def_token(token_n, super_token = Token, reading = nil, *opts)
- token_n = token_n.id2name unless String === token_n
-
- fail AlreadyDefinedToken, token_n if const_defined?(token_n)
-
- token_c = Class.new super_token
- const_set token_n, token_c
-# token_c.inspect
-
- if reading
- if TkReading2Token[reading]
- fail TkReading2TokenDuplicateError, token_n, reading
- end
- if opts.empty?
- TkReading2Token[reading] = [token_c]
- else
- TkReading2Token[reading] = [token_c].concat(opts)
- end
- end
- TkSymbol2Token[token_n.intern] = token_c
-
- if token_c <= TkOp
- token_c.class_eval %{
- def self.op_name; "#{reading}"; end
- }
- end
- end
-
- for defs in TokenDefinitions
- def_token(*defs)
- end
-
- NEWLINE_TOKEN = TkNL.new(0,0)
- NEWLINE_TOKEN.set_text("\n")
-
-end
-
-##
-# Lexical analyzer for Ruby source
-
-class RDoc::RubyLex
-
- ##
- # Read an input stream character by character. We allow for unlimited
- # ungetting of characters just read.
- #
- # We simplify the implementation greatly by reading the entire input
- # into a buffer initially, and then simply traversing it using
- # pointers.
- #
- # We also have to allow for the <i>here document diversion</i>. This
- # little gem comes about when the lexer encounters a here
- # document. At this point we effectively need to split the input
- # stream into two parts: one to read the body of the here document,
- # the other to read the rest of the input line where the here
- # document was initially encountered. For example, we might have
- #
- # do_something(<<-A, <<-B)
- # stuff
- # for
- # A
- # stuff
- # for
- # B
- #
- # When the lexer encounters the <<A, it reads until the end of the
- # line, and keeps it around for later. It then reads the body of the
- # here document. Once complete, it needs to read the rest of the
- # original line, but then skip the here document body.
- #
-
- class BufferedReader
-
- attr_reader :line_num
-
- def initialize(content, options)
- @options = options
-
- if /\t/ =~ content
- tab_width = @options.tab_width
- content = content.split(/\n/).map do |line|
- 1 while line.gsub!(/\t+/) { ' ' * (tab_width*$&.length - $`.length % tab_width)} && $~ #`
- line
- end .join("\n")
- end
- @content = content
- @content << "\n" unless @content[-1,1] == "\n"
- @size = @content.size
- @offset = 0
- @hwm = 0
- @line_num = 1
- @read_back_offset = 0
- @last_newline = 0
- @newline_pending = false
- end
-
- def column
- @offset - @last_newline
- end
-
- def getc
- return nil if @offset >= @size
- ch = @content[@offset, 1]
-
- @offset += 1
- @hwm = @offset if @hwm < @offset
-
- if @newline_pending
- @line_num += 1
- @last_newline = @offset - 1
- @newline_pending = false
- end
-
- if ch == "\n"
- @newline_pending = true
- end
- ch
- end
-
- def getc_already_read
- getc
- end
-
- def ungetc(ch)
- raise "unget past beginning of file" if @offset <= 0
- @offset -= 1
- if @content[@offset] == ?\n
- @newline_pending = false
- end
- end
-
- def get_read
- res = @content[@read_back_offset...@offset]
- @read_back_offset = @offset
- res
- end
-
- def peek(at)
- pos = @offset + at
- if pos >= @size
- nil
- else
- @content[pos, 1]
- end
- end
-
- def peek_equal(str)
- @content[@offset, str.length] == str
- end
-
- def divert_read_from(reserve)
- @content[@offset, 0] = reserve
- @size = @content.size
- end
- end
-
- # end of nested class BufferedReader
-
- extend Exception2MessageMapper
- def_exception(:AlreadyDefinedToken, "Already defined token(%s)")
- def_exception(:TkReading2TokenNoKey, "key nothing(key='%s')")
- def_exception(:TkSymbol2TokenNoKey, "key nothing(key='%s')")
- def_exception(:TkReading2TokenDuplicateError,
- "key duplicate(token_n='%s', key='%s')")
- def_exception(:SyntaxError, "%s")
-
- include RDoc::RubyToken
- include IRB
-
- attr_reader :continue
- attr_reader :lex_state
-
- def self.debug?
- false
- end
-
- def initialize(content, options)
- lex_init
-
- @options = options
-
- @reader = BufferedReader.new content, @options
-
- @exp_line_no = @line_no = 1
- @base_char_no = 0
- @indent = 0
-
- @ltype = nil
- @quoted = nil
- @lex_state = EXPR_BEG
- @space_seen = false
-
- @continue = false
- @line = ""
-
- @skip_space = false
- @read_auto_clean_up = false
- @exception_on_syntax_error = true
- end
-
- attr_accessor :skip_space
- attr_accessor :read_auto_clean_up
- attr_accessor :exception_on_syntax_error
- attr_reader :indent
-
- # io functions
- def line_no
- @reader.line_num
- end
-
- def char_no
- @reader.column
- end
-
- def get_read
- @reader.get_read
- end
-
- def getc
- @reader.getc
- end
-
- def getc_of_rests
- @reader.getc_already_read
- end
-
- def gets
- c = getc or return
- l = ""
- begin
- l.concat c unless c == "\r"
- break if c == "\n"
- end while c = getc
- l
- end
-
-
- def ungetc(c = nil)
- @reader.ungetc(c)
- end
-
- def peek_equal?(str)
- @reader.peek_equal(str)
- end
-
- def peek(i = 0)
- @reader.peek(i)
- end
-
- def lex
- until (TkNL === (tk = token) or TkEND_OF_SCRIPT === tk) and
- not @continue or tk.nil?
- end
-
- line = get_read
-
- if line == "" and TkEND_OF_SCRIPT === tk or tk.nil? then
- nil
- else
- line
- end
- end
-
- def token
- set_token_position(line_no, char_no)
- begin
- begin
- tk = @OP.match(self)
- @space_seen = TkSPACE === tk
- rescue SyntaxError => e
- raise RDoc::Error, "syntax error: #{e.message}" if
- @exception_on_syntax_error
-
- tk = TkError.new(line_no, char_no)
- end
- end while @skip_space and TkSPACE === tk
- if @read_auto_clean_up
- get_read
- end
-# throw :eof unless tk
- tk
- end
-
- ENINDENT_CLAUSE = [
- "case", "class", "def", "do", "for", "if",
- "module", "unless", "until", "while", "begin" #, "when"
- ]
- DEINDENT_CLAUSE = ["end" #, "when"
- ]
-
- PERCENT_LTYPE = {
- "q" => "\'",
- "Q" => "\"",
- "x" => "\`",
- "r" => "/",
- "w" => "]"
- }
-
- PERCENT_PAREN = {
- "{" => "}",
- "[" => "]",
- "<" => ">",
- "(" => ")"
- }
-
- Ltype2Token = {
- "\'" => TkSTRING,
- "\"" => TkSTRING,
- "\`" => TkXSTRING,
- "/" => TkREGEXP,
- "]" => TkDSTRING
- }
- Ltype2Token.default = TkSTRING
-
- DLtype2Token = {
- "\"" => TkDSTRING,
- "\`" => TkDXSTRING,
- "/" => TkDREGEXP,
- }
-
- def lex_init()
- @OP = IRB::SLex.new
- @OP.def_rules("\0", "\004", "\032") do |chars, io|
- Token(TkEND_OF_SCRIPT).set_text(chars)
- end
-
- @OP.def_rules(" ", "\t", "\f", "\r", "\13") do |chars, io|
- @space_seen = TRUE
- while (ch = getc) =~ /[ \t\f\r\13]/
- chars << ch
- end
- ungetc
- Token(TkSPACE).set_text(chars)
- end
-
- @OP.def_rule("#") do
- |op, io|
- identify_comment
- end
-
- @OP.def_rule("=begin", proc{@prev_char_no == 0 && peek(0) =~ /\s/}) do
- |op, io|
- str = op
- @ltype = "="
-
-
- begin
- line = ""
- begin
- ch = getc
- line << ch
- end until ch == "\n"
- str << line
- end until line =~ /^=end/
-
- ungetc
-
- @ltype = nil
-
- if str =~ /\A=begin\s+rdoc/i
- str.sub!(/\A=begin.*\n/, '')
- str.sub!(/^=end.*/m, '')
- Token(TkCOMMENT).set_text(str)
- else
- Token(TkRD_COMMENT)#.set_text(str)
- end
- end
-
- @OP.def_rule("\n") do
- print "\\n\n" if RDoc::RubyLex.debug?
- case @lex_state
- when EXPR_BEG, EXPR_FNAME, EXPR_DOT
- @continue = TRUE
- else
- @continue = FALSE
- @lex_state = EXPR_BEG
- end
- Token(TkNL).set_text("\n")
- end
-
- @OP.def_rules("*", "**",
- "!", "!=", "!~",
- "=", "==", "===",
- "=~", "<=>",
- "<", "<=",
- ">", ">=", ">>") do
- |op, io|
- @lex_state = EXPR_BEG
- Token(op).set_text(op)
- end
-
- @OP.def_rules("<<") do
- |op, io|
- tk = nil
- if @lex_state != EXPR_END && @lex_state != EXPR_CLASS &&
- (@lex_state != EXPR_ARG || @space_seen)
- c = peek(0)
- if /[-\w_\"\'\`]/ =~ c
- tk = identify_here_document
- end
- end
- if !tk
- @lex_state = EXPR_BEG
- tk = Token(op).set_text(op)
- end
- tk
- end
-
- @OP.def_rules("'", '"') do
- |op, io|
- identify_string(op)
- end
-
- @OP.def_rules("`") do
- |op, io|
- if @lex_state == EXPR_FNAME
- Token(op).set_text(op)
- else
- identify_string(op)
- end
- end
-
- @OP.def_rules('?') do
- |op, io|
- if @lex_state == EXPR_END
- @lex_state = EXPR_BEG
- Token(TkQUESTION).set_text(op)
- else
- ch = getc
- if @lex_state == EXPR_ARG && ch !~ /\s/
- ungetc
- @lex_state = EXPR_BEG
- Token(TkQUESTION).set_text(op)
- else
- str = op
- str << ch
- if (ch == '\\') #'
- str << read_escape
- end
- @lex_state = EXPR_END
- Token(TkINTEGER).set_text(str)
- end
- end
- end
-
- @OP.def_rules("&", "&&", "|", "||") do
- |op, io|
- @lex_state = EXPR_BEG
- Token(op).set_text(op)
- end
-
- @OP.def_rules("+=", "-=", "*=", "**=",
- "&=", "|=", "^=", "<<=", ">>=", "||=", "&&=") do
- |op, io|
- @lex_state = EXPR_BEG
- op =~ /^(.*)=$/
- Token(TkOPASGN, $1).set_text(op)
- end
-
- @OP.def_rule("+@", proc{@lex_state == EXPR_FNAME}) do |op, io|
- Token(TkUPLUS).set_text(op)
- end
-
- @OP.def_rule("-@", proc{@lex_state == EXPR_FNAME}) do |op, io|
- Token(TkUMINUS).set_text(op)
- end
-
- @OP.def_rules("+", "-") do
- |op, io|
- catch(:RET) do
- if @lex_state == EXPR_ARG
- if @space_seen and peek(0) =~ /[0-9]/
- throw :RET, identify_number(op)
- else
- @lex_state = EXPR_BEG
- end
- elsif @lex_state != EXPR_END and peek(0) =~ /[0-9]/
- throw :RET, identify_number(op)
- else
- @lex_state = EXPR_BEG
- end
- Token(op).set_text(op)
- end
- end
-
- @OP.def_rule(".") do
- @lex_state = EXPR_BEG
- if peek(0) =~ /[0-9]/
- ungetc
- identify_number("")
- else
- # for obj.if
- @lex_state = EXPR_DOT
- Token(TkDOT).set_text(".")
- end
- end
-
- @OP.def_rules("..", "...") do
- |op, io|
- @lex_state = EXPR_BEG
- Token(op).set_text(op)
- end
-
- lex_int2
- end
-
- def lex_int2
- @OP.def_rules("]", "}", ")") do
- |op, io|
- @lex_state = EXPR_END
- @indent -= 1
- Token(op).set_text(op)
- end
-
- @OP.def_rule(":") do
- if @lex_state == EXPR_END || peek(0) =~ /\s/
- @lex_state = EXPR_BEG
- tk = Token(TkCOLON)
- else
- @lex_state = EXPR_FNAME
- tk = Token(TkSYMBEG)
- end
- tk.set_text(":")
- end
-
- @OP.def_rule("::") do
- if @lex_state == EXPR_BEG or @lex_state == EXPR_ARG && @space_seen
- @lex_state = EXPR_BEG
- tk = Token(TkCOLON3)
- else
- @lex_state = EXPR_DOT
- tk = Token(TkCOLON2)
- end
- tk.set_text("::")
- end
-
- @OP.def_rule("/") do
- |op, io|
- if @lex_state == EXPR_BEG || @lex_state == EXPR_MID
- identify_string(op)
- elsif peek(0) == '='
- getc
- @lex_state = EXPR_BEG
- Token(TkOPASGN, :/).set_text("/=") #")
- elsif @lex_state == EXPR_ARG and @space_seen and peek(0) !~ /\s/
- identify_string(op)
- else
- @lex_state = EXPR_BEG
- Token("/").set_text(op)
- end
- end
-
- @OP.def_rules("^") do
- @lex_state = EXPR_BEG
- Token("^").set_text("^")
- end
-
- @OP.def_rules(",", ";") do
- |op, io|
- @lex_state = EXPR_BEG
- Token(op).set_text(op)
- end
-
- @OP.def_rule("~") do
- @lex_state = EXPR_BEG
- Token("~").set_text("~")
- end
-
- @OP.def_rule("~@", proc{@lex_state = EXPR_FNAME}) do
- @lex_state = EXPR_BEG
- Token("~").set_text("~@")
- end
-
- @OP.def_rule("(") do
- @indent += 1
- if @lex_state == EXPR_BEG || @lex_state == EXPR_MID
- @lex_state = EXPR_BEG
- tk = Token(TkfLPAREN)
- else
- @lex_state = EXPR_BEG
- tk = Token(TkLPAREN)
- end
- tk.set_text("(")
- end
-
- @OP.def_rule("[]", proc{@lex_state == EXPR_FNAME}) do
- Token("[]").set_text("[]")
- end
-
- @OP.def_rule("[]=", proc{@lex_state == EXPR_FNAME}) do
- Token("[]=").set_text("[]=")
- end
-
- @OP.def_rule("[") do
- @indent += 1
- if @lex_state == EXPR_FNAME
- t = Token(TkfLBRACK)
- else
- if @lex_state == EXPR_BEG || @lex_state == EXPR_MID
- t = Token(TkLBRACK)
- elsif @lex_state == EXPR_ARG && @space_seen
- t = Token(TkLBRACK)
- else
- t = Token(TkfLBRACK)
- end
- @lex_state = EXPR_BEG
- end
- t.set_text("[")
- end
-
- @OP.def_rule("{") do
- @indent += 1
- if @lex_state != EXPR_END && @lex_state != EXPR_ARG
- t = Token(TkLBRACE)
- else
- t = Token(TkfLBRACE)
- end
- @lex_state = EXPR_BEG
- t.set_text("{")
- end
-
- @OP.def_rule('\\') do #'
- if getc == "\n"
- @space_seen = true
- @continue = true
- Token(TkSPACE).set_text("\\\n")
- else
- ungetc
- Token("\\").set_text("\\") #"
- end
- end
-
- @OP.def_rule('%') do
- |op, io|
- if @lex_state == EXPR_BEG || @lex_state == EXPR_MID
- identify_quotation('%')
- elsif peek(0) == '='
- getc
- Token(TkOPASGN, "%").set_text("%=")
- elsif @lex_state == EXPR_ARG and @space_seen and peek(0) !~ /\s/
- identify_quotation('%')
- else
- @lex_state = EXPR_BEG
- Token("%").set_text("%")
- end
- end
-
- @OP.def_rule('$') do #'
- identify_gvar
- end
-
- @OP.def_rule('@') do
- if peek(0) =~ /[@\w_]/
- ungetc
- identify_identifier
- else
- Token("@").set_text("@")
- end
- end
-
- @OP.def_rule("__END__", proc{@prev_char_no == 0 && peek(0) =~ /[\r\n]/}) do
- throw :eof
- end
-
- @OP.def_rule("") do
- |op, io|
- printf "MATCH: start %s: %s\n", op, io.inspect if RDoc::RubyLex.debug?
- if peek(0) =~ /[0-9]/
- t = identify_number("")
- elsif peek(0) =~ /[\w_]/
- t = identify_identifier
- end
- printf "MATCH: end %s: %s\n", op, io.inspect if RDoc::RubyLex.debug?
- t
- end
- end
-
- def identify_gvar
- @lex_state = EXPR_END
- str = "$"
-
- tk = case ch = getc
- when /[~_*$?!@\/\\;,=:<>".]/ #"
- str << ch
- Token(TkGVAR, str)
-
- when "-"
- str << "-" << getc
- Token(TkGVAR, str)
-
- when "&", "`", "'", "+"
- str << ch
- Token(TkBACK_REF, str)
-
- when /[1-9]/
- str << ch
- while (ch = getc) =~ /[0-9]/
- str << ch
- end
- ungetc
- Token(TkNTH_REF)
- when /\w/
- ungetc
- ungetc
- return identify_identifier
- else
- ungetc
- Token("$")
- end
- tk.set_text(str)
- end
-
- def identify_identifier
- token = ""
- token.concat getc if peek(0) =~ /[$@]/
- token.concat getc if peek(0) == "@"
-
- while (ch = getc) =~ /\w|_/
- print ":", ch, ":" if RDoc::RubyLex.debug?
- token.concat ch
- end
- ungetc
-
- if ch == "!" or ch == "?"
- token.concat getc
- end
- # fix token
-
- # $stderr.puts "identifier - #{token}, state = #@lex_state"
-
- case token
- when /^\$/
- return Token(TkGVAR, token).set_text(token)
- when /^\@/
- @lex_state = EXPR_END
- return Token(TkIVAR, token).set_text(token)
- end
-
- if @lex_state != EXPR_DOT
- print token, "\n" if RDoc::RubyLex.debug?
-
- token_c, *trans = TkReading2Token[token]
- if token_c
- # reserved word?
-
- if (@lex_state != EXPR_BEG &&
- @lex_state != EXPR_FNAME &&
- trans[1])
- # modifiers
- token_c = TkSymbol2Token[trans[1]]
- @lex_state = trans[0]
- else
- if @lex_state != EXPR_FNAME
- if ENINDENT_CLAUSE.include?(token)
- @indent += 1
- elsif DEINDENT_CLAUSE.include?(token)
- @indent -= 1
- end
- @lex_state = trans[0]
- else
- @lex_state = EXPR_END
- end
- end
- return Token(token_c, token).set_text(token)
- end
- end
-
- if @lex_state == EXPR_FNAME
- @lex_state = EXPR_END
- if peek(0) == '='
- token.concat getc
- end
- elsif @lex_state == EXPR_BEG || @lex_state == EXPR_DOT
- @lex_state = EXPR_ARG
- else
- @lex_state = EXPR_END
- end
-
- if token[0, 1] =~ /[A-Z]/
- return Token(TkCONSTANT, token).set_text(token)
- elsif token[token.size - 1, 1] =~ /[!?]/
- return Token(TkFID, token).set_text(token)
- else
- return Token(TkIDENTIFIER, token).set_text(token)
- end
- end
-
- def identify_here_document
- ch = getc
- if ch == "-"
- ch = getc
- indent = true
- end
- if /['"`]/ =~ ch # '
- lt = ch
- quoted = ""
- while (c = getc) && c != lt
- quoted.concat c
- end
- else
- lt = '"'
- quoted = ch.dup
- while (c = getc) && c =~ /\w/
- quoted.concat c
- end
- ungetc
- end
-
- ltback, @ltype = @ltype, lt
- reserve = ""
-
- while ch = getc
- reserve << ch
- if ch == "\\" #"
- ch = getc
- reserve << ch
- elsif ch == "\n"
- break
- end
- end
-
- str = ""
- while (l = gets)
- l.chomp!
- l.strip! if indent
- break if l == quoted
- str << l.chomp << "\n"
- end
-
- @reader.divert_read_from(reserve)
-
- @ltype = ltback
- @lex_state = EXPR_END
- Token(Ltype2Token[lt], str).set_text(str.dump)
- end
-
- def identify_quotation(initial_char)
- ch = getc
- if lt = PERCENT_LTYPE[ch]
- initial_char += ch
- ch = getc
- elsif ch =~ /\W/
- lt = "\""
- else
- fail SyntaxError, "unknown type of %string ('#{ch}')"
- end
-# if ch !~ /\W/
-# ungetc
-# next
-# end
- #@ltype = lt
- @quoted = ch unless @quoted = PERCENT_PAREN[ch]
- identify_string(lt, @quoted, ch, initial_char)
- end
-
- def identify_number(start)
- str = start.dup
-
- if start == "+" or start == "-" or start == ""
- start = getc
- str << start
- end
-
- @lex_state = EXPR_END
-
- if start == "0"
- if peek(0) == "x"
- ch = getc
- str << ch
- match = /[0-9a-f_]/
- else
- match = /[0-7_]/
- end
- while ch = getc
- if ch !~ match
- ungetc
- break
- else
- str << ch
- end
- end
- return Token(TkINTEGER).set_text(str)
- end
-
- type = TkINTEGER
- allow_point = TRUE
- allow_e = TRUE
- while ch = getc
- case ch
- when /[0-9_]/
- str << ch
-
- when allow_point && "."
- type = TkFLOAT
- if peek(0) !~ /[0-9]/
- ungetc
- break
- end
- str << ch
- allow_point = false
-
- when allow_e && "e", allow_e && "E"
- str << ch
- type = TkFLOAT
- if peek(0) =~ /[+-]/
- str << getc
- end
- allow_e = false
- allow_point = false
- else
- ungetc
- break
- end
- end
- Token(type).set_text(str)
- end
-
- def identify_string(ltype, quoted = ltype, opener=nil, initial_char = nil)
- @ltype = ltype
- @quoted = quoted
- subtype = nil
-
- str = ""
- str << initial_char if initial_char
- str << (opener||quoted)
-
- nest = 0
- begin
- while ch = getc
- str << ch
- if @quoted == ch
- if nest == 0
- break
- else
- nest -= 1
- end
- elsif opener == ch
- nest += 1
- elsif @ltype != "'" && @ltype != "]" and ch == "#"
- ch = getc
- if ch == "{"
- subtype = true
- str << ch << skip_inner_expression
- else
- ungetc(ch)
- end
- elsif ch == '\\' #'
- str << read_escape
- end
- end
- if @ltype == "/"
- if peek(0) =~ /i|o|n|e|s/
- str << getc
- end
- end
- if subtype
- Token(DLtype2Token[ltype], str)
- else
- Token(Ltype2Token[ltype], str)
- end.set_text(str)
- ensure
- @ltype = nil
- @quoted = nil
- @lex_state = EXPR_END
- end
- end
-
- def skip_inner_expression
- res = ""
- nest = 0
- while (ch = getc)
- res << ch
- if ch == '}'
- break if nest.zero?
- nest -= 1
- elsif ch == '{'
- nest += 1
- end
- end
- res
- end
-
- def identify_comment
- @ltype = "#"
- comment = "#"
- while ch = getc
- if ch == "\\"
- ch = getc
- if ch == "\n"
- ch = " "
- else
- comment << "\\"
- end
- else
- if ch == "\n"
- @ltype = nil
- ungetc
- break
- end
- end
- comment << ch
- end
- return Token(TkCOMMENT).set_text(comment)
- end
-
- def read_escape
- res = ""
- case ch = getc
- when /[0-7]/
- ungetc ch
- 3.times do
- case ch = getc
- when /[0-7]/
- when nil
- break
- else
- ungetc
- break
- end
- res << ch
- end
-
- when "x"
- res << ch
- 2.times do
- case ch = getc
- when /[0-9a-fA-F]/
- when nil
- break
- else
- ungetc
- break
- end
- res << ch
- end
-
- when "M"
- res << ch
- if (ch = getc) != '-'
- ungetc
- else
- res << ch
- if (ch = getc) == "\\" #"
- res << ch
- res << read_escape
- else
- res << ch
- end
- end
-
- when "C", "c" #, "^"
- res << ch
- if ch == "C" and (ch = getc) != "-"
- ungetc
- else
- res << ch
- if (ch = getc) == "\\" #"
- res << ch
- res << read_escape
- else
- res << ch
- end
- end
- else
- res << ch
- end
- res
- end
-end
##
# Extracts code elements from a source file returning a TopLevel object
@@ -1373,7 +50,7 @@ end
#
# ##
# # This method tries over and over until it is tired
-#
+#
# def go_go_go(thing_to_try, tries = 10) # :args: thing_to_try
# puts thing_to_try
# go_go_go thing_to_try, tries - 1
@@ -1393,7 +70,7 @@ end
# # :call-seq:
# # my_method(Range)
# # my_method(offset, length)
-#
+#
# def my_method(*args)
# end
#
@@ -1404,7 +81,7 @@ end
#
# ##
# # My method is awesome
-#
+#
# def my_method(&block) # :yields: happy, times
# block.call 1, 2
# end
@@ -1416,7 +93,7 @@ end
#
# ##
# # This is a meta-programmed method!
-#
+#
# add_my_method :meta_method, :arg1, :arg2
#
# The parser looks at the token after the identifier to determine the name, in
@@ -1439,18 +116,29 @@ end
# ##
# # :singleton-method: woo_hoo!
#
-# == Hidden methods
+# Additionally you can mark a method as an attribute by
+# using :attr:, :attr_reader:, :attr_writer: or :attr_accessor:. Just like
+# for :method:, the name is optional.
+#
+# ##
+# # :attr_reader: my_attr_name
+#
+# == Hidden methods and attributes
#
# You can provide documentation for methods that don't appear using
-# the :method: and :singleton-method: directives:
+# the :method:, :singleton-method: and :attr: directives:
#
# ##
+# # :attr_writer: ghost_writer
+# # There is an attribute here, but you can't see it!
+#
+# ##
# # :method: ghost_method
# # There is a method here, but you can't see it!
-#
+#
# ##
# # this is a comment for a regular method
-#
+#
# def regular_method() end
#
# Note that by default, the :method: directive will be ignored if there is a
@@ -1458,12 +146,20 @@ end
class RDoc::Parser::Ruby < RDoc::Parser
- parse_files_matching(/\.(?:rbw?|rdoc)\z/)
+ parse_files_matching(/\.rbw?$/)
include RDoc::RubyToken
include RDoc::TokenStream
+ include RDoc::Parser::RubyTools
+
+ ##
+ # RDoc::NormalClass type
NORMAL = "::"
+
+ ##
+ # RDoc::SingleClass type
+
SINGLE = "<<"
def initialize(top_level, file_name, content, options, stats)
@@ -1473,21 +169,17 @@ class RDoc::Parser::Ruby < RDoc::Parser
@token_listeners = nil
@scanner = RDoc::RubyLex.new content, @options
@scanner.exception_on_syntax_error = false
+ @prev_seek = nil
reset
end
- def add_token_listener(obj)
- @token_listeners ||= []
- @token_listeners << obj
- end
-
##
# Look for the first comment in a file that isn't a shebang line.
def collect_first_comment
skip_tkspace
- res = ''
+ comment = ''
first_line = true
tk = get_tk
@@ -1502,7 +194,7 @@ class RDoc::Parser::Ruby < RDoc::Parser
tk = get_tk
else
first_line = false
- res << tk.text << "\n"
+ comment << tk.text << "\n"
tk = get_tk
if TkNL === tk then
@@ -1514,7 +206,7 @@ class RDoc::Parser::Ruby < RDoc::Parser
unget_tk tk
- res
+ comment
end
def error(msg)
@@ -1560,24 +252,24 @@ class RDoc::Parser::Ruby < RDoc::Parser
name_t = get_tk
# class ::A -> A is in the top level
- if TkCOLON2 === name_t then
+ case name_t
+ when TkCOLON2, TkCOLON3 then # bug
name_t = get_tk
container = @top_level
end
- skip_tkspace(false)
+ skip_tkspace false
while TkCOLON2 === peek_tk do
prev_container = container
- container = container.find_module_named(name_t.name)
- if !container
-# warn("Couldn't find module #{name_t.name}")
+ container = container.find_module_named name_t.name
+ unless container then
container = prev_container.add_module RDoc::NormalModule, name_t.name
end
get_tk
name_t = get_tk
end
- skip_tkspace(false)
+ skip_tkspace false
return [container, name_t]
end
@@ -1590,12 +282,12 @@ class RDoc::Parser::Ruby < RDoc::Parser
res = ""
while TkCOLON2 === tk or TkCOLON3 === tk or TkCONSTANT === tk do
- res += tk.text
+ res += tk.name
tk = get_tk
end
unget_tk(tk)
- skip_tkspace(false)
+ skip_tkspace false
get_tkread # empty out read buffer
@@ -1617,11 +309,11 @@ class RDoc::Parser::Ruby < RDoc::Parser
def get_constant
res = ""
- skip_tkspace(false)
+ skip_tkspace false
tk = get_tk
while TkCOLON2 === tk or TkCOLON3 === tk or TkCONSTANT === tk do
- res += tk.text
+ res += tk.name
tk = get_tk
end
@@ -1636,88 +328,51 @@ class RDoc::Parser::Ruby < RDoc::Parser
# Get a constant that may be surrounded by parens
def get_constant_with_optional_parens
- skip_tkspace(false)
+ skip_tkspace false
nest = 0
while TkLPAREN === (tk = peek_tk) or TkfLPAREN === tk do
get_tk
- skip_tkspace(true)
+ skip_tkspace
nest += 1
end
name = get_constant
while nest > 0
- skip_tkspace(true)
+ skip_tkspace
tk = get_tk
nest -= 1 if TkRPAREN === tk
end
+
name
end
def get_symbol_or_name
tk = get_tk
case tk
- when TkSYMBOL
- tk.text.sub(/^:/, '')
- when TkId, TkOp
+ when TkSYMBOL then
+ text = tk.text.sub(/^:/, '')
+
+ if TkASSIGN === peek_tk then
+ get_tk
+ text << '='
+ end
+
+ text
+ when TkId, TkOp then
tk.name
- when TkSTRING
+ when TkSTRING, TkDSTRING then
tk.text
else
- raise "Name or symbol expected (got #{tk})"
+ raise RDoc::Error, "Name or symbol expected (got #{tk})"
end
end
- def get_tk
- tk = nil
- if @tokens.empty?
- tk = @scanner.token
- @read.push @scanner.get_read
- puts "get_tk1 => #{tk.inspect}" if $TOKEN_DEBUG
- else
- @read.push @unget_read.shift
- tk = @tokens.shift
- puts "get_tk2 => #{tk.inspect}" if $TOKEN_DEBUG
- end
-
- if TkSYMBEG === tk then
- set_token_position(tk.line_no, tk.char_no)
- tk1 = get_tk
- if TkId === tk1 or TkOp === tk1 or TkSTRING === tk1 then
- if tk1.respond_to?(:name)
- tk = Token(TkSYMBOL).set_text(":" + tk1.name)
- else
- tk = Token(TkSYMBOL).set_text(":" + tk1.text)
- end
- # remove the identifier we just read (we're about to
- # replace it with a symbol)
- @token_listeners.each do |obj|
- obj.pop_token
- end if @token_listeners
- else
- warn("':' not followed by identifier or operator")
- tk = tk1
- end
- end
-
- # inform any listeners of our shiny new token
- @token_listeners.each do |obj|
- obj.add_token(tk)
- end if @token_listeners
-
- tk
- end
-
- def get_tkread
- read = @read.join("")
- @read = []
- read
- end
-
##
# Look for directives in a normal comment block:
#
- # #-- - don't display comment from this point forward
+ # # :stopdoc:
+ # # Don't display comment from this point forward
#
# This routine modifies it's parameter
@@ -1732,8 +387,9 @@ class RDoc::Parser::Ruby < RDoc::Parser
when 'main' then
@options.main_page = param
''
- when 'method', 'singleton-method' then
- false # ignore
+ when 'method', 'singleton-method',
+ 'attr', 'attr_accessor', 'attr_reader', 'attr_writer' then
+ false # handled elsewhere
when 'section' then
context.set_current_section(param, comment)
comment.replace ''
@@ -1754,23 +410,30 @@ class RDoc::Parser::Ruby < RDoc::Parser
end
end
- remove_private_comments(comment)
+ remove_private_comments comment
end
- def make_message(msg)
- prefix = "\n" + @file_name + ":"
- if @scanner
- prefix << "#{@scanner.line_no}:#{@scanner.char_no}: "
- end
- return prefix + msg
+ ##
+ # Adds useful info about the parser to +message+
+
+ def make_message message
+ prefix = "\n#{@file_name}:"
+
+ prefix << "#{@scanner.line_no}:#{@scanner.char_no}:" if @scanner
+
+ "#{prefix} #{message}"
end
+ ##
+ # Creates an RDoc::Attr for the name following +tk+, setting the comment to
+ # +comment+.
+
def parse_attr(context, single, tk, comment)
- args = parse_symbol_arg(1)
+ args = parse_symbol_arg 1
if args.size > 0
name = args[0]
rw = "R"
- skip_tkspace(false)
+ skip_tkspace false
tk = get_tk
if TkCOMMA === tk then
rw = "RW" if get_bool
@@ -1787,12 +450,16 @@ class RDoc::Parser::Ruby < RDoc::Parser
end
end
+ ##
+ # Creates an RDoc::Attr for each attribute listed after +tk+, setting the
+ # comment for each to +comment+.
+
def parse_attr_accessor(context, single, tk, comment)
args = parse_symbol_arg
read = get_tkread
rw = "?"
- # If nodoc is given, don't document any of them
+ # TODO If nodoc is given, don't document any of them
tmp = RDoc::CodeObject.new
read_documentation_modifiers tmp, RDoc::ATTR_MODIFIERS
@@ -1803,8 +470,7 @@ class RDoc::Parser::Ruby < RDoc::Parser
when "attr_writer" then rw = "W"
when "attr_accessor" then rw = "RW"
else
- rw = @options.extra_accessor_flags[tk.name]
- rw = '?' if rw.nil?
+ rw = '?'
end
for name in args
@@ -1820,19 +486,24 @@ class RDoc::Parser::Ruby < RDoc::Parser
skip_tkspace
end
new_name = get_symbol_or_name
- @scanner.instance_eval{@lex_state = EXPR_FNAME}
+
+ @scanner.instance_eval { @lex_state = EXPR_FNAME }
+
skip_tkspace
if TkCOMMA === peek_tk then
get_tk
skip_tkspace
end
- old_name = get_symbol_or_name
+
+ begin
+ old_name = get_symbol_or_name
+ rescue RDoc::Error
+ return
+ end
al = RDoc::Alias.new get_tkread, old_name, new_name, comment
read_documentation_modifiers al, RDoc::ATTR_MODIFIERS
- if al.document_self
- context.add_alias(al)
- end
+ context.add_alias al if al.document_self
end
def parse_call_parameters(tk)
@@ -1847,23 +518,25 @@ class RDoc::Parser::Ruby < RDoc::Parser
nest = 0
loop do
- case tk
- when TkSEMICOLON
- break
- when TkLPAREN, TkfLPAREN
- nest += 1
- when end_token
- if end_token == TkRPAREN
- nest -= 1
- break if @scanner.lex_state == EXPR_END and nest <= 0
- else
- break unless @scanner.continue
- end
- when TkCOMMENT
- unget_tk(tk)
- break
+ case tk
+ when TkSEMICOLON
+ break
+ when TkLPAREN, TkfLPAREN
+ nest += 1
+ when end_token
+ if end_token == TkRPAREN
+ nest -= 1
+ break if @scanner.lex_state == EXPR_END and nest <= 0
+ else
+ break unless @scanner.continue
end
- tk = get_tk
+ when TkCOMMENT
+ unget_tk(tk)
+ break
+ when nil then
+ break
+ end
+ tk = get_tk
end
res = get_tkread.tr("\n", " ").strip
res = "" if res == ";"
@@ -1871,7 +544,7 @@ class RDoc::Parser::Ruby < RDoc::Parser
end
def parse_class(container, single, tk, comment)
- container, name_t = get_class_or_module(container)
+ container, name_t = get_class_or_module container
case name_t
when TkCONSTANT
@@ -1880,7 +553,7 @@ class RDoc::Parser::Ruby < RDoc::Parser
if TkLT === peek_tk then
get_tk
- skip_tkspace(true)
+ skip_tkspace
superclass = get_class_specification
superclass = "<unknown>" if superclass.empty?
end
@@ -1888,25 +561,24 @@ class RDoc::Parser::Ruby < RDoc::Parser
cls_type = single == SINGLE ? RDoc::SingleClass : RDoc::NormalClass
cls = container.add_class cls_type, name, superclass
- @stats.add_class cls
-
read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS
cls.record_location @top_level
-
- parse_statements cls
cls.comment = comment
+ @stats.add_class cls
+
+ parse_statements cls
when TkLSHFT
case name = get_class_specification
when "self", container.name
- parse_statements(container, SINGLE)
+ parse_statements container, SINGLE
else
- other = RDoc::TopLevel.find_class_named(name)
- unless other
- # other = @top_level.add_class(NormalClass, name, nil)
- # other.record_location(@top_level)
- # other.comment = comment
- other = RDoc::NormalClass.new "Dummy", nil
+ other = RDoc::TopLevel.find_class_named name
+
+ unless other then
+ other = container.add_module RDoc::NormalModule, name
+ other.record_location @top_level
+ other.comment = comment
end
@stats.add_class other
@@ -1920,47 +592,69 @@ class RDoc::Parser::Ruby < RDoc::Parser
end
end
- def parse_constant(container, single, tk, comment)
+ def parse_constant(container, tk, comment)
name = tk.name
- skip_tkspace(false)
+ skip_tkspace false
eq_tk = get_tk
unless TkASSIGN === eq_tk then
- unget_tk(eq_tk)
+ unget_tk eq_tk
return
end
-
nest = 0
get_tkread
tk = get_tk
+
if TkGT === tk then
- unget_tk(tk)
- unget_tk(eq_tk)
+ unget_tk tk
+ unget_tk eq_tk
return
end
+ rhs_name = ''
+
loop do
- case tk
- when TkSEMICOLON
+ case tk
+ when TkSEMICOLON then
+ break
+ when TkLPAREN, TkfLPAREN, TkLBRACE, TkLBRACK, TkDO, TkIF, TkUNLESS,
+ TkCASE then
+ nest += 1
+ when TkRPAREN, TkRBRACE, TkRBRACK, TkEND then
+ nest -= 1
+ when TkCOMMENT then
+ if nest <= 0 && @scanner.lex_state == EXPR_END
+ unget_tk tk
break
- when TkLPAREN, TkfLPAREN, TkLBRACE, TkLBRACK, TkDO
- nest += 1
- when TkRPAREN, TkRBRACE, TkRBRACK, TkEND
- nest -= 1
- when TkCOMMENT
- if nest <= 0 && @scanner.lex_state == EXPR_END
- unget_tk(tk)
- break
- end
- when TkNL
- if (nest <= 0) && ((@scanner.lex_state == EXPR_END) || (!@scanner.continue))
- unget_tk(tk)
- break
- end
end
- tk = get_tk
+ when TkCONSTANT then
+ rhs_name << tk.name
+
+ if nest <= 0 and TkNL === peek_tk then
+ mod = if rhs_name =~ /^::/ then
+ RDoc::TopLevel.find_class_or_module rhs_name
+ else
+ container.find_module_named rhs_name
+ end
+
+ container.add_module_alias mod, name if mod
+ get_tk # TkNL
+ break
+ end
+ when TkNL then
+ if nest <= 0 &&
+ (@scanner.lex_state == EXPR_END || !@scanner.continue) then
+ unget_tk tk
+ break
+ end
+ when TkCOLON2, TkCOLON3 then
+ rhs_name << '::'
+ when nil then
+ break
+ end
+ tk = get_tk
end
res = get_tkread.tr("\n", " ").strip
@@ -1969,42 +663,60 @@ class RDoc::Parser::Ruby < RDoc::Parser
con = RDoc::Constant.new name, res, comment
read_documentation_modifiers con, RDoc::CONSTANT_MODIFIERS
- if con.document_self
- container.add_constant(con)
- end
+ @stats.add_constant con
+ container.add_constant con if con.document_self
end
+ ##
+ # Generates an RDoc::Method or RDoc::Attr from +comment+ by looking for
+ # :method: or :attr: directives in +comment+.
+
def parse_comment(container, tk, comment)
line_no = tk.line_no
column = tk.char_no
singleton = !!comment.sub!(/(^# +:?)(singleton-)(method:)/, '\1\3')
+ # REFACTOR
if comment.sub!(/^# +:?method: *(\S*).*?\n/i, '') then
name = $1 unless $1.empty?
- else
- return nil
- end
- meth = RDoc::GhostMethod.new get_tkread, name
- meth.singleton = singleton
+ meth = RDoc::GhostMethod.new get_tkread, name
+ meth.singleton = singleton
- @stats.add_method meth
+ meth.start_collecting_tokens
+ indent = TkSPACE.new nil, 1, 1
+ indent.set_text " " * column
- meth.start_collecting_tokens
- indent = TkSPACE.new 1, 1
- indent.set_text " " * column
+ position_comment = TkCOMMENT.new nil, line_no, 1
+ position_comment.set_text "# File #{@top_level.absolute_name}, line #{line_no}"
+ meth.add_tokens [position_comment, NEWLINE_TOKEN, indent]
- position_comment = TkCOMMENT.new(line_no, 1, "# File #{@top_level.file_absolute_name}, line #{line_no}")
- meth.add_tokens [position_comment, NEWLINE_TOKEN, indent]
+ meth.params = ''
- meth.params = ''
+ extract_call_seq comment, meth
- extract_call_seq comment, meth
+ return unless meth.name
- container.add_method meth if meth.document_self
+ container.add_method meth if meth.document_self
- meth.comment = comment
+ meth.comment = comment
+
+ @stats.add_method meth
+ elsif comment.sub!(/# +:?(attr(_reader|_writer|_accessor)?:) *(\S*).*?\n/i, '') then
+ rw = case $1
+ when 'attr_reader' then 'R'
+ when 'attr_writer' then 'W'
+ else 'RW'
+ end
+
+ name = $3 unless $3.empty?
+
+ att = RDoc::Attr.new get_tkread, name, rw, comment
+ container.add_attribute att
+
+ @stats.add_method att
+ end
end
def parse_include(context, comment)
@@ -2020,6 +732,66 @@ class RDoc::Parser::Ruby < RDoc::Parser
end
##
+ # Parses a meta-programmed attribute and creates an RDoc::Attr.
+ #
+ # To create foo and bar attributes on class C with comment "My attributes":
+ #
+ # class C
+ #
+ # ##
+ # # :attr:
+ # #
+ # # My attributes
+ #
+ # my_attr :foo, :bar
+ #
+ # end
+ #
+ # To create a foo attribute on class C with comment "My attribute":
+ #
+ # class C
+ #
+ # ##
+ # # :attr: foo
+ # #
+ # # My attribute
+ #
+ # my_attr :foo, :bar
+ #
+ # end
+
+ def parse_meta_attr(context, single, tk, comment)
+ args = parse_symbol_arg
+ read = get_tkread
+ rw = "?"
+
+ # 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
+
+ if comment.sub!(/^# +:?(attr(_reader|_writer|_accessor)?): *(\S*).*?\n/i, '') then
+ rw = case $1
+ when 'attr_reader' then 'R'
+ when 'attr_writer' then 'W'
+ else 'RW'
+ end
+ name = $3 unless $3.empty?
+ end
+
+ if name then
+ att = RDoc::Attr.new get_tkread, name, rw, comment
+ context.add_attribute att
+ else
+ args.each do |attr_name|
+ att = RDoc::Attr.new get_tkread, attr_name, rw, comment
+ context.add_attribute att
+ end
+ end
+ end
+
+ ##
# Parses a meta-programmed method
def parse_meta_method(container, single, tk, comment)
@@ -2044,9 +816,12 @@ class RDoc::Parser::Ruby < RDoc::Parser
when TkSYMBOL then
name = name_t.text[1..-1]
when TkSTRING then
- name = name_t.text[1..-2]
+ name = name_t.value[1..-2]
+ when TkASSIGN then # ignore
+ remove_token_listener self
+ return
else
- warn "#{container.toplevel.file_relative_name}:#{name_t.line_no} unknown name token #{name_t.inspect} for meta-method"
+ warn "unknown name token #{name_t.inspect} for meta-method '#{tk.name}'"
name = 'unknown'
end
end
@@ -2054,166 +829,183 @@ class RDoc::Parser::Ruby < RDoc::Parser
meth = RDoc::MetaMethod.new get_tkread, name
meth.singleton = singleton
- @stats.add_method meth
-
remove_token_listener self
meth.start_collecting_tokens
- indent = TkSPACE.new 1, 1
+ indent = TkSPACE.new nil, 1, 1
indent.set_text " " * column
- position_comment = TkCOMMENT.new(line_no, 1, "# File #{@top_level.file_absolute_name}, line #{line_no}")
+ position_comment = TkCOMMENT.new nil, line_no, 1
+ position_comment.value = "# File #{@top_level.absolute_name}, line #{line_no}"
meth.add_tokens [position_comment, NEWLINE_TOKEN, indent]
meth.add_tokens @token_stream
- add_token_listener meth
-
- meth.params = ''
+ token_listener meth do
+ meth.params = ''
- extract_call_seq comment, meth
+ extract_call_seq comment, meth
- container.add_method meth if meth.document_self
+ container.add_method meth if meth.document_self
- last_tk = tk
+ last_tk = tk
- while tk = get_tk do
- case tk
- when TkSEMICOLON then
- break
- when TkNL then
- break unless last_tk and TkCOMMA === last_tk
- when TkSPACE then
- # expression continues
- else
- last_tk = tk
+ while tk = get_tk do
+ case tk
+ when TkSEMICOLON then
+ break
+ when TkNL then
+ break unless last_tk and TkCOMMA === last_tk
+ when TkSPACE then
+ # expression continues
+ else
+ last_tk = tk
+ end
end
end
- remove_token_listener meth
-
meth.comment = comment
+
+ @stats.add_method meth
end
##
- # Parses a method
+ # Parses a normal method defined by +def+
def parse_method(container, single, tk, comment)
+ added_container = nil
+ meth = nil
+ name = nil
line_no = tk.line_no
column = tk.char_no
start_collecting_tokens
- add_token(tk)
- add_token_listener(self)
+ add_token tk
- @scanner.instance_eval do @lex_state = EXPR_FNAME end
+ token_listener self do
+ @scanner.instance_eval do @lex_state = EXPR_FNAME end
- skip_tkspace(false)
- name_t = get_tk
- back_tk = skip_tkspace
- meth = nil
- added_container = false
+ skip_tkspace false
+ name_t = get_tk
+ back_tk = skip_tkspace
+ meth = nil
+ added_container = false
- dot = get_tk
- if TkDOT === dot or TkCOLON2 === dot then
- @scanner.instance_eval do @lex_state = EXPR_FNAME end
- skip_tkspace
- name_t2 = get_tk
+ dot = get_tk
+ if TkDOT === dot or TkCOLON2 === dot then
+ @scanner.instance_eval do @lex_state = EXPR_FNAME end
+ skip_tkspace
+ name_t2 = get_tk
+
+ case name_t
+ when TkSELF, TkMOD then
+ name = name_t2.name
+ when TkCONSTANT then
+ name = name_t2.name
+ prev_container = container
+ container = container.find_module_named(name_t.name)
+ unless container then
+ added_container = true
+ obj = name_t.name.split("::").inject(Object) do |state, item|
+ state.const_get(item)
+ end rescue nil
+
+ type = obj.class == Class ? RDoc::NormalClass : RDoc::NormalModule
+
+ unless [Class, Module].include?(obj.class) then
+ warn("Couldn't find #{name_t.name}. Assuming it's a module")
+ end
- case name_t
- when TkSELF then
- name = name_t2.name
- when TkCONSTANT then
- name = name_t2.name
- prev_container = container
- container = container.find_module_named(name_t.name)
- unless container then
- added_container = true
- obj = name_t.name.split("::").inject(Object) do |state, item|
- state.const_get(item)
- end rescue nil
-
- type = obj.class == Class ? RDoc::NormalClass : RDoc::NormalModule
-
- unless [Class, Module].include?(obj.class) then
- warn("Couldn't find #{name_t.name}. Assuming it's a module")
- end
+ if type == RDoc::NormalClass then
+ sclass = obj.superclass ? obj.superclass.name : nil
+ container = prev_container.add_class type, name_t.name, sclass
+ else
+ container = prev_container.add_module type, name_t.name
+ end
- if type == RDoc::NormalClass then
- container = prev_container.add_class(type, name_t.name, obj.superclass.name)
- else
- container = prev_container.add_module(type, name_t.name)
+ container.record_location @top_level
end
-
- container.record_location @top_level
+ when TkIDENTIFIER, TkIVAR then
+ dummy = RDoc::Context.new
+ dummy.parent = container
+ skip_method dummy
+ return
+ else
+ warn "unexpected method name token #{name_t.inspect}"
+ # break
+ skip_method container
+ return
end
+
+ meth = RDoc::AnyMethod.new(get_tkread, name)
+ meth.singleton = true
else
- # warn("Unexpected token '#{name_t2.inspect}'")
- # break
- skip_method(container)
- return
- end
+ unget_tk dot
+ back_tk.reverse_each do |token|
+ unget_tk token
+ end
- meth = RDoc::AnyMethod.new(get_tkread, name)
- meth.singleton = true
- else
- unget_tk dot
- back_tk.reverse_each do |token|
- unget_tk token
- end
- name = name_t.name
+ name = case name_t
+ when TkSTAR, TkAMPER then
+ name_t.text
+ else
+ unless name_t.respond_to? :name then
+ warn "expected method name token, . or ::, got #{name_t.inspect}"
+ skip_method container
+ return
+ end
+ name_t.name
+ end
- meth = RDoc::AnyMethod.new get_tkread, name
- meth.singleton = (single == SINGLE)
+ meth = RDoc::AnyMethod.new get_tkread, name
+ meth.singleton = (single == SINGLE)
+ end
end
- @stats.add_method meth
-
- remove_token_listener self
-
meth.start_collecting_tokens
- indent = TkSPACE.new 1, 1
+ indent = TkSPACE.new nil, 1, 1
indent.set_text " " * column
- token = TkCOMMENT.new(line_no, 1, "# File #{@top_level.file_absolute_name}, line #{line_no}")
+ token = TkCOMMENT.new nil, line_no, 1
+ token.set_text "# File #{@top_level.absolute_name}, line #{line_no}"
meth.add_tokens [token, NEWLINE_TOKEN, indent]
meth.add_tokens @token_stream
- add_token_listener meth
-
- @scanner.instance_eval do @continue = false end
- parse_method_parameters meth
+ token_listener meth do
+ @scanner.instance_eval do @continue = false end
+ parse_method_parameters meth
- if meth.document_self then
- container.add_method meth
- elsif added_container then
- container.document_self = false
- end
+ if meth.document_self then
+ container.add_method meth
+ elsif added_container then
+ container.document_self = false
+ end
- # Having now read the method parameters and documentation modifiers, we
- # now know whether we have to rename #initialize to ::new
+ # Having now read the method parameters and documentation modifiers, we
+ # now know whether we have to rename #initialize to ::new
- if name == "initialize" && !meth.singleton then
- if meth.dont_rename_initialize then
- meth.visibility = :protected
- else
- meth.singleton = true
- meth.name = "new"
- meth.visibility = :public
+ if name == "initialize" && !meth.singleton then
+ if meth.dont_rename_initialize then
+ meth.visibility = :protected
+ else
+ meth.singleton = true
+ meth.name = "new"
+ meth.visibility = :public
+ end
end
- end
-
- parse_statements(container, single, meth)
- remove_token_listener(meth)
+ parse_statements container, single, meth
+ end
extract_call_seq comment, meth
meth.comment = comment
+
+ @stats.add_method meth
end
def parse_method_or_yield_parameters(method = nil,
modifiers = RDoc::METHOD_MODIFIERS)
- skip_tkspace(false)
+ skip_tkspace false
tk = get_tk
# Little hack going on here. In the statement
@@ -2232,33 +1024,39 @@ class RDoc::Parser::Ruby < RDoc::Parser
nest = 0
loop do
- case tk
- when TkSEMICOLON
- break
- when TkLBRACE
- nest += 1
- when TkRBRACE
- # we might have a.each {|i| yield i }
- unget_tk(tk) if nest.zero?
+ case tk
+ when TkSEMICOLON then
+ break
+ when TkLBRACE 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
+ when TkLPAREN, TkfLPAREN then
+ nest += 1
+ when end_token then
+ if end_token == TkRPAREN
nest -= 1
- break if nest <= 0
- when TkLPAREN, TkfLPAREN
- nest += 1
- when end_token
- if end_token == TkRPAREN
- nest -= 1
- break if @scanner.lex_state == EXPR_END and nest <= 0
- else
- break unless @scanner.continue
- end
- when method && method.block_params.nil? && TkCOMMENT
- unget_tk(tk)
- read_documentation_modifiers(method, modifiers)
+ break if @scanner.lex_state == EXPR_END and nest <= 0
+ else
+ break unless @scanner.continue
end
+ when method && method.block_params.nil? && TkCOMMENT then
+ unget_tk tk
+ read_documentation_modifiers method, modifiers
+ @read.pop
+ when TkCOMMENT then
+ @read.pop
+ when nil then
+ break
+ end
tk = get_tk
end
- res = get_tkread.tr("\n", " ").strip
- res = "" if res == ";"
+
+ res = get_tkread.gsub(/\s+/, ' ').strip
+ res = '' if res == ';'
res
end
@@ -2271,56 +1069,53 @@ class RDoc::Parser::Ruby < RDoc::Parser
# and add this as the block_params for the method
def parse_method_parameters(method)
- res = parse_method_or_yield_parameters(method)
- res = "(" + res + ")" unless res[0] == ?(
+ res = parse_method_or_yield_parameters method
+
+ res = "(#{res})" unless res =~ /\A\(/
method.params = res unless method.params
- if method.block_params.nil?
- skip_tkspace(false)
+
+ if method.block_params.nil? then
+ skip_tkspace false
read_documentation_modifiers method, RDoc::METHOD_MODIFIERS
end
end
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
mod = container.add_module RDoc::NormalModule, name
mod.record_location @top_level
- @stats.add_module mod
-
read_documentation_modifiers mod, RDoc::CLASS_MODIFIERS
parse_statements(mod)
mod.comment = comment
+
+ @stats.add_module mod
end
def parse_require(context, comment)
skip_tkspace_comment
tk = get_tk
+
if TkLPAREN === tk then
skip_tkspace_comment
tk = get_tk
end
- name = nil
- case tk
- when TkSTRING
- name = tk.text
- # when TkCONSTANT, TkIDENTIFIER, TkIVAR, TkGVAR
- # name = tk.name
- when TkDSTRING
- warn "Skipping require of dynamic string: #{tk.text}"
- # else
- # warn "'require' used as variable"
- end
- if name
+ name = tk.text if TkSTRING === tk
+
+ if name then
context.add_require RDoc::Require.new(name, comment)
else
- unget_tk(tk)
+ unget_tk tk
end
end
+ ##
+ # The core of the ruby parser.
+
def parse_statements(container, single = NORMAL, current_method = nil,
comment = '')
nest = 1
@@ -2335,7 +1130,7 @@ class RDoc::Parser::Ruby < RDoc::Parser
case tk
when TkNL then
- skip_tkspace true # Skip blanks and newlines
+ skip_tkspace
tk = get_tk
if TkCOMMENT === tk then
@@ -2349,8 +1144,9 @@ class RDoc::Parser::Ruby < RDoc::Parser
while TkCOMMENT === tk do
comment << tk.text << "\n"
- tk = get_tk # this is the newline
- skip_tkspace(false) # leading spaces
+
+ tk = get_tk # this is the newline
+ skip_tkspace false # leading spaces
tk = get_tk
end
@@ -2393,11 +1189,11 @@ class RDoc::Parser::Ruby < RDoc::Parser
when TkCONSTANT then
if container.document_self then
- parse_constant container, single, tk, comment
+ parse_constant container, tk, comment
end
when TkALIAS then
- if container.document_self then
+ if container.document_self and not current_method then
parse_alias container, single, tk, comment
end
@@ -2434,15 +1230,21 @@ class RDoc::Parser::Ruby < RDoc::Parser
keep_comment = true
when 'attr' then
parse_attr container, single, tk, comment
- when /^attr_(reader|writer|accessor)$/, @options.extra_accessors then
+ when /^attr_(reader|writer|accessor)$/ then
parse_attr_accessor container, single, tk, comment
when 'alias_method' then
- if container.document_self then
- parse_alias container, single, tk, comment
- end
+ parse_alias container, single, tk, comment if
+ container.document_self
+ when 'require', 'include' then
+ # ignore
else
if container.document_self and comment =~ /\A#\#$/ then
- parse_meta_method container, single, tk, comment
+ case comment
+ when /^# +:?attr(_reader|_writer|_accessor)?:/ then
+ parse_meta_attr container, single, tk, comment
+ else
+ parse_meta_method container, single, tk, comment
+ end
end
end
end
@@ -2459,16 +1261,18 @@ class RDoc::Parser::Ruby < RDoc::Parser
if nest == 0 then
read_documentation_modifiers container, RDoc::CLASS_MODIFIERS
container.ongoing_visibility = save_visibility
+
+ parse_comment container, tk, comment unless comment.empty?
+
return
end
-
end
comment = '' unless keep_comment
begin
get_tkread
- skip_tkspace(false)
+ skip_tkspace false
end while peek_tk == TkNL
end
end
@@ -2503,7 +1307,7 @@ class RDoc::Parser::Ruby < RDoc::Parser
end
loop do
- skip_tkspace(false)
+ skip_tkspace false
tk1 = get_tk
unless TkCOMMA === tk1 then
@@ -2533,7 +1337,7 @@ class RDoc::Parser::Ruby < RDoc::Parser
end
end
- def parse_toplevel_statements(container)
+ def parse_top_level_statements(container)
comment = collect_first_comment
look_for_directives_in(container, comment)
container.comment = comment unless comment.empty?
@@ -2559,7 +1363,7 @@ class RDoc::Parser::Ruby < RDoc::Parser
singleton = true
:public
else
- raise "Invalid visibility: #{tk.name}"
+ raise RDoc::Error, "Invalid visibility: #{tk.name}"
end
skip_tkspace_comment false
@@ -2613,18 +1417,6 @@ class RDoc::Parser::Ruby < RDoc::Parser
end
end
- def peek_read
- @read.join('')
- end
-
- ##
- # Peek at the next token, but don't remove it from the stream
-
- def peek_tk
- unget_tk(tk = get_tk)
- tk
- end
-
##
# Directives are modifier comments that can appear after class, module, or
# method names. For example:
@@ -2640,16 +1432,18 @@ class RDoc::Parser::Ruby < RDoc::Parser
def read_directive(allowed)
tk = get_tk
result = nil
- if TkCOMMENT === tk
- if tk.text =~ /\s*:?(\w+):\s*(.*)/
+
+ if TkCOMMENT === tk then
+ if tk.text =~ /\s*:?(\w+):\s*(.*)/ then
directive = $1.downcase
- if allowed.include?(directive)
+ if allowed.include? directive then
result = [directive, $2]
end
end
else
- unget_tk(tk)
+ unget_tk tk
end
+
result
end
@@ -2687,40 +1481,40 @@ class RDoc::Parser::Ruby < RDoc::Parser
comment.sub!(/^#--\n.*/m, '')
end
- def remove_token_listener(obj)
- @token_listeners.delete(obj)
- end
-
- def reset
- @tokens = []
- @unget_read = []
- @read = []
- end
-
def scan
reset
- catch(:eof) do
- catch(:enddoc) do
+ catch :eof do
+ catch :enddoc do
begin
- parse_toplevel_statements(@top_level)
- rescue Exception => e
- $stderr.puts <<-EOF
-
-
-RDoc failure in #{@file_name} at or around line #{@scanner.line_no} column
-#{@scanner.char_no}
+ 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
-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.
+ $stderr.puts <<-EOF
-The internal error was:
+#{self.class} failure around line #{@scanner.line_no} of
+#{@file_name}
EOF
- e.set_backtrace(e.backtrace[0,4])
- raise
+ unless bytes.empty? then
+ $stderr.puts
+ $stderr.puts bytes.inspect
+ end
+
+ raise e
end
end
end
@@ -2732,7 +1526,7 @@ The internal error was:
# while, until, and for have an optional do
def skip_optional_do_after_expression
- skip_tkspace(false)
+ skip_tkspace false
tk = get_tk
case tk
when TkLPAREN, TkfLPAREN
@@ -2759,10 +1553,12 @@ The internal error was:
else
break unless @scanner.continue
end
+ when nil then
+ break
end
tk = get_tk
end
- skip_tkspace(false)
+ skip_tkspace false
get_tk if TkDO === peek_tk
end
@@ -2771,31 +1567,17 @@ The internal error was:
# skip the var [in] part of a 'for' statement
def skip_for_variable
- skip_tkspace(false)
+ skip_tkspace false
tk = get_tk
- skip_tkspace(false)
+ skip_tkspace false
tk = get_tk
unget_tk(tk) unless TkIN === tk
end
- def skip_method(container)
+ def skip_method container
meth = RDoc::AnyMethod.new "", "anon"
- parse_method_parameters(meth)
- parse_statements(container, false, meth)
- end
-
- ##
- # Skip spaces
-
- def skip_tkspace(skip_nl = true)
- tokens = []
-
- while TkSPACE === (tk = get_tk) or (skip_nl and TkNL === tk) do
- tokens.push tk
- end
-
- unget_tk(tk)
- tokens
+ parse_method_parameters meth
+ parse_statements container, false, meth
end
##
@@ -2803,22 +1585,12 @@ The internal error was:
def skip_tkspace_comment(skip_nl = true)
loop do
- skip_tkspace(skip_nl)
+ skip_tkspace skip_nl
return unless TkCOMMENT === peek_tk
get_tk
end
end
- def unget_tk(tk)
- @tokens.unshift tk
- @unget_read.unshift @read.pop
-
- # Remove this token from any listeners
- @token_listeners.each do |obj|
- obj.pop_token
- end if @token_listeners
- end
-
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
new file mode 100644
index 0000000000..90c03307b4
--- /dev/null
+++ b/lib/rdoc/parser/ruby_tools.rb
@@ -0,0 +1,157 @@
+##
+# Collection of methods for writing parsers against RDoc::RubyLex and
+# RDoc::RubyToken
+
+module RDoc::Parser::RubyTools
+
+ include RDoc::RubyToken
+
+ ##
+ # Adds a token listener +obj+, but you should probably use token_listener
+
+ def add_token_listener(obj)
+ @token_listeners ||= []
+ @token_listeners << obj
+ end
+
+ ##
+ # Fetches the next token from the scanner
+
+ def get_tk
+ tk = nil
+
+ if @tokens.empty? then
+ tk = @scanner.token
+ @read.push @scanner.get_readed
+ puts "get_tk1 => #{tk.inspect}" if $TOKEN_DEBUG
+ else
+ @read.push @unget_read.shift
+ tk = @tokens.shift
+ puts "get_tk2 => #{tk.inspect}" if $TOKEN_DEBUG
+ end
+
+ tk = nil if TkEND_OF_SCRIPT === tk
+
+ if TkSYMBEG === tk then
+ set_token_position tk.line_no, tk.char_no
+
+ case tk1 = get_tk
+ when TkId, TkOp, TkSTRING, TkDSTRING, TkSTAR, TkAMPER then
+ if tk1.respond_to?(:name) then
+ tk = Token(TkSYMBOL).set_text(":" + tk1.name)
+ else
+ tk = Token(TkSYMBOL).set_text(":" + tk1.text)
+ end
+
+ # remove the identifier we just read (we're about to replace it with a
+ # symbol)
+ @token_listeners.each do |obj|
+ obj.pop_token
+ end if @token_listeners
+ else
+ warn("':' not followed by identifier or operator")
+ tk = tk1
+ end
+ end
+
+ # inform any listeners of our shiny new token
+ @token_listeners.each do |obj|
+ obj.add_token(tk)
+ end if @token_listeners
+
+ tk
+ end
+
+ def get_tk_until(*tokens)
+ read = []
+
+ loop do
+ tk = get_tk
+ case tk when *tokens then unget_tk tk; break end
+ read << tk
+ end
+
+ read
+ end
+
+ ##
+ # Retrieves a String representation of the read tokens
+
+ def get_tkread
+ read = @read.join("")
+ @read = []
+ read
+ end
+
+ ##
+ # Peek equivalent for get_tkread
+
+ def peek_read
+ @read.join('')
+ end
+
+ ##
+ # Peek at the next token, but don't remove it from the stream
+
+ def peek_tk
+ unget_tk(tk = get_tk)
+ tk
+ end
+
+ ##
+ # Removes the token listener +obj+
+
+ def remove_token_listener(obj)
+ @token_listeners.delete(obj)
+ end
+
+ ##
+ # Resets the tools
+
+ def reset
+ @read = []
+ @tokens = []
+ @unget_read = []
+ @nest = 0
+ end
+
+ ##
+ # Skips whitespace tokens including newlines if +skip_nl+ is true
+
+ def skip_tkspace(skip_nl = true) # HACK dup
+ tokens = []
+
+ while TkSPACE === (tk = get_tk) or (skip_nl and TkNL === tk) do
+ tokens.push tk
+ end
+
+ unget_tk tk
+ tokens
+ end
+
+ ##
+ # Has +obj+ listen to tokens
+
+ def token_listener(obj)
+ add_token_listener obj
+ yield
+ ensure
+ remove_token_listener obj
+ end
+
+ ##
+ # Returns +tk+ to the scanner
+
+ def unget_tk(tk)
+ @tokens.unshift tk
+ @unget_read.unshift @read.pop
+
+ # Remove this token from any listeners
+ @token_listeners.each do |obj|
+ obj.pop_token
+ end if @token_listeners
+ end
+
+end
+
+
diff --git a/lib/rdoc/parser/simple.rb b/lib/rdoc/parser/simple.rb
index cdfe686718..9072667f89 100644
--- a/lib/rdoc/parser/simple.rb
+++ b/lib/rdoc/parser/simple.rb
@@ -1,5 +1,3 @@
-require 'rdoc/parser'
-
##
# 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
@@ -23,10 +21,11 @@ class RDoc::Parser::Simple < RDoc::Parser
end
##
- # Extract the file contents and attach them to the toplevel as a comment
+ # Extract the file contents and attach them to the TopLevel as a comment
def scan
@top_level.comment = remove_private_comments(@content)
+ @top_level.parser = self.class
@top_level
end
diff --git a/lib/rdoc/rdoc.rb b/lib/rdoc/rdoc.rb
index 80128ee8ba..230639fb0c 100644
--- a/lib/rdoc/rdoc.rb
+++ b/lib/rdoc/rdoc.rb
@@ -6,285 +6,430 @@ require 'rdoc/parser'
require 'rdoc/parser/simple'
require 'rdoc/parser/ruby'
require 'rdoc/parser/c'
-require 'rdoc/parser/f95'
require 'rdoc/parser/perl'
require 'rdoc/stats'
require 'rdoc/options'
-require 'rdoc/diagram'
-
require 'find'
require 'fileutils'
require 'time'
-module RDoc
+##
+# Encapsulate the production of rdoc documentation. Basically you can use this
+# as you would invoke rdoc from the command line:
+#
+# rdoc = RDoc::RDoc.new
+# 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.
+
+class RDoc::RDoc
##
- # Encapsulate the production of rdoc documentation. Basically you can use
- # this as you would invoke rdoc from the command line:
- #
- # rdoc = RDoc::RDoc.new
- # 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.
+ # File pattern to exclude
- class RDoc
+ attr_accessor :exclude
- Generator = Struct.new(:file_name, :class_name, :key)
+ ##
+ # Generator instance used for creating output
- ##
- # Accessor for statistics. Available after each call to parse_files
+ attr_accessor :generator
- attr_reader :stats
+ ##
+ # RDoc options
- ##
- # This is the list of output generator that we support
+ attr_accessor :options
- GENERATORS = {}
+ ##
+ # Accessor for statistics. Available after each call to parse_files
- $LOAD_PATH.collect do |d|
- File.expand_path d
- end.find_all do |d|
- File.directory? "#{d}/rdoc/generator"
- end.each do |dir|
- Dir.entries("#{dir}/rdoc/generator").each do |gen|
- next unless /(\w+)\.rb$/ =~ gen
- type = $1
- unless GENERATORS.has_key? type
- GENERATORS[type] = Generator.new("rdoc/generator/#{gen}",
- "#{type.upcase}".intern,
- type)
- end
- end
+ attr_reader :stats
+
+ ##
+ # This is the list of supported output generators
+
+ GENERATORS = {}
+
+ ##
+ # Add +klass+ that can generate output after parsing
+
+ def self.add_generator(klass)
+ name = klass.name.sub(/^RDoc::Generator::/, '').downcase
+ GENERATORS[name] = klass
+ end
+
+ ##
+ # Active RDoc::RDoc instance
+
+ def self.current
+ @current
+ end
+
+ ##
+ # Sets the active RDoc::RDoc instance
+
+ def self.current=(rdoc)
+ @current = rdoc
+ end
+
+ def initialize
+ @current = nil
+ @exclude = nil
+ @generator = nil
+ @last_created = nil
+ @old_siginfo = nil
+ @options = nil
+ @stats = nil
+ end
+
+ ##
+ # Report an error message and exit
+
+ def error(msg)
+ raise RDoc::Error, msg
+ end
+
+ ##
+ # Gathers a set of parseable files from the files and directories listed in
+ # +files+.
+
+ def gather_files files
+ files = ["."] if files.empty?
+
+ file_list = normalized_file_list files, true, @exclude
+
+ file_list = file_list.uniq
+
+ file_list = remove_unparseable file_list
+ end
+
+ ##
+ # Turns RDoc from stdin into HTML
+
+ def handle_pipe
+ @html = RDoc::Markup::ToHtml.new
+
+ out = @html.convert $stdin.read
+
+ $stdout.write out
+ end
+
+ ##
+ # Installs a siginfo handler that prints the current filename.
+
+ def install_siginfo_handler
+ return unless Signal.list.include? 'INFO'
+
+ @old_siginfo = trap 'INFO' do
+ puts @current if @current
end
+ end
- def initialize
- @stats = nil
+ ##
+ # Create an output dir if it doesn't exist. If it does exist, but doesn't
+ # contain the flag file <tt>created.rid</tt> then we refuse to use it, as
+ # we may clobber some manually generated documentation
+
+ def setup_output_dir(op_dir, force)
+ flag_file = output_flag_file op_dir
+
+ if File.exist? op_dir then
+ unless File.directory? op_dir then
+ error "'#{op_dir}' exists, and is not a directory"
+ end
+ begin
+ created = File.read(flag_file)
+ rescue SystemCallError
+ error "\nDirectory #{op_dir} already exists, but it looks like it\n" +
+ "isn't an RDoc directory. Because RDoc doesn't want to risk\n" +
+ "destroying any of your existing files, you'll need to\n" +
+ "specify a different output directory name (using the\n" +
+ "--op <dir> option).\n\n"
+ else
+ last = (Time.parse(created) unless force rescue nil)
+ end
+ else
+ FileUtils.mkdir_p(op_dir)
end
- ##
- # Report an error message and exit
+ last
+ end
+
+ ##
+ # Update the flag file in an output directory.
+
+ def update_output_dir(op_dir, time)
+ File.open(output_flag_file(op_dir), "w") { |f| f.puts time.rfc2822 }
+ end
+
+ ##
+ # Return the path name of the flag file in an output directory.
+
+ def output_flag_file(op_dir)
+ File.join op_dir, "created.rid"
+ end
+
+ ##
+ # The .document file contains a list of file and directory name patterns,
+ # representing candidates for documentation. It may also contain comments
+ # (starting with '#')
+
+ def parse_dot_doc_file in_dir, filename
+ # read and strip comments
+ patterns = File.read(filename).gsub(/#.*/, '')
- def error(msg)
- raise ::RDoc::Error, msg
+ result = []
+
+ patterns.split.each do |patt|
+ candidates = Dir.glob(File.join(in_dir, patt))
+ result.concat normalized_file_list(candidates)
end
- ##
- # Create an output dir if it doesn't exist. If it does exist, but doesn't
- # contain the flag file <tt>created.rid</tt> then we refuse to use it, as
- # we may clobber some manually generated documentation
+ result
+ end
+
+ ##
+ # Given a list of files and directories, create a list of all the Ruby
+ # files they contain.
+ #
+ # If +force_doc+ is true we always add the given files, if false, only
+ # add files that we guarantee we can parse. It is true when looking at
+ # files given on the command line, false when recursing through
+ # subdirectories.
+ #
+ # The effect of this is that if you want a file with a non-standard
+ # extension parsed, you must name it explicitly.
+
+ def normalized_file_list(relative_files, force_doc = false,
+ exclude_pattern = nil)
+ file_list = []
+
+ relative_files.each do |rel_file_name|
+ next if exclude_pattern && exclude_pattern =~ rel_file_name
+ stat = File.stat rel_file_name rescue next
- def setup_output_dir(op_dir, force)
- flag_file = output_flag_file(op_dir)
- if File.exist?(op_dir)
- unless File.directory?(op_dir)
- error "'#{op_dir}' exists, and is not a directory"
+ case type = stat.ftype
+ when "file"
+ next if @last_created and stat.mtime < @last_created
+
+ if force_doc or RDoc::Parser.can_parse(rel_file_name) then
+ file_list << rel_file_name.sub(/^\.\//, '')
end
- begin
- created = File.read(flag_file)
- rescue SystemCallError
- error "\nDirectory #{op_dir} already exists, but it looks like it\n" +
- "isn't an RDoc directory. Because RDoc doesn't want to risk\n" +
- "destroying any of your existing files, you'll need to\n" +
- "specify a different output directory name (using the\n" +
- "--op <dir> option).\n\n"
+ when "directory"
+ next if rel_file_name == "CVS" || rel_file_name == ".svn"
+
+ dot_doc = File.join rel_file_name, RDoc::DOT_DOC_FILENAME
+
+ if File.file?(dot_doc) then
+ file_list << parse_dot_doc_file(rel_file_name, dot_doc)
else
- last = (Time.parse(created) unless force rescue nil)
+ file_list << list_files_in_directory(rel_file_name)
end
else
- FileUtils.mkdir_p(op_dir)
+ raise RDoc::Error, "I can't deal with a #{type} #{rel_file_name}"
end
- last
end
- ##
- # Update the flag file in an output directory.
+ file_list.flatten
+ end
- def update_output_dir(op_dir, time)
- File.open(output_flag_file(op_dir), "w") {|f| f.puts time.rfc2822 }
- end
+ ##
+ # Return a list of the files to be processed in a directory. We know that
+ # this directory doesn't have a .document file, so we're looking for real
+ # files. However we may well contain subdirectories which must be tested
+ # for .document files.
- ##
- # Return the path name of the flag file in an output directory.
+ def list_files_in_directory dir
+ files = Dir.glob File.join(dir, "*")
- def output_flag_file(op_dir)
- File.join(op_dir, "created.rid")
- end
+ normalized_file_list files, false, @options.exclude
+ end
- ##
- # The .document file contains a list of file and directory name patterns,
- # representing candidates for documentation. It may also contain comments
- # (starting with '#')
+ ##
+ # Parses +filename+ and returns an RDoc::TopLevel
- def parse_dot_doc_file(in_dir, filename, options)
- # read and strip comments
- patterns = File.read(filename).gsub(/#.*/, '')
+ def parse_file filename
+ @stats.add_file filename
+ content = read_file_contents filename
- result = []
+ return unless content
- patterns.split.each do |patt|
- candidates = Dir.glob(File.join(in_dir, patt))
- result.concat(normalized_file_list(options, candidates))
- end
- result
- end
+ top_level = RDoc::TopLevel.new filename
- ##
- # Given a list of files and directories, create a list of all the Ruby
- # files they contain.
- #
- # If +force_doc+ is true we always add the given files, if false, only
- # add files that we guarantee we can parse. It is true when looking at
- # files given on the command line, false when recursing through
- # subdirectories.
- #
- # The effect of this is that if you want a file with a non-standard
- # extension parsed, you must name it explicitly.
-
- def normalized_file_list(options, relative_files, force_doc = false,
- exclude_pattern = nil)
- file_list = []
-
- relative_files.each do |rel_file_name|
- next if exclude_pattern && exclude_pattern =~ rel_file_name
- stat = File.stat(rel_file_name)
- case type = stat.ftype
- when "file"
- next if @last_created and stat.mtime < @last_created
-
- if force_doc or ::RDoc::Parser.can_parse(rel_file_name) then
- file_list << rel_file_name.sub(/^\.\//, '')
- end
- when "directory"
- next if rel_file_name == "CVS" || rel_file_name == ".svn"
- dot_doc = File.join(rel_file_name, DOT_DOC_FILENAME)
- if File.file?(dot_doc)
- file_list.concat(parse_dot_doc_file(rel_file_name, dot_doc, options))
- else
- file_list.concat(list_files_in_directory(rel_file_name, options))
- end
- else
- raise RDoc::Error, "I can't deal with a #{type} #{rel_file_name}"
- end
- end
+ parser = RDoc::Parser.for top_level, filename, content, @options, @stats
- file_list
- end
+ return unless parser
- ##
- # Return a list of the files to be processed in a directory. We know that
- # this directory doesn't have a .document file, so we're looking for real
- # files. However we may well contain subdirectories which must be tested
- # for .document files.
+ parser.scan
+ 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.
- def list_files_in_directory(dir, options)
- files = Dir.glob File.join(dir, "*")
+The internal error was:
- normalized_file_list options, files, false, options.exclude
- end
+\t(#{e.class}) #{e.message}
- ##
- # Parse each file on the command line, recursively entering directories.
+ EOF
- def parse_files(options)
- @stats = Stats.new options.verbosity
+ $stderr.puts e.backtrace.join("\n\t") if $RDOC_DEBUG
- files = options.files
- files = ["."] if files.empty?
+ raise e
+ nil
+ end
- file_list = normalized_file_list(options, files, true, options.exclude)
+ ##
+ # Parse each file on the command line, recursively entering directories.
- return [] if file_list.empty?
+ def parse_files files
+ file_list = gather_files files
- file_info = []
+ return [] if file_list.empty?
- file_list.each do |filename|
- @stats.add_file filename
+ file_info = []
- content = File.open(filename, "rb") { |f| f.read }
- content.gsub!(/\r$/, '')
+ @stats = RDoc::Stats.new file_list.size, @options.verbosity
+ @stats.begin_adding
- if defined?(::Encoding) then
- if /coding[=:]\s*([^\s;]+)/ =~ content[/\A(?:!.*\n)?(.*\n)/, 1]
- if enc = ::Encoding.find($1)
- content.force_encoding(enc)
- end
- end
- end
+ file_info = file_list.map do |filename|
+ @current = filename
+ parse_file filename
+ end.compact
- top_level = ::RDoc::TopLevel.new filename
+ @stats.done_adding
- parser = ::RDoc::Parser.for top_level, filename, content, options,
- @stats
+ file_info
+ end
- file_info << parser.scan
- end
+ ##
+ # Removes file extensions known to be unparseable from +files+
- file_info
+ def remove_unparseable files
+ files.reject do |file|
+ file =~ /\.(?:class|eps|erb|scpt\.txt|ttf|yml)$/i
end
+ end
- ##
- # Format up one or more files according to the given arguments.
- #
- # 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
- # called +doc+ below the current directory, so make sure you're somewhere
- # writable before invoking.
- #
- # Throws: RDoc::Error on error
-
- def document(argv)
- TopLevel::reset
+ ##
+ # Format up one or more files according to the given arguments.
+ #
+ # 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
+ # called +doc+ below the current directory, so make sure you're somewhere
+ # writable before invoking.
+ #
+ # Throws: RDoc::Error on error
- @options = Options.new GENERATORS
- @options.parse argv
+ def document(argv)
+ RDoc::TopLevel.reset
+ RDoc::Parser::C.reset
+ RDoc::AnyMethod.reset
- @last_created = nil
+ @options = RDoc::Options.new
+ @options.parse argv
- unless @options.all_one_file then
- @last_created = setup_output_dir @options.op_dir, @options.force_update
- end
+ if @options.pipe then
+ handle_pipe
+ exit
+ end
- start_time = Time.now
+ @exclude = @options.exclude
- file_info = parse_files @options
+ @last_created = setup_output_dir @options.op_dir, @options.force_update
- @options.title = "RDoc Documentation"
+ start_time = Time.now
- if file_info.empty?
- $stderr.puts "\nNo newer files." unless @options.quiet
- else
- @gen = @options.generator
+ file_info = parse_files @options.files
- $stderr.puts "\nGenerating #{@gen.key.upcase}..." unless @options.quiet
+ @options.title = "RDoc Documentation"
- require @gen.file_name
+ if file_info.empty?
+ $stderr.puts "\nNo newer files." unless @options.quiet
+ else
+ gen_klass = @options.generator
- gen_class = ::RDoc::Generator.const_get @gen.class_name
- @gen = gen_class.for @options
+ unless @options.quiet then
+ $stderr.puts "\nGenerating #{gen_klass.name.sub(/^.*::/, '')}..."
+ end
- pwd = Dir.pwd
+ @generator = gen_klass.for @options
- Dir.chdir @options.op_dir unless @options.all_one_file
+ pwd = Dir.pwd
+ Dir.chdir @options.op_dir do
begin
- Diagram.new(file_info, @options).draw if @options.diagram
- @gen.generate(file_info)
- update_output_dir(".", start_time)
+ self.class.current = self
+
+ @generator.generate file_info
+ update_output_dir ".", start_time
ensure
- Dir.chdir(pwd)
+ self.class.current = nil
+ end
+ end
+ end
+
+ unless @options.quiet or not @stats then
+ puts
+ @stats.print
+ end
+ end
+
+ def read_file_contents(filename)
+ content = if RUBY_VERSION >= '1.9' then
+ File.open(filename, "r:ascii-8bit") { |f| f.read }
+ else
+ File.read filename
+ end
+
+ if defined? Encoding then
+ if /coding:\s*([\w-]+)/i =~ content[/\A(?:.*\n){0,2}/]
+ if enc = ::Encoding.find($1)
+ content.force_encoding(enc)
end
end
+ end
+
+ content
+ rescue Errno::EISDIR, Errno::ENOENT
+ nil
+ end
+
+ ##
+ # Removes a siginfo handler and replaces the previous
+
+ def remove_siginfo_handler
+ return unless Signal.list.key? 'INFO'
- unless @options.quiet
- puts
- @stats.print
+ handler = @old_siginfo || 'DEFAULT'
+
+ trap 'INFO', handler
+ end
+
+end
+
+begin
+ require 'rubygems'
+
+ if Gem.respond_to? :find_files then
+ rdoc_extensions = Gem.find_files 'rdoc/discover'
+
+ rdoc_extensions.each do |extension|
+ begin
+ load extension
+ rescue => e
+ warn "error loading #{extension.inspect}: #{e.message} (#{e.class})"
end
end
end
+rescue LoadError
end
+# require built-in generators after discovery in case they've been replaced
+require 'rdoc/generator/darkfish'
+require 'rdoc/generator/ri'
+
diff --git a/lib/rdoc/require.rb b/lib/rdoc/require.rb
new file mode 100644
index 0000000000..407b55af35
--- /dev/null
+++ b/lib/rdoc/require.rb
@@ -0,0 +1,32 @@
+require 'rdoc/code_object'
+
+##
+# A file loaded by \#require
+
+class RDoc::Require < RDoc::CodeObject
+
+ ##
+ # Name of the required file
+
+ attr_accessor :name
+
+ ##
+ # Creates a new Require that loads +name+ with +comment+
+
+ def initialize(name, comment)
+ super()
+ @name = name.gsub(/'|"/, "") #'
+ self.comment = comment
+ end
+
+ def inspect # :nodoc:
+ "#<%s:0x%x require '%s' in %s>" % [
+ self.class,
+ object_id,
+ @name,
+ parent_file_name,
+ ]
+ end
+
+end
+
diff --git a/lib/rdoc/ri.rb b/lib/rdoc/ri.rb
index a3a858e673..17da3fbe83 100644
--- a/lib/rdoc/ri.rb
+++ b/lib/rdoc/ri.rb
@@ -1,8 +1,18 @@
require 'rdoc'
+##
+# Namespace for the ri command line tool's implementation.
+#
+# See <tt>ri --help</tt> for details.
+
module RDoc::RI
+ ##
+ # Base RI error class
+
class Error < RDoc::Error; end
end
+require 'rdoc/ri/store'
+
diff --git a/lib/rdoc/ri/cache.rb b/lib/rdoc/ri/cache.rb
deleted file mode 100644
index 06177a00de..0000000000
--- a/lib/rdoc/ri/cache.rb
+++ /dev/null
@@ -1,187 +0,0 @@
-require 'rdoc/ri'
-
-class RDoc::RI::ClassEntry
-
- attr_reader :name
- attr_reader :path_names
-
- def initialize(path_name, name, in_class)
- @path_names = [ path_name ]
- @name = name
- @in_class = in_class
- @class_methods = []
- @instance_methods = []
- @inferior_classes = []
- end
-
- # We found this class in more than one place, so add
- # in the name from there.
- def add_path(path)
- @path_names << path
- end
-
- # read in our methods and any classes
- # and modules in our namespace. Methods are
- # stored in files called name-c|i.yaml,
- # where the 'name' portion is the external
- # form of the method name and the c|i is a class|instance
- # flag
-
- def load_from(dir)
- Dir.foreach(dir) do |name|
- next if name =~ /^\./
-
- # convert from external to internal form, and
- # extract the instance/class flag
-
- if name =~ /^(.*?)-(c|i).yaml$/
- external_name = $1
- is_class_method = $2 == "c"
- internal_name = RDoc::RI::Writer.external_to_internal(external_name)
- list = is_class_method ? @class_methods : @instance_methods
- path = File.join(dir, name)
- list << RDoc::RI::MethodEntry.new(path, internal_name, is_class_method, self)
- else
- full_name = File.join(dir, name)
- if File.directory?(full_name)
- inf_class = @inferior_classes.find {|c| c.name == name }
- if inf_class
- inf_class.add_path(full_name)
- else
- inf_class = RDoc::RI::ClassEntry.new(full_name, name, self)
- @inferior_classes << inf_class
- end
- inf_class.load_from(full_name)
- end
- end
- end
- end
-
- # Return a list of any classes or modules that we contain
- # that match a given string
-
- def contained_modules_matching(name)
- @inferior_classes.find_all {|c| c.name[name]}
- end
-
- def classes_and_modules
- @inferior_classes
- end
-
- # Return an exact match to a particular name
- def contained_class_named(name)
- @inferior_classes.find {|c| c.name == name}
- end
-
- # return the list of local methods matching name
- # We're split into two because we need distinct behavior
- # when called from the _toplevel_
- def methods_matching(name, is_class_method)
- local_methods_matching(name, is_class_method)
- end
-
- # Find methods matching 'name' in ourselves and in
- # any classes we contain
- def recursively_find_methods_matching(name, is_class_method)
- res = local_methods_matching(name, is_class_method)
- @inferior_classes.each do |c|
- res.concat(c.recursively_find_methods_matching(name, is_class_method))
- end
- res
- end
-
-
- # Return our full name
- def full_name
- res = @in_class.full_name
- res << "::" unless res.empty?
- res << @name
- end
-
- # Return a list of all out method names
- def all_method_names
- res = @class_methods.map {|m| m.full_name }
- @instance_methods.each {|m| res << m.full_name}
- res
- end
-
- private
-
- # Return a list of all our methods matching a given string.
- # Is +is_class_methods+ if 'nil', we don't care if the method
- # is a class method or not, otherwise we only return
- # those methods that match
- def local_methods_matching(name, is_class_method)
-
- list = case is_class_method
- when nil then @class_methods + @instance_methods
- when true then @class_methods
- when false then @instance_methods
- else fail "Unknown is_class_method: #{is_class_method.inspect}"
- end
-
- list.find_all {|m| m.name; m.name[name]}
- end
-end
-
-##
-# A TopLevelEntry is like a class entry, but when asked to search for methods
-# searches all classes, not just itself
-
-class RDoc::RI::TopLevelEntry < RDoc::RI::ClassEntry
- def methods_matching(name, is_class_method)
- res = recursively_find_methods_matching(name, is_class_method)
- end
-
- def full_name
- ""
- end
-
- def module_named(name)
-
- end
-
-end
-
-class RDoc::RI::MethodEntry
- attr_reader :name
- attr_reader :path_name
-
- def initialize(path_name, name, is_class_method, in_class)
- @path_name = path_name
- @name = name
- @is_class_method = is_class_method
- @in_class = in_class
- end
-
- def full_name
- res = @in_class.full_name
- unless res.empty?
- if @is_class_method
- res << "::"
- else
- res << "#"
- end
- end
- res << @name
- end
-end
-
-##
-# We represent everything known about all 'ri' files accessible to this program
-
-class RDoc::RI::Cache
-
- attr_reader :toplevel
-
- def initialize(dirs)
- # At the top level we have a dummy module holding the
- # overall namespace
- @toplevel = RDoc::RI::TopLevelEntry.new('', '::', nil)
-
- dirs.each do |dir|
- @toplevel.load_from(dir)
- end
- end
-
-end
diff --git a/lib/rdoc/ri/descriptions.rb b/lib/rdoc/ri/descriptions.rb
deleted file mode 100644
index 467b7de2a9..0000000000
--- a/lib/rdoc/ri/descriptions.rb
+++ /dev/null
@@ -1,156 +0,0 @@
-require 'yaml'
-require 'rdoc/markup/fragments'
-require 'rdoc/ri'
-
-##
-# Descriptions are created by RDoc (in ri_generator) and written out in
-# serialized form into the documentation tree. ri then reads these to generate
-# the documentation
-
-class RDoc::RI::NamedThing
- attr_reader :name
- def initialize(name)
- @name = name
- end
-
- def <=>(other)
- @name <=> other.name
- end
-
- def hash
- @name.hash
- end
-
- def eql?(other)
- @name.eql?(other)
- end
-end
-
-class RDoc::RI::AliasName < RDoc::RI::NamedThing; end
-
-class RDoc::RI::Attribute < RDoc::RI::NamedThing
- attr_reader :rw, :comment
-
- def initialize(name, rw, comment)
- super(name)
- @rw = rw
- @comment = comment
- end
-end
-
-class RDoc::RI::Constant < RDoc::RI::NamedThing
- attr_reader :value, :comment
-
- def initialize(name, value, comment)
- super(name)
- @value = value
- @comment = comment
- end
-end
-
-class RDoc::RI::IncludedModule < RDoc::RI::NamedThing; end
-
-class RDoc::RI::MethodSummary < RDoc::RI::NamedThing
- def initialize(name="")
- super
- end
-end
-
-class RDoc::RI::Description
- attr_accessor :name
- attr_accessor :full_name
- attr_accessor :comment
-
- def serialize
- self.to_yaml
- end
-
- def self.deserialize(from)
- YAML.load(from)
- end
-
- def <=>(other)
- @name <=> other.name
- end
-end
-
-class RDoc::RI::ModuleDescription < RDoc::RI::Description
-
- attr_accessor :class_methods
- attr_accessor :class_method_extensions
- attr_accessor :instance_methods
- attr_accessor :instance_method_extensions
- attr_accessor :attributes
- attr_accessor :constants
- attr_accessor :includes
-
- # merge in another class description into this one
- def merge_in(old)
- merge(@class_methods, old.class_methods)
- merge(@instance_methods, old.instance_methods)
- merge(@attributes, old.attributes)
- merge(@constants, old.constants)
- merge(@includes, old.includes)
- if @comment.nil? || @comment.empty?
- @comment = old.comment
- else
- unless old.comment.nil? or old.comment.empty? then
- if @comment.nil? or @comment.empty? then
- @comment = old.comment
- else
- @comment << RDoc::Markup::Flow::RULE.new
- @comment.concat old.comment
- end
- end
- end
- end
-
- def display_name
- "Module"
- end
-
- # the 'ClassDescription' subclass overrides this
- # to format up the name of a parent
- def superclass_string
- nil
- end
-
- private
-
- def merge(into, from)
- names = {}
- into.each {|i| names[i.name] = i }
- from.each {|i| names[i.name] = i }
- into.replace(names.keys.sort.map {|n| names[n]})
- end
-end
-
-class RDoc::RI::ClassDescription < RDoc::RI::ModuleDescription
- attr_accessor :superclass
-
- def display_name
- "Class"
- end
-
- def superclass_string
- if @superclass && @superclass != "Object"
- @superclass
- else
- nil
- end
- end
-end
-
-class RDoc::RI::MethodDescription < RDoc::RI::Description
-
- attr_accessor :is_class_method
- attr_accessor :visibility
- attr_accessor :block_params
- attr_accessor :is_singleton
- attr_accessor :aliases
- attr_accessor :is_alias_for
- attr_accessor :params
- attr_accessor :source_path
-
-end
-
diff --git a/lib/rdoc/ri/display.rb b/lib/rdoc/ri/display.rb
deleted file mode 100644
index f6b647fbc1..0000000000
--- a/lib/rdoc/ri/display.rb
+++ /dev/null
@@ -1,392 +0,0 @@
-require 'rdoc/ri'
-
-# readline support might not be present, so be careful
-# when requiring it.
-begin
- require('readline')
- require('abbrev')
- CAN_USE_READLINE = true # HACK use an RDoc namespace constant
-rescue LoadError
- CAN_USE_READLINE = false
-end
-
-##
-# This is a kind of 'flag' module. If you want to write your own 'ri' display
-# module (perhaps because you're writing an IDE), you write a class which
-# implements the various 'display' methods in RDoc::RI::DefaultDisplay, and
-# include the RDoc::RI::Display module in that class.
-#
-# To access your class from the command line, you can do
-#
-# ruby -r <your source file> ../ri ....
-
-module RDoc::RI::Display
-
- @@display_class = nil
-
- def self.append_features(display_class)
- @@display_class = display_class
- end
-
- def self.new(*args)
- @@display_class.new(*args)
- end
-
-end
-
-##
-# A paging display module. Uses the RDoc::RI::Formatter class to do the actual
-# presentation.
-
-class RDoc::RI::DefaultDisplay
-
- include RDoc::RI::Display
-
- def initialize(formatter, width, use_stdout, output = $stdout)
- @use_stdout = use_stdout
- @formatter = formatter.new output, width, " "
- end
-
- ##
- # Display information about +klass+. Fetches additional information from
- # +ri_reader+ as necessary.
-
- def display_class_info(klass)
- page do
- superclass = klass.superclass
-
- if superclass
- superclass = " < " + superclass
- else
- superclass = ""
- end
-
- @formatter.draw_line(klass.display_name + ": " +
- klass.full_name + superclass)
-
- display_flow(klass.comment)
- @formatter.draw_line
-
- unless klass.includes.empty?
- @formatter.blankline
- @formatter.display_heading("Includes:", 2, "")
- incs = []
-
- klass.includes.each do |inc|
- incs << inc.name
- end
-
- @formatter.wrap(incs.sort.join(', '))
- end
-
- unless klass.constants.empty?
- @formatter.blankline
- @formatter.display_heading("Constants:", 2, "")
-
- constants = klass.constants.sort_by { |constant| constant.name }
-
- constants.each do |constant|
- @formatter.wrap "#{constant.name} = #{constant.value}"
- if constant.comment then
- @formatter.indent do
- @formatter.display_flow constant.comment
- end
- else
- @formatter.break_to_newline
- end
- end
- end
-
- unless klass.attributes.empty? then
- @formatter.blankline
- @formatter.display_heading 'Attributes:', 2, ''
-
- attributes = klass.attributes.sort_by { |attribute| attribute.name }
-
- attributes.each do |attribute|
- if attribute.comment then
- @formatter.wrap "#{attribute.name} (#{attribute.rw}):"
- @formatter.indent do
- @formatter.display_flow attribute.comment
- end
- else
- @formatter.wrap "#{attribute.name} (#{attribute.rw})"
- @formatter.break_to_newline
- end
- end
- end
-
- return display_class_method_list(klass)
- end
- end
-
- ##
- # Given a Hash mapping a class' methods to method types (returned by
- # display_class_method_list), this method allows the user to
- # choose one of the methods.
-
- def get_class_method_choice(method_map)
- if CAN_USE_READLINE
- # prepare abbreviations for tab completion
- abbreviations = method_map.keys.abbrev
- Readline.completion_proc = proc do |string|
- abbreviations.values.uniq.grep(/^#{string}/)
- end
- end
-
- @formatter.raw_print_line "\nEnter the method name you want.\n"
- @formatter.raw_print_line "Class methods can be preceeded by '::' and instance methods by '#'.\n"
-
- if CAN_USE_READLINE
- @formatter.raw_print_line "You can use tab to autocomplete.\n"
- @formatter.raw_print_line "Enter a blank line to exit.\n"
-
- choice_string = Readline.readline(">> ").strip
- else
- @formatter.raw_print_line "Enter a blank line to exit.\n"
- @formatter.raw_print_line ">> "
- choice_string = $stdin.gets.strip
- end
-
- if choice_string == ''
- return nil
- else
- class_or_instance = method_map[choice_string]
-
- if class_or_instance
- # If the user's choice is not preceeded by a '::' or a '#', figure
- # out whether they want a class or an instance method and decorate
- # the choice appropriately.
- if(choice_string =~ /^[a-zA-Z]/)
- if(class_or_instance == :class)
- choice_string = "::#{choice_string}"
- else
- choice_string = "##{choice_string}"
- end
- end
-
- return choice_string
- else
- @formatter.raw_print_line "No method matched '#{choice_string}'.\n"
- return nil
- end
- end
- end
-
-
- ##
- # Display methods on +klass+
- # Returns a hash mapping method name to method contents (HACK?)
-
- def display_class_method_list(klass)
- method_map = {}
-
- class_data = [
- :class_methods,
- :class_method_extensions,
- :instance_methods,
- :instance_method_extensions,
- ]
-
- class_data.each do |data_type|
- data = klass.send data_type
-
- unless data.nil? or data.empty? then
- @formatter.blankline
-
- heading = data_type.to_s.split('_').join(' ').capitalize << ':'
- @formatter.display_heading heading, 2, ''
-
- method_names = []
- data.each do |item|
- method_names << item.name
-
- if(data_type == :class_methods ||
- data_type == :class_method_extensions) then
- method_map["::#{item.name}"] = :class
- method_map[item.name] = :class
- else
- #
- # Since we iterate over instance methods after class methods,
- # an instance method always will overwrite the unqualified
- # class method entry for a class method of the same name.
- #
- method_map["##{item.name}"] = :instance
- method_map[item.name] = :instance
- end
- end
- method_names.sort!
-
- @formatter.wrap method_names.join(', ')
- end
- end
-
- method_map
- end
- private :display_class_method_list
-
- ##
- # Display an Array of RDoc::Markup::Flow objects, +flow+.
-
- def display_flow(flow)
- if flow and not flow.empty? then
- @formatter.display_flow flow
- else
- @formatter.wrap '[no description]'
- end
- end
-
- ##
- # Display information about +method+.
-
- def display_method_info(method)
- page do
- @formatter.draw_line(method.full_name)
- display_params(method)
-
- @formatter.draw_line
- display_flow(method.comment)
-
- if method.aliases and not method.aliases.empty? then
- @formatter.blankline
- aka = "(also known as #{method.aliases.map { |a| a.name }.join(', ')})"
- @formatter.wrap aka
- end
- end
- end
-
- ##
- # Display the list of +methods+.
-
- def display_method_list(methods)
- page do
- @formatter.wrap "More than one method matched your request. You can refine your search by asking for information on one of:"
- @formatter.blankline
-
- methods.each do |method|
- @formatter.raw_print_line "#{method.full_name} [#{method.source_path}]\n"
- end
- end
- end
-
- ##
- # Display a list of +methods+ and allow the user to select one of them.
-
- def display_method_list_choice(methods)
- page do
- @formatter.wrap "More than one method matched your request. Please choose one of the possible matches."
- @formatter.blankline
-
- methods.each_with_index do |method, index|
- @formatter.raw_print_line "%3d %s [%s]\n" % [index + 1, method.full_name, method.source_path]
- end
-
- @formatter.raw_print_line ">> "
-
- choice = $stdin.gets.strip!
-
- if(choice == '')
- return
- end
-
- choice = choice.to_i
-
- if ((choice == 0) || (choice > methods.size)) then
- @formatter.raw_print_line "Invalid choice!\n"
- else
- method = methods[choice - 1]
- display_method_info(method)
- end
- end
- end
-
- ##
- # Display the params for +method+.
-
- def display_params(method)
- params = method.params
-
- if params[0,1] == "(" then
- if method.is_singleton
- params = method.full_name + params
- else
- params = method.name + params
- end
- end
-
- params.split(/\n/).each do |param|
- @formatter.wrap param
- @formatter.break_to_newline
- end
-
- @formatter.blankline
- @formatter.wrap("From #{method.source_path}")
- end
-
- ##
- # List the classes in +classes+.
-
- def list_known_classes(classes)
- if classes.empty?
- warn_no_database
- else
- page do
- @formatter.draw_line "Known classes and modules"
- @formatter.blankline
-
- @formatter.wrap classes.sort.join(', ')
- end
- end
- end
-
- ##
- # Paginates output through a pager program.
-
- def page
- if pager = setup_pager then
- begin
- orig_output = @formatter.output
- @formatter.output = pager
- yield
- ensure
- @formatter.output = orig_output
- pager.close
- end
- else
- yield
- end
- rescue Errno::EPIPE
- end
-
- ##
- # Sets up a pager program to pass output through.
-
- def setup_pager
- unless @use_stdout then
- for pager in [ ENV['PAGER'], "less", "more", 'pager' ].compact.uniq
- return IO.popen(pager, "w") rescue nil
- end
- @use_stdout = true
- nil
- end
- end
-
- ##
- # Displays a message that describes how to build RI data.
-
- def warn_no_database
- output = @formatter.output
-
- output.puts "No ri data found"
- output.puts
- output.puts "If you've installed Ruby yourself, you need to generate documentation using:"
- output.puts
- output.puts " make install-doc"
- output.puts
- output.puts "from the same place you ran `make` to build ruby."
- output.puts
- output.puts "If you installed Ruby from a packaging system, then you may need to"
- output.puts "install an additional package, or ask the packager to enable ri generation."
- end
-
-end
diff --git a/lib/rdoc/ri/driver.rb b/lib/rdoc/ri/driver.rb
index 89534a5972..1829de3432 100644
--- a/lib/rdoc/ri/driver.rb
+++ b/lib/rdoc/ri/driver.rb
@@ -1,89 +1,77 @@
+require 'abbrev'
require 'optparse'
-require 'yaml'
+
+begin
+ require 'readline'
+rescue LoadError
+end
require 'rdoc/ri'
require 'rdoc/ri/paths'
-require 'rdoc/ri/formatter'
-require 'rdoc/ri/display'
-require 'fileutils'
require 'rdoc/markup'
-require 'rdoc/markup/to_flow'
+require 'rdoc/markup/formatter'
+require 'rdoc/text'
-class RDoc::RI::Driver
+##
+# For RubyGems backwards compatibility
- #
- # This class offers both Hash and OpenStruct functionality.
- # We convert from the Core Hash to this before calling any of
- # the display methods, in order to give the display methods
- # a cleaner API for accessing the data.
- #
- class OpenStructHash < Hash
- #
- # This method converts from a Hash to an OpenStructHash.
- #
- def self.convert(object)
- case object
- when Hash then
- new_hash = new # Convert Hash -> OpenStructHash
-
- object.each do |key, value|
- new_hash[key] = convert(value)
- end
+require 'rdoc/ri/formatter'
- new_hash
- when Array then
- object.map do |element|
- convert(element)
- end
- else
- object
- end
- end
-
- def merge_enums(other)
- other.each do |k, v|
- if self[k] then
- case v
- when Array then
- # HACK dunno
- if String === self[k] and self[k].empty? then
- self[k] = v
- else
- self[k] += v
- end
- when Hash then
- self[k].update v
- else
- # do nothing
- end
- else
- self[k] = v
- end
- end
- end
+##
+# The RI driver implements the command-line ri tool.
+#
+# The driver supports:
+# * loading RI data from:
+# * Ruby's standard library
+# * RubyGems
+# * ~/.rdoc
+# * A user-supplied directory
+# * Paging output (uses RI_PAGER environment variable, PAGER environment
+# variable or the less, more and pager programs)
+# * Interactive mode with tab-completion
+# * Abbreviated names (ri Zl shows Zlib documentation)
+# * Colorized output
+# * Merging output from multiple RI data sources
- def method_missing method, *args
- self[method.to_s]
- end
- end
+class RDoc::RI::Driver
+
+ ##
+ # Base Driver error class
class Error < RDoc::RI::Error; end
+ ##
+ # Raised when a name isn't found in the ri data stores
+
class NotFoundError < Error
- def message
+
+ ##
+ # Name that wasn't found
+
+ alias name message
+
+ def message # :nodoc:
"Nothing known about #{super}"
end
end
- attr_accessor :homepath # :nodoc:
+ attr_accessor :stores
+
+ ##
+ # Controls the user of the pager vs $stdout
+
+ attr_accessor :use_stdout
+
+ ##
+ # Default options for ri
def self.default_options
options = {}
options[:use_stdout] = !$stdout.tty?
options[:width] = 72
- options[:formatter] = RDoc::RI::Formatter.for 'plain'
options[:interactive] = false
options[:use_cache] = true
+ options[:profile] = false
# By default all standard paths are used.
options[:use_system] = true
@@ -95,27 +83,33 @@ class RDoc::RI::Driver
return options
end
- def self.process_args(argv)
+ ##
+ # Dump +data_path+ using pp
+
+ def self.dump data_path
+ require 'pp'
+
+ open data_path, 'rb' do |io|
+ pp Marshal.load(io.read)
+ end
+ end
+
+ ##
+ # Parses +argv+ and returns a Hash of options
+
+ def self.process_args argv
options = default_options
opts = OptionParser.new do |opt|
+ opt.accept File do |file,|
+ File.readable?(file) and not File.directory?(file) and file
+ end
+
opt.program_name = File.basename $0
opt.version = RDoc::VERSION
opt.release = nil
opt.summary_indent = ' ' * 4
- directories = [
- RDoc::RI::Paths::SYSDIR,
- RDoc::RI::Paths::SITEDIR,
- RDoc::RI::Paths::HOMEDIR
- ]
-
- if RDoc::RI::Paths::GEMDIRS then
- Gem.path.each do |dir|
- directories << "#{dir}/doc/*/ri"
- end
- end
-
opt.banner = <<-EOT
Usage: #{opt.program_name} [options] [names...]
@@ -142,9 +136,9 @@ punctuation:
#{opt.program_name} 'Array.[]'
#{opt.program_name} compact\\!
-By default ri searches for documentation in the following directories:
+To see the default directories ri will search, run:
- #{directories.join "\n "}
+ #{opt.program_name} --list-doc-dirs
Specifying the --system, --site, --home, --gems or --doc-dir options will
limit ri to searching only the specified directories.
@@ -154,17 +148,60 @@ Options may also be set in the 'RI' environment variable.
opt.separator nil
opt.separator "Options:"
+
opt.separator nil
- opt.on("--fmt=FORMAT", "--format=FORMAT", "-f",
- RDoc::RI::Formatter::FORMATTERS.keys,
- "Format to use when displaying output:",
- " #{RDoc::RI::Formatter.list}",
- "Use 'bs' (backspace) with most pager",
- "programs. To use ANSI, either disable the",
- "pager or tell the pager to allow control",
- "characters.") do |value|
- options[:formatter] = RDoc::RI::Formatter.for value
+ formatters = RDoc::Markup.constants.grep(/^To[A-Z][a-z]+$/).sort
+ formatters = formatters.sort.map do |formatter|
+ formatter.to_s.sub('To', '').downcase
+ end
+
+ opt.on("--format=NAME", "-f",
+ "Uses the selected formatter. The default",
+ "formatter is bs for paged output and ansi",
+ "otherwise. Valid formatters are:",
+ formatters.join(' '), formatters) do |value|
+ options[:formatter] = RDoc::Markup.const_get "To#{value.capitalize}"
+ end
+
+ opt.separator nil
+
+ opt.on("--no-pager", "-T",
+ "Send output directly to stdout,",
+ "rather than to a pager.") do
+ options[:use_stdout] = true
+ end
+
+ opt.separator nil
+
+ opt.on("--width=WIDTH", "-w", OptionParser::DecimalInteger,
+ "Set the width of the output.") do |value|
+ options[:width] = value
+ end
+
+ opt.separator nil
+
+ opt.on("--interactive", "-i",
+ "In interactive mode you can repeatedly",
+ "look up methods with autocomplete.") do
+ options[:interactive] = true
+ end
+
+ opt.separator nil
+
+ opt.on("--[no-]profile",
+ "Run with the ruby profiler") do |value|
+ options[:profile] = value
+ end
+
+ opt.separator nil
+ opt.separator "Data source options:"
+ opt.separator nil
+
+ opt.on("--list-doc-dirs",
+ "List the directories from which ri will",
+ "source documentation on stdout and exit.") do
+ options[:list_doc_dirs] = true
end
opt.separator nil
@@ -184,21 +221,11 @@ Options may also be set in the 'RI' environment variable.
opt.separator nil
- opt.on("--[no-]use-cache",
- "Whether or not to use ri's cache.",
- "True by default.") do |value|
- options[:use_cache] = value
- end
-
- opt.separator nil
-
opt.on("--no-standard-docs",
"Do not include documentation from",
"the Ruby standard library, site_lib,",
"installed gems, or ~/.rdoc.",
- "Equivalent to specifying",
- "the options --no-system, --no-site, --no-gems,",
- "and --no-home") do
+ "Use with --doc-dir") do
options[:use_system] = false
options[:use_site] = false
options[:use_gems] = false
@@ -239,38 +266,12 @@ Options may also be set in the 'RI' environment variable.
end
opt.separator nil
-
- opt.on("--list-doc-dirs",
- "List the directories from which ri will",
- "source documentation on stdout and exit.") do
- options[:list_doc_dirs] = true
- end
-
- opt.separator nil
-
- opt.on("--no-pager", "-T",
- "Send output directly to stdout,",
- "rather than to a pager.") do
- options[:use_stdout] = true
- end
-
- opt.on("--interactive", "-i",
- "This makes ri go into interactive mode.",
- "When ri is in interactive mode it will",
- "allow the user to disambiguate lists of",
- "methods in case multiple methods match",
- "against a method search string. It also",
- "will allow the user to enter in a method",
- "name (with auto-completion, if readline",
- "is supported) when viewing a class.") do
- options[:interactive] = true
- end
-
+ opt.separator "Debug options:"
opt.separator nil
- opt.on("--width=WIDTH", "-w", OptionParser::DecimalInteger,
- "Set the width of the output.") do |value|
- options[:width] = value
+ opt.on("--dump=CACHE", File,
+ "Dumps data from an ri cache or data file") do |value|
+ options[:dump_path] = value
end
end
@@ -280,7 +281,6 @@ Options may also be set in the 'RI' environment variable.
options[:names] = argv
- options[:formatter] ||= RDoc::RI::Formatter.for('plain')
options[:use_stdout] ||= !$stdout.tty?
options[:use_stdout] ||= options[:interactive]
options[:width] ||= 72
@@ -294,376 +294,765 @@ Options may also be set in the 'RI' environment variable.
exit 1
end
- def self.run(argv = ARGV)
+ ##
+ # Runs the ri command line executable using +argv+
+
+ def self.run argv = ARGV
options = process_args argv
+
+ if options[:dump_path] then
+ dump options[:dump_path]
+ return
+ end
+
ri = new options
ri.run
end
- def initialize(initial_options={})
+ ##
+ # Creates a new driver using +initial_options+ from ::process_args
+
+ def initialize initial_options = {}
+ @paging = false
+ @classes = nil
+
options = self.class.default_options.update(initial_options)
+ @formatter_klass = options[:formatter]
+
+ require 'profile' if options[:profile]
+
@names = options[:names]
- @class_cache_name = 'classes'
- @doc_dirs = RDoc::RI::Paths.path(options[:use_system],
- options[:use_site],
- options[:use_home],
- options[:use_gems],
- options[:extra_doc_dirs])
+ @doc_dirs = []
+ @stores = []
- @homepath = RDoc::RI::Paths.raw_path(false, false, true, false).first
- @homepath = @homepath.sub(/\.rdoc/, '.ri')
- @sys_dir = RDoc::RI::Paths.raw_path(true, false, false, false).first
- @list_doc_dirs = options[:list_doc_dirs]
+ RDoc::RI::Paths.each(options[:use_system], options[:use_site],
+ options[:use_home], options[:use_gems],
+ *options[:extra_doc_dirs]) do |path, type|
+ @doc_dirs << path
- FileUtils.mkdir_p cache_file_path unless File.directory? cache_file_path
- @cache_doc_dirs_path = File.join cache_file_path, ".doc_dirs"
+ store = RDoc::RI::Store.new path, type
+ store.load_cache
+ @stores << store
+ end
- @use_cache = options[:use_cache]
- @class_cache = nil
+ @list_doc_dirs = options[:list_doc_dirs]
@interactive = options[:interactive]
- @display = RDoc::RI::DefaultDisplay.new(options[:formatter],
- options[:width],
- options[:use_stdout])
+ @use_stdout = options[:use_stdout]
end
- def class_cache
- return @class_cache if @class_cache
+ ##
+ # Adds paths for undocumented classes +also_in+ to +out+
- # Get the documentation directories used to make the cache in order to see
- # whether the cache is valid for the current ri instantiation.
- if(File.readable?(@cache_doc_dirs_path))
- cache_doc_dirs = IO.read(@cache_doc_dirs_path).split("\n")
- else
- cache_doc_dirs = []
- end
-
- newest = map_dirs('created.rid') do |f|
- File.mtime f if test ?f, f
- end.max
-
- # An up to date cache file must have been created more recently than
- # the last modification of any of the documentation directories. It also
- # must have been created with the same documentation directories
- # as those from which ri currently is sourcing documentation.
- up_to_date = (File.exist?(class_cache_file_path) and
- newest and newest < File.mtime(class_cache_file_path) and
- (cache_doc_dirs == @doc_dirs))
-
- if up_to_date and @use_cache then
- open class_cache_file_path, 'rb' do |fp|
- begin
- @class_cache = Marshal.load fp.read
- rescue
- #
- # This shouldn't be necessary, since the up_to_date logic above
- # should force the cache to be recreated when a new version of
- # rdoc is installed. This seems like a worthwhile enhancement
- # to ri's robustness, however.
- #
- $stderr.puts "Error reading the class cache; recreating the class cache!"
- @class_cache = create_class_cache
- end
- end
- else
- @class_cache = create_class_cache
+ def add_also_in out, also_in
+ return if also_in.empty?
+
+ out << RDoc::Markup::Rule.new(1)
+ out << RDoc::Markup::Paragraph.new("Also found in:")
+
+ paths = RDoc::Markup::Verbatim.new
+ also_in.each do |store|
+ paths.parts.push ' ', store.friendly_path, "\n"
end
+ out << paths
+ end
- @class_cache
+ ##
+ # Adds a class header to +out+ for class +name+ which is described in
+ # +classes+.
+
+ def add_class out, name, classes
+ heading = if classes.all? { |klass| klass.module? } then
+ name
+ else
+ superclass = classes.map do |klass|
+ klass.superclass unless klass.module?
+ end.compact.shift || 'Object'
+
+ "#{name} < #{superclass}"
+ end
+
+ out << RDoc::Markup::Heading.new(1, heading)
+ out << RDoc::Markup::BlankLine.new
end
- def create_class_cache
- class_cache = OpenStructHash.new
+ ##
+ # Adds "(from ...)" to +out+ for +store+
- if(@use_cache)
- # Dump the documentation directories to a file in the cache, so that
- # we only will use the cache for future instantiations with identical
- # documentation directories.
- File.open @cache_doc_dirs_path, "wb" do |fp|
- fp << @doc_dirs.join("\n")
- end
- end
+ def add_from out, store
+ out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})")
+ end
- classes = map_dirs('**/cdesc*.yaml') { |f| Dir[f] }
- warn "Updating class cache with #{classes.size} classes..."
- populate_class_cache class_cache, classes
+ ##
+ # Adds +includes+ to +out+
- write_cache class_cache, class_cache_file_path
+ def add_includes out, includes
+ return if includes.empty?
- class_cache
- end
+ out << RDoc::Markup::Rule.new(1)
+ out << RDoc::Markup::Heading.new(1, "Includes:")
- def populate_class_cache(class_cache, classes, extension = false)
- classes.each do |cdesc|
- desc = read_yaml cdesc
- klassname = desc["full_name"]
+ includes.each do |modules, store|
+ if modules.length == 1 then
+ include = modules.first
+ name = include.name
+ path = store.friendly_path
+ out << RDoc::Markup::Paragraph.new("#{name} (from #{path})")
- unless class_cache.has_key? klassname then
- desc["display_name"] = "Class"
- desc["sources"] = [cdesc]
- desc["instance_method_extensions"] = []
- desc["class_method_extensions"] = []
- class_cache[klassname] = desc
+ if include.comment then
+ out << RDoc::Markup::BlankLine.new
+ out << include.comment
+ end
else
- klass = class_cache[klassname]
+ out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})")
+
+ wout, with = modules.partition { |incl| incl.comment.empty? }
- if extension then
- desc["instance_method_extensions"] = desc.delete "instance_methods"
- desc["class_method_extensions"] = desc.delete "class_methods"
+ out << RDoc::Markup::BlankLine.new unless with.empty?
+
+ with.each do |incl|
+ out << RDoc::Markup::Paragraph.new(incl.name)
+ out << RDoc::Markup::BlankLine.new
+ out << incl.comment
end
- klass.merge_enums desc
- klass["sources"] << cdesc
+ unless wout.empty? then
+ verb = RDoc::Markup::Verbatim.new
+
+ wout.each do |incl|
+ verb.push ' ', incl.name, "\n"
+ end
+
+ out << verb
+ end
end
end
end
- def class_cache_file_path
- File.join cache_file_path, @class_cache_name
+ ##
+ # Adds a list of +methods+ to +out+ with a heading of +name+
+
+ def add_method_list out, methods, name
+ return unless methods
+
+ out << RDoc::Markup::Heading.new(1, "#{name}:")
+ out << RDoc::Markup::BlankLine.new
+
+ out.push(*methods.map do |method|
+ RDoc::Markup::Verbatim.new ' ', method
+ end)
+
+ out << RDoc::Markup::BlankLine.new
end
- def cache_file_for(klassname)
- File.join cache_file_path, klassname.gsub(/:+/, "-")
+ ##
+ # Returns ancestor classes of +klass+
+
+ def ancestors_of klass
+ ancestors = []
+
+ unexamined = [klass]
+ seen = []
+
+ loop do
+ break if unexamined.empty?
+ current = unexamined.shift
+ seen << current
+
+ stores = classes[current]
+
+ break unless stores and not stores.empty?
+
+ klasses = stores.map do |store|
+ store.ancestors[current]
+ end.flatten.uniq
+
+ klasses = klasses - seen
+
+ ancestors.push(*klasses)
+ unexamined.push(*klasses)
+ end
+
+ ancestors.reverse
end
- def cache_file_path
- File.join @homepath, 'cache'
+ ##
+ # For RubyGems backwards compatibility
+
+ def class_cache # :nodoc:
end
- def display_class(name)
- klass = class_cache[name]
- @display.display_class_info klass
+ ##
+ # Hash mapping a known class or module to the stores it can be loaded from
+
+ def classes
+ return @classes if @classes
+
+ @classes = {}
+
+ @stores.each do |store|
+ store.cache[:modules].each do |mod|
+ # using default block causes searched-for modules to be added
+ @classes[mod] ||= []
+ @classes[mod] << store
+ end
+ end
+
+ @classes
end
- def display_method(method)
- @display.display_method_info method
+ ##
+ # Completes +name+ based on the caches. For Readline
+
+ def complete name
+ klasses = classes.keys
+ completions = []
+
+ klass, selector, method = parse_name name
+
+ # may need to include Foo when given Foo::
+ klass_name = method ? name : klass
+
+ if name !~ /#|\./ then
+ completions.push(*klasses.grep(/^#{klass_name}/))
+ elsif selector then
+ completions << klass if classes.key? klass
+ elsif classes.key? klass_name then
+ completions << klass_name
+ end
+
+ if completions.include? klass and name =~ /#|\.|::/ then
+ methods = list_methods_matching name
+
+ if not methods.empty? then
+ # remove Foo if given Foo:: and a method was found
+ completions.delete klass
+ elsif selector then
+ # replace Foo with Foo:: as given
+ completions.delete klass
+ completions << "#{klass}#{selector}"
+ end
+
+ completions.push(*methods)
+ end
+
+ completions.sort
end
- def get_info_for(arg)
- @names = [arg]
- run
+ ##
+ # Converts +document+ to text and writes it to the pager
+
+ def display document
+ page do |io|
+ text = document.accept formatter
+
+ io.write text
+ end
end
- def load_cache_for(klassname)
- path = cache_file_for klassname
+ ##
+ # Outputs formatted RI data for class +name+. Groups undocumented classes
- cache = nil
+ def display_class name
+ return if name =~ /#|\./
- if File.exist? path and
- File.mtime(path) >= File.mtime(class_cache_file_path) and
- @use_cache then
- open path, 'rb' do |fp|
- begin
- cache = Marshal.load fp.read
- rescue
- #
- # The cache somehow is bad. Recreate the cache.
- #
- $stderr.puts "Error reading the cache for #{klassname}; recreating the cache!"
- cache = create_cache_for klassname, path
- end
+ klasses = []
+ includes = []
+
+ found = @stores.map do |store|
+ begin
+ klass = store.load_class name
+ klasses << klass
+ includes << [klass.includes, store] if klass.includes
+ [store, klass]
+ rescue Errno::ENOENT
end
- else
- cache = create_cache_for klassname, path
+ end.compact
+
+ return if found.empty?
+
+ also_in = []
+
+ includes.reject! do |modules,| modules.empty? end
+
+ out = RDoc::Markup::Document.new
+
+ add_class out, name, klasses
+
+ add_includes out, includes
+
+ found.each do |store, klass|
+ comment = klass.comment
+ class_methods = store.class_methods[klass.full_name]
+ instance_methods = store.instance_methods[klass.full_name]
+ attributes = store.attributes[klass.full_name]
+
+ if comment.empty? and !(instance_methods or class_methods) then
+ also_in << store
+ next
+ end
+
+ add_from out, store
+
+ unless comment.empty? then
+ out << RDoc::Markup::Rule.new(1)
+ out << comment
+ end
+
+ if class_methods or instance_methods or not klass.constants.empty? then
+ out << RDoc::Markup::Rule.new
+ end
+
+ unless klass.constants.empty? then
+ out << RDoc::Markup::Heading.new(1, "Constants:")
+ out << RDoc::Markup::BlankLine.new
+ list = RDoc::Markup::List.new :NOTE
+
+ constants = klass.constants.sort_by { |constant| constant.name }
+
+ list.push(*constants.map do |constant|
+ parts = constant.comment.parts if constant.comment
+ parts << RDoc::Markup::Paragraph.new('[not documented]') if
+ parts.empty?
+
+ RDoc::Markup::ListItem.new(constant.name, *parts)
+ end)
+
+ out << list
+ end
+
+ add_method_list out, class_methods, 'Class methods'
+ add_method_list out, instance_methods, 'Instance methods'
+ add_method_list out, attributes, 'Attributes'
+
+ out << RDoc::Markup::BlankLine.new
end
- cache
+ add_also_in out, also_in
+
+ display out
end
- def create_cache_for(klassname, path)
- klass = class_cache[klassname]
- return nil unless klass
+ ##
+ # Outputs formatted RI data for method +name+
+
+ def display_method name
+ found = load_methods_matching name
- method_files = klass["sources"]
- cache = OpenStructHash.new
+ raise NotFoundError, name if found.empty?
- method_files.each do |f|
- system_file = f.index(@sys_dir) == 0
- Dir[File.join(File.dirname(f), "*")].each do |yaml|
- next unless yaml =~ /yaml$/
- next if yaml =~ /cdesc-[^\/]+yaml$/
+ out = RDoc::Markup::Document.new
- method = read_yaml yaml
+ out << RDoc::Markup::Heading.new(1, name)
+ out << RDoc::Markup::BlankLine.new
- if system_file then
- method["source_path"] = "Ruby #{RDoc::RI::Paths::VERSION}"
- else
- if(f =~ %r%gems/[\d.]+/doc/([^/]+)%) then
- ext_path = "gem #{$1}"
- else
- ext_path = f
- end
+ found.each do |store, methods|
+ methods.each do |method|
+ out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})")
+
+ unless name =~ /^#{Regexp.escape method.parent_name}/ then
+ out << RDoc::Markup::Heading.new(3, "Implementation from #{method.parent_name}")
+ end
+ out << RDoc::Markup::Rule.new(1)
+
+ if method.call_seq then
+ call_seq = method.call_seq.chomp.split "\n"
+ call_seq = call_seq.map { |line| [' ', line, "\n"] }
+ out << RDoc::Markup::Verbatim.new(*call_seq.flatten)
+ end
- method["source_path"] = ext_path
+ if method.block_params then
+ out << RDoc::Markup::BlankLine.new if method.call_seq
+ params = "yields: #{method.block_params}"
+ out << RDoc::Markup::Verbatim.new(' ', params, "\n")
end
- name = method["full_name"]
- cache[name] = method
+ out << RDoc::Markup::Rule.new(1) if
+ method.call_seq or method.block_params
+
+ out << RDoc::Markup::BlankLine.new
+ out << method.comment
+ out << RDoc::Markup::BlankLine.new
end
end
- write_cache cache, path
+ display out
+ end
+
+ ##
+ # Outputs formatted RI data for the class or method +name+.
+ #
+ # Returns true if +name+ was found, false if it was not an alternative could
+ # be guessed, raises an error if +name+ couldn't be guessed.
+
+ def display_name name
+ return true if display_class name
+
+ display_method name if name =~ /::|#|\./
+
+ true
+ rescue NotFoundError
+ matches = list_methods_matching name if name =~ /::|#|\./
+ matches = classes.keys.grep(/^#{name}/) if matches.empty?
+
+ raise if matches.empty?
+
+ page do |io|
+ io.puts "#{name} not found, maybe you meant:"
+ io.puts
+ io.puts matches.join("\n")
+ end
+
+ false
end
##
- # Finds the next ancestor of +orig_klass+ after +klass+.
+ # Displays each name in +name+
+
+ def display_names names
+ names.each do |name|
+ name = expand_name name
- def lookup_ancestor(klass, orig_klass)
- # This is a bit hacky, but ri will go into an infinite
- # loop otherwise, since Object has an Object ancestor
- # for some reason. Depending on the documentation state, I've seen
- # Kernel as an ancestor of Object and not as an ancestor of Object.
- if ((orig_klass == "Object") &&
- ((klass == "Kernel") || (klass == "Object")))
- return nil
+ display_name name
end
+ end
+ ##
+ # Expands abbreviated klass +klass+ into a fully-qualified class. "Zl::Da"
+ # will be expanded to Zlib::DataError.
+
+ def expand_class klass
+ klass.split('::').inject '' do |expanded, klass_part|
+ expanded << '::' unless expanded.empty?
+ short = expanded << klass_part
- cache = class_cache[orig_klass]
+ subset = classes.keys.select do |klass_name|
+ klass_name =~ /^#{expanded}[^:]*$/
+ end
- return nil unless cache
+ abbrevs = Abbrev.abbrev subset
- ancestors = [orig_klass]
- ancestors.push(*cache.includes.map { |inc| inc['name'] })
- ancestors << cache.superclass
+ expanded = abbrevs[short]
- ancestor_index = ancestors.index(klass)
+ raise NotFoundError, short unless expanded
- if ancestor_index
- ancestor = ancestors[ancestors.index(klass) + 1]
- return ancestor if ancestor
+ expanded.dup
end
+ end
+
+ ##
+ # Expands the class portion of +name+ into a fully-qualified class. See
+ # #expand_class.
+
+ def expand_name name
+ klass, selector, method = parse_name name
+
+ return [selector, method].join if klass.empty?
- lookup_ancestor klass, cache.superclass
+ "#{expand_class klass}#{selector}#{method}"
end
##
- # Finds the method
+ # Yields items matching +name+ including the store they were found in, the
+ # class being searched for, the class they were found in (an ancestor) the
+ # types of methods to look up (from #method_type), and the method name being
+ # searched for
+
+ def find_methods name
+ klass, selector, method = parse_name name
+
+ types = method_type selector
+
+ klasses = nil
+ ambiguous = klass.empty?
+
+ if ambiguous then
+ klasses = classes.keys
+ else
+ klasses = ancestors_of klass
+ klasses.unshift klass
+ end
+
+ methods = []
+
+ klasses.each do |ancestor|
+ ancestors = classes[ancestor]
+
+ next unless ancestors
+
+ klass = ancestor if ambiguous
+
+ ancestors.each do |store|
+ methods << [store, klass, ancestor, types, method]
+ end
+ end
+
+ methods = methods.sort_by do |_, k, a, _, m|
+ [k, a, m].compact
+ end
- def lookup_method(name, klass)
- cache = load_cache_for klass
- return nil unless cache
+ methods.each do |item|
+ yield(*item)
+ end
- method = cache[name.gsub('.', '#')]
- method = cache[name.gsub('.', '::')] unless method
- method
+ self
end
- def map_dirs(file_name)
- @doc_dirs.map { |dir| yield File.join(dir, file_name) }.flatten.compact
+ ##
+ # Creates a new RDoc::Markup::Formatter. If a formatter is given with -f,
+ # use it. If we're outputting to a pager, use bs, otherwise ansi.
+
+ def formatter
+ if @formatter_klass then
+ @formatter_klass.new
+ elsif paging? then
+ RDoc::Markup::ToBs.new
+ else
+ RDoc::Markup::ToAnsi.new
+ end
end
##
- # Extract the class and method name parts from +name+ like Foo::Bar#baz
+ # Runs ri interactively using Readline if it is available.
- def parse_name(name)
- parts = name.split(/(::|\#|\.)/)
+ def interactive
+ puts "\nEnter the method name you want to look up."
- if parts[-2] != '::' or parts.last !~ /^[A-Z]/ then
- meth = parts.pop
- parts.pop
+ if defined? Readline then
+ Readline.completion_proc = method :complete
+ puts "You can use tab to autocomplete."
end
- klass = parts.join
+ puts "Enter a blank line to exit.\n\n"
- [klass, meth]
- end
+ loop do
+ name = if defined? Readline then
+ Readline.readline ">> "
+ else
+ print ">> "
+ $stdin.gets
+ end
+
+ return if name.nil? or name.empty?
- def read_yaml(path)
- data = File.read path
+ name = expand_name name.strip
+
+ begin
+ display_name name
+ rescue NotFoundError => e
+ puts e.message
+ end
+ end
- # Necessary to be backward-compatible with documentation generated
- # by earliar RDoc versions.
- data = data.gsub(/ \!ruby\/(object|struct):(RDoc::RI|RI).*/, '')
- data = data.gsub(/ \!ruby\/(object|struct):SM::(\S+)/,
- ' !ruby/\1:RDoc::Markup::\2')
- OpenStructHash.convert(YAML.load(data))
+ rescue Interrupt
+ exit
end
- def run
- if(@list_doc_dirs)
- puts @doc_dirs.join("\n")
- elsif @names.empty? then
- @display.list_known_classes class_cache.keys.sort
- else
- @names.each do |name|
- if class_cache.key? name then
- method_map = display_class name
- if(@interactive)
- method_name = @display.get_class_method_choice(method_map)
-
- if(method_name != nil)
- method = lookup_method "#{name}#{method_name}", name
- display_method method
- end
- end
- elsif name =~ /::|\#|\./ then
- klass, = parse_name name
+ ##
+ # Lists classes known to ri
- orig_klass = klass
- orig_name = name
+ def list_known_classes
+ classes = []
- loop do
- method = lookup_method name, klass
+ stores.each do |store|
+ classes << store.modules
+ end
- break method if method
+ classes = classes.flatten.uniq.sort
- ancestor = lookup_ancestor klass, orig_klass
+ page do |io|
+ if paging? or io.tty? then
+ io.puts "Classes and Modules known to ri:"
+ io.puts
+ end
- break unless ancestor
+ io.puts classes.join("\n")
+ end
+ end
- name = name.sub klass, ancestor
- klass = ancestor
- end
+ ##
+ # Returns an Array of methods matching +name+
+
+ def list_methods_matching name
+ found = []
+
+ find_methods name do |store, klass, ancestor, types, method|
+ if types == :instance or types == :both then
+ methods = store.instance_methods[ancestor]
- raise NotFoundError, orig_name unless method
-
- display_method method
- else
- methods = select_methods(/#{name}/)
-
- if methods.size == 0
- raise NotFoundError, name
- elsif methods.size == 1
- display_method methods[0]
- else
- if(@interactive)
- @display.display_method_list_choice methods
- else
- @display.display_method_list methods
- end
+ if methods then
+ matches = methods.grep(/^#{method}/)
+
+ matches = matches.map do |match|
+ "#{klass}##{match}"
end
+
+ found.push(*matches)
end
end
+
+ if types == :class or types == :both then
+ methods = store.class_methods[ancestor]
+
+ next unless methods
+ matches = methods.grep(/^#{method}/)
+
+ matches = matches.map do |match|
+ "#{klass}::#{match}"
+ end
+
+ found.push(*matches)
+ end
end
- rescue NotFoundError => e
- abort e.message
+
+ found.uniq
end
- def select_methods(pattern)
- methods = []
- class_cache.keys.sort.each do |klass|
- class_cache[klass]["instance_methods"].map{|h|h["name"]}.grep(pattern) do |name|
- method = load_cache_for(klass)[klass+'#'+name]
- methods << method if method
- end
- class_cache[klass]["class_methods"].map{|h|h["name"]}.grep(pattern) do |name|
- method = load_cache_for(klass)[klass+'::'+name]
- methods << method if method
+ ##
+ # Loads RI data for method +name+ on +klass+ from +store+. +type+ and
+ # +cache+ indicate if it is a class or instance method.
+
+ def load_method store, cache, klass, type, name
+ methods = store.send(cache)[klass]
+
+ return unless methods
+
+ method = methods.find do |method_name|
+ method_name == name
+ end
+
+ return unless method
+
+ store.load_method klass, "#{type}#{method}"
+ end
+
+ ##
+ # Returns an Array of RI data for methods matching +name+
+
+ def load_methods_matching name
+ found = []
+
+ find_methods name do |store, klass, ancestor, types, method|
+ methods = []
+
+ methods << load_method(store, :class_methods, ancestor, '::', method) if
+ types == :class or types == :both
+
+ methods << load_method(store, :instance_methods, ancestor, '#', method) if
+ types == :instance or types == :both
+
+ found << [store, methods.compact]
+ end
+
+ found.reject do |path, methods| methods.empty? end
+ end
+
+ ##
+ # Returns the type of method (:both, :instance, :class) for +selector+
+
+ def method_type selector
+ case selector
+ when '.', nil then :both
+ when '#' then :instance
+ else :class
+ end
+ end
+
+ ##
+ # Paginates output through a pager program.
+
+ def page
+ if pager = setup_pager then
+ begin
+ yield pager
+ ensure
+ pager.close
end
+ else
+ yield $stdout
end
- methods
+ rescue Errno::EPIPE
+ ensure
+ @paging = false
+ end
+
+ ##
+ # Are we using a pager?
+
+ def paging?
+ @paging
end
- def write_cache(cache, path)
- if(@use_cache)
- File.open path, "wb" do |cache_file|
- Marshal.dump cache, cache_file
+ ##
+ # Extract the class, selector and method name parts from +name+ like
+ # Foo::Bar#baz.
+ #
+ # NOTE: Given Foo::Bar, Bar is considered a class even though it may be a
+ # method
+
+ def parse_name(name)
+ parts = name.split(/(::|#|\.)/)
+
+ if parts.length == 1 then
+ if parts.first =~ /^[a-z]/ then
+ type = '.'
+ meth = parts.pop
+ else
+ type = nil
+ meth = nil
end
+ elsif parts.length == 2 or parts.last =~ /::|#|\./ then
+ type = parts.pop
+ meth = nil
+ elsif parts[-2] != '::' or parts.last !~ /^[A-Z]/ then
+ meth = parts.pop
+ type = parts.pop
+ end
+
+ klass = parts.join
+
+ [klass, type, meth]
+ end
+
+ ##
+ # Looks up and displays ri data according to the options given.
+
+ def run
+ if @list_doc_dirs then
+ puts @doc_dirs
+ elsif @interactive then
+ interactive
+ elsif @names.empty? then
+ list_known_classes
+ else
+ display_names @names
+ end
+ rescue NotFoundError => e
+ abort e.message
+ end
+
+ ##
+ # Sets up a pager program to pass output through. Tries the RI_PAGER and
+ # PAGER environment variables followed by pager, less then more.
+
+ def setup_pager
+ return if @use_stdout
+
+ pagers = [ENV['RI_PAGER'], ENV['PAGER'], 'pager', 'less', 'more']
+
+ pagers.compact.uniq.each do |pager|
+ io = IO.popen(pager, "w") rescue next
+
+ @paging = true
+
+ return io
end
- cache
+ @use_stdout = true
+
+ nil
end
end
+
diff --git a/lib/rdoc/ri/formatter.rb b/lib/rdoc/ri/formatter.rb
index 258907d141..84d37a9d31 100644
--- a/lib/rdoc/ri/formatter.rb
+++ b/lib/rdoc/ri/formatter.rb
@@ -1,616 +1,5 @@
-require 'rdoc/ri'
-require 'rdoc/markup'
-
-class RDoc::RI::Formatter
-
- attr_writer :indent
- attr_accessor :output
-
- FORMATTERS = { }
-
- def self.for(name)
- FORMATTERS[name.downcase]
- end
-
- def self.list
- FORMATTERS.keys.sort.join ", "
- end
-
- def initialize(output, width, indent)
- @output = output
- @width = width
- @indent = indent
- @original_indent = indent.dup
- end
-
- def draw_line(label=nil)
- len = @width
- len -= (label.size + 1) if label
-
- if len > 0 then
- @output.print '-' * len
- if label
- @output.print ' '
- bold_print label
- end
-
- @output.puts
- else
- @output.print '-' * @width
- @output.puts
-
- @output.puts label
- end
- end
-
- def indent
- return @indent unless block_given?
-
- begin
- indent = @indent.dup
- @indent += @original_indent
- yield
- ensure
- @indent = indent
- end
- end
-
- def wrap(txt, prefix=@indent, linelen=@width)
- return unless txt && !txt.empty?
-
- work = conv_markup(txt)
- textLen = linelen - prefix.length
- patt = Regexp.new("^(.{0,#{textLen}})[ \n]")
- next_prefix = prefix.tr("^ ", " ")
-
- res = []
-
- while work.length > textLen
- if work =~ patt
- res << $1
- work.slice!(0, $&.length)
- else
- res << work.slice!(0, textLen)
- end
- end
- res << work if work.length.nonzero?
- @output.puts(prefix + res.join("\n" + next_prefix))
- end
-
- def blankline
- @output.puts
- end
-
- ##
- # Called when we want to ensure a new 'wrap' starts on a newline. Only
- # needed for HtmlFormatter, because the rest do their own line breaking.
-
- def break_to_newline
- end
-
- def bold_print(txt)
- @output.print txt
- end
-
- def raw_print_line(txt)
- @output.print txt
- end
-
- ##
- # Convert HTML entities back to ASCII
-
- def conv_html(txt)
- txt = txt.gsub(/&gt;/, '>')
- txt.gsub!(/&lt;/, '<')
- txt.gsub!(/&quot;/, '"')
- txt.gsub!(/&amp;/, '&')
- txt
- end
-
- ##
- # Convert markup into display form
-
- def conv_markup(txt)
- txt = txt.gsub(%r{<tt>(.*?)</tt>}, '+\1+')
- txt.gsub!(%r{<code>(.*?)</code>}, '+\1+')
- txt.gsub!(%r{<b>(.*?)</b>}, '*\1*')
- txt.gsub!(%r{<em>(.*?)</em>}, '_\1_')
- txt
- end
-
- def display_list(list)
- case list.type
- when :BULLET
- prefixer = proc { |ignored| @indent + "* " }
-
- when :NUMBER, :UPPERALPHA, :LOWERALPHA then
- start = case list.type
- when :NUMBER then 1
- when :UPPERALPHA then 'A'
- when :LOWERALPHA then 'a'
- end
-
- prefixer = proc do |ignored|
- res = @indent + "#{start}.".ljust(4)
- start = start.succ
- res
- end
-
- when :LABELED, :NOTE then
- longest = 0
-
- list.contents.each do |item|
- if RDoc::Markup::Flow::LI === item and item.label.length > longest then
- longest = item.label.length
- end
- end
-
- longest += 1
-
- prefixer = proc { |li| @indent + li.label.ljust(longest) }
-
- else
- raise ArgumentError, "unknown list type #{list.type}"
- end
-
- list.contents.each do |item|
- if RDoc::Markup::Flow::LI === item then
- prefix = prefixer.call item
- display_flow_item item, prefix
- else
- display_flow_item item
- end
- end
- end
-
- def display_flow_item(item, prefix = @indent)
- case item
- when RDoc::Markup::Flow::P, RDoc::Markup::Flow::LI
- wrap(conv_html(item.body), prefix)
- blankline
-
- when RDoc::Markup::Flow::LIST
- display_list(item)
-
- when RDoc::Markup::Flow::VERB
- display_verbatim_flow_item(item, @indent)
-
- when RDoc::Markup::Flow::H
- display_heading(conv_html(item.text), item.level, @indent)
-
- when RDoc::Markup::Flow::RULE
- draw_line
-
- else
- raise RDoc::Error, "Unknown flow element: #{item.class}"
- end
- end
-
- def display_verbatim_flow_item(item, prefix=@indent)
- item.body.split(/\n/).each do |line|
- @output.print @indent, conv_html(line), "\n"
- end
- blankline
- end
-
- def display_heading(text, level, indent)
- text = strip_attributes text
-
- case level
- when 1 then
- ul = "=" * text.length
- @output.puts
- @output.puts text.upcase
- @output.puts ul
-
- when 2 then
- ul = "-" * text.length
- @output.puts
- @output.puts text
- @output.puts ul
- else
- @output.print indent, text, "\n"
- end
-
- @output.puts
- end
-
- def display_flow(flow)
- flow.each do |f|
- display_flow_item(f)
- end
- end
-
- def strip_attributes(text)
- text.gsub(/(<\/?(?:b|code|em|i|tt)>)/, '')
- end
-
-end
-
-##
-# Handle text with attributes. We're a base class: there are different
-# presentation classes (one, for example, uses overstrikes to handle bold and
-# underlining, while another using ANSI escape sequences.
-
-class RDoc::RI::AttributeFormatter < RDoc::RI::Formatter
-
- BOLD = 1
- ITALIC = 2
- CODE = 4
-
- ATTR_MAP = {
- "b" => BOLD,
- "code" => CODE,
- "em" => ITALIC,
- "i" => ITALIC,
- "tt" => CODE
- }
-
- AttrChar = Struct.new :char, :attr
-
- class AttributeString
- attr_reader :txt
-
- def initialize
- @txt = []
- @optr = 0
- end
-
- def <<(char)
- @txt << char
- end
-
- def empty?
- @optr >= @txt.length
- end
-
- # accept non space, then all following spaces
- def next_word
- start = @optr
- len = @txt.length
-
- while @optr < len && @txt[@optr].char != " "
- @optr += 1
- end
-
- while @optr < len && @txt[@optr].char == " "
- @optr += 1
- end
-
- @txt[start...@optr]
- end
- end
-
- ##
- # Overrides base class. Looks for <tt>...</tt> etc sequences and generates
- # an array of AttrChars. This array is then used as the basis for the
- # split.
-
- def wrap(txt, prefix=@indent, linelen=@width)
- return unless txt && !txt.empty?
-
- txt = add_attributes_to(txt)
- next_prefix = prefix.tr("^ ", " ")
- linelen -= prefix.size
-
- line = []
-
- until txt.empty?
- word = txt.next_word
- if word.size + line.size > linelen
- write_attribute_text(prefix, line)
- prefix = next_prefix
- line = []
- end
- line.concat(word)
- end
-
- write_attribute_text(prefix, line) if line.length > 0
- end
-
- protected
-
- def write_attribute_text(prefix, line)
- @output.print prefix
- line.each do |achar|
- @output.print achar.char
- end
- @output.puts
- end
-
- def bold_print(txt)
- @output.print txt
- end
-
- private
-
- def add_attributes_to(txt)
- tokens = txt.split(%r{(</?(?:b|code|em|i|tt)>)})
- text = AttributeString.new
- attributes = 0
- tokens.each do |tok|
- case tok
- when %r{^</(\w+)>$} then attributes &= ~(ATTR_MAP[$1]||0)
- when %r{^<(\w+)>$} then attributes |= (ATTR_MAP[$1]||0)
- else
- tok.split(//).each {|ch| text << AttrChar.new(ch, attributes)}
- end
- end
- text
- end
-
-end
-
-##
-# This formatter generates overstrike-style formatting, which works with
-# pagers such as man and less.
-
-class RDoc::RI::OverstrikeFormatter < RDoc::RI::AttributeFormatter
-
- BS = "\C-h"
-
- def write_attribute_text(prefix, line)
- @output.print prefix
-
- line.each do |achar|
- attr = achar.attr
- @output.print "_", BS if (attr & (ITALIC + CODE)) != 0
- @output.print achar.char, BS if (attr & BOLD) != 0
- @output.print achar.char
- end
-
- @output.puts
- end
-
- ##
- # Draw a string in bold
-
- def bold_print(text)
- text.split(//).each do |ch|
- @output.print ch, BS, ch
- end
- end
-
-end
-
-##
-# This formatter uses ANSI escape sequences to colorize stuff works with
-# pagers such as man and less.
-
-class RDoc::RI::AnsiFormatter < RDoc::RI::AttributeFormatter
-
- def initialize(*args)
- super
- @output.print "\033[0m"
- end
-
- def write_attribute_text(prefix, line)
- @output.print prefix
- curr_attr = 0
- line.each do |achar|
- attr = achar.attr
- if achar.attr != curr_attr
- update_attributes(achar.attr)
- curr_attr = achar.attr
- end
- @output.print achar.char
- end
- update_attributes(0) unless curr_attr.zero?
- @output.puts
- end
-
- def bold_print(txt)
- @output.print "\033[1m#{txt}\033[m"
- end
-
- HEADINGS = {
- 1 => ["\033[1;32m", "\033[m"],
- 2 => ["\033[4;32m", "\033[m"],
- 3 => ["\033[32m", "\033[m"],
- }
-
- def display_heading(text, level, indent)
- level = 3 if level > 3
- heading = HEADINGS[level]
- @output.print indent
- @output.print heading[0]
- @output.print strip_attributes(text)
- @output.puts heading[1]
- end
-
- private
-
- ATTR_MAP = {
- BOLD => "1",
- ITALIC => "33",
- CODE => "36"
- }
-
- def update_attributes(attr)
- str = "\033["
- for quality in [ BOLD, ITALIC, CODE]
- unless (attr & quality).zero?
- str << ATTR_MAP[quality]
- end
- end
- @output.print str, "m"
- end
-
-end
-
##
-# This formatter uses HTML.
-
-class RDoc::RI::HtmlFormatter < RDoc::RI::AttributeFormatter
-
- def write_attribute_text(prefix, line)
- curr_attr = 0
- line.each do |achar|
- attr = achar.attr
- if achar.attr != curr_attr
- update_attributes(curr_attr, achar.attr)
- curr_attr = achar.attr
- end
- @output.print(escape(achar.char))
- end
- update_attributes(curr_attr, 0) unless curr_attr.zero?
- end
-
- def draw_line(label=nil)
- if label != nil
- bold_print(label)
- end
- @output.puts("<hr>")
- end
-
- def bold_print(txt)
- tag("b") { txt }
- end
-
- def blankline()
- @output.puts("<p>")
- end
-
- def break_to_newline
- @output.puts("<br>")
- end
-
- def display_heading(text, level, indent)
- level = 4 if level > 4
- tag("h#{level}") { text }
- @output.puts
- end
-
- def display_list(list)
- case list.type
- when :BULLET then
- list_type = "ul"
- prefixer = proc { |ignored| "<li>" }
-
- when :NUMBER, :UPPERALPHA, :LOWERALPHA then
- list_type = "ol"
- prefixer = proc { |ignored| "<li>" }
-
- when :LABELED then
- list_type = "dl"
- prefixer = proc do |li|
- "<dt><b>" + escape(li.label) + "</b><dd>"
- end
-
- when :NOTE then
- list_type = "table"
- prefixer = proc do |li|
- %{<tr valign="top"><td>#{li.label.gsub(/ /, '&nbsp;')}</td><td>}
- end
- else
- fail "unknown list type"
- end
-
- @output.print "<#{list_type}>"
- list.contents.each do |item|
- if item.kind_of? RDoc::Markup::Flow::LI
- prefix = prefixer.call(item)
- @output.print prefix
- display_flow_item(item, prefix)
- else
- display_flow_item(item)
- end
- end
- @output.print "</#{list_type}>"
- end
-
- def display_verbatim_flow_item(item, prefix=@indent)
- @output.print("<pre>")
- item.body.split(/\n/).each do |line|
- @output.puts conv_html(line)
- end
- @output.puts("</pre>")
- end
-
- private
-
- ATTR_MAP = {
- BOLD => "b>",
- ITALIC => "i>",
- CODE => "tt>"
- }
-
- def update_attributes(current, wanted)
- str = ""
- # first turn off unwanted ones
- off = current & ~wanted
- for quality in [ BOLD, ITALIC, CODE]
- if (off & quality) > 0
- str << "</" + ATTR_MAP[quality]
- end
- end
-
- # now turn on wanted
- for quality in [ BOLD, ITALIC, CODE]
- unless (wanted & quality).zero?
- str << "<" << ATTR_MAP[quality]
- end
- end
- @output.print str
- end
-
- def tag(code)
- @output.print("<#{code}>")
- @output.print(yield)
- @output.print("</#{code}>")
- end
-
- def escape(str)
- str = str.gsub(/&/n, '&amp;')
- str.gsub!(/\"/n, '&quot;')
- str.gsub!(/>/n, '&gt;')
- str.gsub!(/</n, '&lt;')
- str
- end
-
-end
-
-##
-# This formatter reduces extra lines for a simpler output. It improves way
-# output looks for tools like IRC bots.
-
-class RDoc::RI::SimpleFormatter < RDoc::RI::Formatter
-
- ##
- # No extra blank lines
-
- def blankline
- end
-
- ##
- # Display labels only, no lines
-
- def draw_line(label=nil)
- unless label.nil? then
- bold_print(label)
- @output.puts
- end
- end
-
- ##
- # Place heading level indicators inline with heading.
-
- def display_heading(text, level, indent)
- text = strip_attributes(text)
- case level
- when 1
- @output.puts "= " + text.upcase
- when 2
- @output.puts "-- " + text
- else
- @output.print indent, text, "\n"
- end
- end
+# For RubyGems backwards compatibility
+module RDoc::RI::Formatter # :nodoc:
end
-
-RDoc::RI::Formatter::FORMATTERS['plain'] = RDoc::RI::Formatter
-RDoc::RI::Formatter::FORMATTERS['simple'] = RDoc::RI::SimpleFormatter
-RDoc::RI::Formatter::FORMATTERS['bs'] = RDoc::RI::OverstrikeFormatter
-RDoc::RI::Formatter::FORMATTERS['ansi'] = RDoc::RI::AnsiFormatter
-RDoc::RI::Formatter::FORMATTERS['html'] = RDoc::RI::HtmlFormatter
diff --git a/lib/rdoc/ri/gemdirs.rb b/lib/rdoc/ri/gemdirs.rb
deleted file mode 100644
index 0bca992bca..0000000000
--- a/lib/rdoc/ri/gemdirs.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-module RDoc::RI::Paths
- begin
- require 'rubygems' unless defined?(Gem)
-
- # HACK dup'd from Gem.latest_partials and friends
- all_paths = []
-
- all_paths = Gem.path.map do |dir|
- Dir[File.join(dir, 'doc/*/ri')]
- end.flatten
-
- ri_paths = {}
-
- all_paths.each do |dir|
- if %r"/([^/]*)-((?:\d+\.)*\d+)/ri\z" =~ dir
- name, version = $1, $2
- ver = Gem::Version.new(version)
- if !ri_paths[name] or ver > ri_paths[name][0]
- ri_paths[name] = [ver, dir]
- end
- end
- end
-
- GEMDIRS = ri_paths.map { |k,v| v.last }.sort
- rescue LoadError
- GEMDIRS = []
- end
-end
diff --git a/lib/rdoc/ri/paths.rb b/lib/rdoc/ri/paths.rb
index 399fbbab00..178142d35f 100644
--- a/lib/rdoc/ri/paths.rb
+++ b/lib/rdoc/ri/paths.rb
@@ -1,78 +1,118 @@
require 'rdoc/ri'
##
-# Encapsulate all the strangeness to do with finding out where to find RDoc
-# files
-#
-# We basically deal with three directories:
-#
-# 1. The 'system' documentation directory, which holds the documentation
-# distributed with Ruby, and which is managed by the Ruby install process
-# 2. The 'site' directory, which contains site-wide documentation added
-# locally.
-# 3. The 'user' documentation directory, stored under the user's own home
-# directory.
-#
-# There's contention about all this, but for now:
-#
-# system:: $datadir/ri/<ver>/system/...
-# site:: $datadir/ri/<ver>/site/...
-# user:: ~/.rdoc
+# The directories where ri data lives.
module RDoc::RI::Paths
#:stopdoc:
require 'rbconfig'
- DOC_DIR = "doc/rdoc"
+ version = RbConfig::CONFIG['ruby_version']
- VERSION = RbConfig::CONFIG['ruby_version']
+ base = File.join RbConfig::CONFIG['datadir'], "ri", version
+ SYSDIR = File.join base, "system"
+ SITEDIR = File.join base, "site"
+ homedir = File.expand_path('~') ||
+ ENV['HOME'] || ENV['USERPROFILE'] || ENV['HOMEPATH']
- if VERSION > '1.9.1'
- if m = /ruby/.match(RbConfig::CONFIG['RUBY_INSTALL_NAME'])
- m = [m.pre_match, m.post_match]
- else
- m = [""] * 2
+ HOMEDIR = if homedir then
+ File.join homedir, ".rdoc"
+ end
+ #:startdoc:
+
+ @gemdirs = nil
+
+ ##
+ # Iterates over each selected path yielding the directory and type.
+ #
+ # Yielded types:
+ # :system:: Where Ruby's ri data is stored. Yielded when +system+ is
+ # true
+ # :site:: Where ri for installed libraries are stored. Yielded when
+ # +site+ is true. Normally no ri data is stored here.
+ # :home:: ~/.ri. Yielded when +home+ is true.
+ # :gem:: ri data for an installed gem. Yielded when +gems+ is true.
+ # :extra:: ri data directory from the command line. Yielded for each
+ # entry in +extra_dirs+
+
+ def self.each system, site, home, gems, *extra_dirs # :yields: directory, type
+ extra_dirs.each do |dir|
+ yield dir, :extra
end
- ri = "#{m[0]}ri#{m[1]}"
- rdoc = "#{m[0]}rdoc#{m[1]}"
- base = File.join(RbConfig::CONFIG['datadir'], ri, VERSION)
- else
- if m = /ruby/.match(RbConfig::CONFIG['RUBY_BASE_NAME'])
- m = [m.pre_match, m.post_match]
- else
- m = [""] * 2
+
+ yield SYSDIR, :system if system
+ yield SITEDIR, :site if site
+ yield HOMEDIR, :home if home
+
+ gemdirs.each do |dir|
+ yield dir, :gem
+ end if gems
+
+ nil
+ end
+
+ ##
+ # The latest installed gems' ri directories
+
+ def self.gemdirs
+ return @gemdirs if @gemdirs
+
+ require 'rubygems' unless defined?(Gem) and defined?(Gem::Enable) and
+ Gem::Enable
+
+ # HACK dup'd from Gem.latest_partials and friends
+ all_paths = []
+
+ all_paths = Gem.path.map do |dir|
+ Dir[File.join(dir, 'doc', '*', 'ri')]
+ end.flatten
+
+ ri_paths = {}
+
+ all_paths.each do |dir|
+ base = File.basename File.dirname(dir)
+ if base =~ /(.*)-((\d+\.)*\d+)/ then
+ name, version = $1, $2
+ ver = Gem::Version.new version
+ if ri_paths[name].nil? or ver > ri_paths[name][0] then
+ ri_paths[name] = [ver, dir]
+ end
+ end
end
- ri = "#{m[0]}ri#{m[1]}"
- rdoc = "#{m[0]}rdoc#{m[1]}"
- base = File.join(RbConfig::CONFIG['ridir'], VERSION)
+
+ @gemdirs = ri_paths.map { |k,v| v.last }.sort
+ rescue LoadError
+ @gemdirs = []
end
- SYSDIR = File.join(base, "system")
- SITEDIR = File.join(base, "site")
- HOMEDIR = (File.expand_path("~/.#{rdoc}") rescue nil)
- autoload(:GEMDIRS, File.expand_path('../gemdirs.rb', __FILE__))
+ ##
+ # Returns existing directories from the selected documentation directories
+ # as an Array.
+ #
+ # See also ::each
- # Returns the selected documentation directories as an Array, or PATH if no
- # overriding directories were given.
+ def self.path(system, site, home, gems, *extra_dirs)
+ path = raw_path system, site, home, gems, *extra_dirs
- def self.path(use_system, use_site, use_home, use_gems, *extra_dirs)
- path = raw_path(use_system, use_site, use_home, use_gems, *extra_dirs)
- return path.select { |directory| File.directory? directory }
+ path.select { |directory| File.directory? directory }
end
- # Returns the selected documentation directories including nonexistent
- # directories. Used to print out what paths were searched if no ri was
- # found.
+ ##
+ # Returns selected documentation directories including nonexistent
+ # directories.
+ #
+ # See also ::each
- def self.raw_path(use_system, use_site, use_home, use_gems, *extra_dirs)
+ def self.raw_path(system, site, home, gems, *extra_dirs)
path = []
- path << extra_dirs unless extra_dirs.empty?
- path << SYSDIR if use_system
- path << SITEDIR if use_site
- path << HOMEDIR if use_home
- path << GEMDIRS if use_gems
- return path.flatten.compact
+ each(system, site, home, gems, *extra_dirs) do |dir, type|
+ path << dir
+ end
+
+ path.compact
end
+
end
+
diff --git a/lib/rdoc/ri/reader.rb b/lib/rdoc/ri/reader.rb
deleted file mode 100644
index de3c8d9afa..0000000000
--- a/lib/rdoc/ri/reader.rb
+++ /dev/null
@@ -1,106 +0,0 @@
-require 'rdoc/ri'
-require 'rdoc/ri/descriptions'
-require 'rdoc/ri/writer'
-require 'rdoc/markup/to_flow'
-
-class RDoc::RI::Reader
-
- def initialize(ri_cache)
- @cache = ri_cache
- end
-
- def top_level_namespace
- [ @cache.toplevel ]
- end
-
- def lookup_namespace_in(target, namespaces)
- result = []
- for n in namespaces
- result.concat(n.contained_modules_matching(target))
- end
- result
- end
-
- def find_class_by_name(full_name)
- names = full_name.split(/::/)
- ns = @cache.toplevel
- for name in names
- ns = ns.contained_class_named(name)
- return nil if ns.nil?
- end
- get_class(ns)
- end
-
- def find_methods(name, is_class_method, namespaces)
- result = []
- namespaces.each do |ns|
- result.concat ns.methods_matching(name, is_class_method)
- end
- result
- end
-
- ##
- # Return the MethodDescription for a given MethodEntry by deserializing the
- # YAML
-
- def get_method(method_entry)
- path = method_entry.path_name
- File.open(path) { |f| RDoc::RI::Description.deserialize(f) }
- end
-
- ##
- # Return a class description
-
- def get_class(class_entry)
- result = nil
- for path in class_entry.path_names
- path = RDoc::RI::Writer.class_desc_path(path, class_entry)
- desc = File.open(path) {|f| RDoc::RI::Description.deserialize(f) }
- if result
- result.merge_in(desc)
- else
- result = desc
- end
- end
- result
- end
-
- ##
- # Return the names of all classes and modules
-
- def full_class_names
- res = []
- find_classes_in(res, @cache.toplevel)
- end
-
- ##
- # Return a list of all classes, modules, and methods
-
- def all_names
- res = []
- find_names_in(res, @cache.toplevel)
- end
-
- private
-
- def find_classes_in(res, klass)
- classes = klass.classes_and_modules
- for c in classes
- res << c.full_name
- find_classes_in(res, c)
- end
- res
- end
-
- def find_names_in(res, klass)
- classes = klass.classes_and_modules
- for c in classes
- res << c.full_name
- res.concat c.all_method_names
- find_names_in(res, c)
- end
- res
- end
-
-end
-
diff --git a/lib/rdoc/ri/store.rb b/lib/rdoc/ri/store.rb
new file mode 100644
index 0000000000..81ffb7e674
--- /dev/null
+++ b/lib/rdoc/ri/store.rb
@@ -0,0 +1,248 @@
+require 'rdoc/code_objects'
+require 'fileutils'
+
+##
+# A set of ri data.
+#
+# The store manages reading and writing ri data for a project (gem, path,
+# etc.) and maintains a cache of methods, classes and ancestors in the
+# store.
+
+class RDoc::RI::Store
+
+ ##
+ # Path this store reads or writes
+
+ attr_accessor :path
+
+ ##
+ # Type of ri datastore this was loaded from. See RDoc::RI::Driver,
+ # RDoc::RI::Paths.
+
+ attr_accessor :type
+
+ 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
+
+ @cache = {
+ :class_methods => {},
+ :instance_methods => {},
+ :attributes => {},
+ :modules => [],
+ :ancestors => {},
+ }
+ end
+
+ ##
+ # Ancestors cache accessor. Maps a klass name to an Array of its ancestors
+ # in this store. If Foo in this store inherits from Object, Kernel won't be
+ # listed (it will be included from ruby's ri store).
+
+ def ancestors
+ @cache[:ancestors]
+ end
+
+ ##
+ # Attributes cache accessor. Maps a class to an Array of its attributes.
+
+ def attributes
+ @cache[:attributes]
+ end
+
+ ##
+ # Path to the cache file
+
+ def cache_path
+ File.join @path, 'cache.ri'
+ end
+
+ ##
+ # Path to the ri data for +klass_name+
+
+ def class_file klass_name
+ name = klass_name.split('::').last
+ File.join class_path(klass_name), "cdesc-#{name}.ri"
+ end
+
+ ##
+ # Class methods cache accessor. Maps a class to an Array of its class
+ # methods (not full name).
+
+ def class_methods
+ @cache[:class_methods]
+ end
+
+ ##
+ # Path where data for +klass_name+ will be stored (methods or class data)
+
+ def class_path klass_name
+ File.join @path, *klass_name.split('::')
+ end
+
+ ##
+ # Friendly rendition of #path
+
+ def friendly_path
+ case type
+ when :gem then
+ sep = Regexp.union(*['/', File::ALT_SEPARATOR].compact)
+ @path =~ /#{sep}doc#{sep}(.*?)#{sep}ri$/
+ "gem #{$1}"
+ when :home then '~/.ri'
+ when :site then 'ruby site'
+ when :system then 'ruby core'
+ else @path
+ end
+ end
+
+ def inspect # :nodoc:
+ "#<%s:0x%x %s %p>" % [self.class, object_id, @path, modules.sort]
+ end
+
+ ##
+ # Instance methods cache accessor. Maps a class to an Array of its
+ # instance methods (not full name).
+
+ def instance_methods
+ @cache[:instance_methods]
+ end
+
+ ##
+ # Loads cache file for this store
+
+ def load_cache
+ open cache_path, 'rb' do |io|
+ @cache = Marshal.load io.read
+ end
+ rescue Errno::ENOENT
+ end
+
+ ##
+ # Loads ri data for +klass_name+
+
+ def load_class klass_name
+ open class_file(klass_name), 'rb' do |io|
+ Marshal.load io.read
+ end
+ end
+
+ ##
+ # Loads ri data for +method_name+ in +klass_name+
+
+ def load_method klass_name, method_name
+ open method_file(klass_name, method_name), 'rb' do |io|
+ Marshal.load io.read
+ end
+ end
+
+ ##
+ # Path to the ri data for +method_name+ in +klass_name+
+
+ def method_file klass_name, method_name
+ method_name = method_name.split('::').last
+ method_name =~ /#(.*)/
+ method_type = $1 ? 'i' : 'c'
+ method_name = $1 if $1
+
+ method_name = if ''.respond_to? :ord then
+ method_name.gsub(/\W/) { "%%%02x" % $&[0].ord }
+ else
+ method_name.gsub(/\W/) { "%%%02x" % $&[0] }
+ end
+
+ File.join class_path(klass_name), "#{method_name}-#{method_type}.ri"
+ end
+
+ ##
+ # Modules cache accessor. An Array of all the modules (and classes) in the
+ # store.
+
+ def modules
+ @cache[:modules]
+ end
+
+ ##
+ # Writes the cache file for this store
+
+ def save_cache
+ # HACK mongrel-1.1.5 documents its files twice
+ @cache[:attributes]. each do |_, m| m.uniq!; m.sort! end
+ @cache[:class_methods]. each do |_, m| m.uniq!; m.sort! end
+ @cache[:instance_methods].each do |_, m| m.uniq!; m.sort! end
+
+ open cache_path, 'wb' do |io|
+ Marshal.dump @cache, io
+ end
+ end
+
+ ##
+ # Writes the ri data for +klass+
+
+ def save_class klass
+ FileUtils.mkdir_p class_path(klass.full_name)
+
+ @cache[:modules] << klass.full_name
+
+ path = class_file klass.full_name
+
+ begin
+ disk_klass = nil
+
+ open path, 'rb' do |io|
+ disk_klass = Marshal.load io.read
+ end
+
+ klass.merge disk_klass
+ rescue Errno::ENOENT
+ end
+
+ # BasicObject has no ancestors
+ ancestors = klass.ancestors.compact.map do |ancestor|
+ # HACK for classes we don't know about (class X < RuntimeError)
+ String === ancestor ? ancestor : ancestor.full_name
+ end
+
+ @cache[:ancestors][klass.full_name] ||= []
+ @cache[:ancestors][klass.full_name].push(*ancestors)
+
+ attributes = klass.attributes.map do |attribute|
+ "#{attribute.type} #{attribute.name}"
+ end
+
+ unless attributes.empty? then
+ @cache[:attributes][klass.full_name] ||= []
+ @cache[:attributes][klass.full_name].push(*attributes)
+ end
+
+ open path, 'wb' do |io|
+ Marshal.dump klass, io
+ end
+ end
+
+ ##
+ # Writes the ri data for +method+ on +klass+
+
+ def save_method klass, method
+ FileUtils.mkdir_p class_path(klass.full_name)
+
+ cache = if method.singleton then
+ @cache[:class_methods]
+ else
+ @cache[:instance_methods]
+ end
+ cache[klass.full_name] ||= []
+ cache[klass.full_name] << method.name
+
+ open method_file(klass.full_name, method.full_name), 'wb' do |io|
+ Marshal.dump method, io
+ end
+ end
+
+end
+
diff --git a/lib/rdoc/ri/util.rb b/lib/rdoc/ri/util.rb
deleted file mode 100644
index 51cf881bdd..0000000000
--- a/lib/rdoc/ri/util.rb
+++ /dev/null
@@ -1,79 +0,0 @@
-require 'rdoc/ri'
-
-##
-# Break argument into its constituent class or module names, an
-# optional method type, and a method name
-
-class RDoc::RI::NameDescriptor
-
- attr_reader :class_names
- attr_reader :method_name
-
- ##
- # true and false have the obvious meaning. nil means we don't care
-
- attr_reader :is_class_method
-
- ##
- # +arg+ may be
- #
- # 1. A class or module name (optionally qualified with other class or module
- # names (Kernel, File::Stat etc)
- # 2. A method name
- # 3. A method name qualified by a optionally fully qualified class or module
- # name
- #
- # We're fairly casual about delimiters: folks can say Kernel::puts,
- # Kernel.puts, or Kernel\#puts for example. There's one exception: if you
- # say IO::read, we look for a class method, but if you say IO.read, we look
- # for an instance method
-
- def initialize(arg)
- @class_names = []
- separator = nil
-
- tokens = arg.split(/(\.|::|#)/)
-
- # Skip leading '::', '#' or '.', but remember it might
- # be a method name qualifier
- separator = tokens.shift if tokens[0] =~ /^(\.|::|#)/
-
- # Skip leading '::', but remember we potentially have an inst
-
- # leading stuff must be class names
-
- while tokens[0] =~ /^[A-Z]/
- @class_names << tokens.shift
- unless tokens.empty?
- separator = tokens.shift
- break unless separator == "::"
- end
- end
-
- # Now must have a single token, the method name, or an empty array
- unless tokens.empty?
- @method_name = tokens.shift
- # We may now have a trailing !, ?, or = to roll into
- # the method name
- if !tokens.empty? && tokens[0] =~ /^[!?=]$/
- @method_name << tokens.shift
- end
-
- if @method_name =~ /::|\.|#/ or !tokens.empty?
- raise RDoc::RI::Error.new("Bad argument: #{arg}")
- end
- if separator && separator != '.'
- @is_class_method = separator == "::"
- end
- end
- end
-
- # Return the full class name (with '::' between the components) or "" if
- # there's no class name
-
- def full_class_name
- @class_names.join("::")
- end
-
-end
-
diff --git a/lib/rdoc/ri/writer.rb b/lib/rdoc/ri/writer.rb
deleted file mode 100644
index 92aaa1c2da..0000000000
--- a/lib/rdoc/ri/writer.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-require 'fileutils'
-require 'rdoc/ri'
-
-class RDoc::RI::Writer
-
- def self.class_desc_path(dir, class_desc)
- File.join(dir, "cdesc-" + class_desc.name + ".yaml")
- end
-
- ##
- # Convert a name from internal form (containing punctuation) to an external
- # form (where punctuation is replaced by %xx)
-
- def self.internal_to_external(name)
- if ''.respond_to? :ord then
- name.gsub(/\W/) { "%%%02x" % $&[0].ord }
- else
- name.gsub(/\W/) { "%%%02x" % $&[0] }
- end
- end
-
- ##
- # And the reverse operation
-
- def self.external_to_internal(name)
- name.gsub(/%([0-9a-f]{2,2})/) { $1.to_i(16).chr }
- end
-
- def initialize(base_dir)
- @base_dir = base_dir
- end
-
- def remove_class(class_desc)
- FileUtils.rm_rf(path_to_dir(class_desc.full_name))
- end
-
- def add_class(class_desc)
- dir = path_to_dir(class_desc.full_name)
- FileUtils.mkdir_p(dir)
- class_file_name = self.class.class_desc_path(dir, class_desc)
- File.open(class_file_name, "w") do |f|
- f.write(class_desc.serialize)
- end
- end
-
- def add_method(class_desc, method_desc)
- dir = path_to_dir(class_desc.full_name)
- file_name = self.class.internal_to_external(method_desc.name)
- meth_file_name = File.join(dir, file_name)
- if method_desc.is_singleton
- meth_file_name += "-c.yaml"
- else
- meth_file_name += "-i.yaml"
- end
-
- File.open(meth_file_name, "w") do |f|
- f.write(method_desc.serialize)
- end
- end
-
- private
-
- def path_to_dir(class_name)
- File.join(@base_dir, *class_name.split('::'))
- end
-
-end
-
diff --git a/lib/rdoc/ruby_lex.rb b/lib/rdoc/ruby_lex.rb
new file mode 100644
index 0000000000..014117fb17
--- /dev/null
+++ b/lib/rdoc/ruby_lex.rb
@@ -0,0 +1,1284 @@
+#--
+# irb/ruby-lex.rb - ruby lexcal analyzer
+# $Release Version: 0.9.5$
+# $Revision: 17979 $
+# $Date: 2008-07-09 10:17:05 -0700 (Wed, 09 Jul 2008) $
+# by Keiju ISHITSUKA(keiju@ruby-lang.org)
+#
+#++
+
+require "e2mmap"
+require "irb/slex"
+require "rdoc/ruby_token"
+require "stringio"
+
+##
+# Ruby lexer adapted from irb.
+#
+# The internals are not documented because they are scary.
+
+class RDoc::RubyLex
+
+ # :stopdoc:
+
+ extend Exception2MessageMapper
+
+ def_exception(:AlreadyDefinedToken, "Already defined token(%s)")
+ def_exception(:TkReading2TokenNoKey, "key nothing(key='%s')")
+ def_exception(:TkSymbol2TokenNoKey, "key nothing(key='%s')")
+ def_exception(:TkReading2TokenDuplicateError,
+ "key duplicate(token_n='%s', key='%s')")
+ def_exception(:SyntaxError, "%s")
+
+ def_exception(:TerminateLineInput, "Terminate Line Input")
+
+ include RDoc::RubyToken
+ include IRB
+
+ attr_reader :continue
+ attr_reader :lex_state
+ attr_reader :reader
+
+ class << self
+ attr_accessor :debug_level
+ end
+
+ def self.debug?
+ @debug_level > 0
+ end
+
+ self.debug_level = 0
+
+ def initialize(content, options)
+ lex_init
+
+ if /\t/ =~ content then
+ tab_width = options.tab_width
+ content = content.split(/\n/).map do |line|
+ 1 while line.gsub!(/\t+/) {
+ ' ' * (tab_width*$&.length - $`.length % tab_width)
+ } && $~
+ line
+ end.join("\n")
+ end
+
+ content << "\n" unless content[-1, 1] == "\n"
+
+ set_input StringIO.new content
+
+ @base_char_no = 0
+ @char_no = 0
+ @exp_line_no = @line_no = 1
+ @here_readed = []
+ @readed = []
+ @rests = []
+ @seek = 0
+
+ @here_header = false
+ @indent = 0
+ @indent_stack = []
+ @lex_state = EXPR_BEG
+ @space_seen = false
+
+ @continue = false
+ @line = ""
+
+ @skip_space = false
+ @readed_auto_clean_up = false
+ @exception_on_syntax_error = true
+
+ @prompt = nil
+ @prev_seek = nil
+ end
+
+ def inspect # :nodoc:
+ "#<%s:0x%x lex_state %p space_seen %p>" % [
+ self.class, object_id,
+ @lex_state, @space_seen,
+ ]
+ end
+
+ attr_accessor :skip_space
+ attr_accessor :readed_auto_clean_up
+ attr_accessor :exception_on_syntax_error
+
+ attr_reader :seek
+ attr_reader :char_no
+ attr_reader :line_no
+ attr_reader :indent
+
+ # io functions
+ def set_input(io, p = nil, &block)
+ @io = io
+ if p.respond_to?(:call)
+ @input = p
+ elsif block_given?
+ @input = block
+ else
+ @input = Proc.new{@io.gets}
+ end
+ end
+
+ def get_readed
+ if idx = @readed.reverse.index("\n")
+ @base_char_no = idx
+ else
+ @base_char_no += @readed.size
+ end
+
+ readed = @readed.join("")
+ @readed = []
+ readed
+ end
+
+ def getc
+ while @rests.empty?
+ # return nil unless buf_input
+ @rests.push nil unless buf_input
+ end
+ c = @rests.shift
+ if @here_header
+ @here_readed.push c
+ else
+ @readed.push c
+ end
+ @seek += 1
+ if c == "\n"
+ @line_no += 1
+ @char_no = 0
+ else
+ @char_no += 1
+ end
+ c
+ end
+
+ def gets
+ l = ""
+ while c = getc
+ l.concat(c)
+ break if c == "\n"
+ end
+ return nil if l == "" and c.nil?
+ l
+ end
+
+ def eof?
+ @io.eof?
+ end
+
+ def getc_of_rests
+ if @rests.empty?
+ nil
+ else
+ getc
+ end
+ end
+
+ def ungetc(c = nil)
+ if @here_readed.empty?
+ c2 = @readed.pop
+ else
+ c2 = @here_readed.pop
+ end
+ c = c2 unless c
+ @rests.unshift c #c =
+ @seek -= 1
+ if c == "\n"
+ @line_no -= 1
+ if idx = @readed.reverse.index("\n")
+ @char_no = @readed.size - idx
+ else
+ @char_no = @base_char_no + @readed.size
+ end
+ else
+ @char_no -= 1
+ end
+ end
+
+ def peek_equal?(str)
+ chrs = str.split(//)
+ until @rests.size >= chrs.size
+ return false unless buf_input
+ end
+ @rests[0, chrs.size] == chrs
+ end
+
+ def peek_match?(regexp)
+ while @rests.empty?
+ return false unless buf_input
+ end
+ regexp =~ @rests.join("")
+ end
+
+ def peek(i = 0)
+ while @rests.size <= i
+ return nil unless buf_input
+ end
+ @rests[i]
+ end
+
+ def buf_input
+ prompt
+ line = @input.call
+ return nil unless line
+ @rests.concat line.split(//)
+ true
+ end
+ private :buf_input
+
+ def set_prompt(p = nil, &block)
+ p = block if block_given?
+ if p.respond_to?(:call)
+ @prompt = p
+ else
+ @prompt = Proc.new{print p}
+ end
+ end
+
+ def prompt
+ if @prompt
+ @prompt.call(@ltype, @indent, @continue, @line_no)
+ end
+ end
+
+ def initialize_input
+ @ltype = nil
+ @quoted = nil
+ @indent = 0
+ @indent_stack = []
+ @lex_state = EXPR_BEG
+ @space_seen = false
+ @here_header = false
+
+ @continue = false
+ prompt
+
+ @line = ""
+ @exp_line_no = @line_no
+ end
+
+ def each_top_level_statement
+ initialize_input
+ catch(:TERM_INPUT) do
+ loop do
+ begin
+ @continue = false
+ prompt
+ unless l = lex
+ throw :TERM_INPUT if @line == ''
+ else
+ #p l
+ @line.concat l
+ if @ltype or @continue or @indent > 0
+ next
+ end
+ end
+ if @line != "\n"
+ yield @line, @exp_line_no
+ end
+ break unless l
+ @line = ''
+ @exp_line_no = @line_no
+
+ @indent = 0
+ @indent_stack = []
+ prompt
+ rescue TerminateLineInput
+ initialize_input
+ prompt
+ get_readed
+ end
+ end
+ end
+ end
+
+ def lex
+ until (((tk = token).kind_of?(TkNL) || tk.kind_of?(TkEND_OF_SCRIPT)) &&
+ !@continue or
+ tk.nil?)
+ #p tk
+ #p @lex_state
+ #p self
+ end
+ line = get_readed
+ # print self.inspect
+ if line == "" and tk.kind_of?(TkEND_OF_SCRIPT) || tk.nil?
+ nil
+ else
+ line
+ end
+ end
+
+ def token
+ # require "tracer"
+ # Tracer.on
+ @prev_seek = @seek
+ @prev_line_no = @line_no
+ @prev_char_no = @char_no
+ begin
+ begin
+ tk = @OP.match(self)
+ @space_seen = tk.kind_of?(TkSPACE)
+ rescue SyntaxError => e
+ raise RDoc::Error, "syntax error: #{e.message}" if
+ @exception_on_syntax_error
+
+ tk = TkError.new(@seek, @line_no, @char_no)
+ end
+ end while @skip_space and tk.kind_of?(TkSPACE)
+
+ if @readed_auto_clean_up
+ get_readed
+ end
+ # Tracer.off
+ tk
+ end
+
+ ENINDENT_CLAUSE = [
+ "case", "class", "def", "do", "for", "if",
+ "module", "unless", "until", "while", "begin" #, "when"
+ ]
+
+ DEINDENT_CLAUSE = ["end" #, "when"
+ ]
+
+ PERCENT_LTYPE = {
+ "q" => "\'",
+ "Q" => "\"",
+ "x" => "\`",
+ "r" => "/",
+ "w" => "]",
+ "W" => "]",
+ "s" => ":"
+ }
+
+ PERCENT_PAREN = {
+ "{" => "}",
+ "[" => "]",
+ "<" => ">",
+ "(" => ")"
+ }
+
+ Ltype2Token = {
+ "\'" => TkSTRING,
+ "\"" => TkSTRING,
+ "\`" => TkXSTRING,
+ "/" => TkREGEXP,
+ "]" => TkDSTRING,
+ ":" => TkSYMBOL
+ }
+ DLtype2Token = {
+ "\"" => TkDSTRING,
+ "\`" => TkDXSTRING,
+ "/" => TkDREGEXP,
+ }
+
+ def lex_init()
+ @OP = IRB::SLex.new
+ @OP.def_rules("\0", "\004", "\032") do |op, io|
+ Token(TkEND_OF_SCRIPT)
+ end
+
+ @OP.def_rules(" ", "\t", "\f", "\r", "\13") do |op, io|
+ @space_seen = true
+ str = op
+ while (ch = getc) =~ /[ \t\f\r\13]/ do
+ str << ch
+ end
+ ungetc
+ Token TkSPACE, str
+ end
+
+ @OP.def_rule("#") do |op, io|
+ identify_comment
+ end
+
+ @OP.def_rule("=begin",
+ proc{|op, io| @prev_char_no == 0 && peek(0) =~ /\s/}) do
+ |op, io|
+ @ltype = "="
+ res = ''
+ until (ch = getc) == "\n" do res << ch end
+ until peek_equal?("=end") && peek(4) =~ /\s/ do
+ until (ch = getc) == "\n" do res << ch end
+ end
+ res << gets
+ @ltype = nil
+ Token(TkRD_COMMENT, res)
+ end
+
+ @OP.def_rule("\n") do |op, io|
+ print "\\n\n" if RDoc::RubyLex.debug?
+ case @lex_state
+ when EXPR_BEG, EXPR_FNAME, EXPR_DOT
+ @continue = true
+ else
+ @continue = false
+ @lex_state = EXPR_BEG
+ until (@indent_stack.empty? ||
+ [TkLPAREN, TkLBRACK, TkLBRACE,
+ TkfLPAREN, TkfLBRACK, TkfLBRACE].include?(@indent_stack.last))
+ @indent_stack.pop
+ end
+ end
+ @here_header = false
+ @here_readed = []
+ Token(TkNL)
+ end
+
+ @OP.def_rules("*", "**",
+ "=", "==", "===",
+ "=~", "<=>",
+ "<", "<=",
+ ">", ">=", ">>") do
+ |op, io|
+ case @lex_state
+ when EXPR_FNAME, EXPR_DOT
+ @lex_state = EXPR_ARG
+ else
+ @lex_state = EXPR_BEG
+ end
+ Token(op)
+ end
+
+ @OP.def_rules("!", "!=", "!~") do
+ |op, io|
+ @lex_state = EXPR_BEG
+ Token(op)
+ end
+
+ @OP.def_rules("<<") do
+ |op, io|
+ tk = nil
+ if @lex_state != EXPR_END && @lex_state != EXPR_CLASS &&
+ (@lex_state != EXPR_ARG || @space_seen)
+ c = peek(0)
+ if /\S/ =~ c && (/["'`]/ =~ c || /[\w_]/ =~ c || c == "-")
+ tk = identify_here_document
+ end
+ end
+ unless tk
+ tk = Token(op)
+ case @lex_state
+ when EXPR_FNAME, EXPR_DOT
+ @lex_state = EXPR_ARG
+ else
+ @lex_state = EXPR_BEG
+ end
+ end
+ tk
+ end
+
+ @OP.def_rules("'", '"') do
+ |op, io|
+ identify_string(op)
+ end
+
+ @OP.def_rules("`") do
+ |op, io|
+ if @lex_state == EXPR_FNAME
+ @lex_state = EXPR_END
+ Token(op)
+ else
+ identify_string(op)
+ end
+ end
+
+ @OP.def_rules('?') do
+ |op, io|
+ if @lex_state == EXPR_END
+ @lex_state = EXPR_BEG
+ Token(TkQUESTION)
+ else
+ ch = getc
+ if @lex_state == EXPR_ARG && ch =~ /\s/
+ ungetc
+ @lex_state = EXPR_BEG;
+ Token(TkQUESTION)
+ else
+ str = ch
+ if ch == '\\'
+ str << read_escape
+ end
+ @lex_state = EXPR_END
+ str << (ch.respond_to?(:ord) ? ch.ord : ch[0])
+ Token(TkINTEGER, str)
+ end
+ end
+ end
+
+ @OP.def_rules("&", "&&", "|", "||") do
+ |op, io|
+ @lex_state = EXPR_BEG
+ Token(op)
+ end
+
+ @OP.def_rules("+=", "-=", "*=", "**=",
+ "&=", "|=", "^=", "<<=", ">>=", "||=", "&&=") do
+ |op, io|
+ @lex_state = EXPR_BEG
+ op =~ /^(.*)=$/
+ Token(TkOPASGN, $1)
+ end
+
+ @OP.def_rule("+@", proc{|op, io| @lex_state == EXPR_FNAME}) do
+ |op, io|
+ @lex_state = EXPR_ARG
+ Token(op)
+ end
+
+ @OP.def_rule("-@", proc{|op, io| @lex_state == EXPR_FNAME}) do
+ |op, io|
+ @lex_state = EXPR_ARG
+ Token(op)
+ end
+
+ @OP.def_rules("+", "-") do
+ |op, io|
+ catch(:RET) do
+ if @lex_state == EXPR_ARG
+ if @space_seen and peek(0) =~ /[0-9]/
+ throw :RET, identify_number
+ else
+ @lex_state = EXPR_BEG
+ end
+ elsif @lex_state != EXPR_END and peek(0) =~ /[0-9]/
+ throw :RET, identify_number
+ else
+ @lex_state = EXPR_BEG
+ end
+ Token(op)
+ end
+ end
+
+ @OP.def_rule(".") do
+ |op, io|
+ @lex_state = EXPR_BEG
+ if peek(0) =~ /[0-9]/
+ ungetc
+ identify_number
+ else
+ # for "obj.if" etc.
+ @lex_state = EXPR_DOT
+ Token(TkDOT)
+ end
+ end
+
+ @OP.def_rules("..", "...") do
+ |op, io|
+ @lex_state = EXPR_BEG
+ Token(op)
+ end
+
+ lex_int2
+ end
+
+ def lex_int2
+ @OP.def_rules("]", "}", ")") do
+ |op, io|
+ @lex_state = EXPR_END
+ @indent -= 1
+ @indent_stack.pop
+ Token(op)
+ end
+
+ @OP.def_rule(":") do
+ |op, io|
+ if @lex_state == EXPR_END || peek(0) =~ /\s/
+ @lex_state = EXPR_BEG
+ Token(TkCOLON)
+ else
+ @lex_state = EXPR_FNAME;
+ Token(TkSYMBEG)
+ end
+ end
+
+ @OP.def_rule("::") do
+ |op, io|
+ # p @lex_state.id2name, @space_seen
+ if @lex_state == EXPR_BEG or @lex_state == EXPR_ARG && @space_seen
+ @lex_state = EXPR_BEG
+ Token(TkCOLON3)
+ else
+ @lex_state = EXPR_DOT
+ Token(TkCOLON2)
+ end
+ end
+
+ @OP.def_rule("/") do
+ |op, io|
+ if @lex_state == EXPR_BEG || @lex_state == EXPR_MID
+ identify_string(op)
+ elsif peek(0) == '='
+ getc
+ @lex_state = EXPR_BEG
+ Token(TkOPASGN, "/") #/)
+ elsif @lex_state == EXPR_ARG and @space_seen and peek(0) !~ /\s/
+ identify_string(op)
+ else
+ @lex_state = EXPR_BEG
+ Token("/") #/)
+ end
+ end
+
+ @OP.def_rules("^") do
+ |op, io|
+ @lex_state = EXPR_BEG
+ Token("^")
+ end
+
+ # @OP.def_rules("^=") do
+ # @lex_state = EXPR_BEG
+ # Token(OP_ASGN, :^)
+ # end
+
+ @OP.def_rules(",") do
+ |op, io|
+ @lex_state = EXPR_BEG
+ Token(op)
+ end
+
+ @OP.def_rules(";") do
+ |op, io|
+ @lex_state = EXPR_BEG
+ until (@indent_stack.empty? ||
+ [TkLPAREN, TkLBRACK, TkLBRACE,
+ TkfLPAREN, TkfLBRACK, TkfLBRACE].include?(@indent_stack.last))
+ @indent_stack.pop
+ end
+ Token(op)
+ end
+
+ @OP.def_rule("~") do
+ |op, io|
+ @lex_state = EXPR_BEG
+ Token("~")
+ end
+
+ @OP.def_rule("~@", proc{|op, io| @lex_state == EXPR_FNAME}) do
+ |op, io|
+ @lex_state = EXPR_BEG
+ Token("~")
+ end
+
+ @OP.def_rule("(") do
+ |op, io|
+ @indent += 1
+ if @lex_state == EXPR_BEG || @lex_state == EXPR_MID
+ @lex_state = EXPR_BEG
+ tk_c = TkfLPAREN
+ else
+ @lex_state = EXPR_BEG
+ tk_c = TkLPAREN
+ end
+ @indent_stack.push tk_c
+ tk = Token(tk_c)
+ end
+
+ @OP.def_rule("[]", proc{|op, io| @lex_state == EXPR_FNAME}) do
+ |op, io|
+ @lex_state = EXPR_ARG
+ Token("[]")
+ end
+
+ @OP.def_rule("[]=", proc{|op, io| @lex_state == EXPR_FNAME}) do
+ |op, io|
+ @lex_state = EXPR_ARG
+ Token("[]=")
+ end
+
+ @OP.def_rule("[") do
+ |op, io|
+ @indent += 1
+ if @lex_state == EXPR_FNAME
+ tk_c = TkfLBRACK
+ else
+ if @lex_state == EXPR_BEG || @lex_state == EXPR_MID
+ tk_c = TkLBRACK
+ elsif @lex_state == EXPR_ARG && @space_seen
+ tk_c = TkLBRACK
+ else
+ tk_c = TkfLBRACK
+ end
+ @lex_state = EXPR_BEG
+ end
+ @indent_stack.push tk_c
+ Token(tk_c)
+ end
+
+ @OP.def_rule("{") do
+ |op, io|
+ @indent += 1
+ if @lex_state != EXPR_END && @lex_state != EXPR_ARG
+ tk_c = TkLBRACE
+ else
+ tk_c = TkfLBRACE
+ end
+ @lex_state = EXPR_BEG
+ @indent_stack.push tk_c
+ Token(tk_c)
+ end
+
+ @OP.def_rule('\\') do
+ |op, io|
+ if getc == "\n"
+ @space_seen = true
+ @continue = true
+ Token(TkSPACE)
+ else
+ ungetc
+ Token("\\")
+ end
+ end
+
+ @OP.def_rule('%') do
+ |op, io|
+ if @lex_state == EXPR_BEG || @lex_state == EXPR_MID
+ identify_quotation
+ elsif peek(0) == '='
+ getc
+ Token(TkOPASGN, :%)
+ elsif @lex_state == EXPR_ARG and @space_seen and peek(0) !~ /\s/
+ identify_quotation
+ else
+ @lex_state = EXPR_BEG
+ Token("%") #))
+ end
+ end
+
+ @OP.def_rule('$') do
+ |op, io|
+ identify_gvar
+ end
+
+ @OP.def_rule('@') do
+ |op, io|
+ if peek(0) =~ /[\w@]/
+ ungetc
+ identify_identifier
+ else
+ Token("@")
+ end
+ end
+
+ # @OP.def_rule("def", proc{|op, io| /\s/ =~ io.peek(0)}) do
+ # |op, io|
+ # @indent += 1
+ # @lex_state = EXPR_FNAME
+ # # @lex_state = EXPR_END
+ # # until @rests[0] == "\n" or @rests[0] == ";"
+ # # rests.shift
+ # # end
+ # end
+
+ @OP.def_rule("_") do
+ if peek_match?(/_END__/) and @lex_state == EXPR_BEG then
+ Token(TkEND_OF_SCRIPT)
+ else
+ ungetc
+ identify_identifier
+ end
+ end
+
+ @OP.def_rule("") do
+ |op, io|
+ printf "MATCH: start %s: %s\n", op, io.inspect if RDoc::RubyLex.debug?
+ if peek(0) =~ /[0-9]/
+ t = identify_number
+ else
+ t = identify_identifier
+ end
+ printf "MATCH: end %s: %s\n", op, io.inspect if RDoc::RubyLex.debug?
+ t
+ end
+
+ p @OP if RDoc::RubyLex.debug?
+ end
+
+ def identify_gvar
+ @lex_state = EXPR_END
+
+ case ch = getc
+ when /[~_*$?!@\/\\;,=:<>".]/ #"
+ Token(TkGVAR, "$" + ch)
+ when "-"
+ Token(TkGVAR, "$-" + getc)
+ when "&", "`", "'", "+"
+ Token(TkBACK_REF, "$"+ch)
+ when /[1-9]/
+ ref = ch
+ while (ch = getc) =~ /[0-9]/ do ref << ch end
+ ungetc
+ Token(TkNTH_REF, "$#{ref}")
+ when /\w/
+ ungetc
+ ungetc
+ identify_identifier
+ else
+ ungetc
+ Token("$")
+ end
+ end
+
+ def identify_identifier
+ token = ""
+ if peek(0) =~ /[$@]/
+ token.concat(c = getc)
+ if c == "@" and peek(0) == "@"
+ token.concat getc
+ 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
+ print " :#{ch}: " if RDoc::RubyLex.debug?
+ token.concat ch
+ end
+
+ ungetc
+
+ if (ch == "!" || ch == "?") && token[0,1] =~ /\w/ && peek(0) != "="
+ token.concat getc
+ end
+
+ # almost fix token
+
+ case token
+ when /^\$/
+ return Token(TkGVAR, token)
+ when /^\@\@/
+ @lex_state = EXPR_END
+ # p Token(TkCVAR, token)
+ return Token(TkCVAR, token)
+ when /^\@/
+ @lex_state = EXPR_END
+ return Token(TkIVAR, token)
+ end
+
+ if @lex_state != EXPR_DOT
+ print token, "\n" if RDoc::RubyLex.debug?
+
+ token_c, *trans = TkReading2Token[token]
+ if token_c
+ # reserved word?
+
+ if (@lex_state != EXPR_BEG &&
+ @lex_state != EXPR_FNAME &&
+ trans[1])
+ # modifiers
+ token_c = TkSymbol2Token[trans[1]]
+ @lex_state = trans[0]
+ else
+ if @lex_state != EXPR_FNAME
+ if ENINDENT_CLAUSE.include?(token)
+ # check for ``class = val'' etc.
+ valid = true
+ case token
+ when "class"
+ valid = false unless peek_match?(/^\s*(<<|\w|::)/)
+ when "def"
+ valid = false if peek_match?(/^\s*(([+-\/*&\|^]|<<|>>|\|\||\&\&)=|\&\&|\|\|)/)
+ when "do"
+ valid = false if peek_match?(/^\s*([+-\/*]?=|\*|<|>|\&)/)
+ when *ENINDENT_CLAUSE
+ valid = false if peek_match?(/^\s*([+-\/*]?=|\*|<|>|\&|\|)/)
+ else
+ # no nothing
+ end
+ if valid
+ if token == "do"
+ if ![TkFOR, TkWHILE, TkUNTIL].include?(@indent_stack.last)
+ @indent += 1
+ @indent_stack.push token_c
+ end
+ else
+ @indent += 1
+ @indent_stack.push token_c
+ end
+ end
+
+ elsif DEINDENT_CLAUSE.include?(token)
+ @indent -= 1
+ @indent_stack.pop
+ end
+ @lex_state = trans[0]
+ else
+ @lex_state = EXPR_END
+ end
+ end
+ return Token(token_c, token)
+ end
+ end
+
+ if @lex_state == EXPR_FNAME
+ @lex_state = EXPR_END
+ if peek(0) == '='
+ token.concat getc
+ end
+ elsif @lex_state == EXPR_BEG || @lex_state == EXPR_DOT
+ @lex_state = EXPR_ARG
+ else
+ @lex_state = EXPR_END
+ end
+
+ if token[0, 1] =~ /[A-Z]/
+ return Token(TkCONSTANT, token)
+ elsif token[token.size - 1, 1] =~ /[!?]/
+ return Token(TkFID, token)
+ else
+ return Token(TkIDENTIFIER, token)
+ end
+ end
+
+ def identify_here_document
+ ch = getc
+ # if lt = PERCENT_LTYPE[ch]
+ if ch == "-"
+ ch = getc
+ indent = true
+ end
+ if /['"`]/ =~ ch
+ lt = ch
+ quoted = ""
+ while (c = getc) && c != lt
+ quoted.concat c
+ end
+ else
+ lt = '"'
+ quoted = ch.dup
+ while (c = getc) && c =~ /\w/
+ quoted.concat c
+ end
+ ungetc
+ end
+
+ ltback, @ltype = @ltype, lt
+ reserve = []
+ while ch = getc
+ reserve.push ch
+ if ch == "\\"
+ reserve.push ch = getc
+ elsif ch == "\n"
+ break
+ end
+ end
+
+ @here_header = false
+ doc = ''
+ while l = gets
+ l = l.sub(/(:?\r)?\n\z/, '')
+ if (indent ? l.strip : l) == quoted
+ break
+ end
+ doc << l
+ end
+
+ @here_header = true
+ @here_readed.concat reserve
+ while ch = reserve.pop
+ ungetc ch
+ end
+
+ @ltype = ltback
+ @lex_state = EXPR_END
+ Token(Ltype2Token[lt], doc)
+ end
+
+ def identify_quotation
+ ch = getc
+ if lt = PERCENT_LTYPE[ch]
+ ch = getc
+ elsif ch =~ /\W/
+ lt = "\""
+ else
+ raise RDoc::Error, "unknown type of %string #{ch.inspect}"
+ end
+ # if ch !~ /\W/
+ # ungetc
+ # next
+ # end
+ #@ltype = lt
+ @quoted = ch unless @quoted = PERCENT_PAREN[ch]
+ identify_string(lt, @quoted)
+ end
+
+ def identify_number
+ @lex_state = EXPR_END
+
+ num = ''
+
+ if peek(0) == "0" && peek(1) !~ /[.eE]/
+ num << getc
+
+ case peek(0)
+ when /[xX]/
+ ch = getc
+ match = /[0-9a-fA-F_]/
+ when /[bB]/
+ ch = getc
+ match = /[01_]/
+ when /[oO]/
+ ch = getc
+ match = /[0-7_]/
+ when /[dD]/
+ ch = getc
+ match = /[0-9_]/
+ when /[0-7]/
+ match = /[0-7_]/
+ when /[89]/
+ raise RDoc::Error, "Illegal octal digit"
+ else
+ return Token(TkINTEGER, num)
+ end
+
+ num << ch if ch
+
+ len0 = true
+ non_digit = false
+ while ch = getc
+ num << ch
+ if match =~ ch
+ if ch == "_"
+ if non_digit
+ raise RDoc::Error, "trailing `#{ch}' in number"
+ else
+ non_digit = ch
+ end
+ else
+ non_digit = false
+ len0 = false
+ end
+ else
+ ungetc
+ num[-1, 1] = ''
+ if len0
+ raise RDoc::Error, "numeric literal without digits"
+ end
+ if non_digit
+ raise RDoc::Error, "trailing `#{non_digit}' in number"
+ end
+ break
+ end
+ end
+ return Token(TkINTEGER, num)
+ end
+
+ type = TkINTEGER
+ allow_point = true
+ allow_e = true
+ non_digit = false
+ while ch = getc
+ num << ch
+ case ch
+ when /[0-9]/
+ non_digit = false
+ when "_"
+ non_digit = ch
+ when allow_point && "."
+ if non_digit
+ raise RDoc::Error, "trailing `#{non_digit}' in number"
+ end
+ type = TkFLOAT
+ if peek(0) !~ /[0-9]/
+ type = TkINTEGER
+ ungetc
+ num[-1, 1] = ''
+ break
+ end
+ allow_point = false
+ when allow_e && "e", allow_e && "E"
+ if non_digit
+ raise RDoc::Error, "trailing `#{non_digit}' in number"
+ end
+ type = TkFLOAT
+ if peek(0) =~ /[+-]/
+ num << getc
+ end
+ allow_e = false
+ allow_point = false
+ non_digit = ch
+ else
+ if non_digit
+ raise RDoc::Error, "trailing `#{non_digit}' in number"
+ end
+ ungetc
+ num[-1, 1] = ''
+ break
+ end
+ end
+
+ Token(type, num)
+ end
+
+ def identify_string(ltype, quoted = ltype)
+ @ltype = ltype
+ @quoted = quoted
+ str = @ltype.dup
+
+ subtype = nil
+ begin
+ nest = 0
+
+ while ch = getc
+ str << ch
+
+ if @quoted == ch and nest == 0
+ break
+ elsif @ltype != "'" && @ltype != "]" && @ltype != ":" and ch == "#"
+ ch = getc
+ subtype = true
+ if ch == "{" then
+ str << ch << skip_inner_expression
+ else
+ ungetc
+ end
+ elsif ch == '\\' and @ltype == "'" #'
+ case ch = getc
+ when "\\", "\n", "'"
+ else
+ ungetc
+ end
+ elsif ch == '\\' #'
+ str << read_escape
+ end
+
+ if PERCENT_PAREN.values.include?(@quoted)
+ if PERCENT_PAREN[ch] == @quoted
+ nest += 1
+ elsif ch == @quoted
+ nest -= 1
+ end
+ end
+ end
+
+ if @ltype == "/"
+ if peek(0) =~ /i|m|x|o|e|s|u|n/
+ getc
+ end
+ end
+
+ if subtype
+ Token(DLtype2Token[ltype], str)
+ else
+ Token(Ltype2Token[ltype], str)
+ end
+ ensure
+ @ltype = nil
+ @quoted = nil
+ @lex_state = EXPR_END
+ end
+ end
+
+ def skip_inner_expression
+ res = ""
+ nest = 0
+ while (ch = getc)
+ res << ch
+ if ch == '}'
+ break if nest.zero?
+ nest -= 1
+ elsif ch == '{'
+ nest += 1
+ end
+ end
+ res
+ end
+
+ def identify_comment
+ @ltype = "#"
+
+ comment = '#'
+
+ while ch = getc
+ # if ch == "\\" #"
+ # read_escape
+ # end
+ if ch == "\n"
+ @ltype = nil
+ ungetc
+ break
+ end
+
+ comment << ch
+ end
+
+ return Token(TkCOMMENT, comment)
+ end
+
+ def read_escape
+ escape = ''
+ ch = getc
+ escape << ch
+
+ case ch
+ when "\n", "\r", "\f"
+ when "\\", "n", "t", "r", "f", "v", "a", "e", "b", "s" #"
+ when /[0-7]/
+ ungetc ch
+ 3.times do
+ ch = getc
+ escape << ch
+ case ch
+ when /[0-7]/
+ when nil
+ break
+ else
+ ungetc
+ break
+ end
+ end
+
+ when "x"
+ 2.times do
+ ch = getc
+ escape << ch
+ case ch
+ when /[0-9a-fA-F]/
+ when nil
+ break
+ else
+ ungetc
+ break
+ end
+ end
+
+ when "M"
+ ch = getc
+ escape << ch
+ if ch != '-'
+ ungetc
+ else
+ ch = getc
+ escape << ch
+ if ch == "\\" #"
+ escape << read_escape
+ end
+ end
+
+ when "C", "c" #, "^"
+ if ch == "C" and (ch = getc) != "-"
+ escape << ch
+ ungetc
+ elsif (ch = getc) == "\\" #"
+ escape << ch << read_escape
+ end
+ else
+ # other characters
+ end
+
+ escape
+ end
+
+ # :startdoc:
+
+end
+
+#RDoc::RubyLex.debug_level = 1
+
diff --git a/lib/rdoc/ruby_token.rb b/lib/rdoc/ruby_token.rb
new file mode 100644
index 0000000000..87fdc73b0a
--- /dev/null
+++ b/lib/rdoc/ruby_token.rb
@@ -0,0 +1,416 @@
+#--
+# irb/ruby-token.rb - ruby tokens
+# $Release Version: 0.9.5$
+# $Revision: 11708 $
+# $Date: 2007-02-12 15:01:19 -0800 (Mon, 12 Feb 2007) $
+# by Keiju ISHITSUKA(keiju@ruby-lang.org)
+#++
+# Definitions of all tokens involved in the lexical analysis.
+#
+# This class is not documented because it is so deep in the internals.
+
+module RDoc::RubyToken
+ # :stopdoc:
+
+ EXPR_BEG = :EXPR_BEG
+ EXPR_MID = :EXPR_MID
+ EXPR_END = :EXPR_END
+ EXPR_ARG = :EXPR_ARG
+ EXPR_FNAME = :EXPR_FNAME
+ EXPR_DOT = :EXPR_DOT
+ EXPR_CLASS = :EXPR_CLASS
+
+ # for ruby 1.4X
+ if !defined?(Symbol)
+ Symbol = Integer
+ end
+
+ def set_token_position(line, char)
+ @prev_line_no = line
+ @prev_char_no = char
+ end
+
+ class Token
+ def initialize(seek, line_no, char_no, text = nil)
+ @seek = seek
+ @line_no = line_no
+ @char_no = char_no
+ @text = text
+ end
+
+ attr :seek
+ attr :line_no
+ attr :char_no
+
+ attr_accessor :text
+
+ def ==(other)
+ self.class == other.class and
+ other.line_no == @line_no and
+ other.char_no == @char_no and
+ other.text == @text
+ end
+
+ ##
+ # Because we're used in contexts that expect to return a token, we set the
+ # text string and then return ourselves
+
+ def set_text(text)
+ @text = text
+ self
+ end
+
+ end
+
+ class TkNode < Token
+ def initialize(seek, line_no, char_no, node = nil)
+ super seek, line_no, char_no
+ @node = node
+ end
+
+ attr :node
+
+ def ==(other)
+ self.class == other.class and
+ other.line_no == @line_no and
+ other.char_no == @char_no and
+ other.node == @node
+ end
+
+ def set_text text
+ @node = text
+ self
+ end
+
+ alias text node
+ end
+
+ class TkId < Token
+ def initialize(seek, line_no, char_no, name)
+ super(seek, line_no, char_no)
+ @name = name
+ end
+ attr :name
+
+ def ==(other)
+ self.class == other.class and
+ other.line_no == @line_no and
+ other.char_no == @char_no and
+ other.name == @name
+ end
+
+ def set_text text
+ @name = text
+ self
+ end
+
+ alias text name
+ end
+
+ class TkKW < TkId
+ end
+
+ class TkVal < Token
+ def initialize(seek, line_no, char_no, value = nil)
+ super(seek, line_no, char_no)
+ @value = value
+ end
+ attr_accessor :value
+
+ def ==(other)
+ self.class == other.class and
+ other.line_no == @line_no and
+ other.char_no == @char_no and
+ other.value == @value
+ end
+
+ def set_text text
+ @value = text
+ self
+ end
+
+ alias text value
+ end
+
+ class TkOp < Token
+ def initialize(seek, line_no, char_no, name = nil)
+ super seek, line_no, char_no
+ @name = name
+ end
+
+ attr_accessor :name
+
+ def ==(other)
+ self.class == other.class and
+ other.line_no == @line_no and
+ other.char_no == @char_no and
+ other.name == @name
+ end
+
+ def set_text text
+ @name = text
+ self
+ end
+
+ alias text name
+ end
+
+ class TkOPASGN < TkOp
+ def initialize(seek, line_no, char_no, op)
+ super(seek, line_no, char_no)
+ op = TkReading2Token[op][0] unless op.kind_of?(Symbol)
+ @op = op
+ @text = nil
+ end
+
+ attr :op
+
+ def ==(other)
+ self.class == other.class and
+ other.line_no == @line_no and
+ other.char_no == @char_no and
+ other.op == @op
+ end
+
+ def text
+ @text ||= "#{TkToken2Reading[op]}="
+ end
+ end
+
+ class TkUnknownChar < Token
+ def initialize(seek, line_no, char_no, id)
+ super(seek, line_no, char_no)
+ @name = name
+ end
+ attr :name
+
+ def ==(other)
+ self.class == other.class and
+ other.line_no == @line_no and
+ other.char_no == @char_no and
+ other.name == @name
+ end
+
+ def set_text text
+ @name = text
+ self
+ end
+
+ alias text name
+ end
+
+ class TkError < Token
+ end
+
+ def Token(token, value = nil)
+ value ||= TkToken2Reading[token]
+
+ case token
+ when String
+ if (tk = TkReading2Token[token]).nil?
+ IRB.fail TkReading2TokenNoKey, token
+ end
+
+ tk = Token(tk[0], value)
+
+ if tk.kind_of?(TkOp) then
+ tk.name = token
+ end
+ when Symbol
+ if (tk = TkSymbol2Token[token]).nil?
+ IRB.fail TkSymbol2TokenNoKey, token
+ end
+
+ tk = Token(tk[0], value)
+ else
+ if token.instance_method(:initialize).arity == 3 then
+ tk = token.new(@prev_seek, @prev_line_no, @prev_char_no)
+ tk.set_text value
+ else
+ tk = token.new(@prev_seek, @prev_line_no, @prev_char_no, value)
+ end
+ end
+
+ tk
+ end
+
+ TokenDefinitions = [
+ [:TkCLASS, TkKW, "class", EXPR_CLASS],
+ [:TkMODULE, TkKW, "module", EXPR_BEG],
+ [:TkDEF, TkKW, "def", EXPR_FNAME],
+ [:TkUNDEF, TkKW, "undef", EXPR_FNAME],
+ [:TkBEGIN, TkKW, "begin", EXPR_BEG],
+ [:TkRESCUE, TkKW, "rescue", EXPR_MID],
+ [:TkENSURE, TkKW, "ensure", EXPR_BEG],
+ [:TkEND, TkKW, "end", EXPR_END],
+ [:TkIF, TkKW, "if", EXPR_BEG, :TkIF_MOD],
+ [:TkUNLESS, TkKW, "unless", EXPR_BEG, :TkUNLESS_MOD],
+ [:TkTHEN, TkKW, "then", EXPR_BEG],
+ [:TkELSIF, TkKW, "elsif", EXPR_BEG],
+ [:TkELSE, TkKW, "else", EXPR_BEG],
+ [:TkCASE, TkKW, "case", EXPR_BEG],
+ [:TkWHEN, TkKW, "when", EXPR_BEG],
+ [:TkWHILE, TkKW, "while", EXPR_BEG, :TkWHILE_MOD],
+ [:TkUNTIL, TkKW, "until", EXPR_BEG, :TkUNTIL_MOD],
+ [:TkFOR, TkKW, "for", EXPR_BEG],
+ [:TkBREAK, TkKW, "break", EXPR_END],
+ [:TkNEXT, TkKW, "next", EXPR_END],
+ [:TkREDO, TkKW, "redo", EXPR_END],
+ [:TkRETRY, TkKW, "retry", EXPR_END],
+ [:TkIN, TkKW, "in", EXPR_BEG],
+ [:TkDO, TkKW, "do", EXPR_BEG],
+ [:TkRETURN, TkKW, "return", EXPR_MID],
+ [:TkYIELD, TkKW, "yield", EXPR_END],
+ [:TkSUPER, TkKW, "super", EXPR_END],
+ [:TkSELF, TkKW, "self", EXPR_END],
+ [:TkNIL, TkKW, "nil", EXPR_END],
+ [:TkTRUE, TkKW, "true", EXPR_END],
+ [:TkFALSE, TkKW, "false", EXPR_END],
+ [:TkAND, TkKW, "and", EXPR_BEG],
+ [:TkOR, TkKW, "or", EXPR_BEG],
+ [:TkNOT, TkKW, "not", EXPR_BEG],
+ [:TkIF_MOD, TkKW],
+ [:TkUNLESS_MOD, TkKW],
+ [:TkWHILE_MOD, TkKW],
+ [:TkUNTIL_MOD, TkKW],
+ [:TkALIAS, TkKW, "alias", EXPR_FNAME],
+ [:TkDEFINED, TkKW, "defined?", EXPR_END],
+ [:TklBEGIN, TkKW, "BEGIN", EXPR_END],
+ [:TklEND, TkKW, "END", EXPR_END],
+ [:Tk__LINE__, TkKW, "__LINE__", EXPR_END],
+ [:Tk__FILE__, TkKW, "__FILE__", EXPR_END],
+
+ [:TkIDENTIFIER, TkId],
+ [:TkFID, TkId],
+ [:TkGVAR, TkId],
+ [:TkCVAR, TkId],
+ [:TkIVAR, TkId],
+ [:TkCONSTANT, TkId],
+
+ [:TkINTEGER, TkVal],
+ [:TkFLOAT, TkVal],
+ [:TkSTRING, TkVal],
+ [:TkXSTRING, TkVal],
+ [:TkREGEXP, TkVal],
+ [:TkSYMBOL, TkVal],
+
+ [:TkDSTRING, TkNode],
+ [:TkDXSTRING, TkNode],
+ [:TkDREGEXP, TkNode],
+ [:TkNTH_REF, TkNode],
+ [:TkBACK_REF, TkNode],
+
+ [:TkUPLUS, TkOp, "+@"],
+ [:TkUMINUS, TkOp, "-@"],
+ [:TkPOW, TkOp, "**"],
+ [:TkCMP, TkOp, "<=>"],
+ [:TkEQ, TkOp, "=="],
+ [:TkEQQ, TkOp, "==="],
+ [:TkNEQ, TkOp, "!="],
+ [:TkGEQ, TkOp, ">="],
+ [:TkLEQ, TkOp, "<="],
+ [:TkANDOP, TkOp, "&&"],
+ [:TkOROP, TkOp, "||"],
+ [:TkMATCH, TkOp, "=~"],
+ [:TkNMATCH, TkOp, "!~"],
+ [:TkDOT2, TkOp, ".."],
+ [:TkDOT3, TkOp, "..."],
+ [:TkAREF, TkOp, "[]"],
+ [:TkASET, TkOp, "[]="],
+ [:TkLSHFT, TkOp, "<<"],
+ [:TkRSHFT, TkOp, ">>"],
+ [:TkCOLON2, TkOp, '::'],
+ [:TkCOLON3, TkOp, '::'],
+ #[:OPASGN, TkOp], # +=, -= etc. #
+ [:TkASSOC, TkOp, "=>"],
+ [:TkQUESTION, TkOp, "?"], #?
+ [:TkCOLON, TkOp, ":"], #:
+
+ [:TkfLPAREN, Token, "("], # func( #
+ [:TkfLBRACK, Token, "["], # func[ #
+ [:TkfLBRACE, Token, "{"], # func{ #
+ [:TkSTAR, Token, "*"], # *arg
+ [:TkAMPER, Token, "&"], # &arg #
+ [:TkSYMBEG, Token, ":"], # :SYMBOL
+
+ [:TkGT, TkOp, ">"],
+ [:TkLT, TkOp, "<"],
+ [:TkPLUS, TkOp, "+"],
+ [:TkMINUS, TkOp, "-"],
+ [:TkMULT, TkOp, "*"],
+ [:TkDIV, TkOp, "/"],
+ [:TkMOD, TkOp, "%"],
+ [:TkBITOR, TkOp, "|"],
+ [:TkBITXOR, TkOp, "^"],
+ [:TkBITAND, TkOp, "&"],
+ [:TkBITNOT, TkOp, "~"],
+ [:TkNOTOP, TkOp, "!"],
+
+ [:TkBACKQUOTE, TkOp, "`"],
+
+ [:TkASSIGN, Token, "="],
+ [:TkDOT, Token, "."],
+ [:TkLPAREN, Token, "("], #(exp)
+ [:TkLBRACK, Token, "["], #[arry]
+ [:TkLBRACE, Token, "{"], #{hash}
+ [:TkRPAREN, Token, ")"],
+ [:TkRBRACK, Token, "]"],
+ [:TkRBRACE, Token, "}"],
+ [:TkCOMMA, Token, ","],
+ [:TkSEMICOLON, Token, ";"],
+
+ [:TkCOMMENT, TkVal],
+ [:TkRD_COMMENT],
+ [:TkSPACE, Token, " "],
+ [:TkNL, Token, "\n"],
+ [:TkEND_OF_SCRIPT],
+
+ [:TkBACKSLASH, TkUnknownChar, "\\"],
+ [:TkAT, TkUnknownChar, "@"],
+ [:TkDOLLAR, TkUnknownChar, "$"],
+ ]
+
+ # {reading => token_class}
+ # {reading => [token_class, *opt]}
+ TkReading2Token = {}
+ TkToken2Reading = {}
+ TkSymbol2Token = {}
+
+ def self.def_token(token_n, super_token = Token, reading = nil, *opts)
+ token_n = token_n.id2name if token_n.kind_of?(Symbol)
+ if const_defined?(token_n)
+ IRB.fail AlreadyDefinedToken, token_n
+ end
+ token_c = eval("class #{token_n} < #{super_token}; end; #{token_n}")
+
+ if reading
+ TkToken2Reading[token_c] = reading
+
+ return if TkReading2Token[reading]
+
+ if opts.empty?
+ TkReading2Token[reading] = [token_c]
+ else
+ TkReading2Token[reading] = [token_c].concat(opts)
+ end
+ end
+ TkSymbol2Token[token_n.intern] = token_c
+ end
+
+ for defs in TokenDefinitions
+ def_token(*defs)
+ end
+
+ NEWLINE_TOKEN = TkNL.new nil, 0, 0, "\n"
+
+ class TkSYMBOL
+
+ def to_sym
+ @sym ||= text[1..-1].intern
+ end
+
+ end
+
+ # :startdoc:
+end
+
diff --git a/lib/rdoc/single_class.rb b/lib/rdoc/single_class.rb
new file mode 100644
index 0000000000..1226d56f84
--- /dev/null
+++ b/lib/rdoc/single_class.rb
@@ -0,0 +1,13 @@
+require 'rdoc/class_module'
+
+##
+# A singleton class
+
+class RDoc::SingleClass < RDoc::ClassModule
+
+ def ancestors
+ includes + [superclass]
+ end
+
+end
+
diff --git a/lib/rdoc/stats.rb b/lib/rdoc/stats.rb
index e18e3c23d7..8f51cbedcc 100644
--- a/lib/rdoc/stats.rb
+++ b/lib/rdoc/stats.rb
@@ -1,113 +1,245 @@
require 'rdoc'
##
-# Simple stats collector
+# RDoc stats collector
class RDoc::Stats
- attr_reader :num_classes
+ attr_reader :nodoc_constants
+ attr_reader :nodoc_methods
+
+ attr_reader :num_constants
attr_reader :num_files
attr_reader :num_methods
- attr_reader :num_modules
- def initialize(verbosity = 1)
- @num_classes = 0
- @num_files = 0
- @num_methods = 0
- @num_modules = 0
+ attr_reader :total_files
+
+ 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
@start = Time.now
@display = case verbosity
- when 0 then Quiet.new
- when 1 then Normal.new
- else Verbose.new
+ when 0 then Quiet.new total_files
+ when 1 then Normal.new total_files
+ else Verbose.new total_files
end
end
+ def begin_adding
+ @display.begin_adding
+ end
+
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)
@display.print_class klass
- @num_classes += 1
+ end
+
+ def add_constant(constant)
+ @display.print_constant constant
+ @num_constants += 1
+ @nodoc_constants += 1 if constant.document_self and constant.comment.empty?
end
def add_file(file)
- @display.print_file file
+ @display.print_file @num_files, file
@num_files += 1
end
def add_method(method)
@display.print_method method
@num_methods += 1
+ @nodoc_methods += 1 if method.document_self and method.comment.empty?
end
def add_module(mod)
@display.print_module mod
- @num_modules += 1
+ end
+
+ def done_adding
+ @display.done_adding
end
def print
- puts "Files: #@num_files"
- puts "Classes: #@num_classes"
- puts "Modules: #@num_modules"
- puts "Methods: #@num_methods"
- puts "Elapsed: " + sprintf("%0.1fs", Time.now - @start)
+ classes = RDoc::TopLevel.classes
+ num_classes = classes.length
+ nodoc_classes = classes.select do |klass|
+ klass.document_self and klass.comment.empty?
+ end.length
+
+ 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
+
+ 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
+ puts
+ puts "Elapsed: %0.1fs" % (Time.now - @start)
end
+ ##
+ # Stats printer that prints nothing
+
class Quiet
+
+ def initialize total_files
+ @total_files = total_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 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
- class Normal
- def print_alias(as)
- print 'a'
+ ##
+ # Stats printer that prints just the files being documented with a progress
+ # bar
+
+ class Normal < Quiet
+
+ def begin_adding # :nodoc:
+ puts "Parsing sources..."
end
- def print_class(klass)
- print 'C'
+ ##
+ # 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] = "..."
+ end
+
+ # Pad the line with whitespaces so that leftover output from the
+ # previous line doesn't show up.
+ line = "#{progress_bar}#{filename}"
+ padding = terminal_width - line.size
+ line << (" " * padding) if padding > 0
+
+ $stdout.print("#{line}\r")
+ $stdout.flush
+ else
+ puts "#{progress_bar} #{filename}"
+ end
end
- def print_file(file)
- print "\n#{file}: "
+ def done_adding # :nodoc:
+ puts
end
- def print_method(method)
- print 'm'
+ end
+
+ ##
+ # Stats printer that prints everything documented, including the documented
+ # status
+
+ class Verbose < Normal
+
+ ##
+ # Returns a marker for RDoc::CodeObject +co+ being undocumented
+
+ def nodoc co
+ " (undocumented)" unless co.documented?
end
- def print_module(mod)
- print 'M'
+ def print_alias as # :nodoc:
+ puts "\t\talias #{as.new_name} #{as.old_name}#{nodoc as}"
end
- end
- class Verbose
- def print_alias(as)
- puts "\t\talias #{as.new_name} #{as.old_name}"
+ def print_class(klass) # :nodoc:
+ puts "\tclass #{klass.full_name}#{nodoc klass}"
end
- def print_class(klass)
- puts "\tclass #{klass.full_name}"
+ def print_constant(constant) # :nodoc:
+ puts "\t\t#{constant.name}#{nodoc constant}"
end
- def print_file(file)
- puts file
+ def print_file(files_so_far, file) # :nodoc:
+ super
+ puts
end
- def print_method(method)
- puts "\t\t#{method.singleton ? '::' : '#'}#{method.name}"
+ def print_method(method) # :nodoc:
+ puts "\t\t#{method.singleton ? '::' : '#'}#{method.name}#{nodoc method}"
end
- def print_module(mod)
- puts "\tmodule #{mod.full_name}"
+ def print_module(mod) # :nodoc:
+ puts "\tmodule #{mod.full_name}#{nodoc mod}"
end
+
end
end
diff --git a/lib/rdoc/task.rb b/lib/rdoc/task.rb
new file mode 100644
index 0000000000..6417a66969
--- /dev/null
+++ b/lib/rdoc/task.rb
@@ -0,0 +1,254 @@
+#--
+# Copyright (c) 2003, 2004 Jim Weirich, 2009 Eric Hodel
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# 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/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 :<em>rdoc_dev</em>,
+# :clobber_<em>rdoc_dev</em>, and :re<em>rdoc_dev</em>.
+#
+# If you wish to have completely different task names, then pass a Hash as
+# first argument. With the <tt>:rdoc</tt>, <tt>:clobber_rdoc</tt> and
+# <tt>:rerdoc</tt> 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 <tt>:rdoc</tt>, <tt>:rdoc:clean</tt> and
+# <tt>:rdoc:force</tt>.
+
+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 the RDoc HTML files"
+ task rdoc_task_name
+
+ desc "Force 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
+
+ task rdoc_task_name => [rdoc_target]
+ file rdoc_target => @rdoc_files + [Rake.application.rakefile] do
+ rm_r @rdoc_dir rescue nil
+ @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 Rake
+
+ ##
+ # For backwards compatibility
+
+ RDocTask = RDoc::Task
+
+end
+# :startdoc:
+
diff --git a/lib/rdoc/template.rb b/lib/rdoc/template.rb
deleted file mode 100644
index 53d0e3ce68..0000000000
--- a/lib/rdoc/template.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-require 'erb'
-
-module RDoc; end
-
-##
-# An ERb wrapper that allows nesting of one ERb template inside another.
-#
-# This TemplatePage operates similarly to RDoc 1.x's TemplatePage, but uses
-# ERb instead of a custom template language.
-#
-# Converting from a RDoc 1.x template to an RDoc 2.x template is fairly easy.
-#
-# * %blah% becomes <%= values["blah"] %>
-# * !INCLUDE! becomes <%= template_include %>
-# * HREF:aref:name becomes <%= href values["aref"], values["name"] %>
-# * IF:blah becomes <% if values["blah"] then %>
-# * IFNOT:blah becomes <% unless values["blah"] then %>
-# * ENDIF:blah becomes <% end %>
-# * START:blah becomes <% values["blah"].each do |blah| %>
-# * END:blah becomes <% end %>
-#
-# To make nested loops easier to convert, start by converting START statements
-# to:
-#
-# <% values["blah"].each do |blah| $stderr.puts blah.keys %>
-#
-# So you can see what is being used inside which loop.
-
-class RDoc::TemplatePage
-
- ##
- # Create a new TemplatePage that will use +templates+.
-
- def initialize(*templates)
- @templates = templates
- end
-
- ##
- # Returns "<a href=\"#{ref}\">#{name}</a>"
-
- def href(ref, name)
- if ref then
- "<a href=\"#{ref}\">#{name}</a>"
- else
- name
- end
- end
-
- ##
- # Process the template using +values+, writing the result to +io+.
-
- def write_html_on(io, values)
- b = binding
- template_include = ""
-
- @templates.reverse_each do |template|
- template_include = ERB.new(template).result b
- end
-
- io.write template_include
- end
-
-end
-
diff --git a/lib/rdoc/text.rb b/lib/rdoc/text.rb
new file mode 100644
index 0000000000..5280aa0fd2
--- /dev/null
+++ b/lib/rdoc/text.rb
@@ -0,0 +1,130 @@
+##
+# Methods for manipulating comment text
+
+module RDoc::Text
+
+ ##
+ # Expands tab characters in +text+ to eight spaces
+
+ def expand_tabs text
+ expanded = []
+
+ text.each_line do |line|
+ line.gsub!(/^(.{8}*?)([^\t\r\n]{0,7})\t/) do
+ "#{$1}#{$2}#{' ' * (8 - $2.size)}"
+ end until line !~ /\t/
+
+ expanded << line
+ end
+
+ expanded.join
+ end
+
+ ##
+ # Flush +text+ left based on the shortest line
+
+ def flush_left text
+ indents = []
+
+ text.each_line do |line|
+ indents << (line =~ /[^\s]/ || 9999)
+ end
+
+ indent = indents.min
+
+ flush = []
+
+ text.each_line do |line|
+ line[/^ {0,#{indent}}/] = ''
+ flush << line
+ end
+
+ flush.join
+ end
+
+ ##
+ # Convert a string in markup format into HTML. Removes the first paragraph
+ # tags if +remove_para+ is true.
+ #
+ # Requires the including class to implement #formatter
+
+ def markup text
+ document = parse text
+
+ document.accept formatter
+ end
+
+ ##
+ # Strips hashes, expands tabs then flushes +text+ to the left
+
+ def normalize_comment text
+ return text if text.empty?
+
+ text = strip_hashes text
+ text = expand_tabs text
+ text = flush_left text
+ strip_newlines text
+ end
+
+ ##
+ # Normalizes +text+ then builds a RDoc::Markup::Document from it
+
+ def parse text
+ return text if RDoc::Markup::Document === text
+
+ text = normalize_comment text
+
+ return RDoc::Markup::Document.new if text =~ /\A\n*\z/
+
+ RDoc::Markup::Parser.parse text
+ rescue RDoc::Markup::Parser::Error => e
+ $stderr.puts <<-EOF
+While parsing markup, RDoc encountered a #{e.class}:
+
+#{e}
+\tfrom #{e.backtrace.join "\n\tfrom "}
+
+---8<---
+#{text}
+---8<---
+
+RDoc #{RDoc::VERSION}
+
+Ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} #{RUBY_RELEASE_DATE}
+
+Please file a bug report with the above information at:
+
+http://rubyforge.org/tracker/?atid=2472&group_id=627&func=browse
+
+ EOF
+ raise
+ end
+
+ ##
+ # Strips leading # characters from +text+
+
+ def strip_hashes text
+ return text if text =~ /^(?>\s*)[^\#]/
+ text.gsub(/^\s*(#+)/) { $1.tr '#',' ' }
+ end
+
+ ##
+ # Strips leading and trailing \n characters from +text+
+
+ def strip_newlines text
+ text.gsub(/\A\n*(.*?)\n*\z/m, '\1')
+ end
+
+ ##
+ # Strips /* */ style comments
+
+ def strip_stars text
+ text = text.gsub %r%Document-method:\s+[\w:.#]+%, ''
+ text.sub! %r%/\*+% do " " * $&.length end
+ text.sub! %r%\*+/% do " " * $&.length end
+ text.gsub! %r%^[ \t]*\*%m do " " * $&.length end
+ text
+ end
+
+end
+
diff --git a/lib/rdoc/tokenstream.rb b/lib/rdoc/tokenstream.rb
index 0a1eb9130b..b1e86543f7 100644
--- a/lib/rdoc/tokenstream.rb
+++ b/lib/rdoc/tokenstream.rb
@@ -9,25 +9,44 @@ module RDoc; end
module RDoc::TokenStream
- def token_stream
- @token_stream
+ ##
+ # Adds +tokens+ to the collected tokens
+
+ def add_tokens(*tokens)
+ tokens.flatten.each { |token| @token_stream << token }
end
- def start_collecting_tokens
+ alias add_token add_tokens
+
+ ##
+ # Starts collecting tokens
+
+ def collect_tokens
@token_stream = []
end
- def add_token(tk)
- @token_stream << tk
- end
+ alias start_collecting_tokens collect_tokens
- def add_tokens(tks)
- tks.each {|tk| add_token(tk)}
- end
+ ##
+ # Remove the last token from the collected tokens
def pop_token
@token_stream.pop
end
+ ##
+ # Current token stream
+
+ def token_stream
+ @token_stream
+ end
+
+ ##
+ # Returns a string representation of the token stream
+
+ def tokens_to_s
+ token_stream.map { |token| token.text }.join ''
+ end
+
end
diff --git a/lib/rdoc/top_level.rb b/lib/rdoc/top_level.rb
new file mode 100644
index 0000000000..306790fc15
--- /dev/null
+++ b/lib/rdoc/top_level.rb
@@ -0,0 +1,248 @@
+require 'rdoc/context'
+
+##
+# A TopLevel context is a representation of the contents of a single file
+
+class RDoc::TopLevel < RDoc::Context
+
+ ##
+ # This TopLevel's File::Stat struct
+
+ attr_accessor :file_stat
+
+ ##
+ # Relative name of this file
+
+ attr_accessor :relative_name
+
+ ##
+ # Absolute name of this file
+
+ attr_accessor :absolute_name
+
+ attr_accessor :diagram
+
+ ##
+ # The parser that processed this file
+
+ attr_accessor :parser
+
+ ##
+ # Returns all classes and modules discovered by RDoc
+
+ def self.all_classes_and_modules
+ classes_hash.values + modules_hash.values
+ end
+
+ ##
+ # Returns all classes discovered by RDoc
+
+ def self.classes
+ classes_hash.values
+ end
+
+ ##
+ # Hash of all classes known to RDoc
+
+ def self.classes_hash
+ @all_classes
+ end
+
+ ##
+ # All TopLevels known to RDoc
+
+ def self.files
+ @all_files.values
+ end
+
+ ##
+ # Hash of all files known to RDoc
+
+ def self.files_hash
+ @all_files
+ end
+
+ ##
+ # Finds the class with +name+ in all discovered classes
+
+ def self.find_class_named(name)
+ classes_hash[name]
+ end
+
+ ##
+ # Finds the class with +name+ starting in namespace +from+
+
+ def self.find_class_named_from name, from
+ from = find_class_named from unless RDoc::Context === from
+
+ until RDoc::TopLevel === from do
+ return nil unless from
+
+ klass = from.find_class_named name
+ return klass if klass
+
+ from = from.parent
+ end
+
+ find_class_named name
+ end
+
+ ##
+ # Finds the class or module with +name+
+
+ def self.find_class_or_module(name)
+ name =~ /^::/
+ name = $' || name
+
+ RDoc::TopLevel.classes_hash[name] || RDoc::TopLevel.modules_hash[name]
+ end
+
+ ##
+ # Finds the file with +name+ in all discovered files
+
+ def self.find_file_named(name)
+ @all_files[name]
+ end
+
+ ##
+ # Finds the module with +name+ in all discovered modules
+
+ def self.find_module_named(name)
+ modules_hash[name]
+ end
+
+ ##
+ # Returns all modules discovered by RDoc
+
+ def self.modules
+ modules_hash.values
+ end
+
+ ##
+ # Hash of all modules known to RDoc
+
+ def self.modules_hash
+ @all_modules
+ end
+
+ ##
+ # Empties RDoc of stored class, module and file information
+
+ def self.reset
+ @all_classes = {}
+ @all_modules = {}
+ @all_files = {}
+ end
+
+ reset
+
+ ##
+ # Creates a new TopLevel for +file_name+
+
+ def initialize(file_name)
+ super()
+ @name = nil
+ @relative_name = file_name
+ @absolute_name = file_name
+ @file_stat = File.stat(file_name) rescue nil # HACK for testing
+ @diagram = nil
+ @parser = nil
+
+ RDoc::TopLevel.files_hash[file_name] = self
+ end
+
+ ##
+ # Adds +method+ to Object instead of RDoc::TopLevel
+
+ def add_method(method)
+ object = self.class.find_class_named 'Object'
+ object = add_class RDoc::NormalClass, 'Object' unless object
+
+ object.add_method method
+ end
+
+ ##
+ # Base name of this file
+
+ def base_name
+ File.basename @absolute_name
+ end
+
+ ##
+ # See RDoc::TopLevel.find_class_or_module
+
+ def find_class_or_module name
+ RDoc::TopLevel.find_class_or_module name
+ end
+
+ ##
+ # Finds a class or module named +symbol+
+
+ def find_local_symbol(symbol)
+ find_class_or_module(symbol) || super
+ end
+
+ ##
+ # Finds a module or class with +name+
+
+ def find_module_named(name)
+ find_class_or_module(name) || find_enclosing_module_named(name)
+ end
+
+ ##
+ # The name of this file
+
+ def full_name
+ @relative_name
+ end
+
+ ##
+ # URL for this with a +prefix+
+
+ def http_url(prefix)
+ path = [prefix, @relative_name.tr('.', '_')]
+
+ File.join(*path.compact) + '.html'
+ end
+
+ def inspect # :nodoc:
+ "#<%s:0x%x %p modules: %p classes: %p>" % [
+ self.class, object_id,
+ base_name,
+ @modules.map { |n,m| m },
+ @classes.map { |n,c| c }
+ ]
+ end
+
+ ##
+ # Date this file was last modified, if known
+
+ def last_modified
+ @file_stat ? file_stat.mtime.to_s : 'Unknown'
+ end
+
+ ##
+ # Base name of this file
+
+ alias name base_name
+
+ ##
+ # Path to this file
+
+ def path
+ http_url RDoc::RDoc.current.generator.file_dir
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 2, "[#{self.class}: ", "]" do
+ q.text "base name: #{base_name.inspect}"
+ q.breakable
+
+ items = @modules.map { |n,m| m }
+ items.push(*@modules.map { |n,c| c })
+ q.seplist items do |mod| q.pp mod end
+ end
+ end
+
+end
+