summaryrefslogtreecommitdiff
path: root/jni/ruby/lib/rdoc/rdoc.rb
diff options
context:
space:
mode:
Diffstat (limited to 'jni/ruby/lib/rdoc/rdoc.rb')
-rw-r--r--jni/ruby/lib/rdoc/rdoc.rb570
1 files changed, 570 insertions, 0 deletions
diff --git a/jni/ruby/lib/rdoc/rdoc.rb b/jni/ruby/lib/rdoc/rdoc.rb
new file mode 100644
index 0000000..4d45d47
--- /dev/null
+++ b/jni/ruby/lib/rdoc/rdoc.rb
@@ -0,0 +1,570 @@
+require 'rdoc'
+
+require 'find'
+require 'fileutils'
+require 'pathname'
+require 'time'
+
+##
+# This is the driver for generating RDoc output. It handles file parsing and
+# generation of output.
+#
+# To use this class to generate RDoc output via the API, the recommended way
+# is:
+#
+# rdoc = RDoc::RDoc.new
+# options = rdoc.load_options # returns an RDoc::Options instance
+# # set extra options
+# rdoc.document options
+#
+# You can also generate output like the +rdoc+ executable:
+#
+# rdoc = RDoc::RDoc.new
+# rdoc.document argv
+#
+# Where +argv+ is an array of strings, each corresponding to an argument you'd
+# give rdoc on the command line. See <tt>rdoc --help<tt> for details.
+
+class RDoc::RDoc
+
+ @current = nil
+
+ ##
+ # This is the list of supported output generators
+
+ GENERATORS = {}
+
+ ##
+ # File pattern to exclude
+
+ attr_accessor :exclude
+
+ ##
+ # Generator instance used for creating output
+
+ attr_accessor :generator
+
+ ##
+ # Hash of files and their last modified times.
+
+ attr_reader :last_modified
+
+ ##
+ # RDoc options
+
+ attr_accessor :options
+
+ ##
+ # Accessor for statistics. Available after each call to parse_files
+
+ attr_reader :stats
+
+ ##
+ # The current documentation store
+
+ attr_reader :store
+
+ ##
+ # 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
+
+ ##
+ # Creates a new RDoc::RDoc instance. Call #document to parse files and
+ # generate documentation.
+
+ def initialize
+ @current = nil
+ @exclude = nil
+ @generator = nil
+ @last_modified = {}
+ @old_siginfo = nil
+ @options = nil
+ @stats = nil
+ @store = 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
+
+ file_list.sort
+ end
+
+ ##
+ # Turns RDoc from stdin into HTML
+
+ 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
+
+ ##
+ # 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
+
+ ##
+ # Loads options from .rdoc_options if the file exists, otherwise creates a
+ # new RDoc::Options instance.
+
+ def load_options
+ options_file = File.expand_path '.rdoc_options'
+ return RDoc::Options.new unless File.exist? options_file
+
+ RDoc.load_yaml
+
+ parse_error = if Object.const_defined? :Psych then
+ Psych::SyntaxError
+ else
+ ArgumentError
+ end
+
+ begin
+ options = YAML.load_file '.rdoc_options'
+ rescue *parse_error
+ end
+
+ raise RDoc::Error, "#{options_file} is not a valid rdoc options file" unless
+ RDoc::Options === options
+
+ options
+ 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(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
+ 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
+
+Directory #{dir} already exists, but it looks like it isn't an RDoc directory.
+
+Because RDoc doesn't want to risk destroying any of your existing files,
+you'll need to specify a different output directory name (using the --op <dir>
+option)
+
+ ERROR
+ end unless @options.force_output
+ else
+ FileUtils.mkdir_p dir
+ FileUtils.touch flag_file
+ end
+
+ last
+ end
+
+ ##
+ # Sets the current documentation tree to +store+ and sets the store's rdoc
+ # driver to this instance.
+
+ def store= store
+ @store = store
+ @store.rdoc = self
+ end
+
+ ##
+ # Update the flag file in an output directory.
+
+ def update_output_dir(op_dir, time, last = {})
+ return if @options.dry_run or not @options.update_output_dir
+
+ 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
+
+ ##
+ # 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(/#.*/, '')
+
+ result = []
+
+ patterns.split.each do |patt|
+ candidates = Dir.glob(File.join(in_dir, patt))
+ result.concat normalized_file_list(candidates)
+ end
+
+ 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 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
+ next if 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.sub(/^\.\//, '')
+ @last_modified[rel_file_name] = stat.mtime
+ end
+ when "directory" then
+ next if rel_file_name == "CVS" || rel_file_name == ".svn"
+
+ 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 << parse_dot_doc_file(rel_file_name, dot_doc)
+ else
+ file_list << list_files_in_directory(rel_file_name)
+ end
+ else
+ warn "rdoc can't parse the #{type} #{rel_file_name}"
+ end
+ end
+
+ file_list.flatten
+ 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.
+
+ def list_files_in_directory dir
+ files = Dir.glob File.join(dir, "*")
+
+ normalized_file_list files, false, @options.exclude
+ end
+
+ ##
+ # Parses +filename+ and returns an RDoc::TopLevel
+
+ def parse_file filename
+ if Object.const_defined? :Encoding then
+ encoding = @options.encoding
+ filename = filename.encode encoding
+ end
+
+ @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
+ relative_path = filename_path.relative_path_from @options.root
+
+ 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_path.to_s
+
+ parser = RDoc::Parser.for top_level, filename, 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
+Unable to read #{filename}, #{e.message}
+
+Please check the permissions for this file. Perhaps you do not have access to
+it or perhaps the original author's permissions are to restrictive. If the
+this is not your library please report a bug to the author.
+ EOF
+ rescue => e
+ $stderr.puts <<-EOF
+Before reporting this, could you check that the file you're documenting
+has proper syntax:
+
+ #{Gem.ruby} -c #{filename}
+
+RDoc is not a full Ruby parser and will fail when fed invalid ruby programs.
+
+The internal error was:
+
+\t(#{e.class}) #{e.message}
+
+ EOF
+
+ $stderr.puts e.backtrace.join("\n\t") if $DEBUG_RDOC
+
+ raise e
+ nil
+ end
+
+ ##
+ # Parse each file on the command line, recursively entering directories.
+
+ def parse_files files
+ file_list = gather_files files
+ @stats = RDoc::Stats.new @store, file_list.length, @options.verbosity
+
+ return [] if file_list.empty?
+
+ @stats.begin_adding
+
+ file_info = file_list.map do |filename|
+ @current = filename
+ parse_file filename
+ end.compact
+
+ @stats.done_adding
+
+ file_info
+ end
+
+ ##
+ # Removes file extensions known to be unparseable from +files+ and TAGS
+ # files for emacs and vim.
+
+ def remove_unparseable files
+ files.reject do |file|
+ file =~ /\.(?:class|eps|erb|scpt\.txt|ttf|yml)$/i or
+ (file =~ /tags$/i and
+ open(file, 'rb') { |io|
+ io.read(100) =~ /\A(\f\n[^,]+,\d+$|!_TAG_)/
+ })
+ end
+ end
+
+ ##
+ # Generates documentation or a coverage report depending upon the settings
+ # in +options+.
+ #
+ # +options+ can be either an RDoc::Options instance or an array of strings
+ # equivalent to the strings that would be passed on the command line like
+ # <tt>%w[-q -o doc -t My\ Doc\ Title]</tt>. #document will automatically
+ # call RDoc::Options#finish if an options instance was given.
+ #
+ # For a list of options, see either RDoc::Options or <tt>rdoc --help</tt>.
+ #
+ # By default, output will be stored in a directory called "doc" below the
+ # current directory, so make sure you're somewhere writable before invoking.
+
+ def document options
+ self.store = RDoc::Store.new
+
+ if RDoc::Options === options then
+ @options = options
+ @options.finish
+ else
+ @options = load_options
+ @options.parse options
+ end
+
+ if @options.pipe then
+ handle_pipe
+ exit
+ end
+
+ @exclude = @options.exclude
+
+ unless @options.coverage_report then
+ @last_modified = setup_output_dir @options.op_dir, @options.force_update
+ end
+
+ @store.encoding = @options.encoding if @options.respond_to? :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
+
+ ##
+ # Generates documentation for +file_info+ (from #parse_files) into the
+ # output dir using the generator selected
+ # by the RDoc options
+
+ def generate
+ Dir.chdir @options.op_dir do
+ unless @options.quiet then
+ $stderr.puts "\nGenerating #{@generator.class.name.sub(/^.*::/, '')} format into #{Dir.pwd}..."
+ end
+
+ @generator.generate
+ update_output_dir '.', @start_time, @last_modified
+ end
+ end
+
+ ##
+ # Removes a siginfo handler and replaces the previous
+
+ def remove_siginfo_handler
+ return unless Signal.list.key? 'INFO'
+
+ 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})"
+ warn "\t#{e.backtrace.join "\n\t"}" if $DEBUG
+ 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'
+require 'rdoc/generator/pot'
+