1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@5073 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
dave 2003-12-01 07:12:49 +00:00
parent c1c55573bd
commit 87762adcb0
36 changed files with 12367 additions and 0 deletions

View file

@ -1,3 +1,7 @@
Mon Dec 1 16:10:52 2003 Dave Thomas <dave@pragprog.com>
* lib/rdoc/rdoc.rb: (etc) initial merge into main tree.
Mon Dec 1 14:17:49 2003 Minero Aoki <aamine@loveruby.net>
* lib/fileutils.rb (fu_each_src_dest0): call #to_str to allow

67
bin/rdoc Normal file
View file

@ -0,0 +1,67 @@
#!/usr/bin/env ruby
#
# RDoc: Documentation tool for source code
# (see lib/rdoc/rdoc.rb for more information)
#
# Copyright (c) 2003 Dave Thomas
# Released under the same terms as Ruby
#
# $Revision$
## Transitional Hack ####
#
# RDoc was initially distributed independently, and installed
# itself into <prefix>/lib/ruby/site_ruby/<ver>/rdoc...
#
# Now that RDoc is part of the distribution, it's installed into
# <prefix>/lib/ruby/<ver>, which unfortunately appears later in the
# search path. This means that if you have previously installed RDoc,
# and then install from ruby-lang, you'll pick up the old one by
# default. This hack checks for the condition, and readjusts the
# search path if necessary.
def adjust_for_existing_rdoc(path)
$stderr.puts %{
It seems as if you have a previously-installed RDoc in
the directory #{path}.
Because this is now out-of-date, you might want to consider
removing the directories:
#{File.join(path, "rdoc")}
and
#{File.join(path, "markup")}
}
# Move all the site_ruby directories to the end
p $:
$:.replace($:.partition {|path| /site_ruby/ !~ path}.flatten)
p $:
end
$:.each do |path|
if /site_ruby/ =~ path
rdoc_path = File.join(path, 'rdoc', 'rdoc.rb')
if File.exists?(rdoc_path)
adjust_for_existing_rdoc(path)
break
end
end
end
## End of Transitional Hack ##
require 'rdoc/rdoc'
begin
r = RDoc::RDoc.new
r.document(ARGV)
rescue RDoc::RDocError => e
$stderr.puts e.message
exit(1)
end

445
lib/rdoc/README Normal file
View file

@ -0,0 +1,445 @@
= RDOC - Ruby Documentation System
This package contains Rdoc and SimpleMarkup. 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. (Currently, HTML is the only
supported format. 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.
This library contains two packages, rdoc itself and a text markup
library, 'markup'.
== 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/parsers/parse_c.rb.
* For information on the various markups available in comment
blocks, see markup/simple_markup.rb.
* If you want to drive Rdoc programatically, see RDoc::RDoc.
* If you want to use the library to format text blocks into HTML,
have a look at SM::SimpleMarkup.
* If you want to try writing your own HTML output template, see
RDoc::Page.
== 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/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 <tt>.rb</tt> and <tt>.rbw</tt> are assumed to be Ruby
source. Files ending <tt>.c</tt> are parsed as C files. All other
files are assumed to contain just SimpleMarkup-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.
== 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.
----
= Usage
RDoc is invoked from the command line using:
% rdoc <options> [name...]
Files are parsed, and the information they contain collected, before
any output is produced. This allows cross references between all files
to be resolved. If a name is a directory, it is traversed. If no
names are specified, all Ruby files in the current directory (and
subdirectories) are processed.
Options are:
[<tt>--accessor</tt> <i>name[,name...]</i>]
specifies the name(s) of additional methods that should be treated
as if they were <tt>attr_</tt><i>xxx</i> methods. Specifying
"--accessor db_opt" means lines such as
db_opt :name, :age
will get parsed and displayed in the documentation. Each name may have an
optional "=flagtext" appended, in which case the given flagtext will appear
where (for example) the 'rw' appears for attr_accessor.
[<tt>--all</tt>]
include protected and private methods in the output (by default
only public methods are included)
[<tt>--charset</tt> _charset_]
Set the character set for the generated HTML.
[<tt>--diagram</tt>]
include diagrams showing modules and classes. This is currently
an experimental feature, and may not be supported by all output
templates. You need dot V1.8.6 or later to use the --diagram
option correctly (http://www.research.att.com/sw/tools/graphviz/).
[<tt>--exclude</tt> <i>pattern</i>]
exclude files and directories matching this pattern from processing
[<tt>--extension</tt> <i>new=old</i>]
treat files ending <i>.new</i> as if they ended
<i>.old</i>. Saying '--extension cgi=rb' causes RDoc to treat .cgi
files as Ruby source.
[<tt>fileboxes</tt>]
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 sharing them. Silently discarded if
--diagram is not given Experimental.
[<tt>--fmt</tt> _fmt_]
generate output in a particular format.
[<tt>--help</tt>]
generate a usage summary.
[<tt>--help-output</tt>]
explain the various output options.
[<tt>--image-format</tt> <i>gif/png/jpg/jpeg</i>]
sets output image format for diagrams. Can be png, gif, jpeg,
jpg. If this option is omitted, png is used. Requires --diagram.
[<tt>--include</tt> <i>dir,...</i>]
specify one or more directories to be searched when satisfying
:+include+: directives. Multiple <tt>--include</tt> options may be
given. The directory containing the file currently being processed
is always searched.
[<tt>--inline-source</tt>]
By default, the source code of methods is shown in a popup. With
this option, it's displayed inline.
[<tt>line-numbers</tt>]
include line numbers in the source code
[<tt>--main</tt> _name_]
set the class, module, or file to appear on the index page
[<tt>--one-file</tt>]
place all the output into a single file
[<tt>--op</tt> _dir_]
set the output directory to _dir_ (the default is the directory
"doc")
[<tt>--op-name</tt> _name_]
set the name of the output. Has no effect for HTML.
"doc")
[<tt>--opname</tt> _name_]
set the output name (has no effect for HTML).
[<tt>--promiscuous</tt>]
If a module or class is defined in more than one source file, and
you click on a particular file's name in the top navigation pane,
RDoc will normally only show you the inner classes and modules of
that class that are defined in the particular file. Using this
option makes it show all classes and modules defined in the class,
regardless of the file they were defined in.
[<tt>--quiet</tt>]
do not display progress messages
[<tt>--show-hash</tt>]
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
[<tt>--style</tt> <i>stylesheet url</i>]
specifies the URL of an external stylesheet to use (rather than
generating one of our own)
[<tt>tab-width</tt> _n_]
set the width of tab characters (default 8)
[<tt>--template</tt> <i>name</i>]
specify an alternate template to use when generating output (the
default is 'standard'). This template should be in a directory
accessible via $: as rdoc/generators/xxxx_template, where 'xxxx'
depends on the output formatter.
[<tt>--version</tt>]
display RDoc's version
[<tt>--webcvs</tt> _url_]
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.
= Example
A typical small Ruby program commented using RDoc might be as follows. You
can see the formatted result in EXAMPLE.rb and Anagram.
:include: EXAMPLE.rb
= Markup
Comment blocks can be written fairly naturally.
Paragraphs are lines that share the left margin. Text indented past
this margin are formatted verbatim.
1. Lists are typed as indented paragraphs with:
* a '*' or '-' (for bullet lists)
* a digit followed by a period for
numbered lists
* an upper or lower case letter followed
by a period for alpha lists.
For example, the input that produced the above paragraph looked like
1. Lists are typed as indented
paragraphs with:
* a '*' or '-' (for bullet lists)
* a digit followed by a period for
numbered lists
* an upper or lower case letter followed
by a period for alpha lists.
2. Labeled lists (sometimes called description
lists) are typed using square brackets for the label.
[cat] small domestic animal
[+cat+] command to copy standard input
3. Labeled lists may also be produced by putting a double colon
after the label. This sets the result in tabular form, so the
descriptions all line up. This was used to create the 'author'
block at the bottom of this description.
cat:: small domestic animal
+cat+:: command to copy standard input
For both kinds of labeled lists, if the body text starts on the same
line as the label, then the start of that text determines the block
indent for the rest of the body. The text may also start on the line
following the label, indented from the start of the label. This is
often preferable if the label is long. Both the following are
valid labeled list entries:
<tt>--output</tt> <i>name [, name]</i>::
specify the name of one or more output files. If multiple
files are present, the first is used as the index.
<tt>--quiet:</tt>:: do not output the names, sizes, byte counts,
index areas, or bit ratios of units as
they are processed.
4. Headings are entered using equals signs
= Level One Heading
== Level Two Heading
and so on
5. Rules (horizontal lines) are entered using three or
more hyphens.
6. Non-verbatim text can be marked up:
_italic_:: \_word_ or \<em>text</em>
*bold*:: \*word* or \<b>text</b>
+typewriter+:: \+word+ or \<tt>text</tt>
The first form only works around 'words', where a word is a
sequence of upper and lower case letters and underscores. Putting a
backslash before inline markup stops it being interpreted, which is
how I created the table above:
_italic_:: \_word_ or \<em>text</em>
*bold*:: \*word* or \<b>text</b>
+typewriter+:: \+word+ or \<tt>text</tt>
7. 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.
8. Hyperlinks to the web starting http:, mailto:, ftp:, or www. are
recognized. An HTTP url that references an external image file is
converted into an inline <IMG..>. Hyperlinks starting 'link:' are
assumed to refer to local files whose path is relative to the --op
directory.
Hyperlinks can also be of the form <tt>label</tt>[url], in which
case the label is used in the displayed text, and <tt>url</tt> is
used as the target.
9. 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| ... }
10. ':yields:' is an example of a documentation modifier. These appear
immediately after the start of the document element they are modifying.
Other modifiers include
[<tt>:nodoc:</tt><i>[all]</i>]
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 SM #:nodoc:
class Input
end
end
module Markup #:nodoc: all
class Output
end
end
In the above code, only class <tt>SM::Input</tt> will be
documented.
[<tt>:doc:</tt>]
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.
[<tt>:notnew:</tt>]
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.
11. RDoc stops processing comments if it finds a comment
line containing '<tt>#--</tt>'. 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 '<tt>#++</tt>'.
# 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)
...
12. Comment blocks can contain other directives:
[<tt>:include:</tt><i>filename</i>]
include the contents of the named file at this point. The
file will be searched for in the directories listed by
the <tt>--include</tt> 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.
[<tt>:title:</tt><i>text</i>]
Sets the title for the document. Equivalent to the --title command
line parameter. (The command line parameter overrides any :title:
directive in the source).
[<tt>:enddoc:</tt>]
Document nothing further at the current level.
[<tt>:main:</tt><i>name</i>]
Equivalent to the --main command line parameter.
[<tt>:stopdoc: / :startdoc:</tt>]
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
<tt>:stopdoc:</tt> before the first, and a
<tt>:startdoc:</tt> after the last. If you don't specifiy a
<tt>:startdoc:</tt> by the end of the container, disables
documentation for the entire class or module.
---
See also markup/simple_markup.rb.
= Other stuff
Author:: Dave Thomas <dave@pragmaticprogrammer.com>
Requires:: Ruby 1.8.1 or later
License:: Copyright (c) 2001-2003 Dave Thomas.
Released under the same license as Ruby.
== 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.

653
lib/rdoc/code_objects.rb Normal file
View file

