rdoc 2.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rdoc might be problematic. Click here for more details.

Files changed (62) hide show
  1. data/History.txt +13 -0
  2. data/Manifest.txt +61 -0
  3. data/README.txt +34 -0
  4. data/Rakefile +10 -0
  5. data/bin/rdoc +22 -0
  6. data/bin/ri +6 -0
  7. data/lib/rdoc.rb +277 -0
  8. data/lib/rdoc/code_objects.rb +776 -0
  9. data/lib/rdoc/diagram.rb +338 -0
  10. data/lib/rdoc/dot.rb +249 -0
  11. data/lib/rdoc/generator.rb +1048 -0
  12. data/lib/rdoc/generator/chm.rb +113 -0
  13. data/lib/rdoc/generator/chm/chm.rb +98 -0
  14. data/lib/rdoc/generator/html.rb +370 -0
  15. data/lib/rdoc/generator/html/hefss.rb +414 -0
  16. data/lib/rdoc/generator/html/html.rb +704 -0
  17. data/lib/rdoc/generator/html/kilmer.rb +418 -0
  18. data/lib/rdoc/generator/html/one_page_html.rb +121 -0
  19. data/lib/rdoc/generator/ri.rb +229 -0
  20. data/lib/rdoc/generator/xml.rb +120 -0
  21. data/lib/rdoc/generator/xml/rdf.rb +113 -0
  22. data/lib/rdoc/generator/xml/xml.rb +111 -0
  23. data/lib/rdoc/markup.rb +473 -0
  24. data/lib/rdoc/markup/attribute_manager.rb +274 -0
  25. data/lib/rdoc/markup/formatter.rb +14 -0
  26. data/lib/rdoc/markup/fragments.rb +337 -0
  27. data/lib/rdoc/markup/inline.rb +101 -0
  28. data/lib/rdoc/markup/lines.rb +152 -0
  29. data/lib/rdoc/markup/preprocess.rb +71 -0
  30. data/lib/rdoc/markup/to_flow.rb +185 -0
  31. data/lib/rdoc/markup/to_html.rb +353 -0
  32. data/lib/rdoc/markup/to_html_crossref.rb +86 -0
  33. data/lib/rdoc/markup/to_latex.rb +328 -0
  34. data/lib/rdoc/markup/to_test.rb +50 -0
  35. data/lib/rdoc/options.rb +616 -0
  36. data/lib/rdoc/parsers/parse_c.rb +775 -0
  37. data/lib/rdoc/parsers/parse_f95.rb +1841 -0
  38. data/lib/rdoc/parsers/parse_rb.rb +2584 -0
  39. data/lib/rdoc/parsers/parse_simple.rb +40 -0
  40. data/lib/rdoc/parsers/parserfactory.rb +99 -0
  41. data/lib/rdoc/rdoc.rb +277 -0
  42. data/lib/rdoc/ri.rb +4 -0
  43. data/lib/rdoc/ri/cache.rb +188 -0
  44. data/lib/rdoc/ri/descriptions.rb +150 -0
  45. data/lib/rdoc/ri/display.rb +274 -0
  46. data/lib/rdoc/ri/driver.rb +452 -0
  47. data/lib/rdoc/ri/formatter.rb +616 -0
  48. data/lib/rdoc/ri/paths.rb +102 -0
  49. data/lib/rdoc/ri/reader.rb +106 -0
  50. data/lib/rdoc/ri/util.rb +81 -0
  51. data/lib/rdoc/ri/writer.rb +68 -0
  52. data/lib/rdoc/stats.rb +25 -0
  53. data/lib/rdoc/template.rb +64 -0
  54. data/lib/rdoc/tokenstream.rb +33 -0
  55. data/test/test_rdoc_c_parser.rb +261 -0
  56. data/test/test_rdoc_markup.rb +613 -0
  57. data/test/test_rdoc_markup_attribute_manager.rb +224 -0
  58. data/test/test_rdoc_ri_attribute_formatter.rb +42 -0
  59. data/test/test_rdoc_ri_default_display.rb +295 -0
  60. data/test/test_rdoc_ri_formatter.rb +318 -0
  61. data/test/test_rdoc_ri_overstrike_formatter.rb +69 -0
  62. metadata +134 -0
