forked from tayral/cpp2py
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcpp2rst.py
More file actions
288 lines (217 loc) · 11.3 KB
/
cpp2rst.py
File metadata and controls
288 lines (217 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
import os, re, sys, itertools
from collections import OrderedDict
import cpp2py.clang_parser as CL
import synopsis, renderers, global_vars
from processed_doc import ProcessedDoc
# --------------------------------
regex_space_end_of_line = re.compile(r"[ \t\r\f\v]+$",re.MULTILINE)
def safe_write(output_name, data):
if '/' in output_name:
print "Skipping ", output_name
return
with open("{output_name}.rst".format(output_name=output_name), "w") as f:
f.write(re.sub(regex_space_end_of_line,'',data.strip()))
def mkchdir(*subdirs):
for d in subdirs:
try:
os.mkdir(d)
except OSError:
pass
os.chdir(d)
# -------------------------------------------------------
class Cpp2Rst:
""" """
def __init__(self, filename, namespaces=(), compiler_options=None, includes=None, system_includes=None, libclang_location = None, parse_all_comments = False, target_file_only = False):
"""
Parse the file at construction
Parameters
-----------
filename : string
Name of the file to parse
namespaces : list of string
Restrict the generation to the given namespaces.
includes : string, optional
Additional includes to add (-I xxx) for clang
system_includes : string, optional
Additional System includes to add (-isystem xxx) for clang
compiler_options : string, optional
Additional option for clang compiler
libclang_location : string, optional
Absolute path to libclang. By default, the detected one.
parse_all_comments : bool
Grab all comments, including non doxygen like [default = False]
target_file_only : bool
Neglect any included files during desc generation [default = False]
"""
self.filename, self.namespaces, self.target_file_only = filename, namespaces, target_file_only
self.root = CL.parse(filename, compiler_options, includes, system_includes, libclang_location, parse_all_comments)
# Root of examples
global_vars.examples_root = os.path.dirname(filename) + '/examples'
# ------------------------
def run(self, output_directory):
print "Generating the documentation ..."
mkchdir(output_directory)
# The namespace are going to be cleaned in the parameters in the synopsis
global_vars.process_type_name_ns_to_clean = [x + '::' for x in self.namespaces]
# Gather and preprocess all classes and functions
d = {}
for ns in self.namespaces:
d[ns] = self.analyse_one_ns(ns)
# Cross linking
# synopsis will be called by the renderers, and they need to know the class we are documenting
global_vars.synopsis_class_list = sum((cls_list for (cls_list, fnt_list, using_list) in d.values()), list())
for ns in self.namespaces:
self.run_one_ns(ns, *d[ns])
# Top page
r=renderers.render_top_page(self.namespaces)
safe_write('contents', r)
# ----------------------
# regroup the functions into a list of overloads
def regroup_func_by_names(self, fs):
""" Given a list of functions, regroup them by names in an OrderedDict. The Name will serve a filename """
d = OrderedDict()
def decay(name):
"""Replace operator X by better names for"""
return name
# if 'operator' not in name : return name
# a, ca, c, co = '+ - * /', '+= -= \*= /=', "== !=", r" comparison"
# d = {'*=' : ca,'+=' : ca,'/=' : ca,'-=' : ca,'*' : a,'+' : a,'/' : a,'-' : a, '==': c, '!=' : c, '<': co, '>' : co, '<=' : co, '>=' : co}
# n = name.replace('operator','').strip()
# return 'operator' + d[n] if n in d else name
for f in fs:
d.setdefault(decay(f.spelling),[]).append(f)
return d
# ------------------------
def analyse_one_ns(self, namespace):
print "*** Namespace %s ***"%namespace
# ----------------------
# Filters
# ----------------------
def keep_ns(n):
"""Given a namespace node n, shall we keep it ?"""
ns = CL.fully_qualified(n)
return ns in namespace
def keep_cls(c):
""" Given a class node, shall we keep it ?
Reject if no raw_comment.
Keeps if its namespace is EXACTLY in self.namespaces
e.g. A::B::cls will be kept iif A::B is in self.namespaces, not it A is.
The filter to keep a class/struct or an enum :
it must have a raw comment
if we a namespace list, it must be in it.
if target_file_only it has to be in the file given to c++2py
"""
if c.spelling.startswith('_') : return False
if not c.raw_comment : return False
if namespace:
qualified_ns = CL.get_namespace(c)
if qualified_ns != namespace : return False
return (c.location.file.name == self.filename) if self.target_file_only else True
def keep_fnt(f):
if not f.raw_comment : return False
if f.spelling.startswith('operator') or f.spelling in ['begin','end'] : return False
return keep_cls(f)
def keep_using(c):
if namespace:
qualified_ns = CL.get_namespace(c)
if qualified_ns != namespace : return False
return (c.location.file.name == self.filename) if self.target_file_only else True
def keep_is_documented(c):
return True if c.raw_comment else False
# ----------------------
# A list of AST nodes for classes
classes = CL.get_classes(self.root, keep_cls, traverse_namespaces = True, keep_ns = keep_ns)
classes = list(classes) # make a list to avoid exhaustion of the generator
D = OrderedDict()
for cls in classes:
cls.namespace = CL.get_namespace(cls)
cls.name = CL.get_name_with_template_specialization(cls) or cls.spelling
cls.fully_qualified_name = '::'.join([cls.namespace, cls.name])
cls.fully_qualified_name_no_template = CL.fully_qualified_name(cls)
cls.name_for_label = synopsis.make_label(cls.fully_qualified_name)
D[cls.fully_qualified_name] = cls
print " ... class : %s"%cls.fully_qualified_name, cls.location
#assert ',' not in cls.fully_qualified_name, "Not implemented"
# process the doc of the class and add it to the node
cls.processed_doc = ProcessedDoc(cls)
# all methods and constructors
# we build a OrderedDict of the constructors (first) and the methods, in order of declaration
constructors = list(CL.get_constructors(cls))
for f in constructors : f.is_constructor = True # tag them for later use
methods = OrderedDict()
if constructors: methods['constructor'] = constructors
methods.update(self.regroup_func_by_names(CL.get_methods(cls, True, keep = keep_is_documented))) # True : with inherited
# all non member functions
friend_functions = self.regroup_func_by_names(CL.get_friend_functions(cls, keep = keep_is_documented))
# Analyse the doc string for all methods and functions, and store the result in the node itself
for (n,f_list) in (methods.items() + friend_functions.items()):
for f in f_list:
f.processed_doc = ProcessedDoc(f)
# attach to the node
cls.methods, cls.friend_functions = methods, friend_functions
# members
cls.members = list(CL.get_members(cls, True))
# using
cls.usings = list(CL.get_usings(cls)) # , keep_using))
# Eliminate doublons, like forward declarations
classes = D.values()
# A list of AST nodes for the methods and functions
functions = CL.get_functions(self.root, keep_fnt, traverse_namespaces = True, keep_ns = keep_ns)
functions = list(functions) # make a to avoid exhaustion of the generator
# Analyse the doc strings
for f in functions:
f.processed_doc = ProcessedDoc(f)
# Find the using of this namespace, and make the list unique based on the fully_qualified_name
usings = list(CL.get_usings(self.root, keep_using, traverse_namespaces = True, keep_ns = keep_ns))
D = OrderedDict()
for c in usings:
c.namespace = CL.get_namespace(c)
c.fully_qualified_name = '::'.join([c.namespace, c.spelling])
D[c.fully_qualified_name] = c
usings = D.values()
return classes, functions, usings
# ------------------------
def run_one_ns(self, namespace, classes, functions, usings):
# c : AST node of name A::B::C::clsname makes and cd into A/B/C
def mkchdir_for_one_node(node):
mkchdir(* ( CL.fully_qualified_name(node).split('::')[:-1]))
# First treat the class
for c in classes:
if not c.spelling.strip() :
print "Skipping a class with an empty name !"
continue
# One directory for the class : make it and cd into it
cur_dir = os.getcwd()
mkchdir_for_one_node(c)
# the file for the class
r = renderers.render_cls(c, c.methods, c.friend_functions)
safe_write(synopsis.replace_ltgt(c.name), r)
# create a directory with the class name and cd into it
mkchdir(synopsis.replace_ltgt(c.name))
# write a file for each function
def render(message, d) :
for f_name, f_overloads in d.items():
print " ...... %s [%s]"%(f_name, message)
r = renderers.render_fnt(parent_class = c, f_name = f_name, f_overloads = f_overloads)
safe_write(f_name, r)
render('method', c.methods)
render('non member function', c.friend_functions)
# Change back to up directory
os.chdir(cur_dir)
# Now treat the functions
functions_by_name = self.regroup_func_by_names(functions)
docs = dict ( (n, [ProcessedDoc(f) for f in fs]) for (n,fs) in functions_by_name.items())
for f_name, f_overloads in functions_by_name.items():
print " ... function " + f_name, " [", f_overloads[0].location.file.name, ']'
cur_dir = os.getcwd()
mkchdir_for_one_node(f_overloads[0])
r = renderers.render_fnt(parent_class = None, f_name = f_name, f_overloads = f_overloads)
safe_write(f_name, r)
os.chdir(cur_dir)
# namespace resume function
cur_dir = os.getcwd()
mkchdir(*namespace.split('::')[:-1])
r = renderers.render_ns(namespace, functions_by_name, classes, usings)
ns = namespace.split('::',1)[-1]
safe_write(ns, r)
os.chdir(cur_dir)