2 # Copyright 2012-2015 Bronto Software, Inc. and contributors
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
19 import javasphinx.formatter as formatter
20 import javasphinx.util as util
21 import javasphinx.htmlrst as htmlrst
23 class JavadocRestCompiler(object):
24 """ Javadoc to ReST compiler. Builds ReST documentation from a Java syntax
27 def __init__(self, filter=None, member_headers=True, parser='lxml'):
31 self.filter = self.__default_filter
33 self.converter = htmlrst.Converter(parser)
35 self.member_headers = member_headers
37 def __default_filter(self, node):
38 """Excludes private members and those tagged "@hide" / "@exclude" in their
43 if not isinstance(node, javalang.tree.Declaration):
46 if 'private' in node.modifiers:
49 if isinstance(node, javalang.tree.Documented) and node.documentation:
50 doc = javalang.javadoc.parse(node.documentation)
51 if 'hide' in doc.tags or 'exclude' in doc.tags:
56 def __html_to_rst(self, s):
57 return self.converter.convert(s)
59 def __output_doc(self, documented):
60 if not isinstance(documented, javalang.tree.Documented):
61 raise ValueError('node not documented')
63 output = util.Document()
65 if not documented.documentation:
68 doc = javalang.javadoc.parse(documented.documentation)
71 output.add(self.__html_to_rst(doc.description))
75 output.add_line(':author: %s' % (self.__html_to_rst(', '.join(doc.authors)),))
77 for name, value in doc.params:
78 output.add_line(':param %s: %s' % (name, self.__html_to_rst(value)))
80 for exception in doc.throws:
81 description = doc.throws[exception]
82 output.add_line(':throws %s: %s' % (exception, self.__html_to_rst(description)))
85 output.add_line(':return: %s' % (self.__html_to_rst(doc.return_doc),))
87 if doc.tags.get('see'):
90 see_also = ', '.join(self.__output_see(see) for see in doc.tags['see'])
91 output.add_line('**See also:** %s' % (see_also,))
95 def __output_see(self, see):
96 """ Convert the argument to a @see tag to rest """
98 if see.startswith('<a href'):
99 # HTML link -- <a href="...">...</a>
100 return self.__html_to_rst(see)
105 # Type reference (default)
106 return ':java:ref:`%s`' % (see.replace('#', '.').replace(' ', ''),)
108 def compile_type(self, declaration):
109 signature = util.StringBuilder()
110 formatter.output_declaration(declaration, signature)
112 doc = self.__output_doc(declaration)
114 directive = util.Directive('java:type', signature.build())
115 directive.add_content(doc)
119 def compile_enum_constant(self, enum, constant):
120 signature = util.StringBuilder()
122 for annotation in constant.annotations:
123 formatter.output_annotation(annotation, signature)
125 # All enum constants are public, static, and final
126 signature.append('public static final ')
127 signature.append(enum)
128 signature.append(' ')
129 signature.append(constant.name)
131 doc = self.__output_doc(constant)
133 directive = util.Directive('java:field', signature.build())
134 directive.add_content(doc)
138 def compile_field(self, field):
139 signature = util.StringBuilder()
141 for annotation in field.annotations:
142 formatter.output_annotation(annotation, signature)
144 formatter.output_modifiers(field.modifiers, signature)
145 signature.append(' ')
147 formatter.output_type(field.type, signature)
148 signature.append(' ')
149 signature.append(field.declarators[0].name)
151 doc = self.__output_doc(field)
153 directive = util.Directive('java:field', signature.build())
154 directive.add_content(doc)
158 def compile_constructor(self, constructor):
159 signature = util.StringBuilder()
161 for annotation in constructor.annotations:
162 formatter.output_annotation(annotation, signature)
164 formatter.output_modifiers(constructor.modifiers, signature)
165 signature.append(' ')
167 if constructor.type_parameters:
168 formatter.output_type_params(constructor.type_parameters, signature)
169 signature.append(' ')
171 signature.append(constructor.name)
173 signature.append('(')
174 formatter.output_list(formatter.output_formal_param, constructor.parameters, signature, ', ')
175 signature.append(')')
177 if constructor.throws:
178 signature.append(' throws ')
179 formatter.output_list(formatter.output_exception, constructor.throws, signature, ', ')
181 doc = self.__output_doc(constructor)
183 directive = util.Directive('java:constructor', signature.build())
184 directive.add_content(doc)
188 def compile_method(self, method):
189 signature = util.StringBuilder()
191 for annotation in method.annotations:
192 formatter.output_annotation(annotation, signature)
194 formatter.output_modifiers(method.modifiers, signature)
195 signature.append(' ')
197 if method.type_parameters:
198 formatter.output_type_params(method.type_parameters, signature)
199 signature.append(' ')
201 formatter.output_type(method.return_type, signature)
202 signature.append(' ')
204 signature.append(method.name)
206 signature.append('(')
207 formatter.output_list(formatter.output_formal_param, method.parameters, signature, ', ')
208 signature.append(')')
211 signature.append(' throws ')
212 formatter.output_list(formatter.output_exception, method.throws, signature, ', ')
214 doc = self.__output_doc(method)
216 directive = util.Directive('java:method', signature.build())
217 directive.add_content(doc)
221 def compile_type_document(self, imports_block, package, name, declaration):
222 """ Compile a complete document, documenting a type and its members """
224 outer_type = name.rpartition('.')[0]
226 document = util.Document()
227 document.add(imports_block)
228 document.add_heading(name, '=')
230 method_summary = util.StringBuilder()
231 document.add_object(method_summary)
233 package_dir = util.Directive('java:package', package)
234 package_dir.add_option('noindex')
235 document.add_object(package_dir)
237 # Add type-level documentation
238 type_dir = self.compile_type(declaration)
240 type_dir.add_option('outertype', outer_type)
241 document.add_object(type_dir)
243 if isinstance(declaration, javalang.tree.EnumDeclaration):
244 enum_constants = list(declaration.body.constants)
245 enum_constants.sort(key=lambda c: c.name)
247 document.add_heading('Enum Constants')
248 for enum_constant in enum_constants:
249 if self.member_headers:
250 document.add_heading(enum_constant.name, '^')
251 c = self.compile_enum_constant(name, enum_constant)
252 c.add_option('outertype', name)
253 document.add_object(c)
255 fields = list(filter(self.filter, declaration.fields))
257 document.add_heading('Fields', '-')
258 fields.sort(key=lambda f: f.declarators[0].name)
260 if self.member_headers:
261 document.add_heading(field.declarators[0].name, '^')
262 f = self.compile_field(field)
263 f.add_option('outertype', name)
264 document.add_object(f)
266 constructors = list(filter(self.filter, declaration.constructors))
268 document.add_heading('Constructors', '-')
269 constructors.sort(key=lambda c: c.name)
270 for constructor in constructors:
271 if self.member_headers:
272 document.add_heading(constructor.name, '^')
273 c = self.compile_constructor(constructor)
274 c.add_option('outertype', name)
275 document.add_object(c)
277 methods = list(filter(self.filter, declaration.methods))
279 document.add_heading('Methods', '-')
280 methods.sort(key=lambda m: m.name)
281 for method in methods:
282 if self.member_headers:
283 document.add_heading(method.name, '^')
284 m = self.compile_method(method)
285 m.add_option('outertype', name)
286 document.add_object(m)
290 def compile(self, ast):
291 """ Compile autodocs for the given Java syntax tree. Documents will be
292 returned documenting each separate type. """
296 imports = util.StringBuilder()
297 for imp in ast.imports:
298 if imp.static or imp.wildcard:
304 for part in imp.path.split('.'):
305 if cls_parts or part[0].isupper():
306 cls_parts.append(part)
308 package_parts.append(part)
311 # If the import's final part wasn't capitalized,
312 # append it to the class parts anyway so sphinx doesn't complain.
314 cls_parts.append(package_parts.pop())
316 package = '.'.join(package_parts)
317 cls = '.'.join(cls_parts)
319 imports.append(util.Directive('java:import', package + ' ' + cls).build())
320 import_block = imports.build()
323 raise ValueError('File must have package declaration')
325 package = ast.package.name
326 type_declarations = []
327 for path, node in ast.filter(javalang.tree.TypeDeclaration):
328 if not self.filter(node):
331 classes = [n.name for n in path if isinstance(n, javalang.tree.TypeDeclaration)]
332 classes.append(node.name)
334 name = '.'.join(classes)
335 type_declarations.append((package, name, node))
337 for package, name, declaration in type_declarations:
338 full_name = package + '.' + name
339 document = self.compile_type_document(import_block, package, name, declaration)
340 documents[full_name] = (package, name, document.build())
343 def compile_docblock(self, documented):
344 ''' Compiles a single, standalone docblock. '''
345 return self.__output_doc(documented).build()