1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/lib/rdoc/generators/html_generator.rb
dave a80ba17d67 1. Force --inline-source if --one-file given
2. Add new :section: directive which starts a new section in the output.
   The title following :section: is used as the section heading, and the
   remainder of the comment containing the section is used as
   introductory text. Subsequent methods, aliases, attributes,
   and classes will be documented in this section.


git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@7344 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2004-11-20 15:02:57 +00:00

1502 lines
38 KiB
Ruby

# We're responsible for generating all the HTML files
# from the object tree defined in code_objects.rb. We
# generate:
#
# [files] an html file for each input file given. These
# input files appear as objects of class
# TopLevel
#
# [classes] an html file for each class or module encountered.
# These classes are not grouped by file: if a file
# contains four classes, we'll generate an html
# file for the file itself, and four html files
# for the individual classes.
#
# [indices] we generate three indices for files, classes,
# and methods. These are displayed in a browser
# like window with three index panes across the
# top and the selected description below
#
# Method descriptions appear in whatever entity (file, class,
# or module) that contains them.
#
# We generate files in a structure below a specified subdirectory,
# normally +doc+.
#
# opdir
# |
# |___ files
# | |__ per file summaries
# |
# |___ classes
# |__ per class/module descriptions
#
# HTML is generated using the Template class.
#
require 'ftools'
require 'rdoc/options'
require 'rdoc/template'
require 'rdoc/markup/simple_markup'
require 'rdoc/markup/simple_markup/to_html'
require 'cgi'
module Generators
# Name of sub-direcories that hold file and class/module descriptions
FILE_DIR = "files"
CLASS_DIR = "classes"
CSS_NAME = "rdoc-style.css"
##
# Build a hash of all items that can be cross-referenced.
# This is used when we output required and included names:
# if the names appear in this hash, we can generate
# an html cross reference to the appropriate description.
# We also use this when parsing comment blocks: any decorated
# words matching an entry in this list are hyperlinked.
class AllReferences
@@refs = {}
def AllReferences::reset
@@refs = {}
end
def AllReferences.add(name, html_class)
@@refs[name] = html_class
end
def AllReferences.[](name)
@@refs[name]
end
def AllReferences.keys
@@refs.keys
end
end
##
# Subclass of the SM::ToHtml class that supports looking
# up words in the AllReferences list. Those that are
# found (like AllReferences in this comment) will
# be hyperlinked
class HyperlinkHtml < SM::ToHtml
# We need to record the html path of our caller so we can generate
# correct relative paths for any hyperlinks that we find
def initialize(from_path, context)
super()
@from_path = from_path
@parent_name = context.parent_name
@parent_name += "::" if @parent_name
@context = context
end
# We're invoked when any text matches the CROSSREF pattern
# (defined in MarkUp). If we fine the corresponding reference,
# generate a hyperlink. If the name we're looking for contains
# no punctuation, we look for it up the module/class chain. For
# example, HyperlinkHtml is found, even without the Generators::
# prefix, because we look for it in module Generators first.
def handle_special_CROSSREF(special)
name = special.text
if name[0,1] == '#'
lookup = name[1..-1]
name = lookup unless Options.instance.show_hash
else
lookup = name
end
if /([A-Z].*)[.\#](.*)/ =~ lookup
container = $1
method = $2
ref = @context.find_symbol(container, method)
else
ref = @context.find_symbol(lookup)
end
if ref and ref.document_self
"<a href=\"#{ref.as_href(@from_path)}\">#{name}</a>"
else
name
end
end
# Generate a hyperlink for url, labeled with text. Handle the
# special cases for img: and link: described under handle_special_HYPEDLINK
def gen_url(url, text)
if url =~ /([A-Za-z]+):(.*)/
type = $1
path = $2
else
type = "http"
path = url
url = "http://#{url}"
end
if type == "link"
if path[0,1] == '#' # is this meaningful?
url = path
else
url = HTMLGenerator.gen_url(@from_path, path)
end
end
if (type == "http" || type == "link") &&
url =~ /\.(gif|png|jpg|jpeg|bmp)$/
"<img src=\"#{url}\">"
else
"<a href=\"#{url}\">#{text.sub(%r{^#{type}:/*}, '')}</a>"
end
end
# And we're invoked with a potential external hyperlink mailto:
# just gets inserted. http: links are checked to see if they
# reference an image. If so, that image gets inserted using an
# <img> tag. Otherwise a conventional <a href> is used. We also
# support a special type of hyperlink, link:, which is a reference
# to a local file whose path is relative to the --op directory.
def handle_special_HYPERLINK(special)
url = special.text
gen_url(url, url)
end
# HEre's a hypedlink where the label is different to the URL
# <label>[url]
#
def handle_special_TIDYLINK(special)
text = special.text
# unless text =~ /(\S+)\[(.*?)\]/
unless text =~ /\{(.*?)\}\[(.*?)\]/ or text =~ /(\S+)\[(.*?)\]/
return text
end
label = $1
url = $2
gen_url(url, label)
end
end
#####################################################################
#
# Handle common markup tasks for the various Html classes
#
module MarkUp
# Convert a string in markup format into HTML. We keep a cached
# SimpleMarkup object lying around after the first time we're
# called per object.
def markup(str, remove_para=false)
return '' unless str
unless defined? @markup
@markup = SM::SimpleMarkup.new
# class names, variable names, file names, or instance variables
@markup.add_special(/(
\b([A-Z]\w*(::\w+)*[.\#]\w+) # A::B.meth
| \b([A-Z]\w+(::\w+)*) # A::B..
| \#\w+[!?=]? # #meth_name
| \b\w+([_\/\.]+\w+)+[!?=]? # meth_name
)/x,
:CROSSREF)
# external hyperlinks
@markup.add_special(/((link:|https?:|mailto:|ftp:|www\.)\S+\w)/, :HYPERLINK)
# and links of the form <text>[<url>]
@markup.add_special(/(((\{.*?\})|\b\S+?)\[\S+?\.\S+?\])/, :TIDYLINK)
# @markup.add_special(/\b(\S+?\[\S+?\.\S+?\])/, :TIDYLINK)
end
unless defined? @html_formatter
@html_formatter = HyperlinkHtml.new(self.path, self)
end
# Convert leading comment markers to spaces, but only
# if all non-blank lines have them
if str =~ /^(?>\s*)[^\#]/
content = str
else
content = str.gsub(/^\s*(#+)/) { $1.tr('#',' ') }
end
res = @markup.convert(content, @html_formatter)
if remove_para
res.sub!(/^<p>/, '')
res.sub!(/<\/p>$/, '')
end
res
end
# Qualify a stylesheet URL; if if +css_name+ does not begin with '/' or
# 'http[s]://', prepend a prefix relative to +path+. Otherwise, return it
# unmodified.
def style_url(path, css_name=nil)
# $stderr.puts "style_url( #{path.inspect}, #{css_name.inspect} )"
css_name ||= CSS_NAME
if %r{^(https?:/)?/} =~ css_name
return css_name
else
return HTMLGenerator.gen_url(path, css_name)
end
end
# Build a webcvs URL with the given 'url' argument. URLs with a '%s' in them
# get the file's path sprintfed into them; otherwise they're just catenated
# together.
def cvs_url(url, full_path)
if /%s/ =~ url
return sprintf( url, full_path )
else
return url + full_path
end
end
end
#####################################################################
#
# A Context is built by the parser to represent a container: contexts
# hold classes, modules, methods, require lists and include lists.
# ClassModule and TopLevel are the context objects we process here
#
class ContextUser
include MarkUp
attr_reader :context
def initialize(context, options)
@context = context
@options = options
end
# convenience method to build a hyperlink
def href(link, cls, name)
%{<a href="#{link}" class="#{cls}">#{name}</a>} #"
end
# return a reference to outselves to be used as an href=
# the form depends on whether we're all in one file
# or in multiple files
def as_href(from_path)
if @options.all_one_file
"#" + path
else
HTMLGenerator.gen_url(from_path, path)
end
end
# Create a list of HtmlMethod objects for each method
# in the corresponding context object. If the @options.show_all
# variable is set (corresponding to the <tt>--all</tt> option,
# we include all methods, otherwise just the public ones.
def collect_methods
list = @context.method_list
unless @options.show_all
list = list.find_all {|m| m.visibility == :public || m.force_documentation }
end
@methods = list.collect {|m| HtmlMethod.new(m, self, @options) }
end
# Build a summary list of all the methods in this context
def build_method_summary_list(path_prefix="")
collect_methods unless @methods
meths = @methods.sort
res = []
meths.each do |meth|
res << {
"name" => CGI.escapeHTML(meth.name),
"aref" => "#{path_prefix}\##{meth.aref}"
}
end
res
end
# Build a list of aliases for which we couldn't find a
# corresponding method
def build_alias_summary_list(section)
values = []
@context.aliases.each do |al|
next unless al.section == section
res = {
'old_name' => al.old_name,
'new_name' => al.new_name,
}
if al.comment && !al.comment.empty?
res['desc'] = markup(al.comment, true)
end
values << res
end
values
end
# Build a list of constants
def build_constants_summary_list(section)
values = []
@context.constants.each do |co|
next unless co.section == section
res = {
'name' => co.name,
'value' => CGI.escapeHTML(co.value)
}
res['desc'] = markup(co.comment, true) if co.comment && !co.comment.empty?
values << res
end
values
end
def build_requires_list(context)
potentially_referenced_list(context.requires) {|fn| [fn + ".rb"] }
end
def build_include_list(context)
potentially_referenced_list(context.includes)
end
# Build a list from an array of <i>Htmlxxx</i> items. Look up each
# in the AllReferences hash: if we find a corresponding entry,
# we generate a hyperlink to it, otherwise just output the name.
# However, some names potentially need massaging. For example,
# you may require a Ruby file without the .rb extension,
# but the file names we know about may have it. To deal with
# this, we pass in a block which performs the massaging,
# returning an array of alternative names to match
def potentially_referenced_list(array)
res = []
array.each do |i|
ref = AllReferences[i.name]
# if !ref
# container = @context.parent
# while !ref && container
# name = container.name + "::" + i.name
# ref = AllReferences[name]
# container = container.parent
# end
# end
ref = @context.find_symbol(i.name)
ref = ref.viewer if ref
if !ref && block_given?
possibles = yield(i.name)
while !ref and !possibles.empty?
ref = AllReferences[possibles.shift]
end
end
h_name = CGI.escapeHTML(i.name)
if ref and ref.document_self
path = url(ref.path)
res << { "name" => h_name, "aref" => path }
else
res << { "name" => h_name }
end
end
res
end
# Build an array of arrays of method details. The outer array has up
# to six entries, public, private, and protected for both class
# methods, the other for instance methods. The inner arrays contain
# a hash for each method
def build_method_detail_list(section)
outer = []
methods = @methods.sort
for singleton in [true, false]
for vis in [ :public, :protected, :private ]
res = []
methods.each do |m|
if m.section == section and
m.document_self and
m.visibility == vis and
m.singleton == singleton
row = {}
if m.call_seq
row["callseq"] = m.call_seq.gsub(/->/, '&rarr;')
else
row["name"] = CGI.escapeHTML(m.name)
row["params"] = m.params
end
desc = m.description.strip
row["m_desc"] = desc unless desc.empty?
row["aref"] = m.aref
row["visibility"] = m.visibility.to_s
alias_names = []
m.aliases.each do |other|
if other.viewer # won't be if the alias is private
alias_names << {
'name' => other.name,
'aref' => other.viewer.as_href(path)
}
end
end
unless alias_names.empty?
row["aka"] = alias_names
end
if @options.inline_source
code = m.source_code
row["sourcecode"] = code if code
else
code = m.src_url
if code
row["codeurl"] = code
row["imgurl"] = m.img_url
end
end
res << row
end
end
if res.size > 0
outer << {
"type" => vis.to_s.capitalize,
"category" => singleton ? "Class" : "Instance",
"methods" => res
}
end
end
end
outer
end
# Build the structured list of classes and modules contained
# in this context.
def build_class_list(level, from, section, infile=nil)
res = ""
prefix = "&nbsp;&nbsp;::" * level;
from.modules.sort.each do |mod|
next unless mod.section == section
next if infile && !mod.defined_in?(infile)
if mod.document_self
res <<
prefix <<
"Module " <<
href(url(mod.viewer.path), "link", mod.full_name) <<
"<br />\n" <<
build_class_list(level + 1, mod, section, infile)
end
end
from.classes.sort.each do |cls|
next unless cls.section == section
next if infile && !cls.defined_in?(infile)
if cls.document_self
res <<
prefix <<
"Class " <<
href(url(cls.viewer.path), "link", cls.full_name) <<
"<br />\n" <<
build_class_list(level + 1, cls, section, infile)
end
end
res
end
def url(target)
HTMLGenerator.gen_url(path, target)
end
def aref_to(target)
if @options.all_one_file
"#" + target
else
url(target)
end
end
def document_self
@context.document_self
end
def diagram_reference(diagram)
res = diagram.gsub(/((?:src|href)=")(.*?)"/) {
$1 + url($2) + '"'
}
res
end
# Find a symbol in ourselves or our parent
def find_symbol(symbol, method=nil)
res = @context.find_symbol(symbol, method)
if res
res = res.viewer
end
res
end
# create table of contents if we contain sections
def add_table_of_sections
toc = []
@context.sections.each do |section|
if section.title
toc << {
'secname' => section.title,
'href' => section.sequence
}
end
end
@values['toc'] = toc unless toc.empty?
end
end
#####################################################################
#
# Wrap a ClassModule context
class HtmlClass < ContextUser
attr_reader :path
def initialize(context, html_file, prefix, options)
super(context, options)
@html_file = html_file
@is_module = context.is_module?
@values = {}
context.viewer = self
if options.all_one_file
@path = context.full_name
else
@path = http_url(context.full_name, prefix)
end
collect_methods
AllReferences.add(name, self)
end
# return the relative file name to store this class in,
# which is also its url
def http_url(full_name, prefix)
path = full_name.dup
if path['<<']
path.gsub!(/<<\s*(\w*)/) { "from-#$1" }
end
File.join(prefix, path.split("::")) + ".html"
end
def name
@context.full_name
end
def parent_name
@context.parent.full_name
end
def index_name
name
end
def write_on(f)
value_hash
template = TemplatePage.new(RDoc::Page::BODY,
RDoc::Page::CLASS_PAGE,
RDoc::Page::METHOD_LIST)
template.write_html_on(f, @values)
end
def value_hash
class_attribute_values
add_table_of_sections
@values["charset"] = @options.charset
@values["style_url"] = style_url(path, @options.css)
d = markup(@context.comment)
@values["description"] = d unless d.empty?
ml = build_method_summary_list
@values["methods"] = ml unless ml.empty?
il = build_include_list(@context)
@values["includes"] = il unless il.empty?
@values["sections"] = @context.sections.map do |section|
secdata = {
"sectitle" => section.title,
"secsequence" => section.sequence,
"seccomment" => markup(section.comment)
}
al = build_alias_summary_list(section)
secdata["aliases"] = al unless al.empty?
co = build_constants_summary_list(section)
secdata["constants"] = co unless co.empty?
al = build_attribute_list(section)
secdata["attributes"] = al unless al.empty?
cl = build_class_list(0, @context, section)
secdata["classlist"] = cl unless cl.empty?
mdl = build_method_detail_list(section)
secdata["method_list"] = mdl unless mdl.empty?
secdata
end
@values
end
def build_attribute_list(section)
atts = @context.attributes.sort
res = []
atts.each do |att|
next unless att.section == section
if att.visibility == :public || @options.show_all
entry = {
"name" => CGI.escapeHTML(att.name),
"rw" => att.rw,
"a_desc" => markup(att.comment, true)
}
unless att.visibility == :public
entry["rw"] << "-"
end
res << entry
end
end
res
end
def class_attribute_values
h_name = CGI.escapeHTML(name)
@values["classmod"] = @is_module ? "Module" : "Class"
@values["title"] = "#{@values['classmod']}: #{h_name}"
c = @context
c = c.parent while c and !c.diagram
if c && c.diagram
@values["diagram"] = diagram_reference(c.diagram)
end
@values["full_name"] = h_name
parent_class = @context.superclass
if parent_class
@values["parent"] = CGI.escapeHTML(parent_class)
if parent_name
lookup = parent_name + "::" + parent_class
else
lookup = parent_class
end
parent_url = AllReferences[lookup] || AllReferences[parent_class]
if parent_url and parent_url.document_self
@values["par_url"] = aref_to(parent_url.path)
end
end
files = []
@context.in_files.each do |f|
res = {}
full_path = CGI.escapeHTML(f.file_absolute_name)
res["full_path"] = full_path
res["full_path_url"] = aref_to(f.viewer.path) if f.document_self
if @options.webcvs
res["cvsurl"] = cvs_url( @options.webcvs, full_path )
end
files << res
end
@values['infiles'] = files
end
def <=>(other)
self.name <=> other.name
end
end
#####################################################################
#
# Handles the mapping of a file's information to HTML. In reality,
# a file corresponds to a +TopLevel+ object, containing modules,
# classes, and top-level methods. In theory it _could_ contain
# attributes and aliases, but we ignore these for now.
class HtmlFile < ContextUser
attr_reader :path
attr_reader :name
def initialize(context, options, file_dir)
super(context, options)
@values = {}
if options.all_one_file
@path = filename_to_label
else
@path = http_url(file_dir)
end
@name = @context.file_relative_name
collect_methods
AllReferences.add(name, self)
context.viewer = self
end
def http_url(file_dir)
File.join(file_dir, @context.file_relative_name.tr('.', '_')) +
".html"
end
def filename_to_label
@context.file_relative_name.gsub(/%|\/|\?|\#/) {|s| '%' + ("%x" % s[0]) }
end
def index_name
name
end
def parent_name
nil
end
def value_hash
file_attribute_values
add_table_of_sections
@values["charset"] = @options.charset
@values["href"] = path
@values["style_url"] = style_url(path, @options.css)
if @context.comment
d = markup(@context.comment)
@values["description"] = d if d.size > 0
end
ml = build_method_summary_list
@values["methods"] = ml unless ml.empty?
il = build_include_list(@context)
@values["includes"] = il unless il.empty?
rl = build_requires_list(@context)
@values["requires"] = rl unless rl.empty?
if @options.promiscuous
file_context = nil
else
file_context = @context
end
@values["sections"] = @context.sections.map do |section|
secdata = {
"sectitle" => section.title,
"secsequence" => section.sequence,
"seccomment" => markup(section.comment)
}
cl = build_class_list(0, @context, section, file_context)
@values["classlist"] = cl unless cl.empty?
mdl = build_method_detail_list(section)
secdata["method_list"] = mdl unless mdl.empty?
al = build_alias_summary_list(section)
secdata["aliases"] = al unless al.empty?
co = build_constants_summary_list(section)
@values["constants"] = co unless co.empty?
secdata
end
@values
end
def write_on(f)
value_hash
template = TemplatePage.new(RDoc::Page::BODY,
RDoc::Page::FILE_PAGE,
RDoc::Page::METHOD_LIST)
template.write_html_on(f, @values)
end
def file_attribute_values
full_path = @context.file_absolute_name
short_name = File.basename(full_path)
@values["title"] = CGI.escapeHTML("File: #{short_name}")
if @context.diagram
@values["diagram"] = diagram_reference(@context.diagram)
end
@values["short_name"] = CGI.escapeHTML(short_name)
@values["full_path"] = CGI.escapeHTML(full_path)
@values["dtm_modified"] = @context.file_stat.mtime.to_s
if @options.webcvs
@values["cvsurl"] = cvs_url( @options.webcvs, @values["full_path"] )
end
end
def <=>(other)
self.name <=> other.name
end
end
#####################################################################
class HtmlMethod
include MarkUp
attr_reader :context
attr_reader :src_url
attr_reader :img_url
attr_reader :source_code
@@seq = "M000000"
@@all_methods = []
def HtmlMethod::reset
@@all_methods = []
end
def initialize(context, html_class, options)
@context = context
@html_class = html_class
@options = options
@@seq = @@seq.succ
@seq = @@seq
@@all_methods << self
context.viewer = self
if (ts = @context.token_stream)
@source_code = markup_code(ts)
unless @options.inline_source
@src_url = create_source_code_file(@source_code)
@img_url = HTMLGenerator.gen_url(path, 'source.png')
end
end
AllReferences.add(name, self)
end
# return a reference to outselves to be used as an href=
# the form depends on whether we're all in one file
# or in multiple files
def as_href(from_path)
if @options.all_one_file
"#" + path
else
HTMLGenerator.gen_url(from_path, path)
end
end
def name
@context.name
end
def section
@context.section
end
def index_name
"#{@context.name} (#{@html_class.name})"
end
def parent_name
if @context.parent.parent
@context.parent.parent.full_name
else
nil
end
end
def aref
@seq
end
def path
if @options.all_one_file
aref
else
@html_class.path + "#" + aref
end
end
def description
markup(@context.comment)
end
def visibility
@context.visibility
end
def singleton
@context.singleton
end
def call_seq
cs = @context.call_seq
if cs
cs.gsub(/\n/, "<br />\n")
else
nil
end
end
def params
# params coming from a call-seq in 'C' will start with the
# method name
p = @context.params
if p !~ /^\w/
p = @context.params.gsub(/\s*\#.*/, '')
p = p.tr("\n", " ").squeeze(" ")
p = "(" + p + ")" unless p[0] == ?(
if (block = @context.block_params)
# If this method has explicit block parameters, remove any
# explicit &block
p.sub!(/,?\s*&\w+/, '')
block.gsub!(/\s*\#.*/, '')
block = block.tr("\n", " ").squeeze(" ")
if block[0] == ?(
block.sub!(/^\(/, '').sub!(/\)/, '')
end
p << " {|#{block.strip}| ...}"
end
end
CGI.escapeHTML(p)
end
def create_source_code_file(code_body)
meth_path = @html_class.path.sub(/\.html$/, '.src')
File.makedirs(meth_path)
file_path = File.join(meth_path, @seq) + ".html"
template = TemplatePage.new(RDoc::Page::SRC_PAGE)
File.open(file_path, "w") do |f|
values = {
'title' => CGI.escapeHTML(index_name),
'code' => code_body,
'style_url' => style_url(file_path, @options.css),
'charset' => @options.charset
}
template.write_html_on(f, values)
end
HTMLGenerator.gen_url(path, file_path)
end
def HtmlMethod.all_methods
@@all_methods
end
def <=>(other)
@context <=> other.context
end
##
# Given a sequence of source tokens, mark up the source code
# to make it look purty.
def markup_code(tokens)
src = ""
tokens.each do |t|
next unless t
# p t.class
# style = STYLE_MAP[t.class]
style = case t
when RubyToken::TkCONSTANT then "ruby-constant"
when RubyToken::TkKW then "ruby-keyword kw"
when RubyToken::TkIVAR then "ruby-ivar"
when RubyToken::TkOp then "ruby-operator"
when RubyToken::TkId then "ruby-identifier"
when RubyToken::TkNode then "ruby-node"
when RubyToken::TkCOMMENT then "ruby-comment cmt"
when RubyToken::TkREGEXP then "ruby-regexp re"
when RubyToken::TkSTRING then "ruby-value str"
when RubyToken::TkVal then "ruby-value"
else
nil
end
text = CGI.escapeHTML(t.text)
if style
src << "<span class=\"#{style}\">#{text}</span>"
else
src << text
end
end
add_line_numbers(src) if Options.instance.include_line_numbers
src
end
# we rely on the fact that the first line of a source code
# listing has
# # File xxxxx, line dddd
def add_line_numbers(src)
if src =~ /\A.*, line (\d+)/
first = $1.to_i - 1
last = first + src.count("\n")
size = last.to_s.length
real_fmt = "%#{size}d: "
fmt = " " * (size+2)
src.gsub!(/^/) do
res = sprintf(fmt, first)
first += 1
fmt = real_fmt
res
end
end
end
def document_self
@context.document_self
end
def aliases
@context.aliases
end
def find_symbol(symbol, method=nil)
res = @context.parent.find_symbol(symbol, method)
if res
res = res.viewer
end
res
end
end
#####################################################################
class HTMLGenerator
include MarkUp
##
# convert a target url to one that is relative to a given
# path
def HTMLGenerator.gen_url(path, target)
from = File.dirname(path)
to, to_file = File.split(target)
from = from.split("/")
to = to.split("/")
while from.size > 0 and to.size > 0 and from[0] == to[0]
from.shift
to.shift
end
from.fill("..")
from.concat(to)
from << to_file
File.join(*from)
end
# Generators may need to return specific subclasses depending
# on the options they are passed. Because of this
# we create them using a factory
def HTMLGenerator.for(options)
AllReferences::reset
HtmlMethod::reset
if options.all_one_file
HTMLGeneratorInOne.new(options)
else
HTMLGenerator.new(options)
end
end
class <<self
protected :new
end
# Set up a new HTML generator. Basically all we do here is load
# up the correct output temlate
def initialize(options) #:not-new:
@options = options
load_html_template
end
##
# Build the initial indices and output objects
# based on an array of TopLevel objects containing
# the extracted information.
def generate(toplevels)
@toplevels = toplevels
@files = []
@classes = []
write_style_sheet
gen_sub_directories()
build_indices
generate_html
end
private
##
# Load up the HTML template specified in the options.
# If the template name contains a slash, use it literally
#
def load_html_template
template = @options.template
unless template =~ %r{/|\\}
template = File.join("rdoc/generators/template",
@options.generator.key, template)
end
require template
extend RDoc::Page
rescue LoadError
$stderr.puts "Could not find HTML template '#{template}'"
exit 99
end
##
# Write out the style sheet used by the main frames
#
def write_style_sheet
template = TemplatePage.new(RDoc::Page::STYLE)
unless @options.css
File.open(CSS_NAME, "w") do |f|
values = { "fonts" => RDoc::Page::FONTS }
template.write_html_on(f, values)
end
end
end
##
# See the comments at the top for a description of the
# directory structure
def gen_sub_directories
File.makedirs(FILE_DIR, CLASS_DIR)
rescue
$stderr.puts $!.message
exit 1
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
@toplevels.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
#
def generate_html
# the individual descriptions for files and classes
gen_into(@files)
gen_into(@classes)
# and the index files
gen_file_index
gen_class_index
gen_method_index
gen_main_index
# this method is defined in the template file
write_extra_pages if defined? write_extra_pages
end
def gen_into(list)
list.each do |item|
if item.document_self
op_file = item.path
File.makedirs(File.dirname(op_file))
File.open(op_file, "w") { |file| item.write_on(file) }
end
end
end
def gen_file_index
gen_an_index(@files, 'Files',
RDoc::Page::FILE_INDEX,
"fr_file_index.html")
end
def gen_class_index
gen_an_index(@classes, 'Classes',
RDoc::Page::CLASS_INDEX,
"fr_class_index.html")
end
def gen_method_index
gen_an_index(HtmlMethod.all_methods, 'Methods',
RDoc::Page::METHOD_INDEX,
"fr_method_index.html")
end
def gen_an_index(collection, title, template, filename)
template = TemplatePage.new(RDoc::Page::FR_INDEX_BODY, template)
res = []
collection.sort.each do |f|
if f.document_self
res << { "href" => f.path, "name" => f.index_name }
end
end
values = {
"entries" => res,
'list_title' => CGI.escapeHTML(title),
'index_url' => main_url,
'charset' => @options.charset,
'style_url' => style_url('', @options.css),
}
File.open(filename, "w") do |f|
template.write_html_on(f, values)
end
end
# The main index page is mostly a template frameset, but includes
# the initial page. If the <tt>--main</tt> option was given,
# we use this as our main page, otherwise we use the
# first file specified on the command line.
def gen_main_index
template = TemplatePage.new(RDoc::Page::INDEX)
File.open("index.html", "w") do |f|
values = {
"initial_page" => main_url,
'title' => CGI.escapeHTML(@options.title),
'charset' => @options.charset
}
if @options.inline_source
values['inline_source'] = true
end
template.write_html_on(f, values)
end
end
# return the url of the main page
def main_url
main_page = @options.main_page
ref = nil
if main_page
ref = AllReferences[main_page]
if ref
ref = ref.path
else
$stderr.puts "Could not find main page #{main_page}"
end
end
unless ref
for file in @files
if file.document_self
ref = file.path
break
end
end
end
unless ref
$stderr.puts "Couldn't find anything to document"
$stderr.puts "Perhaps you've used :stopdoc: in all classes"
exit(1)
end
ref
end
end
######################################################################
class HTMLGeneratorInOne < HTMLGenerator
def initialize(*args)
super
end
##
# Build the initial indices and output objects
# based on an array of TopLevel objects containing
# the extracted information.
def generate(info)
@toplevels = info
@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
@toplevels.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),
'title' => CGI.escapeHTML(@options.title),
}
# 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