class RDoc::RDoc

def self.add_generator(klass)

def self.add_generator(klass)
  name = klass.name.sub(/^RDoc::Generator::/, '').downcase
  GENERATORS[name] = klass
end

def self.current

def self.current
  @current
end

def self.current= rdoc

def self.current= rdoc
  @current = rdoc
end

def document options

def document options
  self.store = RDoc::Store.new
  if RDoc::Options === options then
    @options = options
  else
    @options = RDoc::Options.load_options
    @options.parse options
  end
  @options.finish
  if @options.pipe then
    handle_pipe
    exit
  end
  unless @options.coverage_report then
    @last_modified = setup_output_dir @options.op_dir, @options.force_update
  end
  @store.encoding = @options.encoding
  @store.dry_run  = @options.dry_run
  @store.main     = @options.main_page
  @store.title    = @options.title
  @store.path     = @options.op_dir
  @start_time = Time.now
  @store.load_cache
  file_info = parse_files @options.files
  @options.default_title = "RDoc Documentation"
  @store.complete @options.visibility
  @stats.coverage_level = @options.coverage_report
  if @options.coverage_report then
    puts
    puts @stats.report.accept RDoc::Markup::ToRdoc.new
  elsif file_info.empty? then
    $stderr.puts "\nNo newer files." unless @options.quiet
  else
    gen_klass = @options.generator
    @generator = gen_klass.new @store, @options
    generate
  end
  if @stats and (@options.coverage_report or not @options.quiet) then
    puts
    puts @stats.summary.accept RDoc::Markup::ToRdoc.new
  end
  exit @stats.fully_documented? if @options.coverage_report
end

def error(msg)

def error(msg)
  raise RDoc::Error, msg
end

def gather_files files

def gather_files files
  files = [@options.root.to_s] if files.empty?
  file_list = normalized_file_list files, true, @options.exclude
  file_list = remove_unparseable(file_list)
  if file_list.count {|name, mtime|
       file_list[name] = @last_modified[name] unless mtime
       mtime
     } > 0
    @last_modified.replace file_list
    file_list.keys.sort
  else
    []
  end
end

def generate

def generate
  if @options.dry_run then
    # do nothing
    @generator.generate
  else
    Dir.chdir @options.op_dir do
      unless @options.quiet then
        $stderr.puts "\nGenerating #{@generator.class.name.sub(/^.*::/, '')} format into #{Dir.pwd}..."
        $stderr.puts "\nYou can visit the home page at: \e]8;;file://#{Dir.pwd}/index.html\e\\file://#{Dir.pwd}/index.html\e]8;;\e\\"
      end
      @generator.generate
      update_output_dir '.', @start_time, @last_modified
    end
  end
end

def handle_pipe

def handle_pipe
  @html = RDoc::Markup::ToHtml.new @options
  parser = RDoc::Text::MARKUP_FORMAT[@options.markup]
  document = parser.parse $stdin.read
  out = @html.convert document
  $stdout.write out
end

def initialize

def initialize
  @current       = nil
  @generator     = nil
  @last_modified = {}
  @old_siginfo   = nil
  @options       = nil
  @stats         = nil
  @store         = nil
end

def install_siginfo_handler

def install_siginfo_handler
  return unless Signal.list.include? 'INFO'
  @old_siginfo = trap 'INFO' do
    puts @current if @current
  end
end

def list_files_in_directory dir

def list_files_in_directory dir
  files = Dir.glob File.join(dir, "*")
  normalized_file_list files, false, @options.exclude
end

