Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
pthreads are not used anymore. Standard C++ ones are
[simgrid.git] / docs / source / _ext / javasphinx / javasphinx / apidoc.py
1 #
2 # Copyright 2012-2015 Bronto Software, Inc. and contributors
3 #
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
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
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.
15 #
16
17 from __future__ import print_function, unicode_literals
18
19 try:
20    import cPickle as pickle
21 except ImportError:
22    import pickle
23
24 import hashlib
25 import logging
26 import sys
27 import os
28 import os.path
29
30 from optparse import OptionParser
31
32 import javalang
33
34 import javasphinx.compiler as compiler
35 import javasphinx.util as util
36
37 def encode_output(s):
38    if isinstance(s, str):
39       return s
40    return s.encode('utf-8')
41
42 def find_source_files(input_path, excludes):
43     """ Get a list of filenames for all Java source files within the given
44     directory.
45
46     """
47
48     java_files = []
49
50     input_path = os.path.normpath(os.path.abspath(input_path))
51
52     for dirpath, dirnames, filenames in os.walk(input_path):
53         if is_excluded(dirpath, excludes):
54             del dirnames[:]
55             continue
56
57         for filename in filenames:
58             if filename.endswith(".java"):
59                 java_files.append(os.path.join(dirpath, filename))
60
61     return java_files
62
63 def write_toc(packages, opts):
64     doc = util.Document()
65     doc.add_heading(opts.toc_title, '=')
66
67     toc = util.Directive('toctree')
68     toc.add_option('maxdepth', '2')
69     doc.add_object(toc)
70
71     for package in sorted(packages.keys()):
72         toc.add_content("%s/package-index\n" % package.replace('.', '/'))
73
74     filename = 'packages.' + opts.suffix
75     fullpath = os.path.join(opts.destdir, filename)
76
77     if os.path.exists(fullpath) and not (opts.force or opts.update):
78         sys.stderr.write(fullpath + ' already exists. Use -f to overwrite.\n')
79         sys.exit(1)
80
81     f = open(fullpath, 'w')
82     f.write(encode_output(doc.build()))
83     f.close()
84
85 def write_documents(packages, documents, sources, opts):
86     package_contents = dict()
87
88     # Write individual documents
89     for fullname, (package, name, document) in documents.items():
90         if is_package_info_doc(name):
91             continue
92
93         package_path = package.replace('.', os.sep)
94         filebasename = name.replace('.', '-')
95         filename = filebasename + '.' + opts.suffix
96         dirpath = os.path.join(opts.destdir, package_path)
97         fullpath = os.path.join(dirpath, filename)
98
99         if not os.path.exists(dirpath):
100             os.makedirs(dirpath)
101         elif os.path.exists(fullpath) and not (opts.force or opts.update):
102             sys.stderr.write(fullpath + ' already exists. Use -f to overwrite.\n')
103             sys.exit(1)
104
105         # Add to package indexes
106         package_contents.setdefault(package, list()).append(filebasename)
107
108         if opts.update and os.path.exists(fullpath):
109             # If the destination file is newer than the source file than skip
110             # writing it out
111             source_mod_time = os.stat(sources[fullname]).st_mtime
112             dest_mod_time = os.stat(fullpath).st_mtime
113
114             if source_mod_time < dest_mod_time:
115                 continue
116
117         f = open(fullpath, 'w')
118         f.write(encode_output(document))
119         f.close()
120
121     # Write package-index for each package
122     for package, classes in package_contents.items():
123         doc = util.Document()
124         doc.add_heading(package, '=')
125
126         #Adds the package documentation (if any)
127         if packages[package] != '':
128             documentation = packages[package]
129             doc.add_line("\n%s" % documentation)
130
131         doc.add_object(util.Directive('java:package', package))
132
133         toc = util.Directive('toctree')
134         toc.add_option('maxdepth', '1')
135
136         classes.sort()
137         for filebasename in classes:
138             toc.add_content(filebasename + '\n')
139         doc.add_object(toc)
140
141         package_path = package.replace('.', os.sep)
142         filename = 'package-index.' + opts.suffix
143         dirpath = os.path.join(opts.destdir, package_path)
144         fullpath = os.path.join(dirpath, filename)
145
146         if not os.path.exists(dirpath):
147             os.makedirs(dirpath)
148         elif os.path.exists(fullpath) and not (opts.force or opts.update):
149             sys.stderr.write(fullpath + ' already exists. Use -f to overwrite.\n')
150             sys.exit(1)
151
152         f = open(fullpath, 'w')
153         f.write(encode_output(doc.build()))
154         f.close()
155
156 def get_newer(a, b):
157     if not os.path.exists(a):
158         return b
159
160     if not os.path.exists(b):
161         return a
162
163     a_mtime = int(os.stat(a).st_mtime)
164     b_mtime = int(os.stat(b).st_mtime)
165
166     if a_mtime < b_mtime:
167         return b
168
169     return a
170
171 def format_syntax_error(e):
172     rest = ""
173     if e.at.position:
174         value = e.at.value
175         pos = e.at.position
176         rest = ' at %s line %d, character %d' % (value, pos[0], pos[1])
177     return e.description + rest
178
179 def generate_from_source_file(doc_compiler, source_file, cache_dir):
180     if cache_dir:
181         fingerprint = hashlib.md5(source_file.encode()).hexdigest()
182         cache_file = os.path.join(cache_dir, 'parsed-' + fingerprint + '.p')
183
184         if get_newer(source_file, cache_file) == cache_file:
185             return pickle.load(open(cache_file, 'rb'))
186     else:
187         cache_file = None
188
189     f = open(source_file)
190     source = f.read()
191     f.close()
192
193     try:
194         ast = javalang.parse.parse(source)
195     except javalang.parser.JavaSyntaxError as e:
196         util.error('Syntax error in %s: %s', source_file, format_syntax_error(e))
197     except Exception:
198         util.unexpected('Unexpected exception while parsing %s', source_file)
199
200     documents = {}
201     try:
202         if source_file.endswith("package-info.java"):
203             if ast.package is not None:
204                 documentation = doc_compiler.compile_docblock(ast.package)
205                 documents[ast.package.name] = (ast.package.name, 'package-info', documentation)
206         else:
207             documents = doc_compiler.compile(ast)
208     except Exception:
209         util.unexpected('Unexpected exception while compiling %s', source_file)
210
211     if cache_file:
212         dump_file = open(cache_file, 'wb')
213         pickle.dump(documents, dump_file)
214         dump_file.close()
215
216     return documents
217
218 def generate_documents(source_files, cache_dir, verbose, member_headers, parser):
219     documents = {}
220     sources = {}
221     doc_compiler = compiler.JavadocRestCompiler(None, member_headers, parser)
222
223     for source_file in source_files:
224         if verbose:
225             print('Processing', source_file)
226
227         this_file_documents = generate_from_source_file(doc_compiler, source_file, cache_dir)
228         for fullname in this_file_documents:
229             sources[fullname] = source_file
230
231         documents.update(this_file_documents)
232
233     #Existing packages dict, where each key is a package name
234     #and each value is the package documentation (if any)
235     packages = {}
236
237     #Gets the name of the package where the document was declared
238     #and adds it to the packages dict with no documentation.
239     #Package documentation, if any, will be collected from package-info.java files.
240     for package, name, _ in documents.values():
241         packages[package] = ""
242
243     #Gets packages documentation from package-info.java documents (if any).
244     for package, name, content in documents.values():
245         if is_package_info_doc(name):
246             packages[package] = content
247
248     return packages, documents, sources
249
250 def normalize_excludes(rootpath, excludes):
251     f_excludes = []
252     for exclude in excludes:
253         if not os.path.isabs(exclude) and not exclude.startswith(rootpath):
254             exclude = os.path.join(rootpath, exclude)
255         f_excludes.append(os.path.normpath(exclude) + os.path.sep)
256     return f_excludes
257
258 def is_excluded(root, excludes):
259     sep = os.path.sep
260     if not root.endswith(sep):
261         root += sep
262     for exclude in excludes:
263         if root.startswith(exclude):
264             return True
265     return False
266
267 def is_package_info_doc(document_name):
268     ''' Checks if the name of a document represents a package-info.java file. '''
269     return document_name == 'package-info'
270
271
272 def main(argv=sys.argv):
273     logging.basicConfig(level=logging.WARN)
274
275     parser = OptionParser(
276         usage="""\
277 usage: %prog [options] -o <output_path> <input_path> [exclude_paths, ...]
278
279 Look recursively in <input_path> for Java sources files and create reST files
280 for all non-private classes, organized by package under <output_path>. A package
281 index (package-index.<ext>) will be created for each package, and a top level
282 table of contents will be generated named packages.<ext>.
283
284 Paths matching any of the given exclude_paths (interpreted as regular
285 expressions) will be skipped.
286
287 Note: By default this script will not overwrite already created files.""")
288
289     parser.add_option('-o', '--output-dir', action='store', dest='destdir',
290                       help='Directory to place all output', default='')
291     parser.add_option('-f', '--force', action='store_true', dest='force',
292                       help='Overwrite all files')
293     parser.add_option('-c', '--cache-dir', action='store', dest='cache_dir',
294                       help='Directory to stored cachable output')
295     parser.add_option('-u', '--update', action='store_true', dest='update',
296                       help='Overwrite new and changed files', default=False)
297     parser.add_option('-T', '--no-toc', action='store_true', dest='notoc',
298                       help='Don\'t create a table of contents file')
299     parser.add_option('-t', '--title', dest='toc_title', default='Javadoc',
300                       help='Title to use on table of contents')
301     parser.add_option('--no-member-headers', action='store_false', default=True, dest='member_headers',
302                       help='Don\'t generate headers for class members')
303     parser.add_option('-s', '--suffix', action='store', dest='suffix',
304                       help='file suffix (default: rst)', default='rst')
305     parser.add_option('-I', '--include', action='append', dest='includes',
306                       help='Additional input paths to scan', default=[])
307     parser.add_option('-p', '--parser', dest='parser_lib', default='lxml',
308                       help='Beautiful Soup---html parser library option.')
309     parser.add_option('-v', '--verbose', action='store_true', dest='verbose',
310                       help='verbose output')
311
312     (opts, args) = parser.parse_args(argv[1:])
313
314     if not args:
315         parser.error('A source path is required.')
316
317     rootpath, excludes = args[0], args[1:]
318
319     input_paths = opts.includes
320     input_paths.append(rootpath)
321
322     if not opts.destdir:
323         parser.error('An output directory is required.')
324
325     if opts.suffix.startswith('.'):
326         opts.suffix = opts.suffix[1:]
327
328     for input_path in input_paths:
329         if not os.path.isdir(input_path):
330             sys.stderr.write('%s is not a directory.\n' % (input_path,))
331             sys.exit(1)
332
333     if not os.path.isdir(opts.destdir):
334         os.makedirs(opts.destdir)
335
336     if opts.cache_dir and not os.path.isdir(opts.cache_dir):
337         os.makedirs(opts.cache_dir)
338
339     excludes = normalize_excludes(rootpath, excludes)
340     source_files = []
341
342     for input_path in input_paths:
343         source_files.extend(find_source_files(input_path, excludes))
344
345     packages, documents, sources = generate_documents(source_files, opts.cache_dir, opts.verbose,
346                                                       opts.member_headers, opts.parser_lib)
347
348     write_documents(packages, documents, sources, opts)
349
350     if not opts.notoc:
351         write_toc(packages, opts)