@@ -0,0 +1,40 @@
1
+ require 'rdoc'
2
+ require 'rdoc/code_objects'
3
+ require 'rdoc/markup/preprocess'
4
+
5
+ ##
6
+ # Parse a non-source file. We basically take the whole thing as one big
7
+ # comment. If the first character in the file is '#', we strip leading pound
8
+ # signs.
9
+
10
+ class RDoc::SimpleParser
11
+
12
+ ##
13
+ # Prepare to parse a plain file
14
+
15
+ def initialize(top_level, file_name, body, options, stats)
16
+ preprocess = RDoc::Markup::PreProcess.new(file_name, options.rdoc_include)
17
+
18
+ preprocess.handle(body) do |directive, param|
19
+ warn "Unrecognized directive '#{directive}' in #{file_name}"
20
+ end
21
+
22
+ @body = body
23
+ @options = options
24
+ @top_level = top_level
25
+ end
26
+
27
+ ##
28
+ # Extract the file contents and attach them to the toplevel as a comment
29
+
30
+ def scan
31
+ @top_level.comment = remove_private_comments(@body)
32
+ @top_level
33
+ end
34
+
35
+ def remove_private_comments(comment)
36
+ comment.gsub(/^--[^-].*?^\+\+/m, '').sub(/^--.*/m, '')
37
+ end
38
+
39
+ end
40
+
@@ -0,0 +1,99 @@
1
+ require "rdoc/parsers/parse_simple"
2
+
3
+ module RDoc
4
+
5
+ # A parser is simple a class that implements
6
+ #
7
+ # #initialize(file_name, body, options)
8
+ #
9
+ # and
10
+ #
11
+ # #scan
12
+ #
13
+ # The initialize method takes a file name to be used, the body of the
14
+ # file, and an RDoc::Options object. The scan method is then called
15
+ # to return an appropriately parsed TopLevel code object.
16
+ #
17
+ # The ParseFactory is used to redirect to the correct parser given a filename
18
+ # extension. This magic works because individual parsers have to register
19
+ # themselves with us as they are loaded in. The do this using the following
20
+ # incantation
21
+ #
22
+ #
23
+ # require "rdoc/parsers/parsefactory"
24
+ #
25
+ # module RDoc
26
+ #
27
+ # class XyzParser
28
+ # extend ParseFactory <<<<
29
+ # parse_files_matching /\.xyz$/ <<<<
30
+ #
31
+ # def initialize(file_name, body, options)
32
+ # ...
33
+ # end
34
+ #
35
+ # def scan
36
+ # ...
37
+ # end
38
+ # end
39
+ # end
40
+ #
41
+ # Just to make life interesting, if we suspect a plain text file, we
42
+ # also look for a shebang line just in case it's a potential
43
+ # shell script
44
+
45
+
46
+
47
+ module ParserFactory
48
+
49
+ @@parsers = []
50
+
51
+ Parsers = Struct.new(:regexp, :parser)
52
+
53
+ # Record the fact that a particular class parses files that
54
+ # match a given extension
55
+
56
+ def parse_files_matching(regexp)
57
+ @@parsers.unshift Parsers.new(regexp, self)
58
+ end
59
+
60
+ # Return a parser that can handle a particular extension
61
+
62
+ def ParserFactory.can_parse(file_name)
63
+ @@parsers.find {|p| p.regexp.match(file_name) }
64
+ end
65
+
66
+ # Alias an extension to another extension. After this call,
67
+ # files ending "new_ext" will be parsed using the same parser
68
+ # as "old_ext"
69
+
70
+ def ParserFactory.alias_extension(old_ext, new_ext)
71
+ parser = ParserFactory.can_parse("xxx.#{old_ext}")
72
+ return false unless parser
73
+ @@parsers.unshift Parsers.new(Regexp.new("\\.#{new_ext}$"), parser.parser)
74
+ true
75
+ end
76
+
77
+ # Find the correct parser for a particular file name. Return a
78
+ # SimpleParser for ones that we don't know
79
+
80
+ def ParserFactory.parser_for(top_level, file_name, body, options, stats)
81
+ # If no extension, look for shebang
82
+ if file_name !~ /\.\w+$/ && body =~ %r{\A#!(.+)}
83
+ shebang = $1
84
+ case shebang
85
+ when %r{env\s+ruby}, %r{/ruby}
86
+ file_name = "dummy.rb"
87
+ end
88
+ end
89
+ parser_description = can_parse(file_name)
90
+ if parser_description
91
+ parser = parser_description.parser
92
+ else
93
+ parser = SimpleParser
94
+ end
95
+
96
+ parser.new(top_level, file_name, body, options, stats)
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,277 @@
1
+ require 'rdoc'
2
+
3
+ require 'rdoc/parsers/parse_rb.rb'
4
+ require 'rdoc/parsers/parse_c.rb'
5
+ require 'rdoc/parsers/parse_f95.rb'
6
+ require 'rdoc/parsers/parse_simple.rb'
7
+
8
+ require 'rdoc/stats'
9
+ require 'rdoc/options'
10
+
11
+ require 'rdoc/diagram'
12
+
13
+ require 'find'
14
+ require 'fileutils'
15
+ require 'time'
16
+
17
+ module RDoc
18
+
19
+ ##
20
+ # Encapsulate the production of rdoc documentation. Basically
21
+ # you can use this as you would invoke rdoc from the command
22
+ # line:
23
+ #
24
+ # rdoc = RDoc::RDoc.new
25
+ # rdoc.document(args)
26
+ #
27
+ # where _args_ is an array of strings, each corresponding to
28
+ # an argument you'd give rdoc on the command line. See rdoc/rdoc.rb
29
+ # for details.
30
+
31
+ class RDoc
32
+
33
+ Generator = Struct.new(:file_name, :class_name, :key)
34
+
35
+ ##
36
+ # This is the list of output generator that we support
37
+
38
+ GENERATORS = {}
39
+
40
+ $LOAD_PATH.collect do |d|
41
+ File.expand_path d
42
+ end.find_all do |d|
43
+ File.directory? "#{d}/rdoc/generator"
44
+ end.each do |dir|
45
+ Dir.entries("#{dir}/rdoc/generator").each do |gen|
46
+ next unless /(\w+)\.rb$/ =~ gen
47
+ type = $1
48
+ unless GENERATORS.has_key? type
49
+ GENERATORS[type] = Generator.new("rdoc/generator/#{gen}",
50
+ "#{type.upcase}".intern,
51
+ type)
52
+ end
53
+ end
54
+ end
55
+
56
+ def initialize
57
+ @stats = Stats.new
58
+ end
59
+
60
+ ##
61
+ # Report an error message and exit
62
+
63
+ def error(msg)
64
+ raise ::RDoc::Error, msg
65
+ end
66
+
67
+ ##
68
+ # Create an output dir if it doesn't exist. If it does exist, but doesn't
69
+ # contain the flag file <tt>created.rid</tt> then we refuse to use it, as
70
+ # we may clobber some manually generated documentation
71
+
72
+ def setup_output_dir(op_dir, force)
73
+ flag_file = output_flag_file(op_dir)
74
+ if File.exist?(op_dir)
75
+ unless File.directory?(op_dir)
76
+ error "'#{op_dir}' exists, and is not a directory"
77
+ end
78
+ begin
79
+ created = File.read(flag_file)
80
+ rescue SystemCallError
81
+ error "\nDirectory #{op_dir} already exists, but it looks like it\n" +
82
+ "isn't an RDoc directory. Because RDoc doesn't want to risk\n" +
83
+ "destroying any of your existing files, you'll need to\n" +
84
+ "specify a different output directory name (using the\n" +
85
+ "--op <dir> option).\n\n"
86
+ else
87
+ last = (Time.parse(created) unless force rescue nil)
88
+ end
89
+ else
90
+ FileUtils.mkdir_p(op_dir)
91
+ end
92
+ last
93
+ end
94
+
95
+ ##
96
+ # Update the flag file in an output directory.
97
+
98
+ def update_output_dir(op_dir, time)
99
+ File.open(output_flag_file(op_dir), "w") {|f| f.puts time.rfc2822 }
100
+ end
101
+
102
+ ##
103
+ # Return the path name of the flag file in an output directory.
104
+
105
+ def output_flag_file(op_dir)
106
+ File.join(op_dir, "created.rid")
107
+ end
108
+
109
+ ##
110
+ # The .document file contains a list of file and directory name patterns,
111
+ # representing candidates for documentation. It may also contain comments
112
+ # (starting with '#')
113
+
114
+ def parse_dot_doc_file(in_dir, filename, options)
115
+ # read and strip comments
116
+ patterns = File.read(filename).gsub(/#.*/, '')
117
+
118
+ result = []
119
+
120
+ patterns.split.each do |patt|
121
+ candidates = Dir.glob(File.join(in_dir, patt))
122
+ result.concat(normalized_file_list(options, candidates))
123
+ end
124
+ result
125
+ end
126
+
127
+ ##
128
+ # Given a list of files and directories, create a list of all the Ruby
129
+ # files they contain.
130
+ #
131
+ # If +force_doc+ is true we always add the given files, if false, only
132
+ # add files that we guarantee we can parse. It is true when looking at
133
+ # files given on the command line, false when recursing through
134
+ # subdirectories.
135
+ #
136
+ # The effect of this is that if you want a file with a non-standard
137
+ # extension parsed, you must name it explicity.
138
+
139
+ def normalized_file_list(options, relative_files, force_doc = false,
140
+ exclude_pattern = nil)
141
+ file_list = []
142
+
143
+ relative_files.each do |rel_file_name|
144
+ next if exclude_pattern && exclude_pattern =~ rel_file_name
145
+ stat = File.stat(rel_file_name)
146
+ case type = stat.ftype
147
+ when "file"
148
+ next if @last_created and stat.mtime < @last_created
149
+ file_list << rel_file_name.sub(/^\.\//, '') if force_doc || ParserFactory.can_parse(rel_file_name)
150
+ when "directory"
151
+ next if rel_file_name == "CVS" || rel_file_name == ".svn"
152
+ dot_doc = File.join(rel_file_name, DOT_DOC_FILENAME)
153
+ if File.file?(dot_doc)
154
+ file_list.concat(parse_dot_doc_file(rel_file_name, dot_doc, options))
155
+ else
156
+ file_list.concat(list_files_in_directory(rel_file_name, options))
157
+ end
158
+ else
159
+ raise RDoc::Error, "I can't deal with a #{type} #{rel_file_name}"
160
+ end
161
+ end
162
+
163
+ file_list
164
+ end
165
+
166
+ ##
167
+ # Return a list of the files to be processed in a directory. We know that
168
+ # this directory doesn't have a .document file, so we're looking for real
169
+ # files. However we may well contain subdirectories which must be tested
170
+ # for .document files.
171
+
172
+ def list_files_in_directory(dir, options)
173
+ files = Dir.glob File.join(dir, "*")
174
+
175
+ normalized_file_list options, files, false, options.exclude
176
+ end
177
+
178
+ ##
179
+ # Parse each file on the command line, recursively entering directories.
180
+
181
+ def parse_files(options)
182
+ files = options.files
183
+ files = ["."] if files.empty?
184
+
185
+ file_list = normalized_file_list(options, files, true)
186
+
187
+ return [] if file_list.empty?
188
+
189
+ file_info = []
190
+ width = file_list.map { |name| name.length }.max + 1
191
+
192
+ file_list.each do |fn|
193
+ $stderr.printf("\n%*s: ", width, fn) unless options.quiet
194
+
195
+ content = if RUBY_VERSION >= '1.9' then
196
+ File.open(fn, "r:ascii-8bit") { |f| f.read }
197
+ else
198
+ File.read fn
199
+ end
200
+
201
+ if /coding:\s*(\S+)/ =~ content[/\A(?:.*\n){0,2}/]
202
+ if enc = Encoding.find($1)
203
+ content.force_encoding(enc)
204
+ end
205
+ end
206
+
207
+ top_level = TopLevel.new(fn)
208
+ parser = ParserFactory.parser_for(top_level, fn, content, options, @stats)
209
+ file_info << parser.scan
210
+ @stats.num_files += 1
211
+ end
212
+
213
+ file_info
214
+ end
215
+
216
+ ##
217
+ # Format up one or more files according to the given arguments.
218
+ #
219
+ # For simplicity, _argv_ is an array of strings, equivalent to the strings
220
+ # that would be passed on the command line. (This isn't a coincidence, as
221
+ # we _do_ pass in ARGV when running interactively). For a list of options,
222
+ # see rdoc/rdoc.rb. By default, output will be stored in a directory
223
+ # called +doc+ below the current directory, so make sure you're somewhere
224
+ # writable before invoking.
225
+ #
226
+ # Throws: RDoc::Error on error
227
+
228
+ def document(argv)
229
+ TopLevel::reset
230
+
231
+ @options = Options.new GENERATORS
232
+ @options.parse argv
233
+
234
+ @last_created = nil
235
+
236
+ unless @options.all_one_file then
237
+ @last_created = setup_output_dir @options.op_dir, @options.force_update
238
+ end
239
+
240
+ start_time = Time.now
241
+
242
+ file_info = parse_files @options
243
+
244
+ if file_info.empty?
245
+ $stderr.puts "\nNo newer files." unless @options.quiet
246
+ else
247
+ gen = @options.generator
248
+
249
+ $stderr.puts "\nGenerating #{gen.key.upcase}..." unless @options.quiet
250
+
251
+ require gen.file_name
252
+
253
+ gen_class = ::RDoc::Generator.const_get gen.class_name
254
+ gen = gen_class.for @options
255
+
256
+ pwd = Dir.pwd
257
+
258
+ Dir.chdir @options.op_dir unless @options.all_one_file
259
+
260
+ begin
261
+ Diagram.new(file_info, @options).draw if @options.diagram
262
+ gen.generate(file_info)
263
+ update_output_dir(".", start_time)
264
+ ensure
265
+ Dir.chdir(pwd)
266
+ end
267
+ end
268
+
269
+ unless @options.quiet
270
+ puts
271
+ @stats.print
272
+ end
273
+ end
274
+ end
275
+
276
+ end
277
+
@@ -0,0 +1,4 @@
1
+ require 'rdoc'
2
+
3
+ module RDoc::RI; end
4
+
@@ -0,0 +1,188 @@
1
+ require 'rdoc/ri'
2
+
3
+ class RDoc::RI::ClassEntry
4
+
5
+ attr_reader :name
6
+ attr_reader :path_names
7
+
8
+ def initialize(path_name, name, in_class)
9
+ @path_names = [ path_name ]
10
+ @name = name
11
+ @in_class = in_class
12
+ @class_methods = []
13
+ @instance_methods = []
14
+ @inferior_classes = []
15
+ end
16
+
17
+ # We found this class in more tha one place, so add
18
+ # in the name from there.
19
+ def add_path(path)
20
+ @path_names << path
21
+ end
22
+
23
+ # read in our methods and any classes
24
+ # and modules in our namespace. Methods are
25
+ # stored in files called name-c|i.yaml,
26
+ # where the 'name' portion is the external
27
+ # form of the method name and the c|i is a class|instance
28
+ # flag
29
+
30
+ def load_from(dir)
31
+ Dir.foreach(dir) do |name|
32
+ next if name =~ /^\./
33
+
34
+ # convert from external to internal form, and
35
+ # extract the instance/class flag
36
+
37
+ if name =~ /^(.*?)-(c|i).yaml$/
38
+ external_name = $1
39
+ is_class_method = $2 == "c"
40
+ internal_name = RiWriter.external_to_internal(external_name)
41
+ list = is_class_method ? @class_methods : @instance_methods
42
+ path = File.join(dir, name)
43
+ list << MethodEntry.new(path, internal_name, is_class_method, self)
44
+ else
45
+ full_name = File.join(dir, name)
46
+ if File.directory?(full_name)
47
+ inf_class = @inferior_classes.find {|c| c.name == name }
48
+ if inf_class
49
+ inf_class.add_path(full_name)
50
+ else
51
+ inf_class = ClassEntry.new(full_name, name, self)
52
+ @inferior_classes << inf_class
53
+ end
54
+ inf_class.load_from(full_name)
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ # Return a list of any classes or modules that we contain
61
+ # that match a given string
62
+
63
+ def contained_modules_matching(name)
64
+ @inferior_classes.find_all {|c| c.name[name]}
65
+ end
66
+
67
+ def classes_and_modules
68
+ @inferior_classes
69
+ end
70
+
71
+ # Return an exact match to a particular name
72
+ def contained_class_named(name)
73
+ @inferior_classes.find {|c| c.name == name}
74
+ end
75
+
76
+ # return the list of local methods matching name
77
+ # We're split into two because we need distinct behavior
78
+ # when called from the _toplevel_
79
+ def methods_matching(name, is_class_method)
80
+ local_methods_matching(name, is_class_method)
81
+ end
82
+
83
+ # Find methods matching 'name' in ourselves and in
84
+ # any classes we contain
85
+ def recursively_find_methods_matching(name, is_class_method)
86
+ res = local_methods_matching(name, is_class_method)
87
+ @inferior_classes.each do |c|
88
+ res.concat(c.recursively_find_methods_matching(name, is_class_method))
89
+ end
90
+ res
91
+ end
92
+
93
+
94
+ # Return our full name
95
+ def full_name
96
+ res = @in_class.full_name
97
+ res << "::" unless res.empty?
98
+ res << @name
99
+ end
100
+
101
+ # Return a list of all out method names
102
+ def all_method_names
103
+ res = @class_methods.map {|m| m.full_name }
104
+ @instance_methods.each {|m| res << m.full_name}
105
+ res
106
+ end
107
+
108
+ private
109
+
110
+ # Return a list of all our methods matching a given string.
111
+ # Is +is_class_methods+ if 'nil', we don't care if the method
112
+ # is a class method or not, otherwise we only return
113
+ # those methods that match
114
+ def local_methods_matching(name, is_class_method)
115
+
116
+ list = case is_class_method
117
+ when nil then @class_methods + @instance_methods
118
+ when true then @class_methods
119
+ when false then @instance_methods
120
+ else fail "Unknown is_class_method: #{is_class_method.inspect}"
121
+ end
122
+
123
+ list.find_all {|m| m.name; m.name[name]}
124
+ end
125
+ end
126
+
127
+ ##
128
+ # A TopLevelEntry is like a class entry, but when asked to search for methods
129
+ # searches all classes, not just itself
130
+
131
+ class RDoc::RI::TopLevelEntry < RDoc::RI::ClassEntry
132
+ def methods_matching(name, is_class_method)
133
+ res = recursively_find_methods_matching(name, is_class_method)
134
+ end
135
+
136
+ def full_name
137
+ ""
138
+ end
139
+
140
+ def module_named(name)
141
+
142
+ end
143
+
144
+ end
145
+
146
+ class RDoc::RI::MethodEntry
147
+ attr_reader :name
148
+ attr_reader :path_name
149
+
150
+ def initialize(path_name, name, is_class_method, in_class)
151
+ @path_name = path_name
152
+ @name = name
153
+ @is_class_method = is_class_method
154
+ @in_class = in_class
155
+ end
156
+
157
+ def full_name
158
+ res = @in_class.full_name
159
+ unless res.empty?
160
+ if @is_class_method
161
+ res << "::"
162
+ else
163
+ res << "#"
164
+ end
165
+ end
166
+ res << @name
167
+ end
168
+ end
169
+
170
+ ##
171
+ # We represent everything know about all 'ri' files accessible to this program
172
+
173
+ class RDoc::RI::Cache
174
+
175
+ attr_reader :toplevel
176
+
177
+ def initialize(dirs)
178
+ # At the top level we have a dummy module holding the
179
+ # overall namespace
180
+ @toplevel = RDoc::RI::TopLevelEntry.new('', '::', nil)
181
+
182
+ dirs.each do |dir|
183
+ @toplevel.load_from(dir)
184
+ end
185
+ end
186
+
187
+ end
188
+