| OLD | NEW |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import argparse | 5 import argparse |
| 6 import collections |
| 6 import os.path | 7 import os.path |
| 8 import re |
| 7 import sys | 9 import sys |
| 8 import re | |
| 9 try: | 10 try: |
| 10 import json | 11 import json |
| 11 except ImportError: | 12 except ImportError: |
| 12 import simplejson as json | 13 import simplejson as json |
| 13 | 14 |
| 14 # Path handling for libraries and templates | 15 # Path handling for libraries and templates |
| 15 # Paths have to be normalized because Jinja uses the exact template path to | 16 # 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 # 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 # to be concurrency-safe. Use absolute path because __file__ is absolute if |
| 18 # module is imported, and relative if executed directly. | 19 # module is imported, and relative if executed directly. |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 54 def CamelCaseToHackerStyle(name): | 55 def CamelCaseToHackerStyle(name): |
| 55 # Do two passes to insert '_' chars to deal with overlapping matches (e.g., | 56 # Do two passes to insert '_' chars to deal with overlapping matches (e.g., |
| 56 # 'LoLoLoL'). | 57 # '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) |
| 58 name = re.sub(r'([^_])([A-Z][a-z]+?)', r'\1_\2', name) | 59 name = re.sub(r'([^_])([A-Z][a-z]+?)', r'\1_\2', name) |
| 59 return name.lower() | 60 return name.lower() |
| 60 | 61 |
| 61 | 62 |
| 62 def SanitizeLiteral(literal): | 63 def SanitizeLiteral(literal): |
| 63 return { | 64 return { |
| 64 # Rename null enumeration values to avoid a clash with the NULL macro. | 65 # Rename null enumeration values to avoid a clash with the NULL macro. |
| 65 'null': 'none', | 66 'null': 'none', |
| 66 # Rename mathematical constants to avoid colliding with C macros. | 67 # Rename mathematical constants to avoid colliding with C macros. |
| 67 'Infinity': 'InfinityValue', | 68 'Infinity': 'InfinityValue', |
| 68 '-Infinity': 'NegativeInfinityValue', | 69 '-Infinity': 'NegativeInfinityValue', |
| 69 'NaN': 'NaNValue', | 70 'NaN': 'NaNValue', |
| 70 # Turn negative zero into a safe identifier. | 71 # Turn negative zero into a safe identifier. |
| 71 '-0': 'NegativeZeroValue', | 72 '-0': 'NegativeZeroValue', |
| 72 }.get(literal, literal) | 73 }.get(literal, literal) |
| 73 | 74 |
| 74 | 75 |
| 75 def InitializeJinjaEnv(cache_dir): | 76 def InitializeJinjaEnv(cache_dir): |
| 76 jinja_env = jinja2.Environment( | 77 jinja_env = jinja2.Environment( |
| 77 loader=jinja2.FileSystemLoader(templates_dir), | 78 loader=jinja2.FileSystemLoader(templates_dir), |
| 78 # Bytecode cache is not concurrency-safe unless pre-cached: | 79 # Bytecode cache is not concurrency-safe unless pre-cached: |
| 79 # if pre-cached this is read-only, but writing creates a race condition. | 80 # if pre-cached this is read-only, but writing creates a race condition. |
| 80 bytecode_cache=jinja2.FileSystemBytecodeCache(cache_dir), | 81 bytecode_cache=jinja2.FileSystemBytecodeCache(cache_dir), |
| 81 keep_trailing_newline=True, # Newline-terminate generated files. | 82 keep_trailing_newline=True, # Newline-terminate generated files. |
| 82 lstrip_blocks=True, # So we can indent control flow tags. | 83 lstrip_blocks=True, # So we can indent control flow tags. |
| 83 trim_blocks=True) | 84 trim_blocks=True) |
| 84 jinja_env.filters.update({ | 85 jinja_env.filters.update({ |
| 85 'to_title_case': ToTitleCase, | 86 'to_title_case': ToTitleCase, |
| 86 'dash_to_camelcase': DashToCamelCase, | 87 'dash_to_camelcase': DashToCamelCase, |
| 87 'camelcase_to_hacker_style': CamelCaseToHackerStyle, | 88 'camelcase_to_hacker_style': CamelCaseToHackerStyle, |
| 88 'sanitize_literal': SanitizeLiteral, | 89 'sanitize_literal': SanitizeLiteral, |
| 89 }) | 90 }) |
| 90 jinja_env.add_extension('jinja2.ext.loopcontrols') | 91 jinja_env.add_extension('jinja2.ext.loopcontrols') |
| 91 return jinja_env | 92 return jinja_env |
| 92 | 93 |
| 93 | 94 |
| 94 def PatchFullQualifiedRefs(json_api): | 95 def PatchFullQualifiedRefs(json_api): |
| 95 def PatchFullQualifiedRefsInDomain(json, domain_name): | 96 def PatchFullQualifiedRefsInDomain(json, domain_name): |
| 96 if isinstance(json, list): | 97 if isinstance(json, list): |
| 97 for item in json: | 98 for item in json: |
| 98 PatchFullQualifiedRefsInDomain(item, domain_name) | 99 PatchFullQualifiedRefsInDomain(item, domain_name) |
| 99 | 100 |
| 100 if not isinstance(json, dict): | 101 if not isinstance(json, dict): |
| 101 return | 102 return |
| 102 for key in json: | 103 for key in json: |
| 103 if key != '$ref': | 104 if key != '$ref': |
| 104 PatchFullQualifiedRefsInDomain(json[key], domain_name) | 105 PatchFullQualifiedRefsInDomain(json[key], domain_name) |
| 105 continue | 106 continue |
| 106 if not '.' in json['$ref']: | 107 if not '.' in json['$ref']: |
| 107 json['$ref'] = domain_name + '.' + json['$ref'] | 108 json['$ref'] = domain_name + '.' + json['$ref'] |
| 108 | 109 |
| 109 for domain in json_api['domains']: | 110 for domain in json_api['domains']: |
| 110 PatchFullQualifiedRefsInDomain(domain, domain['domain']) | 111 PatchFullQualifiedRefsInDomain(domain, domain['domain']) |
| 111 | 112 |
| 112 | 113 |
| 113 def CreateUserTypeDefinition(domain, type): | 114 def CreateUserTypeDefinition(domain, type): |
| 114 namespace = CamelCaseToHackerStyle(domain['domain']) | 115 namespace = CamelCaseToHackerStyle(domain['domain']) |
| 115 return { | 116 return { |
| 116 'return_type': 'std::unique_ptr<headless::%s::%s>' % ( | 117 'return_type': 'std::unique_ptr<headless::%s::%s>' % ( |
| 117 namespace, type['id']), | 118 namespace, type['id']), |
| 118 'pass_type': 'std::unique_ptr<headless::%s::%s>' % (namespace, type['id']), | 119 'pass_type': 'std::unique_ptr<headless::%s::%s>' % ( |
| 119 'to_raw_type': '*%s', | 120 namespace, type['id']), |
| 120 'to_raw_return_type': '%s.get()', | 121 'to_raw_type': '*%s', |
| 121 'to_pass_type': 'std::move(%s)', | 122 'to_raw_return_type': '%s.get()', |
| 122 'type': 'std::unique_ptr<headless::%s::%s>' % (namespace, type['id']), | 123 'to_pass_type': 'std::move(%s)', |
| 123 'raw_type': 'headless::%s::%s' % (namespace, type['id']), | 124 'type': 'std::unique_ptr<headless::%s::%s>' % (namespace, type['id']), |
| 124 'raw_pass_type': 'headless::%s::%s*' % (namespace, type['id']), | 125 'raw_type': 'headless::%s::%s' % (namespace, type['id']), |
| 125 'raw_return_type': 'const headless::%s::%s*' % (namespace, type['id']), | 126 'raw_pass_type': 'headless::%s::%s*' % (namespace, type['id']), |
| 127 'raw_return_type': 'const headless::%s::%s*' % (namespace, type['id']), |
| 126 } | 128 } |
| 127 | 129 |
| 128 | 130 |
| 129 def CreateEnumTypeDefinition(domain_name, type): | 131 def CreateEnumTypeDefinition(domain_name, type): |
| 130 namespace = CamelCaseToHackerStyle(domain_name) | 132 namespace = CamelCaseToHackerStyle(domain_name) |
| 131 return { | 133 return { |
| 132 'return_type': 'headless::%s::%s' % (namespace, type['id']), | 134 'return_type': 'headless::%s::%s' % (namespace, type['id']), |
| 133 'pass_type': 'headless::%s::%s' % (namespace, type['id']), | 135 'pass_type': 'headless::%s::%s' % (namespace, type['id']), |
| 134 'to_raw_type': '%s', | 136 'to_raw_type': '%s', |
| 135 'to_raw_return_type': '%s', | 137 'to_raw_return_type': '%s', |
| 136 'to_pass_type': '%s', | 138 'to_pass_type': '%s', |
| 137 'type': 'headless::%s::%s' % (namespace, type['id']), | 139 'type': 'headless::%s::%s' % (namespace, type['id']), |
| 138 'raw_type': 'headless::%s::%s' % (namespace, type['id']), | 140 'raw_type': 'headless::%s::%s' % (namespace, type['id']), |
| 139 'raw_pass_type': 'headless::%s::%s' % (namespace, type['id']), | 141 'raw_pass_type': 'headless::%s::%s' % (namespace, type['id']), |
| 140 'raw_return_type': 'headless::%s::%s' % (namespace, type['id']), | 142 'raw_return_type': 'headless::%s::%s' % (namespace, type['id']), |
| 141 } | 143 } |
| 142 | 144 |
| 143 | 145 |
| 144 def CreateObjectTypeDefinition(): | 146 def CreateObjectTypeDefinition(): |
| 145 return { | 147 return { |
| 146 'return_type': 'std::unique_ptr<base::DictionaryValue>', | 148 'return_type': 'std::unique_ptr<base::DictionaryValue>', |
| 147 'pass_type': 'std::unique_ptr<base::DictionaryValue>', | 149 'pass_type': 'std::unique_ptr<base::DictionaryValue>', |
| 148 'to_raw_type': '*%s', | 150 'to_raw_type': '*%s', |
| 149 'to_raw_return_type': '%s.get()', | 151 'to_raw_return_type': '%s.get()', |
| 150 'to_pass_type': 'std::move(%s)', | 152 'to_pass_type': 'std::move(%s)', |
| 151 'type': 'std::unique_ptr<base::DictionaryValue>', | 153 'type': 'std::unique_ptr<base::DictionaryValue>', |
| 152 'raw_type': 'base::DictionaryValue', | 154 'raw_type': 'base::DictionaryValue', |
| 153 'raw_pass_type': 'base::DictionaryValue*', | 155 'raw_pass_type': 'base::DictionaryValue*', |
| 154 'raw_return_type': 'const base::DictionaryValue*', | 156 'raw_return_type': 'const base::DictionaryValue*', |
| 155 } | 157 } |
| 156 | 158 |
| 157 | 159 |
| 158 def WrapObjectTypeDefinition(type): | 160 def WrapObjectTypeDefinition(type): |
| 159 id = type.get('id', 'base::Value') | 161 id = type.get('id', 'base::Value') |
| 160 return { | 162 return { |
| 161 'return_type': 'std::unique_ptr<%s>' % id, | 163 'return_type': 'std::unique_ptr<%s>' % id, |
| 162 'pass_type': 'std::unique_ptr<%s>' % id, | 164 'pass_type': 'std::unique_ptr<%s>' % id, |
| 163 'to_raw_type': '*%s', | 165 'to_raw_type': '*%s', |
| 164 'to_raw_return_type': '%s.get()', | 166 'to_raw_return_type': '%s.get()', |
| 165 'to_pass_type': 'std::move(%s)', | 167 'to_pass_type': 'std::move(%s)', |
| 166 'type': 'std::unique_ptr<%s>' % id, | 168 'type': 'std::unique_ptr<%s>' % id, |
| 167 'raw_type': id, | 169 'raw_type': id, |
| 168 'raw_pass_type': '%s*' % id, | 170 'raw_pass_type': '%s*' % id, |
| 169 'raw_return_type': 'const %s*' % id, | 171 'raw_return_type': 'const %s*' % id, |
| 170 } | 172 } |
| 171 | 173 |
| 172 | 174 |
| 173 def CreateAnyTypeDefinition(): | 175 def CreateAnyTypeDefinition(): |
| 174 return { | 176 return { |
| 175 'return_type': 'std::unique_ptr<base::Value>', | 177 'return_type': 'std::unique_ptr<base::Value>', |
| 176 'pass_type': 'std::unique_ptr<base::Value>', | 178 'pass_type': 'std::unique_ptr<base::Value>', |
| 177 'to_raw_type': '*%s', | 179 'to_raw_type': '*%s', |
| 178 'to_raw_return_type': '%s.get()', | 180 'to_raw_return_type': '%s.get()', |
| 179 'to_pass_type': 'std::move(%s)', | 181 'to_pass_type': 'std::move(%s)', |
| 180 'type': 'std::unique_ptr<base::Value>', | 182 'type': 'std::unique_ptr<base::Value>', |
| 181 'raw_type': 'base::Value', | 183 'raw_type': 'base::Value', |
| 182 'raw_pass_type': 'base::Value*', | 184 'raw_pass_type': 'base::Value*', |
| 183 'raw_return_type': 'const base::Value*', | 185 'raw_return_type': 'const base::Value*', |
| 184 } | 186 } |
| 185 | 187 |
| 186 | 188 |
| 187 def CreateStringTypeDefinition(domain): | 189 def CreateStringTypeDefinition(domain): |
| 188 return { | 190 return { |
| 189 'return_type': 'std::string', | 191 'return_type': 'std::string', |
| 190 'pass_type': 'const std::string&', | 192 'pass_type': 'const std::string&', |
| 191 'to_pass_type': '%s', | 193 'to_pass_type': '%s', |
| 192 'to_raw_type': '%s', | 194 'to_raw_type': '%s', |
| 193 'to_raw_return_type': '%s', | 195 'to_raw_return_type': '%s', |
| 194 'type': 'std::string', | 196 'type': 'std::string', |
| 195 'raw_type': 'std::string', | 197 'raw_type': 'std::string', |
| 196 'raw_pass_type': 'const std::string&', | 198 'raw_pass_type': 'const std::string&', |
| 197 'raw_return_type': 'std::string', | 199 'raw_return_type': 'std::string', |
| 198 } | 200 } |
| 199 | 201 |
| 200 | 202 |
| 201 def CreatePrimitiveTypeDefinition(type): | 203 def CreatePrimitiveTypeDefinition(type): |
| 202 typedefs = { | 204 typedefs = { |
| 203 'number': 'double', | 205 'number': 'double', |
| 204 'integer': 'int', | 206 'integer': 'int', |
| 205 'boolean': 'bool', | 207 'boolean': 'bool', |
| 206 'string': 'std::string', | 208 'string': 'std::string', |
| 207 } | 209 } |
| 208 return { | 210 return { |
| 209 'return_type': typedefs[type], | 211 'return_type': typedefs[type], |
| 210 'pass_type': typedefs[type], | 212 'pass_type': typedefs[type], |
| 211 'to_pass_type': '%s', | 213 'to_pass_type': '%s', |
| 212 'to_raw_type': '%s', | 214 'to_raw_type': '%s', |
| 213 'to_raw_return_type': '%s', | 215 'to_raw_return_type': '%s', |
| 214 'type': typedefs[type], | 216 'type': typedefs[type], |
| 215 'raw_type': typedefs[type], | 217 'raw_type': typedefs[type], |
| 216 'raw_pass_type': typedefs[type], | 218 'raw_pass_type': typedefs[type], |
| 217 'raw_return_type': typedefs[type], | 219 'raw_return_type': typedefs[type], |
| 218 } | 220 } |
| 219 | 221 |
| 220 | 222 |
| 221 type_definitions = {} | 223 type_definitions = {} |
| 222 type_definitions['number'] = CreatePrimitiveTypeDefinition('number') | 224 type_definitions['number'] = CreatePrimitiveTypeDefinition('number') |
| 223 type_definitions['integer'] = CreatePrimitiveTypeDefinition('integer') | 225 type_definitions['integer'] = CreatePrimitiveTypeDefinition('integer') |
| 224 type_definitions['boolean'] = CreatePrimitiveTypeDefinition('boolean') | 226 type_definitions['boolean'] = CreatePrimitiveTypeDefinition('boolean') |
| 225 type_definitions['string'] = CreatePrimitiveTypeDefinition('string') | 227 type_definitions['string'] = CreatePrimitiveTypeDefinition('string') |
| 226 type_definitions['object'] = CreateObjectTypeDefinition() | 228 type_definitions['object'] = CreateObjectTypeDefinition() |
| 227 type_definitions['any'] = CreateAnyTypeDefinition() | 229 type_definitions['any'] = CreateAnyTypeDefinition() |
| 228 | 230 |
| 229 | 231 |
| 230 def WrapArrayDefinition(type): | 232 def WrapArrayDefinition(type): |
| 231 return { | 233 return { |
| 232 'return_type': 'std::vector<%s>' % type['type'], | 234 'return_type': 'std::vector<%s>' % type['type'], |
| 233 'pass_type': 'std::vector<%s>' % type['type'], | 235 'pass_type': 'std::vector<%s>' % type['type'], |
| 234 'to_raw_type': '%s', | 236 'to_raw_type': '%s', |
| 235 'to_raw_return_type': '&%s', | 237 'to_raw_return_type': '&%s', |
| 236 'to_pass_type': 'std::move(%s)', | 238 'to_pass_type': 'std::move(%s)', |
| 237 'type': 'std::vector<%s>' % type['type'], | 239 'type': 'std::vector<%s>' % type['type'], |
| 238 'raw_type': 'std::vector<%s>' % type['type'], | 240 'raw_type': 'std::vector<%s>' % type['type'], |
| 239 'raw_pass_type': 'std::vector<%s>*' % type['type'], | 241 'raw_pass_type': 'std::vector<%s>*' % type['type'], |
| 240 'raw_return_type': 'const std::vector<%s>*' % type['type'], | 242 'raw_return_type': 'const std::vector<%s>*' % type['type'], |
| 241 } | 243 } |
| 242 | 244 |
| 243 | 245 |
| 244 def CreateTypeDefinitions(json_api): | 246 def CreateTypeDefinitions(json_api): |
| 245 for domain in json_api['domains']: | 247 for domain in json_api['domains']: |
| 246 if not ('types' in domain): | 248 if not ('types' in domain): |
| 247 continue | 249 continue |
| 248 for type in domain['types']: | 250 for type in domain['types']: |
| 249 if type['type'] == 'object': | 251 if type['type'] == 'object': |
| 250 if 'properties' in type: | 252 if 'properties' in type: |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 294 def SynthesizeEnumType(domain, owner, type): | 296 def SynthesizeEnumType(domain, owner, type): |
| 295 type['id'] = ToTitleCase(owner) + ToTitleCase(type['name']) | 297 type['id'] = ToTitleCase(owner) + ToTitleCase(type['name']) |
| 296 type_definitions[domain['domain'] + '.' + type['id']] = ( | 298 type_definitions[domain['domain'] + '.' + type['id']] = ( |
| 297 CreateEnumTypeDefinition(domain['domain'], type)) | 299 CreateEnumTypeDefinition(domain['domain'], type)) |
| 298 type['$ref'] = domain['domain'] + '.' + type['id'] | 300 type['$ref'] = domain['domain'] + '.' + type['id'] |
| 299 domain['types'].append(type) | 301 domain['types'].append(type) |
| 300 | 302 |
| 301 | 303 |
| 302 def SynthesizeCommandTypes(json_api): | 304 def SynthesizeCommandTypes(json_api): |
| 303 """Generate types for command parameters, return values and enum | 305 """Generate types for command parameters, return values and enum |
| 304 properties.""" | 306 properties. |
| 307 """ |
| 305 for domain in json_api['domains']: | 308 for domain in json_api['domains']: |
| 306 if not 'types' in domain: | 309 if not 'types' in domain: |
| 307 domain['types'] = [] | 310 domain['types'] = [] |
| 308 for type in domain['types']: | 311 for type in domain['types']: |
| 309 if type['type'] == 'object': | 312 if type['type'] == 'object': |
| 310 for property in type.get('properties', []): | 313 for property in type.get('properties', []): |
| 311 if 'enum' in property and not '$ref' in property: | 314 if 'enum' in property and not '$ref' in property: |
| 312 SynthesizeEnumType(domain, type['id'], property) | 315 SynthesizeEnumType(domain, type['id'], property) |
| 313 | 316 |
| 314 for command in domain.get('commands', []): | 317 for command in domain.get('commands', []): |
| 315 if 'parameters' in command: | 318 if 'parameters' in command: |
| 316 for parameter in command['parameters']: | 319 for parameter in command['parameters']: |
| 317 if 'enum' in parameter and not '$ref' in parameter: | 320 if 'enum' in parameter and not '$ref' in parameter: |
| 318 SynthesizeEnumType(domain, command['name'], parameter) | 321 SynthesizeEnumType(domain, command['name'], parameter) |
| 319 parameters_type = { | 322 parameters_type = { |
| 320 'id': ToTitleCase(command['name']) + 'Params', | 323 'id': ToTitleCase(command['name']) + 'Params', |
| 321 'type': 'object', | 324 'type': 'object', |
| 322 'description': 'Parameters for the %s command.' % ToTitleCase( | 325 'description': 'Parameters for the %s command.' % ToTitleCase( |
| 323 command['name']), | 326 command['name']), |
| 324 'properties': command['parameters'] | 327 'properties': command['parameters'] |
| 325 } | 328 } |
| 326 domain['types'].append(parameters_type) | 329 domain['types'].append(parameters_type) |
| 327 if 'returns' in command: | 330 if 'returns' in command: |
| 328 for parameter in command['returns']: | 331 for parameter in command['returns']: |
| 329 if 'enum' in parameter and not '$ref' in parameter: | 332 if 'enum' in parameter and not '$ref' in parameter: |
| 330 SynthesizeEnumType(domain, command['name'], parameter) | 333 SynthesizeEnumType(domain, command['name'], parameter) |
| 331 result_type = { | 334 result_type = { |
| 332 'id': ToTitleCase(command['name']) + 'Result', | 335 'id': ToTitleCase(command['name']) + 'Result', |
| 333 'type': 'object', | 336 'type': 'object', |
| 334 'description': 'Result for the %s command.' % ToTitleCase( | 337 'description': 'Result for the %s command.' % ToTitleCase( |
| 335 command['name']), | 338 command['name']), |
| 336 'properties': command['returns'] | 339 'properties': command['returns'] |
| 337 } | 340 } |
| 338 domain['types'].append(result_type) | 341 domain['types'].append(result_type) |
| 339 | 342 |
| 340 | 343 |
| 341 def SynthesizeEventTypes(json_api): | 344 def SynthesizeEventTypes(json_api): |
| 342 """Generate types for events and their properties. | 345 """Generate types for events and their properties. |
| 343 | 346 |
| 344 Note that parameter objects are also created for events without parameters to | 347 Note that parameter objects are also created for events without parameters to |
| 345 make it easier to introduce parameters later. | 348 make it easier to introduce parameters later. |
| 346 """ | 349 """ |
| 347 for domain in json_api['domains']: | 350 for domain in json_api['domains']: |
| 348 if not 'types' in domain: | 351 if not 'types' in domain: |
| 349 domain['types'] = [] | 352 domain['types'] = [] |
| 350 for event in domain.get('events', []): | 353 for event in domain.get('events', []): |
| 351 for parameter in event.get('parameters', []): | 354 for parameter in event.get('parameters', []): |
| 352 if 'enum' in parameter and not '$ref' in parameter: | 355 if 'enum' in parameter and not '$ref' in parameter: |
| 353 SynthesizeEnumType(domain, event['name'], parameter) | 356 SynthesizeEnumType(domain, event['name'], parameter) |
| 354 event_type = { | 357 event_type = { |
| 355 'id': ToTitleCase(event['name']) + 'Params', | 358 'id': ToTitleCase(event['name']) + 'Params', |
| 356 'type': 'object', | 359 'type': 'object', |
| 357 'description': 'Parameters for the %s event.' % ToTitleCase( | 360 'description': 'Parameters for the %s event.' % ToTitleCase( |
| 358 event['name']), | 361 event['name']), |
| 359 'properties': event.get('parameters', []) | 362 'properties': event.get('parameters', []) |
| 360 } | 363 } |
| 361 domain['types'].append(event_type) | 364 domain['types'].append(event_type) |
| 362 | 365 |
| 363 | 366 |
| 367 def InitializeDomainDependencies(json_api): |
| 368 """For each domain create list of domains given domain depends on, |
| 369 including itself.""" |
| 370 |
| 371 direct_deps = collections.defaultdict(set) |
| 372 |
| 373 def GetDomainDepsFromRefs(domain_name, json): |
| 374 if isinstance(json, list): |
| 375 for value in json: |
| 376 GetDomainDepsFromRefs(domain_name, value) |
| 377 return |
| 378 |
| 379 if not isinstance(json, dict): |
| 380 return |
| 381 for value in json.itervalues(): |
| 382 GetDomainDepsFromRefs(domain_name, value) |
| 383 |
| 384 if '$ref' in json: |
| 385 if '.' in json['$ref']: |
| 386 dep = json['$ref'].split('.')[0] |
| 387 direct_deps[domain_name].add(dep) |
| 388 |
| 389 for domain in json_api['domains']: |
| 390 direct_deps[domain['domain']] = set(domain.get('dependencies', [])) |
| 391 GetDomainDepsFromRefs(domain['domain'], domain) |
| 392 |
| 393 def TraverseDependencies(domain, deps): |
| 394 if domain in deps: |
| 395 return |
| 396 deps.add(domain) |
| 397 |
| 398 for dep in direct_deps[domain]: |
| 399 TraverseDependencies(dep, deps) |
| 400 |
| 401 for domain in json_api['domains']: |
| 402 domain_deps = set() |
| 403 TraverseDependencies(domain['domain'], domain_deps) |
| 404 domain['dependencies'] = sorted(domain_deps) |
| 405 |
| 406 |
| 364 def PatchExperimentalCommandsAndEvents(json_api): | 407 def PatchExperimentalCommandsAndEvents(json_api): |
| 365 """ | 408 """Mark all commands and events in experimental domains as experimental |
| 366 Mark all commands and events in experimental domains as experimental | |
| 367 and make sure experimental commands have at least empty parameters | 409 and make sure experimental commands have at least empty parameters |
| 368 and return values. | 410 and return values. |
| 369 """ | 411 """ |
| 370 for domain in json_api['domains']: | 412 for domain in json_api['domains']: |
| 371 if domain.get('experimental', False): | 413 if domain.get('experimental', False): |
| 372 for command in domain.get('commands', []): | 414 for command in domain.get('commands', []): |
| 373 command['experimental'] = True | 415 command['experimental'] = True |
| 374 for event in domain.get('events', []): | 416 for event in domain.get('events', []): |
| 375 event['experimental'] = True | 417 event['experimental'] = True |
| 376 | 418 |
| 377 | 419 |
| 420 def EnsureDirectoryExists(path): |
| 421 if not os.path.exists(path): |
| 422 os.makedirs(path) |
| 423 |
| 424 |
| 378 def EnsureCommandsHaveParametersAndReturnTypes(json_api): | 425 def EnsureCommandsHaveParametersAndReturnTypes(json_api): |
| 379 """ | 426 """Make sure all commands have at least empty parameters and return values. |
| 380 Make sure all commands have at least empty parameters and return values. This | 427 This guarantees API compatibility if a previously experimental command is made |
| 381 guarantees API compatibility if a previously experimental command is made | |
| 382 stable. | 428 stable. |
| 383 """ | 429 """ |
| 384 for domain in json_api['domains']: | 430 for domain in json_api['domains']: |
| 385 for command in domain.get('commands', []): | 431 for command in domain.get('commands', []): |
| 386 if not 'parameters' in command: | 432 if not 'parameters' in command: |
| 387 command['parameters'] = [] | 433 command['parameters'] = [] |
| 388 if not 'returns' in command: | 434 if not 'returns' in command: |
| 389 command['returns'] = [] | 435 command['returns'] = [] |
| 390 for event in domain.get('events', []): | 436 for event in domain.get('events', []): |
| 391 if not 'parameters' in event: | 437 if not 'parameters' in event: |
| 392 event['parameters'] = [] | 438 event['parameters'] = [] |
| 393 | 439 |
| 394 | 440 |
| 395 def Generate(jinja_env, output_dirname, json_api, class_name, file_types): | 441 def Generate(jinja_env, output_dirname, json_api, |
| 442 class_name, file_types, file_name=None): |
| 443 if file_name is None: |
| 444 file_name = class_name |
| 445 EnsureDirectoryExists(output_dirname) |
| 396 template_context = { | 446 template_context = { |
| 397 'api': json_api, | 447 'api': json_api, |
| 398 'join_arrays': JoinArrays, | 448 'join_arrays': JoinArrays, |
| 399 'resolve_type': ResolveType, | 449 'resolve_type': ResolveType, |
| 400 'type_definition': TypeDefinition, | 450 'type_definition': TypeDefinition, |
| 401 } | 451 } |
| 402 for file_type in file_types: | 452 for file_type in file_types: |
| 403 template = jinja_env.get_template('/%s_%s.template' % ( | 453 template = jinja_env.get_template('/%s_%s.template' % ( |
| 404 class_name, file_type)) | 454 class_name, file_type)) |
| 405 output_file = '%s/%s.%s' % (output_dirname, class_name, file_type) | 455 output_file = '%s/%s.%s' % (output_dirname, file_name, file_type) |
| 406 with open(output_file, 'w') as f: | 456 with open(output_file, 'w') as f: |
| 407 f.write(template.render(template_context)) | 457 f.write(template.render(template_context)) |
| 408 | 458 |
| 409 | 459 |
| 410 def GenerateDomains(jinja_env, output_dirname, json_api, class_name, | 460 def GeneratePerDomain(jinja_env, output_dirname, json_api, class_name, |
| 411 file_types): | 461 file_types, domain_name_to_file_name_func): |
| 462 EnsureDirectoryExists(output_dirname) |
| 412 for file_type in file_types: | 463 for file_type in file_types: |
| 413 template = jinja_env.get_template('/%s_%s.template' % ( | 464 template = jinja_env.get_template('/%s_%s.template' % ( |
| 414 class_name, file_type)) | 465 class_name, file_type)) |
| 415 for domain in json_api['domains']: | 466 for domain in json_api['domains']: |
| 416 template_context = { | 467 template_context = { |
| 417 'domain': domain, | 468 'domain': domain, |
| 418 'resolve_type': ResolveType, | 469 'resolve_type': ResolveType, |
| 419 } | 470 } |
| 420 domain_name = CamelCaseToHackerStyle(domain['domain']) | 471 domain_name = CamelCaseToHackerStyle(domain['domain']) |
| 421 output_file = '%s/%s.%s' % (output_dirname, domain_name, file_type) | 472 output_file = '%s/%s.%s' % (output_dirname, |
| 473 domain_name_to_file_name_func(domain_name), |
| 474 file_type) |
| 422 with open(output_file, 'w') as f: | 475 with open(output_file, 'w') as f: |
| 423 f.write(template.render(template_context)) | 476 f.write(template.render(template_context)) |
| 424 | 477 |
| 425 | 478 |
| 479 def GenerateDomains(jinja_env, output_dirname, json_api): |
| 480 GeneratePerDomain( |
| 481 jinja_env, os.path.join(output_dirname, 'devtools', 'domains'), json_api, |
| 482 'domain', ['cc', 'h'], |
| 483 lambda domain_name: domain_name) |
| 484 |
| 485 # TODO(altimin): Remove this in 2017. |
| 486 # Generate DOMAIN.h in the old directory for backwards compatibility. |
| 487 GeneratePerDomain( |
| 488 jinja_env, os.path.join(output_dirname, 'domains'), json_api, |
| 489 'deprecated_domain', ['h'], lambda domain_name: domain_name) |
| 490 |
| 491 |
| 492 def GenerateTypes(jinja_env, output_dirname, json_api): |
| 493 # Generate forward declarations for types. |
| 494 GeneratePerDomain( |
| 495 jinja_env, os.path.join(output_dirname, 'devtools', 'internal'), |
| 496 json_api, 'domain_types_forward_declaration', ['h'], |
| 497 lambda domain_name: 'types_forward_declaration_%s' % (domain_name, )) |
| 498 # Generate types on per-domain basis. |
| 499 GeneratePerDomain( |
| 500 jinja_env, os.path.join(output_dirname, 'devtools', 'domains'), |
| 501 json_api, 'domain_types', ['h', 'cc'], |
| 502 lambda domain_name: 'types_%s' % (domain_name, )) |
| 503 |
| 504 # TODO(altimin): Remove this in 2017. |
| 505 # Generate types.h for backwards compatibility. |
| 506 Generate(jinja_env, os.path.join(output_dirname, 'domains'), json_api, |
| 507 'deprecated_types', ['h'], 'types') |
| 508 |
| 509 |
| 510 def GenerateTypeConversions(jinja_env, output_dirname, json_api): |
| 511 # Generate type conversions on per-domain basis. |
| 512 GeneratePerDomain( |
| 513 jinja_env, os.path.join(output_dirname, 'devtools', 'internal'), |
| 514 json_api, 'domain_type_conversions', ['h'], |
| 515 lambda domain_name: 'type_conversions_%s' % (domain_name, )) |
| 516 |
| 517 # TODO(altimin): Remove this in 2017. |
| 518 # Generate type_conversions.h for backwards compatibility. |
| 519 Generate(jinja_env, os.path.join(output_dirname, 'domains'), json_api, |
| 520 'deprecated_type_conversions', ['h'], 'type_conversions') |
| 521 |
| 522 |
| 426 if __name__ == '__main__': | 523 if __name__ == '__main__': |
| 427 json_api, output_dirname = ParseArguments(sys.argv[1:]) | 524 json_api, output_dirname = ParseArguments(sys.argv[1:]) |
| 428 jinja_env = InitializeJinjaEnv(output_dirname) | 525 jinja_env = InitializeJinjaEnv(output_dirname) |
| 526 InitializeDomainDependencies(json_api) |
| 429 PatchExperimentalCommandsAndEvents(json_api) | 527 PatchExperimentalCommandsAndEvents(json_api) |
| 430 EnsureCommandsHaveParametersAndReturnTypes(json_api) | 528 EnsureCommandsHaveParametersAndReturnTypes(json_api) |
| 431 SynthesizeCommandTypes(json_api) | 529 SynthesizeCommandTypes(json_api) |
| 432 SynthesizeEventTypes(json_api) | 530 SynthesizeEventTypes(json_api) |
| 433 PatchFullQualifiedRefs(json_api) | 531 PatchFullQualifiedRefs(json_api) |
| 434 CreateTypeDefinitions(json_api) | 532 CreateTypeDefinitions(json_api) |
| 435 Generate(jinja_env, output_dirname, json_api, 'types', ['cc', 'h']) | 533 GenerateDomains(jinja_env, output_dirname, json_api) |
| 436 Generate(jinja_env, output_dirname, json_api, 'type_conversions', ['h']) | 534 GenerateTypes(jinja_env, output_dirname, json_api) |
| 437 GenerateDomains(jinja_env, output_dirname, json_api, 'domain', ['cc', 'h']) | 535 GenerateTypeConversions(jinja_env, output_dirname, json_api) |
| OLD | NEW |