# See README. # RDOC_VERSION = "1.0pr1" rcs = '$Date$ $Revision$'. gsub(/\$/, ''). sub(/Date: /, ': '). sub(/ Revision: (\S+)/) { "(#$1)" } VERSION_STRING = %{RDoc V} + RDOC_VERSION + rcs 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 # 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 created.rid # 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 option).\n\n" end else File.makedirs(op_dir) end File.open(flag_file, "w") {|f| f.puts Time.now } end # Given a list of files and directories, create a list # of all the Ruby files they contain. def normalized_file_list(options, *relative_files) file_list = [] relative_files.each do |rel_file_name| case type = File.stat(rel_file_name).ftype when "file" file_list << rel_file_name when "directory" next if options.exclude && options.exclude =~ rel_file_name Find.find(rel_file_name) do |fn| next if options.exclude && options.exclude =~ fn next unless ParserFactory.can_parse(fn) next unless File.file?(fn) file_list << fn.sub(%r{\./}, '') end else raise RDocError.new("I can't deal with a #{type} #{rel_file_name}") end end file_list end # Parse each file on the command line, recursively entering # directories def parse_files(options) file_info = [] files = options.files files = ["."] if files.empty? file_list = normalized_file_list(options, *files) 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) parser = ParserFactory.parser_for(top_level, fn, content, options) file_info << parser.scan 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 options = Options.instance options.parse(argv, GENERATORS) 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 unless options.all_one_file setup_output_dir(options.op_dir) Dir.chdir(options.op_dir) end begin Diagram.new(file_info, options).draw if options.diagram gen.generate(file_info) ensure Dir.chdir(pwd) end end end end end