diff options
author | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2010-04-01 07:45:16 +0000 |
---|---|---|
committer | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2010-04-01 07:45:16 +0000 |
commit | 46580b51477355fece514573c88cb67030f4a502 (patch) | |
tree | 779c1a64466643461b3daa4cd9a3548b84f0fd55 /lib/rdoc/rdoc.rb | |
parent | 9b40cdfe8c973a061c5683ad78c283b9ddb8b2e9 (diff) | |
download | ruby-46580b51477355fece514573c88cb67030f4a502.tar.gz |
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.rb | 543 |
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' + |