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'
12 require 'rdoc/options'
14 require 'rdoc/diagram'
23 # Encapsulate the production of rdoc documentation. Basically you can use
24 # this as you would invoke rdoc from the command line:
26 # rdoc = RDoc::RDoc.new
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.
34 Generator = Struct.new(:file_name, :class_name, :key)
37 # Accessor for statistics. Available after each call to parse_files
42 # This is the list of output generator that we support
46 $LOAD_PATH.collect do |d|
49 File.directory? "#{d}/rdoc/generator"
51 Dir.entries("#{dir}/rdoc/generator").each do |gen|
52 next unless /(\w+)\.rb$/ =~ gen
54 unless GENERATORS.has_key? type
55 GENERATORS[type] = Generator.new("rdoc/generator/#{gen}",
56 "#{type.upcase}".intern,
67 # Report an error message and exit
70 raise ::RDoc::Error, msg
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"
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"
93 last = (Time.parse(created) unless force rescue nil)
96 FileUtils.mkdir_p(op_dir)
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 }
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")
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(/#.*/, '')
126 patterns.split.each do |patt|
127 candidates = Dir.glob(File.join(in_dir, patt))
128 result.concat(normalized_file_list(options, candidates))
134 # Given a list of files and directories, create a list of all the Ruby
135 # files they contain.
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
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)
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
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(/^\.\//, '')
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))
165 file_list.concat(list_files_in_directory(rel_file_name, options))
168 raise RDoc::Error, "I can't deal with a #{type} #{rel_file_name}"
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
188 # Parse each file on the command line, recursively entering directories.
190 def parse_files(options)
191 @stats = Stats.new options.verbosity
193 files = options.files
194 files = ["."] if files.empty?
196 file_list = normalized_file_list(options, files, true)
198 return [] if file_list.empty?
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 }
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)
219 top_level = ::RDoc::TopLevel.new filename
221 parser = ::RDoc::Parser.for top_level, filename, content, options,
224 file_info << parser.scan
231 # Format up one or more files according to the given arguments.
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.
240 # Throws: RDoc::Error on error
245 @options = Options.new GENERATORS
250 unless @options.all_one_file then
251 @last_created = setup_output_dir @options.op_dir, @options.force_update
254 start_time = Time.now
256 file_info = parse_files @options
258 @options.title = "RDoc Documentation"
261 $stderr.puts "\nNo newer files." unless @options.quiet
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
274 Dir.chdir @options.op_dir unless @options.all_one_file
277 Diagram.new(file_info, @options).draw if @options.diagram
278 @gen.generate(file_info)
279 update_output_dir(".", start_time)
285 unless @options.quiet