"Fossies" - the Fresh Open Source Software Archive

Member "ruby-2.7.4/lib/rdoc/rdoc.rb" (7 Jul 2021, 13866 Bytes) of package /linux/misc/ruby-2.7.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Ruby source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "rdoc.rb": 2.7.3_vs_2.7.4.

    1 # frozen_string_literal: true
    2 require 'rdoc'
    3 
    4 require 'find'
    5 require 'fileutils'
    6 require 'pathname'
    7 require 'time'
    8 
    9 ##
   10 # This is the driver for generating RDoc output.  It handles file parsing and
   11 # generation of output.
   12 #
   13 # To use this class to generate RDoc output via the API, the recommended way
   14 # is:
   15 #
   16 #   rdoc = RDoc::RDoc.new
   17 #   options = rdoc.load_options # returns an RDoc::Options instance
   18 #   # set extra options
   19 #   rdoc.document options
   20 #
   21 # You can also generate output like the +rdoc+ executable:
   22 #
   23 #   rdoc = RDoc::RDoc.new
   24 #   rdoc.document argv
   25 #
   26 # Where +argv+ is an array of strings, each corresponding to an argument you'd
   27 # give rdoc on the command line.  See <tt>rdoc --help</tt> for details.
   28 
   29 class RDoc::RDoc
   30 
   31   @current = nil
   32 
   33   ##
   34   # This is the list of supported output generators
   35 
   36   GENERATORS = {}
   37 
   38   ##
   39   # Generator instance used for creating output
   40 
   41   attr_accessor :generator
   42 
   43   ##
   44   # Hash of files and their last modified times.
   45 
   46   attr_reader :last_modified
   47 
   48   ##
   49   # RDoc options
   50 
   51   attr_accessor :options
   52 
   53   ##
   54   # Accessor for statistics.  Available after each call to parse_files
   55 
   56   attr_reader :stats
   57 
   58   ##
   59   # The current documentation store
   60 
   61   attr_reader :store
   62 
   63   ##
   64   # Add +klass+ that can generate output after parsing
   65 
   66   def self.add_generator(klass)
   67     name = klass.name.sub(/^RDoc::Generator::/, '').downcase
   68     GENERATORS[name] = klass
   69   end
   70 
   71   ##
   72   # Active RDoc::RDoc instance
   73 
   74   def self.current
   75     @current
   76   end
   77 
   78   ##
   79   # Sets the active RDoc::RDoc instance
   80 
   81   def self.current= rdoc
   82     @current = rdoc
   83   end
   84 
   85   ##
   86   # Creates a new RDoc::RDoc instance.  Call #document to parse files and
   87   # generate documentation.
   88 
   89   def initialize
   90     @current       = nil
   91     @generator     = nil
   92     @last_modified = {}
   93     @old_siginfo   = nil
   94     @options       = nil
   95     @stats         = nil
   96     @store         = nil
   97   end
   98 
   99   ##
  100   # Report an error message and exit
  101 
  102   def error(msg)
  103     raise RDoc::Error, msg
  104   end
  105 
  106   ##
  107   # Gathers a set of parseable files from the files and directories listed in
  108   # +files+.
  109 
  110   def gather_files files
  111     files = ["."] if files.empty?
  112 
  113     file_list = normalized_file_list files, true, @options.exclude
  114 
  115     file_list = file_list.uniq
  116 
  117     file_list = remove_unparseable file_list
  118 
  119     file_list.sort
  120   end
  121 
  122   ##
  123   # Turns RDoc from stdin into HTML
  124 
  125   def handle_pipe
  126     @html = RDoc::Markup::ToHtml.new @options
  127 
  128     parser = RDoc::Text::MARKUP_FORMAT[@options.markup]
  129 
  130     document = parser.parse $stdin.read
  131 
  132     out = @html.convert document
  133 
  134     $stdout.write out
  135   end
  136 
  137   ##
  138   # Installs a siginfo handler that prints the current filename.
  139 
  140   def install_siginfo_handler
  141     return unless Signal.list.include? 'INFO'
  142 
  143     @old_siginfo = trap 'INFO' do
  144       puts @current if @current
  145     end
  146   end
  147 
  148   ##
  149   # Loads options from .rdoc_options if the file exists, otherwise creates a
  150   # new RDoc::Options instance.
  151 
  152   def load_options
  153     options_file = File.expand_path '.rdoc_options'
  154     return RDoc::Options.new unless File.exist? options_file
  155 
  156     RDoc.load_yaml
  157 
  158     begin
  159       options = YAML.load_file '.rdoc_options'
  160     rescue Psych::SyntaxError
  161     end
  162 
  163     raise RDoc::Error, "#{options_file} is not a valid rdoc options file" unless
  164       RDoc::Options === options
  165 
  166     options
  167   end
  168 
  169   ##
  170   # Create an output dir if it doesn't exist. If it does exist, but doesn't
  171   # contain the flag file <tt>created.rid</tt> then we refuse to use it, as
  172   # we may clobber some manually generated documentation
  173 
  174   def setup_output_dir(dir, force)
  175     flag_file = output_flag_file dir
  176 
  177     last = {}
  178 
  179     if @options.dry_run then
  180       # do nothing
  181     elsif File.exist? dir then
  182       error "#{dir} exists and is not a directory" unless File.directory? dir
  183 
  184       begin
  185         File.open flag_file do |io|
  186           unless force then
  187             Time.parse io.gets
  188 
  189             io.each do |line|
  190               file, time = line.split "\t", 2
  191               time = Time.parse(time) rescue next
  192               last[file] = time
  193             end
  194           end
  195         end
  196       rescue SystemCallError, TypeError
  197         error <<-ERROR
  198 
  199 Directory #{dir} already exists, but it looks like it isn't an RDoc directory.
  200 
  201 Because RDoc doesn't want to risk destroying any of your existing files,
  202 you'll need to specify a different output directory name (using the --op <dir>
  203 option)
  204 
  205         ERROR
  206       end unless @options.force_output
  207     else
  208       FileUtils.mkdir_p dir
  209       FileUtils.touch flag_file
  210     end
  211 
  212     last
  213   end
  214 
  215   ##
  216   # Sets the current documentation tree to +store+ and sets the store's rdoc
  217   # driver to this instance.
  218 
  219   def store= store
  220     @store = store
  221     @store.rdoc = self
  222   end
  223 
  224   ##
  225   # Update the flag file in an output directory.
  226 
  227   def update_output_dir(op_dir, time, last = {})
  228     return if @options.dry_run or not @options.update_output_dir
  229     unless ENV['SOURCE_DATE_EPOCH'].nil?
  230       time = Time.at(ENV['SOURCE_DATE_EPOCH'].to_i).gmtime
  231     end
  232 
  233     File.open output_flag_file(op_dir), "w" do |f|
  234       f.puts time.rfc2822
  235       last.each do |n, t|
  236         f.puts "#{n}\t#{t.rfc2822}"
  237       end
  238     end
  239   end
  240 
  241   ##
  242   # Return the path name of the flag file in an output directory.
  243 
  244   def output_flag_file(op_dir)
  245     File.join op_dir, "created.rid"
  246   end
  247 
  248   ##
  249   # The .document file contains a list of file and directory name patterns,
  250   # representing candidates for documentation. It may also contain comments
  251   # (starting with '#')
  252 
  253   def parse_dot_doc_file in_dir, filename
  254     # read and strip comments
  255     patterns = File.read(filename).gsub(/#.*/, '')
  256 
  257     result = []
  258 
  259     patterns.split.each do |patt|
  260       candidates = Dir.glob(File.join(in_dir, patt))
  261       result.concat normalized_file_list(candidates, false, @options.exclude)
  262     end
  263 
  264     result
  265   end
  266 
  267   ##
  268   # Given a list of files and directories, create a list of all the Ruby
  269   # files they contain.
  270   #
  271   # If +force_doc+ is true we always add the given files, if false, only
  272   # add files that we guarantee we can parse.  It is true when looking at
  273   # files given on the command line, false when recursing through
  274   # subdirectories.
  275   #
  276   # The effect of this is that if you want a file with a non-standard
  277   # extension parsed, you must name it explicitly.
  278 
  279   def normalized_file_list(relative_files, force_doc = false,
  280                            exclude_pattern = nil)
  281     file_list = []
  282 
  283     relative_files.each do |rel_file_name|
  284       next if rel_file_name.end_with? 'created.rid'
  285       next if exclude_pattern && exclude_pattern =~ rel_file_name
  286       stat = File.stat rel_file_name rescue next
  287 
  288       case type = stat.ftype
  289       when "file" then
  290         next if last_modified = @last_modified[rel_file_name] and
  291                 stat.mtime.to_i <= last_modified.to_i
  292 
  293         if force_doc or RDoc::Parser.can_parse(rel_file_name) then
  294           file_list << rel_file_name.sub(/^\.\//, '')
  295           @last_modified[rel_file_name] = stat.mtime
  296         end
  297       when "directory" then
  298         next if rel_file_name == "CVS" || rel_file_name == ".svn"
  299 
  300         created_rid = File.join rel_file_name, "created.rid"
  301         next if File.file? created_rid
  302 
  303         dot_doc = File.join rel_file_name, RDoc::DOT_DOC_FILENAME
  304 
  305         if File.file? dot_doc then
  306           file_list << parse_dot_doc_file(rel_file_name, dot_doc)
  307         else
  308           file_list << list_files_in_directory(rel_file_name)
  309         end
  310       else
  311         warn "rdoc can't parse the #{type} #{rel_file_name}"
  312       end
  313     end
  314 
  315     file_list.flatten
  316   end
  317 
  318   ##
  319   # Return a list of the files to be processed in a directory. We know that
  320   # this directory doesn't have a .document file, so we're looking for real
  321   # files. However we may well contain subdirectories which must be tested
  322   # for .document files.
  323 
  324   def list_files_in_directory dir
  325     files = Dir.glob File.join(dir, "*")
  326 
  327     normalized_file_list files, false, @options.exclude
  328   end
  329 
  330   ##
  331   # Parses +filename+ and returns an RDoc::TopLevel
  332 
  333   def parse_file filename
  334     encoding = @options.encoding
  335     filename = filename.encode encoding
  336 
  337     @stats.add_file filename
  338 
  339     return if RDoc::Parser.binary? filename
  340 
  341     content = RDoc::Encoding.read_file filename, encoding
  342 
  343     return unless content
  344 
  345     filename_path = Pathname(filename).expand_path
  346     begin
  347       relative_path = filename_path.relative_path_from @options.root
  348     rescue ArgumentError
  349       relative_path = filename_path
  350     end
  351 
  352     if @options.page_dir and
  353        relative_path.to_s.start_with? @options.page_dir.to_s then
  354       relative_path =
  355         relative_path.relative_path_from @options.page_dir
  356     end
  357 
  358     top_level = @store.add_file filename, relative_name: relative_path.to_s
  359 
  360     parser = RDoc::Parser.for top_level, filename, content, @options, @stats
  361 
  362     return unless parser
  363 
  364     parser.scan
  365 
  366     # restart documentation for the classes & modules found
  367     top_level.classes_or_modules.each do |cm|
  368       cm.done_documenting = false
  369     end
  370 
  371     top_level
  372 
  373   rescue Errno::EACCES => e
  374     $stderr.puts <<-EOF
  375 Unable to read #{filename}, #{e.message}
  376 
  377 Please check the permissions for this file.  Perhaps you do not have access to
  378 it or perhaps the original author's permissions are to restrictive.  If the
  379 this is not your library please report a bug to the author.
  380     EOF
  381   rescue => e
  382     $stderr.puts <<-EOF
  383 Before reporting this, could you check that the file you're documenting
  384 has proper syntax:
  385 
  386   #{Gem.ruby} -c #{filename}
  387 
  388 RDoc is not a full Ruby parser and will fail when fed invalid ruby programs.
  389 
  390 The internal error was:
  391 
  392 \t(#{e.class}) #{e.message}
  393 
  394     EOF
  395 
  396     $stderr.puts e.backtrace.join("\n\t") if $DEBUG_RDOC
  397 
  398     raise e
  399     nil
  400   end
  401 
  402   ##
  403   # Parse each file on the command line, recursively entering directories.
  404 
  405   def parse_files files
  406     file_list = gather_files files
  407     @stats = RDoc::Stats.new @store, file_list.length, @options.verbosity
  408 
  409     return [] if file_list.empty?
  410 
  411     original_options = @options.dup
  412     @stats.begin_adding
  413 
  414     file_info = file_list.map do |filename|
  415       @current = filename
  416       parse_file filename
  417     end.compact
  418 
  419     @stats.done_adding
  420     @options = original_options
  421 
  422     file_info
  423   end
  424 
  425   ##
  426   # Removes file extensions known to be unparseable from +files+ and TAGS
  427   # files for emacs and vim.
  428 
  429   def remove_unparseable files
  430     files.reject do |file|
  431       file =~ /\.(?:class|eps|erb|scpt\.txt|svg|ttf|yml)$/i or
  432         (file =~ /tags$/i and
  433          File.open(file, 'rb') { |io|
  434            io.read(100) =~ /\A(\f\n[^,]+,\d+$|!_TAG_)/
  435          })
  436     end
  437   end
  438 
  439   ##
  440   # Generates documentation or a coverage report depending upon the settings
  441   # in +options+.
  442   #
  443   # +options+ can be either an RDoc::Options instance or an array of strings
  444   # equivalent to the strings that would be passed on the command line like
  445   # <tt>%w[-q -o doc -t My\ Doc\ Title]</tt>.  #document will automatically
  446   # call RDoc::Options#finish if an options instance was given.
  447   #
  448   # For a list of options, see either RDoc::Options or <tt>rdoc --help</tt>.
  449   #
  450   # By default, output will be stored in a directory called "doc" below the
  451   # current directory, so make sure you're somewhere writable before invoking.
  452 
  453   def document options
  454     self.store = RDoc::Store.new
  455 
  456     if RDoc::Options === options then
  457       @options = options
  458       @options.finish
  459     else
  460       @options = load_options
  461       @options.parse options
  462     end
  463 
  464     if @options.pipe then
  465       handle_pipe
  466       exit
  467     end
  468 
  469     unless @options.coverage_report then
  470       @last_modified = setup_output_dir @options.op_dir, @options.force_update
  471     end
  472 
  473     @store.encoding = @options.encoding
  474     @store.dry_run  = @options.dry_run
  475     @store.main     = @options.main_page
  476     @store.title    = @options.title
  477     @store.path     = @options.op_dir
  478 
  479     @start_time = Time.now
  480 
  481     @store.load_cache
  482 
  483     file_info = parse_files @options.files
  484 
  485     @options.default_title = "RDoc Documentation"
  486 
  487     @store.complete @options.visibility
  488 
  489     @stats.coverage_level = @options.coverage_report
  490 
  491     if @options.coverage_report then
  492       puts
  493 
  494       puts @stats.report.accept RDoc::Markup::ToRdoc.new
  495     elsif file_info.empty? then
  496       $stderr.puts "\nNo newer files." unless @options.quiet
  497     else
  498       gen_klass = @options.generator
  499 
  500       @generator = gen_klass.new @store, @options
  501 
  502       generate
  503     end
  504 
  505     if @stats and (@options.coverage_report or not @options.quiet) then
  506       puts
  507       puts @stats.summary.accept RDoc::Markup::ToRdoc.new
  508     end
  509 
  510     exit @stats.fully_documented? if @options.coverage_report
  511   end
  512 
  513   ##
  514   # Generates documentation for +file_info+ (from #parse_files) into the
  515   # output dir using the generator selected
  516   # by the RDoc options
  517 
  518   def generate
  519     if @options.dry_run then
  520       # do nothing
  521       @generator.generate
  522     else
  523       Dir.chdir @options.op_dir do
  524         unless @options.quiet then
  525           $stderr.puts "\nGenerating #{@generator.class.name.sub(/^.*::/, '')} format into #{Dir.pwd}..."
  526         end
  527 
  528         @generator.generate
  529         update_output_dir '.', @start_time, @last_modified
  530       end
  531     end
  532   end
  533 
  534   ##
  535   # Removes a siginfo handler and replaces the previous
  536 
  537   def remove_siginfo_handler
  538     return unless Signal.list.key? 'INFO'
  539 
  540     handler = @old_siginfo || 'DEFAULT'
  541 
  542     trap 'INFO', handler
  543   end
  544 
  545 end
  546 
  547 begin
  548   require 'rubygems'
  549 
  550   rdoc_extensions = Gem.find_files 'rdoc/discover'
  551 
  552   rdoc_extensions.each do |extension|
  553     begin
  554       load extension
  555     rescue => e
  556       warn "error loading #{extension.inspect}: #{e.message} (#{e.class})"
  557       warn "\t#{e.backtrace.join "\n\t"}" if $DEBUG
  558     end
  559   end
  560 rescue LoadError
  561 end
  562 
  563 # require built-in generators after discovery in case they've been replaced
  564 require 'rdoc/generator/darkfish'
  565 require 'rdoc/generator/ri'
  566 require 'rdoc/generator/pot'