mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
eaa9ca7653
make the rdoc(generating html) run faster and use less memory. patch by Tetsu Soh [ruby-core:27656] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@26384 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
449 lines
12 KiB
Ruby
449 lines
12 KiB
Ruby
require 'fileutils'
|
|
|
|
require 'rdoc/generator'
|
|
require 'rdoc/markup/to_html'
|
|
|
|
##
|
|
# We're responsible for generating all the HTML files from the object tree
|
|
# defined in code_objects.rb. We generate:
|
|
#
|
|
# [files] an html file for each input file given. These
|
|
# input files appear as objects of class
|
|
# TopLevel
|
|
#
|
|
# [classes] an html file for each class or module encountered.
|
|
# These classes are not grouped by file: if a file
|
|
# contains four classes, we'll generate an html
|
|
# file for the file itself, and four html files
|
|
# for the individual classes.
|
|
#
|
|
# [indices] we generate three indices for files, classes,
|
|
# and methods. These are displayed in a browser
|
|
# like window with three index panes across the
|
|
# top and the selected description below
|
|
#
|
|
# Method descriptions appear in whatever entity (file, class, or module) that
|
|
# contains them.
|
|
#
|
|
# We generate files in a structure below a specified subdirectory, normally
|
|
# +doc+.
|
|
#
|
|
# opdir
|
|
# |
|
|
# |___ files
|
|
# | |__ per file summaries
|
|
# |
|
|
# |___ classes
|
|
# |__ per class/module descriptions
|
|
#
|
|
# HTML is generated using the Template class.
|
|
|
|
class RDoc::Generator::HTML
|
|
|
|
include RDoc::Generator::MarkUp
|
|
|
|
##
|
|
# Generator may need to return specific subclasses depending on the
|
|
# options they are passed. Because of this we create them using a factory
|
|
|
|
def self.for(options)
|
|
RDoc::Generator::AllReferences.reset
|
|
RDoc::Generator::Method.reset
|
|
|
|
if options.all_one_file
|
|
RDoc::Generator::HTMLInOne.new options
|
|
else
|
|
new options
|
|
end
|
|
end
|
|
|
|
class << self
|
|
protected :new
|
|
end
|
|
|
|
##
|
|
# Set up a new HTML generator. Basically all we do here is load up the
|
|
# correct output temlate
|
|
|
|
def initialize(options) #:not-new:
|
|
@options = options
|
|
load_html_template
|
|
end
|
|
|
|
##
|
|
# Build the initial indices and output objects
|
|
# based on an array of TopLevel objects containing
|
|
# the extracted information.
|
|
|
|
def generate(toplevels)
|
|
@toplevels = toplevels
|
|
@files = []
|
|
@classes = []
|
|
|
|
write_style_sheet
|
|
gen_sub_directories
|
|
build_indices
|
|
generate_html
|
|
end
|
|
|
|
private
|
|
|
|
##
|
|
# Load up the HTML template specified in the options.
|
|
# If the template name contains a slash, use it literally
|
|
|
|
def load_html_template
|
|
#
|
|
# If the template is not a path, first look for it
|
|
# in rdoc's HTML template directory. Perhaps this behavior should
|
|
# be reversed (first try to include the template and, only if that
|
|
# fails, try to include it in the default template directory).
|
|
# One danger with reversing the behavior, however, is that
|
|
# if something like require 'html' could load up an
|
|
# unrelated file in the standard library or in a gem.
|
|
#
|
|
template = @options.template
|
|
|
|
unless template =~ %r{/|\\} then
|
|
template = File.join('rdoc', 'generator', @options.generator.key,
|
|
template)
|
|
end
|
|
|
|
begin
|
|
require template
|
|
|
|
@template = self.class.const_get @options.template.upcase
|
|
@options.template_class = @template
|
|
rescue LoadError => e
|
|
#
|
|
# The template did not exist in the default template directory, so
|
|
# see if require can find the template elsewhere (in a gem, for
|
|
# instance).
|
|
#
|
|
if(e.message[template] && template != @options.template)
|
|
template = @options.template
|
|
retry
|
|
end
|
|
|
|
$stderr.puts "Could not find HTML template '#{template}': #{e.message}"
|
|
exit 99
|
|
end
|
|
end
|
|
|
|
##
|
|
# Write out the style sheet used by the main frames
|
|
|
|
def write_style_sheet
|
|
return unless @template.constants.include? :STYLE or
|
|
@template.constants.include? 'STYLE'
|
|
|
|
template = RDoc::TemplatePage.new @template::STYLE
|
|
|
|
unless @options.css then
|
|
open RDoc::Generator::CSS_NAME, 'w' do |f|
|
|
values = {}
|
|
|
|
if @template.constants.include? :FONTS or
|
|
@template.constants.include? 'FONTS' then
|
|
values["fonts"] = @template::FONTS
|
|
end
|
|
|
|
template.write_html_on(f, values)
|
|
end
|
|
end
|
|
end
|
|
|
|
##
|
|
# See the comments at the top for a description of the directory structure
|
|
|
|
def gen_sub_directories
|
|
FileUtils.mkdir_p RDoc::Generator::FILE_DIR
|
|
FileUtils.mkdir_p RDoc::Generator::CLASS_DIR
|
|
rescue
|
|
$stderr.puts $!.message
|
|
exit 1
|
|
end
|
|
|
|
def build_indices
|
|
@files, @classes = RDoc::Generator::Context.build_indices(@toplevels,
|
|
@options)
|
|
end
|
|
|
|
##
|
|
# Generate all the HTML
|
|
|
|
def generate_html
|
|
@main_url = main_url
|
|
|
|
# the individual descriptions for files and classes
|
|
gen_into(@files)
|
|
gen_into(@classes)
|
|
|
|
# and the index files
|
|
gen_file_index
|
|
gen_class_index
|
|
gen_method_index
|
|
gen_main_index
|
|
|
|
# this method is defined in the template file
|
|
values = {
|
|
'title_suffix' => CGI.escapeHTML("[#{@options.title}]"),
|
|
'charset' => @options.charset,
|
|
'style_url' => style_url('', @options.css),
|
|
}
|
|
|
|
@template.write_extra_pages(values) if @template.respond_to?(:write_extra_pages)
|
|
end
|
|
|
|
def gen_into(list)
|
|
#
|
|
# The file, class, and method lists technically should be regenerated
|
|
# for every output file, in order that the relative links be correct
|
|
# (we are worried here about frameless templates, which need this
|
|
# information for every generated page). Doing this is a bit slow,
|
|
# however. For a medium-sized gem, this increased rdoc's runtime by
|
|
# about 5% (using the 'time' command-line utility). While this is not
|
|
# necessarily a problem, I do not want to pessimize rdoc for large
|
|
# projects, however, and so we only regenerate the lists when the
|
|
# directory of the output file changes, which seems like a reasonable
|
|
# optimization.
|
|
#
|
|
file_list = {}
|
|
class_list = {}
|
|
method_list = {}
|
|
prev_op_dir = nil
|
|
|
|
list.each do |item|
|
|
next unless item.document_self
|
|
|
|
op_file = item.path
|
|
op_dir = File.dirname(op_file)
|
|
|
|
if(op_dir != prev_op_dir)
|
|
file_list = index_to_links op_file, @files
|
|
class_list = index_to_links op_file, @classes
|
|
method_list = index_to_links op_file, RDoc::Generator::Method.all_methods
|
|
end
|
|
prev_op_dir = op_dir
|
|
|
|
FileUtils.mkdir_p op_dir
|
|
|
|
open op_file, 'w' do |io|
|
|
item.write_on io, file_list, class_list, method_list
|
|
end
|
|
|
|
file_list.clear
|
|
class_list.clear
|
|
method_list.clear
|
|
end
|
|
end
|
|
|
|
def gen_file_index
|
|
gen_an_index @files, 'Files', @template::FILE_INDEX, "fr_file_index.html"
|
|
end
|
|
|
|
def gen_class_index
|
|
gen_an_index(@classes, 'Classes', @template::CLASS_INDEX,
|
|
"fr_class_index.html")
|
|
end
|
|
|
|
def gen_method_index
|
|
gen_an_index(RDoc::Generator::Method.all_methods, 'Methods',
|
|
@template::METHOD_INDEX, "fr_method_index.html")
|
|
end
|
|
|
|
def gen_an_index(collection, title, template, filename)
|
|
template = RDoc::TemplatePage.new @template::FR_INDEX_BODY, template
|
|
res = []
|
|
collection.sort.each do |f|
|
|
if f.document_self
|
|
res << { "href" => f.path, "name" => f.index_name }
|
|
end
|
|
end
|
|
|
|
values = {
|
|
"entries" => res,
|
|
'title' => CGI.escapeHTML("#{title} [#{@options.title}]"),
|
|
'list_title' => CGI.escapeHTML(title),
|
|
'index_url' => @main_url,
|
|
'charset' => @options.charset,
|
|
'style_url' => style_url('', @options.css),
|
|
}
|
|
|
|
open filename, 'w' do |f|
|
|
template.write_html_on(f, values)
|
|
end
|
|
end
|
|
|
|
##
|
|
# The main index page is mostly a template frameset, but includes the
|
|
# initial page. If the <tt>--main</tt> option was given, we use this as
|
|
# our main page, otherwise we use the first file specified on the command
|
|
# line.
|
|
|
|
def gen_main_index
|
|
if @template.const_defined? :FRAMELESS then
|
|
#
|
|
# If we're using a template without frames, then just redirect
|
|
# to it from index.html.
|
|
#
|
|
# One alternative to this, expanding the main page's template into
|
|
# index.html, is tricky because the relative URLs will be different
|
|
# (since index.html is located in at the site's root,
|
|
# rather than within a files or a classes subdirectory).
|
|
#
|
|
open 'index.html', 'w' do |f|
|
|
f.puts(%{<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">})
|
|
f.puts(%{<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
|
|
lang="en">})
|
|
f.puts(%{<head>})
|
|
f.puts(%{<title>#{CGI.escapeHTML(@options.title)}</title>})
|
|
f.puts(%{<meta http-equiv="refresh" content="0; url=#{@main_url}" />})
|
|
f.puts(%{</head>})
|
|
f.puts(%{<body></body>})
|
|
f.puts(%{</html>})
|
|
end
|
|
else
|
|
main = RDoc::TemplatePage.new @template::INDEX
|
|
|
|
open 'index.html', 'w' do |f|
|
|
style_url = style_url '', @options.css
|
|
|
|
classes = @classes.sort.map { |klass| klass.value_hash }
|
|
|
|
values = {
|
|
'initial_page' => @main_url,
|
|
'style_url' => style_url('', @options.css),
|
|
'title' => CGI.escapeHTML(@options.title),
|
|
'charset' => @options.charset,
|
|
'classes' => classes,
|
|
}
|
|
|
|
values['inline_source'] = @options.inline_source
|
|
|
|
main.write_html_on f, values
|
|
end
|
|
end
|
|
end
|
|
|
|
def index_to_links(output_path, collection)
|
|
collection.sort.map do |f|
|
|
next unless f.document_self
|
|
{ "href" => RDoc::Markup::ToHtml.gen_relative_url(output_path, f.path),
|
|
"name" => f.index_name }
|
|
end.compact
|
|
end
|
|
|
|
##
|
|
# Returns the url of the main page
|
|
|
|
def main_url
|
|
main_page = @options.main_page
|
|
|
|
#
|
|
# If a main page has been specified (--main), then search for it
|
|
# in the AllReferences array. This allows either files or classes
|
|
# to be used for the main page.
|
|
#
|
|
if main_page then
|
|
main_page_ref = RDoc::Generator::AllReferences[main_page]
|
|
|
|
if main_page_ref then
|
|
return main_page_ref.path
|
|
else
|
|
$stderr.puts "Could not find main page #{main_page}"
|
|
end
|
|
end
|
|
|
|
#
|
|
# No main page has been specified, so just use the README.
|
|
#
|
|
@files.each do |file|
|
|
if file.name =~ /^README/ then
|
|
return file.path
|
|
end
|
|
end
|
|
|
|
#
|
|
# There's no README (shame! shame!). Just use the first file
|
|
# that will be documented.
|
|
#
|
|
@files.each do |file|
|
|
if file.document_self then
|
|
return file.path
|
|
end
|
|
end
|
|
|
|
#
|
|
# There are no files to be documented... Something seems very wrong.
|
|
#
|
|
raise RDoc::Error, "Couldn't find anything to document (perhaps :stopdoc: has been used in all classes)!"
|
|
end
|
|
private :main_url
|
|
|
|
end
|
|
|
|
class RDoc::Generator::HTMLInOne < RDoc::Generator::HTML
|
|
|
|
def initialize(*args)
|
|
super
|
|
end
|
|
|
|
##
|
|
# Build the initial indices and output objects
|
|
# based on an array of TopLevel objects containing
|
|
# the extracted information.
|
|
|
|
def generate(info)
|
|
@toplevels = info
|
|
@hyperlinks = {}
|
|
|
|
build_indices
|
|
generate_xml
|
|
end
|
|
|
|
##
|
|
# Generate:
|
|
#
|
|
# * a list of RDoc::Generator::File objects for each TopLevel object.
|
|
# * a list of RDoc::Generator::Class objects for each first level
|
|
# class or module in the TopLevel objects
|
|
# * a complete list of all hyperlinkable terms (file,
|
|
# class, module, and method names)
|
|
|
|
def build_indices
|
|
@files, @classes = RDoc::Generator::Context.build_indices(@toplevels,
|
|
@options)
|
|
end
|
|
|
|
##
|
|
# Generate all the HTML. For the one-file case, we generate
|
|
# all the information in to one big hash
|
|
|
|
def generate_xml
|
|
values = {
|
|
'charset' => @options.charset,
|
|
'files' => gen_into(@files),
|
|
'classes' => gen_into(@classes),
|
|
'title' => CGI.escapeHTML(@options.title),
|
|
}
|
|
|
|
template = RDoc::TemplatePage.new @template::ONE_PAGE
|
|
|
|
if @options.op_name
|
|
opfile = open @options.op_name, 'w'
|
|
else
|
|
opfile = $stdout
|
|
end
|
|
template.write_html_on(opfile, values)
|
|
end
|
|
|
|
def gen_into(list)
|
|
res = []
|
|
list.each do |item|
|
|
res << item.value_hash
|
|
end
|
|
res
|
|
end
|
|
end
|