| Index: headless/lib/browser/devtools_api/client_api_generator.py
|
| diff --git a/headless/lib/browser/client_api_generator.py b/headless/lib/browser/devtools_api/client_api_generator.py
|
| similarity index 52%
|
| rename from headless/lib/browser/client_api_generator.py
|
| rename to headless/lib/browser/devtools_api/client_api_generator.py
|
| index bb93ede97aa557fde61a8ead59480db0b88f9abd..366b37a2c2e15294c591db841404616e6e36c82e 100644
|
| --- a/headless/lib/browser/client_api_generator.py
|
| +++ b/headless/lib/browser/devtools_api/client_api_generator.py
|
| @@ -3,9 +3,10 @@
|
| # found in the LICENSE file.
|
|
|
| import argparse
|
| +import collections
|
| import os.path
|
| -import sys
|
| import re
|
| +import sys
|
| try:
|
| import json
|
| except ImportError:
|
| @@ -61,14 +62,14 @@ def CamelCaseToHackerStyle(name):
|
|
|
| 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',
|
| + # 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)
|
|
|
|
|
| @@ -82,10 +83,10 @@ def InitializeJinjaEnv(cache_dir):
|
| 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,
|
| + '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
|
| @@ -113,108 +114,109 @@ def PatchFullQualifiedRefs(json_api):
|
| 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']),
|
| + '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']),
|
| + '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*',
|
| + '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,
|
| + '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*',
|
| + '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',
|
| + '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',
|
| + '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],
|
| + '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],
|
| }
|
|
|
|
|
| @@ -229,15 +231,15 @@ 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'],
|
| + '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'],
|
| }
|
|
|
|
|
| @@ -301,7 +303,8 @@ def SynthesizeEnumType(domain, owner, type):
|
|
|
| def SynthesizeCommandTypes(json_api):
|
| """Generate types for command parameters, return values and enum
|
| - properties."""
|
| + properties.
|
| + """
|
| for domain in json_api['domains']:
|
| if not 'types' in domain:
|
| domain['types'] = []
|
| @@ -317,11 +320,11 @@ def SynthesizeCommandTypes(json_api):
|
| 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']
|
| + '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:
|
| @@ -329,11 +332,11 @@ def SynthesizeCommandTypes(json_api):
|
| 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']
|
| + 'id': ToTitleCase(command['name']) + 'Result',
|
| + 'type': 'object',
|
| + 'description': 'Result for the %s command.' % ToTitleCase(
|
| + command['name']),
|
| + 'properties': command['returns']
|
| }
|
| domain['types'].append(result_type)
|
|
|
| @@ -352,18 +355,57 @@ def SynthesizeEventTypes(json_api):
|
| 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', [])
|
| + '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 InitializeDomainDependencies(json_api):
|
| + """For each domain create list of domains given domain depends on,
|
| + including itself."""
|
| +
|
| + direct_deps = collections.defaultdict(set)
|
| +
|
| + def GetDomainDepsFromRefs(domain_name, json):
|
| + if isinstance(json, list):
|
| + for value in json:
|
| + GetDomainDepsFromRefs(domain_name, value)
|
| + return
|
| +
|
| + if not isinstance(json, dict):
|
| + return
|
| + for value in json.itervalues():
|
| + GetDomainDepsFromRefs(domain_name, value)
|
| +
|
| + if '$ref' in json:
|
| + if '.' in json['$ref']:
|
| + dep = json['$ref'].split('.')[0]
|
| + direct_deps[domain_name].add(dep)
|
| +
|
| + for domain in json_api['domains']:
|
| + direct_deps[domain['domain']] = set(domain.get('dependencies', []))
|
| + GetDomainDepsFromRefs(domain['domain'], domain)
|
| +
|
| + def TraverseDependencies(domain, deps):
|
| + if domain in deps:
|
| + return
|
| + deps.add(domain)
|
| +
|
| + for dep in direct_deps[domain]:
|
| + TraverseDependencies(dep, deps)
|
| +
|
| + for domain in json_api['domains']:
|
| + domain_deps = set()
|
| + TraverseDependencies(domain['domain'], domain_deps)
|
| + domain['dependencies'] = sorted(domain_deps)
|
| +
|
| +
|
| def PatchExperimentalCommandsAndEvents(json_api):
|
| - """
|
| - Mark all commands and events in experimental domains as experimental
|
| + """Mark all commands and events in experimental domains as experimental
|
| and make sure experimental commands have at least empty parameters
|
| and return values.
|
| """
|
| @@ -375,10 +417,14 @@ def PatchExperimentalCommandsAndEvents(json_api):
|
| event['experimental'] = True
|
|
|
|
|
| +def EnsureDirectoryExists(path):
|
| + if not os.path.exists(path):
|
| + os.makedirs(path)
|
| +
|
| +
|
| 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
|
| + """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']:
|
| @@ -392,46 +438,98 @@ def EnsureCommandsHaveParametersAndReturnTypes(json_api):
|
| event['parameters'] = []
|
|
|
|
|
| -def Generate(jinja_env, output_dirname, json_api, class_name, file_types):
|
| +def Generate(jinja_env, output_dirname, json_api,
|
| + class_name, file_types, file_name=None):
|
| + if file_name is None:
|
| + file_name = class_name
|
| + EnsureDirectoryExists(output_dirname)
|
| template_context = {
|
| - 'api': json_api,
|
| - 'join_arrays': JoinArrays,
|
| - 'resolve_type': ResolveType,
|
| - 'type_definition': TypeDefinition,
|
| + '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)
|
| + output_file = '%s/%s.%s' % (output_dirname, file_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):
|
| +def GeneratePerDomain(jinja_env, output_dirname, json_api, class_name,
|
| + file_types, domain_name_to_file_name_func):
|
| + EnsureDirectoryExists(output_dirname)
|
| 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': domain,
|
| + 'resolve_type': ResolveType,
|
| }
|
| domain_name = CamelCaseToHackerStyle(domain['domain'])
|
| - output_file = '%s/%s.%s' % (output_dirname, domain_name, file_type)
|
| + output_file = '%s/%s.%s' % (output_dirname,
|
| + domain_name_to_file_name_func(domain_name),
|
| + file_type)
|
| with open(output_file, 'w') as f:
|
| f.write(template.render(template_context))
|
|
|
|
|
| +def GenerateDomains(jinja_env, output_dirname, json_api):
|
| + GeneratePerDomain(
|
| + jinja_env, os.path.join(output_dirname, 'devtools', 'domains'), json_api,
|
| + 'domain', ['cc', 'h'],
|
| + lambda domain_name: domain_name)
|
| +
|
| + # TODO(altimin): Remove this in 2017.
|
| + # Generate DOMAIN.h in the old directory for backwards compatibility.
|
| + GeneratePerDomain(
|
| + jinja_env, os.path.join(output_dirname, 'domains'), json_api,
|
| + 'deprecated_domain', ['h'], lambda domain_name: domain_name)
|
| +
|
| +
|
| +def GenerateTypes(jinja_env, output_dirname, json_api):
|
| + # Generate forward declarations for types.
|
| + GeneratePerDomain(
|
| + jinja_env, os.path.join(output_dirname, 'devtools', 'internal'),
|
| + json_api, 'domain_types_forward_declaration', ['h'],
|
| + lambda domain_name: 'types_forward_declaration_%s' % (domain_name, ))
|
| + # Generate types on per-domain basis.
|
| + GeneratePerDomain(
|
| + jinja_env, os.path.join(output_dirname, 'devtools', 'domains'),
|
| + json_api, 'domain_types', ['h', 'cc'],
|
| + lambda domain_name: 'types_%s' % (domain_name, ))
|
| +
|
| + # TODO(altimin): Remove this in 2017.
|
| + # Generate types.h for backwards compatibility.
|
| + Generate(jinja_env, os.path.join(output_dirname, 'domains'), json_api,
|
| + 'deprecated_types', ['h'], 'types')
|
| +
|
| +
|
| +def GenerateTypeConversions(jinja_env, output_dirname, json_api):
|
| + # Generate type conversions on per-domain basis.
|
| + GeneratePerDomain(
|
| + jinja_env, os.path.join(output_dirname, 'devtools', 'internal'),
|
| + json_api, 'domain_type_conversions', ['h'],
|
| + lambda domain_name: 'type_conversions_%s' % (domain_name, ))
|
| +
|
| + # TODO(altimin): Remove this in 2017.
|
| + # Generate type_conversions.h for backwards compatibility.
|
| + Generate(jinja_env, os.path.join(output_dirname, 'domains'), json_api,
|
| + 'deprecated_type_conversions', ['h'], 'type_conversions')
|
| +
|
| +
|
| if __name__ == '__main__':
|
| json_api, output_dirname = ParseArguments(sys.argv[1:])
|
| jinja_env = InitializeJinjaEnv(output_dirname)
|
| + InitializeDomainDependencies(json_api)
|
| 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'])
|
| + GenerateDomains(jinja_env, output_dirname, json_api)
|
| + GenerateTypes(jinja_env, output_dirname, json_api)
|
| + GenerateTypeConversions(jinja_env, output_dirname, json_api)
|
|
|