@ -0,0 +1,653 @@
# We represent the various high-level code constructs that appear
# in Ruby programs: classes, modules, methods, and so on.
require 'rdoc/tokenstream'
module RDoc
# We contain the common stuff for contexts (which are containers)
# and other elements (methods, attributes and so on)
#
class CodeObject
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
# do we document ourselves?
attr_reader :document_self
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
# Default callbacks to nothing, but this is overridden for classes
# and modules
def remove_classes_and_modules
end
def remove_methods_etc
end
def initialize
@document_self = true
@document_children = true
@force_documentation = false
@done_documenting = false
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 CodeObject.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 :name, :method_list, :attributes, :aliases, :constants
attr_reader :requires, :includes, :in_files, :visibility
def initialize
super()
@in_files = []
@name ||= "unknown"
@comment ||= ""
@parent = nil
@visibility = :public
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
# Change the default visibility for new methods
def ongoing_visibility=(vis)
@visibility = vis
end
# Given an array +methods+ of method names, set the
# visibility of the corresponding AnyMethod object
def set_visibility_for(methods, vis, singleton=false)
@method_list.each_with_index do |m,i|
if methods.include?(m.name) && m.singleton == singleton
m.visibility = vis
end
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)
add_class_or_module(@classes, class_type, name, superclass)
end
def add_module(class_type, name)
add_class_or_module(@modules, class_type, name, nil)
end
def add_method(a_method)
puts "Adding #@visibility method #{a_method.name} to #@name" if $DEBUG
a_method.visibility = @visibility
add_to(@method_list, a_method)
end
def add_attribute(an_attribute)
add_to(@attributes, an_attribute)
end
def add_alias(an_alias)
meth = find_method_named(an_alias.old_name)
if 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)
else
add_to(@aliases, an_alias)
end
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 self.kind_of? TopLevel
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
puts "Reusing class/module #{name}" if $DEBUG
else
cls = class_type.new(name, superclass)
puts "Adding class/module #{name} to #@name" if $DEBUG
collection[name] = cls
cls.parent = self
end
cls
end
def add_to(array, thing)
array << thing if @document_self
thing.parent = self
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 = []
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)
return self if self.name == name
res = @modules[name] || @classes[name]
return res if res
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 the given 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 /^::(.*)/
result = toplevel.find_symbol(symbol)
when /::/
modules = symbol.split(/::/)
unless modules.empty?
module_name = modules.shift
result = find_module_named(module_name)
if result
modules.each do |module_name|
result = result.find_module_named(module_name)
end
end
end
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
if result && method
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)
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 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
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 = {}
def TopLevel::reset
@@all_classes = {}
@@all_modules = {}
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
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
puts "Reusing class/module #{name}" if $DEBUG
else
if class_type == NormalModule
all = @@all_modules
else
all = @@all_classes
end
cls = all[name]
if !cls
cls = class_type.new(name, superclass)
all[name] = cls
end
puts "Adding class/module #{name} to #@name" if $DEBUG
collection[name] = cls
cls.parent = self
end
cls
end
def TopLevel.all_classes_and_modules
@@all_classes.values + @@all_modules.values
end
def TopLevel.find_class_named(name)
@@all_classes.each_value do |c|
res = c.find_class_named(name)
return res if res
end
nil
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
end
# ClassModule is the base class for objects representing either a
# class or a module.
class ClassModule < Context
attr_reader :superclass
attr_accessor :diagram
def initialize(name, superclass = nil)
@name = name
@diagram = nil
@superclass = superclass
@comment = ""
super()
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
# Return +true+ if this object represents a module
def is_module?
false
end
# to_s is simply for debugging
def to_s
res = self.class.name + ": " + @name
res << @comment.to_s
res << super
res
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
end
# Anonymous classes
class AnonClass < ClassModule
end
# Normal classes
class NormalClass < ClassModule
end
# Singleton classes
class SingleClass < ClassModule
end
# Module
class NormalModule < ClassModule
def is_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 :aliases # list of other names for this method
attr_accessor :is_alias_for # or a method we're aliasing
attr_overridable :params, :param, :parameters, :parameter
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 = ""
end
def <=>(other)
@name <=> other.name
end
def to_s
res = self.class.name + ": " + @name + " (" + @text + ")\n"
res << @comment.to_s
res
end
def param_seq
p = params.gsub(/\s*\#.*/, '')
p = p.tr("\n", " ").squeeze(" ")
p = "(" + p + ")" unless p[0] == ?(
if (block = block_params)
block.gsub!(/\s*\#.*/, '')
block = block.tr("\n", " ").squeeze(" ")
if block[0] == ?(
block.sub!(/^\(/, '').sub!(/\)/, '')
end
p << " {|#{block}| ...}"
end
p
end
def add_alias(method)
@aliases << method
end
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 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
def initialize(text, name, rw, comment)
super()
@text = text
@name = name
@rw = rw
self.comment = comment
end
def to_s
"attr: #{self.name} #{self.rw}\n#{self.comment}"
end
def <=>(other)
self.name <=> other.name
end
end
# a required file
class Require < CodeObject
attr_accessor :name
def initialize(name, comment)
super()
@name = name.gsub(/'|"/, "") #'
self.comment = comment
end
end
# an included module
class Include < CodeObject
attr_accessor :name
def initialize(name, comment)
super()
@name = name
self.comment = comment
end
end
end

333
lib/rdoc/diagram.rb Normal file
View file

@ -0,0 +1,333 @@
# 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 "dot/dot"
require 'rdoc/options'
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
File.makedirs(DOT_PATH)
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::DOTDigraph.new('name' => 'TopLevel',
'label' => i.file_absolute_name,
'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::DOTNode.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, i.name)
# 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::DOTDigraph.new('name' => 'TopLevel',
'label' => i.full_name,
'fontname' => FONT,
'fontsize' => '8',
'bgcolor' => 'lightcyan1',
'compound' => 'true')
graph << DOT::DOTNode.new('name' => 'node',
'fontname' => FONT,
'color' => 'black',
'fontsize' => 8)
draw_module(mod, graph, true)
mod.diagram = convert_to_png("m_#{file_count}_#{count}",
graph,
"Module: #{mod.name}")
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::DOTSubgraph.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 |m|
m_full_name = find_full_name(m.name, mod)
if @local_names.include?(m_full_name)
@global_graph << DOT::DOTEdge.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::DOTNode.new('name' => "#{m_full_name.gsub( /:/,'_' )}",
'shape' => 'box',
'label' => "#{m_full_name}",
'URL' => %{"#{url}"})
@global_names << m_full_name
end
@global_graph << DOT::DOTEdge.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.instance.fileboxes
files = {}
# create dummy node (needed if empty and for module includes)
if container.full_name
graph << DOT::DOTNode.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::DOTSubgraph.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::DOTNode.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::DOTEdge.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::DOTNode.new('name' => "#{m_full_name.gsub( /:/,'_' )}",
'shape' => 'box',
'label' => "#{m_full_name}",
'URL' => %{"#{url}"})
@global_names << m_full_name
end
@global_graph << DOT::DOTEdge.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::DOTNode.new(
'name' => "#{sclass_full_name.gsub( /:/, '_' )}",
'label' => sclass_full_name,
'URL' => %{"#{url}"})
@global_names << sclass_full_name
end
@global_graph << DOT::DOTEdge.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, name)
op_type = Options.instance.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 << graph.to_s << "\n"
end
system "dot -T#{op_type} #{src} -o #{dot}"
# Now construct the imagemap wrapper around
# that png
return wrap_in_image_map(src, dot, name)
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, name)
res = %{<map name="map">\n}
dot_map = `dot -Tismap #{src}`
dot_map.each do |area|
unless area =~ /^rectangle \((\d+),(\d+)\) \((\d+),(\d+)\) ([\/\w.]+)\s*(.*)/
$stderr.puts "Unexpected output from dot:\n#{area}"
return nil
end
blx = $1; bly = $2
trx = $3; try = $4
url = $5; area_name = $6
res << %{ <area shape="RECT" coords="#{blx},#{try},#{trx},#{bly}" }
res << %{ href="#{url}" alt="#{area_name}">\n}
end
res << "</map>\n"
# map_file = src.sub(/.dot/, '.map')
# system("dot -Timap #{src} -o #{map_file}")
res << %{<img src="#{dot}" usemap="#map" border=0 alt="#{name}">}
return res
end
end
end

View file

@ -0,0 +1,112 @@
require 'rdoc/generators/html_generator'
module Generators
class CHMGenerator < HTMLGenerator
HHC_PATH = "c:\\Program Files\\HTML Help Workshop\\hhc.exe"
# Standard generator factory
def CHMGenerator.for(options)
CHMGenerator.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"
exit 99
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 = TemplatePage.new(RDoc::Page::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 = TemplatePage.new(RDoc::Page::CONTENTS)
File.open("contents.hhc", "w") do |f|
template.write_html_on(f, values)
end
values = { "index" => index }
template = TemplatePage.new(RDoc::Page::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
end

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,86 @@
module RDoc
module Page
require "rdoc/generators/template/html/html"
# This is a nasty little hack, but hhc doesn't support the <?xml
# tag, so...
BODY.sub!(/<\?xml.*\?>/, '')
HPP_FILE = %{
[OPTIONS]
Auto Index = Yes
Compatibility=1.1 or later
Compiled file=%opname%.chm
Contents file=contents.hhc
Full-text search=Yes
Index file=index.hhk
Language=0x409 English(United States)
Title=%title%
[FILES]
START:all_html_files
%html_file_name%
END:all_html_files
}
CONTENTS = %{
<!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>
START:contents
<LI> <OBJECT type="text/sitemap">
<param name="Name" value="%c_name%">
<param name="Local" value="%ref%">
</OBJECT>
IF:methods
<ul>
START:methods
<LI> <OBJECT type="text/sitemap">
<param name="Name" value="%name%">
<param name="Local" value="%aref%">
</OBJECT>
END:methods
</ul>
ENDIF:methods
</LI>
END:contents
</UL>
</BODY></HTML>
}
CHM_INDEX = %{
<!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>
START:index
<LI> <OBJECT type="text/sitemap">
<param name="Name" value="%name%">
<param name="Local" value="%aref%">
</OBJECT>
END:index
</UL>
</BODY></HTML>
}
end
end

View file

@ -0,0 +1,631 @@
#
# = CSS2 RDoc HTML template
#
# This is a template for RDoc that uses XHTML 1.0 Transitional 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.
#
# == 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
module Page
FONTS = "Verdana,Arial,Helvetica,sans-serif"
STYLE = %{
body {
margin: 0;
padding: 0;
}
h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; }
h1 { font-size: 120%; }
h2,h3,h4 { margin-top: 1em; }
a { background: #eef; color: #039; text-decoration: none; }
a:hover { background: #039; color: #eef; }
/* Override the base stylesheet's Anchor inside a table cell */
td > a {
background: transparent;
color: #039;
text-decoration: none;
}
/* === Structural elements =================================== */
div#index {
margin: 0;
padding: 0;
font-size: 0.9em;
}
div#index a {
margin-left: 0.7em;
}
div#classHeader {
width: auto;
background: #039;
color: white;
padding: 0.5em 1.5em 0.5em 1.5em;
margin: 0;
border-bottom: 3px solid #006;
}
div#classHeader a {
background: inherit;
color: white;
}
div#classHeader td {
background: inherit;
color: white;
}
div#fileHeader {
width: auto;
background: #039;
color: white;
padding: 0.5em 1.5em 0.5em 1.5em;
margin: 0;
border-bottom: 3px solid #006;
}
div#fileHeader a {
background: inherit;
color: white;
}
div#fileHeader td {
background: inherit;
color: white;
}
div#bodyContent {
padding: 0 1.5em 0 1.5em;
}
div#description {
padding: 0.5em 1.5em;
background: #efefef;
border: 1px dotted #999;
}
div#description h1,h2,h3,h4,h5,h6 {
color: black;
background: transparent;
}
div#validator-badges {
text-align: center;
}
div#validator-badges img { border: 0; }
div#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 {
background: #eee;
color: #333;
padding: 3px;
border: 1px solid #999;
}
.top-aligned-row { vertical-align: vertical-align: top }
/* --- Context section classes ----------------------- */
.context-row { }
.context-item-name { font-family: monospace; font-weight: bold; color: black; }
.context-item-value { font-size: x-small; color: #448; }
.context-item-desc { background: #efefef; }
/* --- Method classes -------------------------- */
.method-detail {
background: #EFEFEF;
padding: 0;
margin-top: 0.5em;
margin-bottom: 0.5em;
border: 1px dotted #DDD;
}
.method-heading {
color: black;
background: #AAA;
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 -------------------- */
a.source-toggle { font-size: 90%; }
div.method-source-code {
background: #262626;
color: #ffdead;
margin: 1em;
padding: 0.5em;
border: 1px dashed #999;
overflow: hidden;
}
div.method-source-code pre { color: #ffdead; overflow: hidden; }
/* --- Ruby keyword styles --------------------- */
/* (requires a hacked html_generator.rb to add more class-types) */
.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; }
}
#####################################################################
### H E A D E R T E M P L A T E
#####################################################################
XHTML_PREAMBLE = %{<?xml version="1.0" encoding="%charset%"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"DTD/xhtml1-transitional.dtd">
}
HEADER = XHTML_PREAMBLE + %{
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>%title%</title>
<meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<link rel="stylesheet" href="%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>
}
#####################################################################
### C O N T E X T C O N T E N T T E M P L A T E
#####################################################################
CONTEXT_CONTENT = %{
<div id="contextContent">
IF:diagram
<div id="diagram">
%diagram%
</div>
ENDIF:diagram
IF:description
<div id="description">
%description%
</div>
ENDIF:description
IF:requires
<div id="requires-list">
<h2 class="section-bar">Required files</h2>
<div class="name-list">
START:requires
HREF:aref:name:&nbsp;&nbsp;
END:requires
</div>
</div>
ENDIF:requires
IF:methods
<div id="method-list">
<h2 class="section-bar">Methods</h2>
<div class="name-list">
START:methods
HREF:aref:name:&nbsp;&nbsp;
END:methods
</div>
</div>
ENDIF:methods
IF:constants
<div id="constants-list">
<h2 class="section-bar">Constants</h2>
<div class="name-list">
<table summary="Constants">
START:constants
<tr class="top-aligned-row context-row">
<td class="context-item-name">%name%</td>
<td>=</td>
<td class="context-item-value">%value%</td>
</tr>
IF:desc
<tr class="top-aligned-row context-row">
<td>&nbsp;</td>
<td colspan="2" class="context-item-desc">%desc%</td>
</tr>
ENDIF:desc
END:constants
</table>
</div>
</div>
ENDIF:constants
IF:aliases
<div id="aliases-list">
<h2 class="section-bar">External Aliases</h2>
<div class="name-list">
START:aliases
%old_name% -> %new_name% <br />
END:aliases
</div>
</div>
ENDIF:aliases
IF:attributes
<div id="attribute-list">
<h2 class="section-bar">Attributes</h2>
<div class="name-list">
<table>
START:attributes
<tr class="top-aligned-row context-row">
<td class="context-item-name">%name%</td>
<td class="context-item-value">&nbsp;[%rw%]&nbsp;</td>
<td class="context-item-desc">%a_desc%</td>
</tr>
END:attributes
</table>
</div>
</div>
ENDIF:attributes
IF:classlist
<div id="class-list">
<h2 class="section-bar">Classes and Modules</h2>
%classlist%
</div>
ENDIF:classlist
</div>
}
#####################################################################
### F O O T E R T E M P L A T E
#####################################################################
FOOTER = %{
<div id="validator-badges">
<p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
</div>
</body>
</html>
}
#####################################################################
### F I L E P A G E H E A D E R T E M P L A T E
#####################################################################
FILE_PAGE = %{
<div id="fileHeader">
<h1>%short_name%</h1>
<table class="header-table">
<tr class="top-aligned-row">
<td><strong>Path:</strong></td>
<td>%full_path%
IF:cvsurl
&nbsp;(<a href="%cvsurl%">CVS</a>)
ENDIF:cvsurl
</td>
</tr>
<tr class="top-aligned-row">
<td><strong>Last Update:</strong></td>
<td>%dtm_modified%</td>
</tr>
</table>
</div>
}
#####################################################################
### 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 = %{
<div id="classHeader">
<h1>%full_name% <sup class="type-note">(%classmod%)</sup></h1>
<table class="header-table">
<tr class="top-aligned-row">
<td><strong>In:</strong></td>
<td>
START:infiles
IF:full_path_url
<a href="%full_path_url%">
ENDIF:full_path_url
%full_path%
IF:full_path_url
</a>
ENDIF:full_path_url
IF:cvsurl
&nbsp;(<a href="%cvsurl%">CVS</a>)
ENDIF:cvsurl
<br />
END:infiles
</td>
</tr>
IF:parent
<tr class="top-aligned-row">
<td><strong>Parent:</strong></td>
<td>
IF:par_url
<a href="%par_url%">
ENDIF:par_url
%parent%
IF:par_url
</a>
ENDIF:par_url
</td>
</tr>
ENDIF:parent
</table>
</div>
}
#####################################################################
### M E T H O D L I S T T E M P L A T E
#####################################################################
METHOD_LIST = %{
<!-- if includes -->
IF:includes
<div id="includes">
<h2 class="section-bar">Included Modules</h2>
<div id="includes-list">
START:includes
<span class="include-name">HREF:aref:name:</span>
END:includes
</div>
</div>
ENDIF:includes
<!-- if method_list -->
IF:method_list
<div id="methods">
START:method_list
IF:methods
<h2 class="section-bar">%type% %category% methods</h2>
START:methods
<!-- %name%%params% -->
<div id="method-%aref%" class="method-detail">
<a name="%aref%"></a>
<div class="method-heading">
IF:codeurl
<a href="%codeurl%" target="Code" class="method-signature"
onclick="popupCode('%codeurl%');return false;">
ENDIF:codeurl
IF:sourcecode
<a href="#%aref%" class="method-signature">
ENDIF:sourcecode
<span class="method-name">%name%</span><span class="method-args">%params%</span>
IF:codeurl
</a>
ENDIF:codeurl
IF:sourcecode
</a>
ENDIF:sourcecode
</div>
<div class="method-description">
IF:m_desc
%m_desc%
ENDIF:m_desc
IF:sourcecode
<p><a class="source-toggle" href="#"
onclick="toggleCode('%aref%-source');return false;">[Source]</a></p>
<div class="method-source-code" id="%aref%-source">
<pre>
%sourcecode%
</pre>
</div>
ENDIF:sourcecode
</div>
</div>
END:methods
ENDIF:methods
END:method_list
</div>
ENDIF:method_list
}
#####################################################################
### B O D Y T E M P L A T E
#####################################################################
BODY = HEADER + %{
!INCLUDE! <!-- banner header -->
<div id="bodyContent">
} + CONTEXT_CONTENT + 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_PREAMBLE + %{
<!--
%title%
-->
<html>
<head>
<title>%title%</title>
<meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
<link rel="stylesheet" href="http://www.FaerieMUD.org/stylesheets/rdoc.css" type="text/css" />
</head>
<body>
<pre>%code%</pre>
</body>
</html>
}
#####################################################################
### I N D E X F I L E T E M P L A T E S
#####################################################################
FR_INDEX_BODY = %{
!INCLUDE!
}
FILE_INDEX = XHTML_PREAMBLE + %{
<!--
%list_title%
-->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>%list_title%</title>
<meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
<link rel="stylesheet" href="%style_url%" type="text/css" />
<base target="docwin" />
</head>
<body>
<div id="index">
<h1 class="section-bar">%list_title%</h1>
<div id="index-entries">
START:entries
<a href="%href%">%name%</a><br />
END:entries
</div>
</div>
</body>
</html>
}
CLASS_INDEX = FILE_INDEX
METHOD_INDEX = FILE_INDEX
INDEX = %{<?xml version="1.0" encoding="%charset%"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
"DTD/xhtml1-frameset.dtd">
<!--
%title%
-->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>%title%</title>
<meta http-equiv="Content-Type" content="text/html; charset=%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="%initial_page%" name="docwin" />
</frameset>
</html>
}
end # module Page
end # class RDoc

View file

@ -0,0 +1,418 @@
module RDoc
module Page
FONTS = "Verdana, Arial, Helvetica, sans-serif"
STYLE = %{
body,p { font-family: Verdana, Arial, Helvetica, sans-serif;
color: #000040; background: #BBBBBB;
}
td { font-family: Verdana, Arial, Helvetica, sans-serif;
color: #000040;
}
.attr-rw { font-size: small; color: #444488 }
.title-row {color: #eeeeff;
background: #BBBBDD;
}
.big-title-font { color: white;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: large;
height: 50px}
.small-title-font { color: purple;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: small; }
.aqua { color: purple }
.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;
}
.column-title {
font-size: medium;
font-weight: bold;
text_decoration: none;
padding: 3px 3px 3px 20px;
color: #3333CC;
}
.variable-name {
font-family: monospace;
font-size: medium;
text_decoration: none;
padding: 3px 3px 3px 20px;
color: #0000AA;
}
.row-name {
font-size: medium;
font-weight: medium;
font-family: monospace;
text_decoration: none;
padding: 3px 3px 3px 20px;
}
.paramsig {
font-size: small;
}
.srcbut { float: right }
}
############################################################################
BODY = %{
<html><head>
<title>%title%</title>
<meta http-equiv="Content-Type" content="text/html; charset=%charset%">
<link rel=StyleSheet href="%style_url%" type="text/css" media=screen>
<script type="text/javascript" language="JavaScript">
<!--
function popCode(url) {
parent.frames.source.location = url
}
//-->
</script>
</head>
<body bgcolor="#BBBBBB">
!INCLUDE! <!-- banner header -->
IF:diagram
<table width="100%"><tr><td align="center">
%diagram%
</td></tr></table>
ENDIF:diagram
IF:description
<div class="description">%description%</div>
ENDIF:description
IF:requires
<table cellpadding=5 width="100%">
<tr><td class="tablesubtitle">Required files</td></tr>
</table><br>
<div class="name-list">
START:requires
HREF:aref:name:
END:requires
ENDIF:requires
</div>
IF:methods
<table cellpadding=5 width="100%">
<tr><td class="tablesubtitle">Subroutines and Functions</td></tr>
</table><br>
<div class="name-list">
START:methods
HREF:aref:name:,
END:methods
</div>
ENDIF:methods
IF:attributes
<table cellpadding=5 width="100%">
<tr><td class="tablesubtitle">Arguments</td></tr>
</table><br>
<table cellspacing=5>
START:attributes
<tr valign="top">
IF:rw
<td align="center" class="attr-rw">&nbsp;[%rw%]&nbsp;</td>
ENDIF:rw
IFNOT:rw
<td></td>
ENDIF:rw
<td class="attr-name">%name%</td>
<td>%a_desc%</td>
</tr>
END:attributes
</table>
ENDIF:attributes
IF:classlist
<table cellpadding=5 width="100%">
<tr><td class="tablesubtitle">Modules</td></tr>
</table><br>
%classlist%<br>
ENDIF:classlist
!INCLUDE! <!-- method descriptions -->
</body>
</html>
}
###############################################################################
FILE_PAGE = <<_FILE_PAGE_
<table width="100%">
<tr class="title-row">
<td><table width="100%"><tr>
<td class="big-title-font" colspan=2><font size=-3><B>File</B><BR></font>%short_name%</td>
<td align="right"><table cellspacing=0 cellpadding=2>
<tr>
<td class="small-title-font">Path:</td>
<td class="small-title-font">%full_path%
IF:cvsurl
&nbsp;(<a href="%cvsurl%">CVS</a>)
ENDIF:cvsurl
</td>
</tr>
<tr>
<td class="small-title-font">Modified:</td>
<td class="small-title-font">%dtm_modified%</td>
</tr>
</table>
</td></tr></table></td>
</tr>
</table><br>
_FILE_PAGE_
###################################################################
CLASS_PAGE = %{
<table width="100%" border=0 cellspacing=0>
<tr class="title-row">
<td class="big-title-font">
<font size=-3><B>%classmod%</B><BR></font>%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">
START:infiles
HREF:full_path_url:full_path:
IF:cvsurl
&nbsp;(<a href="%cvsurl%">CVS</a>)
ENDIF:cvsurl
END:infiles
</td>
</tr>
IF:parent
<tr>
<td class="small-title-font">Parent:</td>
<td class="small-title-font">
IF:par_url
<a href="%par_url%" class="cyan">
ENDIF:par_url
%parent%
IF:par_url
</a>
ENDIF:par_url
</td>
</tr>
ENDIF:parent
</table>
</td>
</tr>
</table><br>
}
###################################################################
METHOD_LIST = %{
IF:includes
<div class="tablesubsubtitle">Uses</div><br>
<div class="name-list">
START:includes
<span class="method-name">HREF:aref:name:</span>
END:includes
</div>
ENDIF:includes
IF:method_list
START:method_list
IF:methods
<table cellpadding=5 width="100%">
<tr><td class="tablesubtitle">%type% %category% methods</td></tr>
</table>
START:methods
<table width="100%" cellspacing = 0 cellpadding=5 border=0>
<tr><td class="methodtitle">
<a name="%aref%">
<b>%name%</b>%params%
IF:codeurl
<a href="%codeurl%" target="source" class="srclink">src</a>
ENDIF:codeurl
</a></td></tr>
</table>
IF:m_desc
<div class="description">
%m_desc%
</div>
ENDIF:m_desc
END:methods
ENDIF:methods
END:method_list
ENDIF:method_list
}
=begin
=end
########################## Source code ##########################
SRC_PAGE = %{
<html>
<head><title>%title%</title>
<meta http-equiv="Content-Type" content="text/html; charset=%charset%">
<style>
.kw { color: #3333FF; font-weight: bold }
.cmt { color: green; font-style: italic }
.str { color: #662222; font-style: italic }
.re { color: #662222; }
.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 }
</style>
</head>
<body bgcolor="#BBBBBB">
<pre>%code%</pre>
</body>
</html>
}
########################## Index ################################
FR_INDEX_BODY = %{
!INCLUDE!
}
FILE_INDEX = %{
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=%charset%">
<style>
<!--
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%;
}
-->
</style>
<base target="docwin">
</head>
<body>
<div class="banner">%list_title%</div>
START:entries
<a href="%href%">%name%</a><br>
END:entries
</body></html>
}
CLASS_INDEX = FILE_INDEX
METHOD_INDEX = FILE_INDEX
INDEX = %{
<html>
<head>
<title>%title%</title>
<meta http-equiv="Content-Type" content="text/html; charset=%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="Modules">
<frame src="fr_method_index.html" name="Subroutines and Functions">
</frameset>
<frameset rows="80%,20%">
<frame src="%initial_page%" name="docwin">
<frame src="blank.html" name="source">
</frameset>
<noframes>
<body bgcolor="#BBBBBB">
Click <a href="html/index.html">here</a> for a non-frames
version of this page.
</body>
</noframes>
</frameset>
</html>
}
# and a blank page to use as a target
BLANK = %{
<html><body bgcolor="#BBBBBB"></body></html>
}
def write_extra_pages
template = TemplatePage.new(BLANK)
File.open("blank.html", "w") { |f| template.write_html_on(f, {}) }
end
end
end

View file

@ -0,0 +1,762 @@
module RDoc
# This is how you define the HTML that RDoc generates. Simply create
# a file in rdoc/generators/html_templates that creates the
# module RDoc::Page and populate it as described below. Then invoke
# rdoc using the --template <name of your file> option, and
# your template will be used.
#
# The constants defining pages use a simple templating system:
#
# * The templating system is passed a hash. Keys in the hash correspond
# to tags on this page. The tag %abc% is looked up in the hash,
# and is replaced by the corresponding hash value.
#
# * Some tags are optional. You can detect this using IF/ENDIF
#
# IF: title
# The value of title is %title%
# ENDIF: title
#
# * Some entries in the hash have values that are arrays, where each
# entry in the array is itself a hash. These are used to generate
# lists using the START: construct. For example, given a hash
# containing
#
# { 'people' => [ { 'name' => 'Fred', 'age' => '12' },
# { 'name' => 'Mary', 'age' => '21' } ]
#
# You could generate a simple table using
#
# <table>
# START:people
# <tr><td>%name%<td>%age%</tr>
# END:people
# </table>
#
# These lists can be nested to an arbitrary depth
#
# * the construct HREF:url:name: generates <a href="%url%">%name%</a>
# if +url+ is defined in the hash, or %name% otherwise.
#
#
# Your file must contain the following constants
#
# [*FONTS*] a list of fonts to be used
# [*STYLE*] a CSS section (without the <style> or comments). This is
# used to generate a style.css file
#
# [*BODY*]
# The main body of all non-index RDoc pages. BODY will contain
# two !INCLUDE!s. The first is used to include a document-type
# specific header (FILE_PAGE or CLASS_PAGE). The second include
# is for the method list (METHOD_LIST). THe body is passed:
#
# %title%::
# the page's title
#
# %style_url%::
# the url of a style sheet for this page
#
# %diagram%::
# the optional URL of a diagram for this page
#
# %description%::
# a (potentially multi-paragraph) string containing the
# description for th file/class/module.
#
# %requires%::
# an optional list of %aref%/%name% pairs, one for each module
# required by this file.
#
# %methods%::
# an optional list of %aref%/%name%, one for each method
# documented on this page. This is intended to be an index.
#
# %attributes%::
# An optional list. For each attribute it contains:
# %name%:: the attribute name
# %rw%:: r/o, w/o, or r/w
# %a_desc%:: description of the attribute
#
# %classlist%::
# An optional string containing an already-formatted list of
# classes and modules documented in this file
#
# For FILE_PAGE entries, the body will be passed
#
# %short_name%::
# The name of the file
#
# %full_path%::
# The full path to the file
#
# %dtm_modified%::
# The date/time the file was last changed
#
# For class and module pages, the body will be passed
#
# %classmod%::
# The name of the class or module
#
# %files%::
# A list. For each file this class is defined in, it contains:
# %full_path_url%:: an (optional) URL of the RDoc page
# for this file
# %full_path%:: the name of the file
#
# %par_url%::
# The (optional) URL of the RDoc page documenting this class's
# parent class
#
# %parent%::
# The name of this class's parent.
#
# For both files and classes, the body is passed the following information
# on includes and methods:
#
# %includes%::
# Optional list of included modules. For each, it receives
# %aref%:: optional URL to RDoc page for the module
# %name%:: the name of the module
#
# %method_list%::
# Optional list of methods of a particular class and category.
#
# Each method list entry contains:
#
# %type%:: public/private/protected
# %category%:: instance/class
# %methods%:: a list of method descriptions
#
# Each method description contains:
#
# %aref%:: a target aref, used when referencing this method
# description. You should code this as <a name="%aref%">
# %codeurl%:: the optional URL to the page containing this method's
# source code.
# %name%:: the method's name
# %params%:: the method's parameters
# %m_desc%:: the (potentially multi-paragraph) description of
# this method.
#
# [*CLASS_PAGE*]
# Header for pages documenting classes and modules. See
# BODY above for the available parameters.
#
# [*FILE_PAGE*]
# Header for pages documenting files. See
# BODY above for the available parameters.
#
# [*METHOD_LIST*]
# Controls the display of the listing of methods. See BODY for
# parameters.
#
# [*INDEX*]
# The top-level index page. For a browser-like environment
# define a frame set that includes the file, class, and
# method indices. Passed
# %title%:: title of page
# %initial_page% :: url of initial page to display
#
# [*CLASS_INDEX*]
# Individual files for the three indexes. Passed:
# %index_url%:: URL of main index page
# %entries%:: List of
# %name%:: name of an index entry
# %href%:: url of corresponding page
# [*METHOD_INDEX*]
# Same as CLASS_INDEX for methods
#
# [*FILE_INDEX*]
# Same as CLASS_INDEX for methods
#
# [*FR_INDEX_BODY*]
# A wrapper around CLASS_INDEX, METHOD_INDEX, and FILE_INDEX.
# If those index strings contain the complete HTML for the
# output, then FR_INDEX_BODY can simply be !INCLUDE!
#
# [*SRC_PAGE*]
# Page used to display source code. Passed %title% and %code%,
# the latter being a multi-line string of code.
module Page
FONTS = "Verdana, Arial, Helvetica, sans-serif"
STYLE = %{
body,td,p { font-family: %fonts%;
color: #000040;
}
.attr-rw { font-size: x-small; color: #444488 }
.title-row { background: #0000aa;
color: #eeeeff;
}
.big-title-font { color: white;
font-family: %fonts%;
font-size: large;
height: 50px}
.small-title-font { color: aqua;
font-family: %fonts%;
font-size: xx-small; }
.aqua { color: aqua }
.method-name, attr-name {
font-family: monospace; font-weight: bold;
}
.tablesubtitle, .tablesubsubtitle {
width: 100%;
margin-top: 1ex;
margin-bottom: .5ex;
padding: 5px 0px 5px 20px;
font-size: large;
color: aqua;
background: #3333cc;
}
.name-list {
font-family: monospace;
margin-left: 40px;
margin-bottom: 2ex;
line-height: 140%;
}
.description {
margin-left: 40px;
margin-top: -2ex;
margin-bottom: 2ex;
}
.description p {
line-height: 140%;
}
.aka {
margin-left: 40px;
margin-bottom: 2ex;
line-height: 100%;
font-size: small;
color: #808080;
}
.methodtitle {
font-size: medium;
text-decoration: none;
color: #0000AA;
background: white;
}
.paramsig {
font-size: small;
}
.srcbut { float: right }
pre { font-size: 1.2em; }
tt { font-size: 1.2em; }
pre.source {
border-style: groove;
background-color: #ddddff;
margin-left: 40px;
padding: 1em 0em 1em 2em;
}
.classlist {
margin-left: 40px;
margin-bottom: 2ex;
line-height: 140%;
}
li {
display: list-item;
margin-top: .6em;
}
.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 }
}
############################################################################
HEADER = %{
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>%title%</title>
<meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
<link rel=StyleSheet href="%style_url%" type="text/css" media="screen" />
<script type="text/javascript" language="JavaScript">
<!--
function popCode(url) {
window.open(url, "Code",
"resizable=yes,scrollbars=yes,toolbar=no,status=no,height=150,width=400")
}
//-->
</script>
</head>
}
###################################################################
METHOD_LIST = %{
IF:includes
<table summary="Included modules" cellpadding="5" width="100%">
<tr><td class="tablesubtitle">Included modules</td></tr>
</table>
<div class="name-list">
START:includes
<span class="method-name">HREF:aref:name:</span>
END:includes
</div>
ENDIF:includes
IF:method_list
START:method_list
IF:methods
<table summary="Method list" cellpadding="5" width="100%">
<tr><td class="tablesubtitle">%type% %category% methods</td></tr>
</table>
START:methods
<table summary="method" width="100%" cellspacing="0" cellpadding="5" border="0">
<tr><td class="methodtitle">
<a name="%aref%"></a>
IF:codeurl
<a href="%codeurl%" target="Code" class="methodtitle"
onClick="popCode('%codeurl%');return false;">
ENDIF:codeurl
<b>%name%</b>%params%
IF:codeurl
</a>
ENDIF:codeurl
</td></tr>
</table>
IF:m_desc
<div class="description">
%m_desc%
</div>
ENDIF:m_desc
IF:aka
<div class="aka">
This method is also aliased as
START:aka
<a href="%aref%">%name%</a>
END:aka
</div>
ENDIF:aka
IF:sourcecode
<pre class="source">
%sourcecode%
</pre>
ENDIF:sourcecode
END:methods
ENDIF:methods
END:method_list
ENDIF:method_list
}
###################################################################
CONTEXT_CONTENT = %{
IF:diagram
<table summary="Diagram of classes and modules" width="100%">
<tr><td align="center">
%diagram%
</td></tr></table>
ENDIF:diagram
IF:description
<div class="description">%description%</div>
ENDIF:description
IF:requires
<table summary="Requires" cellpadding="5" width="100%">
<tr><td class="tablesubtitle">Required files</td></tr>
</table>
<div class="name-list">
START:requires
HREF:aref:name:&nbsp; &nbsp;
END:requires
</div>
ENDIF:requires
IF:methods
<table summary="Methods" cellpadding="5" width="100%">
<tr><td class="tablesubtitle">Methods</td></tr>
</table>
<div class="name-list">
START:methods
HREF:aref:name:&nbsp; &nbsp;
END:methods
</div>
ENDIF:methods
IF:constants
<table summary="Constants" cellpadding="5" width="100%">
<tr><td class="tablesubtitle">Constants</td></tr>
</table>
<table cellpadding="5">
START:constants
<tr valign="top"><td>%name%</td><td>=</td><td>%value%</td></tr>
IF:desc
<tr><td></td><td></td><td>%desc%</td></tr>
ENDIF:desc
END:constants
</table>
ENDIF:constants
IF:aliases
<table summary="Aliases" cellpadding="5" width="100%">
<tr><td class="tablesubtitle">External Aliases</td></tr>
</table>
<div class="name-list">
START:aliases
%old_name% -> %new_name%<br />
END:aliases
</div>
ENDIF:aliases
IF:attributes
<table summary="Attributes" cellpadding="5" width="100%">
<tr><td class="tablesubtitle">Attributes</td></tr>
</table>
<table summary="Attribute details" cellspacing="5">
START:attributes
<tr valign="top">
<td class="attr-name">%name%</td>
IF:rw
<td align="center" class="attr-rw">&nbsp;[%rw%]&nbsp;</td>
ENDIF:rw
IFNOT:rw
<td></td>
ENDIF:rw
<td>%a_desc%</td>
</tr>
END:attributes
</table>
ENDIF:attributes
IF:classlist
<table summary="List of classes" cellpadding="5" width="100%">
<tr><td class="tablesubtitle">Classes and Modules</td></tr>
</table>
<div class="classlist">
%classlist%
</div>
ENDIF:classlist
}
###############################################################################
BODY = HEADER + %{
<body bgcolor="white">
!INCLUDE! <!-- banner header -->
} +
CONTEXT_CONTENT + METHOD_LIST +
%{
</body>
</html>
}
###############################################################################
FILE_PAGE = <<_FILE_PAGE_
<table summary="Information on file" width="100%">
<tr class="title-row">
<td><table summary="layout" width="100%"><tr>
<td class="big-title-font" colspan="2">%short_name%</td>
<td align="right"><table summary="layout" cellspacing="0" cellpadding="2">
<tr>
<td class="small-title-font">Path:</td>
<td class="small-title-font">%full_path%
IF:cvsurl
&nbsp;(<a href="%cvsurl%">CVS</a>)
ENDIF:cvsurl
</td>
</tr>
<tr>
<td class="small-title-font">Modified:</td>
<td class="small-title-font">%dtm_modified%</td>
</tr>
</table>
</td></tr></table></td>
</tr>
</table>
_FILE_PAGE_
###################################################################
CLASS_PAGE = %{
<table summary="Information on class" width="100%" border="0" cellspacing="0">
<tr class="title-row">
<td class="big-title-font">
<sup><font color="aqua">%classmod%</font></sup> %full_name%
</td>
<td align="right">
<table summary="layout" cellspacing="0" cellpadding="2">
<tr valign="top">
<td class="small-title-font">In:</td>
<td class="small-title-font">
START:infiles
IF:full_path_url
<a href="%full_path_url%" class="aqua">
ENDIF:full_path_url
%full_path%
IF:full_path_url
</a>
ENDIF:full_path_url
IF:cvsurl
&nbsp;(<a href="%cvsurl%">CVS</a>)
ENDIF:cvsurl
<br />
END:infiles
</td>
</tr>
IF:parent
<tr>
<td class="small-title-font">Parent:</td>
<td class="small-title-font">
IF:par_url
<a href="%par_url%" class="aqua">
ENDIF:par_url
%parent%
IF:par_url
</a>
ENDIF:par_url
</td>
</tr>
ENDIF:parent
</table>
</td>
</tr>
</table>
}
=begin
=end
########################## Source code ##########################
SRC_PAGE = %{
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=%charset%">
<title>%title%</title>
<link rel=StyleSheet href="%style_url%" type="text/css" media="screen" />
</head>
<body bgcolor="white">
<pre>%code%</pre>
</body>
</html>
}
########################## Index ################################
FR_INDEX_BODY = %{
!INCLUDE!
}
FILE_INDEX = %{
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=%charset%">
<title>%list_title%</title>
<style type="text/css">
<!--
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%;
}
A.xx { color: white; font-weight: bold; }
-->
</style>
<base target="docwin">
</head>
<body>
<div class="banner"><a href="%index_url%" class="xx">%list_title%</a></div>
START:entries
<a href="%href%">%name%</a><br />
END:entries
</body></html>
}
CLASS_INDEX = FILE_INDEX
METHOD_INDEX = FILE_INDEX
INDEX = %{
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=%charset%">
<title>%title%</title></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="%initial_page%" name="docwin">
<noframes>
<body bgcolor="white">
Sorry, RDoc currently only generates HTML using frames.
</body>
</noframes>
</frameset>
</html>
}
######################################################################
#
# The following is used for the -1 option
#
CONTENTS_XML = %{
IF:description
%description%
ENDIF:description
IF:requires
<h4>Requires:</h4>
<ul>
START:requires
IF:aref
<li><a href="%aref%">%name%</a></li>
ENDIF:aref
IFNOT:aref
<li>%name%</li>
ENDIF:aref
END:requires
</ul>
ENDIF:requires
IF:attributes
<h4>Attributes</h4>
<table>
START:attributes
<tr><td>%name%</td><td>%rw%</td><td>%a_desc%</td></tr>
END:attributes
</table>
ENDIF:attributes
IF:includes
<h4>Includes</h4>
<ul>
START:includes
IF:aref
<li><a href="%aref%">%name%</a></li>
ENDIF:aref
IFNOT:aref
<li>%name%</li>
ENDIF:aref
END:includes
</ul>
ENDIF:includes
IF:method_list
<h3>Methods</h3>
START:method_list
IF:methods
START:methods
<h4>%type% %category% method: <a name="%aref%">%name%%params%</a></h4>
IF:m_desc
%m_desc%
ENDIF:m_desc
IF:sourcecode
<blockquote><pre>
%sourcecode%
</pre></blockquote>
ENDIF:sourcecode
END:methods
ENDIF:methods
END:method_list
ENDIF:method_list
}
########################################################################
ONE_PAGE = %{
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>%title%</title>
<meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
</head>
<body>
START:files
<h2>File: %short_name%</h2>
<table>
<tr><td>Path:</td><td>%full_path%</td></tr>
<tr><td>Modified:</td><td>%dtm_modified%</td></tr>
</table>
} + CONTENTS_XML + %{
END:files
IF:classes
<h2>Classes</h2>
START:classes
IF:parent
<h3>%classmod% %full_name% &lt; HREF:par_url:parent:</h3>
ENDIF:parent
IFNOT:parent
<h3>%classmod% %full_name%</h3>
ENDIF:parent
IF:infiles
(in files
START:infiles
HREF:full_path_url:full_path:
END:infiles
)
ENDIF:infiles
} + CONTENTS_XML + %{
END:classes
ENDIF:classes
</body>
</html>
}
end
end

View file

@ -0,0 +1,398 @@
module RDoc
module Page
FONTS = "Verdana, Arial, Helvetica, sans-serif"
STYLE = %{
body,td,p { font-family: %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: %fonts%;
font-size: large;
height: 60px;
padding: 10px 3px 10px 3px;
}
.small-title-font { color: black;
font-family: %fonts%;
font-size:10; }
.aqua { color: black }
.method-name, .attr-name {
font-family: font-family: %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-color: white;
}
.srclink {
font-size: small;
font-weight: bold;
text-decoration: none;
color: #0000DD;
background-color: white;
}
.paramsig {
font-size: small;
}
.srcbut { float: right }
}
############################################################################
BODY = %{
<html><head>
<title>%title%</title>
<meta http-equiv="Content-Type" content="text/html; charset=%charset%">
<link rel=StyleSheet href="%style_url%" type="text/css" media=screen>
<script type="text/javascript" language="JavaScript">
<!--
function popCode(url) {
parent.frames.source.location = url
}
//-->
</script>
</head>
<body bgcolor="white">
!INCLUDE! <!-- banner header -->
IF:diagram
<table width="100%"><tr><td align="center">
%diagram%
</td></tr></table>
ENDIF:diagram
IF:description
<div class="description">%description%</div>
ENDIF:description
IF:requires
<table cellpadding=5 width="100%">
<tr><td class="tablesubtitle">Required files</td></tr>
</table><br>
<div class="name-list">
START:requires
HREF:aref:name:
END:requires
ENDIF:requires
</div>
IF:methods
<table cellpadding=5 width="100%">
<tr><td class="tablesubtitle">Methods</td></tr>
</table><br>
<div class="name-list">
START:methods
HREF:aref:name:,
END:methods
</div>
ENDIF:methods
IF:attributes
<table cellpadding=5 width="100%">
<tr><td class="tablesubtitle">Attributes</td></tr>
</table><br>
<table cellspacing=5>
START:attributes
<tr valign="top">
IF:rw
<td align="center" class="attr-rw">&nbsp;[%rw%]&nbsp;</td>
ENDIF:rw
IFNOT:rw
<td></td>
ENDIF:rw
<td class="attr-name">%name%</td>
<td>%a_desc%</td>
</tr>
END:attributes
</table>
ENDIF:attributes
IF:classlist
<table cellpadding=5 width="100%">
<tr><td class="tablesubtitle">Classes and Modules</td></tr>
</table><br>
%classlist%<br>
ENDIF:classlist
!INCLUDE! <!-- method descriptions -->
</body>
</html>
}
###############################################################################
FILE_PAGE = <<_FILE_PAGE_
<table width="100%">
<tr class="title-row">
<td><table width="100%"><tr>
<td class="big-title-font" colspan=2><font size=-3><B>File</B><BR></font>%short_name%</td>
<td align="right"><table cellspacing=0 cellpadding=2>
<tr>
<td class="small-title-font">Path:</td>
<td class="small-title-font">%full_path%
IF:cvsurl
&nbsp;(<a href="%cvsurl%">CVS</a>)
ENDIF:cvsurl
</td>
</tr>
<tr>
<td class="small-title-font">Modified:</td>
<td class="small-title-font">%dtm_modified%</td>
</tr>
</table>
</td></tr></table></td>
</tr>
</table><br>
_FILE_PAGE_
###################################################################
CLASS_PAGE = %{
<table width="100%" border=0 cellspacing=0>
<tr class="title-row">
<td class="big-title-font">
<font size=-3><B>%classmod%</B><BR></font>%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">
START:infiles
HREF:full_path_url:full_path:
IF:cvsurl
&nbsp;(<a href="%cvsurl%">CVS</a>)
ENDIF:cvsurl
END:infiles
</td>
</tr>
IF:parent
<tr>
<td class="small-title-font">Parent:</td>
<td class="small-title-font">
IF:par_url
<a href="%par_url%" class="cyan">
ENDIF:par_url
%parent%
IF:par_url
</a>
ENDIF:par_url
</td>
</tr>
ENDIF:parent
</table>
</td>
</tr>
</table><br>
}
###################################################################
METHOD_LIST = %{
IF:includes
<div class="tablesubsubtitle">Included modules</div><br>
<div class="name-list">
START:includes
<span class="method-name">HREF:aref:name:</span>
END:includes
</div>
ENDIF:includes
IF:method_list
START:method_list
IF:methods
<table cellpadding=5 width="100%">
<tr><td class="tablesubtitle">%type% %category% methods</td></tr>
</table>
START:methods
<table width="100%" cellspacing = 0 cellpadding=5 border=0>
<tr><td class="methodtitle">
<a name="%aref%">
<b>%name%</b>%params%
IF:codeurl
<a href="%codeurl%" target="source" class="srclink">src</a>
ENDIF:codeurl
</a></td></tr>
</table>
IF:m_desc
<div class="description">
%m_desc%
</div>
ENDIF:m_desc
END:methods
ENDIF:methods
END:method_list
ENDIF:method_list
}
=begin
=end
########################## Source code ##########################
SRC_PAGE = %{
<html>
<head><title>%title%</title>
<meta http-equiv="Content-Type" content="text/html; charset=%charset%">
<style>
.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 }
.kw { color: #3333FF; font-weight: bold }
.cmt { color: green; font-style: italic }
.str { color: #662222; font-style: italic }
.re { color: #662222; }
</style>
</head>
<body bgcolor="white">
<pre>%code%</pre>
</body>
</html>
}
########################## Index ################################
FR_INDEX_BODY = %{
!INCLUDE!
}
FILE_INDEX = %{
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=%charset%">
<style>
<!--
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%;
}
-->
</style>
<base target="docwin">
</head>
<body>
<div class="banner">%list_title%</div>
START:entries
<a href="%href%">%name%</a><br>
END:entries
</body></html>
}
CLASS_INDEX = FILE_INDEX
METHOD_INDEX = FILE_INDEX
INDEX = %{
<html>
<head>
<title>%title%</title>
<meta http-equiv="Content-Type" content="text/html; charset=%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>
<frameset rows="80%,20%">
<frame src="%initial_page%" name="docwin">
<frame src="blank.html" name="source">
</frameset>
<noframes>
<body bgcolor="white">
Click <a href="html/index.html">here</a> for a non-frames
version of this page.
</body>
</noframes>
</frameset>
</html>
}
# and a blank page to use as a target
BLANK = %{
<html><body bgcolor="white"></body></html>
}
def write_extra_pages
template = TemplatePage.new(BLANK)
File.open("blank.html", "w") { |f| template.write_html_on(f, {}) }
end
end
end

View file

@ -0,0 +1,112 @@
module RDoc
module Page
CONTENTS_RDF = %{
IF:description
<description rd:parseType="Literal">
%description%
</description>
ENDIF:description
IF:requires
START:requires
<rd:required-file rd:name="%name%" />
END:requires
ENDIF:requires
IF:attributes
START:attributes
<contents>
<Attribute rd:name="%name%">
IF:rw
<attribute-rw>%rw%</attribute-rw>
ENDIF:rw
<description rdf:parseType="Literal">%a_desc%</description>
</Attribute>
</contents>
END:attributes
ENDIF:attributes
IF:includes
<IncludedModuleList>
START:includes
<included-module rd:name="%name%" />
END:includes
</IncludedModuleList>
ENDIF:includes
IF:method_list
START:method_list
IF:methods
START:methods
<contents>
<Method rd:name="%name%" rd:visibility="%type%"
rd:category="%category%" rd:id="%aref%">
<parameters>%params%</parameters>
IF:m_desc
<description rdf:parseType="Literal">
%m_desc%
</description>
ENDIF:m_desc
IF:sourcecode
<source-code-listing rdf:parseType="Literal">
%sourcecode%
</source-code-listing>
ENDIF:sourcecode
</Method>
</contents>
END:methods
ENDIF:methods
END:method_list
ENDIF:method_list
<!-- end method list -->
}
########################################################################
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 -->
START:files
<rd:File rd:name="%short_name%" rd:id="%href%">
<path>%full_path%</path>
<dtm-modified>%dtm_modified%</dtm-modified>
} + CONTENTS_RDF + %{
</rd:File>
END:files
START:classes
<%classmod% rd:name="%full_name%" rd:id="%full_name%">
<classmod-info>
IF:infiles
<InFiles>
START:infiles
<infile>
<File rd:name="%full_path%"
IF:full_path_url
rdf:about="%full_path_url%"
ENDIF:full_path_url
/>
</infile>
END:infiles
</InFiles>
ENDIF:infiles
IF:parent
<superclass>HREF:par_url:parent:</superclass>
ENDIF:parent
</classmod-info>
} + CONTENTS_RDF + %{
</%classmod%>
END:classes
<!-- /RDoc -->
</rdf:RDF>
}
end
end

View file

@ -0,0 +1,112 @@
module RDoc
module Page
CONTENTS_XML = %{
IF:description
<description>
%description%
</description>
ENDIF:description
<contents>
IF:requires
<required-file-list>
START:requires
<required-file name="%name%"
IF:aref
href="%aref%"
ENDIF:aref
/>
END:requires
</required-file-list>
ENDIF:requires
IF:attributes
<attribute-list>
START:attributes
<attribute name="%name%">
IF:rw
<attribute-rw>%rw%</attribute-rw>
ENDIF:rw
<description>%a_desc%</description>
</attribute>
END:attributes
</attribute-list>
ENDIF:attributes
IF:includes
<included-module-list>
START:includes
<included-module name="%name%"
IF:aref
href="%aref%"
ENDIF:aref
/>
END:includes
</included-module-list>
ENDIF:includes
IF:method_list
<method-list>
START:method_list
IF:methods
START:methods
<method name="%name%" type="%type%" category="%category%" id="%aref%">
<parameters>%params%</parameters>
IF:m_desc
<description>
%m_desc%
</description>
ENDIF:m_desc
IF:sourcecode
<source-code-listing>
%sourcecode%
</source-code-listing>
ENDIF:sourcecode
</method>
END:methods
ENDIF:methods
END:method_list
</method-list>
ENDIF:method_list
</contents>
}
########################################################################
ONE_PAGE = %{<?xml version="1.0" encoding="utf-8"?>
<rdoc>
<file-list>
START:files
<file name="%short_name%" id="%href%">
<file-info>
<path>%full_path%</path>
<dtm-modified>%dtm_modified%</dtm-modified>
</file-info>
} + CONTENTS_XML + %{
</file>
END:files
</file-list>
<class-module-list>
START:classes
<%classmod% name="%full_name%" id="%full_name%">
<classmod-info>
IF:infiles
<infiles>
START:infiles
<infile>HREF:full_path_url:full_path:</infile>
END:infiles
</infiles>
ENDIF:infiles
IF:parent
<superclass>HREF:par_url:parent:</superclass>
ENDIF:parent
</classmod-info>
} + CONTENTS_XML + %{
</%classmod%>
END:classes
</class-module-list>
</rdoc>
}
end
end

View file

@ -0,0 +1,130 @@
require 'ftools'
require 'rdoc/options'
require 'rdoc/markup/simple_markup'
require 'rdoc/markup/simple_markup/to_html'
require 'rdoc/generators/html_generator'
module Generators
# Generate XML output as one big file
class XMLGenerator < HTMLGenerator
# Standard generator factory
def XMLGenerator.for(options)
XMLGenerator.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 HtmlFile objects for each TopLevel object.
# * a list of HtmlClass 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 << HtmlFile.new(toplevel, @options, FILE_DIR)
end
RDoc::TopLevel.all_classes_and_modules.each do |cls|
build_class_list(cls, @files[0], CLASS_DIR)
end
end
def build_class_list(from, html_file, class_dir)
@classes << HtmlClass.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)
}
# this method is defined in the template file
write_extra_pages if defined? write_extra_pages
template = TemplatePage.new(RDoc::Page::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(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
end

View file

@ -0,0 +1,16 @@
#!/usr/local/bin/ruby
# Illustration of a script to convert an RDoc-style file to a LaTeX
# document
require 'rdoc/markup/simple_markup'
require 'rdoc/markup/simple_markup/to_latex'
p = SM::SimpleMarkup.new
h = SM::ToLaTeX.new
#puts "\\documentclass{report}"
#puts "\\usepackage{tabularx}"
#puts "\\usepackage{parskip}"
#puts "\\begin{document}"
puts p.convert(ARGF.read, h)
#puts "\\end{document}"

View file

@ -0,0 +1,42 @@
# This program illustrates the basic use of the SimpleMarkup
# class. It extracts the first comment block from the
# simple_markup.rb file and converts it into HTML on
# standard output. Run it using
#
# % ruby sample.rb
#
# You should be in the sample/ directory when you do this,
# as it hardwires the path to the files it needs to require.
# This isn't necessary in the code you write once you've
# installed the package.
#
# For a better way of formatting code comment blocks (and more)
# see the rdoc package.
#
$:.unshift "../../.."
require 'rdoc/markup/simple_markup'
require 'rdoc/markup/simple_markup/to_html'
# Extract the comment block from the source file
input_string = ""
File.foreach("../simple_markup.rb") do |line|
break unless line.gsub!(/^\# ?/, '')
input_string << line
end
# Create a markup object
markup = SM::SimpleMarkup.new
# Attach it to an HTML formatter
h = SM::ToHtml.new
# And convert out comment block to html. Wrap it a body
# tag pair to let browsers view it
puts "<html><body>"
puts markup.convert(input_string, h)
puts "</body></html>"

View file

@ -0,0 +1,477 @@
# = Introduction
#
# SimpleMarkup parses plain text documents and attempts to decompose
# them into their constituent parts. Some of these parts are high-level:
# paragraphs, chunks of verbatim text, list entries and the like. Other
# parts happen at the character level: a piece of bold text, a word in
# code font. This markup is similar in spirit to that used on WikiWiki
# webs, where folks create web pages using a simple set of formatting
# rules.
#
# SimpleMarkup itself does no output formatting: this is left to a
# different set of classes.
#
# SimpleMarkup is extendable at runtime: you can add new markup
# elements to be recognised in the documents that SimpleMarkup parses.
#
# SimpleMarkup is intended to be the basis for a family of tools which
# share the common requirement that simple, plain-text should be
# rendered in a variety of different output formats and media. It is
# envisaged that SimpleMarkup could be the basis for formating RDoc
# style comment blocks, Wiki entries, and online FAQs.
#
# = Basic Formatting
#
# * SimpleMarkup looks for a document's natural left margin. This is
# used as the initial margin for the document.
#
# * Consecutive lines starting at this margin are considered to be a
# paragraph.
#
# * If a paragraph starts with a "*", "-", or with "<digit>.", then it is
# taken to be the start of a list. The margin in increased to be the
# first non-space following the list start flag. Subsequent lines
# should be indented to this new margin until the list ends. For
# example:
#
# * this is a list with three paragraphs in
# the first item. This is the first paragraph.
#
# And this is the second paragraph.
#
# 1. This is an indented, numbered list.
# 2. This is the second item in that list
#
# This is the third conventional paragraph in the
# first list item.
#
# * This is the second item in the original list
#
# * You can also construct labeled lists, sometimes called description
# or definition lists. Do this by putting the label in square brackets
# and indenting the list body:
#
# [cat] a small furry mammal
# that seems to sleep a lot
#
# [ant] a little insect that is known
# to enjoy picnics
#
# A minor variation on labeled lists uses two colons to separate the
# label from the list body:
#
# cat:: a small furry mammal
# that seems to sleep a lot
#
# ant:: a little insect that is known
# to enjoy picnics
#
# This latter style guarantees that the list bodies' left margins are
# aligned: think of them as a two column table.
#
# * Any line that starts to the right of the current margin is treated
# as verbatim text. This is useful for code listings. The example of a
# list above is also verbatim text.
#
# * A line starting with an equals sign (=) is treated as a
# heading. Level one headings have one equals sign, level two headings
# have two,and so on.
#
# * A line starting with three or more hyphens (at the current indent)
# generates a horizontal rule. THe more hyphens, the thicker the rule
# (within reason, and if supported by the output device)
#
# * You can use markup within text (except verbatim) to change the
# appearance of parts of that text. Out of the box, SimpleMarkup
# supports word-based and general markup.
#
# Word-based markup uses flag characters around individual words:
#
# [\*word*] displays word in a *bold* font
# [\_word_] displays word in an _emphasized_ font
# [\+word+] displays word in a +code+ font
#
# General markup affects text between a start delimiter and and end
# delimiter. Not surprisingly, these delimiters look like HTML markup.
#
# [\<b>text...</b>] displays word in a *bold* font
# [\<em>text...</em>] displays word in an _emphasized_ font
# [\<i>text...</i>] displays word in an _emphasized_ font
# [\<tt>text...</tt>] displays word in a +code+ font
#
# Unlike conventional Wiki markup, general markup can cross line
# boundaries. You can turn off the interpretation of markup by
# preceding the first character with a backslash, so \\\<b>bold
# text</b> and \\\*bold* produce \<b>bold text</b> and \*bold
# respectively.
#
# = Using SimpleMarkup
#
# For information on using SimpleMarkup programatically,
# see SM::SimpleMarkup.
#
# Author:: Dave Thomas, dave@pragmaticprogrammer.com
# Version:: 0.0
# License:: Ruby license
require 'rdoc/markup/simple_markup/fragments'
require 'rdoc/markup/simple_markup/lines.rb'
module SM #:nodoc:
# == Synopsis
#
# This code converts <tt>input_string</tt>, which is in the format
# described in markup/simple_markup.rb, to HTML. The conversion
# takes place in the +convert+ method, so you can use the same
# SimpleMarkup object to convert multiple input strings.
#
# require 'rdoc/markup/simple_markup'
# require 'rdoc/markup/simple_markup/to_html'
#
# p = SM::SimpleMarkup.new
# h = SM::ToHtml.new
#
# puts p.convert(input_string, h)
#
# You can extend the SimpleMarkup parser to recognise new markup
# sequences, and to add special processing for text that matches a
# regular epxression. Here we make WikiWords significant to the parser,
# and also make the sequences {word} and \<no>text...</no> signify
# strike-through text. When then subclass the HTML output class to deal
# with these:
#
# require 'rdoc/markup/simple_markup'
# require 'rdoc/markup/simple_markup/to_html'
#
# class WikiHtml < SM::ToHtml
# def handle_special_WIKIWORD(special)
# "<font color=red>" + special.text + "</font>"
# end
# end
#
# p = SM::SimpleMarkup.new
# p.add_word_pair("{", "}", :STRIKE)
# p.add_html("no", :STRIKE)
#
# p.add_special(/\b([A-Z][a-z]+[A-Z]\w+)/, :WIKIWORD)
#
# h = WikiHtml.new
# h.add_tag(:STRIKE, "<strike>", "</strike>")
#
# puts "<body>" + p.convert(ARGF.read, h) + "</body>"
#
# == Output Formatters
#
# _missing_
#
#
class SimpleMarkup
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|$)
\s*
)/x
##
# take a block of text and use various heuristics to determine
# it's structure (paragraphs, lists, and so on). Invoke an
# event handler as we identify significant chunks.
#
def initialize
@am = AttributeManager.new
@output = nil
end
##
# Add to the sequences used to add formatting to an individual word
# (such as *bold*). Matching entries will generate attibutes
# that the output formatters can recognize by their +name+
def add_word_pair(start, stop, name)
@am.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)
end
##
# Add to other inline sequences. For example, we could add
# WikiWords using something like:
#
# parser.add_special(/\b([A-Z][a-z]+[A-Z]\w+)/, :WIKIWORD)
#
# Each wiki word will be presented to the output formatter
# via the accept_special method
#
def add_special(pattern, name)
@am.add_special(pattern, name)
end
# We take a string, split it into lines, work out the type of
# each line, and from there deduce groups of lines (for example
# all lines in a paragraph). We then invoke the output formatter
# using a Visitor to display the result
def convert(str, op)
@lines = Lines.new(str.split(/\r?\n/).collect { |aLine|
Line.new(aLine) })
return "" if @lines.empty?
@lines.normalize
assign_types_to_lines
group = group_lines
# call the output formatter to handle the result
# group.to_a.each {|i| p i}
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.isBlank?
line.stamp(Line::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(Line::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 ListBase::BULLET
when /^\d/ then ListBase::NUMBER
when /^[A-Z]/ then ListBase::UPPERALPHA
when /^[a-z]/ then ListBase::LOWERALPHA
else raise "Invalid List Type: #{self.inspect}"
end
line.stamp(Line::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(Line::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(Line::VERBATIM, level)
else
line.stamp(Line::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 ab 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 /^\[/
flag = ListBase::LABELED
prefix = prefix[1, prefix.length-2]
when /:$/
flag = ListBase::NOTE
prefix.chop!
else raise "Invalid List Type: #{self.inspect}"
end
# body is on the next line
if text.length <= offset
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
@lines.unget
return false
else
offset = i
prefix_length = 0
@lines.delete(original_line)
end
end
line.stamp(Line::LIST, level+1, prefix, flag)
text[margin, prefix_length] = " " * prefix_length
assign_types_to_lines(offset, level + 1)
return true
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
inList = false
wantedType = wantedLevel = nil
block = LineCollection.new
group = nil
while line = @lines.next
if line.level == wantedLevel and line.type == wantedType
group.add_text(line.text)
else
group = block.fragment_for(line)
block.add(group)
if line.type == Line::LIST
wantedType = Line::PARAGRAPH
else
wantedType = line.type
end
wantedLevel = 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
end

View file

@ -0,0 +1,328 @@
require 'rdoc/markup/simple_markup/lines.rb'
require 'rdoc/markup/simple_markup/inline.rb'
module SM
##
# 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
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
######
# This is a simple factory system that lets us associate fragement
# types (a string) with a subclass of fragment
TYPE_MAP = {}
def Fragment.type_name(name)
TYPE_MAP[name] = self
end
def Fragment.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
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 Line::PARAGRAPH
end
class BlankLine < Paragraph
type_name Line::BLANK
end
class Heading < Paragraph
type_name Line::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 = :BULLET
NUMBER = :NUMBER
UPPERALPHA = :UPPERALPHA
LOWERALPHA = :LOWERALPHA
LABELED = :LABELED
NOTE = :NOTE
end
class ListItem < ListBase
type_name Line::LIST
# def label
# am = AttributeManager.new(@param)
# am.flow
# 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 Line::VERBATIM
def add_text(txt)
@txt << txt.chomp << "\n"
end
end
##
# A horizontal rule
class Rule < Fragment
type_name Line::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
# For testing
def to_a
@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
# 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
# now insert 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
# Finally 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 @fragments[i].kind_of?(BlankLine) and
@fragments[i+1].kind_of?(ListEnd)
@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

View file

@ -0,0 +1,348 @@
module SM
# We manage a set of attributes. Each attribute has a symbol name
# and a bit value
class Attribute
SPECIAL = 1
@@name_to_bitmap = { :_SPECIAL_ => SPECIAL }
@@next_bitmap = 2
def Attribute.bitmap_for(name)
bitmap = @@name_to_bitmap[name]
if !bitmap
bitmap = @@next_bitmap
@@next_bitmap <<= 1
@@name_to_bitmap[name] = bitmap
end
bitmap
end
def Attribute.as_string(bitmap)
return "none" if bitmap.zero?
res = []
@@name_to_bitmap.each do |name, bit|
res << name if (bitmap & bit) != 0
end
res.join(",")
end
def Attribute.each_name_of(bitmap)
@@name_to_bitmap.each do |name, bit|
next if bit == SPECIAL
yield name.to_s if (bitmap & bit) != 0
end
end
end
# 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
AttrChanger = Struct.new(:turn_on, :turn_off)
class AttrChanger
def to_s
"Attr: +#{Attribute.as_string(@turn_on)}/-#{Attribute.as_string(@turn_on)}"
end
end
# An array of attributes which parallels the characters in a string
class AttrSpan
def initialize(length)
@attrs = Array.new(length, 0)
end
def set_attrs(start, length, bits)
for i in start ... (start+length)
@attrs[i] |= bits
end
end
def [](n)
@attrs[n]
end
end
##
# Hold details of a special sequence
class Special
attr_reader :type
attr_accessor :text
def initialize(type, text)
@type, @text = type, text
end
def ==(o)
self.text == o.text && self.type == o.type
end
def to_s
"Special: type=#{type}, text=#{text.dump}"
end
end
class AttributeManager
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
#
=begin
ATTR_FLAG = 001
A_START = 002
A_END = 003
A_SPECIAL_START = 005
A_SPECIAL_END = 006
START_ATTR = ATTR_FLAG.chr + A_START.chr
END_ATTR = ATTR_FLAG.chr + A_END.chr
START_SPECIAL = ATTR_FLAG.chr + A_SPECIAL_START.chr
END_SPECIAL = ATTR_FLAG.chr + A_SPECIAL_END.chr
=end
A_PROTECT = 004
PROTECT_ATTR = A_PROTECT.chr
# 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 = {}
# 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 = {}
# This maps HTML tags to the corresponding attribute char
HTML_TAGS = {}
# And this maps _special_ sequences to a name. A special sequence
# is something like a WikiWord
SPECIAL = {}
# Return an attribute object with the given turn_on
# and turn_off bits set
def attribute(turn_on, turn_off)
AttrChanger.new(turn_on, turn_off)
end
def change_attribute(current, new)
diff = current ^ new
attribute(new & diff, current & diff)
end
def changed_attribute_by_name(current_set, new_set)
current = new = 0
current_set.each {|name| current |= Attribute.bitmap_for(name) }
new_set.each {|name| new |= Attribute.bitmap_for(name) }
change_attribute(current, new)
end
def copy_string(start_pos, end_pos)
res = @str[start_pos...end_pos]
res.gsub!(/\000/, '')
res
end
# Map attributes like <b>text</b>to the sequence \001\002<char>\001\003<char>,
# where <char> is a per-attribute specific character
def convert_attrs(str, attrs)
# first do matching ones
tags = MATCHING_WORD_PAIRS.keys.join("")
re = "(^|\\W)([#{tags}])([A-Za-z_]+?)\\2(\\W|\$)"
# re = "(^|\\W)([#{tags}])(\\S+?)\\2(\\W|\$)"
1 while str.gsub!(Regexp.new(re)) {
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
}
# then non-matching
unless WORD_PAIR_MAP.empty?
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
}
end
end
end
def convert_html(str, attrs)
tags = HTML_TAGS.keys.join("|")
re = "<(#{tags})>(.*?)</\\1>"
1 while str.gsub!(Regexp.new(re, Regexp::IGNORECASE)) {
attr = HTML_TAGS[$1.downcase]
html_length = $1.length + 2
seq = NULL * html_length
attrs.set_attrs($`.length + html_length, $2.length, attr)
seq + $2 + seq + NULL
}
end
def convert_specials(str, attrs)
unless SPECIAL.empty?
SPECIAL.each do |regexp, attr|
str.scan(regexp) do
attrs.set_attrs($`.length, $1.length, attr | Attribute::SPECIAL)
end
end
end
end
# A \ in front of a character that would normally be
# processed turns off processing. We do this by turning
# \< into <#{PROTECT}
PROTECTABLE = [ "<" << "\\" ] #"
def mask_protected_sequences
protect_pattern = Regexp.new("\\\\([#{Regexp.escape(PROTECTABLE.join(''))}])")
@str.gsub!(protect_pattern, "\\1#{PROTECT_ATTR}")
end
def unmask_protected_sequences
@str.gsub!(/(.)#{PROTECT_ATTR}/, '\1')
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)
end
def add_word_pair(start, stop, name)
raise "Word flags may not start '<'" if start[0] == ?<
bitmap = Attribute.bitmap_for(name)
if start == stop
MATCHING_WORD_PAIRS[start] = bitmap
else
pattern = Regexp.new("(" + Regexp.escape(start) + ")" +
# "([A-Za-z]+)" +
"(\\S+)" +
"(" + Regexp.escape(stop) +")")
WORD_PAIR_MAP[pattern] = bitmap
end
PROTECTABLE << start[0,1]
PROTECTABLE.uniq!
end
def add_html(tag, name)
HTML_TAGS[tag.downcase] = Attribute.bitmap_for(name)
end
def add_special(pattern, name)
SPECIAL[pattern] = Attribute.bitmap_for(name)
end
def flow(str)
@str = str
@attrs = AttrSpan.new(str.length)
puts("Before flow, str='#{@str.dump}'") if $DEBUG
mask_protected_sequences
convert_attrs(@str, @attrs)
convert_html(@str, @attrs)
convert_specials(str, @attrs)
unmask_protected_sequences
puts("After flow, str='#{@str.dump}'") if $DEBUG
return split_into_flow
end
def display_attributes
puts
puts @str.tr(NULL, "!")
bit = 1
16.times do |bno|
line = ""
@str.length.times do |i|
if (@attrs[i] & bit) == 0
line << " "
else
if bno.zero?
line << "S"
else
line << ("%d" % (bno+1))
end
end
end
puts(line) unless line =~ /^ *$/
bit <<= 1
end
end
def split_into_flow
display_attributes if $DEBUG
res = []
current_attr = 0
str = ""
str_len = @str.length
# skip leading invisible text
i = 0
i += 1 while i < str_len and @str[i].zero?
start_pos = i
# then scan the string, chunking it on attribute changes
while i < str_len
new_attr = @attrs[i]
if new_attr != current_attr
if i > start_pos
res << copy_string(start_pos, i)
start_pos = i
end
res << change_attribute(current_attr, new_attr)
current_attr = new_attr
if (current_attr & Attribute::SPECIAL) != 0
i += 1 while i < str_len and (@attrs[i] & Attribute::SPECIAL) != 0
res << Special.new(current_attr, copy_string(start_pos, i))
start_pos = i
next
end
end
# move on, skipping any invisible characters
begin
i += 1
end while i < str_len and @str[i].zero?
end
# tidy up trailing text
if start_pos < str_len
res << copy_string(start_pos, str_len)
end
# and reset to all attributes off
res << change_attribute(current_attr, 0) if current_attr != 0
return res
end
end
end

View file

@ -0,0 +1,151 @@
##########################################################################
#
# 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
module SM
class Line
INFINITY = 9999
BLANK = :BLANK
HEADING = :HEADING
LIST = :LIST
RULE = :RULE
PARAGRAPH = :PARAGRAPH
VERBATIM = :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 isBlank?
@text.length.zero?
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 # for debugging
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 == Line::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

View file

@ -0,0 +1,68 @@
module SM
##
# Handle common directives that can occur in a block of text:
#
# : include : filename
#
class PreProcess
def initialize(input_file_name, include_path)
@input_file_name = input_file_name
@include_path = include_path
end
# Look for common options in a chunk of text. Options that
# we don't handle are passed back to our caller
# as |directive, param|
def handle(text)
text.gsub!(/^([ \t#]*):(\w+):\s*(.+)?\n/) do
directive = $2.downcase
param = $3
case directive
when "include"
include_file($3, $1)
else
yield(directive, param)
end
end
end
#######
private
#######
# Include a file, indenting it correctly
def include_file(name, indent)
if (full_name = find_include_file(name))
content = File.open(full_name) {|f| f.read}
res = content.gsub(/^#?/, indent)
else
$stderr.puts "Couldn't find file to include: '#{name}'"
''
end
end
# Look for the given file in the directory containing the current
# file, and then in each of the directories specified in the
# RDOC_INCLUDE path
def find_include_file(name)
to_search = [ File.dirname(@input_file_name) ].concat @include_path
to_search.each do |dir|
full_name = File.join(dir, name)
stat = File.stat(full_name) rescue next
return full_name if stat.readable?
end
nil
end
end
end

View file

@ -0,0 +1,289 @@
require 'rdoc/markup/simple_markup/fragments'
require 'rdoc/markup/simple_markup/inline'
require 'cgi'
module SM
class ToHtml
LIST_TYPE_TO_HTML = {
ListBase::BULLET => [ "<ul>", "</ul>" ],
ListBase::NUMBER => [ "<ol>", "</ol>" ],
ListBase::UPPERALPHA => [ "<ol>", "</ol>" ],
ListBase::LOWERALPHA => [ "<ol>", "</ol>" ],
ListBase::LABELED => [ "<dl>", "</dl>" ],
ListBase::NOTE => [ "<table>", "</table>" ],
}
InlineTag = Struct.new(:bit, :on, :off)
def initialize
init_tags
end
##
# Set up the standard mapping of attributes to HTML tags
#
def init_tags
@attr_tags = [
InlineTag.new(SM::Attribute.bitmap_for(:BOLD), "<b>", "</b>"),
InlineTag.new(SM::Attribute.bitmap_for(:TT), "<tt>", "</tt>"),
InlineTag.new(SM::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(SM::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 = ""
@in_list_entry = []
end
def end_accepting
@res
end
def accept_paragraph(am, fragment)
@res << annotate("<p>") + "\n"
@res << wrap(convert_flow(am.flow(fragment.txt)))
@res << annotate("</p>") + "\n"
end
def accept_verbatim(am, fragment)
@res << annotate("<pre>") + "\n"
@res << CGI.escapeHTML(fragment.txt)
@res << annotate("</pre>") << "\n"
end
def accept_rule(am, fragment)
size = fragment.param
size = 10 if size > 10
@res << "<hr size=\"#{size}\"></hr>"
end
def accept_list_start(am, fragment)
@res << html_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 << annotate(tag) << "\n"
end
@res << html_list_name(fragment.type, false) <<"\n"
end
def accept_list_item(am, fragment)
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"
@in_list_entry[-1] = list_end_for(fragment.type)
end
def accept_blank_line(am, fragment)
# @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
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
# some of these patterns are taken from SmartyPants...
def convert_string(item)
CGI.escapeHTML(item).
# convert -- to em-dash, (-- to en-dash)
gsub(/---?/, '&#8212;'). #gsub(/--/, '&#8211;').
# convert ... to elipsis (and make sure .... becomes .<elipsis>)
gsub(/\.\.\.\./, '.&#8230;').gsub(/\.\.\./, '&#8230;').
# convert single closing quote
gsub(%r{([^ \t\r\n\[\{\(])\'}) { "#$1&#8217;" }.
gsub(%r{\'(?=\W|s\b)}) { "&#8217;" }.
# convert single opening quote
gsub(/'/, '&#8216;').
# convert double closing quote
gsub(%r{([^ \t\r\n\[\{\(])\'(?=\W)}) { "#$1&#8221;" }.
# convert double opening quote
gsub(/'/, '&#8220;').
# convert copyright
gsub(/\(c\)/, '&#169;').
# convert and registered trademark
gsub(/\(r\)/, '&#174;')
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 =
annotate("<h#{level}>") +
convert_flow(flow) +
annotate("</h#{level}>\n")
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
def list_item_start(am, fragment)
case fragment.type
when ListBase::BULLET, ListBase::NUMBER
annotate("<li>")
when ListBase::UPPERALPHA
annotate("<li type=\"A\">")
when ListBase::LOWERALPHA
annotate("<li type=\"a\">")
when ListBase::LABELED
annotate("<dt>") +
convert_flow(am.flow(fragment.param)) +
annotate("</dt>") +
annotate("<dd>")
when ListBase::NOTE
annotate("<tr>") +
annotate("<td valign=\"top\">") +
convert_flow(am.flow(fragment.param)) +
annotate("</td>") +
annotate("<td>")
else
raise "Invalid list type"
end
end
def list_end_for(fragment_type)
case fragment_type
when ListBase::BULLET, ListBase::NUMBER, ListBase::UPPERALPHA, ListBase::LOWERALPHA
"</li>"
when ListBase::LABELED
"</dd>"
when ListBase::NOTE
"</td></tr>"
else
raise "Invalid list type"
end
end
end
end

View file

@ -0,0 +1,333 @@
require 'rdoc/markup/simple_markup/fragments'
require 'rdoc/markup/simple_markup/inline'
require 'cgi'
module SM
# Convert SimpleMarkup to basic LaTeX report format
class ToLaTeX
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)
SM::ToLaTeX.l(arg)
end
LIST_TYPE_TO_LATEX = {
ListBase::BULLET => [ l("\\begin{itemize}"), l("\\end{itemize}") ],
ListBase::NUMBER => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\arabic" ],
ListBase::UPPERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\Alph" ],
ListBase::LOWERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\alph" ],
ListBase::LABELED => [ l("\\begin{description}"), l("\\end{description}") ],
ListBase::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(SM::Attribute.bitmap_for(:BOLD), l("\\textbf{"), l("}")),
InlineTag.new(SM::Attribute.bitmap_for(:TT), l("\\texttt{"), l("}")),
InlineTag.new(SM::Attribute.bitmap_for(:EM), l("\\emph{"), l("}")),
]
end
##
# Escape a LaTeX string
def escape(str)
# $stderr.print "FE: ", str
s = str.
# sub(/\s+$/, '').
gsub(/([_\${}&%#])/, "#{BS}\\1").
gsub(/\\/, BACKSLASH).
gsub(/\^/, HAT).
gsub(/~/, TILDE).
gsub(/</, LESSTHAN).
gsub(/>/, GREATERTHAN).
gsub(/,,/, ",{},").
gsub(/\`/, BACKQUOTE)
# $stderr.print "-> ", s, "\n"
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(SM::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}'"
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 ListBase::BULLET, ListBase::NUMBER, ListBase::UPPERALPHA, ListBase::LOWERALPHA
"\\item "
when ListBase::LABELED
"\\item[" + convert_flow(am.flow(fragment.param)) + "] "
when ListBase::NOTE
convert_flow(am.flow(fragment.param)) + " & "
else
raise "Invalid list type"
end
end
def list_end_for(fragment_type)
case fragment_type
when ListBase::BULLET, ListBase::NUMBER, ListBase::UPPERALPHA, ListBase::LOWERALPHA, ListBase::LABELED
""
when ListBase::NOTE
"\\\\\n"
else
raise "Invalid list type"
end
end
end
end

View file

@ -0,0 +1,2 @@
require 'TestParse.rb'
require 'TestInline.rb'

View file

@ -0,0 +1,151 @@
require "test/unit"
$:.unshift "../../.."
require "rdoc/markup/simple_markup/inline"
class TestInline < Test::Unit::TestCase
def setup
@am = SM::AttributeManager.new
@bold_on = @am.changed_attribute_by_name([], [:BOLD])
@bold_off = @am.changed_attribute_by_name([:BOLD], [])
@tt_on = @am.changed_attribute_by_name([], [:TT])
@tt_off = @am.changed_attribute_by_name([:TT], [])
@em_on = @am.changed_attribute_by_name([], [:EM])
@em_off = @am.changed_attribute_by_name([:EM], [])
@bold_em_on = @am.changed_attribute_by_name([], [:BOLD] | [:EM])
@bold_em_off = @am.changed_attribute_by_name([:BOLD] | [:EM], [])
@em_then_bold = @am.changed_attribute_by_name([:EM], [:EM] | [:BOLD])
@em_to_bold = @am.changed_attribute_by_name([:EM], [:BOLD])
@am.add_word_pair("{", "}", :WOMBAT)
@wombat_on = @am.changed_attribute_by_name([], [:WOMBAT])
@wombat_off = @am.changed_attribute_by_name([:WOMBAT], [])
end
def crossref(text)
[ @am.changed_attribute_by_name([], [:CROSSREF] | [:_SPECIAL_]),
SM::Special.new(33, text),
@am.changed_attribute_by_name([:CROSSREF] | [:_SPECIAL_], [])
]
end
def test_special
# class names, variable names, file names, or instance variables
@am.add_special(/(
\b([A-Z]\w+(::\w+)*)
| \#\w+[!?=]?
| \b\w+([_\/\.]+\w+)+[!?=]?
)/x,
:CROSSREF)
assert_equal(["cat"], @am.flow("cat"))
assert_equal(["cat ", crossref("#fred"), " dog"].flatten,
@am.flow("cat #fred dog"))
assert_equal([crossref("#fred"), " dog"].flatten,
@am.flow("#fred dog"))
assert_equal(["cat ", crossref("#fred")].flatten, @am.flow("cat #fred"))
end
def test_basic
assert_equal(["cat"], @am.flow("cat"))
assert_equal(["cat ", @bold_on, "and", @bold_off, " dog"],
@am.flow("cat *and* dog"))
assert_equal(["cat ", @bold_on, "AND", @bold_off, " dog"],
@am.flow("cat *AND* dog"))
assert_equal(["cat ", @em_on, "And", @em_off, " dog"],
@am.flow("cat _And_ dog"))
assert_equal(["cat *and dog*"], @am.flow("cat *and dog*"))
assert_equal(["*cat and* dog"], @am.flow("*cat and* dog"))
assert_equal(["cat *and ", @bold_on, "dog", @bold_off],
@am.flow("cat *and *dog*"))
assert_equal(["cat ", @em_on, "and", @em_off, " dog"],
@am.flow("cat _and_ dog"))
assert_equal(["cat_and_dog"],
@am.flow("cat_and_dog"))
assert_equal(["cat ", @tt_on, "and", @tt_off, " dog"],
@am.flow("cat +and+ dog"))
assert_equal(["cat ", @bold_on, "a_b_c", @bold_off, " dog"],
@am.flow("cat *a_b_c* dog"))
assert_equal(["cat __ dog"],
@am.flow("cat __ dog"))
assert_equal(["cat ", @em_on, "_", @em_off, " dog"],
@am.flow("cat ___ dog"))
end
def test_combined
assert_equal(["cat ", @em_on, "and", @em_off, " ", @bold_on, "dog", @bold_off],
@am.flow("cat _and_ *dog*"))
assert_equal(["cat ", @em_on, "a__nd", @em_off, " ", @bold_on, "dog", @bold_off],
@am.flow("cat _a__nd_ *dog*"))
end
def test_html_like
assert_equal(["cat ", @tt_on, "dog", @tt_off], @am.flow("cat <tt>dog</Tt>"))
assert_equal(["cat ", @em_on, "and", @em_off, " ", @bold_on, "dog", @bold_off],
@am.flow("cat <i>and</i> <B>dog</b>"))
assert_equal(["cat ", @em_on, "and ", @em_then_bold, "dog", @bold_em_off],
@am.flow("cat <i>and <B>dog</B></I>"))
assert_equal(["cat ", @em_on, "and ", @em_to_bold, "dog", @bold_off],
@am.flow("cat <i>and </i><b>dog</b>"))
assert_equal(["cat ", @em_on, "and ", @em_to_bold, "dog", @bold_off],
@am.flow("cat <i>and <b></i>dog</b>"))
assert_equal([@tt_on, "cat", @tt_off, " ", @em_on, "and ", @em_to_bold, "dog", @bold_off],
@am.flow("<tt>cat</tt> <i>and <b></i>dog</b>"))
assert_equal(["cat ", @em_on, "and ", @em_then_bold, "dog", @bold_em_off],
@am.flow("cat <i>and <b>dog</b></i>"))
assert_equal(["cat ", @bold_em_on, "and", @bold_em_off, " dog"],
@am.flow("cat <i><b>and</b></i> dog"))
end
def test_protect
assert_equal(['cat \\ dog'], @am.flow('cat \\ dog'))
assert_equal(["cat <tt>dog</Tt>"], @am.flow("cat \\<tt>dog</Tt>"))
assert_equal(["cat ", @em_on, "and", @em_off, " <B>dog</b>"],
@am.flow("cat <i>and</i> \\<B>dog</b>"))
assert_equal(["*word* or <b>text</b>"], @am.flow("\\*word* or \\<b>text</b>"))
end
def test_adding
assert_equal(["cat ", @wombat_on, "and", @wombat_off, " dog" ],
@am.flow("cat {and} dog"))
# assert_equal(["cat {and} dog" ], @am.flow("cat \\{and} dog"))
end
end

View file

@ -0,0 +1,503 @@
require 'test/unit'
$:.unshift "../../.."
require 'rdoc/markup/simple_markup'
include SM
class TestParse < Test::Unit::TestCase
class MockOutput
def start_accepting
@res = []
end
def end_accepting
@res
end
def accept_paragraph(am, fragment)
@res << fragment.to_s
end
def accept_verbatim(am, fragment)
@res << fragment.to_s
end
def accept_list_start(am, fragment)
@res << fragment.to_s
end
def accept_list_end(am, fragment)
@res << fragment.to_s
end
def accept_list_item(am, fragment)
@res << fragment.to_s
end
def accept_blank_line(am, fragment)
@res << fragment.to_s
end
def accept_heading(am, fragment)
@res << fragment.to_s
end
def accept_rule(am, fragment)
@res << fragment.to_s
end
end
def basic_conv(str)
sm = SimpleMarkup.new
mock = MockOutput.new
sm.convert(str, mock)
sm.content
end
def line_types(str, expected)
p = SimpleMarkup.new
mock = MockOutput.new
p.convert(str, mock)
assert_equal(expected, p.get_line_types.map{|type| type.to_s[0,1]}.join(''))
end
def line_groups(str, expected)
p = SimpleMarkup.new
mock = MockOutput.new
block = p.convert(str, mock)
if block != expected
rows = (0...([expected.size, block.size].max)).collect{|i|
[expected[i]||"nil", block[i]||"nil"]
}
printf "\n\n%35s %35s\n", "Expected", "Got"
rows.each {|e,g| printf "%35s %35s\n", e.dump, g.dump }
end
assert_equal(expected, block)
end
def test_tabs
str = "hello\n dave"
assert_equal(str, basic_conv(str))
str = "hello\n\tdave"
assert_equal("hello\n dave", basic_conv(str))
str = "hello\n \tdave"
assert_equal("hello\n dave", basic_conv(str))
str = "hello\n \tdave"
assert_equal("hello\n dave", basic_conv(str))
str = "hello\n \tdave"
assert_equal("hello\n dave", basic_conv(str))
str = "hello\n \tdave"
assert_equal("hello\n dave", basic_conv(str))
str = "hello\n \tdave"
assert_equal("hello\n dave", basic_conv(str))
str = "hello\n \tdave"
assert_equal("hello\n dave", basic_conv(str))
str = "hello\n \tdave"
assert_equal("hello\n dave", basic_conv(str))
str = "hello\n \tdave"
assert_equal("hello\n dave", basic_conv(str))
str = ".\t\t."
assert_equal(". .", basic_conv(str))
end
def test_whitespace
assert_equal("hello", basic_conv("hello"))
assert_equal("hello", basic_conv(" hello "))
assert_equal("hello", basic_conv(" \t \t hello\t\t"))
assert_equal("1\n 2\n 3", basic_conv("1\n 2\n 3"))
assert_equal("1\n 2\n 3", basic_conv(" 1\n 2\n 3"))
assert_equal("1\n 2\n 3\n1\n 2", basic_conv("1\n 2\n 3\n1\n 2"))
assert_equal("1\n 2\n 3\n1\n 2", basic_conv(" 1\n 2\n 3\n 1\n 2"))
assert_equal("1\n 2\n\n 3", basic_conv(" 1\n 2\n\n 3"))
end
def test_types
str = "now is the time"
line_types(str, 'P')
str = "now is the time\nfor all good men"
line_types(str, 'PP')
str = "now is the time\n code\nfor all good men"
line_types(str, 'PVP')
str = "now is the time\n code\n more code\nfor all good men"
line_types(str, 'PVVP')
str = "now is\n---\nthe time"
line_types(str, 'PRP')
str = %{\
now is
* l1
* l2
the time}
line_types(str, 'PLLP')
str = %{\
now is
* l1
l1+
* l2
the time}
line_types(str, 'PLPLP')
str = %{\
now is
* l1
* l1.1
* l2
the time}
line_types(str, 'PLLLP')
str = %{\
now is
* l1
* l1.1
text
code
code
text
* l2
the time}
line_types(str, 'PLLPVVBPLP')
str = %{\
now is
1. l1
* l1.1
2. l2
the time}
line_types(str, 'PLLLP')
str = %{\
now is
[cat] l1
* l1.1
[dog] l2
the time}
line_types(str, 'PLLLP')
str = %{\
now is
[cat] l1
continuation
[dog] l2
the time}
line_types(str, 'PLPLP')
end
def test_groups
str = "now is the time"
line_groups(str, ["L0: Paragraph\nnow is the time"] )
str = "now is the time\nfor all good men"
line_groups(str, ["L0: Paragraph\nnow is the time for all good men"] )
str = %{\
now is the time
code _line_ here
for all good men}
line_groups(str,
[ "L0: Paragraph\nnow is the time",
"L0: Verbatim\n code _line_ here\n",
"L0: Paragraph\nfor all good men"
] )
str = "now is the time\n code\n more code\nfor all good men"
line_groups(str,
[ "L0: Paragraph\nnow is the time",
"L0: Verbatim\n code\n more code\n",
"L0: Paragraph\nfor all good men"
] )
str = %{\
now is
* l1
* l2
the time}
line_groups(str,
[ "L0: Paragraph\nnow is",
"L1: ListStart\n",
"L1: ListItem\nl1",
"L1: ListItem\nl2",
"L1: ListEnd\n",
"L0: Paragraph\nthe time"
])
str = %{\
now is
* l1
l1+
* l2
the time}
line_groups(str,
[ "L0: Paragraph\nnow is",
"L1: ListStart\n",
"L1: ListItem\nl1 l1+",
"L1: ListItem\nl2",
"L1: ListEnd\n",
"L0: Paragraph\nthe time"
])
str = %{\
now is
* l1
* l1.1
* l2
the time}
line_groups(str,
[ "L0: Paragraph\nnow is",
"L1: ListStart\n",
"L1: ListItem\nl1",
"L2: ListStart\n",
"L2: ListItem\nl1.1",
"L2: ListEnd\n",
"L1: ListItem\nl2",
"L1: ListEnd\n",
"L0: Paragraph\nthe time"
])
str = %{\
now is
* l1
* l1.1
text
code
code
text
* l2
the time}
line_groups(str,
[ "L0: Paragraph\nnow is",
"L1: ListStart\n",
"L1: ListItem\nl1",
"L2: ListStart\n",
"L2: ListItem\nl1.1 text",
"L2: Verbatim\n code\n code\n",
"L2: Paragraph\ntext",
"L2: ListEnd\n",
"L1: ListItem\nl2",
"L1: ListEnd\n",
"L0: Paragraph\nthe time"
])
str = %{\
now is
1. l1
* l1.1
2. l2
the time}
line_groups(str,
[ "L0: Paragraph\nnow is",
"L1: ListStart\n",
"L1: ListItem\nl1",
"L2: ListStart\n",
"L2: ListItem\nl1.1",
"L2: ListEnd\n",
"L1: ListItem\nl2",
"L1: ListEnd\n",
"L0: Paragraph\nthe time"
])
str = %{\
now is
[cat] l1
* l1.1
[dog] l2
the time}
line_groups(str,
[ "L0: Paragraph\nnow is",
"L1: ListStart\n",
"L1: ListItem\nl1",
"L2: ListStart\n",
"L2: ListItem\nl1.1",
"L2: ListEnd\n",
"L1: ListItem\nl2",
"L1: ListEnd\n",
"L0: Paragraph\nthe time"
])
str = %{\
now is
[cat] l1
continuation
[dog] l2
the time}
line_groups(str,
[ "L0: Paragraph\nnow is",
"L1: ListStart\n",
"L1: ListItem\nl1 continuation",
"L1: ListItem\nl2",
"L1: ListEnd\n",
"L0: Paragraph\nthe time"
])
end
def test_verbatim_merge
str = %{\
now is
code
the time}
line_groups(str,
[ "L0: Paragraph\nnow is",
"L0: Verbatim\n code\n",
"L0: Paragraph\nthe time"
])
str = %{\
now is
code
code1
the time}
line_groups(str,
[ "L0: Paragraph\nnow is",
"L0: Verbatim\n code\n code1\n",
"L0: Paragraph\nthe time"
])
str = %{\
now is
code
code1
the time}
line_groups(str,
[ "L0: Paragraph\nnow is",
"L0: Verbatim\n code\n\n code1\n",
"L0: Paragraph\nthe time"
])
str = %{\
now is
code
code1
the time}
line_groups(str,
[ "L0: Paragraph\nnow is",
"L0: Verbatim\n code\n\n code1\n",
"L0: Paragraph\nthe time"
])
str = %{\
now is
code
code1
code2
the time}
line_groups(str,
[ "L0: Paragraph\nnow is",
"L0: Verbatim\n code\n\n code1\n\n code2\n",
"L0: Paragraph\nthe time"
])
# Folds multiple blank lines
str = %{\
now is
code
code1
the time}
line_groups(str,
[ "L0: Paragraph\nnow is",
"L0: Verbatim\n code\n\n code1\n",
"L0: Paragraph\nthe time"
])
end
def test_list_split
str = %{\
now is
* l1
1. n1
2. n2
* l2
the time}
line_groups(str,
[ "L0: Paragraph\nnow is",
"L1: ListStart\n",
"L1: ListItem\nl1",
"L1: ListEnd\n",
"L1: ListStart\n",
"L1: ListItem\nn1",
"L1: ListItem\nn2",
"L1: ListEnd\n",
"L1: ListStart\n",
"L1: ListItem\nl2",
"L1: ListEnd\n",
"L0: Paragraph\nthe time"
])
end
def test_headings
str = "= heading one"
line_groups(str,
[ "L0: Heading\nheading one"
])
str = "=== heading three"
line_groups(str,
[ "L0: Heading\nheading three"
])
str = "text\n === heading three"
line_groups(str,
[ "L0: Paragraph\ntext",
"L0: Verbatim\n === heading three\n"
])
str = "text\n code\n === heading three"
line_groups(str,
[ "L0: Paragraph\ntext",
"L0: Verbatim\n code\n === heading three\n"
])
str = "text\n code\n=== heading three"
line_groups(str,
[ "L0: Paragraph\ntext",
"L0: Verbatim\n code\n",
"L0: Heading\nheading three"
])
end
end

526
lib/rdoc/options.rb Normal file
View file

@ -0,0 +1,526 @@
# We handle the parsing of options, and subsequently as a singleton
# object to be queried for option values
class Options
require 'singleton'
require 'getoptlong'
include Singleton
# files matching this pattern will be excluded
attr_accessor :exclude
# the name of the output directory
attr_accessor :op_dir
# the name to use for the output
attr_reader :op_name
# include private and protected methods in the
# output
attr_accessor :show_all
# 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
# Don't display progress as we process the files
attr_reader :quiet
# description of the output generator (set with the <tt>-fmt</tt>
# option
attr_accessor :generator
# and the list of files to be processed
attr_reader :files
# array of directories to search for files to satisfy an :include:
attr_reader :rdoc_include
# title to be used out the output
#attr_writer :title
# template to be used when generating output
attr_reader :template
# should diagrams be drawn
attr_reader :diagram
# should we draw fileboxes in diagrams
attr_reader :fileboxes
# include the '#' at the front of hyperlinked instance method names
attr_reader :show_hash
# image format for diagrams
attr_reader :image_format
# character-set
attr_reader :charset
# should source code be included inline, or displayed in a popup
attr_reader :inline_source
# should the output be placed into a single file
attr_reader :all_one_file
# the number of columns in a tab
attr_reader :tab_width
# include line numbers in the source listings
attr_reader :include_line_numbers
# pattern for additional attr_... style methods
attr_reader :extra_accessors
attr_reader :extra_accessor_flags
# URL of stylesheet
attr_reader :css
# URL of web cvs frontend
attr_reader :webcvs
# Are we promiscuous about showing module contents across
# multiple files
attr_reader :promiscuous
module OptionList
OPTION_LIST = [
[ "--accessor", "-A", "accessorname[,..]",
"comma separated list of additional class methods\n" +
"that should be treated like 'attr_reader' and\n" +
"friends. Option may be repeated. Each accessorname\n" +
"may have '=text' appended, in which case that text\n" +
"appears where the r/w/rw appears for normal accessors."],
[ "--all", "-a", nil,
"include all methods (not just public)\nin the output" ],
[ "--charset", "-c", "charset",
"specifies HTML character-set" ],
[ "--debug", "-D", nil,
"displays lots on internal stuff" ],
[ "--diagram", "-d", nil,
"Generate diagrams showing modules and classes.\n" +
"You need dot V1.8.6 or later to use the --diagram\n" +
"option correctly. Dot is available from\n"+
"http://www.research.att.com/sw/tools/graphviz/" ],
[ "--exclude", "-x", "pattern",
"do not process files or directories matching\n" +
"pattern. Files given explicitly on the command\n" +
"line will never be excluded." ],
[ "--extension", "-E", "new=old",
"Treat files ending with .new as if they ended with\n" +
".old. Using '-E cgi=rb' will cause xxx.cgi to be\n" +
"parsed as a Ruby file"],
[ "--fileboxes", "-F", nil,
"classes are put in boxes which represents\n" +
"files, where these classes reside. Classes\n" +
"shared between more than one file are\n" +
"shown with list of files that sharing them.\n" +
"Silently discarded if --diagram is not given\n" +
"Experimental." ],
[ "--fmt", "-f", "format name",
"set the output formatter (see below)" ],
[ "--help", "-h", nil,
"you're looking at it" ],
[ "--help-output", "-O", nil,
"explain the various output options" ],
[ "--image-format", "-I", "gif/png/jpg/jpeg",
"Sets output image format for diagrams. Can\n" +
"be png, gif, jpeg, jpg. If this option is\n" +
"omitted, png is used. Requires --diagram." ],
[ "--include", "-i", "dir[,dir...]",
"set (or add to) the list of directories\n" +
"to be searched when satisfying :include:\n" +
"requests. Can be used more than once." ],
[ "--inline-source", "-S", nil,
"Show method source code inline, rather\n" +
"than via a popup link" ],
[ "--line-numbers", "-N", nil,
"Include line numbers in the source code" ],
[ "--main", "-m", "name",
"'name' will be the initial page displayed" ],
[ "--one-file", "-1", nil,
"put all the output into a single file" ],
[ "--op", "-o", "dir",
"set the output directory" ],
[ "--opname", "-n", "name",
"Set the 'name' of the output. Has no\n" +
"effect for HTML." ],
[ "--promiscuous", "-p", nil,
"When documenting a file that contains a module\n" +
"or class also defined in other files, show\n" +
"all stuff for that module/class in each files\n" +
"page. By default, only show stuff defined in\n" +
"that particular file." ],
[ "--quiet", "-q", nil,
"don't show progress as we parse" ],
[ "--show-hash", "-H", nil,
"A name of the form #name in a comment\n" +
"is a possible hyperlink to an instance\n" +
"method name. When displayed, the '#' is\n" +
"removed unless this option is specified" ],
[ "--style", "-s", "stylesheet url",
"specifies the URL of a separate stylesheet." ],
[ "--tab-width", "-w", "n",
"Set the width of tab characters (default 8)"],
[ "--template", "-T", "template name",
"Set the template used when generating output" ],
[ "--title", "-t", "text",
"Set 'txt' as the title for the output" ],
[ "--version", "-v", nil,
"display RDoc's version" ],
[ "--webcvs", "-W", "url",
"Specify a URL for linking to a web frontend\n" +
"to CVS. If the URL contains a '\%s', the\n" +
"name of the current file will be substituted;\n" +
"if the URL doesn't contain a '\%s', the\n" +
"filename will be appended to it." ],
]
def OptionList.options
OPTION_LIST.map do |long, short, arg,|
[ long,
short,
arg ? GetoptLong::REQUIRED_ARGUMENT : GetoptLong::NO_ARGUMENT
]
end
end
def OptionList.strip_output(text)
text =~ /^\s+/
leading_spaces = $&
text.gsub!(/^#{leading_spaces}/, '')
$stdout.puts text
end
# Show an error and exit
def OptionList.error(msg)
$stderr.puts
$stderr.puts msg
$stderr.puts "\nFor help on options, try 'rdoc --help'\n\n"
exit 1
end
# Show usage and exit
def OptionList.usage(generator_names)
puts
puts(VERSION_STRING)
puts
name = File.basename($0)
OptionList.strip_output(<<-EOT)
Usage:
#{name} [options] [names...]
Files are parsed, and the information they contain
collected, before any output is produced. This allows cross
references between all files to be resolved. If a name is a
directory, it is traversed. If no names are specified, all
Ruby files in the current directory (and subdirectories) are
processed.
Options:
EOT
OPTION_LIST.each do |long, short, arg, desc|
opt = sprintf("%20s", "#{long}, #{short}")
oparg = sprintf("%-7s", arg)
print "#{opt} #{oparg}"
desc = desc.split("\n")
if arg.nil? || arg.length < 7
puts desc.shift
else
puts
end
desc.each do |line|
puts(" "*28 + line)
end
puts
end
puts "\nAvailable output formatters: " +
generator_names.sort.join(', ') + "\n\n"
puts "For information on where the output goes, use\n\n"
puts " rdoc --help-output\n\n"
exit 0
end
def OptionList.help_output
OptionList.strip_output(<<-EOT)
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).
- 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.
- .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.
For information on other RDoc options, use "rdoc --help".
EOT
exit 0
end
end
# Parse command line options. We're passed a hash containing
# output generators, keyed by the generator name
def parse(argv, generators)
old_argv = ARGV.dup
begin
ARGV.replace(argv)
@op_dir = "doc"
@op_name = nil
@show_all = false
@main_page = nil
@exclude = nil
@quiet = false
@generator_name = 'html'
@generator = generators[@generator_name]
@rdoc_include = []
@title = nil
@template = 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
@css = nil
@webcvs = nil
@charset = case $KCODE
when /^S/i
'Shift_JIS'
when /^E/i
'EUC-JP'
else
'iso-8859-1'
end
accessors = []
go = GetoptLong.new(*OptionList.options)
go.quiet = true
go.each do |opt, arg|
case opt
when "--all" then @show_all = true
when "--charset" then @charset = arg
when "--debug" then $DEBUG = true
when "--exclude" then @exclude = Regexp.new(arg)
when "--inline-source" then @inline_source = true
when "--line-numbers" then @include_line_numbers = true
when "--main" then @main_page = arg
when "--one-file" then @all_one_file = true
when "--op" then @op_dir = arg
when "--opname" then @op_name = arg
when "--promiscuous" then @promiscuous = true
when "--quiet" then @quiet = true
when "--show-hash" then @show_hash = true
when "--style" then @css = arg
when "--template" then @template = arg
when "--title" then @title = arg
when "--webcvs" then @webcvs = arg
when "--accessor"
arg.split(/,/).each do |accessor|
if accessor =~ /^(\w+)(=(.*))?$/
accessors << $1
@extra_accessor_flags[$1] = $3
end
end
when "--diagram"
check_diagram
@diagram = true
when "--fileboxes"
@fileboxes = true if @diagram
when "--fmt"
@generator_name = arg.downcase
@generator = generators[@generator_name]
if !@generator
OptionList.error("Invalid output formatter")
end
if @generator_name == "xml"
@all_one_file = true
@inline_source = true
end
when "--help"
OptionList.usage(generators.keys)
when "--help-output"
OptionList.help_output
when "--image-format"
if ['gif', 'png', 'jpeg', 'jpg'].include?(arg)
@image_format = arg
else
raise GetoptLong::InvalidOption.new("unknown image format: #{arg}")
end
when "--include"
@rdoc_include.concat arg.split(/\s*,\s*/)
when "--tab-width"
begin
@tab_width = Integer(arg)
rescue
$stderr.puts "Invalid tab width: '#{arg}'"
exit 1
end
when "--extension"
new, old = arg.split(/=/, 2)
OptionList.error("Invalid parameter to '-E'") unless new && old
unless RDoc::ParserFactory.alias_extension(old, new)
OptionList.error("Unknown extension .#{old} to -E")
end
when "--version"
puts VERSION_STRING
exit
end
end
@files = ARGV.dup
@rdoc_include << "." if @rdoc_include.empty?
check_files
# If no template was specified, use the default
# template for the output formatter
@template ||= @generator_name
# Generate a regexp from the accessors
unless accessors.empty?
re = '^(' + accessors.map{|a| Regexp.quote(a)}.join('|') + ')$'
@extra_accessors = Regexp.new(re)
end
rescue GetoptLong::InvalidOption, GetoptLong::MissingArgument => error
OptionList.error(error.message)
ensure
ARGV.replace(old_argv)
end
end
def title
@title ||= "RDoc Documentation"
end
# Set the title, but only if not already set. This means that a title set from
# the command line trumps one set in a source file
def title=(string)
@title ||= string
end
private
# Check that the right version of 'dot' is available.
# Unfortuately this doesn't work correctly under Windows NT,
# so we'll bypass the test under Windows
def check_diagram
return if RUBY_PLATFORM =~ /win/
ok = false
ver = nil
IO.popen("dot -V 2>&1") do |io|
ver = io.read
if ver =~ /dot\s+version(?:\s+gviz)?\s+(\d+)\.(\d+)/
ok = ($1.to_i > 1) || ($1.to_i == 1 && $2.to_i >= 8)
end
end
unless ok
if ver =~ /^dot version/
$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
# exit
end
end
# Check that the files on the command line exist
def check_files
@files.each do |f|
stat = File.stat f rescue error("File not found: #{f}")
error("File '#{f}' not readable") unless stat.readable?
end
end
def error(str)
$stderr.puts str
exit(1)
end
end

287
lib/rdoc/parsers/parse_c.rb Normal file
View file

@ -0,0 +1,287 @@
# We attempt to parse C extension files. Basically we look for
# the standard patterns that you find in extensions: <tt>rb_define_class,
# rb_define_method</tt> and so on. We also try to find the corresponding
# C source for the methods and extract comments, but if we fail
# we don't worry too much.
#
# The comments associated with a Ruby method are extracted from the C
# comment block associated with the routine that _implements_ that
# 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;
# }
#
# ...
#
# 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
# in the method rb_ary_flatten. It will then use the comment from that
# method in the HTML output. This method must be in the same source file
# as the rb_define_method.
#
# C classes can be diagramed (see /tc/dl/ruby/ruby/error.c), and RDoc
# integrates C and Ruby source into one tree
# Classes and modules built in to the interpreter. We need
# these to define superclasses of user objects
require "rdoc/code_objects"
require "rdoc/parsers/parserfactory"
module RDoc
KNOWN_CLASSES = {
"rb_cObject" => "Object",
"rb_cArray" => "Array",
"rb_cBignum" => "Bignum",
"rb_cClass" => "Class",
"rb_cDir" => "Dir",
"rb_cData" => "Data",
"rb_cFalseClass" => "FalseClass",
"rb_cFile" => "File",
"rb_cFixnum" => "Fixnum",
"rb_cFloat" => "Float",
"rb_cHash" => "Hash",
"rb_cInteger" => "Integer",
"rb_cIO" => "IO",
"rb_cModule" => "Module",
"rb_cNilClass" => "NilClass",
"rb_cNumeric" => "Numeric",
"rb_cProc" => "Proc",
"rb_cRange" => "Range",
"rb_cRegexp" => "Regexp",
"rb_cString" => "String",
"rb_cSymbol" => "Symbol",
"rb_cThread" => "Thread",
"rb_cTime" => "Time",
"rb_cTrueClass" => "TrueClass",
"rb_cStruct" => "Struct",
"rb_eException" => "Exception",
"rb_eStandardError" => "StandardError",
"rb_eSystemExit" => "SystemExit",
"rb_eInterrupt" => "Interrupt",
"rb_eSignal" => "Signal",
"rb_eFatal" => "Fatal",
"rb_eArgError" => "ArgError",
"rb_eEOFError" => "EOFError",
"rb_eIndexError" => "IndexError",
"rb_eRangeError" => "RangeError",
"rb_eIOError" => "IOError",
"rb_eRuntimeError" => "RuntimeError",
"rb_eSecurityError" => "SecurityError",
"rb_eSystemCallError" => "SystemCallError",
"rb_eTypeError" => "TypeError",
"rb_eZeroDivError" => "ZeroDivError",
"rb_eNotImpError" => "NotImpError",
"rb_eNoMemError" => "NoMemError",
"rb_eFloatDomainError" => "FloatDomainError",
"rb_eScriptError" => "ScriptError",
"rb_eNameError" => "NameError",
"rb_eSyntaxError" => "SyntaxError",
"rb_eLoadError" => "LoadError",
"rb_mKernel" => "Kernel",
"rb_mComparable" => "Comparable",
"rb_mEnumerable" => "Enumerable",
"rb_mPrecision" => "Precision",
"rb_mErrno" => "Errno",
"rb_mFileTest" => "FileTest",
"rb_mGC" => "GC",
"rb_mMath" => "Math",
"rb_mProcess" => "Process"
}
# See rdoc/c_parse.rb
class C_Parser
extend ParserFactory
parse_files_matching(/\.(c|cc|cpp|CC)$/)
# prepare to parse a C file
def initialize(top_level, file_name, body, options)
@known_classes = KNOWN_CLASSES.dup
@body = body
@options = options
@top_level = top_level
@classes = Hash.new
end
# Extract the classes/modules and methods from a C file
# and return the corresponding top-level object
def scan
remove_commented_out_lines
do_classes
do_methods
@top_level
end
#######
private
#######
# remove lines that are commented out that might otherwise get
# picked up when scanning for classes and methods
def remove_commented_out_lines
@body.gsub!(%r{//.*rb_define_}, '//')
end
def handle_class_module(var_name, class_mod, class_name, parent, in_module)
@known_classes[var_name] = class_name
parent_name = @known_classes[parent] || parent
if in_module
enclosure = @classes[in_module]
unless enclosure
$stderr.puts("Enclosing class/module '#{in_module}' for " +
class_mod + " #{class_name} not known")
return
end
else
enclosure = @top_level
end
if class_mod == "class"
cm = enclosure.add_class(NormalClass, class_name, parent_name)
else
cm = enclosure.add_module(NormalModule, class_name)
end
cm.record_location(enclosure.toplevel)
@classes[var_name] = cm
end
def do_classes
@body.scan(/(\w+)\s* = \s*rb_define_module\(\s*"(\w+)"\s*\)/mx) do
|var_name, class_name|
handle_class_module(var_name, "module", class_name, nil, nil)
end
@body.scan(/(\w+)\s* = \s*rb_define_module_under
\(
\s*(\w+),
\s*"(\w+)"
\)/mx) do
|var_name, in_module, class_name|
handle_class_module(var_name, "module", class_name, nil, in_module)
end
@body.scan(/(\w+)\s* = \s*rb_define_class
\(
\s*"(\w+)",
\s*(\w+)\s*
\)/mx) do
|var_name, class_name, parent|
handle_class_module(var_name, "class", class_name, parent, nil)
end
@body.scan(/(\w+)\s* = \s*rb_define_class_under
\(
\s*(\w+),
\s*"(\w+)",
\s*(\w+)\s*
\)/mx) do
|var_name, in_module, class_name, parent|
handle_class_module(var_name, "class", class_name, parent, in_module)
end
end
def do_methods
@body.scan(/rb_define_(singleton_method|method|module_function)\(\s*(\w+),
\s*"([^"]+)",
\s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?,
\s*(-?\w+)\s*\)/xm) do
|type, var_name, meth_name, meth_body, param_count|
class_name = @known_classes[var_name] || var_name
class_obj = @classes[var_name]
if class_obj
meth_obj = AnyMethod.new("", meth_name)
meth_obj.singleton = type == "singleton_method"
p_count = (Integer(param_count) rescue -1)
if p_count < 0
meth_obj.params = "(...)"
elsif p_count == 0
meth_obj.params = "()"
else
meth_obj.params = "(" +
(1..p_count).map{|i| "p#{i}"}.join(", ") +
")"
end
find_body(meth_body, meth_obj)
class_obj.add_method(meth_obj)
end
end
end
# Find the C code corresponding to a c method
def find_body(meth_name, meth_obj)
if @body =~ %r{((?>/\*.*?\*/\s+))(static\s+)?VALUE\s+#{meth_name}
\s*(\(.*?\)).*?^}xm
comment, params = $1, $3
body_text = $&
# see if we can find the whole body
re = Regexp.escape(body_text) + "[^(]*^{.*?^}"
if Regexp.new(re, Regexp::MULTILINE).match(@body)
body_text = $&
end
meth_obj.params = params
meth_obj.start_collecting_tokens
meth_obj.add_token(RubyToken::Token.new(1,1).set_text(body_text))
meth_obj.comment = mangle_comment(comment)
end
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
end
end

View file

@ -0,0 +1,118 @@
# Parse a Fortran 95 file.
require "rdoc/code_objects"
module RDoc
# See rdoc/parsers/parse_f95.rb
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
class Fortran95parser
extend ParserFactory
parse_files_matching(/\.(f9(0|5)|F)$/)
# prepare to parse a Fortran 95 file
def initialize(top_level, file_name, body, options)
@body = body
@options = options
@top_level = top_level
@progress = $stderr unless options.quiet
end
# devine code constructs
def scan
# modules and programs
if @body =~ /^(module|program)\s+(\w+)/i
progress "m"
f9x_module = @top_level.add_module NormalClass, $2
f9x_module.record_location @top_level
first_comment, second_comment = $`.gsub(/^!\s?/,"").split "\n\s*\n"
if second_comment
@top_level.comment = first_comment if first_comment
f9x_module.comment = second_comment
else
f9x_module.comment = first_comment if first_comment
end
end
# use modules
remaining_code = @body
while remaining_code =~ /^\s*use\s+(\w+)/i
remaining_code = $~.post_match
progress "."
f9x_module.add_include Include.new($1, "") if f9x_module
end
# subroutines
remaining_code = @body
while remaining_code =~ /^\s*subroutine\s+(\w+)\s*\((.*?)\)/im
remaining_code = $~.post_match
subroutine = AnyMethod.new("Text", $1)
subroutine.singleton = false
prematchText = $~.pre_match
params = $2
params.gsub!(/&/,'')
subroutine.params = params
comment = find_comments prematchText
subroutine.comment = comment if comment
subroutine.start_collecting_tokens
remaining_code =~ /^\s*end\s+subroutine/i
code = "subroutine #{subroutine.name} (#{subroutine.params})\n"
code += $~.pre_match
code += "\nend subroutine\n"
subroutine.add_token Token.new(1,1).set_text(code)
progress "s"
f9x_module.add_method subroutine if f9x_module
end
@top_level
end
def find_comments text
lines = text.split("\n").reverse
comment_block = Array.new
lines.each do |line|
break if line =~ /^\s*\w/
comment_block.unshift line.sub(/^!\s?/,"")
end
nice_lines = comment_block.join("\n").split "\n\s*\n"
nice_lines.shift
nice_lines.shift
nice_lines.shift
end
def progress(char)
unless @options.quiet
@progress.print(char)
@progress.flush
end
end
end # class Fortran95parser
end # module RDoc

2494
lib/rdoc/parsers/parse_rb.rb Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,37 @@
# Parse a non-source file. We basically take the whole thing
# as one big comment. If the first character in the file
# is '#', we strip leading pound signs.
require "rdoc/code_objects"
require "rdoc/markup/simple_markup/preprocess"
module RDoc
# See rdoc/parsers/parse_c.rb
class SimpleParser
# prepare to parse a plain file
def initialize(top_level, file_name, body, options)
preprocess = SM::PreProcess.new(file_name, options.rdoc_include)
preprocess.handle(body) do |directive, param|
$stderr.puts "Unrecognized directive '#{directive}' in #{file_name}"
end
@body = body
@options = options
@top_level = top_level
end
# Extract the file contents and attach them to the toplevel as a
# comment
def scan
# @body.gsub(/^(\s\n)+/, '')
@top_level.comment = @body
@top_level
end
end
end

View file

@ -0,0 +1,86 @@
require "rdoc/parsers/parse_simple"
module RDoc
# A parser is simple a class that implements
#
# #initialize(file_name, body, options)
#
# and
#
# #scan
#
# The initialize method takes a file name to be used, the body of the
# file, and an RDoc::Options object. The scan method is then called
# to return an appropriately parsed TopLevel code object.
# The ParseFactory is used to redirect to the correct parser given a filename
# extension. This magic works because individual parsers have to register
# themselves with us as they are loaded in. The do this using the following
# incantation
#
#
# require "rdoc/parsers/parsefactory"
#
# module RDoc
#
# class XyzParser
# extend ParseFactory <<<<
# parse_files_matching /\.xyz$/ <<<<
#
# def initialize(file_name, body, options)
# ...
# end
#
# def scan
# ...
# end
# end
# end
module ParserFactory
@@parsers = []
Parsers = Struct.new(:regexp, :parser)
# Record the fact that a particular class parses files that
# match a given extension
def parse_files_matching(regexp)
@@parsers.unshift Parsers.new(regexp, self)
end
# Return a parser that can handle a particular extension
def ParserFactory.can_parse(file_name)
@@parsers.find {|p| p.regexp.match(file_name) }
end
# Alias an extension to another extension. After this call,
# files ending "new_ext" will be parsed using the same parser
# as "old_ext"
def ParserFactory.alias_extension(old_ext, new_ext)
parser = ParserFactory.can_parse("xxx.#{old_ext}")
return false unless parser
@@parsers.unshift Parsers.new(Regexp.new("\\.#{new_ext}$"), parser.parser)
true
end
# Find the correct parser for a particular file name. Return a
# SimpleParser for ones that we don't know
def ParserFactory.parser_for(top_level, file_name, body, options)
parser_description = can_parse(file_name)
if parser_description
parser = parser_description.parser
else
parser = SimpleParser
end
parser.new(top_level, file_name, body, options)
end
end
end

219
lib/rdoc/rdoc.rb Normal file
View file

@ -0,0 +1,219 @@
# See README.
#
RDOC_VERSION = "1.0pr1"
rcs = '$Date$ $Revision$'.
gsub(/\$/, '').
sub(/Date: /, ': ').
sub(/ Revision: (\S+)/) { "(#$1)" }
VERSION_STRING = %{RDoc V} + RDOC_VERSION + rcs
require 'rdoc/parsers/parse_rb.rb'
require 'rdoc/parsers/parse_c.rb'
require 'rdoc/parsers/parse_f95.rb'
require 'rdoc/parsers/parse_simple.rb'
require 'rdoc/options'
require 'rdoc/diagram'
require 'find'
require 'ftools'
# We put rdoc stuff in the RDoc module to avoid namespace
# clutter.
#
# ToDo: This isn't universally true.
module RDoc
# Exception thrown by any rdoc error. Only the #message part is
# of use externally.
class RDocError < Exception
end
# 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
##
# This is the list of output generators that we
# support
Generator = Struct.new(:file_name, :class_name, :key)
GENERATORS = {}
$:.collect {|d|
File::expand_path(d)
}.find_all {|d|
File::directory?("#{d}/rdoc/generators")
}.each {|dir|
Dir::entries("#{dir}/rdoc/generators").each {|gen|
next unless /(\w+)_generator.rb$/ =~ gen
type = $1
unless GENERATORS.has_key? type
GENERATORS[type] = Generator.new("rdoc/generators/#{gen}",
"#{type.upcase}Generator".intern,
type)
end
}
}
#######
private
#######
##
# Report an error message and exit
def error(msg)
raise RDocError.new(msg)
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
def setup_output_dir(op_dir)
flag_file = File.join(op_dir, "created.rid")
if File.exist?(op_dir)
unless File.directory?(op_dir)
error "'#{op_dir}' exists, and is not a directory"
end
unless File.file?(flag_file)
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"
end
else
File.makedirs(op_dir)
end
File.open(flag_file, "w") {|f| f.puts Time.now }
end
# Given a list of files and directories, create a list
# of all the Ruby files they contain.
def normalized_file_list(options, *relative_files)
file_list = []
relative_files.each do |rel_file_name|
case type = File.stat(rel_file_name).ftype
when "file"
file_list << rel_file_name
when "directory"
next if options.exclude && options.exclude =~ rel_file_name
Find.find(rel_file_name) do |fn|
next if options.exclude && options.exclude =~ fn
next unless ParserFactory.can_parse(fn)
next unless File.file?(fn)
file_list << fn.sub(%r{\./}, '')
end
else
raise RDocError.new("I can't deal with a #{type} #{rel_file_name}")
end
end
file_list
end
# Parse each file on the command line, recursively entering
# directories
def parse_files(options)
file_info = []
files = options.files
files = ["."] if files.empty?
file_list = normalized_file_list(options, *files)
file_list.each do |fn|
$stderr.printf("\n%35s: ", File.basename(fn)) unless options.quiet
content = File.open(fn, "r") {|f| f.read}
top_level = TopLevel.new(fn)
parser = ParserFactory.parser_for(top_level, fn, content, options)
file_info << parser.scan
end
file_info
end
public
###################################################################
#
# 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: RDocError on error
def document(argv)
TopLevel::reset
options = Options.instance
options.parse(argv, GENERATORS)
file_info = parse_files(options)
gen = options.generator
$stderr.puts "\nGenerating #{gen.key.upcase}..." unless options.quiet
require gen.file_name
gen_class = Generators.const_get(gen.class_name)
unless file_info.empty?
gen = gen_class.for(options)
pwd = Dir.pwd
unless options.all_one_file
setup_output_dir(options.op_dir)
Dir.chdir(options.op_dir)
end
begin
Diagram.new(file_info, options).draw if options.diagram
gen.generate(file_info)
ensure
Dir.chdir(pwd)
end
end
end
end
end

234
lib/rdoc/template.rb Normal file
View file

@ -0,0 +1,234 @@
# Cheap-n-cheerful HTML page template system. You create a
# template containing:
#
# * variable names between percent signs (<tt>%fred%</tt>)
# * blocks of repeating stuff:
#
# START:key
# ... stuff
# END:key
#
# You feed the code a hash. For simple variables, the values
# are resolved directly from the hash. For blocks, the hash entry
# corresponding to +key+ will be an array of hashes. The block will
# be generated once for each entry. Blocks can be nested arbitrarily
# deeply.
#
# The template may also contain
#
# IF:key
# ... stuff
# ENDIF:key
#
# _stuff_ will only be included in the output if the corresponding
# key is set in the value hash.
#
# Usage: Given a set of templates <tt>T1, T2,</tt> etc
#
# values = { "name" => "Dave", state => "TX" }
#
# t = TemplatePage.new(T1, T2, T3)
# File.open(name, "w") {|f| t.write_html_on(f, values)}
# or
# res = ''
# t.write_html_on(res, values)
#
#
class TemplatePage
##########
# A context holds a stack of key/value pairs (like a symbol
# table). When asked to resolve a key, it first searches the top of
# the stack, then the next level, and so on until it finds a match
# (or runs out of entries)
class Context
def initialize
@stack = []
end
def push(hash)
@stack.push(hash)
end
def pop
@stack.pop
end
# Find a scalar value, throwing an exception if not found. This
# method is used when substituting the %xxx% constructs
def find_scalar(key)
@stack.reverse_each do |level|
if val = level[key]
return val unless val.kind_of? Array
end
end
raise "Template error: can't find variable '#{key}'"
end
# Lookup any key in the stack of hashes
def lookup(key)
@stack.reverse_each do |level|
val = level[key]
return val if val
end
nil
end
end
#########
# Simple class to read lines out of a string
class LineReader
# we're initialized with an array of lines
def initialize(lines)
@lines = lines
end
# read the next line
def read
@lines.shift
end
# Return a list of lines up to the line that matches
# a pattern. That last line is discarded.
def read_up_to(pattern)
res = []
while line = read
if pattern.match(line)
return LineReader.new(res)
else
res << line
end
end
raise "Missing end tag in template: #{pattern.source}"
end
# Return a copy of ourselves that can be modified without
# affecting us
def dup
LineReader.new(@lines.dup)
end
end
# +templates+ is an array of strings containing the templates.
# We start at the first, and substitute in subsequent ones
# where the string <tt>!INCLUDE!</tt> occurs. For example,
# we could have the overall page template containing
#
# <html><body>
# <h1>Master</h1>
# !INCLUDE!
# </bost></html>
#
# and substitute subpages in to it by passing [master, sub_page].
# This gives us a cheap way of framing pages
def initialize(*templates)
result = "!INCLUDE!"
templates.each do |content|
result.sub!(/!INCLUDE!/, content)
end
@lines = LineReader.new(result.split($/))
end
# Render the templates into HTML, storing the result on +op+
# using the method <tt><<</tt>. The <tt>value_hash</tt> contains
# key/value pairs used to drive the substitution (as described above)
def write_html_on(op, value_hash)
@context = Context.new
op << substitute_into(@lines, value_hash).tr("\000", '\\')
end
# Substitute a set of key/value pairs into the given template.
# Keys with scalar values have them substituted directly into
# the page. Those with array values invoke <tt>substitute_array</tt>
# (below), which examples a block of the template once for each
# row in the array.
#
# This routine also copes with the <tt>IF:</tt>_key_ directive,
# removing chunks of the template if the corresponding key
# does not appear in the hash, and the START: directive, which
# loops its contents for each value in an array
def substitute_into(lines, values)
@context.push(values)
skip_to = nil
result = []
while line = lines.read
case line
when /^IF:(\w+)/
lines.read_up_to(/^ENDIF:#$1/) unless @context.lookup($1)
when /^IFNOT:(\w+)/
lines.read_up_to(/^ENDIF:#$1/) if @context.lookup($1)
when /^ENDIF:/
;
when /^START:(\w+)/
tag = $1
body = lines.read_up_to(/^END:#{tag}/)
inner_values = @context.lookup(tag)
raise "unknown tag: #{tag}" unless inner_values
raise "not array: #{tag}" unless inner_values.kind_of?(Array)
inner_values.each do |vals|
result << substitute_into(body.dup, vals)
end
else
result << expand_line(line.dup)
end
end
@context.pop
result.join("\n")
end
# Given an individual line, we look for %xxx% constructs and
# HREF:ref:name: constructs, substituting for each.
def expand_line(line)
# Generate a cross reference if a reference is given,
# otherwise just fill in the name part
line.gsub!(/HREF:(\w+?):(\w+?):/) {
ref = @context.lookup($1)
name = @context.find_scalar($2)
if ref and !ref.kind_of?(Array)
"<a href=\"#{ref}\">#{name}</a>"
else
name
end
}
# Substitute in values for %xxx% constructs. This is made complex
# because the replacement string may contain characters that are
# meaningful to the regexp (like \1)
line = line.gsub(/%([a-zA-Z]\w*)%/) {
val = @context.find_scalar($1)
val.tr('\\', "\000")
}
line
rescue Exception => e
$stderr.puts "Error in template: #{e}"
$stderr.puts "Original line: #{line}"
exit
end
end

25
lib/rdoc/tokenstream.rb Normal file
View file

@ -0,0 +1,25 @@
# A TokenStream is a list of tokens, gathered during the parse
# of some entity (say a method). Entities populate these streams
# by being registered with the lexer. Any class can collect tokens
# by including TokenStream. From the outside, you use such an object
# by calling the start_collecting_tokens method, followed by calls
# to add_token and pop_token
module TokenStream
def token_stream
@token_stream
end
def start_collecting_tokens
@token_stream = []
end
def add_token(tk)
@token_stream << tk
end
def add_tokens(tks)
tks.each {|tk| add_token(tk)}
end
def pop_token
@token_stream.pop
end
end