Imported File#ftype spec from rubyspecs.
[rbx.git] / lib / rdoc / generator / ri.rb
blob2452d791328aa7527d2eb17f333e303f9d0f1a20
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   class << self
20     protected :new
21   end
23   ##
24   # Set up a new RDoc::Generator::RI.
26   def initialize(options) #:not-new:
27     @options   = options
28     @ri_writer = RDoc::RI::Writer.new "."
29     @markup    = RDoc::Markup.new
30     @to_flow   = RDoc::Markup::ToFlow.new
32     @generated = {}
33   end
35   ##
36   # Build the initial indices and output objects based on an array of
37   # TopLevel objects containing the extracted information.
39   def generate(toplevels)
40     RDoc::TopLevel.all_classes_and_modules.each do |cls|
41       process_class cls
42     end
43   end
45   def process_class(from_class)
46     generate_class_info(from_class)
48     # now recure into this classes constituent classess
49     from_class.each_classmodule do |mod|
50       process_class(mod)
51     end
52   end
54   def generate_class_info(cls)
55     if cls === RDoc::NormalModule
56       cls_desc = RDoc::RI::ModuleDescription.new
57     else
58       cls_desc = RDoc::RI::ClassDescription.new
59       cls_desc.superclass  = cls.superclass
60     end
62     cls_desc.name        = cls.name
63     cls_desc.full_name   = cls.full_name
64     cls_desc.comment     = markup(cls.comment)
66     cls_desc.attributes = cls.attributes.sort.map do |a|
67       RDoc::RI::Attribute.new(a.name, a.rw, markup(a.comment))
68     end
70     cls_desc.constants = cls.constants.map do |c|
71       RDoc::RI::Constant.new(c.name, c.value, markup(c.comment))
72     end
74     cls_desc.includes = cls.includes.map do |i|
75       RDoc::RI::IncludedModule.new(i.name)
76     end
78     class_methods, instance_methods = method_list(cls)
80     cls_desc.class_methods = class_methods.map do |m|
81       RDoc::RI::MethodSummary.new(m.name)
82     end
84     cls_desc.instance_methods = instance_methods.map do |m|
85       RDoc::RI::MethodSummary.new(m.name)
86     end
88     update_or_replace(cls_desc)
90     class_methods.each do |m|
91       generate_method_info(cls_desc, m)
92     end
94     instance_methods.each do |m|
95       generate_method_info(cls_desc, m)
96     end
97   end
99   def generate_method_info(cls_desc, method)
100     meth_desc = RDoc::RI::MethodDescription.new
101     meth_desc.name = method.name
102     meth_desc.full_name = cls_desc.full_name
103     if method.singleton
104       meth_desc.full_name += "::"
105     else
106       meth_desc.full_name += "#"
107     end
108     meth_desc.full_name << method.name
110     meth_desc.comment = markup(method.comment)
111     meth_desc.params = params_of(method)
112     meth_desc.visibility = method.visibility.to_s
113     meth_desc.is_singleton = method.singleton
114     meth_desc.block_params = method.block_params
116     meth_desc.aliases = method.aliases.map do |a|
117       RDoc::RI::AliasName.new(a.name)
118     end
120     @ri_writer.add_method(cls_desc, meth_desc)
121   end
123   private
125   ##
126   # Returns a list of class and instance methods that we'll be documenting
128   def method_list(cls)
129     list = cls.method_list
130     unless @options.show_all
131       list = list.find_all do |m|
132         m.visibility == :public || m.visibility == :protected || m.force_documentation
133       end
134     end
136     c = []
137     i = []
138     list.sort.each do |m|
139       if m.singleton
140         c << m
141       else
142         i << m
143       end
144     end
145     return c,i
146   end
148   def params_of(method)
149     if method.call_seq
150       method.call_seq
151     else
152       params = method.params || ""
154       p = params.gsub(/\s*\#.*/, '')
155       p = p.tr("\n", " ").squeeze(" ")
156       p = "(" + p + ")" unless p[0] == ?(
158       if (block = method.block_params)
159         block.gsub!(/\s*\#.*/, '')
160         block = block.tr("\n", " ").squeeze(" ")
161         if block[0] == ?(
162           block.sub!(/^\(/, '').sub!(/\)/, '')
163         end
164         p << " {|#{block.strip}| ...}"
165       end
166       p
167     end
168   end
170   def markup(comment)
171     return nil if !comment || comment.empty?
173     # Convert leading comment markers to spaces, but only
174     # if all non-blank lines have them
176     if comment =~ /^(?>\s*)[^\#]/
177       content = comment
178     else
179       content = comment.gsub(/^\s*(#+)/)  { $1.tr('#',' ') }
180     end
181     @markup.convert(content, @to_flow)
182   end
184   ##
185   # By default we replace existing classes with the same name. If the
186   # --merge option was given, we instead merge this definition into an
187   # existing class. We add our methods, aliases, etc to that class, but do
188   # not change the class's description.
190   def update_or_replace(cls_desc)
191     old_cls = nil
193     if @options.merge
194       rdr = RDoc::RI::Reader.new RDoc::RI::Cache.new(@options.op_dir)
196       namespace = rdr.top_level_namespace
197       namespace = rdr.lookup_namespace_in(cls_desc.name, namespace)
198       if namespace.empty?
199         $stderr.puts "You asked me to merge this source into existing "
200         $stderr.puts "documentation. This file references a class or "
201         $stderr.puts "module called #{cls_desc.name} which I don't"
202         $stderr.puts "have existing documentation for."
203         $stderr.puts
204         $stderr.puts "Perhaps you need to generate its documentation first"
205         exit 1
206       else
207         old_cls = namespace[0]
208       end
209     end
211     prev_cls = @generated[cls_desc.full_name]
213     if old_cls and not prev_cls then
214       old_desc = rdr.get_class old_cls
215       cls_desc.merge_in old_desc
216     end
218     if prev_cls then
219       cls_desc.merge_in prev_cls
220     end
222     @generated[cls_desc.full_name] = cls_desc
224     @ri_writer.remove_class cls_desc
225     @ri_writer.add_class cls_desc
226   end