def normalized_file_list(relative_files, force_doc = false,

def normalized_file_list(relative_files, force_doc = false,
                         exclude_pattern = nil)
  file_list = {}
  relative_files.each do |rel_file_name|
    rel_file_name = rel_file_name.sub(/^\.\//, '')
    next if rel_file_name.end_with? 'created.rid'
    next if exclude_pattern && exclude_pattern =~ rel_file_name
    stat = File.stat rel_file_name rescue next
    case type = stat.ftype
    when "file" then
      mtime = (stat.mtime unless (last_modified = @last_modified[rel_file_name] and
                                  stat.mtime.to_i <= last_modified.to_i))
      if force_doc or RDoc::Parser.can_parse(rel_file_name) then
        file_list[rel_file_name] = mtime
      end
    when "directory" then
      next if UNCONDITIONALLY_SKIPPED_DIRECTORIES.include?(rel_file_name)
      basename = File.basename(rel_file_name)
      next if options.skip_tests && TEST_SUITE_DIRECTORY_NAMES.include?(basename)
      created_rid = File.join rel_file_name, "created.rid"
      next if File.file? created_rid
      dot_doc = File.join rel_file_name, RDoc::DOT_DOC_FILENAME
      if File.file? dot_doc then
        file_list.update(parse_dot_doc_file(rel_file_name, dot_doc))
      else
        file_list.update(list_files_in_directory(rel_file_name))
      end
    else
      warn "rdoc can't parse the #{type} #{rel_file_name}"
    end
  end
  file_list
end

def output_flag_file(op_dir)

def output_flag_file(op_dir)
  File.join op_dir, "created.rid"
end

def parse_dot_doc_file in_dir, filename

def parse_dot_doc_file in_dir, filename
  # read and strip comments
  patterns = File.read(filename).gsub(/#.*/, '')
  result = {}
  patterns.split(' ').each do |patt|
    candidates = Dir.glob(File.join(in_dir, patt))
    result.update normalized_file_list(candidates, false, @options.exclude)
  end
  result
end

def parse_file filename

def parse_file filename
  encoding = @options.encoding
  filename = filename.encode encoding
  @stats.add_file filename
  return if RDoc::Parser.binary? filename
  content = RDoc::Encoding.read_file filename, encoding
  return unless content
  filename_path = Pathname(filename).expand_path
  begin
    relative_path = filename_path.relative_path_from @options.root
  rescue ArgumentError
    relative_path = filename_path
  end
  if @options.page_dir and
     relative_path.to_s.start_with? @options.page_dir.to_s then
    relative_path =
      relative_path.relative_path_from @options.page_dir
  end
  top_level = @store.add_file filename, relative_name: relative_path.to_s
  parser = RDoc::Parser.for top_level, content, @options, @stats
  return unless parser
  parser.scan
  # restart documentation for the classes & modules found
  top_level.classes_or_modules.each do |cm|
    cm.done_documenting = false
  end
  top_level
rescue Errno::EACCES => e
  $stderr.puts <<-EOF
able to read #{filename}, #{e.message}
ease check the permissions for this file.  Perhaps you do not have access to
 or perhaps the original author's permissions are to restrictive.  If the
is is not your library please report a bug to the author.
  EOF
rescue => e
  $stderr.puts <<-EOF
fore reporting this, could you check that the file you're documenting
s proper syntax:
#{Gem.ruby} -c #{filename}
oc is not a full Ruby parser and will fail when fed invalid ruby programs.
e internal error was:
(#{e.class}) #{e.message}
  EOF
  $stderr.puts e.backtrace.join("\n\t") if $DEBUG_RDOC
  raise e
end

def parse_files files

def parse_files files
  file_list = gather_files files
  @stats = RDoc::Stats.new @store, file_list.length, @options.verbosity
  return [] if file_list.empty?
  # This workaround can be removed after the :main: directive is removed
  original_options = @options.dup
  @stats.begin_adding
  file_info = file_list.map do |filename|
    @current = filename
    parse_file filename
  end.compact
  @store.resolve_c_superclasses
  @stats.done_adding
  @options = original_options
  file_info
end

def remove_siginfo_handler

def remove_siginfo_handler
  return unless Signal.list.key? 'INFO'
  handler = @old_siginfo || 'DEFAULT'
  trap 'INFO', handler
end

def remove_unparseable files

def remove_unparseable files
  files.reject do |file, *|
    file =~ /\.(?:class|eps|erb|scpt\.txt|svg|ttf|yml)$/i or
      (file =~ /tags$/i and
       /\A(\f\n[^,]+,\d+$|!_TAG_)/.match?(File.binread(file, 100)))
  end
end

def setup_output_dir(dir, force)

def setup_output_dir(dir, force)
  flag_file = output_flag_file dir
  last = {}
  if @options.dry_run then
    # do nothing
  elsif File.exist? dir then
    error "#{dir} exists and is not a directory" unless File.directory? dir
    begin
      File.open flag_file do |io|
        unless force then
          Time.parse io.gets
          io.each do |line|
            file, time = line.split "\t", 2
            time = Time.parse(time) rescue next
            last[file] = time
          end
        end
      end
    rescue SystemCallError, TypeError
      error <<-ERROR
rectory #{dir} already exists, but it looks like it isn't an RDoc directory.
cause RDoc doesn't want to risk destroying any of your existing files,
u'll need to specify a different output directory name (using the --op <dir>
tion)
      ERROR
    end unless @options.force_output
  else
    FileUtils.mkdir_p dir
    FileUtils.touch flag_file
  end
  last
end

def store= store

def store= store
  @store = store
  @store.rdoc = self
end

def update_output_dir(op_dir, time, last = {})

def update_output_dir(op_dir, time, last = {})
  return if @options.dry_run or not @options.update_output_dir
  unless ENV['SOURCE_DATE_EPOCH'].nil?
    time = Time.at(ENV['SOURCE_DATE_EPOCH'].to_i).gmtime
  end
  File.open output_flag_file(op_dir), "w" do |f|
    f.puts time.rfc2822
    last.each do |n, t|
      f.puts "#{n}\t#{t.rfc2822}"
    end
  end
end