2003-12-01 02:12:49 -05:00
|
|
|
# See README.
|
|
|
|
#
|
|
|
|
|
|
|
|
|
2004-11-08 10:55:23 -05:00
|
|
|
VERSION_STRING = %{RDoc V1.0.1 - 20041108}
|
2003-12-01 02:12:49 -05:00
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2004-01-02 01:01:12 -05:00
|
|
|
# Name of the dotfile that contains the description of files to be
|
|
|
|
# processed in the current directory
|
|
|
|
DOT_DOC_FILENAME = ".document"
|
|
|
|
|
|
|
|
# Simple stats collector
|
|
|
|
class Stats
|
|
|
|
attr_accessor :num_files, :num_classes, :num_modules, :num_methods
|
|
|
|
def initialize
|
|
|
|
@num_files = @num_classes = @num_modules = @num_methods = 0
|
|
|
|
@start = Time.now
|
|
|
|
end
|
|
|
|
def print
|
|
|
|
puts "Files: #@num_files"
|
|
|
|
puts "Classes: #@num_classes"
|
|
|
|
puts "Modules: #@num_modules"
|
|
|
|
puts "Methods: #@num_methods"
|
|
|
|
puts "Elapsed: " + sprintf("%0.3fs", Time.now - @start)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2003-12-01 02:12:49 -05:00
|
|
|
# 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
|
|
|
|
|
|
|
|
|
2004-01-02 01:01:12 -05:00
|
|
|
# The .document file contains a list of file and directory name
|
|
|
|
# patterns, representing candidates for documentation. It may
|
|
|
|
# also contain comments (starting with '#')
|
|
|
|
def parse_dot_doc_file(in_dir, filename, options)
|
|
|
|
# read and strip comments
|
|
|
|
patterns = File.read(filename).gsub(/#.*/, '')
|
|
|
|
|
|
|
|
result = []
|
|
|
|
|
|
|
|
patterns.split.each do |patt|
|
|
|
|
candidates = Dir.glob(File.join(in_dir, patt))
|
|
|
|
result.concat(normalized_file_list(options, candidates))
|
|
|
|
end
|
|
|
|
result
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2003-12-01 02:12:49 -05:00
|
|
|
# Given a list of files and directories, create a list
|
|
|
|
# of all the Ruby files they contain.
|
2004-02-23 23:24:02 -05:00
|
|
|
#
|
2004-12-11 20:16:19 -05:00
|
|
|
# If +force_doc+ is true, we always add the given files.
|
2004-02-23 23:24:02 -05:00
|
|
|
# If false, only add files that we guarantee we can parse
|
|
|
|
# It is true when looking at files given on the command line,
|
|
|
|
# false when recursing through subdirectories.
|
|
|
|
#
|
|
|
|
# The effect of this is that if you want a file with a non-
|
|
|
|
# standard extension parsed, you must name it explicity.
|
2004-12-11 20:16:19 -05:00
|
|
|
#
|
2004-02-23 23:24:02 -05:00
|
|
|
|
2004-12-11 20:16:19 -05:00
|
|
|
def normalized_file_list(options, relative_files, force_doc = false, exclude_pattern=nil)
|
2003-12-01 02:12:49 -05:00
|
|
|
file_list = []
|
|
|
|
|
|
|
|
relative_files.each do |rel_file_name|
|
2004-12-11 20:16:19 -05:00
|
|
|
next if exclude_pattern && exclude_pattern =~ rel_file_name
|
2003-12-01 02:12:49 -05:00
|
|
|
case type = File.stat(rel_file_name).ftype
|
|
|
|
when "file"
|
2004-04-04 19:19:58 -04:00
|
|
|
file_list << rel_file_name.sub(/^\.\//, '') if force_doc || ParserFactory.can_parse(rel_file_name)
|
2003-12-01 02:12:49 -05:00
|
|
|
when "directory"
|
2004-01-12 11:06:35 -05:00
|
|
|
next if rel_file_name == "CVS" || rel_file_name == ".svn"
|
2004-01-02 01:01:12 -05:00
|
|
|
dot_doc = File.join(rel_file_name, DOT_DOC_FILENAME)
|
|
|
|
if File.file?(dot_doc)
|
|
|
|
file_list.concat(parse_dot_doc_file(rel_file_name, dot_doc, options))
|
|
|
|
else
|
|
|
|
file_list.concat(list_files_in_directory(rel_file_name, options))
|
2003-12-01 02:12:49 -05:00
|
|
|
end
|
|
|
|
else
|
|
|
|
raise RDocError.new("I can't deal with a #{type} #{rel_file_name}")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
file_list
|
|
|
|
end
|
|
|
|
|
2004-01-02 01:01:12 -05:00
|
|
|
# Return a list of the files to be processed in
|
|
|
|
# a directory. We know that this directory doesn't have
|
|
|
|
# a .document file, so we're looking for real files. However
|
|
|
|
# we may well contain subdirectories which must
|
|
|
|
# be tested for .document files
|
|
|
|
def list_files_in_directory(dir, options)
|
2004-12-11 20:16:19 -05:00
|
|
|
normalized_file_list(options, Dir.glob(File.join(dir, "*")), false, options.exclude)
|
2004-01-02 01:01:12 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
|
2003-12-01 02:12:49 -05:00
|
|
|
# Parse each file on the command line, recursively entering
|
|
|
|
# directories
|
|
|
|
|
|
|
|
def parse_files(options)
|
|
|
|
|
|
|
|
file_info = []
|
|
|
|
|
|
|
|
files = options.files
|
|
|
|
files = ["."] if files.empty?
|
|
|
|
|
2004-02-23 23:24:02 -05:00
|
|
|
file_list = normalized_file_list(options, files, true)
|
2003-12-01 02:12:49 -05:00
|
|
|
|
|
|
|
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)
|
2004-01-02 01:01:12 -05:00
|
|
|
parser = ParserFactory.parser_for(top_level, fn, content, options, @stats)
|
2003-12-01 02:12:49 -05:00
|
|
|
file_info << parser.scan
|
2004-01-02 01:01:12 -05:00
|
|
|
@stats.num_files += 1
|
2003-12-01 02:12:49 -05:00
|
|
|
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
|
|
|
|
|
2004-01-02 01:01:12 -05:00
|
|
|
@stats = Stats.new
|
|
|
|
|
2003-12-01 02:12:49 -05:00
|
|
|
options = Options.instance
|
|
|
|
options.parse(argv, GENERATORS)
|
|
|
|
|
2004-02-23 16:17:26 -05:00
|
|
|
unless options.all_one_file
|
|
|
|
setup_output_dir(options.op_dir)
|
|
|
|
end
|
|
|
|
|
2003-12-01 02:12:49 -05:00
|
|
|
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
|
|
|
|
|
2004-02-23 16:17:26 -05:00
|
|
|
Dir.chdir(options.op_dir) unless options.all_one_file
|
2003-12-01 02:12:49 -05:00
|
|
|
|
|
|
|
begin
|
|
|
|
Diagram.new(file_info, options).draw if options.diagram
|
|
|
|
gen.generate(file_info)
|
|
|
|
ensure
|
|
|
|
Dir.chdir(pwd)
|
|
|
|
end
|
2004-01-02 01:01:12 -05:00
|
|
|
end
|
2003-12-01 02:12:49 -05:00
|
|
|
|
2004-01-02 01:01:12 -05:00
|
|
|
unless options.quiet
|
|
|
|
puts
|
|
|
|
@stats.print
|
2003-12-01 02:12:49 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|