]>
Commit | Line | Data |
---|---|---|
31f18b77 FG |
1 | #!/usr/bin/env python |
2 | # | |
3 | # Copyright 2008 Google Inc. All Rights Reserved. | |
4 | # | |
5 | # Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | # you may not use this file except in compliance with the License. | |
7 | # You may obtain a copy of the License at | |
8 | # | |
9 | # http://www.apache.org/licenses/LICENSE-2.0 | |
10 | # | |
11 | # Unless required by applicable law or agreed to in writing, software | |
12 | # distributed under the License is distributed on an "AS IS" BASIS, | |
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | # See the License for the specific language governing permissions and | |
15 | # limitations under the License. | |
16 | ||
17 | """Generate Google Mock classes from base classes. | |
18 | ||
19 | This program will read in a C++ source file and output the Google Mock | |
20 | classes for the specified classes. If no class is specified, all | |
21 | classes in the source file are emitted. | |
22 | ||
23 | Usage: | |
24 | gmock_class.py header-file.h [ClassName]... | |
25 | ||
26 | Output is sent to stdout. | |
27 | """ | |
28 | ||
29 | __author__ = 'nnorwitz@google.com (Neal Norwitz)' | |
30 | ||
31 | ||
32 | import os | |
33 | import re | |
34 | import sys | |
35 | ||
36 | from cpp import ast | |
37 | from cpp import utils | |
38 | ||
39 | # Preserve compatibility with Python 2.3. | |
40 | try: | |
41 | _dummy = set | |
42 | except NameError: | |
43 | import sets | |
44 | set = sets.Set | |
45 | ||
46 | _VERSION = (1, 0, 1) # The version of this script. | |
47 | # How many spaces to indent. Can set me with the INDENT environment variable. | |
48 | _INDENT = 2 | |
49 | ||
50 | ||
51 | def _GenerateMethods(output_lines, source, class_node): | |
52 | function_type = (ast.FUNCTION_VIRTUAL | ast.FUNCTION_PURE_VIRTUAL | | |
53 | ast.FUNCTION_OVERRIDE) | |
54 | ctor_or_dtor = ast.FUNCTION_CTOR | ast.FUNCTION_DTOR | |
55 | indent = ' ' * _INDENT | |
56 | ||
57 | for node in class_node.body: | |
58 | # We only care about virtual functions. | |
59 | if (isinstance(node, ast.Function) and | |
60 | node.modifiers & function_type and | |
61 | not node.modifiers & ctor_or_dtor): | |
62 | # Pick out all the elements we need from the original function. | |
63 | const = '' | |
64 | if node.modifiers & ast.FUNCTION_CONST: | |
65 | const = 'CONST_' | |
66 | return_type = 'void' | |
67 | if node.return_type: | |
68 | # Add modifiers like 'const'. | |
69 | modifiers = '' | |
70 | if node.return_type.modifiers: | |
71 | modifiers = ' '.join(node.return_type.modifiers) + ' ' | |
72 | return_type = modifiers + node.return_type.name | |
73 | template_args = [arg.name for arg in node.return_type.templated_types] | |
74 | if template_args: | |
75 | return_type += '<' + ', '.join(template_args) + '>' | |
76 | if len(template_args) > 1: | |
77 | for line in [ | |
78 | '// The following line won\'t really compile, as the return', | |
79 | '// type has multiple template arguments. To fix it, use a', | |
80 | '// typedef for the return type.']: | |
81 | output_lines.append(indent + line) | |
82 | if node.return_type.pointer: | |
83 | return_type += '*' | |
84 | if node.return_type.reference: | |
85 | return_type += '&' | |
86 | num_parameters = len(node.parameters) | |
87 | if len(node.parameters) == 1: | |
88 | first_param = node.parameters[0] | |
89 | if source[first_param.start:first_param.end].strip() == 'void': | |
90 | # We must treat T(void) as a function with no parameters. | |
91 | num_parameters = 0 | |
92 | tmpl = '' | |
93 | if class_node.templated_types: | |
94 | tmpl = '_T' | |
95 | mock_method_macro = 'MOCK_%sMETHOD%d%s' % (const, num_parameters, tmpl) | |
96 | ||
97 | args = '' | |
98 | if node.parameters: | |
99 | # Due to the parser limitations, it is impossible to keep comments | |
100 | # while stripping the default parameters. When defaults are | |
101 | # present, we choose to strip them and comments (and produce | |
102 | # compilable code). | |
103 | # TODO(nnorwitz@google.com): Investigate whether it is possible to | |
104 | # preserve parameter name when reconstructing parameter text from | |
105 | # the AST. | |
106 | if len([param for param in node.parameters if param.default]) > 0: | |
107 | args = ', '.join(param.type.name for param in node.parameters) | |
108 | else: | |
109 | # Get the full text of the parameters from the start | |
110 | # of the first parameter to the end of the last parameter. | |
111 | start = node.parameters[0].start | |
112 | end = node.parameters[-1].end | |
113 | # Remove // comments. | |
114 | args_strings = re.sub(r'//.*', '', source[start:end]) | |
115 | # Condense multiple spaces and eliminate newlines putting the | |
116 | # parameters together on a single line. Ensure there is a | |
117 | # space in an argument which is split by a newline without | |
118 | # intervening whitespace, e.g.: int\nBar | |
119 | args = re.sub(' +', ' ', args_strings.replace('\n', ' ')) | |
120 | ||
121 | # Create the mock method definition. | |
122 | output_lines.extend(['%s%s(%s,' % (indent, mock_method_macro, node.name), | |
123 | '%s%s(%s));' % (indent*3, return_type, args)]) | |
124 | ||
125 | ||
126 | def _GenerateMocks(filename, source, ast_list, desired_class_names): | |
127 | processed_class_names = set() | |
128 | lines = [] | |
129 | for node in ast_list: | |
130 | if (isinstance(node, ast.Class) and node.body and | |
131 | # desired_class_names being None means that all classes are selected. | |
132 | (not desired_class_names or node.name in desired_class_names)): | |
133 | class_name = node.name | |
134 | parent_name = class_name | |
135 | processed_class_names.add(class_name) | |
136 | class_node = node | |
137 | # Add namespace before the class. | |
138 | if class_node.namespace: | |
139 | lines.extend(['namespace %s {' % n for n in class_node.namespace]) # } | |
140 | lines.append('') | |
141 | ||
142 | # Add template args for templated classes. | |
143 | if class_node.templated_types: | |
144 | # TODO(paulchang): The AST doesn't preserve template argument order, | |
145 | # so we have to make up names here. | |
146 | # TODO(paulchang): Handle non-type template arguments (e.g. | |
147 | # template<typename T, int N>). | |
148 | template_arg_count = len(class_node.templated_types.keys()) | |
149 | template_args = ['T%d' % n for n in range(template_arg_count)] | |
150 | template_decls = ['typename ' + arg for arg in template_args] | |
151 | lines.append('template <' + ', '.join(template_decls) + '>') | |
152 | parent_name += '<' + ', '.join(template_args) + '>' | |
153 | ||
154 | # Add the class prolog. | |
155 | lines.append('class Mock%s : public %s {' # } | |
156 | % (class_name, parent_name)) | |
157 | lines.append('%spublic:' % (' ' * (_INDENT // 2))) | |
158 | ||
159 | # Add all the methods. | |
160 | _GenerateMethods(lines, source, class_node) | |
161 | ||
162 | # Close the class. | |
163 | if lines: | |
164 | # If there are no virtual methods, no need for a public label. | |
165 | if len(lines) == 2: | |
166 | del lines[-1] | |
167 | ||
168 | # Only close the class if there really is a class. | |
169 | lines.append('};') | |
170 | lines.append('') # Add an extra newline. | |
171 | ||
172 | # Close the namespace. | |
173 | if class_node.namespace: | |
174 | for i in range(len(class_node.namespace)-1, -1, -1): | |
175 | lines.append('} // namespace %s' % class_node.namespace[i]) | |
176 | lines.append('') # Add an extra newline. | |
177 | ||
178 | if desired_class_names: | |
179 | missing_class_name_list = list(desired_class_names - processed_class_names) | |
180 | if missing_class_name_list: | |
181 | missing_class_name_list.sort() | |
182 | sys.stderr.write('Class(es) not found in %s: %s\n' % | |
183 | (filename, ', '.join(missing_class_name_list))) | |
184 | elif not processed_class_names: | |
185 | sys.stderr.write('No class found in %s\n' % filename) | |
186 | ||
187 | return lines | |
188 | ||
189 | ||
190 | def main(argv=sys.argv): | |
191 | if len(argv) < 2: | |
192 | sys.stderr.write('Google Mock Class Generator v%s\n\n' % | |
193 | '.'.join(map(str, _VERSION))) | |
194 | sys.stderr.write(__doc__) | |
195 | return 1 | |
196 | ||
197 | global _INDENT | |
198 | try: | |
199 | _INDENT = int(os.environ['INDENT']) | |
200 | except KeyError: | |
201 | pass | |
202 | except: | |
203 | sys.stderr.write('Unable to use indent of %s\n' % os.environ.get('INDENT')) | |
204 | ||
205 | filename = argv[1] | |
206 | desired_class_names = None # None means all classes in the source file. | |
207 | if len(argv) >= 3: | |
208 | desired_class_names = set(argv[2:]) | |
209 | source = utils.ReadFile(filename) | |
210 | if source is None: | |
211 | return 1 | |
212 | ||
213 | builder = ast.BuilderFromSource(source, filename) | |
214 | try: | |
215 | entire_ast = filter(None, builder.Generate()) | |
216 | except KeyboardInterrupt: | |
217 | return | |
218 | except: | |
219 | # An error message was already printed since we couldn't parse. | |
220 | sys.exit(1) | |
221 | else: | |
222 | lines = _GenerateMocks(filename, source, entire_ast, desired_class_names) | |
223 | sys.stdout.write('\n'.join(lines)) | |
224 | ||
225 | ||
226 | if __name__ == '__main__': | |
227 | main(sys.argv) |