Updating tags for StringIO.
[rbx.git] / lib / rdoc / generator / ri.rb
blob6b7a5932f8344af659716557d684daae674bd6c7
1 require 'rdoc/generator'
2 require 'rdoc/markup/to_flow'
4 require 'rdoc/ri/cache'
5 require 'rdoc/ri/reader'
6 require 'rdoc/ri/writer'
7 require 'rdoc/ri/descriptions'
9 class RDoc::Generator::RI
11   ##
12   # Generator may need to return specific subclasses depending on the
13   # options they are passed. Because of this we create them using a factory
15   def self.for(options)
16     new(options)
17   end
19   ##
20   # Set up a new ri generator
22   def initialize(options) #:not-new:
23     @options   = options
24     @ri_writer = RDoc::RI::Writer.new "."
25     @markup    = RDoc::Markup.new
26     @to_flow   = RDoc::Markup::ToFlow.new
28     @generated = {}
29   end
31   ##
32   # Build the initial indices and output objects based on an array of
33   # TopLevel objects containing the extracted information.
35   def generate(toplevels)
36     RDoc::TopLevel.all_classes_and_modules.each do |cls|
37       process_class cls
38     end
39   end
41   def process_class(from_class)
42     generate_class_info(from_class)
44     # now recurse into this class' constituent classes
45     from_class.each_classmodule do |mod|
46       process_class(mod)
47     end
48   end
50   def generate_class_info(cls)
51     case cls
52     when RDoc::NormalModule then
53       cls_desc = RDoc::RI::ModuleDescription.new
54     else
55       cls_desc = RDoc::RI::ClassDescription.new
56       cls_desc.superclass = cls.superclass
57     end
59     cls_desc.name        = cls.name
60     cls_desc.full_name   = cls.full_name
61     cls_desc.comment     = markup(cls.comment)
63     cls_desc.attributes = cls.attributes.sort.map do |a|
64       RDoc::RI::Attribute.new(a.name, a.rw, markup(a.comment))
65     end
67     cls_desc.constants = cls.constants.map do |c|
68       RDoc::RI::Constant.new(c.name, c.value, markup(c.comment))
69     end
71     cls_desc.includes = cls.includes.map do |i|
72       RDoc::RI::IncludedModule.new(i.name)
73     end
75     class_methods, instance_methods = method_list(cls)
77     cls_desc.class_methods = class_methods.map do |m|
78       RDoc::RI::MethodSummary.new(m.name)
79     end
81     cls_desc.instance_methods = instance_methods.map do |m|
82       RDoc::RI::MethodSummary.new(m.name)
83     end
85     update_or_replace(cls_desc)
87     class_methods.each do |m|
88       generate_method_info(cls_desc, m)
89     end
91     instance_methods.each do |m|
92       generate_method_info(cls_desc, m)
93     end
94   end
96   def generate_method_info(cls_desc, method)
97     meth_desc = RDoc::RI::MethodDescription.new
98     meth_desc.name = method.name
99     meth_desc.full_name = cls_desc.full_name
100     if method.singleton
101       meth_desc.full_name += "::"
102     else
103       meth_desc.full_name += "#"
104     end
105     meth_desc.full_name << method.name
107     meth_desc.comment = markup(method.comment)
108     meth_desc.params = params_of(method)
109     meth_desc.visibility = method.visibility.to_s
110     meth_desc.is_singleton = method.singleton
111     meth_desc.block_params = method.block_params
113     meth_desc.aliases = method.aliases.map do |a|
114       RDoc::RI::AliasName.new(a.name)
115     end
117     @ri_writer.add_method(cls_desc, meth_desc)
118   end
120   private
122   ##
123   # Returns a list of class and instance methods that we'll be documenting
125   def method_list(cls)
126     list = cls.method_list
127     unless @options.show_all
128       list = list.find_all do |m|
129         m.visibility == :public || m.visibility == :protected || m.force_documentation
130       end
131     end
133     c = []
134     i = []
135     list.sort.each do |m|
136       if m.singleton
137         c << m
138       else
139         i << m
140       end
141     end
142     return c,i
143   end
145   def params_of(method)
146     if method.call_seq
147       method.call_seq
148     else
149       params = method.params || ""
151       p = params.gsub(/\s*\#.*/, '')
152       p = p.tr("\n", " ").squeeze(" ")
153       p = "(" + p + ")" unless p[0] == ?(
155       if (block = method.block_params)
156         block.gsub!(/\s*\#.*/, '')
157         block = block.tr("\n", " ").squeeze(" ")
158         if block[0] == ?(
159           block.sub!(/^\(/, '').sub!(/\)/, '')
160         end
161         p << " {|#{block.strip}| ...}"
162       end
163       p
164     end
165   end
167   def markup(comment)
168     return nil if !comment || comment.empty?
170     # Convert leading comment markers to spaces, but only
171     # if all non-blank lines have them
173     if comment =~ /^(?>\s*)[^\#]/
174       content = comment
175     else
176       content = comment.gsub(/^\s*(#+)/)  { $1.tr('#',' ') }
177     end
178     @markup.convert(content, @to_flow)
179   end
181   ##
182   # By default we replace existing classes with the same name. If the
183   # --merge option was given, we instead merge this definition into an
184   # existing class. We add our methods, aliases, etc to that class, but do
185   # not change the class's description.
187   def update_or_replace(cls_desc)
188     old_cls = nil
190     if @options.merge
191       rdr = RDoc::RI::Reader.new RDoc::RI::Cache.new(@options.op_dir)
193       namespace = rdr.top_level_namespace
194       namespace = rdr.lookup_namespace_in(cls_desc.name, namespace)
195       if namespace.empty?
196         $stderr.puts "You asked me to merge this source into existing "
197         $stderr.puts "documentation. This file references a class or "
198         $stderr.puts "module called #{cls_desc.name} which I don't"
199         $stderr.puts "have existing documentation for."
200         $stderr.puts
201         $stderr.puts "Perhaps you need to generate its documentation first"
202         exit 1
203       else
204         old_cls = namespace[0]
205       end
206     end
208     prev_cls = @generated[cls_desc.full_name]
210     if old_cls and not prev_cls then
211       old_desc = rdr.get_class old_cls
212       cls_desc.merge_in old_desc
213     end
215     if prev_cls then
216       cls_desc.merge_in prev_cls
217     end
219     @generated[cls_desc.full_name] = cls_desc
221     @ri_writer.remove_class cls_desc
222     @ri_writer.add_class cls_desc
223   end