1 | # We're responsible for generating all the HTML files
|
---|
2 | # from the object tree defined in code_objects.rb. We
|
---|
3 | # generate:
|
---|
4 | #
|
---|
5 | # [files] an html file for each input file given. These
|
---|
6 | # input files appear as objects of class
|
---|
7 | # TopLevel
|
---|
8 | #
|
---|
9 | # [classes] an html file for each class or module encountered.
|
---|
10 | # These classes are not grouped by file: if a file
|
---|
11 | # contains four classes, we'll generate an html
|
---|
12 | # file for the file itself, and four html files
|
---|
13 | # for the individual classes.
|
---|
14 | #
|
---|
15 | # [indices] we generate three indices for files, classes,
|
---|
16 | # and methods. These are displayed in a browser
|
---|
17 | # like window with three index panes across the
|
---|
18 | # top and the selected description below
|
---|
19 | #
|
---|
20 | # Method descriptions appear in whatever entity (file, class,
|
---|
21 | # or module) that contains them.
|
---|
22 | #
|
---|
23 | # We generate files in a structure below a specified subdirectory,
|
---|
24 | # normally +doc+.
|
---|
25 | #
|
---|
26 | # opdir
|
---|
27 | # |
|
---|
28 | # |___ files
|
---|
29 | # | |__ per file summaries
|
---|
30 | # |
|
---|
31 | # |___ classes
|
---|
32 | # |__ per class/module descriptions
|
---|
33 | #
|
---|
34 | # HTML is generated using the Template class.
|
---|
35 | #
|
---|
36 |
|
---|
37 | require 'ftools'
|
---|
38 |
|
---|
39 | require 'rdoc/options'
|
---|
40 | require 'rdoc/template'
|
---|
41 | require 'rdoc/markup/simple_markup'
|
---|
42 | require 'rdoc/markup/simple_markup/to_flow'
|
---|
43 | require 'cgi'
|
---|
44 |
|
---|
45 | require 'rdoc/ri/ri_cache'
|
---|
46 | require 'rdoc/ri/ri_reader'
|
---|
47 | require 'rdoc/ri/ri_writer'
|
---|
48 | require 'rdoc/ri/ri_descriptions'
|
---|
49 |
|
---|
50 | module Generators
|
---|
51 |
|
---|
52 |
|
---|
53 | class RIGenerator
|
---|
54 |
|
---|
55 | # Generators may need to return specific subclasses depending
|
---|
56 | # on the options they are passed. Because of this
|
---|
57 | # we create them using a factory
|
---|
58 |
|
---|
59 | def RIGenerator.for(options)
|
---|
60 | new(options)
|
---|
61 | end
|
---|
62 |
|
---|
63 | class <<self
|
---|
64 | protected :new
|
---|
65 | end
|
---|
66 |
|
---|
67 | # Set up a new HTML generator. Basically all we do here is load
|
---|
68 | # up the correct output temlate
|
---|
69 |
|
---|
70 | def initialize(options) #:not-new:
|
---|
71 | @options = options
|
---|
72 | @ri_writer = RI::RiWriter.new(".")
|
---|
73 | @markup = SM::SimpleMarkup.new
|
---|
74 | @to_flow = SM::ToFlow.new
|
---|
75 | end
|
---|
76 |
|
---|
77 |
|
---|
78 | ##
|
---|
79 | # Build the initial indices and output objects
|
---|
80 | # based on an array of TopLevel objects containing
|
---|
81 | # the extracted information.
|
---|
82 |
|
---|
83 | def generate(toplevels)
|
---|
84 | RDoc::TopLevel.all_classes_and_modules.each do |cls|
|
---|
85 | process_class(cls)
|
---|
86 | end
|
---|
87 | end
|
---|
88 |
|
---|
89 | def process_class(from_class)
|
---|
90 | generate_class_info(from_class)
|
---|
91 |
|
---|
92 | # now recure into this classes constituent classess
|
---|
93 | from_class.each_classmodule do |mod|
|
---|
94 | process_class(mod)
|
---|
95 | end
|
---|
96 | end
|
---|
97 |
|
---|
98 | def generate_class_info(cls)
|
---|
99 | if cls === RDoc::NormalModule
|
---|
100 | cls_desc = RI::ModuleDescription.new
|
---|
101 | else
|
---|
102 | cls_desc = RI::ClassDescription.new
|
---|
103 | cls_desc.superclass = cls.superclass
|
---|
104 | end
|
---|
105 | cls_desc.name = cls.name
|
---|
106 | cls_desc.full_name = cls.full_name
|
---|
107 | cls_desc.comment = markup(cls.comment)
|
---|
108 |
|
---|
109 | cls_desc.attributes =cls.attributes.sort.map do |a|
|
---|
110 | RI::Attribute.new(a.name, a.rw, markup(a.comment))
|
---|
111 | end
|
---|
112 |
|
---|
113 | cls_desc.constants = cls.constants.map do |c|
|
---|
114 | RI::Constant.new(c.name, c.value, markup(c.comment))
|
---|
115 | end
|
---|
116 |
|
---|
117 | cls_desc.includes = cls.includes.map do |i|
|
---|
118 | RI::IncludedModule.new(i.name)
|
---|
119 | end
|
---|
120 |
|
---|
121 | class_methods, instance_methods = method_list(cls)
|
---|
122 |
|
---|
123 | cls_desc.class_methods = class_methods.map do |m|
|
---|
124 | RI::MethodSummary.new(m.name)
|
---|
125 | end
|
---|
126 | cls_desc.instance_methods = instance_methods.map do |m|
|
---|
127 | RI::MethodSummary.new(m.name)
|
---|
128 | end
|
---|
129 |
|
---|
130 | update_or_replace(cls_desc)
|
---|
131 |
|
---|
132 | class_methods.each do |m|
|
---|
133 | generate_method_info(cls_desc, m)
|
---|
134 | end
|
---|
135 |
|
---|
136 | instance_methods.each do |m|
|
---|
137 | generate_method_info(cls_desc, m)
|
---|
138 | end
|
---|
139 | end
|
---|
140 |
|
---|
141 |
|
---|
142 | def generate_method_info(cls_desc, method)
|
---|
143 | meth_desc = RI::MethodDescription.new
|
---|
144 | meth_desc.name = method.name
|
---|
145 | meth_desc.full_name = cls_desc.full_name
|
---|
146 | if method.singleton
|
---|
147 | meth_desc.full_name += "::"
|
---|
148 | else
|
---|
149 | meth_desc.full_name += "#"
|
---|
150 | end
|
---|
151 | meth_desc.full_name << method.name
|
---|
152 |
|
---|
153 | meth_desc.comment = markup(method.comment)
|
---|
154 | meth_desc.params = params_of(method)
|
---|
155 | meth_desc.visibility = method.visibility.to_s
|
---|
156 | meth_desc.is_singleton = method.singleton
|
---|
157 | meth_desc.block_params = method.block_params
|
---|
158 |
|
---|
159 | meth_desc.aliases = method.aliases.map do |a|
|
---|
160 | RI::AliasName.new(a.name)
|
---|
161 | end
|
---|
162 |
|
---|
163 | @ri_writer.add_method(cls_desc, meth_desc)
|
---|
164 | end
|
---|
165 |
|
---|
166 | private
|
---|
167 |
|
---|
168 | # return a list of class and instance methods that we'll be
|
---|
169 | # documenting
|
---|
170 |
|
---|
171 | def method_list(cls)
|
---|
172 | list = cls.method_list
|
---|
173 | unless @options.show_all
|
---|
174 | list = list.find_all do |m|
|
---|
175 | m.visibility == :public || m.visibility == :protected || m.force_documentation
|
---|
176 | end
|
---|
177 | end
|
---|
178 |
|
---|
179 | c = []
|
---|
180 | i = []
|
---|
181 | list.sort.each do |m|
|
---|
182 | if m.singleton
|
---|
183 | c << m
|
---|
184 | else
|
---|
185 | i << m
|
---|
186 | end
|
---|
187 | end
|
---|
188 | return c,i
|
---|
189 | end
|
---|
190 |
|
---|
191 | def params_of(method)
|
---|
192 | if method.call_seq
|
---|
193 | method.call_seq
|
---|
194 | else
|
---|
195 | params = method.params || ""
|
---|
196 |
|
---|
197 | p = params.gsub(/\s*\#.*/, '')
|
---|
198 | p = p.tr("\n", " ").squeeze(" ")
|
---|
199 | p = "(" + p + ")" unless p[0] == ?(
|
---|
200 |
|
---|
201 | if (block = method.block_params)
|
---|
202 | block.gsub!(/\s*\#.*/, '')
|
---|
203 | block = block.tr("\n", " ").squeeze(" ")
|
---|
204 | if block[0] == ?(
|
---|
205 | block.sub!(/^\(/, '').sub!(/\)/, '')
|
---|
206 | end
|
---|
207 | p << " {|#{block.strip}| ...}"
|
---|
208 | end
|
---|
209 | p
|
---|
210 | end
|
---|
211 | end
|
---|
212 |
|
---|
213 | def markup(comment)
|
---|
214 | return nil if !comment || comment.empty?
|
---|
215 |
|
---|
216 | # Convert leading comment markers to spaces, but only
|
---|
217 | # if all non-blank lines have them
|
---|
218 |
|
---|
219 | if comment =~ /^(?>\s*)[^\#]/
|
---|
220 | content = comment
|
---|
221 | else
|
---|
222 | content = comment.gsub(/^\s*(#+)/) { $1.tr('#',' ') }
|
---|
223 | end
|
---|
224 | @markup.convert(content, @to_flow)
|
---|
225 | end
|
---|
226 |
|
---|
227 |
|
---|
228 | # By default we replace existing classes with the
|
---|
229 | # same name. If the --merge option was given, we instead
|
---|
230 | # merge this definition into an existing class. We add
|
---|
231 | # our methods, aliases, etc to that class, but do not
|
---|
232 | # change the class's description.
|
---|
233 |
|
---|
234 | def update_or_replace(cls_desc)
|
---|
235 | old_cls = nil
|
---|
236 |
|
---|
237 | if @options.merge
|
---|
238 | rdr = RI::RiReader.new(RI::RiCache.new(@options.op_dir))
|
---|
239 |
|
---|
240 | namespace = rdr.top_level_namespace
|
---|
241 | namespace = rdr.lookup_namespace_in(cls_desc.name, namespace)
|
---|
242 | if namespace.empty?
|
---|
243 | $stderr.puts "You asked me to merge this source into existing "
|
---|
244 | $stderr.puts "documentation. This file references a class or "
|
---|
245 | $stderr.puts "module called #{cls_desc.name} which I don't"
|
---|
246 | $stderr.puts "have existing documentation for."
|
---|
247 | $stderr.puts
|
---|
248 | $stderr.puts "Perhaps you need to generate its documentation first"
|
---|
249 | exit 1
|
---|
250 | else
|
---|
251 | old_cls = namespace[0]
|
---|
252 | end
|
---|
253 | end
|
---|
254 |
|
---|
255 | if old_cls.nil?
|
---|
256 | # no merge: simply overwrite
|
---|
257 | @ri_writer.remove_class(cls_desc)
|
---|
258 | @ri_writer.add_class(cls_desc)
|
---|
259 | else
|
---|
260 | # existing class: merge in
|
---|
261 | old_desc = rdr.get_class(old_cls)
|
---|
262 |
|
---|
263 | old_desc.merge_in(cls_desc)
|
---|
264 | @ri_writer.add_class(old_desc)
|
---|
265 | end
|
---|
266 | end
|
---|
267 | end
|
---|
268 | end
|
---|