summaryrefslogtreecommitdiff
path: root/lib/rdoc/rdoc.rb
diff options
context:
space:
mode:
authordrbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2010-04-01 07:45:16 +0000
committerdrbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2010-04-01 07:45:16 +0000
commit46580b51477355fece514573c88cb67030f4a502 (patch)
tree779c1a64466643461b3daa4cd9a3548b84f0fd55 /lib/rdoc/rdoc.rb
parent9b40cdfe8c973a061c5683ad78c283b9ddb8b2e9 (diff)
Import RDoc 2.5
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@27147 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/rdoc/rdoc.rb')
-rw-r--r--lib/rdoc/rdoc.rb543
1 files changed, 344 insertions, 199 deletions
diff --git a/lib/rdoc/rdoc.rb b/lib/rdoc/rdoc.rb
index 80128ee8ba..230639fb0c 100644
--- a/lib/rdoc/rdoc.rb
+++ b/lib/rdoc/rdoc.rb
@@ -6,285 +6,430 @@ require 'rdoc/parser'
require 'rdoc/parser/simple'
require 'rdoc/parser/ruby'
require 'rdoc/parser/c'
-require 'rdoc/parser/f95'
require 'rdoc/parser/perl'
require 'rdoc/stats'
require 'rdoc/options'
-require 'rdoc/diagram'
-
require 'find'
require 'fileutils'
require 'time'
-module RDoc
+##
+# Encapsulate the production of rdoc documentation. Basically you can use this
+# as you would invoke rdoc from the command line:
+#
+# rdoc = RDoc::RDoc.new
+# rdoc.document(args)
+#
+# Where +args+ is an array of strings, each corresponding to an argument you'd
+# give rdoc on the command line. See rdoc/rdoc.rb for details.
+
+class RDoc::RDoc
##
- # Encapsulate the production of rdoc documentation. Basically you can use
- # this as you would invoke rdoc from the command line:
- #
- # rdoc = RDoc::RDoc.new
- # rdoc.document(args)
- #
- # Where +args+ is an array of strings, each corresponding to an argument
- # you'd give rdoc on the command line. See rdoc/rdoc.rb for details.
+ # File pattern to exclude
- class RDoc
+ attr_accessor :exclude
- Generator = Struct.new(:file_name, :class_name, :key)
+ ##
+ # Generator instance used for creating output
- ##
- # Accessor for statistics. Available after each call to parse_files
+ attr_accessor :generator
- attr_reader :stats
+ ##
+ # RDoc options
- ##
- # This is the list of output generator that we support
+ attr_accessor :options
- GENERATORS = {}
+ ##
+ # Accessor for statistics. Available after each call to parse_files
- $LOAD_PATH.collect do |d|
- File.expand_path d
- end.find_all do |d|
- File.directory? "#{d}/rdoc/generator"
- end.each do |dir|
- Dir.entries("#{dir}/rdoc/generator").each do |gen|
- next unless /(\w+)\.rb$/ =~ gen
- type = $1
- unless GENERATORS.has_key? type
- GENERATORS[type] = Generator.new("rdoc/generator/#{gen}",
- "#{type.upcase}".intern,
- type)
- end
- end
+ attr_reader :stats
+
+ ##
+ # This is the list of supported output generators
+
+ GENERATORS = {}
+
+ ##
+ # Add +klass+ that can generate output after parsing
+
+ def self.add_generator(klass)
+ name = klass.name.sub(/^RDoc::Generator::/, '').downcase
+ GENERATORS[name] = klass
+ end
+
+ ##
+ # Active RDoc::RDoc instance
+
+ def self.current
+ @current
+ end
+
+ ##
+ # Sets the active RDoc::RDoc instance
+
+ def self.current=(rdoc)
+ @current = rdoc
+ end
+
+ def initialize
+ @current = nil
+ @exclude = nil
+ @generator = nil
+ @last_created = nil
+ @old_siginfo = nil
+ @options = nil
+ @stats = nil
+ end
+
+ ##
+ # Report an error message and exit
+
+ def error(msg)
+ raise RDoc::Error, msg
+ end
+
+ ##
+ # Gathers a set of parseable files from the files and directories listed in
+ # +files+.
+
+ def gather_files files
+ files = ["."] if files.empty?
+
+ file_list = normalized_file_list files, true, @exclude
+
+ file_list = file_list.uniq
+
+ file_list = remove_unparseable file_list
+ end
+
+ ##
+ # Turns RDoc from stdin into HTML
+
+ def handle_pipe
+ @html = RDoc::Markup::ToHtml.new
+
+ out = @html.convert $stdin.read
+
+ $stdout.write out
+ end
+
+ ##
+ # Installs a siginfo handler that prints the current filename.
+
+ def install_siginfo_handler
+ return unless Signal.list.include? 'INFO'
+
+ @old_siginfo = trap 'INFO' do
+ puts @current if @current
end
+ end
- def initialize
- @stats = nil
+ ##
+ # Create an output dir if it doesn't exist. If it does exist, but doesn't
+ # contain the flag file <tt>created.rid</tt> then we refuse to use it, as
+ # we may clobber some manually generated documentation
+
+ def setup_output_dir(op_dir, force)
+ flag_file = output_flag_file op_dir
+
+ if File.exist? op_dir then
+ unless File.directory? op_dir then
+ error "'#{op_dir}' exists, and is not a directory"
+ end
+ begin
+ created = File.read(flag_file)
+ rescue SystemCallError
+ error "\nDirectory #{op_dir} already exists, but it looks like it\n" +
+ "isn't an RDoc directory. Because RDoc doesn't want to risk\n" +
+ "destroying any of your existing files, you'll need to\n" +
+ "specify a different output directory name (using the\n" +
+ "--op <dir> option).\n\n"
+ else
+ last = (Time.parse(created) unless force rescue nil)
+ end
+ else
+ FileUtils.mkdir_p(op_dir)
end
- ##
- # Report an error message and exit
+ last
+ end
+
+ ##
+ # Update the flag file in an output directory.
+
+ def update_output_dir(op_dir, time)
+ File.open(output_flag_file(op_dir), "w") { |f| f.puts time.rfc2822 }
+ end
+
+ ##
+ # Return the path name of the flag file in an output directory.
+
+ def output_flag_file(op_dir)
+ File.join op_dir, "created.rid"
+ end
+
+ ##
+ # The .document file contains a list of file and directory name patterns,
+ # representing candidates for documentation. It may also contain comments
+ # (starting with '#')
+
+ def parse_dot_doc_file in_dir, filename
+ # read and strip comments
+ patterns = File.read(filename).gsub(/#.*/, '')
- def error(msg)
- raise ::RDoc::Error, msg
+ result = []
+
+ patterns.split.each do |patt|
+ candidates = Dir.glob(File.join(in_dir, patt))
+ result.concat normalized_file_list(candidates)
end
- ##
- # Create an output dir if it doesn't exist. If it does exist, but doesn't
- # contain the flag file <tt>created.rid</tt> then we refuse to use it, as
- # we may clobber some manually generated documentation
+ result
+ end
+
+ ##
+ # Given a list of files and directories, create a list of all the Ruby
+ # files they contain.
+ #
+ # If +force_doc+ is true we always add the given files, if false, only
+ # add files that we guarantee we can parse. It is true when looking at
+ # files given on the command line, false when recursing through
+ # subdirectories.
+ #
+ # The effect of this is that if you want a file with a non-standard
+ # extension parsed, you must name it explicitly.
+
+ def normalized_file_list(relative_files, force_doc = false,
+ exclude_pattern = nil)
+ file_list = []
+
+ relative_files.each do |rel_file_name|
+ next if exclude_pattern && exclude_pattern =~ rel_file_name
+ stat = File.stat rel_file_name rescue next
- def setup_output_dir(op_dir, force)
- flag_file = output_flag_file(op_dir)
- if File.exist?(op_dir)
- unless File.directory?(op_dir)
- error "'#{op_dir}' exists, and is not a directory"
+ case type = stat.ftype
+ when "file"
+ next if @last_created and stat.mtime < @last_created
+
+ if force_doc or RDoc::Parser.can_parse(rel_file_name) then
+ file_list << rel_file_name.sub(/^\.\//, '')
end
- begin
- created = File.read(flag_file)
- rescue SystemCallError
- error "\nDirectory #{op_dir} already exists, but it looks like it\n" +
- "isn't an RDoc directory. Because RDoc doesn't want to risk\n" +
- "destroying any of your existing files, you'll need to\n" +
- "specify a different output directory name (using the\n" +
- "--op <dir> option).\n\n"
+ when "directory"
+ next if rel_file_name == "CVS" || rel_file_name == ".svn"
+
+ dot_doc = File.join rel_file_name, RDoc::DOT_DOC_FILENAME
+
+ if File.file?(dot_doc) then
+ file_list << parse_dot_doc_file(rel_file_name, dot_doc)
else
- last = (Time.parse(created) unless force rescue nil)
+ file_list << list_files_in_directory(rel_file_name)
end
else
- FileUtils.mkdir_p(op_dir)
+ raise RDoc::Error, "I can't deal with a #{type} #{rel_file_name}"
end
- last
end
- ##
- # Update the flag file in an output directory.
+ file_list.flatten
+ end
- def update_output_dir(op_dir, time)
- File.open(output_flag_file(op_dir), "w") {|f| f.puts time.rfc2822 }
- end
+ ##
+ # Return a list of the files to be processed in a directory. We know that
+ # this directory doesn't have a .document file, so we're looking for real
+ # files. However we may well contain subdirectories which must be tested
+ # for .document files.
- ##
- # Return the path name of the flag file in an output directory.
+ def list_files_in_directory dir
+ files = Dir.glob File.join(dir, "*")
- def output_flag_file(op_dir)
- File.join(op_dir, "created.rid")
- end
+ normalized_file_list files, false, @options.exclude
+ end
- ##
- # The .document file contains a list of file and directory name patterns,
- # representing candidates for documentation. It may also contain comments
- # (starting with '#')
+ ##
+ # Parses +filename+ and returns an RDoc::TopLevel
- def parse_dot_doc_file(in_dir, filename, options)
- # read and strip comments
- patterns = File.read(filename).gsub(/#.*/, '')
+ def parse_file filename
+ @stats.add_file filename
+ content = read_file_contents filename
- result = []
+ return unless content
- patterns.split.each do |patt|
- candidates = Dir.glob(File.join(in_dir, patt))
- result.concat(normalized_file_list(options, candidates))
- end
- result
- end
+ top_level = RDoc::TopLevel.new filename
- ##
- # Given a list of files and directories, create a list of all the Ruby
- # files they contain.
- #
- # If +force_doc+ is true we always add the given files, if false, only
- # add files that we guarantee we can parse. It is true when looking at
- # files given on the command line, false when recursing through
- # subdirectories.
- #
- # The effect of this is that if you want a file with a non-standard
- # extension parsed, you must name it explicitly.
-
- def normalized_file_list(options, relative_files, force_doc = false,
- exclude_pattern = nil)
- file_list = []
-
- relative_files.each do |rel_file_name|
- next if exclude_pattern && exclude_pattern =~ rel_file_name
- stat = File.stat(rel_file_name)
- case type = stat.ftype
- when "file"
- next if @last_created and stat.mtime < @last_created
-
- if force_doc or ::RDoc::Parser.can_parse(rel_file_name) then
- file_list << rel_file_name.sub(/^\.\//, '')
- end
- when "directory"
- next if rel_file_name == "CVS" || rel_file_name == ".svn"
- dot_doc = File.join(rel_file_name, DOT_DOC_FILENAME)
- if File.file?(dot_doc)
- file_list.concat(parse_dot_doc_file(rel_file_name, dot_doc, options))
- else
- file_list.concat(list_files_in_directory(rel_file_name, options))
- end
- else
- raise RDoc::Error, "I can't deal with a #{type} #{rel_file_name}"
- end
- end
+ parser = RDoc::Parser.for top_level, filename, content, @options, @stats
- file_list
- end
+ return unless parser
- ##
- # Return a list of the files to be processed in a directory. We know that
- # this directory doesn't have a .document file, so we're looking for real
- # files. However we may well contain subdirectories which must be tested
- # for .document files.
+ parser.scan
+ rescue => e
+ $stderr.puts <<-EOF
+Before reporting this, could you check that the file you're documenting
+compiles cleanly--RDoc is not a full Ruby parser, and gets confused easily if
+fed invalid programs.
- def list_files_in_directory(dir, options)
- files = Dir.glob File.join(dir, "*")
+The internal error was:
- normalized_file_list options, files, false, options.exclude
- end
+\t(#{e.class}) #{e.message}
- ##
- # Parse each file on the command line, recursively entering directories.
+ EOF
- def parse_files(options)
- @stats = Stats.new options.verbosity
+ $stderr.puts e.backtrace.join("\n\t") if $RDOC_DEBUG
- files = options.files
- files = ["."] if files.empty?
+ raise e
+ nil
+ end
- file_list = normalized_file_list(options, files, true, options.exclude)
+ ##
+ # Parse each file on the command line, recursively entering directories.
- return [] if file_list.empty?
+ def parse_files files
+ file_list = gather_files files
- file_info = []
+ return [] if file_list.empty?
- file_list.each do |filename|
- @stats.add_file filename
+ file_info = []
- content = File.open(filename, "rb") { |f| f.read }
- content.gsub!(/\r$/, '')
+ @stats = RDoc::Stats.new file_list.size, @options.verbosity
+ @stats.begin_adding
- if defined?(::Encoding) then
- if /coding[=:]\s*([^\s;]+)/ =~ content[/\A(?:!.*\n)?(.*\n)/, 1]
- if enc = ::Encoding.find($1)
- content.force_encoding(enc)
- end
- end
- end
+ file_info = file_list.map do |filename|
+ @current = filename
+ parse_file filename
+ end.compact
- top_level = ::RDoc::TopLevel.new filename
+ @stats.done_adding
- parser = ::RDoc::Parser.for top_level, filename, content, options,
- @stats
+ file_info
+ end
- file_info << parser.scan
- end
+ ##
+ # Removes file extensions known to be unparseable from +files+
- file_info
+ def remove_unparseable files
+ files.reject do |file|
+ file =~ /\.(?:class|eps|erb|scpt\.txt|ttf|yml)$/i
end
+ end
- ##
- # Format up one or more files according to the given arguments.
- #
- # For simplicity, _argv_ is an array of strings, equivalent to the strings
- # that would be passed on the command line. (This isn't a coincidence, as
- # we _do_ pass in ARGV when running interactively). For a list of options,
- # see rdoc/rdoc.rb. By default, output will be stored in a directory
- # called +doc+ below the current directory, so make sure you're somewhere
- # writable before invoking.
- #
- # Throws: RDoc::Error on error
-
- def document(argv)
- TopLevel::reset
+ ##
+ # Format up one or more files according to the given arguments.
+ #
+ # For simplicity, +argv+ is an array of strings, equivalent to the strings
+ # that would be passed on the command line. (This isn't a coincidence, as
+ # we _do_ pass in ARGV when running interactively). For a list of options,
+ # see rdoc/rdoc.rb. By default, output will be stored in a directory
+ # called +doc+ below the current directory, so make sure you're somewhere
+ # writable before invoking.
+ #
+ # Throws: RDoc::Error on error
- @options = Options.new GENERATORS
- @options.parse argv
+ def document(argv)
+ RDoc::TopLevel.reset
+ RDoc::Parser::C.reset
+ RDoc::AnyMethod.reset
- @last_created = nil
+ @options = RDoc::Options.new
+ @options.parse argv
- unless @options.all_one_file then
- @last_created = setup_output_dir @options.op_dir, @options.force_update
- end
+ if @options.pipe then
+ handle_pipe
+ exit
+ end
- start_time = Time.now
+ @exclude = @options.exclude
- file_info = parse_files @options
+ @last_created = setup_output_dir @options.op_dir, @options.force_update
- @options.title = "RDoc Documentation"
+ start_time = Time.now
- if file_info.empty?
- $stderr.puts "\nNo newer files." unless @options.quiet
- else
- @gen = @options.generator
+ file_info = parse_files @options.files
- $stderr.puts "\nGenerating #{@gen.key.upcase}..." unless @options.quiet
+ @options.title = "RDoc Documentation"
- require @gen.file_name
+ if file_info.empty?
+ $stderr.puts "\nNo newer files." unless @options.quiet
+ else
+ gen_klass = @options.generator
- gen_class = ::RDoc::Generator.const_get @gen.class_name
- @gen = gen_class.for @options
+ unless @options.quiet then
+ $stderr.puts "\nGenerating #{gen_klass.name.sub(/^.*::/, '')}..."
+ end
- pwd = Dir.pwd
+ @generator = gen_klass.for @options
- Dir.chdir @options.op_dir unless @options.all_one_file
+ pwd = Dir.pwd
+ Dir.chdir @options.op_dir do
begin
- Diagram.new(file_info, @options).draw if @options.diagram
- @gen.generate(file_info)
- update_output_dir(".", start_time)
+ self.class.current = self
+
+ @generator.generate file_info
+ update_output_dir ".", start_time
ensure
- Dir.chdir(pwd)
+ self.class.current = nil
+ end
+ end
+ end
+
+ unless @options.quiet or not @stats then
+ puts
+ @stats.print
+ end
+ end
+
+ def read_file_contents(filename)
+ content = if RUBY_VERSION >= '1.9' then
+ File.open(filename, "r:ascii-8bit") { |f| f.read }
+ else
+ File.read filename
+ end
+
+ if defined? Encoding then
+ if /coding:\s*([\w-]+)/i =~ content[/\A(?:.*\n){0,2}/]
+ if enc = ::Encoding.find($1)
+ content.force_encoding(enc)
end
end
+ end
+
+ content
+ rescue Errno::EISDIR, Errno::ENOENT
+ nil
+ end
+
+ ##
+ # Removes a siginfo handler and replaces the previous
+
+ def remove_siginfo_handler
+ return unless Signal.list.key? 'INFO'
- unless @options.quiet
- puts
- @stats.print
+ handler = @old_siginfo || 'DEFAULT'
+
+ trap 'INFO', handler
+ end
+
+end
+
+begin
+ require 'rubygems'
+
+ if Gem.respond_to? :find_files then
+ rdoc_extensions = Gem.find_files 'rdoc/discover'
+
+ rdoc_extensions.each do |extension|
+ begin
+ load extension
+ rescue => e
+ warn "error loading #{extension.inspect}: #{e.message} (#{e.class})"
end
end
end
+rescue LoadError
end
+# require built-in generators after discovery in case they've been replaced
+require 'rdoc/generator/darkfish'
+require 'rdoc/generator/ri'
+