Index: headless/lib/browser/client_api_generator.py |
diff --git a/headless/lib/browser/client_api_generator.py b/headless/lib/browser/client_api_generator.py |
deleted file mode 100644 |
index bb93ede97aa557fde61a8ead59480db0b88f9abd..0000000000000000000000000000000000000000 |
--- a/headless/lib/browser/client_api_generator.py |
+++ /dev/null |
@@ -1,437 +0,0 @@ |
-# Copyright 2016 The Chromium Authors. All rights reserved. |
-# Use of this source code is governed by a BSD-style license that can be |
-# found in the LICENSE file. |
- |
-import argparse |
-import os.path |
-import sys |
-import re |
-try: |
- import json |
-except ImportError: |
- import simplejson as json |
- |
-# Path handling for libraries and templates |
-# Paths have to be normalized because Jinja uses the exact template path to |
-# determine the hash used in the cache filename, and we need a pre-caching step |
-# to be concurrency-safe. Use absolute path because __file__ is absolute if |
-# module is imported, and relative if executed directly. |
-# If paths differ between pre-caching and individual file compilation, the cache |
-# is regenerated, which causes a race condition and breaks concurrent build, |
-# since some compile processes will try to read the partially written cache. |
-module_path, module_filename = os.path.split(os.path.realpath(__file__)) |
-third_party_dir = os.path.normpath(os.path.join( |
- module_path, os.pardir, os.pardir, os.pardir, 'third_party')) |
-templates_dir = module_path |
- |
-# jinja2 is in chromium's third_party directory. |
-# Insert at 1 so at front to override system libraries, and |
-# after path[0] == invoking script dir |
-sys.path.insert(1, third_party_dir) |
-import jinja2 |
- |
- |
-def ParseArguments(args): |
- """Parses command line arguments and returns a (json_api, output_dir) tuple. |
- """ |
- cmdline_parser = argparse.ArgumentParser() |
- cmdline_parser.add_argument('--protocol', required=True) |
- cmdline_parser.add_argument('--output_dir', required=True) |
- |
- args = cmdline_parser.parse_args(args) |
- with open(args.protocol, 'r') as f: |
- return json.load(f), args.output_dir |
- |
- |
-def ToTitleCase(name): |
- return name[:1].upper() + name[1:] |
- |
- |
-def DashToCamelCase(word): |
- return ''.join(ToTitleCase(x) for x in word.split('-')) |
- |
- |
-def CamelCaseToHackerStyle(name): |
- # Do two passes to insert '_' chars to deal with overlapping matches (e.g., |
- # 'LoLoLoL'). |
- name = re.sub(r'([^_])([A-Z][a-z]+?)', r'\1_\2', name) |
- name = re.sub(r'([^_])([A-Z][a-z]+?)', r'\1_\2', name) |
- return name.lower() |
- |
- |
-def SanitizeLiteral(literal): |
- return { |
- # Rename null enumeration values to avoid a clash with the NULL macro. |
- 'null': 'none', |
- # Rename mathematical constants to avoid colliding with C macros. |
- 'Infinity': 'InfinityValue', |
- '-Infinity': 'NegativeInfinityValue', |
- 'NaN': 'NaNValue', |
- # Turn negative zero into a safe identifier. |
- '-0': 'NegativeZeroValue', |
- }.get(literal, literal) |
- |
- |
-def InitializeJinjaEnv(cache_dir): |
- jinja_env = jinja2.Environment( |
- loader=jinja2.FileSystemLoader(templates_dir), |
- # Bytecode cache is not concurrency-safe unless pre-cached: |
- # if pre-cached this is read-only, but writing creates a race condition. |
- bytecode_cache=jinja2.FileSystemBytecodeCache(cache_dir), |
- keep_trailing_newline=True, # Newline-terminate generated files. |
- lstrip_blocks=True, # So we can indent control flow tags. |
- trim_blocks=True) |
- jinja_env.filters.update({ |
- 'to_title_case': ToTitleCase, |
- 'dash_to_camelcase': DashToCamelCase, |
- 'camelcase_to_hacker_style': CamelCaseToHackerStyle, |
- 'sanitize_literal': SanitizeLiteral, |
- }) |
- jinja_env.add_extension('jinja2.ext.loopcontrols') |
- return jinja_env |
- |
- |
-def PatchFullQualifiedRefs(json_api): |
- def PatchFullQualifiedRefsInDomain(json, domain_name): |
- if isinstance(json, list): |
- for item in json: |
- PatchFullQualifiedRefsInDomain(item, domain_name) |
- |
- if not isinstance(json, dict): |
- return |
- for key in json: |
- if key != '$ref': |
- PatchFullQualifiedRefsInDomain(json[key], domain_name) |
- continue |
- if not '.' in json['$ref']: |
- json['$ref'] = domain_name + '.' + json['$ref'] |
- |
- for domain in json_api['domains']: |
- PatchFullQualifiedRefsInDomain(domain, domain['domain']) |
- |
- |
-def CreateUserTypeDefinition(domain, type): |
- namespace = CamelCaseToHackerStyle(domain['domain']) |
- return { |
- 'return_type': 'std::unique_ptr<headless::%s::%s>' % ( |
- namespace, type['id']), |
- 'pass_type': 'std::unique_ptr<headless::%s::%s>' % (namespace, type['id']), |
- 'to_raw_type': '*%s', |
- 'to_raw_return_type': '%s.get()', |
- 'to_pass_type': 'std::move(%s)', |
- 'type': 'std::unique_ptr<headless::%s::%s>' % (namespace, type['id']), |
- 'raw_type': 'headless::%s::%s' % (namespace, type['id']), |
- 'raw_pass_type': 'headless::%s::%s*' % (namespace, type['id']), |
- 'raw_return_type': 'const headless::%s::%s*' % (namespace, type['id']), |
- } |
- |
- |
-def CreateEnumTypeDefinition(domain_name, type): |
- namespace = CamelCaseToHackerStyle(domain_name) |
- return { |
- 'return_type': 'headless::%s::%s' % (namespace, type['id']), |
- 'pass_type': 'headless::%s::%s' % (namespace, type['id']), |
- 'to_raw_type': '%s', |
- 'to_raw_return_type': '%s', |
- 'to_pass_type': '%s', |
- 'type': 'headless::%s::%s' % (namespace, type['id']), |
- 'raw_type': 'headless::%s::%s' % (namespace, type['id']), |
- 'raw_pass_type': 'headless::%s::%s' % (namespace, type['id']), |
- 'raw_return_type': 'headless::%s::%s' % (namespace, type['id']), |
- } |
- |
- |
-def CreateObjectTypeDefinition(): |
- return { |
- 'return_type': 'std::unique_ptr<base::DictionaryValue>', |
- 'pass_type': 'std::unique_ptr<base::DictionaryValue>', |
- 'to_raw_type': '*%s', |
- 'to_raw_return_type': '%s.get()', |
- 'to_pass_type': 'std::move(%s)', |
- 'type': 'std::unique_ptr<base::DictionaryValue>', |
- 'raw_type': 'base::DictionaryValue', |
- 'raw_pass_type': 'base::DictionaryValue*', |
- 'raw_return_type': 'const base::DictionaryValue*', |
- } |
- |
- |
-def WrapObjectTypeDefinition(type): |
- id = type.get('id', 'base::Value') |
- return { |
- 'return_type': 'std::unique_ptr<%s>' % id, |
- 'pass_type': 'std::unique_ptr<%s>' % id, |
- 'to_raw_type': '*%s', |
- 'to_raw_return_type': '%s.get()', |
- 'to_pass_type': 'std::move(%s)', |
- 'type': 'std::unique_ptr<%s>' % id, |
- 'raw_type': id, |
- 'raw_pass_type': '%s*' % id, |
- 'raw_return_type': 'const %s*' % id, |
- } |
- |
- |
-def CreateAnyTypeDefinition(): |
- return { |
- 'return_type': 'std::unique_ptr<base::Value>', |
- 'pass_type': 'std::unique_ptr<base::Value>', |
- 'to_raw_type': '*%s', |
- 'to_raw_return_type': '%s.get()', |
- 'to_pass_type': 'std::move(%s)', |
- 'type': 'std::unique_ptr<base::Value>', |
- 'raw_type': 'base::Value', |
- 'raw_pass_type': 'base::Value*', |
- 'raw_return_type': 'const base::Value*', |
- } |
- |
- |
-def CreateStringTypeDefinition(domain): |
- return { |
- 'return_type': 'std::string', |
- 'pass_type': 'const std::string&', |
- 'to_pass_type': '%s', |
- 'to_raw_type': '%s', |
- 'to_raw_return_type': '%s', |
- 'type': 'std::string', |
- 'raw_type': 'std::string', |
- 'raw_pass_type': 'const std::string&', |
- 'raw_return_type': 'std::string', |
- } |
- |
- |
-def CreatePrimitiveTypeDefinition(type): |
- typedefs = { |
- 'number': 'double', |
- 'integer': 'int', |
- 'boolean': 'bool', |
- 'string': 'std::string', |
- } |
- return { |
- 'return_type': typedefs[type], |
- 'pass_type': typedefs[type], |
- 'to_pass_type': '%s', |
- 'to_raw_type': '%s', |
- 'to_raw_return_type': '%s', |
- 'type': typedefs[type], |
- 'raw_type': typedefs[type], |
- 'raw_pass_type': typedefs[type], |
- 'raw_return_type': typedefs[type], |
- } |
- |
- |
-type_definitions = {} |
-type_definitions['number'] = CreatePrimitiveTypeDefinition('number') |
-type_definitions['integer'] = CreatePrimitiveTypeDefinition('integer') |
-type_definitions['boolean'] = CreatePrimitiveTypeDefinition('boolean') |
-type_definitions['string'] = CreatePrimitiveTypeDefinition('string') |
-type_definitions['object'] = CreateObjectTypeDefinition() |
-type_definitions['any'] = CreateAnyTypeDefinition() |
- |
- |
-def WrapArrayDefinition(type): |
- return { |
- 'return_type': 'std::vector<%s>' % type['type'], |
- 'pass_type': 'std::vector<%s>' % type['type'], |
- 'to_raw_type': '%s', |
- 'to_raw_return_type': '&%s', |
- 'to_pass_type': 'std::move(%s)', |
- 'type': 'std::vector<%s>' % type['type'], |
- 'raw_type': 'std::vector<%s>' % type['type'], |
- 'raw_pass_type': 'std::vector<%s>*' % type['type'], |
- 'raw_return_type': 'const std::vector<%s>*' % type['type'], |
- } |
- |
- |
-def CreateTypeDefinitions(json_api): |
- for domain in json_api['domains']: |
- if not ('types' in domain): |
- continue |
- for type in domain['types']: |
- if type['type'] == 'object': |
- if 'properties' in type: |
- type_definitions[domain['domain'] + '.' + type['id']] = ( |
- CreateUserTypeDefinition(domain, type)) |
- else: |
- type_definitions[domain['domain'] + '.' + type['id']] = ( |
- CreateObjectTypeDefinition()) |
- elif type['type'] == 'array': |
- items_type = type['items']['type'] |
- type_definitions[domain['domain'] + '.' + type['id']] = ( |
- WrapArrayDefinition(type_definitions[items_type])) |
- elif 'enum' in type: |
- type_definitions[domain['domain'] + '.' + type['id']] = ( |
- CreateEnumTypeDefinition(domain['domain'], type)) |
- type['$ref'] = domain['domain'] + '.' + type['id'] |
- elif type['type'] == 'any': |
- type_definitions[domain['domain'] + '.' + type['id']] = ( |
- CreateAnyTypeDefinition()) |
- else: |
- type_definitions[domain['domain'] + '.' + type['id']] = ( |
- CreatePrimitiveTypeDefinition(type['type'])) |
- |
- |
-def TypeDefinition(name): |
- return type_definitions[name] |
- |
- |
-def ResolveType(property): |
- if '$ref' in property: |
- return type_definitions[property['$ref']] |
- elif property['type'] == 'object': |
- return WrapObjectTypeDefinition(property) |
- elif property['type'] == 'array': |
- return WrapArrayDefinition(ResolveType(property['items'])) |
- return type_definitions[property['type']] |
- |
- |
-def JoinArrays(dict, keys): |
- result = [] |
- for key in keys: |
- if key in dict: |
- result += dict[key] |
- return result |
- |
- |
-def SynthesizeEnumType(domain, owner, type): |
- type['id'] = ToTitleCase(owner) + ToTitleCase(type['name']) |
- type_definitions[domain['domain'] + '.' + type['id']] = ( |
- CreateEnumTypeDefinition(domain['domain'], type)) |
- type['$ref'] = domain['domain'] + '.' + type['id'] |
- domain['types'].append(type) |
- |
- |
-def SynthesizeCommandTypes(json_api): |
- """Generate types for command parameters, return values and enum |
- properties.""" |
- for domain in json_api['domains']: |
- if not 'types' in domain: |
- domain['types'] = [] |
- for type in domain['types']: |
- if type['type'] == 'object': |
- for property in type.get('properties', []): |
- if 'enum' in property and not '$ref' in property: |
- SynthesizeEnumType(domain, type['id'], property) |
- |
- for command in domain.get('commands', []): |
- if 'parameters' in command: |
- for parameter in command['parameters']: |
- if 'enum' in parameter and not '$ref' in parameter: |
- SynthesizeEnumType(domain, command['name'], parameter) |
- parameters_type = { |
- 'id': ToTitleCase(command['name']) + 'Params', |
- 'type': 'object', |
- 'description': 'Parameters for the %s command.' % ToTitleCase( |
- command['name']), |
- 'properties': command['parameters'] |
- } |
- domain['types'].append(parameters_type) |
- if 'returns' in command: |
- for parameter in command['returns']: |
- if 'enum' in parameter and not '$ref' in parameter: |
- SynthesizeEnumType(domain, command['name'], parameter) |
- result_type = { |
- 'id': ToTitleCase(command['name']) + 'Result', |
- 'type': 'object', |
- 'description': 'Result for the %s command.' % ToTitleCase( |
- command['name']), |
- 'properties': command['returns'] |
- } |
- domain['types'].append(result_type) |
- |
- |
-def SynthesizeEventTypes(json_api): |
- """Generate types for events and their properties. |
- |
- Note that parameter objects are also created for events without parameters to |
- make it easier to introduce parameters later. |
- """ |
- for domain in json_api['domains']: |
- if not 'types' in domain: |
- domain['types'] = [] |
- for event in domain.get('events', []): |
- for parameter in event.get('parameters', []): |
- if 'enum' in parameter and not '$ref' in parameter: |
- SynthesizeEnumType(domain, event['name'], parameter) |
- event_type = { |
- 'id': ToTitleCase(event['name']) + 'Params', |
- 'type': 'object', |
- 'description': 'Parameters for the %s event.' % ToTitleCase( |
- event['name']), |
- 'properties': event.get('parameters', []) |
- } |
- domain['types'].append(event_type) |
- |
- |
-def PatchExperimentalCommandsAndEvents(json_api): |
- """ |
- Mark all commands and events in experimental domains as experimental |
- and make sure experimental commands have at least empty parameters |
- and return values. |
- """ |
- for domain in json_api['domains']: |
- if domain.get('experimental', False): |
- for command in domain.get('commands', []): |
- command['experimental'] = True |
- for event in domain.get('events', []): |
- event['experimental'] = True |
- |
- |
-def EnsureCommandsHaveParametersAndReturnTypes(json_api): |
- """ |
- Make sure all commands have at least empty parameters and return values. This |
- guarantees API compatibility if a previously experimental command is made |
- stable. |
- """ |
- for domain in json_api['domains']: |
- for command in domain.get('commands', []): |
- if not 'parameters' in command: |
- command['parameters'] = [] |
- if not 'returns' in command: |
- command['returns'] = [] |
- for event in domain.get('events', []): |
- if not 'parameters' in event: |
- event['parameters'] = [] |
- |
- |
-def Generate(jinja_env, output_dirname, json_api, class_name, file_types): |
- template_context = { |
- 'api': json_api, |
- 'join_arrays': JoinArrays, |
- 'resolve_type': ResolveType, |
- 'type_definition': TypeDefinition, |
- } |
- for file_type in file_types: |
- template = jinja_env.get_template('/%s_%s.template' % ( |
- class_name, file_type)) |
- output_file = '%s/%s.%s' % (output_dirname, class_name, file_type) |
- with open(output_file, 'w') as f: |
- f.write(template.render(template_context)) |
- |
- |
-def GenerateDomains(jinja_env, output_dirname, json_api, class_name, |
- file_types): |
- for file_type in file_types: |
- template = jinja_env.get_template('/%s_%s.template' % ( |
- class_name, file_type)) |
- for domain in json_api['domains']: |
- template_context = { |
- 'domain': domain, |
- 'resolve_type': ResolveType, |
- } |
- domain_name = CamelCaseToHackerStyle(domain['domain']) |
- output_file = '%s/%s.%s' % (output_dirname, domain_name, file_type) |
- with open(output_file, 'w') as f: |
- f.write(template.render(template_context)) |
- |
- |
-if __name__ == '__main__': |
- json_api, output_dirname = ParseArguments(sys.argv[1:]) |
- jinja_env = InitializeJinjaEnv(output_dirname) |
- PatchExperimentalCommandsAndEvents(json_api) |
- EnsureCommandsHaveParametersAndReturnTypes(json_api) |
- SynthesizeCommandTypes(json_api) |
- SynthesizeEventTypes(json_api) |
- PatchFullQualifiedRefs(json_api) |
- CreateTypeDefinitions(json_api) |
- Generate(jinja_env, output_dirname, json_api, 'types', ['cc', 'h']) |
- Generate(jinja_env, output_dirname, json_api, 'type_conversions', ['h']) |
- GenerateDomains(jinja_env, output_dirname, json_api, 'domain', ['cc', 'h']) |