Fix up Rubinius specific library specs.
[rbx.git] / lib / rdoc / rdoc.rb
blobbc0a32f407611840d21814720e759e928888ec64
1 require 'rdoc'
3 require 'rdoc/parser'
5 # Simple must come first
6 require 'rdoc/parser/simple'
7 require 'rdoc/parser/ruby'
8 require 'rdoc/parser/c'
9 require 'rdoc/parser/f95'
11 require 'rdoc/stats'
12 require 'rdoc/options'
14 require 'rdoc/diagram'
16 require 'find'
17 require 'fileutils'
18 require 'time'
20 module RDoc
22   ##
23   # Encapsulate the production of rdoc documentation. Basically you can use
24   # this as you would invoke rdoc from the command line:
25   #
26   #   rdoc = RDoc::RDoc.new
27   #   rdoc.document(args)
28   #
29   # Where +args+ is an array of strings, each corresponding to an argument
30   # you'd give rdoc on the command line. See rdoc/rdoc.rb for details.
32   class RDoc
34     Generator = Struct.new(:file_name, :class_name, :key)
36     ##
37     # Accessor for statistics.  Available after each call to parse_files
39     attr_reader :stats
41     ##
42     # This is the list of output generator that we support
44     GENERATORS = {}
46     $LOAD_PATH.collect do |d|
47       File.expand_path d
48     end.find_all do |d|
49       File.directory? "#{d}/rdoc/generator"
50     end.each do |dir|
51       Dir.entries("#{dir}/rdoc/generator").each do |gen|
52         next unless /(\w+)\.rb$/ =~ gen
53         type = $1
54         unless GENERATORS.has_key? type
55           GENERATORS[type] = Generator.new("rdoc/generator/#{gen}",
56                                            "#{type.upcase}".intern,
57                                            type)
58         end
59       end
60     end
62     def initialize
63       @stats = nil
64     end
66     ##
67     # Report an error message and exit
69     def error(msg)
70       raise ::RDoc::Error, msg
71     end
73     ##
74     # Create an output dir if it doesn't exist. If it does exist, but doesn't
75     # contain the flag file <tt>created.rid</tt> then we refuse to use it, as
76     # we may clobber some manually generated documentation
78     def setup_output_dir(op_dir, force)
79       flag_file = output_flag_file(op_dir)
80       if File.exist?(op_dir)
81         unless File.directory?(op_dir)
82           error "'#{op_dir}' exists, and is not a directory"
83         end
84         begin
85           created = File.read(flag_file)
86         rescue SystemCallError
87           error "\nDirectory #{op_dir} already exists, but it looks like it\n" +
88             "isn't an RDoc directory. Because RDoc doesn't want to risk\n" +
89             "destroying any of your existing files, you'll need to\n" +
90             "specify a different output directory name (using the\n" +
91             "--op <dir> option).\n\n"
92         else
93           last = (Time.parse(created) unless force rescue nil)
94         end
95       else
96         FileUtils.mkdir_p(op_dir)
97       end
98       last
99     end
101     ##
102     # Update the flag file in an output directory.
104     def update_output_dir(op_dir, time)
105       File.open(output_flag_file(op_dir), "w") {|f| f.puts time.rfc2822 }
106     end
108     ##
109     # Return the path name of the flag file in an output directory.
111     def output_flag_file(op_dir)
112       File.join(op_dir, "created.rid")
113     end
115     ##
116     # The .document file contains a list of file and directory name patterns,
117     # representing candidates for documentation. It may also contain comments
118     # (starting with '#')
120     def parse_dot_doc_file(in_dir, filename, options)
121       # read and strip comments
122       patterns = File.read(filename).gsub(/#.*/, '')
124       result = []
126       patterns.split.each do |patt|
127         candidates = Dir.glob(File.join(in_dir, patt))
128         result.concat(normalized_file_list(options,  candidates))
129       end
130       result
131     end
133     ##
134     # Given a list of files and directories, create a list of all the Ruby
135     # files they contain.
136     #
137     # If +force_doc+ is true we always add the given files, if false, only
138     # add files that we guarantee we can parse.  It is true when looking at
139     # files given on the command line, false when recursing through
140     # subdirectories.
141     #
142     # The effect of this is that if you want a file with a non-standard
143     # extension parsed, you must name it explicitly.
145     def normalized_file_list(options, relative_files, force_doc = false,
146                              exclude_pattern = nil)
147       file_list = []
149       relative_files.each do |rel_file_name|
150         next if exclude_pattern && exclude_pattern =~ rel_file_name
151         stat = File.stat(rel_file_name)
152         case type = stat.ftype
153         when "file"
154           next if @last_created and stat.mtime < @last_created
156           if force_doc or ::RDoc::Parser.can_parse(rel_file_name) then
157             file_list << rel_file_name.sub(/^\.\//, '')
158           end
159         when "directory"
160           next if rel_file_name == "CVS" || rel_file_name == ".svn"
161           dot_doc = File.join(rel_file_name, DOT_DOC_FILENAME)
162           if File.file?(dot_doc)
163             file_list.concat(parse_dot_doc_file(rel_file_name, dot_doc, options))
164           else
165             file_list.concat(list_files_in_directory(rel_file_name, options))
166           end
167         else
168           raise RDoc::Error, "I can't deal with a #{type} #{rel_file_name}"
169         end
170       end
172       file_list
173     end
175     ##
176     # Return a list of the files to be processed in a directory. We know that
177     # this directory doesn't have a .document file, so we're looking for real
178     # files. However we may well contain subdirectories which must be tested
179     # for .document files.
181     def list_files_in_directory(dir, options)
182       files = Dir.glob File.join(dir, "*")
184       normalized_file_list options, files, false, options.exclude
185     end
187     ##
188     # Parse each file on the command line, recursively entering directories.
190     def parse_files(options)
191       @stats = Stats.new options.verbosity
192       
193       files = options.files
194       files = ["."] if files.empty?
196       file_list = normalized_file_list(options, files, true)
198       return [] if file_list.empty?
200       file_info = []
202       file_list.each do |filename|
203         @stats.add_file filename
205         content = if RUBY_VERSION >= '1.9' then
206                     File.open(filename, "r:ascii-8bit") { |f| f.read }
207                   else
208                     File.read filename
209                   end
211         if defined? Encoding then
212           if /coding:\s*(\S+)/ =~ content[/\A(?:.*\n){0,2}/]
213             if enc = ::Encoding.find($1)
214               content.force_encoding(enc)
215             end
216           end
217         end
219         top_level = ::RDoc::TopLevel.new filename
221         parser = ::RDoc::Parser.for top_level, filename, content, options,
222                                     @stats
224         file_info << parser.scan
225       end
227       file_info
228     end
230     ##
231     # Format up one or more files according to the given arguments.
232     #
233     # For simplicity, _argv_ is an array of strings, equivalent to the strings
234     # that would be passed on the command line. (This isn't a coincidence, as
235     # we _do_ pass in ARGV when running interactively). For a list of options,
236     # see rdoc/rdoc.rb. By default, output will be stored in a directory
237     # called +doc+ below the current directory, so make sure you're somewhere
238     # writable before invoking.
239     #
240     # Throws: RDoc::Error on error
242     def document(argv)
243       TopLevel::reset
245       @options = Options.new GENERATORS
246       @options.parse argv
248       @last_created = nil
250       unless @options.all_one_file then
251         @last_created = setup_output_dir @options.op_dir, @options.force_update
252       end
254       start_time = Time.now
256       file_info = parse_files @options
258       @options.title = "RDoc Documentation"
260       if file_info.empty?
261         $stderr.puts "\nNo newer files." unless @options.quiet
262       else
263         @gen = @options.generator
265         $stderr.puts "\nGenerating #{@gen.key.upcase}..." unless @options.quiet
267         require @gen.file_name
269         gen_class = ::RDoc::Generator.const_get @gen.class_name
270         @gen = gen_class.for @options
272         pwd = Dir.pwd
274         Dir.chdir @options.op_dir unless @options.all_one_file
276         begin
277           Diagram.new(file_info, @options).draw if @options.diagram
278           @gen.generate(file_info)
279           update_output_dir(".", start_time)
280         ensure
281           Dir.chdir(pwd)
282         end
283       end
285       unless @options.quiet
286         puts
287         @stats.print
288       end
289     end
290   end