Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(142)

Side by Side Diff: headless/lib/browser/client_api_generator.py

Issue 1805983002: headless: Implement client API generation (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Make PendingMessage moveable Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 # Copyright 2016 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 import argparse
6 import os.path
7 import sys
8 import re
9 try:
10 import json
11 except ImportError:
12 import simplejson as json
13
14 # Path handling for libraries and templates
15 # Paths have to be normalized because Jinja uses the exact template path to
16 # determine the hash used in the cache filename, and we need a pre-caching step
17 # to be concurrency-safe. Use absolute path because __file__ is absolute if
18 # module is imported, and relative if executed directly.
19 # If paths differ between pre-caching and individual file compilation, the cache
20 # is regenerated, which causes a race condition and breaks concurrent build,
21 # since some compile processes will try to read the partially written cache.
22 module_path, module_filename = os.path.split(os.path.realpath(__file__))
23 third_party_dir = os.path.normpath(os.path.join(
24 module_path, os.pardir, os.pardir, os.pardir, 'third_party'))
25 templates_dir = module_path
26
27 # jinja2 is in chromium's third_party directory.
28 # Insert at 1 so at front to override system libraries, and
29 # after path[0] == invoking script dir
30 sys.path.insert(1, third_party_dir)
31 import jinja2
32
33
34 def ParseArguments(args):
35 """Parses command line arguments and returns a (json_api, output_dir) tuple.
36 """
37 cmdline_parser = argparse.ArgumentParser()
38 cmdline_parser.add_argument('--protocol', required=True)
39 cmdline_parser.add_argument('--output_dir', required=True)
40
41 args = cmdline_parser.parse_args(args)
42 with open(args.protocol, 'r') as f:
43 return json.load(f), args.output_dir
44
45
46 def ToTitleCase(name):
47 return name[:1].upper() + name[1:]
48
49
50 def DashToCamelCase(word):
51 return ''.join(ToTitleCase(x) for x in word.split('-'))
52
53
54 def CamelCaseToHackerStyle(name):
55 # Do two passes to insert '_' chars to deal with overlapping matches (e.g.,
56 # 'LoLoLoL').
57 name = re.sub(r'([^_])([A-Z][a-z]+?)', r'\1_\2', name)
58 name = re.sub(r'([^_])([A-Z][a-z]+?)', r'\1_\2', name)
59 return name.lower()
60
61
62 def MangleEnum(value):
63 # Rename NULL enumeration values to avoid a clash with the actual NULL.
64 return 'NONE' if value == 'NULL' else value
65
66
67 def InitializeJinjaEnv(cache_dir):
68 jinja_env = jinja2.Environment(
69 loader=jinja2.FileSystemLoader(templates_dir),
70 # Bytecode cache is not concurrency-safe unless pre-cached:
71 # if pre-cached this is read-only, but writing creates a race condition.
72 bytecode_cache=jinja2.FileSystemBytecodeCache(cache_dir),
73 keep_trailing_newline=True, # Newline-terminate generated files.
74 lstrip_blocks=True, # So we can indent control flow tags.
75 trim_blocks=True)
76 jinja_env.filters.update({
77 'to_title_case': ToTitleCase,
78 'dash_to_camelcase': DashToCamelCase,
79 'camelcase_to_hacker_style': CamelCaseToHackerStyle,
80 'mangle_enum': MangleEnum,
81 })
82 jinja_env.add_extension('jinja2.ext.loopcontrols')
83 return jinja_env
84
85
86 def PatchFullQualifiedRefs(json_api):
87 def PatchFullQualifiedRefsInDomain(json, domain_name):
88 if isinstance(json, list):
89 for item in json:
90 PatchFullQualifiedRefsInDomain(item, domain_name)
91
92 if not isinstance(json, dict):
93 return
94 for key in json:
95 if key != '$ref':
96 PatchFullQualifiedRefsInDomain(json[key], domain_name)
97 continue
98 if not '.' in json['$ref']:
99 json['$ref'] = domain_name + '.' + json['$ref']
100
101 for domain in json_api['domains']:
102 PatchFullQualifiedRefsInDomain(domain, domain['domain'])
103
104
105 def CreateUserTypeDefinition(domain, type):
106 namespace = CamelCaseToHackerStyle(domain['domain'])
107 return {
108 'return_type': 'std::unique_ptr<headless::%s::%s>' % (
109 namespace, type['id']),
110 'pass_type': 'std::unique_ptr<headless::%s::%s>' % (namespace, type['id']),
111 'to_raw_type': '*%s',
112 'to_raw_return_type': '%s.get()',
113 'to_pass_type': 'std::move(%s)',
114 'type': 'std::unique_ptr<headless::%s::%s>' % (namespace, type['id']),
115 'raw_type': 'headless::%s::%s' % (namespace, type['id']),
116 'raw_pass_type': 'headless::%s::%s*' % (namespace, type['id']),
117 'raw_return_type': 'headless::%s::%s*' % (namespace, type['id']),
118 }
119
120
121 def CreateEnumTypeDefinition(domain_name, type):
122 namespace = CamelCaseToHackerStyle(domain_name)
123 return {
124 'return_type': 'headless::%s::%s' % (namespace, type['id']),
125 'pass_type': 'headless::%s::%s' % (namespace, type['id']),
126 'to_raw_type': '%s',
127 'to_raw_return_type': '%s',
128 'to_pass_type': '%s',
129 'type': 'headless::%s::%s' % (namespace, type['id']),
130 'raw_type': 'headless::%s::%s' % (namespace, type['id']),
131 'raw_pass_type': 'headless::%s::%s' % (namespace, type['id']),
132 'raw_return_type': 'headless::%s::%s' % (namespace, type['id']),
133 }
134
135
136 def CreateObjectTypeDefinition():
137 return {
138 'return_type': 'std::unique_ptr<base::DictionaryValue>',
139 'pass_type': 'std::unique_ptr<base::DictionaryValue>',
140 'to_raw_type': '*%s',
141 'to_raw_return_type': '%s.get()',
142 'to_pass_type': 'std::move(%s)',
143 'type': 'std::unique_ptr<base::DictionaryValue>',
144 'raw_type': 'base::DictionaryValue',
145 'raw_pass_type': 'base::DictionaryValue*',
146 'raw_return_type': 'base::DictionaryValue*',
147 }
148
149
150 def WrapObjectTypeDefinition(type):
151 id = type.get('id', 'base::Value')
152 return {
153 'return_type': 'std::unique_ptr<%s>' % id,
154 'pass_type': 'std::unique_ptr<%s>' % id,
155 'to_raw_type': '*%s',
156 'to_raw_return_type': '%s.get()',
157 'to_pass_type': 'std::move(%s)',
158 'type': 'std::unique_ptr<%s>' % id,
159 'raw_type': id,
160 'raw_pass_type': '%s*' % id,
161 'raw_return_type': '%s*' % id,
162 }
163
164
165 def CreateAnyTypeDefinition():
166 return {
167 'return_type': 'std::unique_ptr<base::Value>',
168 'pass_type': 'std::unique_ptr<base::Value>',
169 'to_raw_type': '*%s',
170 'to_raw_return_type': '%s.get()',
171 'to_pass_type': 'std::move(%s)',
172 'type': 'std::unique_ptr<base::Value>',
173 'raw_type': 'base::Value',
174 'raw_pass_type': 'base::Value*',
175 'raw_return_type': 'base::Value*',
176 }
177
178
179 def CreateStringTypeDefinition(domain):
180 return {
181 'return_type': 'std::string',
182 'pass_type': 'const std::string&',
183 'to_pass_type': '%s',
184 'to_raw_type': '%s',
185 'to_raw_return_type': '%s',
186 'type': 'std::string',
187 'raw_type': 'std::string',
188 'raw_pass_type': 'const std::string&',
189 'raw_return_type': 'std::string',
190 }
191
192
193 def CreatePrimitiveTypeDefinition(type):
194 typedefs = {
195 'number': 'double',
196 'integer': 'int',
197 'boolean': 'bool',
198 'string': 'std::string',
199 }
200 return {
201 'return_type': typedefs[type],
202 'pass_type': typedefs[type],
203 'to_pass_type': '%s',
204 'to_raw_type': '%s',
205 'to_raw_return_type': '%s',
206 'type': typedefs[type],
207 'raw_type': typedefs[type],
208 'raw_pass_type': typedefs[type],
209 'raw_return_type': typedefs[type],
210 }
211
212
213 type_definitions = {}
214 type_definitions['number'] = CreatePrimitiveTypeDefinition('number')
215 type_definitions['integer'] = CreatePrimitiveTypeDefinition('integer')
216 type_definitions['boolean'] = CreatePrimitiveTypeDefinition('boolean')
217 type_definitions['string'] = CreatePrimitiveTypeDefinition('string')
218 type_definitions['object'] = CreateObjectTypeDefinition()
219 type_definitions['any'] = CreateAnyTypeDefinition()
220
221
222 def WrapArrayDefinition(type):
223 return {
224 'return_type': 'std::vector<%s>' % type['type'],
225 'pass_type': 'std::vector<%s>' % type['type'],
226 'to_raw_type': '%s',
227 'to_raw_return_type': '&%s',
228 'to_pass_type': 'std::move(%s)',
229 'type': 'std::vector<%s>' % type['type'],
230 'raw_type': 'std::vector<%s>' % type['type'],
231 'raw_pass_type': 'std::vector<%s>*' % type['type'],
232 'raw_return_type': 'std::vector<%s>*' % type['type'],
233 }
234
235
236 def CreateTypeDefinitions(json_api):
237 for domain in json_api['domains']:
238 if not ('types' in domain):
239 continue
240 for type in domain['types']:
241 if type['type'] == 'object':
242 if 'properties' in type:
243 type_definitions[domain['domain'] + '.' + type['id']] = (
244 CreateUserTypeDefinition(domain, type))
245 else:
246 type_definitions[domain['domain'] + '.' + type['id']] = (
247 CreateObjectTypeDefinition())
248 elif type['type'] == 'array':
249 items_type = type['items']['type']
250 type_definitions[domain['domain'] + '.' + type['id']] = (
251 WrapArrayDefinition(type_definitions[items_type]))
252 elif 'enum' in type:
253 type_definitions[domain['domain'] + '.' + type['id']] = (
254 CreateEnumTypeDefinition(domain['domain'], type))
255 type['$ref'] = domain['domain'] + '.' + type['id']
256 elif type['type'] == 'any':
257 type_definitions[domain['domain'] + '.' + type['id']] = (
258 CreateAnyTypeDefinition())
259 else:
260 type_definitions[domain['domain'] + '.' + type['id']] = (
261 CreatePrimitiveTypeDefinition(type['type']))
262
263
264 def TypeDefinition(name):
265 return type_definitions[name]
266
267
268 def ResolveType(property):
269 if '$ref' in property:
270 return type_definitions[property['$ref']]
271 elif property['type'] == 'object':
272 return WrapObjectTypeDefinition(property)
273 elif property['type'] == 'array':
274 return WrapArrayDefinition(ResolveType(property['items']))
275 return type_definitions[property['type']]
276
277
278 def JoinArrays(dict, keys):
279 result = []
280 for key in keys:
281 if key in dict:
282 result += dict[key]
283 return result
284
285
286 def SynthesizeEnumType(domain, owner, type):
287 type['id'] = ToTitleCase(owner) + ToTitleCase(type['name'])
288 type_definitions[domain['domain'] + '.' + type['id']] = (
289 CreateEnumTypeDefinition(domain['domain'], type))
290 type['$ref'] = domain['domain'] + '.' + type['id']
291 domain['types'].append(type)
292
293
294 def SynthesizeCommandTypes(json_api):
295 """Generate types for command parameters, return values and enum
296 properties."""
297 for domain in json_api['domains']:
298 if not 'types' in domain:
299 domain['types'] = []
300 for type in domain['types']:
301 if type['type'] == 'object':
302 for property in type.get('properties', []):
303 if 'enum' in property and not '$ref' in property:
304 SynthesizeEnumType(domain, type['id'], property)
305
306 for command in domain.get('commands', []):
307 if 'parameters' in command:
308 for parameter in command['parameters']:
309 if 'enum' in parameter and not '$ref' in parameter:
310 SynthesizeEnumType(domain, command['name'], parameter)
311 parameters_type = {
312 'id': ToTitleCase(command['name']) + 'Params',
313 'type': 'object',
314 'description': 'Parameters for the %s command.' % ToTitleCase(
315 command['name']),
316 'properties': command['parameters']
317 }
318 domain['types'].append(parameters_type)
319 if 'returns' in command:
320 for parameter in command['returns']:
321 if 'enum' in parameter and not '$ref' in parameter:
322 SynthesizeEnumType(domain, command['name'], parameter)
323 result_type = {
324 'id': ToTitleCase(command['name']) + 'Result',
325 'type': 'object',
326 'description': 'Result for the %s command.' % ToTitleCase(
327 command['name']),
328 'properties': command['returns']
329 }
330 domain['types'].append(result_type)
331
332
333 def Generate(jinja_env, output_dirname, json_api, class_name, file_types):
334 template_context = {
335 'api': json_api,
336 'join_arrays': JoinArrays,
337 'resolve_type': ResolveType,
338 'type_definition': TypeDefinition,
339 }
340 for file_type in file_types:
341 template = jinja_env.get_template('/%s_%s.template' % (
342 class_name, file_type))
343 output_file = '%s/%s.%s' % (output_dirname, class_name, file_type)
344 with open(output_file, 'w') as f:
345 f.write(template.render(template_context))
346
347
348 def GenerateDomains(jinja_env, output_dirname, json_api, class_name,
349 file_types):
350 for file_type in file_types:
351 template = jinja_env.get_template('/%s_%s.template' % (
352 class_name, file_type))
353 for domain in json_api['domains']:
354 template_context = {
355 'domain': domain,
356 'resolve_type': ResolveType,
357 }
358 domain_name = CamelCaseToHackerStyle(domain['domain'])
359 output_file = '%s/%s.%s' % (output_dirname, domain_name, file_type)
360 with open(output_file, 'w') as f:
361 f.write(template.render(template_context))
362
363
364 if __name__ == '__main__':
365 json_api, output_dirname = ParseArguments(sys.argv[1:])
366 jinja_env = InitializeJinjaEnv(output_dirname)
367 SynthesizeCommandTypes(json_api)
368 PatchFullQualifiedRefs(json_api)
369 CreateTypeDefinitions(json_api)
370 Generate(jinja_env, output_dirname, json_api, 'types', ['cc', 'h'])
371 Generate(jinja_env, output_dirname, json_api, 'type_conversions', ['h'])
372 GenerateDomains(jinja_env, output_dirname, json_api, 'domain', ['cc', 'h'])
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698