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,150 @@
1
+ require 'yaml'
2
+ require 'rdoc/markup/fragments'
3
+ require 'rdoc/ri'
4
+
5
+ #--
6
+ # Descriptions are created by RDoc (in ri_generator) and written out in
7
+ # serialized form into the documentation tree. ri then reads these to generate
8
+ # the documentation
9
+ #++
10
+
11
+ class RDoc::RI::NamedThing
12
+ attr_reader :name
13
+ def initialize(name)
14
+ @name = name
15
+ end
16
+
17
+ def <=>(other)
18
+ @name <=> other.name
19
+ end
20
+
21
+ def hash
22
+ @name.hash
23
+ end
24
+
25
+ def eql?(other)
26
+ @name.eql?(other)
27
+ end
28
+ end
29
+
30
+ class RDoc::RI::AliasName < RDoc::RI::NamedThing; end
31
+
32
+ class RDoc::RI::Attribute < RDoc::RI::NamedThing
33
+ attr_reader :rw, :comment
34
+
35
+ def initialize(name, rw, comment)
36
+ super(name)
37
+ @rw = rw
38
+ @comment = comment
39
+ end
40
+ end
41
+
42
+ class RDoc::RI::Constant < RDoc::RI::NamedThing
43
+ attr_reader :value, :comment
44
+
45
+ def initialize(name, value, comment)
46
+ super(name)
47
+ @value = value
48
+ @comment = comment
49
+ end
50
+ end
51
+
52
+ class RDoc::RI::IncludedModule < RDoc::RI::NamedThing; end
53
+
54
+ class RDoc::RI::MethodSummary < RDoc::RI::NamedThing
55
+ def initialize(name="")
56
+ super
57
+ end
58
+ end
59
+
60
+ class RDoc::RI::Description
61
+ attr_accessor :name
62
+ attr_accessor :full_name
63
+ attr_accessor :comment
64
+
65
+ def serialize
66
+ self.to_yaml
67
+ end
68
+
69
+ def self.deserialize(from)
70
+ YAML.load(from)
71
+ end
72
+
73
+ def <=>(other)
74
+ @name <=> other.name
75
+ end
76
+ end
77
+
78
+ class RDoc::RI::ModuleDescription < RDoc::RI::Description
79
+
80
+ attr_accessor :class_methods
81
+ attr_accessor :instance_methods
82
+ attr_accessor :attributes
83
+ attr_accessor :constants
84
+ attr_accessor :includes
85
+
86
+ # merge in another class desscription into this one
87
+ def merge_in(old)
88
+ merge(@class_methods, old.class_methods)
89
+ merge(@instance_methods, old.instance_methods)
90
+ merge(@attributes, old.attributes)
91
+ merge(@constants, old.constants)
92
+ merge(@includes, old.includes)
93
+ if @comment.nil? || @comment.empty?
94
+ @comment = old.comment
95
+ else
96
+ unless old.comment.nil? or old.comment.empty? then
97
+ @comment << RDoc::Markup::Flow::RULE.new
98
+ @comment.concat old.comment
99
+ end
100
+ end
101
+ end
102
+
103
+ def display_name
104
+ "Module"
105
+ end
106
+
107
+ # the 'ClassDescription' subclass overrides this
108
+ # to format up the name of a parent
109
+ def superclass_string
110
+ nil
111
+ end
112
+
113
+ private
114
+
115
+ def merge(into, from)
116
+ names = {}
117
+ into.each {|i| names[i.name] = i }
118
+ from.each {|i| names[i.name] = i }
119
+ into.replace(names.keys.sort.map {|n| names[n]})
120
+ end
121
+ end
122
+
123
+ class RDoc::RI::ClassDescription < RDoc::RI::ModuleDescription
124
+ attr_accessor :superclass
125
+
126
+ def display_name
127
+ "Class"
128
+ end
129
+
130
+ def superclass_string
131
+ if @superclass && @superclass != "Object"
132
+ @superclass
133
+ else
134
+ nil
135
+ end
136
+ end
137
+ end
138
+
139
+ class RDoc::RI::MethodDescription < RDoc::RI::Description
140
+
141
+ attr_accessor :is_class_method
142
+ attr_accessor :visibility
143
+ attr_accessor :block_params
144
+ attr_accessor :is_singleton
145
+ attr_accessor :aliases
146
+ attr_accessor :is_alias_for
147
+ attr_accessor :params
148
+
149
+ end
150
+
@@ -0,0 +1,274 @@
1
+ require 'rdoc/ri'
2
+
3
+ ##
4
+ # This is a kind of 'flag' module. If you want to write your own 'ri' display
5
+ # module (perhaps because you're writing an IDE), you write a class which
6
+ # implements the various 'display' methods in RDoc::RI::DefaultDisplay, and
7
+ # include the RDoc::RI::Display module in that class.
8
+ #
9
+ # To access your class from the command line, you can do
10
+ #
11
+ # ruby -r <your source file> ../ri ....
12
+
13
+ module RDoc::RI::Display
14
+
15
+ @@display_class = nil
16
+
17
+ def self.append_features(display_class)
18
+ @@display_class = display_class
19
+ end
20
+
21
+ def self.new(*args)
22
+ @@display_class.new(*args)
23
+ end
24
+
25
+ end
26
+
27
+ ##
28
+ # A paging display module. Uses the RDoc::RI::Formatter class to do the actual
29
+ # presentation.
30
+
31
+ class RDoc::RI::DefaultDisplay
32
+
33
+ include RDoc::RI::Display
34
+
35
+ def initialize(formatter, width, use_stdout, output = $stdout)
36
+ @use_stdout = use_stdout
37
+ @formatter = formatter.new output, width, " "
38
+ end
39
+
40
+ ##
41
+ # Display information about +klass+. Fetches additional information from
42
+ # +ri_reader+ as necessary.
43
+
44
+ def display_class_info(klass, ri_reader)
45
+ page do
46
+ superclass = klass.superclass_string
47
+
48
+ if superclass
49
+ superclass = " < " + superclass
50
+ else
51
+ superclass = ""
52
+ end
53
+
54
+ @formatter.draw_line(klass.display_name + ": " +
55
+ klass.full_name + superclass)
56
+
57
+ display_flow(klass.comment)
58
+ @formatter.draw_line
59
+
60
+ unless klass.includes.empty?
61
+ @formatter.blankline
62
+ @formatter.display_heading("Includes:", 2, "")
63
+ incs = []
64
+ klass.includes.each do |inc|
65
+ inc_desc = ri_reader.find_class_by_name(inc.name)
66
+ if inc_desc
67
+ str = inc.name + "("
68
+ str << inc_desc.instance_methods.map{|m| m.name}.join(", ")
69
+ str << ")"
70
+ incs << str
71
+ else
72
+ incs << inc.name
73
+ end
74
+ end
75
+ @formatter.wrap(incs.sort.join(', '))
76
+ end
77
+
78
+ unless klass.constants.empty?
79
+ @formatter.blankline
80
+ @formatter.display_heading("Constants:", 2, "")
81
+
82
+ constants = klass.constants.sort_by { |constant| constant.name }
83
+
84
+ constants.each do |constant|
85
+ if constant.comment then
86
+ @formatter.wrap "#{constant.name}:"
87
+
88
+ @formatter.indent do
89
+ @formatter.display_flow constant.comment
90
+ end
91
+ else
92
+ @formatter.wrap constant.name
93
+ end
94
+ end
95
+ end
96
+
97
+ class_data = [
98
+ :class_methods,
99
+ :class_method_extensions,
100
+ :instance_methods,
101
+ :instance_method_extensions,
102
+ ]
103
+
104
+ class_data.each do |data_type|
105
+ data = klass.send data_type
106
+
107
+ unless data.empty? then
108
+ @formatter.blankline
109
+
110
+ heading = data_type.to_s.split('_').join(' ').capitalize << ':'
111
+ @formatter.display_heading heading, 2, ''
112
+
113
+ data = data.map { |item| item.name }.sort.join ', '
114
+ @formatter.wrap data
115
+ end
116
+ end
117
+
118
+ unless klass.attributes.empty? then
119
+ @formatter.blankline
120
+
121
+ @formatter.display_heading 'Attributes:', 2, ''
122
+
123
+ attributes = klass.attributes.sort_by { |attribute| attribute.name }
124
+
125
+ attributes.each do |attribute|
126
+ if attribute.comment then
127
+ @formatter.wrap "#{attribute.name} (#{attribute.rw}):"
128
+ @formatter.indent do
129
+ @formatter.display_flow attribute.comment
130
+ end
131
+ else
132
+ @formatter.wrap "#{attribute.name} (#{attribute.rw})"
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ ##
140
+ # Display an Array of RDoc::Markup::Flow objects, +flow+.
141
+
142
+ def display_flow(flow)
143
+ if flow and not flow.empty? then
144
+ @formatter.display_flow flow
145
+ else
146
+ @formatter.wrap '[no description]'
147
+ end
148
+ end
149
+
150
+ ##
151
+ # Display information about +method+.
152
+
153
+ def display_method_info(method)
154
+ page do
155
+ @formatter.draw_line(method.full_name)
156
+ display_params(method)
157
+
158
+ @formatter.draw_line
159
+ display_flow(method.comment)
160
+
161
+ if method.aliases and not method.aliases.empty? then
162
+ @formatter.blankline
163
+ aka = "(also known as #{method.aliases.map { |a| a.name }.join(', ')})"
164
+ @formatter.wrap aka
165
+ end
166
+ end
167
+ end
168
+
169
+ ##
170
+ # Display the list of +methods+.
171
+
172
+ def display_method_list(methods)
173
+ page do
174
+ @formatter.wrap "More than one method matched your request. You can refine your search by asking for information on one of:"
175
+
176
+ @formatter.blankline
177
+
178
+ @formatter.wrap methods.map { |m| m.full_name }.join(", ")
179
+ end
180
+ end
181
+
182
+ ##
183
+ # Display the params for +method+.
184
+
185
+ def display_params(method)
186
+ params = method.params
187
+
188
+ if params[0,1] == "(" then
189
+ if method.is_singleton
190
+ params = method.full_name + params
191
+ else
192
+ params = method.name + params
193
+ end
194
+ end
195
+
196
+ params.split(/\n/).each do |param|
197
+ @formatter.wrap param
198
+ @formatter.break_to_newline
199
+ end
200
+
201
+ if method.source_path then
202
+ @formatter.blankline
203
+ @formatter.wrap("Extension from #{method.source_path}")
204
+ end
205
+ end
206
+
207
+ ##
208
+ # List the classes in +classes+.
209
+
210
+ def list_known_classes(classes)
211
+ if classes.empty?
212
+ warn_no_database
213
+ else
214
+ page do
215
+ @formatter.draw_line "Known classes and modules"
216
+ @formatter.blankline
217
+
218
+ @formatter.wrap classes.sort.join(', ')
219
+ end
220
+ end
221
+ end
222
+
223
+ ##
224
+ # Paginates output through a pager program.
225
+
226
+ def page
227
+ if pager = setup_pager then
228
+ begin
229
+ orig_output = @formatter.output
230
+ @formatter.output = pager
231
+ yield
232
+ ensure
233
+ @formatter.output = orig_output
234
+ pager.close
235
+ end
236
+ else
237
+ yield
238
+ end
239
+ rescue Errno::EPIPE
240
+ end
241
+
242
+ ##
243
+ # Sets up a pager program to pass output through.
244
+
245
+ def setup_pager
246
+ unless @use_stdout then
247
+ for pager in [ ENV['PAGER'], "less", "more", 'pager' ].compact.uniq
248
+ return IO.popen(pager, "w") rescue nil
249
+ end
250
+ @use_stdout = true
251
+ nil
252
+ end
253
+ end
254
+
255
+ ##
256
+ # Displays a message that describes how to build RI data.
257
+
258
+ def warn_no_database
259
+ output = @formatter.output
260
+
261
+ output.puts "No ri data found"
262
+ output.puts
263
+ output.puts "If you've installed Ruby yourself, you need to generate documentation using:"
264
+ output.puts
265
+ output.puts " make install-doc"
266
+ output.puts
267
+ output.puts "from the same place you ran `make` to build ruby."
268
+ output.puts
269
+ output.puts "If you installed Ruby from a packaging system, then you may need to"
270
+ output.puts "install an additional package, or ask the packager to enable ri generation."
271
+ end
272
+
273
+ end
274
+
@@ -0,0 +1,452 @@
1
+ require 'optparse'
2
+ require 'yaml'
3
+
4
+ require 'rdoc/ri'
5
+ require 'rdoc/ri/paths'
6
+ require 'rdoc/ri/formatter'
7
+ require 'rdoc/ri/display'
8
+ require 'fileutils'
9
+ require 'rdoc/markup'
10
+ require 'rdoc/markup/to_flow'
11
+
12
+ class RDoc::RI::Driver
13
+
14
+ def self.process_args(argv)
15
+ options = {}
16
+ options[:use_stdout] = !$stdout.tty?
17
+ options[:width] = 72
18
+ options[:formatter] = RDoc::RI::Formatter.for 'plain'
19
+ options[:list_classes] = false
20
+ options[:list_names] = false
21
+
22
+ # By default all paths are used. If any of these are true, only those
23
+ # directories are used.
24
+ use_system = false
25
+ use_site = false
26
+ use_home = false
27
+ use_gems = false
28
+ doc_dirs = []
29
+
30
+ opts = OptionParser.new do |opt|
31
+ opt.program_name = File.basename $0
32
+ opt.version = RDoc::VERSION
33
+ opt.summary_indent = ' ' * 4
34
+
35
+ directories = [
36
+ RDoc::RI::Paths::SYSDIR,
37
+ RDoc::RI::Paths::SITEDIR,
38
+ RDoc::RI::Paths::HOMEDIR
39
+ ]
40
+
41
+ if RDoc::RI::Paths::GEMDIRS then
42
+ Gem.path.each do |dir|
43
+ directories << "#{dir}/doc/*/ri"
44
+ end
45
+ end
46
+
47
+ opt.banner = <<-EOT
48
+ Usage: #{opt.program_name} [options] [names...]
49
+
50
+ Where name can be:
51
+
52
+ Class | Class::method | Class#method | Class.method | method
53
+
54
+ All class names may be abbreviated to their minimum unambiguous form. If a name
55
+ is ambiguous, all valid options will be listed.
56
+
57
+ The form '.' method matches either class or instance methods, while #method
58
+ matches only instance and ::method matches only class methods.
59
+
60
+ For example:
61
+
62
+ #{opt.program_name} Fil
63
+ #{opt.program_name} File
64
+ #{opt.program_name} File.new
65
+ #{opt.program_name} zip
66
+
67
+ Note that shell quoting may be required for method names containing
68
+ punctuation:
69
+
70
+ #{opt.program_name} 'Array.[]'
71
+ #{opt.program_name} compact\\!
72
+
73
+ By default ri searches for documentation in the following directories:
74
+
75
+ #{directories.join "\n "}
76
+
77
+ Specifying the --system, --site, --home, --gems or --doc-dir options will
78
+ limit ri to searching only the specified directories.
79
+
80
+ Options may also be set in the 'RI' environment variable.
81
+ EOT
82
+
83
+ opt.separator nil
84
+ opt.separator "Options:"
85
+ opt.separator nil
86
+
87
+ opt.on("--classes", "-c",
88
+ "Display the names of classes and modules we",
89
+ "know about.") do |value|
90
+ options[:list_classes] = value
91
+ end
92
+
93
+ opt.separator nil
94
+
95
+ opt.on("--doc-dir=DIRNAME", "-d", Array,
96
+ "List of directories to search for",
97
+ "documentation. If not specified, we search",
98
+ "the standard rdoc/ri directories. May be",
99
+ "repeated.") do |value|
100
+ value.each do |dir|
101
+ unless File.directory? dir then
102
+ raise OptionParser::InvalidArgument, "#{dir} is not a directory"
103
+ end
104
+ end
105
+
106
+ doc_dirs.concat value
107
+ end
108
+
109
+ opt.separator nil
110
+
111
+ opt.on("--fmt=FORMAT", "--format=FORMAT", "-f",
112
+ RDoc::RI::Formatter::FORMATTERS.keys,
113
+ "Format to use when displaying output:",
114
+ " #{RDoc::RI::Formatter.list}",
115
+ "Use 'bs' (backspace) with most pager",
116
+ "programs. To use ANSI, either disable the",
117
+ "pager or tell the pager to allow control",
118
+ "characters.") do |value|
119
+ options[:formatter] = RDoc::RI::Formatter.for value
120
+ end
121
+
122
+ opt.separator nil
123
+
124
+ unless RDoc::RI::Paths::GEMDIRS.empty? then
125
+ opt.on("--[no-]gems",
126
+ "Include documentation from RubyGems.") do |value|
127
+ use_gems = value
128
+ end
129
+ end
130
+
131
+ opt.separator nil
132
+
133
+ opt.on("--[no-]home",
134
+ "Include documentation stored in ~/.rdoc.") do |value|
135
+ use_home = value
136
+ end
137
+
138
+ opt.separator nil
139
+
140
+ opt.on("--[no-]list-names", "-l",
141
+ "List all the names known to RDoc, one per",
142
+ "line.") do |value|
143
+ options[:list_names] = value
144
+ end
145
+
146
+ opt.separator nil
147
+
148
+ opt.on("--no-pager", "-T",
149
+ "Send output directly to stdout.") do |value|
150
+ options[:use_stdout] = !value
151
+ end
152
+
153
+ opt.separator nil
154
+
155
+ opt.on("--[no-]site",
156
+ "Include documentation from libraries",
157
+ "installed in site_lib.") do |value|
158
+ use_site = value
159
+ end
160
+
161
+ opt.separator nil
162
+
163
+ opt.on("--[no-]system",
164
+ "Include documentation from Ruby's standard",
165
+ "library.") do |value|
166
+ use_system = value
167
+ end
168
+
169
+ opt.separator nil
170
+
171
+ opt.on("--width=WIDTH", "-w", OptionParser::DecimalInteger,
172
+ "Set the width of the output.") do |value|
173
+ options[:width] = value
174
+ end
175
+ end
176
+
177
+ argv = ENV['RI'].to_s.split.concat argv
178
+
179
+ opts.parse! argv
180
+
181
+ options[:names] = argv
182
+
183
+ options[:path] = RDoc::RI::Paths.path(use_system, use_site, use_home,
184
+ use_gems, *doc_dirs)
185
+ options[:raw_path] = RDoc::RI::Paths.raw_path(use_system, use_site,
186
+ use_home, use_gems, *doc_dirs)
187
+
188
+ options
189
+
190
+ rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e
191
+ puts opts
192
+ puts
193
+ puts e
194
+ exit 1
195
+ end
196
+
197
+ def self.run(argv = ARGV)
198
+ options = process_args argv
199
+ ri = new options
200
+ ri.run
201
+ end
202
+
203
+ def initialize(options={})
204
+ options[:formatter] ||= RDoc::RI::Formatter.for('plain')
205
+ options[:use_stdout] ||= !$stdout.tty?
206
+ options[:width] ||= 72
207
+ @names = options[:names]
208
+
209
+ @class_cache_name = 'classes'
210
+ @all_dirs = RDoc::RI::Paths.path(true, true, true, true)
211
+ @homepath = RDoc::RI::Paths.raw_path(false, false, true, false).first
212
+ @homepath = @homepath.sub(/\.rdoc/, '.ri')
213
+ @sys_dirs = RDoc::RI::Paths.raw_path(true, false, false, false)
214
+
215
+ FileUtils.mkdir_p cache_file_path unless File.directory? cache_file_path
216
+
217
+ @class_cache = nil
218
+
219
+ @display = RDoc::RI::DefaultDisplay.new(options[:formatter],
220
+ options[:width],
221
+ options[:use_stdout])
222
+ end
223
+
224
+ def class_cache
225
+ return @class_cache if @class_cache
226
+
227
+ newest = map_dirs('created.rid', :all) do |f|
228
+ File.mtime f if test ?f, f
229
+ end.max
230
+
231
+ up_to_date = (File.exist?(class_cache_file_path) and
232
+ newest and newest < File.mtime(class_cache_file_path))
233
+
234
+ @class_cache = if up_to_date then
235
+ load_cache_for @class_cache_name
236
+ else
237
+ class_cache = {}
238
+
239
+ classes = map_dirs('**/cdesc*.yaml', :sys) { |f| Dir[f] }
240
+ populate_class_cache class_cache, classes
241
+
242
+ classes = map_dirs('**/cdesc*.yaml') { |f| Dir[f] }
243
+ warn "Updating class cache with #{classes.size} classes..."
244
+
245
+ populate_class_cache class_cache, classes, true
246
+ write_cache class_cache, class_cache_file_path
247
+ end
248
+ end
249
+
250
+ def class_cache_file_path
251
+ File.join cache_file_path, @class_cache_name
252
+ end
253
+
254
+ def cache_file_for(klassname)
255
+ File.join cache_file_path, klassname.gsub(/:+/, "-")
256
+ end
257
+
258
+ def cache_file_path
259
+ File.join @homepath, 'cache'
260
+ end
261
+
262
+ def display_class(name)
263
+ klass = class_cache[name]
264
+ @display.display_class_info klass, class_cache
265
+ end
266
+
267
+ def load_cache_for(klassname)
268
+ path = cache_file_for klassname
269
+
270
+ if File.exist? path and
271
+ File.mtime(path) >= File.mtime(class_cache_file_path) then
272
+ File.open path, 'rb' do |fp|
273
+ Marshal.load fp.read
274
+ end
275
+ else
276
+ class_cache = nil
277
+
278
+ File.open class_cache_file_path, 'rb' do |fp|
279
+ class_cache = Marshal.load fp.read
280
+ end
281
+
282
+ klass = class_cache[klassname]
283
+ return nil unless klass
284
+
285
+ method_files = klass["sources"]
286
+ cache = {}
287
+
288
+ sys_dir = @sys_dirs.first
289
+ method_files.each do |f|
290
+ system_file = f.index(sys_dir) == 0
291
+ Dir[File.join(File.dirname(f), "*")].each do |yaml|
292
+ next unless yaml =~ /yaml$/
293
+ next if yaml =~ /cdesc-[^\/]+yaml$/
294
+ method = read_yaml yaml
295
+ name = method["full_name"]
296
+ ext_path = f
297
+ ext_path = "gem #{$1}" if f =~ %r%gems/[\d.]+/doc/([^/]+)%
298
+ method["source_path"] = ext_path unless system_file
299
+ cache[name] = method
300
+ end
301
+ end
302
+
303
+ write_cache cache, path
304
+ end
305
+ end
306
+
307
+ def map_dirs(file_name, system=false)
308
+ dirs = if system == :all then
309
+ @all_dirs
310
+ else
311
+ if system then
312
+ @sys_dirs
313
+ else
314
+ @all_dirs - @sys_dirs
315
+ end
316
+ end
317
+
318
+ dirs.map { |dir| yield File.join(dir, file_name) }.flatten.compact
319
+ end
320
+
321
+ def populate_class_cache(class_cache, classes, extension = false)
322
+ classes.each do |cdesc|
323
+ desc = read_yaml cdesc
324
+ klassname = desc["full_name"]
325
+
326
+ unless class_cache.has_key? klassname then
327
+ desc["display_name"] = "Class"
328
+ desc["sources"] = [cdesc]
329
+ desc["instance_method_extensions"] = []
330
+ desc["class_method_extensions"] = []
331
+ class_cache[klassname] = desc
332
+ else
333
+ klass = class_cache[klassname]
334
+
335
+ if extension then
336
+ desc["instance_method_extensions"] = desc.delete "instance_methods"
337
+ desc["class_method_extensions"] = desc.delete "class_methods"
338
+ end
339
+
340
+ klass.merge_enums desc
341
+ klass["sources"] << cdesc
342
+ end
343
+ end
344
+ end
345
+
346
+ def read_yaml(path)
347
+ data = File.read path
348
+ data = data.gsub(/ \!ruby\/(object|struct):(RDoc::RI|RI).*/, '')
349
+ data = data.gsub(/ \!ruby\/(object|struct):SM::(\S+)/,
350
+ ' !ruby/\1:RDoc::Markup::\2')
351
+ YAML.load data
352
+ end
353
+
354
+ def get_info_for(arg)
355
+ @names = [arg]
356
+ run
357
+ end
358
+
359
+ def run
360
+ if @names.empty? then
361
+ @display.list_known_classes class_cache.keys.sort
362
+ else
363
+ @names.each do |name|
364
+ case name
365
+ when /::|\#|\./ then
366
+ if class_cache.key? name then
367
+ display_class name
368
+ else
369
+ meth = nil
370
+
371
+ parts = name.split(/::|\#|\./)
372
+ meth = parts.pop unless parts.last =~ /^[A-Z]/
373
+ klass = parts.join '::'
374
+
375
+ cache = load_cache_for klass
376
+ # HACK Does not support F.n
377
+ abort "Nothing known about #{name}" unless cache
378
+ method = cache[name.gsub(/\./, '#')]
379
+ abort "Nothing known about #{name}" unless method
380
+ @display.display_method_info method
381
+ end
382
+ else
383
+ if class_cache.key? name then
384
+ display_class name
385
+ else
386
+ methods = select_methods(/^#{name}/)
387
+ if methods.size == 0
388
+ abort "Nothing known about #{name}"
389
+ elsif methods.size == 1
390
+ @display.display_method_info methods.first
391
+ else
392
+ @display.display_method_list methods
393
+ end
394
+ end
395
+ end
396
+ end
397
+ end
398
+ end
399
+
400
+ def select_methods(pattern)
401
+ methods = []
402
+ class_cache.keys.sort.each do |klass|
403
+ class_cache[klass]["instance_methods"].map{|h|h["name"]}.grep(pattern) do |name|
404
+ method = load_cache_for(klass)[klass+'#'+name]
405
+ methods << method if method
406
+ end
407
+ class_cache[klass]["class_methods"].map{|h|h["name"]}.grep(pattern) do |name|
408
+ method = load_cache_for(klass)[klass+'::'+name]
409
+ methods << method if method
410
+ end
411
+ end
412
+ methods
413
+ end
414
+
415
+ def write_cache(cache, path)
416
+ File.open path, "wb" do |cache_file|
417
+ Marshal.dump cache, cache_file
418
+ end
419
+
420
+ cache
421
+ end
422
+
423
+ end
424
+
425
+ class Hash # HACK don't add stuff to Hash.
426
+ def method_missing method, *args
427
+ self[method.to_s]
428
+ end
429
+
430
+ def merge_enums(other)
431
+ other.each do |k,v|
432
+ if self[k] then
433
+ case v
434
+ when Array then
435
+ # HACK dunno
436
+ if String === self[k] and self[k].empty? then
437
+ self[k] = v
438
+ else
439
+ self[k] += v
440
+ end
441
+ when Hash then
442
+ self[k].merge! v
443
+ else
444
+ # do nothing
445
+ end
446
+ else
447
+ self[k] = v
448
+ end
449
+ end
450
+ end
451
+ end
452
+