Enable python formatting in //tools/json_schema_compiler/
Python formatting using `git cl format` has been enabled in the codebase for a while now by including a .style.yapf file in your folder hierarchy. However back in 2020 an exception was added to ignore the json schema compiler folder, as the code in there uses a lot of chained function calls that are split across lines for readability and the formatter was condensing them into a much less readable state. This CL resolves this by using the line continuation character `\` for these chained function lines, allowing us to retain the more readable indentation and also enable the formatter by default. It also runs a full format over all the files in the directory, to get them into a consistent state where we can have the formatter enabled by default going forward with low impact. No behavior change is expected. Bug: 40711753 Change-Id: I6e10dc5af022ce0e3557099a84773aa9cc92d2e4 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5804254 Commit-Queue: Tim <tjudkins@chromium.org> Reviewed-by: Devlin Cronin <rdevlin.cronin@chromium.org> Cr-Commit-Position: refs/heads/main@{#1345613}
This commit is contained in:
parent
91e2dd0507
commit
1e3d052650
.yapfignore
tools/json_schema_compiler
cc_generator.pycode_util.pycode_util_test.pycompiler.pycpp_bundle_generator.pycpp_bundle_generator_test.pycpp_generator.pycpp_namespace_environment.pycpp_type_generator.pycpp_type_generator_test.pycpp_util.pycpp_util_test.pyfeature_compiler.pyfeature_compiler_test.pyfeatures_cc_generator.pyfeatures_compiler.pyfeatures_h_generator.pygenerate_all_externs.pyh_generator.pyidl_schema.pyidl_schema_test.pyjs_externs_generator.pyjs_externs_generator_test.pyjs_interface_generator.pyjs_interface_generator_test.pyjs_util.pyjson_parse.pyjson_schema.pyjson_schema_test.pymemoize.pymodel.pymodel_test.pynamespace_resolver.pypreview.pyschema_loader.pyschema_util.pyschema_util_test.pyts_definition_generator.pyts_definition_generator_test.pyutil_cc_helper.pyweb_idl_schema.pyweb_idl_schema_test.py
@ -5,6 +5,3 @@
|
||||
third_party/blink/tools/blinkpy/third_party/*
|
||||
third_party/blink/web_tests/external/wpt/*
|
||||
tools/valgrind/asan/third_party/asan_symbolize.py
|
||||
|
||||
# TODO(crbug.com/1116155): Enable this for formatting by yapf.
|
||||
tools/json_schema_compiler/*
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2,19 +2,22 @@
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
||||
class Code(object):
|
||||
"""A convenience object for constructing code.
|
||||
|
||||
Logically each object should be a block of code. All methods except |Render|
|
||||
and |IsEmpty| return self.
|
||||
"""
|
||||
|
||||
def __init__(self, indent_size=2, comment_length=80):
|
||||
self._code = []
|
||||
self._indent_size = indent_size
|
||||
self._comment_length = comment_length
|
||||
self._line_prefixes = []
|
||||
|
||||
def Append(self, line='',
|
||||
def Append(self,
|
||||
line='',
|
||||
substitute=True,
|
||||
indent_level=None,
|
||||
new_line=True,
|
||||
@ -110,8 +113,11 @@ class Code(object):
|
||||
self.Append(line)
|
||||
return self
|
||||
|
||||
def Comment(self, comment, comment_prefix='// ',
|
||||
wrap_indent=0, new_line=True):
|
||||
def Comment(self,
|
||||
comment,
|
||||
comment_prefix='// ',
|
||||
wrap_indent=0,
|
||||
new_line=True):
|
||||
"""Adds the given string as a comment.
|
||||
|
||||
Will split the comment if it's too long. Use mainly for variable length
|
||||
@ -119,6 +125,7 @@ class Code(object):
|
||||
|
||||
Unaffected by code.Substitute().
|
||||
"""
|
||||
|
||||
# Helper function to trim a comment to the maximum length, and return one
|
||||
# line and the remainder of the comment.
|
||||
def trim_comment(comment, max_len):
|
||||
@ -196,6 +203,7 @@ class Code(object):
|
||||
class Line(object):
|
||||
"""A line of code.
|
||||
"""
|
||||
|
||||
def __init__(self, value, substitute=True):
|
||||
self.value = value
|
||||
self.substitute = substitute
|
||||
|
@ -6,7 +6,9 @@
|
||||
from code_util import Code
|
||||
import unittest
|
||||
|
||||
|
||||
class CodeTest(unittest.TestCase):
|
||||
|
||||
def testAppend(self):
|
||||
c = Code()
|
||||
c.Append('line')
|
||||
@ -14,61 +16,56 @@ class CodeTest(unittest.TestCase):
|
||||
|
||||
def testBlock(self):
|
||||
c = Code()
|
||||
(c.Append('line')
|
||||
.Sblock('sblock')
|
||||
.Append('inner')
|
||||
.Append('moreinner')
|
||||
.Sblock('moresblock')
|
||||
.Append('inner')
|
||||
.Eblock('out')
|
||||
.Append('inner')
|
||||
(c.Append('line') \
|
||||
.Sblock('sblock') \
|
||||
.Append('inner') \
|
||||
.Append('moreinner') \
|
||||
.Sblock('moresblock') \
|
||||
.Append('inner') \
|
||||
.Eblock('out') \
|
||||
.Append('inner') \
|
||||
.Eblock('out')
|
||||
)
|
||||
self.assertEqual(
|
||||
'line\n'
|
||||
'sblock\n'
|
||||
' inner\n'
|
||||
' moreinner\n'
|
||||
' moresblock\n'
|
||||
' inner\n'
|
||||
' out\n'
|
||||
' inner\n'
|
||||
'out',
|
||||
c.Render())
|
||||
'line\n'
|
||||
'sblock\n'
|
||||
' inner\n'
|
||||
' moreinner\n'
|
||||
' moresblock\n'
|
||||
' inner\n'
|
||||
' out\n'
|
||||
' inner\n'
|
||||
'out', c.Render())
|
||||
|
||||
def testConcat(self):
|
||||
b = Code()
|
||||
(b.Sblock('2')
|
||||
.Append('2')
|
||||
(b.Sblock('2') \
|
||||
.Append('2') \
|
||||
.Eblock('2')
|
||||
)
|
||||
c = Code()
|
||||
(c.Sblock('1')
|
||||
.Concat(b)
|
||||
.Append('1')
|
||||
(c.Sblock('1') \
|
||||
.Concat(b) \
|
||||
.Append('1') \
|
||||
.Eblock('1')
|
||||
)
|
||||
self.assertMultiLineEqual(
|
||||
'1\n'
|
||||
' 2\n'
|
||||
' 2\n'
|
||||
' 2\n'
|
||||
' 1\n'
|
||||
'1',
|
||||
c.Render())
|
||||
self.assertMultiLineEqual('1\n'
|
||||
' 2\n'
|
||||
' 2\n'
|
||||
' 2\n'
|
||||
' 1\n'
|
||||
'1', c.Render())
|
||||
d = Code()
|
||||
a = Code()
|
||||
a.Concat(d)
|
||||
self.assertEqual('', a.Render())
|
||||
a.Concat(c)
|
||||
self.assertEqual(
|
||||
'1\n'
|
||||
' 2\n'
|
||||
' 2\n'
|
||||
' 2\n'
|
||||
' 1\n'
|
||||
'1',
|
||||
a.Render())
|
||||
self.assertEqual('1\n'
|
||||
' 2\n'
|
||||
' 2\n'
|
||||
' 2\n'
|
||||
' 1\n'
|
||||
'1', a.Render())
|
||||
|
||||
def testConcatErrors(self):
|
||||
c = Code()
|
||||
@ -89,11 +86,9 @@ class CodeTest(unittest.TestCase):
|
||||
c.Append('%(var1)s %(var2)s %(var3)s')
|
||||
c.Append('%(var2)s %(var1)s %(var3)s')
|
||||
c.Substitute({'var1': 'one', 'var2': 'two', 'var3': 'three'})
|
||||
self.assertEqual(
|
||||
'one two one\n'
|
||||
'one two three\n'
|
||||
'two one three',
|
||||
c.Render())
|
||||
self.assertEqual('one two one\n'
|
||||
'one two three\n'
|
||||
'two one three', c.Render())
|
||||
|
||||
def testSubstituteErrors(self):
|
||||
# No unnamed placeholders allowed when substitute is run
|
||||
@ -118,14 +113,13 @@ class CodeTest(unittest.TestCase):
|
||||
|
||||
def testComment(self):
|
||||
long_comment = ('This comment is ninety one characters in longness, '
|
||||
'that is, using a different word, length.')
|
||||
'that is, using a different word, length.')
|
||||
c = Code()
|
||||
c.Comment(long_comment)
|
||||
self.assertEqual(
|
||||
'// This comment is ninety one characters '
|
||||
'in longness, that is, using a different\n'
|
||||
'// word, length.',
|
||||
c.Render())
|
||||
'// word, length.', c.Render())
|
||||
c = Code()
|
||||
c.Sblock('sblock')
|
||||
c.Comment(long_comment)
|
||||
@ -139,27 +133,26 @@ class CodeTest(unittest.TestCase):
|
||||
'eblock\n'
|
||||
'// This comment is ninety one characters in '
|
||||
'longness, that is, using a different\n'
|
||||
'// word, length.',
|
||||
c.Render())
|
||||
'// word, length.', c.Render())
|
||||
# Words that cannot be broken up are left as too long.
|
||||
long_word = 'x' * 100
|
||||
c = Code()
|
||||
c.Comment('xxx')
|
||||
c.Comment(long_word)
|
||||
c.Comment('xxx')
|
||||
self.assertEqual(
|
||||
'// xxx\n'
|
||||
'// ' + 'x' * 100 + '\n'
|
||||
'// xxx',
|
||||
c.Render())
|
||||
self.assertEqual('// xxx\n'
|
||||
'// ' + 'x' * 100 + '\n'
|
||||
'// xxx', c.Render())
|
||||
c = Code(indent_size=2, comment_length=40)
|
||||
c.Comment('Pretend this is a Closure Compiler style comment, which should '
|
||||
'both wrap and indent', comment_prefix=' * ', wrap_indent=4)
|
||||
c.Comment(
|
||||
'Pretend this is a Closure Compiler style comment, which should '
|
||||
'both wrap and indent',
|
||||
comment_prefix=' * ',
|
||||
wrap_indent=4)
|
||||
self.assertEqual(
|
||||
' * Pretend this is a Closure Compiler\n'
|
||||
' * style comment, which should both\n'
|
||||
' * wrap and indent',
|
||||
c.Render())
|
||||
' * wrap and indent', c.Render())
|
||||
|
||||
def testCommentWithSpecialCharacters(self):
|
||||
c = Code()
|
||||
@ -170,8 +163,7 @@ class CodeTest(unittest.TestCase):
|
||||
d.Append('90')
|
||||
d.Concat(c)
|
||||
self.assertEqual('90\n'
|
||||
'// 20% of 80%s',
|
||||
d.Render())
|
||||
'// 20% of 80%s', d.Render())
|
||||
|
||||
def testLinePrefixes(self):
|
||||
c = Code()
|
||||
@ -192,8 +184,7 @@ class CodeTest(unittest.TestCase):
|
||||
' * x: y\n'
|
||||
' * }\n'
|
||||
' * }}\n'
|
||||
' */',
|
||||
output)
|
||||
' */', output)
|
||||
|
||||
def testSameLineAppendConcatComment(self):
|
||||
c = Code()
|
||||
@ -206,12 +197,13 @@ class CodeTest(unittest.TestCase):
|
||||
c = Code()
|
||||
c.Append('This is a')
|
||||
c.Comment(' spectacular 80-character line thingy ' +
|
||||
'that fits wonderfully everywhere.',
|
||||
'that fits wonderfully everywhere.',
|
||||
comment_prefix='',
|
||||
new_line=False)
|
||||
self.assertEqual('This is a spectacular 80-character line thingy that ' +
|
||||
'fits wonderfully everywhere.',
|
||||
c.Render())
|
||||
self.assertEqual(
|
||||
'This is a spectacular 80-character line thingy that ' +
|
||||
'fits wonderfully everywhere.', c.Render())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -101,12 +101,10 @@ def GenerateSchema(
|
||||
|
||||
# Construct the type generator with all the namespaces in this model.
|
||||
schema_dir = os.path.dirname(os.path.relpath(file_paths[0], root))
|
||||
namespace_resolver = NamespaceResolver(
|
||||
root, schema_dir, include_rules, cpp_namespace_pattern
|
||||
)
|
||||
type_generator = CppTypeGenerator(
|
||||
api_model, namespace_resolver, default_namespace
|
||||
)
|
||||
namespace_resolver = NamespaceResolver(root, schema_dir, include_rules,
|
||||
cpp_namespace_pattern)
|
||||
type_generator = CppTypeGenerator(api_model, namespace_resolver,
|
||||
default_namespace)
|
||||
if generator_name in ('cpp-bundle-registration', 'cpp-bundle-schema'):
|
||||
cpp_bundle_generator = CppBundleGenerator(
|
||||
root,
|
||||
@ -187,12 +185,11 @@ if __name__ == '__main__':
|
||||
default='.',
|
||||
help=(
|
||||
'logical include root directory. Path to schema files from specified'
|
||||
' dir will be the include path.'
|
||||
),
|
||||
)
|
||||
parser.add_option(
|
||||
'-d', '--destdir', help='root directory to output generated files.'
|
||||
' dir will be the include path.'),
|
||||
)
|
||||
parser.add_option('-d',
|
||||
'--destdir',
|
||||
help='root directory to output generated files.')
|
||||
parser.add_option(
|
||||
'-n',
|
||||
'--namespace',
|
||||
@ -203,11 +200,9 @@ if __name__ == '__main__':
|
||||
'-b',
|
||||
'--bundle-name',
|
||||
default='',
|
||||
help=(
|
||||
'A string to prepend to generated bundle class names, so that '
|
||||
'multiple bundle rules can be used without conflicting. '
|
||||
'Only used with one of the cpp-bundle generators.'
|
||||
),
|
||||
help=('A string to prepend to generated bundle class names, so that '
|
||||
'multiple bundle rules can be used without conflicting. '
|
||||
'Only used with one of the cpp-bundle generators.'),
|
||||
)
|
||||
parser.add_option(
|
||||
'-g',
|
||||
@ -216,9 +211,7 @@ if __name__ == '__main__':
|
||||
choices=GENERATORS,
|
||||
help=(
|
||||
'The generator to use to build the output code. Supported values are'
|
||||
' %s'
|
||||
)
|
||||
% GENERATORS,
|
||||
' %s') % GENERATORS,
|
||||
)
|
||||
parser.add_option(
|
||||
'-i',
|
||||
@ -229,11 +222,9 @@ if __name__ == '__main__':
|
||||
parser.add_option(
|
||||
'-I',
|
||||
'--include-rules',
|
||||
help=(
|
||||
'A list of paths to include when searching for referenced objects,'
|
||||
" with the namespace separated by a ':'. Example: "
|
||||
'/foo/bar:Foo::Bar::%(namespace)s'
|
||||
),
|
||||
help=('A list of paths to include when searching for referenced objects,'
|
||||
" with the namespace separated by a ':'. Example: "
|
||||
'/foo/bar:Foo::Bar::%(namespace)s'),
|
||||
)
|
||||
|
||||
(opts, file_paths) = parser.parse_args()
|
||||
@ -242,28 +233,23 @@ if __name__ == '__main__':
|
||||
sys.exit(0) # This is OK as a no-op
|
||||
|
||||
# Unless in bundle mode, only one file should be specified.
|
||||
if (
|
||||
opts.generator not in ('cpp-bundle-registration', 'cpp-bundle-schema')
|
||||
and len(file_paths) > 1
|
||||
):
|
||||
if (opts.generator not in ('cpp-bundle-registration', 'cpp-bundle-schema')
|
||||
and len(file_paths) > 1):
|
||||
# TODO(sashab): Could also just use file_paths[0] here and not complain.
|
||||
raise Exception(
|
||||
'Unless in bundle mode, only one file can be specified at a time.'
|
||||
)
|
||||
'Unless in bundle mode, only one file can be specified at a time.')
|
||||
|
||||
def split_path_and_namespace(path_and_namespace):
|
||||
if ':' not in path_and_namespace:
|
||||
raise ValueError(
|
||||
'Invalid include rule "%s". Rules must be of the form path:namespace'
|
||||
% path_and_namespace
|
||||
)
|
||||
% path_and_namespace)
|
||||
return path_and_namespace.split(':', 1)
|
||||
|
||||
include_rules = []
|
||||
if opts.include_rules:
|
||||
include_rules = list(
|
||||
map(split_path_and_namespace, shlex.split(opts.include_rules))
|
||||
)
|
||||
map(split_path_and_namespace, shlex.split(opts.include_rules)))
|
||||
|
||||
result = GenerateSchema(
|
||||
opts.generator,
|
||||
|
@ -26,6 +26,7 @@ def _RemoveKey(node, key, type_restriction):
|
||||
for value in node:
|
||||
_RemoveKey(value, key, type_restriction)
|
||||
|
||||
|
||||
def _RemoveUnneededFields(schema):
|
||||
"""Returns a copy of |schema| with fields that aren't necessary at runtime
|
||||
removed.
|
||||
@ -41,6 +42,7 @@ def _RemoveUnneededFields(schema):
|
||||
_RemoveKey(ret, 'manifest_keys', object)
|
||||
return ret
|
||||
|
||||
|
||||
def _PrefixSchemaWithNamespace(schema):
|
||||
"""Modifies |schema| in place to prefix all types and references with a
|
||||
namespace, if they aren't already qualified. That is, in the tabs API, this
|
||||
@ -49,10 +51,11 @@ def _PrefixSchemaWithNamespace(schema):
|
||||
"""
|
||||
assert isinstance(schema, dict), "Schema is unexpected type"
|
||||
namespace = schema['namespace']
|
||||
|
||||
def prefix(obj, key, mandatory):
|
||||
if not key in obj:
|
||||
assert not mandatory, (
|
||||
'Required key "%s" is not present in object.' % key)
|
||||
assert not mandatory, ('Required key "%s" is not present in object.' %
|
||||
key)
|
||||
return
|
||||
assert type(obj[key]) is str
|
||||
if obj[key].find('.') == -1:
|
||||
@ -73,6 +76,7 @@ def _PrefixSchemaWithNamespace(schema):
|
||||
prefix(val, '$ref', False)
|
||||
for key, sub_val in val.items():
|
||||
prefix_refs(sub_val)
|
||||
|
||||
prefix_refs(schema)
|
||||
return schema
|
||||
|
||||
@ -81,15 +85,8 @@ class CppBundleGenerator(object):
|
||||
"""This class contains methods to generate code based on multiple schemas.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
root,
|
||||
model,
|
||||
api_defs,
|
||||
cpp_type_generator,
|
||||
cpp_namespace_pattern,
|
||||
bundle_name,
|
||||
source_file_dir,
|
||||
impl_dir):
|
||||
def __init__(self, root, model, api_defs, cpp_type_generator,
|
||||
cpp_namespace_pattern, bundle_name, source_file_dir, impl_dir):
|
||||
self._root = root
|
||||
self._model = model
|
||||
self._api_defs = api_defs
|
||||
@ -196,9 +193,9 @@ class CppBundleGenerator(object):
|
||||
if function.nocompile:
|
||||
continue
|
||||
namespace_types_name = JsFunctionNameToClassName(
|
||||
namespace.name, type_.name)
|
||||
c.Concat(self._GenerateRegistrationEntry(namespace_types_name,
|
||||
function))
|
||||
namespace.name, type_.name)
|
||||
c.Concat(
|
||||
self._GenerateRegistrationEntry(namespace_types_name, function))
|
||||
|
||||
if namespace_ifdefs is not None:
|
||||
c.Append("#endif // %s" % namespace_ifdefs, indent_level=0)
|
||||
@ -218,6 +215,7 @@ class CppBundleGenerator(object):
|
||||
|
||||
class _APIHGenerator(object):
|
||||
"""Generates the header for API registration / declaration"""
|
||||
|
||||
def __init__(self, cpp_bundle):
|
||||
self._bundle = cpp_bundle
|
||||
|
||||
@ -234,7 +232,7 @@ class _APIHGenerator(object):
|
||||
self._bundle._GenerateBundleClass('GeneratedFunctionRegistry'))
|
||||
c.Sblock(' public:')
|
||||
c.Append('static void RegisterAll('
|
||||
'ExtensionFunctionRegistry* registry);')
|
||||
'ExtensionFunctionRegistry* registry);')
|
||||
c.Eblock('};')
|
||||
c.Append()
|
||||
c.Concat(cpp_util.CloseNamespace(self._bundle._cpp_namespace))
|
||||
@ -251,9 +249,8 @@ class _APICCGenerator(object):
|
||||
c = code_util.Code()
|
||||
c.Append(cpp_util.CHROMIUM_LICENSE)
|
||||
c.Append()
|
||||
c.Append('#include "%s"' % (
|
||||
cpp_util.ToPosixPath(os.path.join(self._bundle._impl_dir,
|
||||
'generated_api_registration.h'))))
|
||||
c.Append('#include "%s"' % (cpp_util.ToPosixPath(
|
||||
os.path.join(self._bundle._impl_dir, 'generated_api_registration.h'))))
|
||||
c.Append()
|
||||
c.Append('#include "build/build_config.h"')
|
||||
c.Append('#include "build/chromeos_buildflags.h"')
|
||||
@ -261,17 +258,15 @@ class _APICCGenerator(object):
|
||||
for namespace in self._bundle._model.namespaces.values():
|
||||
namespace_name = namespace.unix_name.replace("experimental_", "")
|
||||
implementation_header = namespace.compiler_options.get(
|
||||
"implemented_in",
|
||||
"%s/%s/%s_api.h" % (self._bundle._impl_dir,
|
||||
namespace_name,
|
||||
namespace_name))
|
||||
"implemented_in", "%s/%s/%s_api.h" %
|
||||
(self._bundle._impl_dir, namespace_name, namespace_name))
|
||||
if not os.path.exists(
|
||||
os.path.join(self._bundle._root,
|
||||
os.path.normpath(implementation_header))):
|
||||
if "implemented_in" in namespace.compiler_options:
|
||||
raise ValueError('Header file for namespace "%s" specified in '
|
||||
'compiler_options not found: %s' %
|
||||
(namespace.unix_name, implementation_header))
|
||||
'compiler_options not found: %s' %
|
||||
(namespace.unix_name, implementation_header))
|
||||
continue
|
||||
ifdefs = self._bundle._GetPlatformIfdefs(namespace)
|
||||
if ifdefs is not None:
|
||||
@ -283,7 +278,7 @@ class _APICCGenerator(object):
|
||||
c.Append("#endif // %s" % ifdefs, indent_level=0)
|
||||
c.Append()
|
||||
c.Append('#include '
|
||||
'"extensions/browser/extension_function_registry.h"')
|
||||
'"extensions/browser/extension_function_registry.h"')
|
||||
c.Append()
|
||||
c.Concat(cpp_util.OpenNamespace(self._bundle._cpp_namespace))
|
||||
c.Append()
|
||||
@ -296,6 +291,7 @@ class _APICCGenerator(object):
|
||||
|
||||
class _SchemasHGenerator(object):
|
||||
"""Generates a code_util.Code object for the generated schemas .h file"""
|
||||
|
||||
def __init__(self, cpp_bundle):
|
||||
self._bundle = cpp_bundle
|
||||
|
||||
@ -322,8 +318,7 @@ class _SchemasHGenerator(object):
|
||||
def _FormatNameAsConstant(name):
|
||||
"""Formats a name to be a C++ constant of the form kConstantName"""
|
||||
name = '%s%s' % (name[0].upper(), name[1:])
|
||||
return 'k%s' % re.sub('_[a-z]',
|
||||
lambda m: m.group(0)[1].upper(),
|
||||
return 'k%s' % re.sub('_[a-z]', lambda m: m.group(0)[1].upper(),
|
||||
name.replace('.', '_'))
|
||||
|
||||
|
||||
@ -337,9 +332,8 @@ class _SchemasCCGenerator(object):
|
||||
c = code_util.Code()
|
||||
c.Append(cpp_util.CHROMIUM_LICENSE)
|
||||
c.Append()
|
||||
c.Append('#include "%s"' % (
|
||||
cpp_util.ToPosixPath(os.path.join(self._bundle._source_file_dir,
|
||||
'generated_schemas.h'))))
|
||||
c.Append('#include "%s"' % (cpp_util.ToPosixPath(
|
||||
os.path.join(self._bundle._source_file_dir, 'generated_schemas.h'))))
|
||||
c.Append()
|
||||
c.Append('#include <algorithm>')
|
||||
c.Append('#include <iterator>')
|
||||
@ -351,7 +345,7 @@ class _SchemasCCGenerator(object):
|
||||
for api in self._bundle._api_defs:
|
||||
namespace = self._bundle._model.namespaces[api.get('namespace')]
|
||||
json_content = json.dumps(_PrefixSchemaWithNamespace(
|
||||
_RemoveUnneededFields(api)),
|
||||
_RemoveUnneededFields(api)),
|
||||
separators=(',', ':'))
|
||||
# This will output a valid JSON C string. Note that some schemas are
|
||||
# too large to compile on windows. Split the JSON up into several
|
||||
@ -381,8 +375,10 @@ class _SchemasCCGenerator(object):
|
||||
c.Append('static constexpr auto kSchemas = '
|
||||
'base::MakeFixedFlatMap<std::string_view, std::string_view>({')
|
||||
c.Sblock()
|
||||
namespaces = [self._bundle._model.namespaces[api.get('namespace')].name
|
||||
for api in self._bundle._api_defs]
|
||||
namespaces = [
|
||||
self._bundle._model.namespaces[api.get('namespace')].name
|
||||
for api in self._bundle._api_defs
|
||||
]
|
||||
for namespace in sorted(namespaces):
|
||||
schema_constant_name = _FormatNameAsConstant(namespace)
|
||||
c.Append('{"%s", %s},' % (namespace, schema_constant_name))
|
||||
|
@ -10,26 +10,29 @@ import json_schema
|
||||
import os
|
||||
import unittest
|
||||
|
||||
|
||||
def _createCppBundleGenerator(file_path):
|
||||
json_object = json_schema.Load(file_path)
|
||||
model = Model()
|
||||
model.AddNamespace(json_object[0], file_path)
|
||||
cpp_bundle_generator = CppBundleGenerator(
|
||||
None, model, None, None, 'generated_api_schemas',
|
||||
None, None, None)
|
||||
cpp_bundle_generator = CppBundleGenerator(None, model, None, None,
|
||||
'generated_api_schemas', None, None,
|
||||
None)
|
||||
return (cpp_bundle_generator, model)
|
||||
|
||||
|
||||
def _getPlatformIfdefs(cpp_bundle_generator, model):
|
||||
return cpp_bundle_generator._GetPlatformIfdefs(
|
||||
list(list(model.namespaces.values())[0].functions.values())[0])
|
||||
|
||||
|
||||
class CppBundleGeneratorTest(unittest.TestCase):
|
||||
|
||||
def testIfDefsForWinLinux(self):
|
||||
cpp_bundle_generator, model = _createCppBundleGenerator(
|
||||
'test/function_platform_win_linux.json')
|
||||
self.assertEqual(
|
||||
'BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)',
|
||||
_getPlatformIfdefs(cpp_bundle_generator, model))
|
||||
self.assertEqual('BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)',
|
||||
_getPlatformIfdefs(cpp_bundle_generator, model))
|
||||
|
||||
def testIfDefsForAll(self):
|
||||
cpp_bundle_generator, model = _createCppBundleGenerator(
|
||||
@ -43,7 +46,7 @@ class CppBundleGeneratorTest(unittest.TestCase):
|
||||
cpp_bundle_generator, model = _createCppBundleGenerator(
|
||||
'test/function_platform_chromeos.json')
|
||||
self.assertEqual('BUILDFLAG(IS_CHROMEOS_ASH)',
|
||||
_getPlatformIfdefs(cpp_bundle_generator, model))
|
||||
_getPlatformIfdefs(cpp_bundle_generator, model))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -5,7 +5,9 @@
|
||||
from cc_generator import CCGenerator
|
||||
from h_generator import HGenerator
|
||||
|
||||
|
||||
class CppGenerator(object):
|
||||
|
||||
def __init__(self, type_generator):
|
||||
self.h_generator = HGenerator(type_generator)
|
||||
self.cc_generator = CCGenerator(type_generator)
|
||||
|
@ -2,6 +2,8 @@
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
||||
class CppNamespaceEnvironment(object):
|
||||
|
||||
def __init__(self, namespace_pattern):
|
||||
self.namespace_pattern = namespace_pattern
|
||||
|
@ -8,11 +8,13 @@ import cpp_util
|
||||
from json_parse import OrderedDict
|
||||
import schema_util
|
||||
|
||||
|
||||
class _TypeDependency(object):
|
||||
"""Contains information about a dependency a namespace has on a type: the
|
||||
type's model, and whether that dependency is "hard" meaning that it cannot be
|
||||
forward declared.
|
||||
"""
|
||||
|
||||
def __init__(self, type_, hard=False):
|
||||
self.type_ = type_
|
||||
self.hard = hard
|
||||
@ -25,6 +27,7 @@ class CppTypeGenerator(object):
|
||||
"""Manages the types of properties and provides utilities for getting the
|
||||
C++ type out of a model.Property
|
||||
"""
|
||||
|
||||
def __init__(self, model, namespace_resolver, default_namespace=None):
|
||||
"""Creates a cpp_type_generator. The given root_namespace should be of the
|
||||
format extensions::api::sub. The generator will generate code suitable for
|
||||
@ -40,9 +43,8 @@ class CppTypeGenerator(object):
|
||||
typename in an optional, for the regular case, or uses a base::expected for
|
||||
when it should support string errors.
|
||||
"""
|
||||
return (('base::expected<{typename}, std::u16string>'
|
||||
if support_errors else 'std::optional<{typename}>')
|
||||
.format(typename=typename))
|
||||
return (('base::expected<{typename}, std::u16string>' if support_errors else
|
||||
'std::optional<{typename}>').format(typename=typename))
|
||||
|
||||
def GetEnumNoneValue(self, type_, full_name=True):
|
||||
"""Gets the enum value in the given model. Property indicating no value has
|
||||
@ -91,7 +93,7 @@ class CppTypeGenerator(object):
|
||||
result = ''
|
||||
for char in name:
|
||||
if char in {'_', '-'}:
|
||||
change_to_upper=True
|
||||
change_to_upper = True
|
||||
elif change_to_upper:
|
||||
# Numbers must be kept separate, for better readability (e.g. kX86_64).
|
||||
if char.isnumeric() and result and result[-1].isnumeric():
|
||||
@ -125,9 +127,9 @@ class CppTypeGenerator(object):
|
||||
prefix = '{classname}::'.format(classname=classname)
|
||||
# We kCamelCase the string, also removing any _ from the name, to allow
|
||||
# SHOUTY_CASE keys to be kCamelCase as well.
|
||||
return '{prefix}k{name}'.format(
|
||||
prefix=prefix,
|
||||
name=self.FormatStringForEnumValue(enum_value.name))
|
||||
return '{prefix}k{name}'.format(prefix=prefix,
|
||||
name=self.FormatStringForEnumValue(
|
||||
enum_value.name))
|
||||
|
||||
def GetCppType(self, type_, is_optional=False):
|
||||
"""Translates a model.Property or model.Type into its C++ type.
|
||||
@ -155,8 +157,7 @@ class CppTypeGenerator(object):
|
||||
cpp_type = 'double'
|
||||
elif type_.property_type == PropertyType.STRING:
|
||||
cpp_type = 'std::string'
|
||||
elif type_.property_type in (PropertyType.ENUM,
|
||||
PropertyType.OBJECT,
|
||||
elif type_.property_type in (PropertyType.ENUM, PropertyType.OBJECT,
|
||||
PropertyType.CHOICES):
|
||||
if self._default_namespace is type_.namespace:
|
||||
cpp_type = cpp_util.Classname(type_.name)
|
||||
@ -164,8 +165,7 @@ class CppTypeGenerator(object):
|
||||
cpp_namespace = cpp_util.GetCppNamespace(
|
||||
type_.namespace.environment.namespace_pattern,
|
||||
type_.namespace.unix_name)
|
||||
cpp_type = '%s::%s' % (cpp_namespace,
|
||||
cpp_util.Classname(type_.name))
|
||||
cpp_type = '%s::%s' % (cpp_namespace, cpp_util.Classname(type_.name))
|
||||
elif type_.property_type == PropertyType.ANY:
|
||||
cpp_type = 'base::Value'
|
||||
elif type_.property_type == PropertyType.FUNCTION:
|
||||
@ -201,10 +201,9 @@ class CppTypeGenerator(object):
|
||||
return cpp_type
|
||||
|
||||
def IsCopyable(self, type_):
|
||||
return not (self.FollowRef(type_).property_type in (PropertyType.ANY,
|
||||
PropertyType.ARRAY,
|
||||
PropertyType.OBJECT,
|
||||
PropertyType.CHOICES))
|
||||
return not (self.FollowRef(type_).property_type
|
||||
in (PropertyType.ANY, PropertyType.ARRAY, PropertyType.OBJECT,
|
||||
PropertyType.CHOICES))
|
||||
|
||||
def GenerateForwardDeclarations(self):
|
||||
"""Returns the forward declarations for self._default_namespace.
|
||||
@ -212,17 +211,16 @@ class CppTypeGenerator(object):
|
||||
c = Code()
|
||||
for namespace, deps in self._NamespaceTypeDependencies().items():
|
||||
filtered_deps = [
|
||||
dep for dep in deps
|
||||
# Add more ways to forward declare things as necessary.
|
||||
if (not dep.hard and
|
||||
dep.type_.property_type in (PropertyType.CHOICES,
|
||||
PropertyType.OBJECT))]
|
||||
dep for dep in deps
|
||||
# Add more ways to forward declare things as necessary.
|
||||
if (not dep.hard and dep.type_.property_type in (PropertyType.CHOICES,
|
||||
PropertyType.OBJECT))
|
||||
]
|
||||
if not filtered_deps:
|
||||
continue
|
||||
|
||||
cpp_namespace = cpp_util.GetCppNamespace(
|
||||
namespace.environment.namespace_pattern,
|
||||
namespace.unix_name)
|
||||
namespace.environment.namespace_pattern, namespace.unix_name)
|
||||
c.Concat(cpp_util.OpenNamespace(cpp_namespace))
|
||||
for dep in filtered_deps:
|
||||
c.Append('struct %s;' % dep.type_.name)
|
||||
@ -236,9 +234,9 @@ class CppTypeGenerator(object):
|
||||
|
||||
# The inclusion of the std::string_view header is dependent on either the
|
||||
# presence of enums, or manifest keys.
|
||||
include_string_view = (self._default_namespace.manifest_keys or
|
||||
any(type_.property_type is PropertyType.ENUM for type_ in
|
||||
self._default_namespace.types.values()))
|
||||
include_string_view = (self._default_namespace.manifest_keys or any(
|
||||
type_.property_type is PropertyType.ENUM
|
||||
for type_ in self._default_namespace.types.values()))
|
||||
|
||||
if include_string_view:
|
||||
c.Append('#include <string_view>')
|
||||
@ -246,11 +244,10 @@ class CppTypeGenerator(object):
|
||||
# The header for `base::expected` should be included whenever error messages
|
||||
# are supposed to be returned, which only occurs with object, choices, or
|
||||
# functions.
|
||||
if (generate_error_messages and (
|
||||
len(self._default_namespace.functions.values()) or
|
||||
any(type_.property_type in
|
||||
[PropertyType.OBJECT, PropertyType.CHOICES] for type_ in
|
||||
self._default_namespace.types.values()))):
|
||||
if (generate_error_messages
|
||||
and (len(self._default_namespace.functions.values()) or any(
|
||||
type_.property_type in [PropertyType.OBJECT, PropertyType.CHOICES]
|
||||
for type_ in self._default_namespace.types.values()))):
|
||||
c.Append('#include "base/types/expected.h"')
|
||||
|
||||
# Note: It's possible that there are multiple dependencies from the same
|
||||
@ -361,11 +358,7 @@ class CppTypeGenerator(object):
|
||||
cpp_value = '"%s"' % cpp_value
|
||||
cpp_type = 'char'
|
||||
cpp_name = '%s[]' % cpp_name
|
||||
c.Append(line % {
|
||||
"type": cpp_type,
|
||||
"name": cpp_name,
|
||||
"value": cpp_value
|
||||
})
|
||||
c.Append(line % {"type": cpp_type, "name": cpp_name, "value": cpp_value})
|
||||
else:
|
||||
has_child_code = False
|
||||
c.Sblock('namespace %s {' % prop.name)
|
||||
|
@ -12,7 +12,9 @@ import unittest
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
class _FakeSchemaLoader(object):
|
||||
|
||||
def __init__(self, model):
|
||||
self._model = model
|
||||
|
||||
@ -22,13 +24,15 @@ class _FakeSchemaLoader(object):
|
||||
return default if type_name in default.types else None
|
||||
return self._model.namespaces[parts[0]]
|
||||
|
||||
|
||||
class CppTypeGeneratorTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.models = defaultdict(model.Model)
|
||||
|
||||
forbidden_json = CachedLoad('test/forbidden.json')
|
||||
self.models['forbidden'].AddNamespace(
|
||||
forbidden_json[0], 'path/to/forbidden.json')
|
||||
self.models['forbidden'].AddNamespace(forbidden_json[0],
|
||||
'path/to/forbidden.json')
|
||||
|
||||
permissions_json = CachedLoad('test/permissions.json')
|
||||
self.permissions = self.models['permissions'].AddNamespace(
|
||||
@ -59,12 +63,13 @@ class CppTypeGeneratorTest(unittest.TestCase):
|
||||
|
||||
objects_movable_idl = idl_schema.Load('test/objects_movable.idl')
|
||||
self.objects_movable = self.models['objects_movable'].AddNamespace(
|
||||
objects_movable_idl[0], 'path/to/objects_movable.idl',
|
||||
objects_movable_idl[0],
|
||||
'path/to/objects_movable.idl',
|
||||
include_compiler_options=True)
|
||||
|
||||
self.simple_api_json = CachedLoad('test/simple_api.json')
|
||||
self.models['simple_api'].AddNamespace(
|
||||
self.simple_api_json[0], 'path/to/simple_api.json')
|
||||
self.models['simple_api'].AddNamespace(self.simple_api_json[0],
|
||||
'path/to/simple_api.json')
|
||||
|
||||
self.crossref_enums_json = CachedLoad('test/crossref_enums.json')
|
||||
self.crossref_enums = self.models['crossref_enums'].AddNamespace(
|
||||
@ -73,8 +78,7 @@ class CppTypeGeneratorTest(unittest.TestCase):
|
||||
self.crossref_enums_array_json = CachedLoad(
|
||||
'test/crossref_enums_array.json')
|
||||
self.models['crossref_enums_array'].AddNamespace(
|
||||
self.crossref_enums_array_json[0],
|
||||
'path/to/crossref_enums_array.json')
|
||||
self.crossref_enums_array_json[0], 'path/to/crossref_enums_array.json')
|
||||
|
||||
def testGenerateIncludesAndForwardDeclarations(self):
|
||||
m = model.Model()
|
||||
@ -88,7 +92,7 @@ class CppTypeGeneratorTest(unittest.TestCase):
|
||||
|
||||
self.assertEqual('', manager.GenerateIncludes().Render())
|
||||
self.assertEqual('#include "path/to/tabs.h"',
|
||||
manager.GenerateIncludes(include_soft=True).Render())
|
||||
manager.GenerateIncludes(include_soft=True).Render())
|
||||
self.assertEqual(
|
||||
'namespace tabs {\n'
|
||||
'struct Tab;\n'
|
||||
@ -96,14 +100,14 @@ class CppTypeGeneratorTest(unittest.TestCase):
|
||||
manager.GenerateForwardDeclarations().Render())
|
||||
|
||||
m = model.Model()
|
||||
m.AddNamespace(self.windows_json[0],
|
||||
'path/to/windows.json',
|
||||
environment=CppNamespaceEnvironment(
|
||||
'foo::bar::%(namespace)s'))
|
||||
m.AddNamespace(self.tabs_json[0],
|
||||
'path/to/tabs.json',
|
||||
environment=CppNamespaceEnvironment(
|
||||
'foo::bar::%(namespace)s'))
|
||||
m.AddNamespace(
|
||||
self.windows_json[0],
|
||||
'path/to/windows.json',
|
||||
environment=CppNamespaceEnvironment('foo::bar::%(namespace)s'))
|
||||
m.AddNamespace(
|
||||
self.tabs_json[0],
|
||||
'path/to/tabs.json',
|
||||
environment=CppNamespaceEnvironment('foo::bar::%(namespace)s'))
|
||||
manager = CppTypeGenerator(m, _FakeSchemaLoader(m))
|
||||
self.assertEqual(
|
||||
'namespace foo {\n'
|
||||
@ -134,9 +138,10 @@ class CppTypeGeneratorTest(unittest.TestCase):
|
||||
manager = CppTypeGenerator(m,
|
||||
_FakeSchemaLoader(m),
|
||||
default_namespace=dependency_tester)
|
||||
self.assertEqual('#include "path/to/browser_action.h"\n'
|
||||
'#include "path/to/font_settings.h"',
|
||||
manager.GenerateIncludes().Render())
|
||||
self.assertEqual(
|
||||
'#include "path/to/browser_action.h"\n'
|
||||
'#include "path/to/font_settings.h"',
|
||||
manager.GenerateIncludes().Render())
|
||||
self.assertEqual('', manager.GenerateForwardDeclarations().Render())
|
||||
|
||||
def testGetCppTypeSimple(self):
|
||||
@ -167,7 +172,7 @@ class CppTypeGeneratorTest(unittest.TestCase):
|
||||
|
||||
def testGetCppTypeArray(self):
|
||||
manager = CppTypeGenerator(self.models.get('windows'),
|
||||
_FakeSchemaLoader(None))
|
||||
_FakeSchemaLoader(None))
|
||||
self.assertEqual(
|
||||
'std::vector<Window>',
|
||||
manager.GetCppType(
|
||||
@ -183,9 +188,8 @@ class CppTypeGeneratorTest(unittest.TestCase):
|
||||
_FakeSchemaLoader(None))
|
||||
self.assertEqual(
|
||||
'std::vector<MovablePod>',
|
||||
manager.GetCppType(
|
||||
self.objects_movable.types['MovableParent'].
|
||||
properties['pods'].type_))
|
||||
manager.GetCppType(self.objects_movable.types['MovableParent'].
|
||||
properties['pods'].type_))
|
||||
|
||||
def testGetCppTypeLocalRef(self):
|
||||
manager = CppTypeGenerator(self.models.get('tabs'), _FakeSchemaLoader(None))
|
||||
@ -211,7 +215,8 @@ class CppTypeGeneratorTest(unittest.TestCase):
|
||||
def testGetCppTypeWithPadForGeneric(self):
|
||||
manager = CppTypeGenerator(self.models.get('permissions'),
|
||||
_FakeSchemaLoader(None))
|
||||
self.assertEqual('std::vector<std::string>',
|
||||
self.assertEqual(
|
||||
'std::vector<std::string>',
|
||||
manager.GetCppType(
|
||||
self.permissions.types['Permissions'].properties['origins'].type_))
|
||||
self.assertEqual(
|
||||
@ -235,7 +240,7 @@ class CppTypeGeneratorTest(unittest.TestCase):
|
||||
_FakeSchemaLoader(m))
|
||||
|
||||
self.assertEqual('#include "path/to/simple_api.h"',
|
||||
manager.GenerateIncludes().Render())
|
||||
manager.GenerateIncludes().Render())
|
||||
|
||||
def testHardIncludesForEnumArrays(self):
|
||||
"""Tests that enums in arrays generate hard includes. Note that it's
|
||||
@ -253,7 +258,7 @@ class CppTypeGeneratorTest(unittest.TestCase):
|
||||
_FakeSchemaLoader(m))
|
||||
|
||||
self.assertEqual('#include "path/to/simple_api.h"',
|
||||
manager.GenerateIncludes().Render())
|
||||
manager.GenerateIncludes().Render())
|
||||
|
||||
def testCrossNamespaceGetEnumDefaultValue(self):
|
||||
m = model.Model()
|
||||
@ -272,9 +277,9 @@ class CppTypeGeneratorTest(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
'namespace1::api::simple_api::TestEnum()',
|
||||
manager.GetEnumDefaultValue(
|
||||
self.crossref_enums.types['CrossrefType']
|
||||
.properties['testEnumOptional'].type_,
|
||||
self.crossref_enums))
|
||||
self.crossref_enums.types['CrossrefType'].
|
||||
properties['testEnumOptional'].type_, self.crossref_enums))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -1,7 +1,6 @@
|
||||
# Copyright 2012 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
"""Utilies and constants specific to Chromium C++ code.
|
||||
"""
|
||||
|
||||
@ -12,11 +11,9 @@ import os
|
||||
import posixpath
|
||||
import re
|
||||
|
||||
CHROMIUM_LICENSE = (
|
||||
"""// Copyright %d The Chromium Authors
|
||||
CHROMIUM_LICENSE = ("""// Copyright %d The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.""" % datetime.now().year
|
||||
)
|
||||
// found in the LICENSE file.""" % datetime.now().year)
|
||||
GENERATED_FILE_MESSAGE = """// GENERATED FROM THE API DEFINITION IN
|
||||
// %s
|
||||
// by tools/json_schema_compiler.
|
||||
@ -33,6 +30,7 @@ GENERATED_FEATURE_MESSAGE = """// GENERATED FROM THE FEATURE DEFINITIONS IN
|
||||
// DO NOT EDIT.
|
||||
"""
|
||||
|
||||
|
||||
def Classname(s):
|
||||
"""Translates a namespace name or function name into something more
|
||||
suited to C++.
|
||||
@ -56,6 +54,7 @@ def Classname(s):
|
||||
result = '_' + result
|
||||
return result
|
||||
|
||||
|
||||
def GetAsFundamentalValue(type_, src):
|
||||
"""Returns the C++ code for retrieving a fundamental type from a
|
||||
Value into a variable.
|
||||
@ -68,9 +67,9 @@ def GetAsFundamentalValue(type_, src):
|
||||
s = '%s.GetIfDouble()'
|
||||
elif type_.property_type == PropertyType.INTEGER:
|
||||
s = '%s.GetIfInt()'
|
||||
elif (type_.property_type == PropertyType.STRING or
|
||||
(type_.property_type == PropertyType.FUNCTION and
|
||||
type_.is_serializable_function)):
|
||||
elif (type_.property_type == PropertyType.STRING
|
||||
or (type_.property_type == PropertyType.FUNCTION
|
||||
and type_.is_serializable_function)):
|
||||
s = '%s.GetIfString()'
|
||||
else:
|
||||
raise ValueError('Type %s is not a fundamental value' % type_.name)
|
||||
@ -104,43 +103,49 @@ def GetValueType(type_):
|
||||
|
||||
raise ValueError('Invalid type: %s' % type_.name)
|
||||
|
||||
|
||||
def ShouldUseStdOptional(type_):
|
||||
"""Called to validate whether or not an optional value should be represented
|
||||
with std::optional. This function is a temporary utility, while optional
|
||||
fields are gradually migrated away from using std::unique_ptr.
|
||||
"""
|
||||
|
||||
if type_.property_type in (PropertyType.ANY,
|
||||
PropertyType.ARRAY,
|
||||
PropertyType.BINARY,
|
||||
PropertyType.BOOLEAN,
|
||||
PropertyType.CHOICES,
|
||||
PropertyType.DOUBLE,
|
||||
PropertyType.FUNCTION,
|
||||
PropertyType.INTEGER,
|
||||
PropertyType.OBJECT,
|
||||
PropertyType.STRING):
|
||||
if type_.property_type in (
|
||||
PropertyType.ANY,
|
||||
PropertyType.ARRAY,
|
||||
PropertyType.BINARY,
|
||||
PropertyType.BOOLEAN,
|
||||
PropertyType.CHOICES,
|
||||
PropertyType.DOUBLE,
|
||||
PropertyType.FUNCTION,
|
||||
PropertyType.INTEGER,
|
||||
PropertyType.OBJECT,
|
||||
PropertyType.STRING,
|
||||
):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def GetParameterDeclaration(param, type_):
|
||||
"""Gets a parameter declaration of a given model.Property and its C++
|
||||
type.
|
||||
"""
|
||||
if param.type_.property_type in (PropertyType.ANY,
|
||||
PropertyType.ARRAY,
|
||||
PropertyType.BINARY,
|
||||
PropertyType.CHOICES,
|
||||
PropertyType.OBJECT,
|
||||
PropertyType.REF,
|
||||
PropertyType.STRING):
|
||||
if param.type_.property_type in (
|
||||
PropertyType.ANY,
|
||||
PropertyType.ARRAY,
|
||||
PropertyType.BINARY,
|
||||
PropertyType.CHOICES,
|
||||
PropertyType.OBJECT,
|
||||
PropertyType.REF,
|
||||
PropertyType.STRING,
|
||||
):
|
||||
arg = 'const %(type)s& %(name)s'
|
||||
else:
|
||||
arg = '%(type)s %(name)s'
|
||||
return arg % {
|
||||
'type': type_,
|
||||
'name': param.unix_name,
|
||||
'type': type_,
|
||||
'name': param.unix_name,
|
||||
}
|
||||
|
||||
|
||||
@ -150,10 +155,10 @@ def GenerateIfndefName(file_path):
|
||||
|
||||
e.g chrome/extensions/gen/file.h becomes CHROME_EXTENSIONS_GEN_FILE_H__.
|
||||
"""
|
||||
return (('%s__' % file_path).upper()
|
||||
.replace('\\', '_')
|
||||
.replace('/', '_')
|
||||
.replace('-', '_')
|
||||
return (('%s__' % file_path).upper() \
|
||||
.replace('\\', '_') \
|
||||
.replace('/', '_') \
|
||||
.replace('-', '_') \
|
||||
.replace('.', '_'))
|
||||
|
||||
|
||||
@ -180,7 +185,7 @@ def FeatureNameToConstantName(feature_name):
|
||||
"""Returns a kName for a feature's name.
|
||||
"""
|
||||
return ('k' + ''.join(word[0].upper() + word[1:]
|
||||
for word in feature_name.replace('.', ' ').split()))
|
||||
for word in feature_name.replace('.', ' ').split()))
|
||||
|
||||
|
||||
def UnixNameToConstantName(unix_name):
|
||||
@ -189,6 +194,7 @@ def UnixNameToConstantName(unix_name):
|
||||
"""
|
||||
return ('k' + ''.join(word.capitalize() for word in unix_name.split('_')))
|
||||
|
||||
|
||||
def IsUnixName(s):
|
||||
# type (str) -> bool
|
||||
"""Returns true if |s| is of the type unix_name i.e. only has lower cased
|
||||
@ -196,6 +202,7 @@ def IsUnixName(s):
|
||||
"""
|
||||
return all(x.islower() or x == '_' for x in s) and '_' in s
|
||||
|
||||
|
||||
def ToPosixPath(path):
|
||||
"""Returns |path| with separator converted to POSIX style.
|
||||
|
||||
@ -220,7 +227,7 @@ def GetCppNamespace(pattern, namespace):
|
||||
# For some reason Windows builds escape the % characters, so unescape them.
|
||||
# This means that %% can never appear legitimately within a pattern, but
|
||||
# that's ok. It should never happen.
|
||||
cpp_namespace = pattern.replace('%%', '%') % { 'namespace': namespace }
|
||||
cpp_namespace = pattern.replace('%%', '%') % {'namespace': namespace}
|
||||
assert '%' not in cpp_namespace, \
|
||||
('Did not manage to fully substitute namespace "%s" into pattern "%s"'
|
||||
% (namespace, pattern))
|
||||
|
@ -5,35 +5,27 @@
|
||||
|
||||
import unittest
|
||||
|
||||
from cpp_util import (
|
||||
Classname,
|
||||
CloseNamespace,
|
||||
GetCppNamespace,
|
||||
GenerateIfndefName,
|
||||
OpenNamespace
|
||||
)
|
||||
from cpp_util import (Classname, CloseNamespace, GetCppNamespace,
|
||||
GenerateIfndefName, OpenNamespace)
|
||||
|
||||
|
||||
class CppUtilTest(unittest.TestCase):
|
||||
|
||||
def testClassname(self):
|
||||
self.assertEqual('Permissions', Classname('permissions'))
|
||||
self.assertEqual('UpdateAllTheThings',
|
||||
Classname('updateAllTheThings'))
|
||||
self.assertEqual('UpdateAllTheThings', Classname('updateAllTheThings'))
|
||||
self.assertEqual('Aa_Bb_Cc', Classname('aa.bb.cc'))
|
||||
|
||||
def testNamespaceDeclaration(self):
|
||||
self.assertEqual('namespace foo {',
|
||||
OpenNamespace('foo').Render())
|
||||
self.assertEqual('} // namespace foo',
|
||||
CloseNamespace('foo').Render())
|
||||
self.assertEqual('namespace foo {', OpenNamespace('foo').Render())
|
||||
self.assertEqual('} // namespace foo', CloseNamespace('foo').Render())
|
||||
|
||||
self.assertEqual(
|
||||
'namespace extensions {\n'
|
||||
'namespace foo {',
|
||||
OpenNamespace('extensions::foo').Render())
|
||||
self.assertEqual(
|
||||
'} // namespace foo\n'
|
||||
'} // namespace extensions',
|
||||
CloseNamespace('extensions::foo').Render())
|
||||
self.assertEqual('namespace extensions {\n'
|
||||
'namespace foo {',
|
||||
OpenNamespace('extensions::foo').Render())
|
||||
self.assertEqual('} // namespace foo\n'
|
||||
'} // namespace extensions',
|
||||
CloseNamespace('extensions::foo').Render())
|
||||
|
||||
self.assertEqual(
|
||||
'namespace extensions {\n'
|
||||
|
@ -72,6 +72,7 @@ CC_FILE_END = """
|
||||
} // namespace extensions
|
||||
"""
|
||||
|
||||
|
||||
def ToPosixPath(path):
|
||||
"""Returns |path| with separator converted to POSIX style.
|
||||
|
||||
@ -79,11 +80,13 @@ def ToPosixPath(path):
|
||||
"""
|
||||
return path.replace(os.path.sep, posixpath.sep)
|
||||
|
||||
|
||||
# Returns true if the list 'l' only contains strings that are a hex-encoded SHA1
|
||||
# hashes.
|
||||
def ListContainsOnlySha1Hashes(l):
|
||||
return len(list(filter(lambda s: not re.match("^[A-F0-9]{40}$", s), l))) == 0
|
||||
|
||||
|
||||
# A "grammar" for what is and isn't allowed in the features.json files. This
|
||||
# grammar has to list all possible keys and the requirements for each. The
|
||||
# format of each entry is:
|
||||
@ -170,19 +173,25 @@ FEATURE_GRAMMAR = ({
|
||||
list: {
|
||||
'enum_map': {
|
||||
'privileged_extension':
|
||||
'mojom::ContextType::kPrivilegedExtension',
|
||||
'privileged_web_page': 'mojom::ContextType::kPrivilegedWebPage',
|
||||
'content_script': 'mojom::ContextType::kContentScript',
|
||||
'mojom::ContextType::kPrivilegedExtension',
|
||||
'privileged_web_page':
|
||||
'mojom::ContextType::kPrivilegedWebPage',
|
||||
'content_script':
|
||||
'mojom::ContextType::kContentScript',
|
||||
'lock_screen_extension':
|
||||
'mojom::ContextType::kLockscreenExtension',
|
||||
'mojom::ContextType::kLockscreenExtension',
|
||||
'offscreen_extension':
|
||||
'mojom::ContextType::kOffscreenExtension',
|
||||
'user_script': 'mojom::ContextType::kUserScript',
|
||||
'web_page': 'mojom::ContextType::kWebPage',
|
||||
'webui': 'mojom::ContextType::kWebUi',
|
||||
'webui_untrusted': 'mojom::ContextType::kUntrustedWebUi',
|
||||
'mojom::ContextType::kOffscreenExtension',
|
||||
'user_script':
|
||||
'mojom::ContextType::kUserScript',
|
||||
'web_page':
|
||||
'mojom::ContextType::kWebPage',
|
||||
'webui':
|
||||
'mojom::ContextType::kWebUi',
|
||||
'webui_untrusted':
|
||||
'mojom::ContextType::kUntrustedWebUi',
|
||||
'unprivileged_extension':
|
||||
'mojom::ContextType::kUnprivilegedExtension',
|
||||
'mojom::ContextType::kUnprivilegedExtension',
|
||||
},
|
||||
'allow_all': True,
|
||||
'allow_empty': True
|
||||
@ -210,12 +219,18 @@ FEATURE_GRAMMAR = ({
|
||||
'extension_types': {
|
||||
list: {
|
||||
'enum_map': {
|
||||
'extension': 'Manifest::TYPE_EXTENSION',
|
||||
'hosted_app': 'Manifest::TYPE_HOSTED_APP',
|
||||
'legacy_packaged_app': 'Manifest::TYPE_LEGACY_PACKAGED_APP',
|
||||
'platform_app': 'Manifest::TYPE_PLATFORM_APP',
|
||||
'shared_module': 'Manifest::TYPE_SHARED_MODULE',
|
||||
'theme': 'Manifest::TYPE_THEME',
|
||||
'extension':
|
||||
'Manifest::TYPE_EXTENSION',
|
||||
'hosted_app':
|
||||
'Manifest::TYPE_HOSTED_APP',
|
||||
'legacy_packaged_app':
|
||||
'Manifest::TYPE_LEGACY_PACKAGED_APP',
|
||||
'platform_app':
|
||||
'Manifest::TYPE_PLATFORM_APP',
|
||||
'shared_module':
|
||||
'Manifest::TYPE_SHARED_MODULE',
|
||||
'theme':
|
||||
'Manifest::TYPE_THEME',
|
||||
'login_screen_extension':
|
||||
'Manifest::TYPE_LOGIN_SCREEN_EXTENSION',
|
||||
'chromeos_system_extension':
|
||||
@ -288,10 +303,12 @@ FEATURE_GRAMMAR = ({
|
||||
'session_types': {
|
||||
list: {
|
||||
'enum_map': {
|
||||
'regular': 'mojom::FeatureSessionType::kRegular',
|
||||
'kiosk': 'mojom::FeatureSessionType::kKiosk',
|
||||
'regular':
|
||||
'mojom::FeatureSessionType::kRegular',
|
||||
'kiosk':
|
||||
'mojom::FeatureSessionType::kKiosk',
|
||||
'kiosk.autolaunched':
|
||||
'mojom::FeatureSessionType::kAutolaunchedKiosk',
|
||||
'mojom::FeatureSessionType::kAutolaunchedKiosk',
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -301,21 +318,27 @@ FEATURE_GRAMMAR = ({
|
||||
},
|
||||
})
|
||||
|
||||
FEATURE_TYPES = ['APIFeature', 'BehaviorFeature',
|
||||
'ManifestFeature', 'PermissionFeature']
|
||||
FEATURE_TYPES = [
|
||||
'APIFeature', 'BehaviorFeature', 'ManifestFeature', 'PermissionFeature'
|
||||
]
|
||||
|
||||
|
||||
def HasProperty(property_name, value):
|
||||
return property_name in value
|
||||
|
||||
|
||||
def HasAtLeastOneProperty(property_names, value):
|
||||
return any([HasProperty(name, value) for name in property_names])
|
||||
|
||||
|
||||
def DoesNotHaveAllProperties(property_names, value):
|
||||
return not all([HasProperty(name, value) for name in property_names])
|
||||
|
||||
|
||||
def DoesNotHaveProperty(property_name, value):
|
||||
return property_name not in value
|
||||
|
||||
|
||||
def DoesNotHavePropertyInComplexFeature(property_name, feature, all_features):
|
||||
if type(feature) is ComplexFeature:
|
||||
for child_feature in feature.feature_list:
|
||||
@ -323,6 +346,7 @@ def DoesNotHavePropertyInComplexFeature(property_name, feature, all_features):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def IsEmptyContextsAllowed(feature, all_features):
|
||||
# An alias feature wouldn't have the 'contexts' feature value.
|
||||
if feature.GetValue('source'):
|
||||
@ -338,12 +362,13 @@ def IsEmptyContextsAllowed(feature, all_features):
|
||||
assert contexts, 'contexts must have been specified for the APIFeature'
|
||||
|
||||
allowlisted_empty_context_namespaces = [
|
||||
'manifestTypes',
|
||||
'extensionsManifestTypes',
|
||||
'empty_contexts' # Only added for testing.
|
||||
'manifestTypes',
|
||||
'extensionsManifestTypes',
|
||||
'empty_contexts' # Only added for testing.
|
||||
]
|
||||
return (contexts != '{}' or
|
||||
feature.name in allowlisted_empty_context_namespaces)
|
||||
return (contexts != '{}'
|
||||
or feature.name in allowlisted_empty_context_namespaces)
|
||||
|
||||
|
||||
def IsFeatureCrossReference(property_name, reverse_property_name, feature,
|
||||
all_features):
|
||||
@ -374,6 +399,7 @@ def IsFeatureCrossReference(property_name, reverse_property_name, feature,
|
||||
return True
|
||||
return reverse_reference_value == ('"%s"' % feature.name)
|
||||
|
||||
|
||||
# Verifies that a feature with an allowlist is not available to hosted apps,
|
||||
# returning true on success.
|
||||
def DoesNotHaveAllowlistForHostedApps(value):
|
||||
@ -428,92 +454,90 @@ def DoesNotHaveAllowlistForHostedApps(value):
|
||||
|
||||
|
||||
SIMPLE_FEATURE_CPP_CLASSES = ({
|
||||
'APIFeature': 'SimpleFeature',
|
||||
'ManifestFeature': 'ManifestFeature',
|
||||
'PermissionFeature': 'PermissionFeature',
|
||||
'BehaviorFeature': 'SimpleFeature',
|
||||
'APIFeature': 'SimpleFeature',
|
||||
'ManifestFeature': 'ManifestFeature',
|
||||
'PermissionFeature': 'PermissionFeature',
|
||||
'BehaviorFeature': 'SimpleFeature',
|
||||
})
|
||||
|
||||
VALIDATION = ({
|
||||
'all': [
|
||||
(partial(HasAtLeastOneProperty, ['channel', 'dependencies']),
|
||||
'Features must specify either a channel or dependencies'),
|
||||
(DoesNotHaveAllowlistForHostedApps,
|
||||
'Hosted apps are not allowed to use restricted features'),
|
||||
],
|
||||
'APIFeature': [
|
||||
(partial(HasProperty, 'contexts'),
|
||||
'APIFeatures must specify the contexts property'),
|
||||
(partial(DoesNotHaveAllProperties, ['alias', 'source']),
|
||||
'Features cannot specify both alias and source.')
|
||||
],
|
||||
'ManifestFeature': [
|
||||
(partial(HasProperty, 'extension_types'),
|
||||
'ManifestFeatures must specify at least one extension type'),
|
||||
(partial(DoesNotHaveProperty, 'contexts'),
|
||||
'ManifestFeatures do not support contexts.'),
|
||||
(partial(DoesNotHaveProperty, 'alias'),
|
||||
'ManifestFeatures do not support alias.'),
|
||||
(partial(DoesNotHaveProperty, 'source'),
|
||||
'ManifestFeatures do not support source.'),
|
||||
# The `required_buildflags` field is intended to be used to toggle the
|
||||
# availability of certain APIs; if we support this for feature types other
|
||||
# than APIFeature, we may emit warnings that are visible to developers which
|
||||
# is not desirable.
|
||||
(partial(DoesNotHaveProperty, 'required_buildflags'),
|
||||
'ManifestFeatures do not support required_buildflags.'),
|
||||
],
|
||||
'BehaviorFeature': [
|
||||
(partial(DoesNotHaveProperty, 'alias'),
|
||||
'BehaviorFeatures do not support alias.'),
|
||||
(partial(DoesNotHaveProperty, 'source'),
|
||||
'BehaviorFeatures do not support source.'),
|
||||
(partial(DoesNotHaveProperty, 'required_buildflags'),
|
||||
# The `required_buildflags` field is intended to be used to toggle the
|
||||
# availability of certain APIs; if we support this for feature types other
|
||||
# than APIFeature, we may emit warnings that are visible to developers which
|
||||
# is not desirable.
|
||||
'BehaviorFeatures do not support required_buildflags.'),
|
||||
],
|
||||
'PermissionFeature': [
|
||||
(partial(HasProperty, 'extension_types'),
|
||||
'PermissionFeatures must specify at least one extension type'),
|
||||
(partial(DoesNotHaveProperty, 'contexts'),
|
||||
'PermissionFeatures do not support contexts.'),
|
||||
(partial(DoesNotHaveProperty, 'alias'),
|
||||
'PermissionFeatures do not support alias.'),
|
||||
(partial(DoesNotHaveProperty, 'source'),
|
||||
'PermissionFeatures do not support source.'),
|
||||
(partial(DoesNotHaveProperty, 'required_buildflags'),
|
||||
# The `required_buildflags` field is intended to be used to toggle the
|
||||
# availability of certain APIs; if we support this for feature types other
|
||||
# than APIFeature, we may emit warnings that are visible to developers which
|
||||
# is not desirable.
|
||||
'PermissionFeatures do not support required_buildflags.'),
|
||||
],
|
||||
'all': [
|
||||
(partial(HasAtLeastOneProperty, ['channel', 'dependencies']),
|
||||
'Features must specify either a channel or dependencies'),
|
||||
(DoesNotHaveAllowlistForHostedApps,
|
||||
'Hosted apps are not allowed to use restricted features'),
|
||||
],
|
||||
'APIFeature':
|
||||
[(partial(HasProperty,
|
||||
'contexts'), 'APIFeatures must specify the contexts property'),
|
||||
(partial(DoesNotHaveAllProperties, ['alias', 'source']),
|
||||
'Features cannot specify both alias and source.')],
|
||||
'ManifestFeature': [
|
||||
(partial(HasProperty, 'extension_types'),
|
||||
'ManifestFeatures must specify at least one extension type'),
|
||||
(partial(DoesNotHaveProperty,
|
||||
'contexts'), 'ManifestFeatures do not support contexts.'),
|
||||
(partial(DoesNotHaveProperty,
|
||||
'alias'), 'ManifestFeatures do not support alias.'),
|
||||
(partial(DoesNotHaveProperty,
|
||||
'source'), 'ManifestFeatures do not support source.'),
|
||||
# The `required_buildflags` field is intended to be used to toggle the
|
||||
# availability of certain APIs; if we support this for feature types
|
||||
# other than APIFeature, we may emit warnings that are visible to
|
||||
# developers which is not desirable.
|
||||
(partial(DoesNotHaveProperty, 'required_buildflags'),
|
||||
'ManifestFeatures do not support required_buildflags.'),
|
||||
],
|
||||
'BehaviorFeature': [
|
||||
(partial(DoesNotHaveProperty,
|
||||
'alias'), 'BehaviorFeatures do not support alias.'),
|
||||
(partial(DoesNotHaveProperty,
|
||||
'source'), 'BehaviorFeatures do not support source.'),
|
||||
# The `required_buildflags` field is intended to be used to toggle the
|
||||
# availability of certain APIs; if we support this for feature types
|
||||
# other than APIFeature, we may emit warnings that are visible to
|
||||
# developers which is not desirable.
|
||||
(partial(DoesNotHaveProperty, 'required_buildflags'),
|
||||
'BehaviorFeatures do not support required_buildflags.'),
|
||||
],
|
||||
'PermissionFeature': [
|
||||
(partial(HasProperty, 'extension_types'),
|
||||
'PermissionFeatures must specify at least one extension type'),
|
||||
(partial(DoesNotHaveProperty,
|
||||
'contexts'), 'PermissionFeatures do not support contexts.'),
|
||||
(partial(DoesNotHaveProperty,
|
||||
'alias'), 'PermissionFeatures do not support alias.'),
|
||||
(partial(DoesNotHaveProperty,
|
||||
'source'), 'PermissionFeatures do not support source.'),
|
||||
# The `required_buildflags` field is intended to be used to toggle the
|
||||
# availability of certain APIs; if we support this for feature types
|
||||
# other than APIFeature, we may emit warnings that are visible to
|
||||
# developers which is not desirable.
|
||||
(partial(DoesNotHaveProperty, 'required_buildflags'),
|
||||
'PermissionFeatures do not support required_buildflags.'),
|
||||
],
|
||||
})
|
||||
|
||||
FINAL_VALIDATION = ({
|
||||
'all': [
|
||||
# A complex feature requires at least one child entry at all times; with
|
||||
# `required_buildflags` it becomes harder to guarantee that this holds for
|
||||
# every potential combination of the provided flags.
|
||||
(partial(DoesNotHavePropertyInComplexFeature, 'required_buildflags'),
|
||||
'required_buildflags cannot be nested in a ComplexFeature'),
|
||||
],
|
||||
'APIFeature': [
|
||||
(partial(IsFeatureCrossReference, 'alias', 'source'),
|
||||
'A feature alias property should reference a feature whose source '
|
||||
'property references it back.'),
|
||||
(partial(IsFeatureCrossReference, 'source', 'alias'),
|
||||
'A feature source property should reference a feature whose alias '
|
||||
'property references it back.'),
|
||||
(IsEmptyContextsAllowed,
|
||||
'An empty contexts list is not allowed for this feature.')
|
||||
],
|
||||
'ManifestFeature': [],
|
||||
'BehaviorFeature': [],
|
||||
'PermissionFeature': []
|
||||
'all': [
|
||||
# A complex feature requires at least one child entry at all times; with
|
||||
# `required_buildflags` it becomes harder to guarantee that this holds
|
||||
# for every potential combination of the provided flags.
|
||||
(partial(DoesNotHavePropertyInComplexFeature, 'required_buildflags'),
|
||||
'required_buildflags cannot be nested in a ComplexFeature'),
|
||||
],
|
||||
'APIFeature':
|
||||
[(partial(IsFeatureCrossReference, 'alias', 'source'),
|
||||
'A feature alias property should reference a feature whose source '
|
||||
'property references it back.'),
|
||||
(partial(IsFeatureCrossReference, 'source', 'alias'),
|
||||
'A feature source property should reference a feature whose alias '
|
||||
'property references it back.'),
|
||||
(IsEmptyContextsAllowed,
|
||||
'An empty contexts list is not allowed for this feature.')],
|
||||
'ManifestFeature': [],
|
||||
'BehaviorFeature': [],
|
||||
'PermissionFeature': []
|
||||
})
|
||||
|
||||
# These keys can not be set on a feature and are hence ignored.
|
||||
@ -523,6 +547,7 @@ IGNORED_KEYS = ['default_parent', 'required_buildflags']
|
||||
# can be disabled for testing.
|
||||
ENABLE_ASSERTIONS = True
|
||||
|
||||
|
||||
def GetCodeForFeatureValues(feature_values):
|
||||
""" Gets the Code object for setting feature values for this object. """
|
||||
c = Code()
|
||||
@ -533,10 +558,12 @@ def GetCodeForFeatureValues(feature_values):
|
||||
c.Append('feature->set_%s(%s);' % (key, feature_values[key]))
|
||||
return c
|
||||
|
||||
|
||||
class Feature(object):
|
||||
"""A representation of a single simple feature that can handle all parsing,
|
||||
validation, and code generation.
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.has_parent = False
|
||||
@ -557,10 +584,10 @@ class Feature(object):
|
||||
"""Adds an error relating to a particular key in the feature.
|
||||
"""
|
||||
self.AddError('Error parsing feature "%s" at key "%s": %s' %
|
||||
(self.name, key, error))
|
||||
(self.name, key, error))
|
||||
|
||||
def _GetCheckedValue(self, key, expected_type, expected_values,
|
||||
enum_map, value):
|
||||
def _GetCheckedValue(self, key, expected_type, expected_values, enum_map,
|
||||
value):
|
||||
"""Returns a string to be used in the generated C++ code for a given key's
|
||||
python value, or None if the value is invalid. For example, if the python
|
||||
value is True, this returns 'true', for a string foo, this returns "foo",
|
||||
@ -737,13 +764,15 @@ class Feature(object):
|
||||
return values
|
||||
|
||||
def GetErrors(self):
|
||||
return self.errors;
|
||||
return self.errors
|
||||
|
||||
|
||||
class ComplexFeature(Feature):
|
||||
""" Complex feature - feature that is comprised of list of features.
|
||||
Overall complex feature is available if any of contained
|
||||
feature is available.
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
Feature.__init__(self, name)
|
||||
self.feature_list = []
|
||||
@ -779,11 +808,13 @@ class ComplexFeature(Feature):
|
||||
errors.extend(feature.GetErrors())
|
||||
return errors
|
||||
|
||||
|
||||
class FeatureCompiler(object):
|
||||
"""A compiler to load, parse, and generate C++ code for a number of
|
||||
features.json files."""
|
||||
def __init__(self, chrome_root, source_files, feature_type,
|
||||
method_name, out_root, gen_dir_relpath, out_base_filename):
|
||||
|
||||
def __init__(self, chrome_root, source_files, feature_type, method_name,
|
||||
out_root, gen_dir_relpath, out_base_filename):
|
||||
# See __main__'s ArgumentParser for documentation on these properties.
|
||||
self._chrome_root = chrome_root
|
||||
self._source_files = source_files
|
||||
@ -808,7 +839,7 @@ class FeatureCompiler(object):
|
||||
f_json = json_parse.Parse(f.read())
|
||||
except:
|
||||
print('FAILED: Exception encountered while loading "%s"' %
|
||||
abs_source_file)
|
||||
abs_source_file)
|
||||
raise
|
||||
dupes = set(f_json) & set(self._json)
|
||||
assert not dupes, 'Duplicate keys found: %s' % list(dupes)
|
||||
@ -822,8 +853,8 @@ class FeatureCompiler(object):
|
||||
no_parent_values = ['noparent' in v for v in feature_value]
|
||||
no_parent = all(no_parent_values)
|
||||
assert no_parent or not any(no_parent_values), (
|
||||
'"%s:" All child features must contain the same noparent value' %
|
||||
feature_name)
|
||||
'"%s:" All child features must contain the same noparent value' %
|
||||
feature_name)
|
||||
else:
|
||||
no_parent = 'noparent' in feature_value
|
||||
sep = feature_name.rfind('.')
|
||||
@ -889,8 +920,9 @@ class FeatureCompiler(object):
|
||||
parse_and_validate(feature_name, v, parent, shared_values))
|
||||
self._features[feature_name] = feature
|
||||
else:
|
||||
self._features[feature_name] = parse_and_validate(
|
||||
feature_name, feature_value, parent, shared_values)
|
||||
self._features[feature_name] = parse_and_validate(feature_name,
|
||||
feature_value, parent,
|
||||
shared_values)
|
||||
|
||||
# Apply parent shared values at the end to enable child features to
|
||||
# override parent shared value - if parent shared values are added to
|
||||
@ -926,7 +958,8 @@ class FeatureCompiler(object):
|
||||
required_buildflags = feature.GetValue('required_buildflags')
|
||||
if required_buildflags:
|
||||
formatted_buildflags = [
|
||||
'BUILDFLAG(%s)' % format(flag.upper()) for flag in required_buildflags
|
||||
'BUILDFLAG(%s)' % format(flag.upper())
|
||||
for flag in required_buildflags
|
||||
]
|
||||
c.Append('#if %s' % format(' && '.join(formatted_buildflags)))
|
||||
c.Concat(feature.GetCode(self._feature_type))
|
||||
@ -942,16 +975,20 @@ class FeatureCompiler(object):
|
||||
header_file = self._out_base_filename + '.h'
|
||||
cc_file = self._out_base_filename + '.cc'
|
||||
|
||||
include_file_root = self._out_root[len(self._gen_dir_relpath)+1:]
|
||||
include_file_root = self._out_root[len(self._gen_dir_relpath) + 1:]
|
||||
header_file_path = '%s/%s' % (include_file_root, header_file)
|
||||
cc_file_path = '%s/%s' % (include_file_root, cc_file)
|
||||
substitutions = ({
|
||||
'header_file_path': header_file_path,
|
||||
'header_guard': (header_file_path.replace('/', '_').
|
||||
replace('.', '_').upper()),
|
||||
'method_name': self._method_name,
|
||||
'source_files': str([ToPosixPath(f) for f in self._source_files]),
|
||||
'year': str(datetime.now().year)
|
||||
'header_file_path':
|
||||
header_file_path,
|
||||
'header_guard':
|
||||
(header_file_path.replace('/', '_').replace('.', '_').upper()),
|
||||
'method_name':
|
||||
self._method_name,
|
||||
'source_files':
|
||||
str([ToPosixPath(f) for f in self._source_files]),
|
||||
'year':
|
||||
str(datetime.now().year)
|
||||
})
|
||||
if not os.path.exists(self._out_root):
|
||||
os.makedirs(self._out_root)
|
||||
@ -973,25 +1010,36 @@ class FeatureCompiler(object):
|
||||
cc_file.Concat(cc_end)
|
||||
f.write(cc_file.Render().strip())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='Compile json feature files')
|
||||
parser.add_argument('chrome_root', type=str,
|
||||
parser.add_argument('chrome_root',
|
||||
type=str,
|
||||
help='The root directory of the chrome checkout')
|
||||
parser.add_argument(
|
||||
'feature_type', type=str,
|
||||
'feature_type',
|
||||
type=str,
|
||||
help='The name of the class to use in feature generation ' +
|
||||
'(e.g. APIFeature, PermissionFeature)')
|
||||
parser.add_argument('method_name', type=str,
|
||||
'(e.g. APIFeature, PermissionFeature)')
|
||||
parser.add_argument('method_name',
|
||||
type=str,
|
||||
help='The name of the method to populate the provider')
|
||||
parser.add_argument('out_root', type=str,
|
||||
parser.add_argument('out_root',
|
||||
type=str,
|
||||
help='The root directory to generate the C++ files into')
|
||||
parser.add_argument('gen_dir_relpath', default='gen', help='Path of the '
|
||||
parser.add_argument(
|
||||
'gen_dir_relpath',
|
||||
default='gen',
|
||||
help='Path of the '
|
||||
'gen directory relative to the out/. If running in the default '
|
||||
'toolchain, the path is gen, otherwise $toolchain_name/gen')
|
||||
parser.add_argument(
|
||||
'out_base_filename', type=str,
|
||||
'out_base_filename',
|
||||
type=str,
|
||||
help='The base filename for the C++ files (.h and .cc will be appended)')
|
||||
parser.add_argument('source_files', type=str, nargs='+',
|
||||
parser.add_argument('source_files',
|
||||
type=str,
|
||||
nargs='+',
|
||||
help='The source features.json files')
|
||||
args = parser.parse_args()
|
||||
if args.feature_type not in FEATURE_TYPES:
|
||||
|
@ -7,6 +7,7 @@ import copy
|
||||
import feature_compiler
|
||||
import unittest
|
||||
|
||||
|
||||
class FeatureCompilerTest(unittest.TestCase):
|
||||
"""Test the FeatureCompiler. Note that we test that the expected features are
|
||||
generated more thoroughly in features_generation_unittest.cc. And, of course,
|
||||
@ -14,6 +15,7 @@ class FeatureCompilerTest(unittest.TestCase):
|
||||
feature fails to parse, the compile fails).
|
||||
These tests primarily focus on catching errors during parsing.
|
||||
"""
|
||||
|
||||
def _parseFeature(self, value):
|
||||
"""Parses a feature from the given value and returns the result."""
|
||||
f = feature_compiler.Feature('alpha')
|
||||
@ -22,7 +24,8 @@ class FeatureCompilerTest(unittest.TestCase):
|
||||
|
||||
def _createTestFeatureCompiler(self, feature_class):
|
||||
return feature_compiler.FeatureCompiler('chrome_root', [], feature_class,
|
||||
'provider_class', 'out_root', 'gen', 'out_base_filename')
|
||||
'provider_class', 'out_root', 'gen',
|
||||
'out_base_filename')
|
||||
|
||||
def _hasError(self, f, error):
|
||||
"""Asserts that |error| is present somewhere in the given feature's
|
||||
@ -37,60 +40,69 @@ class FeatureCompilerTest(unittest.TestCase):
|
||||
def testFeature(self):
|
||||
# Test some basic feature parsing for a sanity check.
|
||||
f = self._parseFeature({
|
||||
'blocklist': [
|
||||
'ABCDEF0123456789ABCDEF0123456789ABCDEF01',
|
||||
'10FEDCBA9876543210FEDCBA9876543210FEDCBA'
|
||||
],
|
||||
'channel': 'stable',
|
||||
'command_line_switch': 'switch',
|
||||
'component_extensions_auto_granted': False,
|
||||
'contexts': [
|
||||
'privileged_extension',
|
||||
'privileged_web_page',
|
||||
'lock_screen_extension'
|
||||
],
|
||||
'default_parent': True,
|
||||
'dependencies': ['dependency1', 'dependency2'],
|
||||
'developer_mode_only': True,
|
||||
'disallow_for_service_workers': True,
|
||||
'extension_types': ['extension'],
|
||||
'location': 'component',
|
||||
'internal': True,
|
||||
'matches': ['*://*/*'],
|
||||
'max_manifest_version': 1,
|
||||
'requires_delegated_availability_check': True,
|
||||
'noparent': True,
|
||||
'platforms': ['mac', 'win'],
|
||||
'session_types': ['kiosk', 'regular'],
|
||||
'allowlist': [
|
||||
'0123456789ABCDEF0123456789ABCDEF01234567',
|
||||
'76543210FEDCBA9876543210FEDCBA9876543210'
|
||||
],
|
||||
'required_buildflags': [
|
||||
'use_cups'
|
||||
]
|
||||
'blocklist': [
|
||||
'ABCDEF0123456789ABCDEF0123456789ABCDEF01',
|
||||
'10FEDCBA9876543210FEDCBA9876543210FEDCBA'
|
||||
],
|
||||
'channel':
|
||||
'stable',
|
||||
'command_line_switch':
|
||||
'switch',
|
||||
'component_extensions_auto_granted':
|
||||
False,
|
||||
'contexts': [
|
||||
'privileged_extension', 'privileged_web_page',
|
||||
'lock_screen_extension'
|
||||
],
|
||||
'default_parent':
|
||||
True,
|
||||
'dependencies': ['dependency1', 'dependency2'],
|
||||
'developer_mode_only':
|
||||
True,
|
||||
'disallow_for_service_workers':
|
||||
True,
|
||||
'extension_types': ['extension'],
|
||||
'location':
|
||||
'component',
|
||||
'internal':
|
||||
True,
|
||||
'matches': ['*://*/*'],
|
||||
'max_manifest_version':
|
||||
1,
|
||||
'requires_delegated_availability_check':
|
||||
True,
|
||||
'noparent':
|
||||
True,
|
||||
'platforms': ['mac', 'win'],
|
||||
'session_types': ['kiosk', 'regular'],
|
||||
'allowlist': [
|
||||
'0123456789ABCDEF0123456789ABCDEF01234567',
|
||||
'76543210FEDCBA9876543210FEDCBA9876543210'
|
||||
],
|
||||
'required_buildflags': ['use_cups']
|
||||
})
|
||||
self.assertFalse(f.GetErrors())
|
||||
|
||||
def testInvalidAll(self):
|
||||
f = self._parseFeature({
|
||||
'channel': 'stable',
|
||||
'dependencies': 'all',
|
||||
'channel': 'stable',
|
||||
'dependencies': 'all',
|
||||
})
|
||||
self._hasError(f, 'Illegal value: "all"')
|
||||
|
||||
def testUnknownKeyError(self):
|
||||
f = self._parseFeature({
|
||||
'contexts': ['privileged_extension'],
|
||||
'channel': 'stable',
|
||||
'unknownkey': 'unknownvalue'
|
||||
'contexts': ['privileged_extension'],
|
||||
'channel': 'stable',
|
||||
'unknownkey': 'unknownvalue'
|
||||
})
|
||||
self._hasError(f, 'Unrecognized key')
|
||||
|
||||
def testUnknownEnumValue(self):
|
||||
f = self._parseFeature({
|
||||
'contexts': ['privileged_extension', 'unknown_context'],
|
||||
'channel': 'stable'
|
||||
'contexts': ['privileged_extension', 'unknown_context'],
|
||||
'channel':
|
||||
'stable'
|
||||
})
|
||||
self._hasError(f, 'Illegal value: "unknown_context"')
|
||||
|
||||
@ -116,15 +128,19 @@ class FeatureCompilerTest(unittest.TestCase):
|
||||
self.assertFalse(f.GetErrors())
|
||||
|
||||
def testApiFeaturesNeedContexts(self):
|
||||
f = self._parseFeature({'extension_types': ['extension'],
|
||||
'channel': 'trunk'})
|
||||
f = self._parseFeature({
|
||||
'extension_types': ['extension'],
|
||||
'channel': 'trunk'
|
||||
})
|
||||
f.Validate('APIFeature', {})
|
||||
self._hasError(f, 'APIFeatures must specify the contexts property')
|
||||
|
||||
def testAPIFeaturesCanSpecifyEmptyContexts(self):
|
||||
f = self._parseFeature({'extension_types': ['extension'],
|
||||
'channel': 'trunk',
|
||||
'contexts': []})
|
||||
f = self._parseFeature({
|
||||
'extension_types': ['extension'],
|
||||
'channel': 'trunk',
|
||||
'contexts': []
|
||||
})
|
||||
f.Validate('APIFeature', {})
|
||||
self.assertFalse(f.GetErrors())
|
||||
|
||||
@ -135,10 +151,12 @@ class FeatureCompilerTest(unittest.TestCase):
|
||||
'ManifestFeatures must specify at least one extension type')
|
||||
|
||||
def testManifestFeaturesCantHaveContexts(self):
|
||||
f = self._parseFeature({'dependencies': 'alpha',
|
||||
'channel': 'beta',
|
||||
'extension_types': ['extension'],
|
||||
'contexts': ['privileged_extension']})
|
||||
f = self._parseFeature({
|
||||
'dependencies': 'alpha',
|
||||
'channel': 'beta',
|
||||
'extension_types': ['extension'],
|
||||
'contexts': ['privileged_extension']
|
||||
})
|
||||
f.Validate('ManifestFeature', {})
|
||||
self._hasError(f, 'ManifestFeatures do not support contexts')
|
||||
|
||||
@ -149,18 +167,20 @@ class FeatureCompilerTest(unittest.TestCase):
|
||||
f, 'PermissionFeatures must specify at least one extension type')
|
||||
|
||||
def testPermissionFeaturesCantHaveContexts(self):
|
||||
f = self._parseFeature({'dependencies': 'alpha',
|
||||
'channel': 'beta',
|
||||
'extension_types': ['extension'],
|
||||
'contexts': ['privileged_extension']})
|
||||
f = self._parseFeature({
|
||||
'dependencies': 'alpha',
|
||||
'channel': 'beta',
|
||||
'extension_types': ['extension'],
|
||||
'contexts': ['privileged_extension']
|
||||
})
|
||||
f.Validate('PermissionFeature', {})
|
||||
self._hasError(f, 'PermissionFeatures do not support contexts')
|
||||
|
||||
def testAllPermissionsNeedChannelOrDependencies(self):
|
||||
api_feature = self._parseFeature({'contexts': ['privileged_extension']})
|
||||
api_feature.Validate('APIFeature', {})
|
||||
self._hasError(
|
||||
api_feature, 'Features must specify either a channel or dependencies')
|
||||
self._hasError(api_feature,
|
||||
'Features must specify either a channel or dependencies')
|
||||
permission_feature = self._parseFeature({'extension_types': ['extension']})
|
||||
permission_feature.Validate('PermissionFeature', {})
|
||||
self._hasError(permission_feature,
|
||||
@ -169,25 +189,28 @@ class FeatureCompilerTest(unittest.TestCase):
|
||||
manifest_feature.Validate('ManifestFeature', {})
|
||||
self._hasError(manifest_feature,
|
||||
'Features must specify either a channel or dependencies')
|
||||
channel_feature = self._parseFeature({'contexts': ['privileged_extension'],
|
||||
'channel': 'trunk'})
|
||||
channel_feature = self._parseFeature({
|
||||
'contexts': ['privileged_extension'],
|
||||
'channel': 'trunk'
|
||||
})
|
||||
channel_feature.Validate('APIFeature', {})
|
||||
self.assertFalse(channel_feature.GetErrors())
|
||||
dependency_feature = self._parseFeature(
|
||||
{'contexts': ['privileged_extension'],
|
||||
'dependencies': ['alpha']})
|
||||
dependency_feature = self._parseFeature({
|
||||
'contexts': ['privileged_extension'],
|
||||
'dependencies': ['alpha']
|
||||
})
|
||||
dependency_feature.Validate('APIFeature', {})
|
||||
self.assertFalse(dependency_feature.GetErrors())
|
||||
|
||||
def testBothAliasAndSource(self):
|
||||
compiler = self._createTestFeatureCompiler('APIFeature')
|
||||
compiler._json = {
|
||||
'feature_alpha': {
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'alias': 'feature_alpha',
|
||||
'source': 'feature_alpha'
|
||||
}
|
||||
'feature_alpha': {
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'alias': 'feature_alpha',
|
||||
'source': 'feature_alpha'
|
||||
}
|
||||
}
|
||||
compiler.Compile()
|
||||
|
||||
@ -198,20 +221,20 @@ class FeatureCompilerTest(unittest.TestCase):
|
||||
def testAliasOnNonApiFeature(self):
|
||||
compiler = self._createTestFeatureCompiler('PermissionFeature')
|
||||
compiler._json = {
|
||||
'feature_alpha': {
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'alias': 'feature_beta'
|
||||
},
|
||||
'feature_beta': [{
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'source': 'feature_alpha'
|
||||
},{
|
||||
'channel': 'dev',
|
||||
'context': ['privileged_extension']
|
||||
}]
|
||||
};
|
||||
'feature_alpha': {
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'alias': 'feature_beta'
|
||||
},
|
||||
'feature_beta': [{
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'source': 'feature_alpha'
|
||||
}, {
|
||||
'channel': 'dev',
|
||||
'context': ['privileged_extension']
|
||||
}]
|
||||
}
|
||||
compiler.Compile()
|
||||
|
||||
feature = compiler._features.get('feature_alpha')
|
||||
@ -225,17 +248,17 @@ class FeatureCompilerTest(unittest.TestCase):
|
||||
def testAliasFeature(self):
|
||||
compiler = self._createTestFeatureCompiler('APIFeature')
|
||||
compiler._json = {
|
||||
'feature_alpha': {
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'alias': 'feature_beta'
|
||||
},
|
||||
'feature_beta': {
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'source': 'feature_alpha'
|
||||
}
|
||||
};
|
||||
'feature_alpha': {
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'alias': 'feature_beta'
|
||||
},
|
||||
'feature_beta': {
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'source': 'feature_alpha'
|
||||
}
|
||||
}
|
||||
compiler.Compile()
|
||||
|
||||
feature = compiler._features.get('feature_alpha')
|
||||
@ -249,40 +272,41 @@ class FeatureCompilerTest(unittest.TestCase):
|
||||
def testMultipleAliasesInComplexFeature(self):
|
||||
compiler = self._createTestFeatureCompiler('APIFeature')
|
||||
compiler._json = {
|
||||
'feature_alpha': [{
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'alias': 'feature_beta'
|
||||
}, {
|
||||
'contexts': ['privileged_extension'],
|
||||
'channel': 'beta',
|
||||
'alias': 'feature_beta'
|
||||
}]
|
||||
};
|
||||
'feature_alpha': [{
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'alias': 'feature_beta'
|
||||
}, {
|
||||
'contexts': ['privileged_extension'],
|
||||
'channel': 'beta',
|
||||
'alias': 'feature_beta'
|
||||
}]
|
||||
}
|
||||
compiler.Compile()
|
||||
|
||||
feature = compiler._features.get('feature_alpha')
|
||||
self.assertTrue(feature)
|
||||
self._hasError(feature, 'Error parsing feature "feature_alpha" at key ' +
|
||||
'"alias": Key can be set at most once per feature.')
|
||||
self._hasError(
|
||||
feature, 'Error parsing feature "feature_alpha" at key ' +
|
||||
'"alias": Key can be set at most once per feature.')
|
||||
|
||||
def testAliasReferenceInComplexFeature(self):
|
||||
compiler = self._createTestFeatureCompiler('APIFeature')
|
||||
compiler._json = {
|
||||
'feature_alpha': [{
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'alias': 'feature_beta'
|
||||
}, {
|
||||
'contexts': ['privileged_extension'],
|
||||
'channel': 'beta',
|
||||
}],
|
||||
'feature_beta': {
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'source': 'feature_alpha'
|
||||
}
|
||||
};
|
||||
'feature_alpha': [{
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'alias': 'feature_beta'
|
||||
}, {
|
||||
'contexts': ['privileged_extension'],
|
||||
'channel': 'beta',
|
||||
}],
|
||||
'feature_beta': {
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'source': 'feature_alpha'
|
||||
}
|
||||
}
|
||||
compiler.Compile()
|
||||
|
||||
feature = compiler._features.get('feature_alpha')
|
||||
@ -296,57 +320,58 @@ class FeatureCompilerTest(unittest.TestCase):
|
||||
def testSourceMissingReference(self):
|
||||
compiler = self._createTestFeatureCompiler('APIFeature')
|
||||
compiler._json = {
|
||||
'feature_alpha': {
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'alias': 'feature_beta'
|
||||
},
|
||||
'feature_beta': {
|
||||
'contexts': ['privileged_extension'],
|
||||
'channel': 'beta',
|
||||
'source': 'does_not_exist'
|
||||
}
|
||||
};
|
||||
'feature_alpha': {
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'alias': 'feature_beta'
|
||||
},
|
||||
'feature_beta': {
|
||||
'contexts': ['privileged_extension'],
|
||||
'channel': 'beta',
|
||||
'source': 'does_not_exist'
|
||||
}
|
||||
}
|
||||
compiler.Compile()
|
||||
|
||||
feature = compiler._features.get('feature_beta')
|
||||
self.assertTrue(feature)
|
||||
self._hasError(feature, 'A feature source property should reference a ' +
|
||||
'feature whose alias property references it back.')
|
||||
|
||||
self._hasError(
|
||||
feature, 'A feature source property should reference a ' +
|
||||
'feature whose alias property references it back.')
|
||||
|
||||
def testAliasMissingReferenceInComplexFeature(self):
|
||||
compiler = self._createTestFeatureCompiler('APIFeature')
|
||||
compiler._json = {
|
||||
'feature_alpha': [{
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'alias': 'feature_beta'
|
||||
}, {
|
||||
'contexts': ['privileged_extension'],
|
||||
'channel': 'beta'
|
||||
}]
|
||||
};
|
||||
'feature_alpha': [{
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'alias': 'feature_beta'
|
||||
}, {
|
||||
'contexts': ['privileged_extension'],
|
||||
'channel': 'beta'
|
||||
}]
|
||||
}
|
||||
compiler.Compile()
|
||||
|
||||
feature = compiler._features.get('feature_alpha')
|
||||
self.assertTrue(feature)
|
||||
self._hasError(feature, 'A feature alias property should reference a ' +
|
||||
'feature whose source property references it back.')
|
||||
self._hasError(
|
||||
feature, 'A feature alias property should reference a ' +
|
||||
'feature whose source property references it back.')
|
||||
|
||||
def testAliasReferenceMissingSourceInComplexFeature(self):
|
||||
compiler = self._createTestFeatureCompiler('APIFeature')
|
||||
compiler._json = {
|
||||
'feature_alpha': {
|
||||
'contexts': ['privileged_extension'],
|
||||
'channel': 'beta',
|
||||
},
|
||||
'feature_beta': {
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'alias': 'feature_alpha'
|
||||
}
|
||||
};
|
||||
'feature_alpha': {
|
||||
'contexts': ['privileged_extension'],
|
||||
'channel': 'beta',
|
||||
},
|
||||
'feature_beta': {
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'alias': 'feature_alpha'
|
||||
}
|
||||
}
|
||||
compiler.Compile()
|
||||
|
||||
feature = compiler._features.get('feature_alpha')
|
||||
@ -355,23 +380,23 @@ class FeatureCompilerTest(unittest.TestCase):
|
||||
|
||||
feature = compiler._features.get('feature_beta')
|
||||
self.assertTrue(feature)
|
||||
self._hasError(feature, 'A feature alias property should reference a ' +
|
||||
'feature whose source property references it back.')
|
||||
self._hasError(
|
||||
feature, 'A feature alias property should reference a ' +
|
||||
'feature whose source property references it back.')
|
||||
|
||||
def testComplexParentWithoutDefaultParent(self):
|
||||
c = feature_compiler.FeatureCompiler(
|
||||
None, None, 'APIFeature', None, None, None, None)
|
||||
c._CompileFeature('bookmarks',
|
||||
[{
|
||||
'contexts': ['privileged_extension'],
|
||||
}, {
|
||||
'channel': 'stable',
|
||||
'contexts': ['webui'],
|
||||
}])
|
||||
c = feature_compiler.FeatureCompiler(None, None, 'APIFeature', None, None,
|
||||
None, None)
|
||||
c._CompileFeature('bookmarks', [{
|
||||
'contexts': ['privileged_extension'],
|
||||
}, {
|
||||
'channel': 'stable',
|
||||
'contexts': ['webui'],
|
||||
}])
|
||||
|
||||
with self.assertRaisesRegex(AssertionError,
|
||||
'No default parent found for bookmarks'):
|
||||
c._CompileFeature('bookmarks.export', { "allowlist": ["asdf"] })
|
||||
c._CompileFeature('bookmarks.export', {"allowlist": ["asdf"]})
|
||||
|
||||
def testComplexFeatureWithSinglePropertyBlock(self):
|
||||
compiler = self._createTestFeatureCompiler('APIFeature')
|
||||
@ -383,16 +408,18 @@ class FeatureCompilerTest(unittest.TestCase):
|
||||
'feature key instead of a list.')
|
||||
with self.assertRaisesRegex(AssertionError, error):
|
||||
compiler._CompileFeature('feature_alpha',
|
||||
[{
|
||||
'contexts': ['privileged_extension'],
|
||||
'channel': 'stable',
|
||||
}])
|
||||
[{
|
||||
'contexts': ['privileged_extension'],
|
||||
'channel': 'stable',
|
||||
}])
|
||||
|
||||
def testRealIdsDisallowedInAllowlist(self):
|
||||
fake_id = 'a' * 32;
|
||||
f = self._parseFeature({'allowlist': [fake_id],
|
||||
'extension_types': ['extension'],
|
||||
'channel': 'beta'})
|
||||
fake_id = 'a' * 32
|
||||
f = self._parseFeature({
|
||||
'allowlist': [fake_id],
|
||||
'extension_types': ['extension'],
|
||||
'channel': 'beta'
|
||||
})
|
||||
f.Validate('PermissionFeature', {})
|
||||
self._hasError(
|
||||
f, 'list should only have hex-encoded SHA1 hashes of extension ids')
|
||||
@ -401,31 +428,34 @@ class FeatureCompilerTest(unittest.TestCase):
|
||||
f = self._parseFeature({
|
||||
'extension_types': ['extension', 'hosted_app'],
|
||||
'allowlist': ['0123456789ABCDEF0123456789ABCDEF01234567'],
|
||||
'channel': 'beta',
|
||||
'channel':
|
||||
'beta',
|
||||
})
|
||||
f.Validate('PermissionFeature', {})
|
||||
self._hasError(f, 'Hosted apps are not allowed to use restricted features')
|
||||
|
||||
def testHostedAppsCantUseAllowlistedFeatures_ComplexFeature(self):
|
||||
c = feature_compiler.FeatureCompiler(
|
||||
None, None, 'PermissionFeature', None, None, None, None)
|
||||
c._CompileFeature('invalid_feature',
|
||||
c = feature_compiler.FeatureCompiler(None, None, 'PermissionFeature', None,
|
||||
None, None, None)
|
||||
c._CompileFeature(
|
||||
'invalid_feature',
|
||||
[{
|
||||
'extension_types': ['extension'],
|
||||
'channel': 'beta',
|
||||
'extension_types': ['extension'],
|
||||
'channel': 'beta',
|
||||
}, {
|
||||
'channel': 'beta',
|
||||
'extension_types': ['hosted_app'],
|
||||
'allowlist': ['0123456789ABCDEF0123456789ABCDEF01234567'],
|
||||
'channel': 'beta',
|
||||
'extension_types': ['hosted_app'],
|
||||
'allowlist': ['0123456789ABCDEF0123456789ABCDEF01234567'],
|
||||
}])
|
||||
c._CompileFeature('valid_feature',
|
||||
c._CompileFeature(
|
||||
'valid_feature',
|
||||
[{
|
||||
'extension_types': ['extension'],
|
||||
'channel': 'beta',
|
||||
'allowlist': ['0123456789ABCDEF0123456789ABCDEF01234567'],
|
||||
'extension_types': ['extension'],
|
||||
'channel': 'beta',
|
||||
'allowlist': ['0123456789ABCDEF0123456789ABCDEF01234567'],
|
||||
}, {
|
||||
'channel': 'beta',
|
||||
'extension_types': ['hosted_app'],
|
||||
'channel': 'beta',
|
||||
'extension_types': ['hosted_app'],
|
||||
}])
|
||||
|
||||
valid_feature = c._features.get('valid_feature')
|
||||
@ -437,20 +467,17 @@ class FeatureCompilerTest(unittest.TestCase):
|
||||
self._hasError(invalid_feature,
|
||||
'Hosted apps are not allowed to use restricted features')
|
||||
|
||||
|
||||
def testHostedAppsCantUseAllowlistedFeatures_ChildFeature(self):
|
||||
c = feature_compiler.FeatureCompiler(
|
||||
None, None, 'PermissionFeature', None, None, None, None)
|
||||
c._CompileFeature('parent',
|
||||
{
|
||||
'extension_types': ['hosted_app'],
|
||||
'channel': 'beta',
|
||||
})
|
||||
c = feature_compiler.FeatureCompiler(None, None, 'PermissionFeature', None,
|
||||
None, None, None)
|
||||
c._CompileFeature('parent', {
|
||||
'extension_types': ['hosted_app'],
|
||||
'channel': 'beta',
|
||||
})
|
||||
|
||||
c._CompileFeature('parent.child',
|
||||
{
|
||||
'allowlist': ['0123456789ABCDEF0123456789ABCDEF01234567']
|
||||
})
|
||||
c._CompileFeature(
|
||||
'parent.child',
|
||||
{'allowlist': ['0123456789ABCDEF0123456789ABCDEF01234567']})
|
||||
feature = c._features.get('parent.child')
|
||||
self.assertTrue(feature)
|
||||
self._hasError(feature,
|
||||
@ -459,27 +486,27 @@ class FeatureCompilerTest(unittest.TestCase):
|
||||
def testEmptyContextsDisallowed(self):
|
||||
compiler = self._createTestFeatureCompiler('APIFeature')
|
||||
compiler._json = {
|
||||
'feature_alpha': {
|
||||
'channel': 'beta',
|
||||
'contexts': [],
|
||||
'extension_types': ['extension']
|
||||
}
|
||||
'feature_alpha': {
|
||||
'channel': 'beta',
|
||||
'contexts': [],
|
||||
'extension_types': ['extension']
|
||||
}
|
||||
}
|
||||
compiler.Compile()
|
||||
|
||||
feature = compiler._features.get('feature_alpha')
|
||||
self.assertTrue(feature)
|
||||
self._hasError(feature,
|
||||
'An empty contexts list is not allowed for this feature.')
|
||||
'An empty contexts list is not allowed for this feature.')
|
||||
|
||||
def testEmptyContextsAllowed(self):
|
||||
compiler = self._createTestFeatureCompiler('APIFeature')
|
||||
compiler._json = {
|
||||
'empty_contexts': {
|
||||
'channel': 'beta',
|
||||
'contexts': [],
|
||||
'extension_types': ['extension']
|
||||
}
|
||||
'empty_contexts': {
|
||||
'channel': 'beta',
|
||||
'contexts': [],
|
||||
'extension_types': ['extension']
|
||||
}
|
||||
}
|
||||
compiler.Compile()
|
||||
|
||||
@ -491,18 +518,19 @@ class FeatureCompilerTest(unittest.TestCase):
|
||||
compiler = self._createTestFeatureCompiler('APIFeature')
|
||||
|
||||
compiler._json = {
|
||||
'feature_cups': {
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'extension_types': ['extension'],
|
||||
'required_buildflags': ['use_cups']
|
||||
}
|
||||
'feature_cups': {
|
||||
'channel': 'beta',
|
||||
'contexts': ['privileged_extension'],
|
||||
'extension_types': ['extension'],
|
||||
'required_buildflags': ['use_cups']
|
||||
}
|
||||
}
|
||||
compiler.Compile()
|
||||
cc_code = compiler.Render()
|
||||
|
||||
# The code below is formatted correctly!
|
||||
self.assertEqual(cc_code.Render(), ''' {
|
||||
self.assertEqual(
|
||||
cc_code.Render(), ''' {
|
||||
#if BUILDFLAG(USE_CUPS)
|
||||
SimpleFeature* feature = new SimpleFeature();
|
||||
feature->set_name("feature_cups");
|
||||
@ -513,5 +541,6 @@ class FeatureCompilerTest(unittest.TestCase):
|
||||
#endif
|
||||
}''')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -9,6 +9,7 @@ import cpp_util
|
||||
|
||||
|
||||
class CCGenerator(object):
|
||||
|
||||
def Generate(self, feature_defs, source_file, namespace):
|
||||
return _Generator(feature_defs, source_file, namespace).Generate()
|
||||
|
||||
@ -16,6 +17,7 @@ class CCGenerator(object):
|
||||
class _Generator(object):
|
||||
"""A .cc generator for features.
|
||||
"""
|
||||
|
||||
def __init__(self, feature_defs, source_file, namespace):
|
||||
self._feature_defs = feature_defs
|
||||
self._source_file = source_file
|
||||
@ -27,54 +29,53 @@ class _Generator(object):
|
||||
"""Generates a Code object for features.
|
||||
"""
|
||||
c = Code()
|
||||
(c.Append(cpp_util.CHROMIUM_LICENSE)
|
||||
.Append()
|
||||
(c.Append(cpp_util.CHROMIUM_LICENSE) \
|
||||
.Append() \
|
||||
.Append(cpp_util.GENERATED_FEATURE_MESSAGE %
|
||||
cpp_util.ToPosixPath(self._source_file))
|
||||
.Append()
|
||||
.Append('#include <string>')
|
||||
.Append()
|
||||
cpp_util.ToPosixPath(self._source_file)) \
|
||||
.Append() \
|
||||
.Append('#include <string>') \
|
||||
.Append() \
|
||||
.Append('#include "%s.h"' %
|
||||
cpp_util.ToPosixPath(self._source_file_filename))
|
||||
.Append()
|
||||
.Append('#include "base/notreached.h"')
|
||||
.Append()
|
||||
.Concat(cpp_util.OpenNamespace(self._namespace))
|
||||
cpp_util.ToPosixPath(self._source_file_filename)) \
|
||||
.Append() \
|
||||
.Append('#include "base/notreached.h"') \
|
||||
.Append() \
|
||||
.Concat(cpp_util.OpenNamespace(self._namespace)) \
|
||||
.Append()
|
||||
)
|
||||
|
||||
# Generate the constructor.
|
||||
(c.Append('%s::%s() {' % (self._class_name, self._class_name))
|
||||
(c.Append('%s::%s() {' % (self._class_name, self._class_name)) \
|
||||
.Sblock()
|
||||
)
|
||||
for feature in self._feature_defs:
|
||||
c.Append('features_["%s"] = %s;'
|
||||
% (feature.name,
|
||||
cpp_util.FeatureNameToConstantName(feature.name)))
|
||||
(c.Eblock()
|
||||
.Append('}')
|
||||
c.Append('features_["%s"] = %s;' %
|
||||
(feature.name, cpp_util.FeatureNameToConstantName(feature.name)))
|
||||
(c.Eblock() \
|
||||
.Append('}') \
|
||||
.Append()
|
||||
)
|
||||
|
||||
# Generate the ToString function.
|
||||
(c.Append('const char* %s::ToString('
|
||||
'%s::ID id) const {' % (self._class_name, self._class_name))
|
||||
.Sblock()
|
||||
.Append('switch (id) {')
|
||||
'%s::ID id) const {' % (self._class_name, self._class_name)) \
|
||||
.Sblock() \
|
||||
.Append('switch (id) {') \
|
||||
.Sblock()
|
||||
)
|
||||
for feature in self._feature_defs:
|
||||
c.Append('case %s: return "%s";' %
|
||||
(cpp_util.FeatureNameToConstantName(feature.name), feature.name))
|
||||
(c.Append('case kUnknown: break;')
|
||||
.Append('case kEnumBoundary: break;')
|
||||
.Eblock()
|
||||
.Append('}')
|
||||
.Append('NOTREACHED_IN_MIGRATION();')
|
||||
(cpp_util.FeatureNameToConstantName(feature.name), feature.name))
|
||||
(c.Append('case kUnknown: break;') \
|
||||
.Append('case kEnumBoundary: break;') \
|
||||
.Eblock() \
|
||||
.Append('}') \
|
||||
.Append('NOTREACHED_IN_MIGRATION();') \
|
||||
.Append('return "";')
|
||||
)
|
||||
(c.Eblock()
|
||||
.Append('}')
|
||||
(c.Eblock() \
|
||||
.Append('}') \
|
||||
.Append()
|
||||
)
|
||||
|
||||
@ -82,13 +83,13 @@ class _Generator(object):
|
||||
|
||||
(c.Append('%s::ID %s::FromString('
|
||||
'const std::string& id) const {'
|
||||
% (self._class_name, self._class_name))
|
||||
.Sblock()
|
||||
.Append('const auto& it = features_.find(id);' % self._class_name)
|
||||
.Append('return (it == features_.end()) ? kUnknown : it->second;')
|
||||
.Eblock()
|
||||
.Append('}')
|
||||
.Append()
|
||||
% (self._class_name, self._class_name)) \
|
||||
.Sblock() \
|
||||
.Append('const auto& it = features_.find(id);' % self._class_name) \
|
||||
.Append('return (it == features_.end()) ? kUnknown : it->second;') \
|
||||
.Eblock() \
|
||||
.Append('}') \
|
||||
.Append() \
|
||||
.Cblock(cpp_util.CloseNamespace(self._namespace))
|
||||
)
|
||||
|
||||
|
@ -23,9 +23,7 @@ def _GenerateSchema(filename, root, destdir, namespace):
|
||||
# Load in the feature permissions from the JSON file.
|
||||
schema = os.path.normpath(filename)
|
||||
schema_loader = SchemaLoader(os.path.dirname(os.path.relpath(schema, root)),
|
||||
os.path.dirname(schema),
|
||||
[],
|
||||
None)
|
||||
os.path.dirname(schema), [], None)
|
||||
schema_filename = os.path.splitext(schema)[0]
|
||||
feature_defs = schema_loader.LoadSchema(schema)
|
||||
|
||||
@ -38,10 +36,8 @@ def _GenerateSchema(filename, root, destdir, namespace):
|
||||
relpath = os.path.relpath(os.path.normpath(source_file_dir), root)
|
||||
full_path = os.path.join(relpath, schema)
|
||||
|
||||
generators = [
|
||||
('%s.cc' % schema_filename, CCGenerator()),
|
||||
('%s.h' % schema_filename, HGenerator())
|
||||
]
|
||||
generators = [('%s.cc' % schema_filename, CCGenerator()),
|
||||
('%s.h' % schema_filename, HGenerator())]
|
||||
|
||||
# Generate and output the code for all features.
|
||||
output_code = []
|
||||
@ -59,12 +55,19 @@ if __name__ == '__main__':
|
||||
parser = optparse.OptionParser(
|
||||
description='Generates a C++ features model from JSON schema',
|
||||
usage='usage: %prog [option]... schema')
|
||||
parser.add_option('-r', '--root', default='.',
|
||||
parser.add_option(
|
||||
'-r',
|
||||
'--root',
|
||||
default='.',
|
||||
help='logical include root directory. Path to schema files from '
|
||||
'specified dir will be the include path.')
|
||||
parser.add_option('-d', '--destdir',
|
||||
help='root directory to output generated files.')
|
||||
parser.add_option('-n', '--namespace', default='generated_features',
|
||||
'specified dir will be the include path.')
|
||||
parser.add_option('-d',
|
||||
'--destdir',
|
||||
help='root directory to output generated files.')
|
||||
parser.add_option(
|
||||
'-n',
|
||||
'--namespace',
|
||||
default='generated_features',
|
||||
help='C++ namespace for generated files. e.g extensions::api.')
|
||||
(opts, filenames) = parser.parse_args()
|
||||
|
||||
|
@ -9,6 +9,7 @@ import cpp_util
|
||||
|
||||
|
||||
class HGenerator(object):
|
||||
|
||||
def Generate(self, features, source_file, namespace):
|
||||
return _Generator(features, source_file, namespace).Generate()
|
||||
|
||||
@ -16,6 +17,7 @@ class HGenerator(object):
|
||||
class _Generator(object):
|
||||
"""A .cc generator for features.
|
||||
"""
|
||||
|
||||
def __init__(self, features, source_file, namespace):
|
||||
self._feature_defs = features
|
||||
self._source_file = source_file
|
||||
@ -27,10 +29,10 @@ class _Generator(object):
|
||||
"""Generates a Code object for features.
|
||||
"""
|
||||
c = Code()
|
||||
(c.Append(cpp_util.CHROMIUM_LICENSE)
|
||||
.Append()
|
||||
(c.Append(cpp_util.CHROMIUM_LICENSE) \
|
||||
.Append() \
|
||||
.Append(cpp_util.GENERATED_FEATURE_MESSAGE %
|
||||
cpp_util.ToPosixPath(self._source_file))
|
||||
cpp_util.ToPosixPath(self._source_file)) \
|
||||
.Append()
|
||||
)
|
||||
|
||||
@ -39,31 +41,31 @@ class _Generator(object):
|
||||
output_file = os.path.splitext(self._namespace.source_file)[0] + '.h'
|
||||
ifndef_name = cpp_util.GenerateIfndefName(output_file)
|
||||
|
||||
(c.Append('#ifndef %s' % ifndef_name)
|
||||
.Append('#define %s' % ifndef_name)
|
||||
(c.Append('#ifndef %s' % ifndef_name) \
|
||||
.Append('#define %s' % ifndef_name) \
|
||||
.Append()
|
||||
)
|
||||
|
||||
(c.Append('#include <map>')
|
||||
.Append('#include <string>')
|
||||
.Append()
|
||||
.Concat(cpp_util.OpenNamespace(self._namespace))
|
||||
(c.Append('#include <map>') \
|
||||
.Append('#include <string>') \
|
||||
.Append() \
|
||||
.Concat(cpp_util.OpenNamespace(self._namespace)) \
|
||||
.Append()
|
||||
)
|
||||
|
||||
(c.Append('class %s {' % self._class_name)
|
||||
.Append(' public:')
|
||||
.Sblock()
|
||||
.Concat(self._GeneratePublicBody())
|
||||
.Eblock()
|
||||
.Append(' private:')
|
||||
.Sblock()
|
||||
.Concat(self._GeneratePrivateBody())
|
||||
.Eblock('};')
|
||||
.Append()
|
||||
(c.Append('class %s {' % self._class_name) \
|
||||
.Append(' public:') \
|
||||
.Sblock() \
|
||||
.Concat(self._GeneratePublicBody()) \
|
||||
.Eblock() \
|
||||
.Append(' private:') \
|
||||
.Sblock() \
|
||||
.Concat(self._GeneratePrivateBody()) \
|
||||
.Eblock('};') \
|
||||
.Append() \
|
||||
.Cblock(cpp_util.CloseNamespace(self._namespace))
|
||||
)
|
||||
(c.Append('#endif // %s' % ifndef_name)
|
||||
(c.Append('#endif // %s' % ifndef_name) \
|
||||
.Append()
|
||||
)
|
||||
return c
|
||||
@ -71,14 +73,14 @@ class _Generator(object):
|
||||
def _GeneratePublicBody(self):
|
||||
c = Code()
|
||||
|
||||
(c.Append('%s();' % self._class_name)
|
||||
.Append()
|
||||
.Append('enum ID {')
|
||||
.Concat(self._GenerateEnumConstants())
|
||||
.Eblock('};')
|
||||
.Append()
|
||||
.Append('const char* ToString(ID id) const;')
|
||||
.Append('ID FromString(const std::string& id) const;')
|
||||
(c.Append('%s();' % self._class_name) \
|
||||
.Append() \
|
||||
.Append('enum ID {') \
|
||||
.Concat(self._GenerateEnumConstants()) \
|
||||
.Eblock('};') \
|
||||
.Append() \
|
||||
.Append('const char* ToString(ID id) const;') \
|
||||
.Append('ID FromString(const std::string& id) const;') \
|
||||
.Append()
|
||||
)
|
||||
return c
|
||||
@ -90,7 +92,7 @@ class _Generator(object):
|
||||
def _GenerateEnumConstants(self):
|
||||
c = Code()
|
||||
|
||||
(c.Sblock()
|
||||
(c.Sblock() \
|
||||
.Append('kUnknown,')
|
||||
)
|
||||
for feature in self._feature_defs:
|
||||
|
@ -2,7 +2,6 @@
|
||||
# Copyright 2022 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
"""Helper for quickly generating all known JS externs."""
|
||||
|
||||
import argparse
|
||||
@ -31,6 +30,7 @@ REPO_ROOT = os.path.dirname(os.path.dirname(DIR))
|
||||
# Import the helper module.
|
||||
sys.path.insert(0, os.path.join(REPO_ROOT, 'extensions', 'common', 'api'))
|
||||
from externs_checker import ExternsChecker
|
||||
|
||||
sys.path.pop(0)
|
||||
|
||||
|
||||
@ -72,6 +72,7 @@ class FakeOutputApi:
|
||||
"""
|
||||
|
||||
class PresubmitResult:
|
||||
|
||||
def __init__(self, msg, long_text=None):
|
||||
self.msg = msg
|
||||
self.long_text = long_text
|
||||
@ -112,7 +113,7 @@ def Generate(input_api, output_api, force=False, dryrun=False):
|
||||
externs_relpath = input_api.os_path.relpath(externs, src_root)
|
||||
|
||||
print('\r' + ' ' * msg_len, end='\r')
|
||||
msg = 'Checking %s ...' % (source_relpath,)
|
||||
msg = 'Checking %s ...' % (source_relpath, )
|
||||
msg_len = len(msg)
|
||||
print(msg, end='')
|
||||
sys.stdout.flush()
|
||||
@ -123,9 +124,9 @@ def Generate(input_api, output_api, force=False, dryrun=False):
|
||||
if not dryrun:
|
||||
print('\n%s: %s' % (source_relpath, e))
|
||||
ret.append(
|
||||
output_api.PresubmitResult(
|
||||
'%s: unable to generate' % (source_relpath,),
|
||||
long_text=str(e)))
|
||||
output_api.PresubmitResult('%s: unable to generate' %
|
||||
(source_relpath, ),
|
||||
long_text=str(e)))
|
||||
continue
|
||||
|
||||
# Ignore the first line (copyright) to avoid yearly thrashing.
|
||||
@ -148,7 +149,7 @@ def Generate(input_api, output_api, force=False, dryrun=False):
|
||||
if not dryrun:
|
||||
print('\r' + ' ' * msg_len, end='\r')
|
||||
msg_len = 0
|
||||
print('Updating %s' % (externs_relpath,))
|
||||
print('Updating %s' % (externs_relpath, ))
|
||||
with open(externs, 'w', encoding='utf-8') as fp:
|
||||
fp.write(copyright + '\n')
|
||||
fp.write(new_data)
|
||||
@ -161,11 +162,16 @@ def Generate(input_api, output_api, force=False, dryrun=False):
|
||||
def get_parser():
|
||||
"""Get CLI parser."""
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('-n', '--dry-run', dest='dryrun', action='store_true',
|
||||
parser.add_argument('-n',
|
||||
'--dry-run',
|
||||
dest='dryrun',
|
||||
action='store_true',
|
||||
help="Don't make changes; only show changed files")
|
||||
parser.add_argument('-f', '--force', action='store_true',
|
||||
parser.add_argument('-f',
|
||||
'--force',
|
||||
action='store_true',
|
||||
help='Regenerate files even if they have a TODO '
|
||||
'disabling generation')
|
||||
'disabling generation')
|
||||
return parser
|
||||
|
||||
|
||||
@ -174,7 +180,9 @@ def main(argv):
|
||||
parser = get_parser()
|
||||
opts = parser.parse_args(argv)
|
||||
|
||||
results = Generate(FakeInputApi(), FakeOutputApi(), force=opts.force,
|
||||
results = Generate(FakeInputApi(),
|
||||
FakeOutputApi(),
|
||||
force=opts.force,
|
||||
dryrun=opts.dryrun)
|
||||
if opts.dryrun and results:
|
||||
for result in results:
|
||||
|
@ -9,7 +9,9 @@ from model import PropertyType, Type, Property
|
||||
import cpp_util
|
||||
import schema_util
|
||||
|
||||
|
||||
class HGenerator(object):
|
||||
|
||||
def __init__(self, type_generator):
|
||||
self._type_generator = type_generator
|
||||
|
||||
@ -20,6 +22,7 @@ class HGenerator(object):
|
||||
class _Generator(object):
|
||||
"""A .h generator for a namespace.
|
||||
"""
|
||||
|
||||
def __init__(self, namespace, cpp_type_generator):
|
||||
self._namespace = namespace
|
||||
self._type_helper = cpp_type_generator
|
||||
@ -30,10 +33,10 @@ class _Generator(object):
|
||||
"""Generates a Code object with the .h for a single namespace.
|
||||
"""
|
||||
c = Code()
|
||||
(c.Append(cpp_util.CHROMIUM_LICENSE)
|
||||
.Append()
|
||||
(c.Append(cpp_util.CHROMIUM_LICENSE) \
|
||||
.Append() \
|
||||
.Append(cpp_util.GENERATED_FILE_MESSAGE %
|
||||
cpp_util.ToPosixPath(self._namespace.source_file))
|
||||
cpp_util.ToPosixPath(self._namespace.source_file)) \
|
||||
.Append()
|
||||
)
|
||||
|
||||
@ -48,21 +51,21 @@ class _Generator(object):
|
||||
# non-optional types from other namespaces.
|
||||
include_soft = self._namespace.name not in ('tabs', 'windows')
|
||||
|
||||
(c.Append('#ifndef %s' % ifndef_name)
|
||||
.Append('#define %s' % ifndef_name)
|
||||
.Append()
|
||||
.Append('#include <stdint.h>')
|
||||
.Append()
|
||||
.Append('#include <map>')
|
||||
.Append('#include <memory>')
|
||||
.Append('#include <optional>')
|
||||
.Append('#include <string>')
|
||||
.Append('#include <vector>')
|
||||
.Append()
|
||||
.Append('#include "base/values.h"')
|
||||
(c.Append('#ifndef %s' % ifndef_name) \
|
||||
.Append('#define %s' % ifndef_name) \
|
||||
.Append() \
|
||||
.Append('#include <stdint.h>') \
|
||||
.Append() \
|
||||
.Append('#include <map>') \
|
||||
.Append('#include <memory>') \
|
||||
.Append('#include <optional>') \
|
||||
.Append('#include <string>') \
|
||||
.Append('#include <vector>') \
|
||||
.Append() \
|
||||
.Append('#include "base/values.h"') \
|
||||
.Cblock(self._type_helper.GenerateIncludes(
|
||||
include_soft=include_soft,
|
||||
generate_error_messages=self._generate_error_messages))
|
||||
generate_error_messages=self._generate_error_messages)) \
|
||||
.Append()
|
||||
)
|
||||
|
||||
@ -77,22 +80,21 @@ class _Generator(object):
|
||||
c.Concat(cpp_util.OpenNamespace(cpp_namespace))
|
||||
c.Append()
|
||||
if self._namespace.properties:
|
||||
(c.Append('//')
|
||||
.Append('// Properties')
|
||||
.Append('//')
|
||||
(c.Append('//') \
|
||||
.Append('// Properties') \
|
||||
.Append('//') \
|
||||
.Append()
|
||||
)
|
||||
for prop in self._namespace.properties.values():
|
||||
property_code = self._type_helper.GeneratePropertyValues(
|
||||
prop,
|
||||
'extern const %(type)s %(name)s;')
|
||||
prop, 'extern const %(type)s %(name)s;')
|
||||
if property_code:
|
||||
c.Cblock(property_code)
|
||||
if self._namespace.types:
|
||||
(c.Append('//')
|
||||
.Append('// Types')
|
||||
.Append('//')
|
||||
.Append()
|
||||
(c.Append('//') \
|
||||
.Append('// Types') \
|
||||
.Append('//') \
|
||||
.Append() \
|
||||
.Cblock(self._GenerateTypes(self._FieldDependencyOrder(),
|
||||
is_toplevel=True,
|
||||
generate_typedefs=True))
|
||||
@ -104,24 +106,24 @@ class _Generator(object):
|
||||
c.Append()
|
||||
c.Cblock(self._GenerateManifestKeys())
|
||||
if self._namespace.functions:
|
||||
(c.Append('//')
|
||||
.Append('// Functions')
|
||||
.Append('//')
|
||||
(c.Append('//') \
|
||||
.Append('// Functions') \
|
||||
.Append('//') \
|
||||
.Append()
|
||||
)
|
||||
for function in self._namespace.functions.values():
|
||||
c.Cblock(self._GenerateFunction(function))
|
||||
if self._namespace.events:
|
||||
(c.Append('//')
|
||||
.Append('// Events')
|
||||
.Append('//')
|
||||
(c.Append('//') \
|
||||
.Append('// Events') \
|
||||
.Append('//') \
|
||||
.Append()
|
||||
)
|
||||
for event in self._namespace.events.values():
|
||||
c.Cblock(self._GenerateEvent(event))
|
||||
(c.Concat(cpp_util.CloseNamespace(cpp_namespace))
|
||||
.Append()
|
||||
.Append('#endif // %s' % ifndef_name)
|
||||
(c.Concat(cpp_util.CloseNamespace(cpp_namespace)) \
|
||||
.Append() \
|
||||
.Append('#endif // %s' % ifndef_name) \
|
||||
.Append()
|
||||
)
|
||||
return c
|
||||
@ -137,8 +139,8 @@ class _Generator(object):
|
||||
raise ValueError("Illegal circular dependency via cycle " +
|
||||
", ".join(map(lambda x: x.name, path + [type_])))
|
||||
for prop in type_.properties.values():
|
||||
if (prop.type_ == PropertyType.REF and
|
||||
schema_util.GetNamespace(prop.ref_type) == self._namespace.name):
|
||||
if (prop.type_ == PropertyType.REF and schema_util.GetNamespace(
|
||||
prop.ref_type) == self._namespace.name):
|
||||
ExpandType(path + [type_], self._namespace.types[prop.ref_type])
|
||||
if not type_ in dependency_order:
|
||||
dependency_order.append(type_)
|
||||
@ -151,23 +153,23 @@ class _Generator(object):
|
||||
"""Generate a code object with the declaration of a C++ enum.
|
||||
"""
|
||||
c = Code()
|
||||
c.Sblock('enum class {name} {{'.format(
|
||||
name=enum_name))
|
||||
c.Sblock('enum class {name} {{'.format(name=enum_name))
|
||||
|
||||
# Explicitly initialize kNone to 0, since we rely on default initialization
|
||||
# for enum members. Otherwise, default initialization will always set a
|
||||
# value to 0, even if it's not a valid enum entry.
|
||||
c.Append(
|
||||
self._type_helper.GetEnumNoneValue(type_, full_name=False) + ' = 0,')
|
||||
self._type_helper.GetEnumNoneValue(type_, full_name=False) + ' = 0,')
|
||||
|
||||
for value in type_.enum_values:
|
||||
current_enum_string = (
|
||||
self._type_helper.GetEnumValue(type_, value, full_name=False))
|
||||
current_enum_string = (self._type_helper.GetEnumValue(type_,
|
||||
value,
|
||||
full_name=False))
|
||||
c.Append(current_enum_string + ',')
|
||||
|
||||
# Adding kMaxValue, which is friendly to enumaration histogram macros.
|
||||
c.Append('kMaxValue = {last_key_value},'.format(
|
||||
last_key_value=current_enum_string))
|
||||
last_key_value=current_enum_string))
|
||||
|
||||
c.Eblock('};')
|
||||
return c
|
||||
@ -183,10 +185,8 @@ class _Generator(object):
|
||||
needs_blank_line = True
|
||||
if prop.description:
|
||||
c.Comment(prop.description)
|
||||
(c.Append('%s %s;' % (
|
||||
self._type_helper.GetCppType(prop.type_, is_optional=prop.optional),
|
||||
prop.unix_name))
|
||||
)
|
||||
(c.Append('%s %s;' % (self._type_helper.GetCppType(
|
||||
prop.type_, is_optional=prop.optional), prop.unix_name)))
|
||||
return c
|
||||
|
||||
def _GenerateType(self, type_, is_toplevel=False, generate_typedefs=False):
|
||||
@ -204,7 +204,7 @@ class _Generator(object):
|
||||
|
||||
if type_.functions:
|
||||
# Wrap functions within types in the type's namespace.
|
||||
(c.Append('namespace %s {' % classname)
|
||||
(c.Append('namespace %s {' % classname) \
|
||||
.Append()
|
||||
)
|
||||
for function in type_.functions.values():
|
||||
@ -217,10 +217,8 @@ class _Generator(object):
|
||||
if generate_typedefs:
|
||||
item_cpp_type = self._type_helper.GetCppType(type_.item_type)
|
||||
if item_cpp_type != 'base::Value':
|
||||
(c.Append('using %s = std::vector<%s >;' % (
|
||||
classname,
|
||||
item_cpp_type))
|
||||
)
|
||||
(c.Append('using %s = std::vector<%s >;' %
|
||||
(classname, item_cpp_type)))
|
||||
else:
|
||||
c.Append('using %s = base::Value::List;' % classname)
|
||||
elif type_.property_type == PropertyType.STRING:
|
||||
@ -235,26 +233,25 @@ class _Generator(object):
|
||||
# Top level enums are in a namespace scope so the methods shouldn't be
|
||||
# static. On the other hand, those declared inline (e.g. in an object) do.
|
||||
maybe_static = '' if is_toplevel else 'static '
|
||||
(c.Append()
|
||||
(c.Append() \
|
||||
.Append('%sconst char* ToString(%s as_enum);' %
|
||||
(maybe_static, classname))
|
||||
(maybe_static, classname)) \
|
||||
.Append('%s%s Parse%s(std::string_view as_string);' %
|
||||
(maybe_static, classname, classname))
|
||||
(maybe_static, classname, classname)) \
|
||||
.Append(
|
||||
'%sstd::u16string Get%sParseError(std::string_view as_string);' %
|
||||
(maybe_static, classname))
|
||||
)
|
||||
elif type_.property_type in (PropertyType.CHOICES,
|
||||
PropertyType.OBJECT):
|
||||
elif type_.property_type in (PropertyType.CHOICES, PropertyType.OBJECT):
|
||||
if type_.description:
|
||||
c.Comment(type_.description)
|
||||
|
||||
(c.Sblock('struct %(classname)s {')
|
||||
.Append('%(classname)s();')
|
||||
.Append('~%(classname)s();')
|
||||
.Append('%(classname)s(const %(classname)s&) = delete;')
|
||||
.Append('%(classname)s& operator=(const %(classname)s&) = delete;')
|
||||
.Append('%(classname)s(%(classname)s&& rhs) noexcept;')
|
||||
(c.Sblock('struct %(classname)s {') \
|
||||
.Append('%(classname)s();') \
|
||||
.Append('~%(classname)s();') \
|
||||
.Append('%(classname)s(const %(classname)s&) = delete;') \
|
||||
.Append('%(classname)s& operator=(const %(classname)s&) = delete;') \
|
||||
.Append('%(classname)s(%(classname)s&& rhs) noexcept;') \
|
||||
.Append('%(classname)s& operator=(%(classname)s&& rhs) noexcept;')
|
||||
)
|
||||
|
||||
@ -263,28 +260,27 @@ class _Generator(object):
|
||||
c.Comment('Manifest key constants.')
|
||||
c.Concat(self._GenerateManifestKeyConstants(type_.properties.values()))
|
||||
|
||||
value_type = ('base::Value'
|
||||
if type_.property_type is PropertyType.CHOICES else
|
||||
'base::Value::Dict')
|
||||
value_type = ('base::Value' if type_.property_type is PropertyType.CHOICES
|
||||
else 'base::Value::Dict')
|
||||
|
||||
if (type_.origin.from_json or
|
||||
(type_.origin.from_manifest_keys and
|
||||
type_.property_type is PropertyType.CHOICES)):
|
||||
(c.Append()
|
||||
if (type_.origin.from_json
|
||||
or (type_.origin.from_manifest_keys
|
||||
and type_.property_type is PropertyType.CHOICES)):
|
||||
(c.Append() \
|
||||
.Comment('Populates a %s object from a base::Value& instance. Returns'
|
||||
' whether |out| was successfully populated.' % classname)
|
||||
' whether |out| was successfully populated.' % classname) \
|
||||
.Append('static bool Populate(%s);' % self._GenerateParams(
|
||||
('const base::Value& value', '%s& out' % classname)))
|
||||
)
|
||||
if type_.property_type is not PropertyType.CHOICES:
|
||||
(c.Append()
|
||||
(c.Append() \
|
||||
.Comment('Populates a %s object from a Dict& instance. Returns'
|
||||
' whether |out| was successfully populated.' % classname)
|
||||
' whether |out| was successfully populated.' % classname) \
|
||||
.Append('static bool Populate(%s);' % self._GenerateParams(
|
||||
('const base::Value::Dict& value', '%s& out' % classname)))
|
||||
)
|
||||
(c.Append()
|
||||
.Comment('Creates a deep copy of %s.' % classname)
|
||||
(c.Append() \
|
||||
.Comment('Creates a deep copy of %s.' % classname) \
|
||||
.Append('%s Clone() const;' % classname)
|
||||
)
|
||||
|
||||
@ -292,32 +288,32 @@ class _Generator(object):
|
||||
classname, support_errors=self._generate_error_messages)
|
||||
|
||||
if type_.property_type is not PropertyType.CHOICES:
|
||||
(c.Append()
|
||||
(c.Append() \
|
||||
.Comment('Creates a {classname} object from a base::Value::Dict,'
|
||||
' or {failure} on failure.'.format(
|
||||
classname=classname,
|
||||
failure=('unexpected'
|
||||
if self._generate_error_messages else 'nullopt')))
|
||||
if self._generate_error_messages else 'nullopt'))) \
|
||||
.Append('static {return_type} '
|
||||
'FromValue(const base::Value::Dict& value);'.format(
|
||||
return_type=return_type))
|
||||
)
|
||||
|
||||
(c.Append()
|
||||
(c.Append() \
|
||||
.Comment('Creates a {classname} object from a base::Value,'
|
||||
' or {failure} on failure.'.format(
|
||||
classname=classname,
|
||||
failure=('unexpected'
|
||||
if self._generate_error_messages else 'nullopt')))
|
||||
if self._generate_error_messages else 'nullopt'))) \
|
||||
.Append('static {return_type} '
|
||||
'FromValue(const base::Value& value);'.format(
|
||||
return_type=return_type))
|
||||
)
|
||||
|
||||
if type_.origin.from_client:
|
||||
(c.Append()
|
||||
(c.Append() \
|
||||
.Comment('Returns a new %s representing the serialized form of this'
|
||||
'%s object.' % (value_type, classname))
|
||||
'%s object.' % (value_type, classname)) \
|
||||
.Append('%s ToValue() const;' % value_type)
|
||||
)
|
||||
|
||||
@ -330,13 +326,12 @@ class _Generator(object):
|
||||
c.Cblock(self._GenerateTypes(type_.choices))
|
||||
c.Append('// Choices:')
|
||||
for choice_type in type_.choices:
|
||||
c.Append('%s as_%s;' % (
|
||||
self._type_helper.GetCppType(choice_type, is_optional=True),
|
||||
choice_type.unix_name))
|
||||
c.Append('%s as_%s;' % (self._type_helper.GetCppType(
|
||||
choice_type, is_optional=True), choice_type.unix_name))
|
||||
else:
|
||||
properties = type_.properties.values()
|
||||
(c.Append()
|
||||
.Cblock(self._GenerateTypes(p.type_ for p in properties))
|
||||
(c.Append() \
|
||||
.Cblock(self._GenerateTypes(p.type_ for p in properties)) \
|
||||
.Cblock(self._GenerateFields(properties)))
|
||||
if type_.additional_properties is not None:
|
||||
# Most additionalProperties actually have type "any", which is better
|
||||
@ -344,7 +339,7 @@ class _Generator(object):
|
||||
if type_.additional_properties.property_type == PropertyType.ANY:
|
||||
c.Append('base::Value::Dict additional_properties;')
|
||||
else:
|
||||
(c.Cblock(self._GenerateType(type_.additional_properties))
|
||||
(c.Cblock(self._GenerateType(type_.additional_properties)) \
|
||||
.Append('std::map<std::string, %s> additional_properties;' %
|
||||
self._type_helper.GetCppType(type_.additional_properties))
|
||||
)
|
||||
@ -357,10 +352,10 @@ class _Generator(object):
|
||||
c = Code()
|
||||
# TODO(kalman): use event.unix_name not Classname.
|
||||
event_namespace = cpp_util.Classname(event.name)
|
||||
(c.Append('namespace %s {' % event_namespace)
|
||||
.Append()
|
||||
.Concat(self._GenerateEventNameConstant(event))
|
||||
.Concat(self._GenerateAsyncResponseArguments(event.params))
|
||||
(c.Append('namespace %s {' % event_namespace) \
|
||||
.Append() \
|
||||
.Concat(self._GenerateEventNameConstant(event)) \
|
||||
.Concat(self._GenerateAsyncResponseArguments(event.params)) \
|
||||
.Append('} // namespace %s' % event_namespace)
|
||||
)
|
||||
return c
|
||||
@ -375,8 +370,8 @@ class _Generator(object):
|
||||
# to not use the name.
|
||||
if function_namespace == 'SendMessage':
|
||||
function_namespace = 'PassMessage'
|
||||
(c.Append('namespace %s {' % function_namespace)
|
||||
.Append()
|
||||
(c.Append('namespace %s {' % function_namespace) \
|
||||
.Append() \
|
||||
.Cblock(self._GenerateFunctionParams(function))
|
||||
)
|
||||
if function.returns_async:
|
||||
@ -394,26 +389,26 @@ class _Generator(object):
|
||||
(c.Sblock('struct Params {'))
|
||||
if self._generate_error_messages:
|
||||
(c.Append('static base::expected<Params, std::u16string> '
|
||||
'Create(const base::Value::List& args);')
|
||||
'Create(const base::Value::List& args);') \
|
||||
.Comment('DEPRECATED: prefer the variant of this function '
|
||||
'returning errors with `base::expected`.')
|
||||
)
|
||||
|
||||
(c.Append('static std::optional<Params> Create(%s);' %
|
||||
self._GenerateParams(
|
||||
('const base::Value::List& args',)))
|
||||
.Append('Params(const Params&) = delete;')
|
||||
.Append('Params& operator=(const Params&) = delete;')
|
||||
.Append('Params(Params&& rhs) noexcept;')
|
||||
.Append('Params& operator=(Params&& rhs) noexcept;')
|
||||
.Append('~Params();')
|
||||
.Append()
|
||||
.Cblock(self._GenerateTypes(p.type_ for p in function.params))
|
||||
.Cblock(self._GenerateFields(function.params))
|
||||
.Eblock()
|
||||
.Append()
|
||||
.Sblock(' private:')
|
||||
.Append('Params();')
|
||||
('const base::Value::List& args',))) \
|
||||
.Append('Params(const Params&) = delete;') \
|
||||
.Append('Params& operator=(const Params&) = delete;') \
|
||||
.Append('Params(Params&& rhs) noexcept;') \
|
||||
.Append('Params& operator=(Params&& rhs) noexcept;') \
|
||||
.Append('~Params();') \
|
||||
.Append() \
|
||||
.Cblock(self._GenerateTypes(p.type_ for p in function.params)) \
|
||||
.Cblock(self._GenerateFields(function.params)) \
|
||||
.Eblock() \
|
||||
.Append() \
|
||||
.Sblock(' private:') \
|
||||
.Append('Params();') \
|
||||
.Eblock('};')
|
||||
)
|
||||
return c
|
||||
@ -424,9 +419,10 @@ class _Generator(object):
|
||||
"""
|
||||
c = Code()
|
||||
for type_ in types:
|
||||
c.Cblock(self._GenerateType(type_,
|
||||
is_toplevel=is_toplevel,
|
||||
generate_typedefs=generate_typedefs))
|
||||
c.Cblock(
|
||||
self._GenerateType(type_,
|
||||
is_toplevel=is_toplevel,
|
||||
generate_typedefs=generate_typedefs))
|
||||
return c
|
||||
|
||||
def _GenerateManifestKeys(self):
|
||||
@ -446,26 +442,23 @@ class _Generator(object):
|
||||
# manifest types.
|
||||
if type_.IsRootManifestKeyType():
|
||||
params = [
|
||||
'const base::Value::Dict& root_dict',
|
||||
'%s& out' % classname,
|
||||
'std::u16string& error'
|
||||
'const base::Value::Dict& root_dict',
|
||||
'%s& out' % classname, 'std::u16string& error'
|
||||
]
|
||||
comment = (
|
||||
'Parses manifest keys for this namespace. Any keys not available to the'
|
||||
' manifest will be ignored. On a parsing error, false is returned and '
|
||||
'|error| is populated.')
|
||||
'Parses manifest keys for this namespace. Any keys not available to'
|
||||
' the manifest will be ignored. On a parsing error, false is returned'
|
||||
' and |error| is populated.')
|
||||
else:
|
||||
params = [
|
||||
'const base::Value::Dict& root_dict',
|
||||
'std::string_view key',
|
||||
'%s& out' % classname,
|
||||
'std::u16string& error',
|
||||
'std::vector<std::string_view>& error_path_reversed'
|
||||
'const base::Value::Dict& root_dict', 'std::string_view key',
|
||||
'%s& out' % classname, 'std::u16string& error',
|
||||
'std::vector<std::string_view>& error_path_reversed'
|
||||
]
|
||||
comment = (
|
||||
'Parses the given |key| from |root_dict|. Any keys not available to the'
|
||||
' manifest will be ignored. On a parsing error, false is returned and '
|
||||
'|error| and |error_path_reversed| are populated.')
|
||||
'Parses the given |key| from |root_dict|. Any keys not available to'
|
||||
' the manifest will be ignored. On a parsing error, false is returned'
|
||||
' and |error| and |error_path_reversed| are populated.')
|
||||
|
||||
c = Code()
|
||||
c.Append().Comment(comment)
|
||||
@ -499,18 +492,18 @@ class _Generator(object):
|
||||
for param in params:
|
||||
if param.description:
|
||||
c.Comment(param.description)
|
||||
declaration_list.append(cpp_util.GetParameterDeclaration(
|
||||
param, self._type_helper.GetCppType(param.type_)))
|
||||
c.Append('base::Value::List Create(%s);' %
|
||||
', '.join(declaration_list))
|
||||
declaration_list.append(
|
||||
cpp_util.GetParameterDeclaration(
|
||||
param, self._type_helper.GetCppType(param.type_)))
|
||||
c.Append('base::Value::List Create(%s);' % ', '.join(declaration_list))
|
||||
return c
|
||||
|
||||
def _GenerateEventNameConstant(self, event):
|
||||
"""Generates a constant string array for the event name.
|
||||
"""
|
||||
c = Code()
|
||||
c.Append('extern const char kEventName[]; // "%s.%s"' % (
|
||||
self._namespace.name, event.name))
|
||||
c.Append('extern const char kEventName[]; // "%s.%s"' %
|
||||
(self._namespace.name, event.name))
|
||||
c.Append()
|
||||
return c
|
||||
|
||||
@ -518,15 +511,14 @@ class _Generator(object):
|
||||
"""Generates namespace for passing a function's result back.
|
||||
"""
|
||||
c = Code()
|
||||
(c.Append('namespace Results {')
|
||||
.Append()
|
||||
.Concat(self._GenerateAsyncResponseArguments(returns_async.params))
|
||||
(c.Append('namespace Results {') \
|
||||
.Append() \
|
||||
.Concat(self._GenerateAsyncResponseArguments(returns_async.params)) \
|
||||
.Append('} // namespace Results')
|
||||
)
|
||||
return c
|
||||
|
||||
def _GenerateParams(
|
||||
self, params, generate_error_messages=None):
|
||||
def _GenerateParams(self, params, generate_error_messages=None):
|
||||
"""Builds the parameter list for a function, given an array of parameters.
|
||||
If |generate_error_messages| is specified, it overrides
|
||||
|self._generate_error_messages|.
|
||||
@ -539,5 +531,5 @@ class _Generator(object):
|
||||
if generate_error_messages is None:
|
||||
generate_error_messages = self._generate_error_messages
|
||||
if generate_error_messages:
|
||||
params += ('std::u16string& error',)
|
||||
params += ('std::u16string& error', )
|
||||
return ', '.join(str(p) for p in params)
|
||||
|
@ -32,6 +32,7 @@ else:
|
||||
finally:
|
||||
sys.path.pop(0)
|
||||
|
||||
|
||||
def ProcessComment(comment):
|
||||
'''
|
||||
Convert a comment into a parent comment and a list of parameter comments.
|
||||
@ -57,6 +58,7 @@ def ProcessComment(comment):
|
||||
}
|
||||
)
|
||||
'''
|
||||
|
||||
def add_paragraphs(content):
|
||||
paragraphs = content.split('\n\n')
|
||||
if len(paragraphs) < 2:
|
||||
@ -69,8 +71,8 @@ def ProcessComment(comment):
|
||||
# Get the parent comment (everything before the first parameter comment.
|
||||
first_parameter_location = (parameter_starts[0].start()
|
||||
if parameter_starts else len(comment))
|
||||
parent_comment = (add_paragraphs(comment[:first_parameter_location].strip())
|
||||
.replace('\n', ''))
|
||||
parent_comment = (add_paragraphs(
|
||||
comment[:first_parameter_location].strip()).replace('\n', ''))
|
||||
|
||||
params = OrderedDict()
|
||||
for (cur_param, next_param) in itertools.zip_longest(parameter_starts,
|
||||
@ -81,9 +83,9 @@ def ProcessComment(comment):
|
||||
# beginning of the next parameter's introduction.
|
||||
param_comment_start = cur_param.end()
|
||||
param_comment_end = next_param.start() if next_param else len(comment)
|
||||
params[param_name] = (
|
||||
add_paragraphs(comment[param_comment_start:param_comment_end].strip())
|
||||
.replace('\n', ''))
|
||||
params[param_name] = (add_paragraphs(
|
||||
comment[param_comment_start:param_comment_end].strip()).replace(
|
||||
'\n', ''))
|
||||
|
||||
return (parent_comment, params)
|
||||
|
||||
@ -94,6 +96,7 @@ class Callspec(object):
|
||||
a tuple:
|
||||
(name, list of function parameters, return type, async return)
|
||||
'''
|
||||
|
||||
def __init__(self, callspec_node, comment):
|
||||
self.node = callspec_node
|
||||
self.comment = comment
|
||||
@ -103,9 +106,10 @@ class Callspec(object):
|
||||
return_type = None
|
||||
returns_async = None
|
||||
if self.node.GetProperty('TYPEREF') not in ('void', None):
|
||||
return_type = Typeref(self.node.GetProperty('TYPEREF'),
|
||||
self.node.parent,
|
||||
{'name': self.node.GetName()}).process(callbacks)
|
||||
return_type = Typeref(self.node.GetProperty('TYPEREF'), self.node.parent,
|
||||
{
|
||||
'name': self.node.GetName()
|
||||
}).process(callbacks)
|
||||
# The IDL parser doesn't allow specifying return types as optional.
|
||||
# Instead we infer any object return values to be optional.
|
||||
# TODO(asargent): fix the IDL parser to support optional return types.
|
||||
@ -127,32 +131,25 @@ class Callspec(object):
|
||||
# trailingCallbackIsFunctionParameter extended attribute).
|
||||
# TODO(tjudkins): Once IDL definitions are changed to describe returning
|
||||
# promises, we can condition on that instead.
|
||||
if (
|
||||
use_returns_async
|
||||
if (use_returns_async
|
||||
and not self.node.GetProperty('trailingCallbackIsFunctionParameter')
|
||||
and len(parameters) > 0
|
||||
and parameters[-1].get('type') == 'function'
|
||||
):
|
||||
and len(parameters) > 0 and parameters[-1].get('type') == 'function'):
|
||||
returns_async = parameters.pop()
|
||||
# The returns_async field is inherently a function, so doesn't need type
|
||||
# specified on it.
|
||||
returns_async.pop('type')
|
||||
does_not_support_promises = self.node.GetProperty(
|
||||
'doesNotSupportPromises'
|
||||
)
|
||||
'doesNotSupportPromises')
|
||||
if does_not_support_promises is not None:
|
||||
returns_async['does_not_support_promises'] = does_not_support_promises
|
||||
else:
|
||||
assert return_type is None, (
|
||||
'Function "%s" cannot support promises and also have a '
|
||||
'return value.' % self.node.GetName()
|
||||
)
|
||||
'return value.' % self.node.GetName())
|
||||
else:
|
||||
assert not self.node.GetProperty('doesNotSupportPromises'), (
|
||||
'Callspec "%s" does not need to specify [doesNotSupportPromises] if '
|
||||
'it does not have a trailing callback'
|
||||
% self.node.GetName()
|
||||
)
|
||||
'it does not have a trailing callback' % self.node.GetName())
|
||||
|
||||
return (self.node.GetName(), parameters, return_type, returns_async)
|
||||
|
||||
@ -162,13 +159,14 @@ class Param(object):
|
||||
Given a Param node representing a function parameter, converts into a Python
|
||||
dictionary that the JSON schema compiler expects to see.
|
||||
'''
|
||||
|
||||
def __init__(self, param_node):
|
||||
self.node = param_node
|
||||
|
||||
def process(self, callbacks):
|
||||
return Typeref(self.node.GetProperty('TYPEREF'),
|
||||
self.node,
|
||||
{'name': self.node.GetName()}).process(callbacks)
|
||||
return Typeref(self.node.GetProperty('TYPEREF'), self.node, {
|
||||
'name': self.node.GetName()
|
||||
}).process(callbacks)
|
||||
|
||||
|
||||
class Dictionary(object):
|
||||
@ -176,6 +174,7 @@ class Dictionary(object):
|
||||
Given an IDL Dictionary node, converts into a Python dictionary that the JSON
|
||||
schema compiler expects to see.
|
||||
'''
|
||||
|
||||
def __init__(self, dictionary_node):
|
||||
self.node = dictionary_node
|
||||
|
||||
@ -185,9 +184,11 @@ class Dictionary(object):
|
||||
if node.cls == 'Member':
|
||||
k, v = Member(node).process(callbacks)
|
||||
properties[k] = v
|
||||
result = {'id': self.node.GetName(),
|
||||
'properties': properties,
|
||||
'type': 'object'}
|
||||
result = {
|
||||
'id': self.node.GetName(),
|
||||
'properties': properties,
|
||||
'type': 'object'
|
||||
}
|
||||
# If this has the `ignoreAdditionalProperties` extended attribute, copy it
|
||||
# into the resulting object with a value of True.
|
||||
if self.node.GetProperty('ignoreAdditionalProperties'):
|
||||
@ -208,19 +209,22 @@ class Member(object):
|
||||
where the value is a Python dictionary that the JSON schema compiler expects
|
||||
to see.
|
||||
'''
|
||||
|
||||
def __init__(self, member_node):
|
||||
self.node = member_node
|
||||
|
||||
def process(
|
||||
self, callbacks, functions_are_properties=False, use_returns_async=False
|
||||
):
|
||||
def process(self,
|
||||
callbacks,
|
||||
functions_are_properties=False,
|
||||
use_returns_async=False):
|
||||
properties = OrderedDict()
|
||||
name = self.node.GetName()
|
||||
if self.node.GetProperty('deprecated'):
|
||||
properties['deprecated'] = self.node.GetProperty('deprecated')
|
||||
|
||||
for property_name in ['nodoc', 'nocompile', 'nodart',
|
||||
'serializableFunction']:
|
||||
for property_name in [
|
||||
'nodoc', 'nocompile', 'nodart', 'serializableFunction'
|
||||
]:
|
||||
if self.node.GetProperty(property_name):
|
||||
properties[property_name] = True
|
||||
|
||||
@ -230,27 +234,24 @@ class Member(object):
|
||||
if self.node.GetProperty('platforms'):
|
||||
properties['platforms'] = list(self.node.GetProperty('platforms'))
|
||||
|
||||
for option_name, sanitizer in [
|
||||
('maxListeners', int),
|
||||
('supportsFilters', lambda s: s == 'true'),
|
||||
('supportsListeners', lambda s: s == 'true'),
|
||||
('supportsRules', lambda s: s == 'true')]:
|
||||
for option_name, sanitizer in [('maxListeners', int),
|
||||
('supportsFilters', lambda s: s == 'true'),
|
||||
('supportsListeners', lambda s: s == 'true'),
|
||||
('supportsRules', lambda s: s == 'true')]:
|
||||
if self.node.GetProperty(option_name):
|
||||
if 'options' not in properties:
|
||||
properties['options'] = {}
|
||||
properties['options'][option_name] = sanitizer(self.node.GetProperty(
|
||||
option_name))
|
||||
properties['options'][option_name] = sanitizer(
|
||||
self.node.GetProperty(option_name))
|
||||
type_override = None
|
||||
parameter_comments = OrderedDict()
|
||||
for node in self.node.GetChildren():
|
||||
if node.cls == 'Comment':
|
||||
(parent_comment, parameter_comments) = ProcessComment(
|
||||
node.GetName())
|
||||
(parent_comment, parameter_comments) = ProcessComment(node.GetName())
|
||||
properties['description'] = parent_comment
|
||||
elif node.cls == 'Callspec':
|
||||
name, parameters, return_type, returns_async = Callspec(
|
||||
node, parameter_comments
|
||||
).process(use_returns_async, callbacks)
|
||||
node, parameter_comments).process(use_returns_async, callbacks)
|
||||
if functions_are_properties:
|
||||
# If functions are treated as properties (which will happen if the
|
||||
# interface is named Properties) then this isn't a function, it's a
|
||||
@ -258,14 +259,14 @@ class Member(object):
|
||||
# property type is the return type. This is an egregious hack in lieu
|
||||
# of the IDL parser supporting 'const'.
|
||||
assert parameters == [], (
|
||||
'Property "%s" must be no-argument functions '
|
||||
'with a non-void return type' % name)
|
||||
'Property "%s" must be no-argument functions '
|
||||
'with a non-void return type' % name)
|
||||
assert return_type is not None, (
|
||||
'Property "%s" must be no-argument functions '
|
||||
'with a non-void return type' % name)
|
||||
'Property "%s" must be no-argument functions '
|
||||
'with a non-void return type' % name)
|
||||
assert 'type' in return_type, (
|
||||
'Property return type "%s" from "%s" must specify a '
|
||||
'fundamental IDL type.' % (pprint.pformat(return_type), name))
|
||||
'Property return type "%s" from "%s" must specify a '
|
||||
'fundamental IDL type.' % (pprint.pformat(return_type), name))
|
||||
type_override = return_type['type']
|
||||
else:
|
||||
type_override = 'function'
|
||||
@ -279,8 +280,8 @@ class Member(object):
|
||||
if type_override is not None:
|
||||
properties['type'] = type_override
|
||||
else:
|
||||
properties = Typeref(self.node.GetProperty('TYPEREF'),
|
||||
self.node, properties).process(callbacks)
|
||||
properties = Typeref(self.node.GetProperty('TYPEREF'), self.node,
|
||||
properties).process(callbacks)
|
||||
value = self.node.GetProperty('value')
|
||||
if value is not None:
|
||||
# IDL always returns values as strings, so cast to their real type.
|
||||
@ -298,9 +299,9 @@ class Member(object):
|
||||
return float(string_value)
|
||||
# Add more as necessary.
|
||||
assert json_type == 'string', (
|
||||
'No rule exists to cast JSON Schema type "%s" to its equivalent '
|
||||
'Python type for value "%s". You must add a new rule here.' %
|
||||
(json_type, string_value))
|
||||
'No rule exists to cast JSON Schema type "%s" to its equivalent '
|
||||
'Python type for value "%s". You must add a new rule here.' %
|
||||
(json_type, string_value))
|
||||
return string_value
|
||||
|
||||
|
||||
@ -310,6 +311,7 @@ class Typeref(object):
|
||||
function parameter, converts into a Python dictionary that the JSON schema
|
||||
compiler expects to see.
|
||||
'''
|
||||
|
||||
def __init__(self, typeref, parent, additional_properties):
|
||||
self.typeref = typeref
|
||||
self.parent = parent
|
||||
@ -363,11 +365,11 @@ class Typeref(object):
|
||||
properties['additionalProperties'] = OrderedDict()
|
||||
properties['additionalProperties']['type'] = 'any'
|
||||
elif self.parent.GetPropertyLocal('Union'):
|
||||
properties['choices'] = [Typeref(node.GetProperty('TYPEREF'),
|
||||
node,
|
||||
OrderedDict()).process(callbacks)
|
||||
for node in self.parent.GetChildren()
|
||||
if node.cls == 'Option']
|
||||
properties['choices'] = [
|
||||
Typeref(node.GetProperty('TYPEREF'), node,
|
||||
OrderedDict()).process(callbacks)
|
||||
for node in self.parent.GetChildren() if node.cls == 'Option'
|
||||
]
|
||||
elif self.typeref is None:
|
||||
properties['type'] = 'function'
|
||||
else:
|
||||
@ -390,6 +392,7 @@ class Enum(object):
|
||||
Given an IDL Enum node, converts into a Python dictionary that the JSON
|
||||
schema compiler expects to see.
|
||||
'''
|
||||
|
||||
def __init__(self, enum_node):
|
||||
self.node = enum_node
|
||||
self.description = ''
|
||||
@ -411,12 +414,15 @@ class Enum(object):
|
||||
self.description = ProcessComment(node.GetName())[0]
|
||||
else:
|
||||
sys.exit('Did not process %s %s' % (node.cls, node))
|
||||
result = {'id' : self.node.GetName(),
|
||||
'description': self.description,
|
||||
'type': 'string',
|
||||
'enum': enum}
|
||||
for property_name in ['cpp_enum_prefix_override', 'inline_doc',
|
||||
'noinline_doc', 'nodoc']:
|
||||
result = {
|
||||
'id': self.node.GetName(),
|
||||
'description': self.description,
|
||||
'type': 'string',
|
||||
'enum': enum
|
||||
}
|
||||
for property_name in [
|
||||
'cpp_enum_prefix_override', 'inline_doc', 'noinline_doc', 'nodoc'
|
||||
]:
|
||||
if self.node.GetProperty(property_name):
|
||||
result[property_name] = self.node.GetProperty(property_name)
|
||||
if self.node.GetProperty('deprecated'):
|
||||
@ -485,19 +491,19 @@ class Namespace(object):
|
||||
compiler_options = self.compiler_options or {}
|
||||
documentation_options = self.documentation_options or {}
|
||||
return {
|
||||
'namespace': self.namespace.GetName(),
|
||||
'description': self.description,
|
||||
'nodoc': self.nodoc,
|
||||
'types': self.types,
|
||||
'functions': self.functions,
|
||||
'properties': self.properties,
|
||||
'manifest_keys': self.manifest_keys,
|
||||
'internal': self.internal,
|
||||
'events': self.events,
|
||||
'platforms': self.platforms,
|
||||
'compiler_options': compiler_options,
|
||||
'deprecated': self.deprecated,
|
||||
'documentation_options': documentation_options
|
||||
'namespace': self.namespace.GetName(),
|
||||
'description': self.description,
|
||||
'nodoc': self.nodoc,
|
||||
'types': self.types,
|
||||
'functions': self.functions,
|
||||
'properties': self.properties,
|
||||
'manifest_keys': self.manifest_keys,
|
||||
'internal': self.internal,
|
||||
'events': self.events,
|
||||
'platforms': self.platforms,
|
||||
'compiler_options': compiler_options,
|
||||
'deprecated': self.deprecated,
|
||||
'documentation_options': documentation_options
|
||||
}
|
||||
|
||||
def process_interface(self, node, functions_are_properties=False):
|
||||
@ -542,9 +548,12 @@ class IDLSchema(object):
|
||||
if not description:
|
||||
# TODO(kalman): Go back to throwing an error here.
|
||||
print('%s must have a namespace-level comment. This will '
|
||||
'appear on the API summary page.' % node.GetName())
|
||||
'appear on the API summary page.' % node.GetName())
|
||||
description = ''
|
||||
namespace = Namespace(node, description, nodoc, internal,
|
||||
namespace = Namespace(node,
|
||||
description,
|
||||
nodoc,
|
||||
internal,
|
||||
platforms=platforms,
|
||||
compiler_options=compiler_options or None,
|
||||
deprecated=deprecated,
|
||||
@ -621,8 +630,9 @@ def Main():
|
||||
contents = sys.stdin.read()
|
||||
for i, char in enumerate(contents):
|
||||
if not char.isascii():
|
||||
raise Exception('Non-ascii character "%s" (ord %d) found at offset %d.'
|
||||
% (char, ord(char), i))
|
||||
raise Exception(
|
||||
'Non-ascii character "%s" (ord %d) found at offset %d.' %
|
||||
(char, ord(char), i))
|
||||
idl = idl_parser.IDLParser().ParseData(contents, '<stdin>')
|
||||
schema = IDLSchema(idl).process()
|
||||
print(json.dumps(schema, indent=2))
|
||||
|
@ -8,6 +8,7 @@ import unittest
|
||||
|
||||
from json_parse import OrderedDict
|
||||
|
||||
|
||||
def getFunction(schema, name):
|
||||
for item in schema['functions']:
|
||||
if item['name'] == name:
|
||||
@ -19,10 +20,12 @@ def getParams(schema, name):
|
||||
function = getFunction(schema, name)
|
||||
return function['parameters']
|
||||
|
||||
|
||||
def getReturnsAsync(schema, name):
|
||||
function = getFunction(schema, name)
|
||||
return function.get('returns_async', False)
|
||||
|
||||
|
||||
def getReturns(schema, name):
|
||||
function = getFunction(schema, name)
|
||||
return function['returns']
|
||||
@ -35,6 +38,7 @@ def getType(schema, id):
|
||||
|
||||
|
||||
class IdlSchemaTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
loaded = idl_schema.Load('test/idl_basics.idl')
|
||||
self.assertEqual(1, len(loaded))
|
||||
@ -44,66 +48,123 @@ class IdlSchemaTest(unittest.TestCase):
|
||||
|
||||
def testSimpleCallbacks(self):
|
||||
schema = self.idl_basics
|
||||
expected = {'name': 'cb', 'parameters':[]}
|
||||
expected = {'name': 'cb', 'parameters': []}
|
||||
self.assertEqual(expected, getReturnsAsync(schema, 'function4'))
|
||||
|
||||
expected = {'name': 'cb',
|
||||
'parameters':[{'name': 'x', 'type': 'integer'}]}
|
||||
expected = {'name': 'cb', 'parameters': [{'name': 'x', 'type': 'integer'}]}
|
||||
self.assertEqual(expected, getReturnsAsync(schema, 'function5'))
|
||||
|
||||
expected = {'name': 'cb',
|
||||
'parameters':[{'name': 'arg', '$ref': 'MyType1'}]}
|
||||
expected = {
|
||||
'name': 'cb',
|
||||
'parameters': [{
|
||||
'name': 'arg',
|
||||
'$ref': 'MyType1'
|
||||
}]
|
||||
}
|
||||
self.assertEqual(expected, getReturnsAsync(schema, 'function6'))
|
||||
|
||||
def testCallbackWithArrayArgument(self):
|
||||
schema = self.idl_basics
|
||||
expected = {'name': 'cb',
|
||||
'parameters':[{'name': 'arg', 'type': 'array',
|
||||
'items':{'$ref': 'MyType2'}}]}
|
||||
expected = {
|
||||
'name':
|
||||
'cb',
|
||||
'parameters': [{
|
||||
'name': 'arg',
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'$ref': 'MyType2'
|
||||
}
|
||||
}]
|
||||
}
|
||||
self.assertEqual(expected, getReturnsAsync(schema, 'function12'))
|
||||
|
||||
def testArrayOfCallbacks(self):
|
||||
schema = idl_schema.Load('test/idl_function_types.idl')[0]
|
||||
expected = [{'type': 'array', 'name': 'callbacks',
|
||||
'items':{'type': 'function', 'name': 'MyCallback',
|
||||
'parameters':[{'type': 'integer', 'name': 'x'}]}}]
|
||||
expected = [{
|
||||
'type': 'array',
|
||||
'name': 'callbacks',
|
||||
'items': {
|
||||
'type': 'function',
|
||||
'name': 'MyCallback',
|
||||
'parameters': [{
|
||||
'type': 'integer',
|
||||
'name': 'x'
|
||||
}]
|
||||
}
|
||||
}]
|
||||
self.assertEqual(expected, getParams(schema, 'whatever'))
|
||||
|
||||
def testProperties(self):
|
||||
self.assertEqual({
|
||||
'x': {'name': 'x', 'type': 'integer',
|
||||
'description': 'This comment tests "double-quotes".'},
|
||||
'y': {'name': 'y', 'type': 'string'},
|
||||
'z': {'name': 'z', 'type': 'string'},
|
||||
'a': {'name': 'a', 'type': 'string'},
|
||||
'b': {'name': 'b', 'type': 'string'},
|
||||
'c': {'name': 'c', 'type': 'string'}},
|
||||
getType(self.idl_basics, 'MyType1')['properties'])
|
||||
self.assertEqual(
|
||||
{
|
||||
'x': {
|
||||
'name': 'x',
|
||||
'type': 'integer',
|
||||
'description': 'This comment tests "double-quotes".'
|
||||
},
|
||||
'y': {
|
||||
'name': 'y',
|
||||
'type': 'string'
|
||||
},
|
||||
'z': {
|
||||
'name': 'z',
|
||||
'type': 'string'
|
||||
},
|
||||
'a': {
|
||||
'name': 'a',
|
||||
'type': 'string'
|
||||
},
|
||||
'b': {
|
||||
'name': 'b',
|
||||
'type': 'string'
|
||||
},
|
||||
'c': {
|
||||
'name': 'c',
|
||||
'type': 'string'
|
||||
}
|
||||
},
|
||||
getType(self.idl_basics, 'MyType1')['properties'])
|
||||
|
||||
def testMemberOrdering(self):
|
||||
self.assertEqual(
|
||||
['x', 'y', 'z', 'a', 'b', 'c'],
|
||||
list(getType(self.idl_basics, 'MyType1')['properties'].keys()))
|
||||
self.assertEqual(['x', 'y', 'z', 'a', 'b', 'c'],
|
||||
list(
|
||||
getType(self.idl_basics,
|
||||
'MyType1')['properties'].keys()))
|
||||
|
||||
def testEnum(self):
|
||||
schema = self.idl_basics
|
||||
expected = {'enum': [{'name': 'name1', 'description': 'comment1'},
|
||||
{'name': 'name2'}],
|
||||
'description': 'Enum description',
|
||||
'type': 'string', 'id': 'EnumType'}
|
||||
expected = {
|
||||
'enum': [{
|
||||
'name': 'name1',
|
||||
'description': 'comment1'
|
||||
}, {
|
||||
'name': 'name2'
|
||||
}],
|
||||
'description': 'Enum description',
|
||||
'type': 'string',
|
||||
'id': 'EnumType'
|
||||
}
|
||||
self.assertEqual(expected, getType(schema, expected['id']))
|
||||
|
||||
expected_params = [{'name': 'type', '$ref': 'EnumType'}]
|
||||
expected_returns_async = {
|
||||
'name': 'cb',
|
||||
'parameters':[{'name': 'type', '$ref': 'EnumType'}]}
|
||||
'parameters': [{
|
||||
'name': 'type',
|
||||
'$ref': 'EnumType'
|
||||
}]
|
||||
}
|
||||
self.assertEqual(expected_params, getParams(schema, 'function13'))
|
||||
self.assertEqual(
|
||||
expected_returns_async, getReturnsAsync(schema, 'function13')
|
||||
)
|
||||
self.assertEqual(expected_returns_async,
|
||||
getReturnsAsync(schema, 'function13'))
|
||||
|
||||
expected = [{'items': {'$ref': 'EnumType'}, 'name': 'types',
|
||||
'type': 'array'}]
|
||||
expected = [{
|
||||
'items': {
|
||||
'$ref': 'EnumType'
|
||||
},
|
||||
'name': 'types',
|
||||
'type': 'array'
|
||||
}]
|
||||
self.assertEqual(expected, getParams(schema, 'function14'))
|
||||
|
||||
def testScopedArguments(self):
|
||||
@ -111,19 +172,29 @@ class IdlSchemaTest(unittest.TestCase):
|
||||
expected = [{'name': 'value', '$ref': 'idl_other_namespace.SomeType'}]
|
||||
self.assertEqual(expected, getParams(schema, 'function20'))
|
||||
|
||||
expected = [{'items': {'$ref': 'idl_other_namespace.SomeType'},
|
||||
'name': 'values',
|
||||
'type': 'array'}]
|
||||
expected = [{
|
||||
'items': {
|
||||
'$ref': 'idl_other_namespace.SomeType'
|
||||
},
|
||||
'name': 'values',
|
||||
'type': 'array'
|
||||
}]
|
||||
self.assertEqual(expected, getParams(schema, 'function21'))
|
||||
|
||||
expected = [{'name': 'value',
|
||||
'$ref': 'idl_other_namespace.sub_namespace.AnotherType'}]
|
||||
expected = [{
|
||||
'name': 'value',
|
||||
'$ref': 'idl_other_namespace.sub_namespace.AnotherType'
|
||||
}]
|
||||
self.assertEqual(expected, getParams(schema, 'function22'))
|
||||
|
||||
expected = [{'items': {'$ref': 'idl_other_namespace.sub_namespace.'
|
||||
'AnotherType'},
|
||||
'name': 'values',
|
||||
'type': 'array'}]
|
||||
expected = [{
|
||||
'items': {
|
||||
'$ref': 'idl_other_namespace.sub_namespace.'
|
||||
'AnotherType'
|
||||
},
|
||||
'name': 'values',
|
||||
'type': 'array'
|
||||
}]
|
||||
self.assertEqual(expected, getParams(schema, 'function23'))
|
||||
|
||||
def testNoCompile(self):
|
||||
@ -151,45 +222,73 @@ class IdlSchemaTest(unittest.TestCase):
|
||||
'name': 'name3',
|
||||
'description': 'comment3'
|
||||
}],
|
||||
'type': 'string',
|
||||
'id': 'EnumTypeWithNoDocValue',
|
||||
'description': ''
|
||||
'type':
|
||||
'string',
|
||||
'id':
|
||||
'EnumTypeWithNoDocValue',
|
||||
'description':
|
||||
''
|
||||
}
|
||||
self.assertEqual(expected, getType(schema, expected['id']))
|
||||
|
||||
def testInternalNamespace(self):
|
||||
idl_basics = self.idl_basics
|
||||
idl_basics = self.idl_basics
|
||||
self.assertEqual('idl_basics', idl_basics['namespace'])
|
||||
self.assertTrue(idl_basics['internal'])
|
||||
self.assertFalse(idl_basics['nodoc'])
|
||||
|
||||
def testReturnTypes(self):
|
||||
schema = self.idl_basics
|
||||
self.assertEqual({'name': 'function24', 'type': 'integer'},
|
||||
getReturns(schema, 'function24'))
|
||||
self.assertEqual({'name': 'function25', '$ref': 'MyType1',
|
||||
'optional': True},
|
||||
getReturns(schema, 'function25'))
|
||||
self.assertEqual({'name': 'function26', 'type': 'array',
|
||||
'items': {'$ref': 'MyType1'}},
|
||||
getReturns(schema, 'function26'))
|
||||
self.assertEqual({'name': 'function27', '$ref': 'EnumType',
|
||||
'optional': True},
|
||||
getReturns(schema, 'function27'))
|
||||
self.assertEqual({'name': 'function28', 'type': 'array',
|
||||
'items': {'$ref': 'EnumType'}},
|
||||
getReturns(schema, 'function28'))
|
||||
self.assertEqual({'name': 'function29', '$ref':
|
||||
'idl_other_namespace.SomeType',
|
||||
'optional': True},
|
||||
getReturns(schema, 'function29'))
|
||||
self.assertEqual({'name': 'function30', 'type': 'array',
|
||||
'items': {'$ref': 'idl_other_namespace.SomeType'}},
|
||||
getReturns(schema, 'function30'))
|
||||
self.assertEqual({
|
||||
'name': 'function24',
|
||||
'type': 'integer'
|
||||
}, getReturns(schema, 'function24'))
|
||||
self.assertEqual({
|
||||
'name': 'function25',
|
||||
'$ref': 'MyType1',
|
||||
'optional': True
|
||||
}, getReturns(schema, 'function25'))
|
||||
self.assertEqual(
|
||||
{
|
||||
'name': 'function26',
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'$ref': 'MyType1'
|
||||
}
|
||||
}, getReturns(schema, 'function26'))
|
||||
self.assertEqual(
|
||||
{
|
||||
'name': 'function27',
|
||||
'$ref': 'EnumType',
|
||||
'optional': True
|
||||
}, getReturns(schema, 'function27'))
|
||||
self.assertEqual(
|
||||
{
|
||||
'name': 'function28',
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'$ref': 'EnumType'
|
||||
}
|
||||
}, getReturns(schema, 'function28'))
|
||||
self.assertEqual(
|
||||
{
|
||||
'name': 'function29',
|
||||
'$ref': 'idl_other_namespace.SomeType',
|
||||
'optional': True
|
||||
}, getReturns(schema, 'function29'))
|
||||
self.assertEqual(
|
||||
{
|
||||
'name': 'function30',
|
||||
'type': 'array',
|
||||
'items': {
|
||||
'$ref': 'idl_other_namespace.SomeType'
|
||||
}
|
||||
}, getReturns(schema, 'function30'))
|
||||
|
||||
def testIgnoresAdditionalPropertiesOnType(self):
|
||||
self.assertTrue(getType(self.idl_basics, 'IgnoreAdditionalPropertiesType')
|
||||
['ignoreAdditionalProperties'])
|
||||
self.assertTrue(
|
||||
getType(self.idl_basics,
|
||||
'IgnoreAdditionalPropertiesType')['ignoreAdditionalProperties'])
|
||||
|
||||
def testChromeOSPlatformsNamespace(self):
|
||||
schema = idl_schema.Load('test/idl_namespace_chromeos.idl')[0]
|
||||
@ -206,7 +305,7 @@ class IdlSchemaTest(unittest.TestCase):
|
||||
def testNonSpecificPlatformsNamespace(self):
|
||||
schema = idl_schema.Load('test/idl_namespace_non_specific_platforms.idl')[0]
|
||||
self.assertEqual('idl_namespace_non_specific_platforms',
|
||||
schema['namespace'])
|
||||
schema['namespace'])
|
||||
expected = None
|
||||
self.assertEqual(expected, schema['platforms'])
|
||||
|
||||
@ -214,17 +313,16 @@ class IdlSchemaTest(unittest.TestCase):
|
||||
schema = idl_schema.Load('test/idl_generate_error_messages.idl')[0]
|
||||
self.assertEqual('idl_generate_error_messages', schema['namespace'])
|
||||
self.assertTrue(schema['compiler_options'].get('generate_error_messages',
|
||||
False))
|
||||
False))
|
||||
|
||||
schema = idl_schema.Load('test/idl_basics.idl')[0]
|
||||
self.assertEqual('idl_basics', schema['namespace'])
|
||||
self.assertFalse(schema['compiler_options'].get('generate_error_messages',
|
||||
False))
|
||||
False))
|
||||
|
||||
def testSpecificImplementNamespace(self):
|
||||
schema = idl_schema.Load('test/idl_namespace_specific_implement.idl')[0]
|
||||
self.assertEqual('idl_namespace_specific_implement',
|
||||
schema['namespace'])
|
||||
self.assertEqual('idl_namespace_specific_implement', schema['namespace'])
|
||||
expected = 'idl_namespace_specific_implement.idl'
|
||||
self.assertEqual(expected, schema['compiler_options']['implemented_in'])
|
||||
|
||||
@ -232,39 +330,39 @@ class IdlSchemaTest(unittest.TestCase):
|
||||
schema = idl_schema.Load(
|
||||
'test/idl_namespace_specific_implement_chromeos.idl')[0]
|
||||
self.assertEqual('idl_namespace_specific_implement_chromeos',
|
||||
schema['namespace'])
|
||||
schema['namespace'])
|
||||
expected_implemented_path = 'idl_namespace_specific_implement_chromeos.idl'
|
||||
expected_platform = ['chromeos']
|
||||
self.assertEqual(expected_implemented_path,
|
||||
schema['compiler_options']['implemented_in'])
|
||||
schema['compiler_options']['implemented_in'])
|
||||
self.assertEqual(expected_platform, schema['platforms'])
|
||||
|
||||
def testCallbackComment(self):
|
||||
schema = self.idl_basics
|
||||
self.assertEqual('A comment on a callback.',
|
||||
getReturnsAsync(schema, 'function16')['description'])
|
||||
getReturnsAsync(schema, 'function16')['description'])
|
||||
self.assertEqual(
|
||||
'A parameter.',
|
||||
getReturnsAsync(schema, 'function16')['parameters'][0]['description'])
|
||||
self.assertEqual(
|
||||
'Just a parameter comment, with no comment on the callback.',
|
||||
getReturnsAsync(schema, 'function17')['parameters'][0]['description'])
|
||||
self.assertEqual(
|
||||
'Override callback comment.',
|
||||
getReturnsAsync(schema, 'function18')['description'])
|
||||
self.assertEqual('Override callback comment.',
|
||||
getReturnsAsync(schema, 'function18')['description'])
|
||||
|
||||
def testFunctionComment(self):
|
||||
schema = self.idl_basics
|
||||
func = getFunction(schema, 'function3')
|
||||
self.assertEqual(('This comment should appear in the documentation, '
|
||||
'despite occupying multiple lines.'),
|
||||
func['description'])
|
||||
self.assertEqual(
|
||||
[{'description': ('So should this comment about the argument. '
|
||||
'<em>HTML</em> is fine too.'),
|
||||
'name': 'arg',
|
||||
'$ref': 'MyType1'}],
|
||||
func['parameters'])
|
||||
'despite occupying multiple lines.'), func['description'])
|
||||
self.assertEqual([{
|
||||
'description': ('So should this comment about the argument. '
|
||||
'<em>HTML</em> is fine too.'),
|
||||
'name':
|
||||
'arg',
|
||||
'$ref':
|
||||
'MyType1'
|
||||
}], func['parameters'])
|
||||
func = getFunction(schema, 'function4')
|
||||
self.assertEqual(
|
||||
'<p>This tests if "double-quotes" are escaped correctly.</p>'
|
||||
@ -275,12 +373,18 @@ class IdlSchemaTest(unittest.TestCase):
|
||||
schema = idl_schema.Load('test/idl_reserved_words.idl')[0]
|
||||
|
||||
foo_type = getType(schema, 'Foo')
|
||||
self.assertEqual([{'name': 'float'}, {'name': 'DOMString'}],
|
||||
foo_type['enum'])
|
||||
self.assertEqual([{
|
||||
'name': 'float'
|
||||
}, {
|
||||
'name': 'DOMString'
|
||||
}], foo_type['enum'])
|
||||
|
||||
enum_type = getType(schema, 'enum')
|
||||
self.assertEqual([{'name': 'callback'}, {'name': 'namespace'}],
|
||||
enum_type['enum'])
|
||||
self.assertEqual([{
|
||||
'name': 'callback'
|
||||
}, {
|
||||
'name': 'namespace'
|
||||
}], enum_type['enum'])
|
||||
|
||||
dictionary = getType(schema, 'dictionary')
|
||||
self.assertEqual('integer', dictionary['properties']['long']['type'])
|
||||
@ -300,12 +404,10 @@ class IdlSchemaTest(unittest.TestCase):
|
||||
self.assertEqual('integer', foo_type['properties']['x']['type'])
|
||||
self.assertEqual('object', foo_type['properties']['y']['type'])
|
||||
self.assertEqual(
|
||||
'any',
|
||||
foo_type['properties']['y']['additionalProperties']['type'])
|
||||
'any', foo_type['properties']['y']['additionalProperties']['type'])
|
||||
self.assertEqual('object', foo_type['properties']['z']['type'])
|
||||
self.assertEqual(
|
||||
'any',
|
||||
foo_type['properties']['z']['additionalProperties']['type'])
|
||||
'any', foo_type['properties']['z']['additionalProperties']['type'])
|
||||
self.assertEqual('Window', foo_type['properties']['z']['isInstanceOf'])
|
||||
|
||||
bar_type = getType(schema, 'BarType')
|
||||
@ -337,35 +439,48 @@ class IdlSchemaTest(unittest.TestCase):
|
||||
|
||||
union_type = getType(schema, 'UnionType')
|
||||
expected = {
|
||||
'type': 'object',
|
||||
'id': 'UnionType',
|
||||
'properties': {
|
||||
'x': {
|
||||
'name': 'x',
|
||||
'optional': True,
|
||||
'choices': [
|
||||
{'type': 'integer'},
|
||||
{'$ref': 'FooType'},
|
||||
]
|
||||
},
|
||||
'y': {
|
||||
'name': 'y',
|
||||
'choices': [
|
||||
{'type': 'string'},
|
||||
{'type': 'object',
|
||||
'additionalProperties': {'type': 'any'}}
|
||||
]
|
||||
},
|
||||
'z': {
|
||||
'name': 'z',
|
||||
'choices': [
|
||||
{'type': 'object', 'isInstanceOf': 'ImageData',
|
||||
'additionalProperties': {'type': 'any'}},
|
||||
{'type': 'integer'}
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
'type': 'object',
|
||||
'id': 'UnionType',
|
||||
'properties': {
|
||||
'x': {
|
||||
'name': 'x',
|
||||
'optional': True,
|
||||
'choices': [
|
||||
{
|
||||
'type': 'integer'
|
||||
},
|
||||
{
|
||||
'$ref': 'FooType'
|
||||
},
|
||||
]
|
||||
},
|
||||
'y': {
|
||||
'name':
|
||||
'y',
|
||||
'choices': [{
|
||||
'type': 'string'
|
||||
}, {
|
||||
'type': 'object',
|
||||
'additionalProperties': {
|
||||
'type': 'any'
|
||||
}
|
||||
}]
|
||||
},
|
||||
'z': {
|
||||
'name':
|
||||
'z',
|
||||
'choices': [{
|
||||
'type': 'object',
|
||||
'isInstanceOf': 'ImageData',
|
||||
'additionalProperties': {
|
||||
'type': 'any'
|
||||
}
|
||||
}, {
|
||||
'type': 'integer'
|
||||
}]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
self.assertEqual(expected, union_type)
|
||||
|
||||
@ -374,19 +489,20 @@ class IdlSchemaTest(unittest.TestCase):
|
||||
|
||||
union_type = getType(schema, 'ModifiedUnionType')
|
||||
expected = {
|
||||
'type': 'object',
|
||||
'id': 'ModifiedUnionType',
|
||||
'properties': {
|
||||
'x': {
|
||||
'name': 'x',
|
||||
'nodoc': True,
|
||||
'choices': [
|
||||
{'type': 'integer'},
|
||||
{'type': 'string'}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
'type': 'object',
|
||||
'id': 'ModifiedUnionType',
|
||||
'properties': {
|
||||
'x': {
|
||||
'name': 'x',
|
||||
'nodoc': True,
|
||||
'choices': [{
|
||||
'type': 'integer'
|
||||
}, {
|
||||
'type': 'string'
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.assertEqual(expected, union_type)
|
||||
|
||||
@ -394,17 +510,17 @@ class IdlSchemaTest(unittest.TestCase):
|
||||
schema = idl_schema.Load('test/idl_object_types.idl')[0]
|
||||
object_type = getType(schema, 'SerializableFunctionObject')
|
||||
expected = {
|
||||
'type': 'object',
|
||||
'id': 'SerializableFunctionObject',
|
||||
'properties': {
|
||||
'func': {
|
||||
'name': 'func',
|
||||
'serializableFunction': True,
|
||||
'type': 'function',
|
||||
'parameters': []
|
||||
}
|
||||
}
|
||||
}
|
||||
'type': 'object',
|
||||
'id': 'SerializableFunctionObject',
|
||||
'properties': {
|
||||
'func': {
|
||||
'name': 'func',
|
||||
'serializableFunction': True,
|
||||
'type': 'function',
|
||||
'parameters': []
|
||||
}
|
||||
}
|
||||
}
|
||||
self.assertEqual(expected, object_type)
|
||||
|
||||
def testUnionsWithFunctions(self):
|
||||
@ -412,12 +528,13 @@ class IdlSchemaTest(unittest.TestCase):
|
||||
|
||||
union_params = getParams(schema, 'union_params')
|
||||
expected = [{
|
||||
'name': 'x',
|
||||
'choices': [
|
||||
{'type': 'integer'},
|
||||
{'type': 'string'}
|
||||
]
|
||||
}]
|
||||
'name': 'x',
|
||||
'choices': [{
|
||||
'type': 'integer'
|
||||
}, {
|
||||
'type': 'string'
|
||||
}]
|
||||
}]
|
||||
|
||||
self.assertEqual(expected, union_params)
|
||||
|
||||
@ -426,24 +543,32 @@ class IdlSchemaTest(unittest.TestCase):
|
||||
|
||||
blah_params = getReturnsAsync(schema, 'blah')
|
||||
expected = {
|
||||
'name': 'callback', 'parameters': [{
|
||||
'name': 'x',
|
||||
'choices': [
|
||||
{'type': 'integer'},
|
||||
{'type': 'string'}
|
||||
]}
|
||||
]
|
||||
}
|
||||
'name':
|
||||
'callback',
|
||||
'parameters': [{
|
||||
'name': 'x',
|
||||
'choices': [{
|
||||
'type': 'integer'
|
||||
}, {
|
||||
'type': 'string'
|
||||
}]
|
||||
}]
|
||||
}
|
||||
|
||||
badabish_params = getReturnsAsync(schema, 'badabish')
|
||||
expected = {
|
||||
'name': 'callback', 'parameters': [{
|
||||
'name': 'x', 'optional': True, 'choices': [
|
||||
{'type': 'integer'},
|
||||
{'type': 'string'}
|
||||
]
|
||||
}]
|
||||
}
|
||||
'name':
|
||||
'callback',
|
||||
'parameters': [{
|
||||
'name': 'x',
|
||||
'optional': True,
|
||||
'choices': [{
|
||||
'type': 'integer'
|
||||
}, {
|
||||
'type': 'string'
|
||||
}]
|
||||
}]
|
||||
}
|
||||
|
||||
self.assertEqual(expected, badabish_params)
|
||||
|
||||
@ -453,7 +578,10 @@ class IdlSchemaTest(unittest.TestCase):
|
||||
expected_params = []
|
||||
expected_returns_async = {
|
||||
'name': 'callback',
|
||||
'parameters': [{'name': 'x', 'type': 'integer'}],
|
||||
'parameters': [{
|
||||
'name': 'x',
|
||||
'type': 'integer'
|
||||
}],
|
||||
'does_not_support_promises': 'Test'
|
||||
}
|
||||
params = getParams(schema, 'non_promise_supporting')
|
||||
@ -465,46 +593,60 @@ class IdlSchemaTest(unittest.TestCase):
|
||||
def testFunctionWithoutPromiseSupportAndParams(self):
|
||||
schema = idl_schema.Load('test/idl_function_types.idl')[0]
|
||||
|
||||
expected_params = [
|
||||
{'name': 'z', 'type': 'integer'},
|
||||
{'name': 'y', 'choices': [{'type': 'integer'}, {'type': 'string'}]}
|
||||
]
|
||||
expected_params = [{
|
||||
'name': 'z',
|
||||
'type': 'integer'
|
||||
}, {
|
||||
'name': 'y',
|
||||
'choices': [{
|
||||
'type': 'integer'
|
||||
}, {
|
||||
'type': 'string'
|
||||
}]
|
||||
}]
|
||||
expected_returns_async = {
|
||||
'name': 'callback',
|
||||
'parameters': [{'name': 'x', 'type': 'integer'}],
|
||||
'parameters': [{
|
||||
'name': 'x',
|
||||
'type': 'integer'
|
||||
}],
|
||||
'does_not_support_promises': 'Test'
|
||||
}
|
||||
params = getParams(schema, 'non_promise_supporting_with_params')
|
||||
returns_async = getReturnsAsync(
|
||||
schema, 'non_promise_supporting_with_params'
|
||||
)
|
||||
returns_async = getReturnsAsync(schema,
|
||||
'non_promise_supporting_with_params')
|
||||
|
||||
self.assertEqual(expected_params, params)
|
||||
self.assertEqual(expected_returns_async, returns_async)
|
||||
|
||||
def testProperties(self):
|
||||
schema = idl_schema.Load('test/idl_properties.idl')[0]
|
||||
self.assertEqual(OrderedDict([
|
||||
('first', OrderedDict([
|
||||
('description', 'Integer property.'),
|
||||
('type', 'integer'),
|
||||
('value', 42),
|
||||
])),
|
||||
('second', OrderedDict([
|
||||
('description', 'Double property.'),
|
||||
('type', 'number'),
|
||||
('value', 42.1),
|
||||
])),
|
||||
('third', OrderedDict([
|
||||
('description', 'String property.'),
|
||||
('type', 'string'),
|
||||
('value', 'hello world'),
|
||||
])),
|
||||
('fourth', OrderedDict([
|
||||
('description', 'Unvalued property.'),
|
||||
('type', 'integer'),
|
||||
])),
|
||||
]), schema.get('properties'))
|
||||
self.assertEqual(
|
||||
OrderedDict([
|
||||
('first',
|
||||
OrderedDict([
|
||||
('description', 'Integer property.'),
|
||||
('type', 'integer'),
|
||||
('value', 42),
|
||||
])),
|
||||
('second',
|
||||
OrderedDict([
|
||||
('description', 'Double property.'),
|
||||
('type', 'number'),
|
||||
('value', 42.1),
|
||||
])),
|
||||
('third',
|
||||
OrderedDict([
|
||||
('description', 'String property.'),
|
||||
('type', 'string'),
|
||||
('value', 'hello world'),
|
||||
])),
|
||||
('fourth',
|
||||
OrderedDict([
|
||||
('description', 'Unvalued property.'),
|
||||
('type', 'integer'),
|
||||
])),
|
||||
]), schema.get('properties'))
|
||||
|
||||
def testManifestKeys(self):
|
||||
schema = self.idl_basics
|
||||
@ -512,19 +654,20 @@ class IdlSchemaTest(unittest.TestCase):
|
||||
# exhaustive so we don't have to update it each time we add a new key in the
|
||||
# test file.
|
||||
manifest_keys = schema.get('manifest_keys')
|
||||
self.assertEqual(manifest_keys['key_str'],
|
||||
OrderedDict([('description', 'String manifest key.'),
|
||||
('name', 'key_str'),
|
||||
('type', 'string')]))
|
||||
self.assertEqual(
|
||||
manifest_keys['key_str'],
|
||||
OrderedDict([('description', 'String manifest key.'),
|
||||
('name', 'key_str'), ('type', 'string')]))
|
||||
self.assertEqual(manifest_keys['key_ref'],
|
||||
OrderedDict([('name', 'key_ref'),
|
||||
('$ref', 'MyType2')])),
|
||||
self.assertEqual(manifest_keys['choice_with_arrays'],
|
||||
OrderedDict([('name', 'choice_with_arrays'),
|
||||
('$ref', 'ChoiceWithArraysType')])),
|
||||
self.assertEqual(manifest_keys['choice_with_optional'],
|
||||
OrderedDict([('name', 'choice_with_optional'),
|
||||
('$ref', 'ChoiceWithOptionalType')]))
|
||||
OrderedDict([('name', 'key_ref'), ('$ref', 'MyType2')])),
|
||||
self.assertEqual(
|
||||
manifest_keys['choice_with_arrays'],
|
||||
OrderedDict([('name', 'choice_with_arrays'),
|
||||
('$ref', 'ChoiceWithArraysType')])),
|
||||
self.assertEqual(
|
||||
manifest_keys['choice_with_optional'],
|
||||
OrderedDict([('name', 'choice_with_optional'),
|
||||
('$ref', 'ChoiceWithOptionalType')]))
|
||||
|
||||
def testNoManifestKeys(self):
|
||||
schema = idl_schema.Load('test/idl_properties.idl')[0]
|
||||
|
@ -23,11 +23,15 @@ NOTE = """// NOTE: The format of types has changed. 'FooType' is now
|
||||
// See https://chromium.googlesource.com/chromium/src/+/main/docs/closure_compilation.md
|
||||
"""
|
||||
|
||||
|
||||
class JsExternsGenerator(object):
|
||||
|
||||
def Generate(self, namespace):
|
||||
return _Generator(namespace).Generate()
|
||||
|
||||
|
||||
class _Generator(object):
|
||||
|
||||
def __init__(self, namespace):
|
||||
self._namespace = namespace
|
||||
self._class_name = None
|
||||
@ -45,7 +49,7 @@ class _Generator(object):
|
||||
src_to_script = os.path.relpath(script_dir, src_root)
|
||||
# tools/json_schema_compiler/compiler.py
|
||||
compiler_path = os.path.join(src_to_script, 'compiler.py')
|
||||
(c.Append(self._GetHeader(compiler_path, self._namespace.name))
|
||||
(c.Append(self._GetHeader(compiler_path, self._namespace.name)) \
|
||||
.Append())
|
||||
|
||||
self._AppendNamespaceObject(c)
|
||||
@ -69,13 +73,10 @@ class _Generator(object):
|
||||
def _GetHeader(self, tool, namespace):
|
||||
"""Returns the file header text.
|
||||
"""
|
||||
return (self._js_util.GetLicense() + '\n' +
|
||||
self._js_util.GetInfo(tool) + (NOTE % namespace) + '\n' +
|
||||
'/**\n' +
|
||||
return (self._js_util.GetLicense() + '\n' + self._js_util.GetInfo(tool) +
|
||||
(NOTE % namespace) + '\n' + '/**\n' +
|
||||
(' * @fileoverview Externs generated from namespace: %s\n' %
|
||||
namespace) +
|
||||
' * @externs\n' +
|
||||
' */')
|
||||
namespace) + ' * @externs\n' + ' */')
|
||||
|
||||
def _AppendType(self, c, js_type):
|
||||
"""Given a Type object, generates the Code for this type's definition.
|
||||
@ -95,9 +96,10 @@ class _Generator(object):
|
||||
js_type.simple_name)
|
||||
c.Eblock(' */')
|
||||
c.Append('%s.%s = {' % (self._GetNamespace(), js_type.name))
|
||||
c.Append('\n'.join(
|
||||
[" %s: '%s'," % (self._js_util.GetPropertyName(v.name), v.name)
|
||||
for v in js_type.enum_values]))
|
||||
c.Append('\n'.join([
|
||||
" %s: '%s'," % (self._js_util.GetPropertyName(v.name), v.name)
|
||||
for v in js_type.enum_values
|
||||
]))
|
||||
c.Append('};')
|
||||
|
||||
def _IsTypeConstructor(self, js_type):
|
||||
@ -120,8 +122,8 @@ class _Generator(object):
|
||||
if js_type.property_type is not PropertyType.OBJECT:
|
||||
self._js_util.AppendTypeJsDoc(c, self._namespace.name, js_type, optional)
|
||||
elif is_constructor:
|
||||
c.Comment('@constructor', comment_prefix = '', wrap_indent=4)
|
||||
c.Comment('@private', comment_prefix = '', wrap_indent=4)
|
||||
c.Comment('@constructor', comment_prefix='', wrap_indent=4)
|
||||
c.Comment('@private', comment_prefix='', wrap_indent=4)
|
||||
else:
|
||||
self._AppendTypedef(c, js_type.properties)
|
||||
|
||||
@ -151,8 +153,10 @@ class _Generator(object):
|
||||
|
||||
c.Append('@typedef {')
|
||||
if properties:
|
||||
self._js_util.AppendObjectDefinition(
|
||||
c, self._namespace.name, properties, new_line=False)
|
||||
self._js_util.AppendObjectDefinition(c,
|
||||
self._namespace.name,
|
||||
properties,
|
||||
new_line=False)
|
||||
else:
|
||||
c.Append('Object', new_line=False)
|
||||
c.Append('}', new_line=False)
|
||||
@ -178,8 +182,8 @@ class _Generator(object):
|
||||
"""
|
||||
self._js_util.AppendFunctionJsDoc(c, self._namespace.name, function)
|
||||
params = self._GetFunctionParams(function)
|
||||
c.Append('%s.%s = function(%s) {};' % (self._GetNamespace(),
|
||||
function.name, params))
|
||||
c.Append('%s.%s = function(%s) {};' %
|
||||
(self._GetNamespace(), function.name, params))
|
||||
c.Append()
|
||||
|
||||
def _AppendEvent(self, c, event):
|
||||
|
@ -496,15 +496,16 @@ chrome.fakeJson.funcWithReturnsAsync = function(someNumber, callback) {};""" % (
|
||||
|
||||
|
||||
class JsExternGeneratorTest(unittest.TestCase):
|
||||
|
||||
def _GetNamespace(self, fake_content, filename, is_idl):
|
||||
"""Returns a namespace object for the given content"""
|
||||
api_def = (idl_schema.Process(fake_content, filename) if is_idl
|
||||
else json_parse.Parse(fake_content))
|
||||
api_def = (idl_schema.Process(fake_content, filename)
|
||||
if is_idl else json_parse.Parse(fake_content))
|
||||
m = model.Model()
|
||||
return m.AddNamespace(api_def[0], filename)
|
||||
|
||||
def setUp(self):
|
||||
self.maxDiff = None # Lets us see the full diff when inequal.
|
||||
self.maxDiff = None # Lets us see the full diff when inequal.
|
||||
|
||||
def testBasic(self):
|
||||
namespace = self._GetNamespace(fake_idl, 'fake_api.idl', True)
|
||||
|
@ -15,11 +15,15 @@ import os
|
||||
import sys
|
||||
import re
|
||||
|
||||
|
||||
class JsInterfaceGenerator(object):
|
||||
|
||||
def Generate(self, namespace):
|
||||
return _Generator(namespace).Generate()
|
||||
|
||||
|
||||
class _Generator(object):
|
||||
|
||||
def __init__(self, namespace):
|
||||
self._namespace = namespace
|
||||
first = namespace.name[0].upper()
|
||||
@ -31,7 +35,7 @@ class _Generator(object):
|
||||
"""Generates a Code object with the schema for the entire namespace.
|
||||
"""
|
||||
c = Code()
|
||||
(c.Append(self._GetHeader(sys.argv[0], self._namespace.name))
|
||||
(c.Append(self._GetHeader(sys.argv[0], self._namespace.name)) \
|
||||
.Append())
|
||||
|
||||
self._AppendInterfaceObject(c)
|
||||
@ -56,10 +60,10 @@ class _Generator(object):
|
||||
def _GetHeader(self, tool, namespace):
|
||||
"""Returns the file header text.
|
||||
"""
|
||||
return (
|
||||
self._js_util.GetLicense() + '\n' + self._js_util.GetInfo(tool) + '\n' +
|
||||
('/** @fileoverview Interface for %s that can be overriden. */' %
|
||||
namespace))
|
||||
return (self._js_util.GetLicense() + '\n' + self._js_util.GetInfo(tool) +
|
||||
'\n' +
|
||||
('/** @fileoverview Interface for %s that can be overriden. */' %
|
||||
namespace))
|
||||
|
||||
def _AppendInterfaceObject(self, c):
|
||||
"""Appends the code creating the interface object.
|
||||
@ -67,7 +71,7 @@ class _Generator(object):
|
||||
/** @interface */
|
||||
function SettingsPrivate() {}
|
||||
"""
|
||||
(c.Append('/** @interface */')
|
||||
(c.Append('/** @interface */') \
|
||||
.Append('function %s() {}' % self._interface))
|
||||
|
||||
def _AppendFunction(self, c, function):
|
||||
@ -88,8 +92,8 @@ class _Generator(object):
|
||||
|
||||
self._js_util.AppendFunctionJsDoc(c, self._namespace.name, function)
|
||||
|
||||
c.Append('%s: function(%s) {},' % (function.name, ', '.join(
|
||||
getParamNames(function))))
|
||||
c.Append('%s: function(%s) {},' %
|
||||
(function.name, ', '.join(getParamNames(function))))
|
||||
c.Append()
|
||||
|
||||
def _AppendEvent(self, c, event):
|
||||
|
@ -110,10 +110,12 @@ FakeApi.prototype = {
|
||||
* @type {!ChromeEvent}
|
||||
* @see https://developer.chrome.com/extensions/fakeApi#event-onTrapDetected
|
||||
*/
|
||||
FakeApi.prototype.onTrapDetected;""" % (datetime.now().year,
|
||||
sys.argv[0].replace('\\', '/')))
|
||||
FakeApi.prototype.onTrapDetected;""" %
|
||||
(datetime.now().year, sys.argv[0].replace('\\', '/')))
|
||||
|
||||
|
||||
class JsExternGeneratorTest(unittest.TestCase):
|
||||
|
||||
def _GetNamespace(self, fake_content, filename):
|
||||
"""Returns a namespace object for the given content"""
|
||||
api_def = idl_schema.Process(fake_content, filename)
|
||||
@ -121,7 +123,7 @@ class JsExternGeneratorTest(unittest.TestCase):
|
||||
return m.AddNamespace(api_def[0], filename)
|
||||
|
||||
def setUp(self):
|
||||
self.maxDiff = None # Lets us see the full diff when inequal.
|
||||
self.maxDiff = None # Lets us see the full diff when inequal.
|
||||
|
||||
def testBasic(self):
|
||||
namespace = self._GetNamespace(fake_idl, 'fake_api.idl')
|
||||
|
@ -21,6 +21,7 @@ INFO = """// This file was generated by:
|
||||
class JsUtil(object):
|
||||
"""A helper class for generating JS Code.
|
||||
"""
|
||||
|
||||
def GetLicense(self):
|
||||
"""Returns the license text for JS extern and interface files.
|
||||
"""
|
||||
@ -31,7 +32,7 @@ class JsUtil(object):
|
||||
"""
|
||||
return (INFO % tool.replace('\\', '/'))
|
||||
|
||||
def GetPropertyName(self,e):
|
||||
def GetPropertyName(self, e):
|
||||
# Enum properties are normified to be in ALL_CAPS_STYLE.
|
||||
# Assume enum '1ring-rulesThemAll'.
|
||||
# Transform to '1ring-rules_Them_All'.
|
||||
@ -43,8 +44,11 @@ class JsUtil(object):
|
||||
# Transform to '_1RING_RULES_THEM_ALL'.
|
||||
return e.upper()
|
||||
|
||||
def AppendObjectDefinition(self, c, namespace_name, properties,
|
||||
new_line=True):
|
||||
def AppendObjectDefinition(self,
|
||||
c,
|
||||
namespace_name,
|
||||
properties,
|
||||
new_line=True):
|
||||
"""Given an OrderedDict of properties, returns a Code containing the
|
||||
description of an object.
|
||||
"""
|
||||
@ -62,8 +66,8 @@ class JsUtil(object):
|
||||
first = False
|
||||
js_type = self._TypeToJsType(namespace_name, prop.type_)
|
||||
if prop.optional:
|
||||
js_type = (Code().Append('(')
|
||||
.Concat(js_type, new_line=False)
|
||||
js_type = (Code().Append('(') \
|
||||
.Concat(js_type, new_line=False) \
|
||||
.Append('|undefined)', new_line=False))
|
||||
c.Append('%s: ' % field, strip_right=False)
|
||||
c.Concat(js_type, new_line=False)
|
||||
@ -88,16 +92,18 @@ class JsUtil(object):
|
||||
c.Append('}', new_line=False)
|
||||
c.Comment(' %s' % name, comment_prefix='', wrap_indent=4, new_line=False)
|
||||
if description:
|
||||
c.Comment(' %s' % description, comment_prefix='',
|
||||
wrap_indent=4, new_line=False)
|
||||
c.Comment(' %s' % description,
|
||||
comment_prefix='',
|
||||
wrap_indent=4,
|
||||
new_line=False)
|
||||
|
||||
for i, param in enumerate(function.params):
|
||||
# Mark the parameter as optional, *only if* all following parameters are
|
||||
# also optional, to avoid JSC_OPTIONAL_ARG_AT_END errors thrown by Closure
|
||||
# Compiler.
|
||||
optional = (
|
||||
all(p.optional for p in function.params[i:]) and
|
||||
(function.returns_async is None or function.returns_async.optional))
|
||||
optional = (all(p.optional for p in function.params[i:])
|
||||
and (function.returns_async is None
|
||||
or function.returns_async.optional))
|
||||
js_type = self._TypeToJsType(namespace_name, param.type_)
|
||||
|
||||
# If the parameter was originally optional, but was followed by
|
||||
@ -123,8 +129,8 @@ class JsUtil(object):
|
||||
|
||||
if function.returns:
|
||||
append_field(c, 'return',
|
||||
self._TypeToJsType(namespace_name, function.returns),
|
||||
'', False, function.returns.description)
|
||||
self._TypeToJsType(namespace_name, function.returns), '',
|
||||
False, function.returns.description)
|
||||
|
||||
if function.deprecated:
|
||||
c.Append('@deprecated %s' % function.deprecated)
|
||||
@ -149,9 +155,8 @@ class JsUtil(object):
|
||||
"""Converts a model.Function to a JS type (i.e., function([params])...)"""
|
||||
c = Code()
|
||||
c.Append('function(')
|
||||
c.Concat(
|
||||
self._FunctionParamsToJsParams(namespace_name, function.params),
|
||||
new_line=False)
|
||||
c.Concat(self._FunctionParamsToJsParams(namespace_name, function.params),
|
||||
new_line=False)
|
||||
c.Append('): ', new_line=False, strip_right=False)
|
||||
|
||||
if function.returns:
|
||||
@ -169,9 +174,9 @@ class JsUtil(object):
|
||||
# appended to the params as a callback.
|
||||
c = Code()
|
||||
c.Append('function(')
|
||||
c.Concat(
|
||||
self._FunctionParamsToJsParams(namespace_name, returns_async.params),
|
||||
new_line=False)
|
||||
c.Concat(self._FunctionParamsToJsParams(namespace_name,
|
||||
returns_async.params),
|
||||
new_line=False)
|
||||
c.Append('): ', new_line=False, strip_right=False)
|
||||
|
||||
c.Append('void', new_line=False)
|
||||
@ -211,10 +216,9 @@ class JsUtil(object):
|
||||
return Code().Append(js_type.instance_of)
|
||||
return Code().Append('Object')
|
||||
if js_type.property_type is PropertyType.ARRAY:
|
||||
return (Code().Append('!Array<').
|
||||
Concat(self._TypeToJsType(namespace_name, js_type.item_type),
|
||||
new_line=False).
|
||||
Append('>', new_line=False))
|
||||
return (Code().Append('!Array<').Concat(
|
||||
self._TypeToJsType(namespace_name, js_type.item_type),
|
||||
new_line=False).Append('>', new_line=False))
|
||||
if js_type.property_type is PropertyType.REF:
|
||||
ref_type = '!chrome.%s.%s' % (namespace_name, js_type.ref_type)
|
||||
return Code().Append(ref_type)
|
||||
@ -235,7 +239,7 @@ class JsUtil(object):
|
||||
return Code().Append('*')
|
||||
if js_type.property_type.is_fundamental:
|
||||
return Code().Append(js_type.property_type.name)
|
||||
return Code().Append('?') # TODO(tbreisacher): Make this more specific.
|
||||
return Code().Append('?') # TODO(tbreisacher): Make this more specific.
|
||||
|
||||
def AppendSeeLink(self, c, namespace_name, object_type, object_name):
|
||||
"""Appends a @see link for a given API 'object' (type, method, or event).
|
||||
|
@ -9,8 +9,8 @@ import sys
|
||||
_FILE_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||
_SYS_PATH = sys.path[:]
|
||||
try:
|
||||
_COMMENT_EATER_PATH = os.path.join(
|
||||
_FILE_PATH, os.pardir, 'json_comment_eater')
|
||||
_COMMENT_EATER_PATH = os.path.join(_FILE_PATH, os.pardir,
|
||||
'json_comment_eater')
|
||||
sys.path.insert(0, _COMMENT_EATER_PATH)
|
||||
import json_comment_eater
|
||||
finally:
|
||||
@ -36,17 +36,14 @@ except ImportError:
|
||||
|
||||
_SYS_PATH = sys.path[:]
|
||||
try:
|
||||
_SIMPLE_JSON_PATH = os.path.join(_FILE_PATH,
|
||||
os.pardir,
|
||||
os.pardir,
|
||||
_SIMPLE_JSON_PATH = os.path.join(_FILE_PATH, os.pardir, os.pardir,
|
||||
'third_party')
|
||||
sys.path.insert(0, _SIMPLE_JSON_PATH)
|
||||
# Add this path in case this is being used in the docs server.
|
||||
sys.path.insert(0, os.path.join(_FILE_PATH,
|
||||
os.pardir,
|
||||
os.pardir,
|
||||
'third_party',
|
||||
'json_schema_compiler'))
|
||||
sys.path.insert(
|
||||
0,
|
||||
os.path.join(_FILE_PATH, os.pardir, os.pardir, 'third_party',
|
||||
'json_schema_compiler'))
|
||||
import simplejson
|
||||
from simplejson import OrderedDict
|
||||
finally:
|
||||
|
@ -17,8 +17,8 @@ def DeleteNodes(item, delete_key=None, matcher=None):
|
||||
|
||||
def ShouldDelete(thing):
|
||||
return json_parse.IsDict(thing) and (
|
||||
delete_key is not None and delete_key in thing or
|
||||
matcher is not None and matcher(thing))
|
||||
delete_key is not None and delete_key in thing
|
||||
or matcher is not None and matcher(thing))
|
||||
|
||||
if json_parse.IsDict(item):
|
||||
toDelete = []
|
||||
@ -30,8 +30,10 @@ def DeleteNodes(item, delete_key=None, matcher=None):
|
||||
for key in toDelete:
|
||||
del item[key]
|
||||
elif type(item) == list:
|
||||
item[:] = [DeleteNodes(thing, delete_key, matcher)
|
||||
for thing in item if not ShouldDelete(thing)]
|
||||
item[:] = [
|
||||
DeleteNodes(thing, delete_key, matcher) for thing in item
|
||||
if not ShouldDelete(thing)
|
||||
]
|
||||
|
||||
return item
|
||||
|
||||
|
@ -6,93 +6,85 @@
|
||||
import json_schema
|
||||
import unittest
|
||||
|
||||
|
||||
class JsonSchemaUnittest(unittest.TestCase):
|
||||
|
||||
def testNocompile(self):
|
||||
compiled = [
|
||||
{
|
||||
compiled = [{
|
||||
"namespace": "compile",
|
||||
"description": "The compile API.",
|
||||
"functions": [],
|
||||
"types": {}
|
||||
},
|
||||
|
||||
{
|
||||
"types": {}
|
||||
}, {
|
||||
"namespace": "functions",
|
||||
"description": "The functions API.",
|
||||
"functions": [
|
||||
{
|
||||
"functions": [{
|
||||
"id": "two"
|
||||
},
|
||||
{
|
||||
}, {
|
||||
"id": "four"
|
||||
}
|
||||
],
|
||||
|
||||
}],
|
||||
"types": {
|
||||
"one": { "key": "value" }
|
||||
"one": {
|
||||
"key": "value"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
}, {
|
||||
"namespace": "types",
|
||||
"description": "The types API.",
|
||||
"functions": [
|
||||
{ "id": "one" }
|
||||
],
|
||||
"functions": [{
|
||||
"id": "one"
|
||||
}],
|
||||
"types": {
|
||||
"two": {
|
||||
"key": "value"
|
||||
},
|
||||
"four": {
|
||||
"key": "value"
|
||||
}
|
||||
"two": {
|
||||
"key": "value"
|
||||
},
|
||||
"four": {
|
||||
"key": "value"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
}, {
|
||||
"namespace": "nested",
|
||||
"description": "The nested API.",
|
||||
"properties": {
|
||||
"sync": {
|
||||
"functions": [
|
||||
{
|
||||
"id": "two"
|
||||
},
|
||||
{
|
||||
"id": "four"
|
||||
}
|
||||
],
|
||||
"types": {
|
||||
"two": {
|
||||
"key": "value"
|
||||
},
|
||||
"four": {
|
||||
"key": "value"
|
||||
}
|
||||
"sync": {
|
||||
"functions": [{
|
||||
"id": "two"
|
||||
}, {
|
||||
"id": "four"
|
||||
}],
|
||||
"types": {
|
||||
"two": {
|
||||
"key": "value"
|
||||
},
|
||||
"four": {
|
||||
"key": "value"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}]
|
||||
|
||||
schema = json_schema.CachedLoad('test/json_schema_test.json')
|
||||
self.assertEqual(compiled, json_schema.DeleteNodes(schema, 'nocompile'))
|
||||
|
||||
def should_delete(value):
|
||||
return isinstance(value, dict) and not value.get('valid', True)
|
||||
expected = [
|
||||
{'one': {'test': 'test'}},
|
||||
{'valid': True},
|
||||
{}
|
||||
]
|
||||
given = [
|
||||
{'one': {'test': 'test'}, 'two': {'valid': False}},
|
||||
{'valid': True},
|
||||
{},
|
||||
{'valid': False}
|
||||
]
|
||||
self.assertEqual(
|
||||
expected, json_schema.DeleteNodes(given, matcher=should_delete))
|
||||
|
||||
expected = [{'one': {'test': 'test'}}, {'valid': True}, {}]
|
||||
given = [{
|
||||
'one': {
|
||||
'test': 'test'
|
||||
},
|
||||
'two': {
|
||||
'valid': False
|
||||
}
|
||||
}, {
|
||||
'valid': True
|
||||
}, {}, {
|
||||
'valid': False
|
||||
}]
|
||||
self.assertEqual(expected,
|
||||
json_schema.DeleteNodes(given, matcher=should_delete))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -2,13 +2,16 @@
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
|
||||
def memoize(fn):
|
||||
'''Decorates |fn| to memoize.
|
||||
'''
|
||||
memory = {}
|
||||
|
||||
def impl(*args, **optargs):
|
||||
full_args = args + tuple(optargs.items())
|
||||
if full_args not in memory:
|
||||
memory[full_args] = fn(*args, **optargs)
|
||||
return memory[full_args]
|
||||
|
||||
return impl
|
||||
|
@ -17,14 +17,16 @@ def _IsTypeFromManifestKeys(namespace, typename, fallback):
|
||||
|
||||
return fallback
|
||||
|
||||
|
||||
class ParseException(Exception):
|
||||
"""Thrown when data in the model is invalid.
|
||||
"""
|
||||
|
||||
def __init__(self, parent, message):
|
||||
hierarchy = _GetModelHierarchy(parent)
|
||||
hierarchy.append(message)
|
||||
Exception.__init__(
|
||||
self, 'Model parse exception at:\n' + '\n'.join(hierarchy))
|
||||
Exception.__init__(self,
|
||||
'Model parse exception at:\n' + '\n'.join(hierarchy))
|
||||
|
||||
|
||||
class Model(object):
|
||||
@ -33,6 +35,7 @@ class Model(object):
|
||||
Properties:
|
||||
- |namespaces| a map of a namespace name to its model.Namespace
|
||||
"""
|
||||
|
||||
def __init__(self, allow_inline_enums=True):
|
||||
self._allow_inline_enums = allow_inline_enums
|
||||
self.namespaces = {}
|
||||
@ -67,11 +70,13 @@ class ComplexFeature(object):
|
||||
- |unix_name| the unix_name of the feature
|
||||
- |feature_list| a list of simple features which make up the feature
|
||||
"""
|
||||
|
||||
def __init__(self, feature_name, features):
|
||||
self.name = feature_name
|
||||
self.unix_name = UnixName(self.name)
|
||||
self.feature_list = features
|
||||
|
||||
|
||||
class SimpleFeature(object):
|
||||
"""A simple feature, which can make up a complex feature, as specified in
|
||||
files such as chrome/common/extensions/api/_permission_features.json.
|
||||
@ -83,6 +88,7 @@ class SimpleFeature(object):
|
||||
- |extension_types| the types which can use the feature
|
||||
- |allowlist| a list of extensions allowed to use the feature
|
||||
"""
|
||||
|
||||
def __init__(self, feature_name, feature_def):
|
||||
self.name = feature_name
|
||||
self.unix_name = UnixName(self.name)
|
||||
@ -112,6 +118,7 @@ class Namespace(object):
|
||||
|include_compiler_options| is True
|
||||
- |manifest_keys| is a Type representing the manifest keys for this namespace.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
json,
|
||||
source_file,
|
||||
@ -122,7 +129,7 @@ class Namespace(object):
|
||||
if 'description' not in json:
|
||||
# TODO(kalman): Go back to throwing an error here.
|
||||
print('%s must have a "description" field. This will appear '
|
||||
'on the API summary page.' % self.name)
|
||||
'on the API summary page.' % self.name)
|
||||
json['description'] = ''
|
||||
self.description = json['description']
|
||||
self.nodoc = json.get('nodoc', False)
|
||||
@ -198,12 +205,8 @@ class Type(object):
|
||||
- |additional_properties| the type of the additional properties, if any is
|
||||
specified
|
||||
"""
|
||||
def __init__(self,
|
||||
parent,
|
||||
name,
|
||||
json,
|
||||
namespace,
|
||||
input_origin):
|
||||
|
||||
def __init__(self, parent, name, json, namespace, input_origin):
|
||||
self.name = name
|
||||
|
||||
# The typename "ManifestKeys" is reserved.
|
||||
@ -221,8 +224,9 @@ class Type(object):
|
||||
# We need to do this to ensure types reference by manifest types have the
|
||||
# correct value for |origin.from_manifest_keys|.
|
||||
self.origin = Origin(
|
||||
input_origin.from_client, input_origin.from_json,
|
||||
_IsTypeFromManifestKeys(namespace, name, input_origin.from_manifest_keys))
|
||||
input_origin.from_client, input_origin.from_json,
|
||||
_IsTypeFromManifestKeys(namespace, name,
|
||||
input_origin.from_manifest_keys))
|
||||
|
||||
self.parent = parent
|
||||
self.instance_of = json.get('isInstanceOf', None)
|
||||
@ -252,7 +256,7 @@ class Type(object):
|
||||
raise ParseException(
|
||||
self,
|
||||
'Inline enum "%s" found in namespace "%s". These are not allowed. '
|
||||
'See crbug.com/472279' % (name, namespace.name))
|
||||
'See crbug.com/472279' % (name, namespace.name))
|
||||
self.property_type = PropertyType.ENUM
|
||||
self.enum_values = [EnumValue(value, namespace) for value in json['enum']]
|
||||
self.cpp_enum_prefix_override = json.get('cpp_enum_prefix_override', None)
|
||||
@ -264,13 +268,13 @@ class Type(object):
|
||||
self.property_type = PropertyType.BOOLEAN
|
||||
elif json_type == 'integer':
|
||||
self.property_type = PropertyType.INTEGER
|
||||
elif (json_type == 'double' or
|
||||
json_type == 'number'):
|
||||
elif (json_type == 'double' or json_type == 'number'):
|
||||
self.property_type = PropertyType.DOUBLE
|
||||
elif json_type == 'string':
|
||||
self.property_type = PropertyType.STRING
|
||||
elif 'choices' in json:
|
||||
self.property_type = PropertyType.CHOICES
|
||||
|
||||
def generate_type_name(type_json):
|
||||
if 'items' in type_json:
|
||||
return '%ss' % generate_type_name(type_json['items'])
|
||||
@ -279,28 +283,22 @@ class Type(object):
|
||||
if 'type' in type_json:
|
||||
return type_json['type']
|
||||
return None
|
||||
|
||||
self.choices = [
|
||||
Type(self,
|
||||
generate_type_name(choice) or 'choice%s' % i,
|
||||
choice,
|
||||
namespace,
|
||||
self.origin)
|
||||
for i, choice in enumerate(json['choices'])]
|
||||
generate_type_name(choice) or 'choice%s' % i, choice, namespace,
|
||||
self.origin) for i, choice in enumerate(json['choices'])
|
||||
]
|
||||
elif json_type == 'object':
|
||||
if not (
|
||||
'isInstanceOf' in json or
|
||||
'properties' in json or
|
||||
'additionalProperties' in json or
|
||||
'functions' in json or
|
||||
'events' in json):
|
||||
if not ('isInstanceOf' in json or 'properties' in json
|
||||
or 'additionalProperties' in json or 'functions' in json
|
||||
or 'events' in json):
|
||||
raise ParseException(self, name + " has no properties or functions")
|
||||
self.property_type = PropertyType.OBJECT
|
||||
additional_properties_json = json.get('additionalProperties', None)
|
||||
if additional_properties_json is not None:
|
||||
self.additional_properties = Type(self,
|
||||
'additionalProperties',
|
||||
additional_properties_json,
|
||||
namespace,
|
||||
self.additional_properties = Type(self, 'additionalProperties',
|
||||
additional_properties_json, namespace,
|
||||
self.origin)
|
||||
else:
|
||||
self.additional_properties = None
|
||||
@ -309,8 +307,8 @@ class Type(object):
|
||||
# Sometimes we might have an unnamed function, e.g. if it's a property
|
||||
# of an object. Use the name of the property in that case.
|
||||
function_name = json.get('name', name)
|
||||
self.function = Function(
|
||||
self, function_name, json, namespace, self.origin)
|
||||
self.function = Function(self, function_name, json, namespace,
|
||||
self.origin)
|
||||
else:
|
||||
raise ParseException(self, 'Unsupported JSON type %s' % json_type)
|
||||
|
||||
@ -321,6 +319,7 @@ class Type(object):
|
||||
'''
|
||||
return self.name == 'ManifestKeys'
|
||||
|
||||
|
||||
class Function(object):
|
||||
"""A Function defined in the API.
|
||||
|
||||
@ -340,12 +339,8 @@ class Function(object):
|
||||
- |returns| the return type of the function; None if the function does not
|
||||
return a value
|
||||
"""
|
||||
def __init__(self,
|
||||
parent,
|
||||
name,
|
||||
json,
|
||||
namespace,
|
||||
origin):
|
||||
|
||||
def __init__(self, parent, name, json, namespace, origin):
|
||||
self.name = name
|
||||
self.simple_name = _StripNamespace(self.name, namespace)
|
||||
self.platforms = _GetPlatforms(json)
|
||||
@ -367,8 +362,10 @@ class Function(object):
|
||||
def GeneratePropertyFromParam(p):
|
||||
return Property(self, p['name'], p, namespace, origin)
|
||||
|
||||
self.filters = [GeneratePropertyFromParam(filter_instance)
|
||||
for filter_instance in json.get('filters', [])]
|
||||
self.filters = [
|
||||
GeneratePropertyFromParam(filter_instance)
|
||||
for filter_instance in json.get('filters', [])
|
||||
]
|
||||
|
||||
# Any asynchronous return should be defined using the returns_async field.
|
||||
returns_async = json.get('returns_async', None)
|
||||
@ -383,14 +380,11 @@ class Function(object):
|
||||
# incompatible with returning a promise. There are APIs that specify this,
|
||||
# though, so we make sure they have specified does_not_support_promises if
|
||||
# they do.
|
||||
if (
|
||||
json.get('returns') is not None
|
||||
and self.returns_async.can_return_promise
|
||||
):
|
||||
if (json.get('returns') is not None
|
||||
and self.returns_async.can_return_promise):
|
||||
raise ValueError(
|
||||
'Cannot specify both returns and returns_async on a function '
|
||||
'which supports promies: %s.%s' % (namespace.name, name)
|
||||
)
|
||||
'which supports promies: %s.%s' % (namespace.name, name))
|
||||
|
||||
params = json.get('parameters', [])
|
||||
for i, param in enumerate(params):
|
||||
@ -398,11 +392,8 @@ class Function(object):
|
||||
|
||||
self.returns = None
|
||||
if 'returns' in json:
|
||||
self.returns = Type(self,
|
||||
'%sReturnType' % name,
|
||||
json['returns'],
|
||||
namespace,
|
||||
origin)
|
||||
self.returns = Type(self, '%sReturnType' % name, json['returns'],
|
||||
namespace, origin)
|
||||
|
||||
|
||||
class ReturnsAsync(object):
|
||||
@ -423,6 +414,7 @@ class ReturnsAsync(object):
|
||||
callback. Currently only consumed for documentation
|
||||
purposes
|
||||
"""
|
||||
|
||||
def __init__(self, parent, json, namespace, origin):
|
||||
self.name = json.get('name')
|
||||
self.simple_name = _StripNamespace(self.name, namespace)
|
||||
@ -434,25 +426,21 @@ class ReturnsAsync(object):
|
||||
|
||||
if json.get('returns') is not None:
|
||||
raise ValueError(
|
||||
'Cannot return a value from an asynchronous return: %s.%s in %s'
|
||||
% (namespace.name, parent.name, namespace.source_file)
|
||||
)
|
||||
'Cannot return a value from an asynchronous return: %s.%s in %s' %
|
||||
(namespace.name, parent.name, namespace.source_file))
|
||||
if json.get('deprecated') is not None:
|
||||
raise ValueError(
|
||||
'Cannot specify deprecated on an asynchronous return: %s.%s in %s'
|
||||
% (namespace.name, parent.name, namespace.source_file)
|
||||
)
|
||||
'Cannot specify deprecated on an asynchronous return: %s.%s in %s' %
|
||||
(namespace.name, parent.name, namespace.source_file))
|
||||
if json.get('parameters') is None:
|
||||
raise ValueError(
|
||||
'parameters key not specified on returns_async: %s.%s in %s'
|
||||
% (namespace.name, parent.name, namespace.source_file)
|
||||
)
|
||||
'parameters key not specified on returns_async: %s.%s in %s' %
|
||||
(namespace.name, parent.name, namespace.source_file))
|
||||
if len(json.get('parameters')) > 1 and self.can_return_promise:
|
||||
raise ValueError(
|
||||
'Only a single parameter can be specific on a returns_async which'
|
||||
' supports promises: %s.%s in %s'
|
||||
% (namespace.name, parent.name, namespace.source_file)
|
||||
)
|
||||
' supports promises: %s.%s in %s' %
|
||||
(namespace.name, parent.name, namespace.source_file))
|
||||
|
||||
def GeneratePropertyFromParam(p):
|
||||
return Property(self, p['name'], p, namespace, origin)
|
||||
@ -475,6 +463,7 @@ class Property(object):
|
||||
- |simple_name| the name of this Property without a namespace
|
||||
- |deprecated| a reason and possible alternative for a deprecated property
|
||||
"""
|
||||
|
||||
def __init__(self, parent, name, json, namespace, origin):
|
||||
"""Creates a Property from JSON.
|
||||
"""
|
||||
@ -491,11 +480,10 @@ class Property(object):
|
||||
self.nodoc = json.get('nodoc', False)
|
||||
|
||||
# HACK: only support very specific value types.
|
||||
is_allowed_value = (
|
||||
'$ref' not in json and
|
||||
('type' not in json or json['type'] == 'integer'
|
||||
or json['type'] == 'number'
|
||||
or json['type'] == 'string'))
|
||||
is_allowed_value = ('$ref' not in json
|
||||
and ('type' not in json or json['type'] == 'integer'
|
||||
or json['type'] == 'number'
|
||||
or json['type'] == 'string'))
|
||||
|
||||
self.value = None
|
||||
if 'value' in json and is_allowed_value:
|
||||
@ -532,20 +520,21 @@ class Property(object):
|
||||
if unix_name == self._unix_name:
|
||||
return
|
||||
if self._unix_name_used:
|
||||
raise AttributeError(
|
||||
'Cannot set the unix_name on %s; '
|
||||
'it is already used elsewhere as %s' %
|
||||
(self.name, self._unix_name))
|
||||
raise AttributeError('Cannot set the unix_name on %s; '
|
||||
'it is already used elsewhere as %s' %
|
||||
(self.name, self._unix_name))
|
||||
self._unix_name = unix_name
|
||||
|
||||
unix_name = property(GetUnixName, SetUnixName)
|
||||
|
||||
|
||||
class EnumValue(object):
|
||||
"""A single value from an enum.
|
||||
Properties:
|
||||
- |name| name of the property as in the json.
|
||||
- |description| a description of the property (if provided)
|
||||
"""
|
||||
|
||||
def __init__(self, json, namespace):
|
||||
if isinstance(json, dict):
|
||||
self.name = json['name']
|
||||
@ -556,17 +545,18 @@ class EnumValue(object):
|
||||
|
||||
# Using empty string values as enum key is only allowed in a few namespaces,
|
||||
# as an exception to the rule, and we should not add more.
|
||||
if (not self.name and
|
||||
namespace.name not in ['enums', 'webstorePrivate']):
|
||||
if (not self.name and namespace.name not in ['enums', 'webstorePrivate']):
|
||||
raise ValueError('Enum value cannot be an empty string')
|
||||
|
||||
def CamelName(self):
|
||||
return CamelName(self.name)
|
||||
|
||||
|
||||
class _Enum(object):
|
||||
"""Superclass for enum types with a "name" field, setting up repr/eq/ne.
|
||||
Enums need to do this so that equality/non-equality work over pickling.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def GetAll(cls):
|
||||
"""Yields all _Enum objects declared in |cls|.
|
||||
@ -581,6 +571,7 @@ class _Enum(object):
|
||||
|
||||
def __eq__(self, other):
|
||||
return type(other) == type(self) and other.name == self.name
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
@ -595,6 +586,7 @@ class _Enum(object):
|
||||
|
||||
|
||||
class _PropertyTypeInfo(_Enum):
|
||||
|
||||
def __init__(self, is_fundamental, name):
|
||||
_Enum.__init__(self, name)
|
||||
self.is_fundamental = is_fundamental
|
||||
@ -602,6 +594,7 @@ class _PropertyTypeInfo(_Enum):
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class PropertyType(object):
|
||||
"""Enum of different types of properties/parameters.
|
||||
"""
|
||||
@ -619,111 +612,113 @@ class PropertyType(object):
|
||||
REF = _PropertyTypeInfo(False, "ref")
|
||||
STRING = _PropertyTypeInfo(True, "string")
|
||||
|
||||
|
||||
def IsCPlusPlusKeyword(name):
|
||||
"""Returns true if `name` is a C++ reserved keyword.
|
||||
"""
|
||||
# Obtained from https://en.cppreference.com/w/cpp/keyword.
|
||||
keywords = {
|
||||
"alignas",
|
||||
"alignof",
|
||||
"and",
|
||||
"and_eq",
|
||||
"asm",
|
||||
"atomic_cancel",
|
||||
"atomic_commit",
|
||||
"atomic_noexcept",
|
||||
"auto",
|
||||
"bitand",
|
||||
"bitor",
|
||||
"bool",
|
||||
"break",
|
||||
"case",
|
||||
"catch",
|
||||
"char",
|
||||
"char8_t",
|
||||
"char16_t",
|
||||
"char32_t",
|
||||
"class",
|
||||
"compl",
|
||||
"concept",
|
||||
"const",
|
||||
"consteval",
|
||||
"constexpr",
|
||||
"constinit",
|
||||
"const_cast",
|
||||
"continue",
|
||||
"co_await",
|
||||
"co_return",
|
||||
"co_yield",
|
||||
"decltype",
|
||||
"default",
|
||||
"delete",
|
||||
"do",
|
||||
"double",
|
||||
"dynamic_cast",
|
||||
"else",
|
||||
"enum",
|
||||
"explicit",
|
||||
"export",
|
||||
"extern",
|
||||
"false",
|
||||
"float",
|
||||
"for",
|
||||
"friend",
|
||||
"goto",
|
||||
"if",
|
||||
"inline",
|
||||
"int",
|
||||
"long",
|
||||
"mutable",
|
||||
"namespace",
|
||||
"new",
|
||||
"noexcept",
|
||||
"not",
|
||||
"not_eq",
|
||||
"nullptr",
|
||||
"operator",
|
||||
"or",
|
||||
"or_eq",
|
||||
"private",
|
||||
"protected",
|
||||
"public",
|
||||
"reflexpr",
|
||||
"register",
|
||||
"reinterpret_cast",
|
||||
"requires",
|
||||
"return",
|
||||
"short",
|
||||
"signed",
|
||||
"sizeof",
|
||||
"static",
|
||||
"static_assert",
|
||||
"static_cast",
|
||||
"struct",
|
||||
"switch",
|
||||
"synchronized",
|
||||
"template",
|
||||
"this",
|
||||
"thread_local",
|
||||
"throw",
|
||||
"true",
|
||||
"try",
|
||||
"typedef",
|
||||
"typeid",
|
||||
"typename",
|
||||
"union",
|
||||
"unsigned",
|
||||
"using",
|
||||
"virtual",
|
||||
"void",
|
||||
"volatile",
|
||||
"wchar_t",
|
||||
"while",
|
||||
"xor",
|
||||
"xor_eq"
|
||||
"alignas",
|
||||
"alignof",
|
||||
"and",
|
||||
"and_eq",
|
||||
"asm",
|
||||
"atomic_cancel",
|
||||
"atomic_commit",
|
||||
"atomic_noexcept",
|
||||
"auto",
|
||||
"bitand",
|
||||
"bitor",
|
||||
"bool",
|
||||
"break",
|
||||
"case",
|
||||
"catch",
|
||||
"char",
|
||||
"char8_t",
|
||||
"char16_t",
|
||||
"char32_t",
|
||||
"class",
|
||||
"compl",
|
||||
"concept",
|
||||
"const",
|
||||
"consteval",
|
||||
"constexpr",
|
||||
"constinit",
|
||||
"const_cast",
|
||||
"continue",
|
||||
"co_await",
|
||||
"co_return",
|
||||
"co_yield",
|
||||
"decltype",
|
||||
"default",
|
||||
"delete",
|
||||
"do",
|
||||
"double",
|
||||
"dynamic_cast",
|
||||
"else",
|
||||
"enum",
|
||||
"explicit",
|
||||
"export",
|
||||
"extern",
|
||||
"false",
|
||||
"float",
|
||||
"for",
|
||||
"friend",
|
||||
"goto",
|
||||
"if",
|
||||
"inline",
|
||||
"int",
|
||||
"long",
|
||||
"mutable",
|
||||
"namespace",
|
||||
"new",
|
||||
"noexcept",
|
||||
"not",
|
||||
"not_eq",
|
||||
"nullptr",
|
||||
"operator",
|
||||
"or",
|
||||
"or_eq",
|
||||
"private",
|
||||
"protected",
|
||||
"public",
|
||||
"reflexpr",
|
||||
"register",
|
||||
"reinterpret_cast",
|
||||
"requires",
|
||||
"return",
|
||||
"short",
|
||||
"signed",
|
||||
"sizeof",
|
||||
"static",
|
||||
"static_assert",
|
||||
"static_cast",
|
||||
"struct",
|
||||
"switch",
|
||||
"synchronized",
|
||||
"template",
|
||||
"this",
|
||||
"thread_local",
|
||||
"throw",
|
||||
"true",
|
||||
"try",
|
||||
"typedef",
|
||||
"typeid",
|
||||
"typename",
|
||||
"union",
|
||||
"unsigned",
|
||||
"using",
|
||||
"virtual",
|
||||
"void",
|
||||
"volatile",
|
||||
"wchar_t",
|
||||
"while",
|
||||
"xor",
|
||||
"xor_eq",
|
||||
}
|
||||
return name in keywords
|
||||
|
||||
|
||||
@memoize
|
||||
def UnixName(name):
|
||||
'''Returns the unix_style name for a given lowerCamelCase string.
|
||||
@ -740,7 +735,7 @@ def UnixName(name):
|
||||
# Prepend an extra underscore to the |name|'s start if it doesn't start with a
|
||||
# letter or underscore to ensure the generated unix name follows C++
|
||||
# identifier rules.
|
||||
assert(name)
|
||||
assert (name)
|
||||
if name[0].isdigit():
|
||||
name = '_' + name
|
||||
|
||||
@ -811,10 +806,7 @@ def _GetFunctions(parent, json, namespace):
|
||||
"""
|
||||
functions = OrderedDict()
|
||||
for function_json in json.get('functions', []):
|
||||
function = Function(parent,
|
||||
function_json['name'],
|
||||
function_json,
|
||||
namespace,
|
||||
function = Function(parent, function_json['name'], function_json, namespace,
|
||||
Origin(from_json=True))
|
||||
functions[function.name] = function
|
||||
return functions
|
||||
@ -825,10 +817,7 @@ def _GetEvents(parent, json, namespace):
|
||||
"""
|
||||
events = OrderedDict()
|
||||
for event_json in json.get('events', []):
|
||||
event = Function(parent,
|
||||
event_json['name'],
|
||||
event_json,
|
||||
namespace,
|
||||
event = Function(parent, event_json['name'], event_json, namespace,
|
||||
Origin(from_client=True))
|
||||
events[event.name] = event
|
||||
return events
|
||||
@ -853,12 +842,13 @@ def _GetManifestKeysType(self, json):
|
||||
|
||||
# Create a dummy object to parse "manifest_keys" as a type.
|
||||
manifest_keys_type = {
|
||||
'type': 'object',
|
||||
'properties': json['manifest_keys'],
|
||||
'type': 'object',
|
||||
'properties': json['manifest_keys'],
|
||||
}
|
||||
return Type(self, 'ManifestKeys', manifest_keys_type, self,
|
||||
Origin(from_manifest_keys=True))
|
||||
|
||||
|
||||
def _GetWithDefaultChecked(self, json, key, default):
|
||||
if json.get(key) == default:
|
||||
raise ParseException(
|
||||
@ -867,7 +857,9 @@ def _GetWithDefaultChecked(self, json, key, default):
|
||||
% (key, default))
|
||||
return json.get(key, default)
|
||||
|
||||
|
||||
class _PlatformInfo(_Enum):
|
||||
|
||||
def __init__(self, name):
|
||||
_Enum.__init__(self, name)
|
||||
|
||||
|
@ -9,63 +9,62 @@ from model import Platforms
|
||||
import model
|
||||
import unittest
|
||||
|
||||
|
||||
class ModelTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.model = model.Model()
|
||||
self.permissions_json = CachedLoad('test/permissions.json')
|
||||
self.model.AddNamespace(self.permissions_json[0],
|
||||
'path/to/permissions.json')
|
||||
'path/to/permissions.json')
|
||||
self.permissions = self.model.namespaces.get('permissions')
|
||||
self.windows_json = CachedLoad('test/windows.json')
|
||||
self.model.AddNamespace(self.windows_json[0],
|
||||
'path/to/window.json')
|
||||
self.model.AddNamespace(self.windows_json[0], 'path/to/window.json')
|
||||
self.windows = self.model.namespaces.get('windows')
|
||||
self.tabs_json = CachedLoad('test/tabs.json')
|
||||
self.model.AddNamespace(self.tabs_json[0],
|
||||
'path/to/tabs.json')
|
||||
self.model.AddNamespace(self.tabs_json[0], 'path/to/tabs.json')
|
||||
self.tabs = self.model.namespaces.get('tabs')
|
||||
self.idl_chromeos = Load('test/idl_namespace_chromeos.idl')
|
||||
self.model.AddNamespace(self.idl_chromeos[0],
|
||||
'path/to/idl_namespace_chromeos.idl')
|
||||
'path/to/idl_namespace_chromeos.idl')
|
||||
self.idl_namespace_chromeos = self.model.namespaces.get(
|
||||
'idl_namespace_chromeos')
|
||||
self.idl_all_platforms = Load('test/idl_namespace_all_platforms.idl')
|
||||
self.model.AddNamespace(self.idl_all_platforms[0],
|
||||
'path/to/idl_namespace_all_platforms.idl')
|
||||
'path/to/idl_namespace_all_platforms.idl')
|
||||
self.idl_namespace_all_platforms = self.model.namespaces.get(
|
||||
'idl_namespace_all_platforms')
|
||||
self.idl_non_specific_platforms = Load(
|
||||
'test/idl_namespace_non_specific_platforms.idl')
|
||||
self.model.AddNamespace(self.idl_non_specific_platforms[0],
|
||||
'path/to/idl_namespace_non_specific_platforms.idl')
|
||||
'path/to/idl_namespace_non_specific_platforms.idl')
|
||||
self.idl_namespace_non_specific_platforms = self.model.namespaces.get(
|
||||
'idl_namespace_non_specific_platforms')
|
||||
self.returns_async_json = CachedLoad('test/returns_async.json')
|
||||
self.model.AddNamespace(self.returns_async_json[0],
|
||||
'path/to/returns_async.json')
|
||||
'path/to/returns_async.json')
|
||||
self.returns_async = self.model.namespaces.get('returns_async')
|
||||
self.idl_returns_async_idl = Load('test/idl_returns_async.idl')
|
||||
self.model.AddNamespace(self.idl_returns_async_idl[0],
|
||||
'path/to/idl_returns_async.idl')
|
||||
'path/to/idl_returns_async.idl')
|
||||
self.idl_returns_async = self.model.namespaces.get('idl_returns_async')
|
||||
self.nodoc_json = CachedLoad('test/namespace_nodoc.json')
|
||||
self.model.AddNamespace(self.nodoc_json[0],
|
||||
'path/to/namespace_nodoc.json')
|
||||
self.model.AddNamespace(self.nodoc_json[0], 'path/to/namespace_nodoc.json')
|
||||
self.nodoc = self.model.namespaces.get('nodoc')
|
||||
self.fakeapi_json = CachedLoad('test/namespace_fakeapi.json')
|
||||
self.model.AddNamespace(self.fakeapi_json[0],
|
||||
'path/to/namespace_fakeapi.json')
|
||||
'path/to/namespace_fakeapi.json')
|
||||
self.fakeapi = self.model.namespaces.get('fakeapi')
|
||||
|
||||
self.function_platforms_idl = Load('test/function_platforms.idl')
|
||||
self.model.AddNamespace(self.function_platforms_idl[0],
|
||||
'/path/to/function_platforms.idl')
|
||||
'/path/to/function_platforms.idl')
|
||||
self.function_platforms = self.model.namespaces.get('function_platforms')
|
||||
|
||||
self.function_platform_win_linux_json = CachedLoad(
|
||||
'test/function_platform_win_linux.json')
|
||||
self.model.AddNamespace(self.function_platform_win_linux_json[0],
|
||||
'path/to/function_platform_win_linux.json')
|
||||
'path/to/function_platform_win_linux.json')
|
||||
self.function_platform_win_linux = self.model.namespaces.get(
|
||||
'function_platform_win_linux')
|
||||
|
||||
@ -75,7 +74,7 @@ class ModelTest(unittest.TestCase):
|
||||
|
||||
def testHasFunctions(self):
|
||||
self.assertEqual(["contains", "getAll", "remove", "request"],
|
||||
sorted(self.permissions.functions.keys()))
|
||||
sorted(self.permissions.functions.keys()))
|
||||
|
||||
def testHasTypes(self):
|
||||
self.assertEqual(['Tab'], list(self.tabs.types.keys()))
|
||||
@ -83,41 +82,38 @@ class ModelTest(unittest.TestCase):
|
||||
self.assertEqual(['Window'], list(self.windows.types.keys()))
|
||||
|
||||
def testHasProperties(self):
|
||||
self.assertEqual(["active", "favIconUrl", "highlighted", "id",
|
||||
"incognito", "index", "pinned", "selected", "status", "title", "url",
|
||||
"windowId"],
|
||||
sorted(self.tabs.types['Tab'].properties.keys()))
|
||||
self.assertEqual([
|
||||
"active", "favIconUrl", "highlighted", "id", "incognito", "index",
|
||||
"pinned", "selected", "status", "title", "url", "windowId"
|
||||
], sorted(self.tabs.types['Tab'].properties.keys()))
|
||||
|
||||
def testProperties(self):
|
||||
string_prop = self.tabs.types['Tab'].properties['status']
|
||||
self.assertEqual(model.PropertyType.STRING,
|
||||
string_prop.type_.property_type)
|
||||
self.assertEqual(model.PropertyType.STRING, string_prop.type_.property_type)
|
||||
integer_prop = self.tabs.types['Tab'].properties['id']
|
||||
self.assertEqual(model.PropertyType.INTEGER,
|
||||
integer_prop.type_.property_type)
|
||||
integer_prop.type_.property_type)
|
||||
array_prop = self.windows.types['Window'].properties['tabs']
|
||||
self.assertEqual(model.PropertyType.ARRAY,
|
||||
array_prop.type_.property_type)
|
||||
self.assertEqual(model.PropertyType.ARRAY, array_prop.type_.property_type)
|
||||
self.assertEqual(model.PropertyType.REF,
|
||||
array_prop.type_.item_type.property_type)
|
||||
array_prop.type_.item_type.property_type)
|
||||
self.assertEqual('tabs.Tab', array_prop.type_.item_type.ref_type)
|
||||
object_prop = self.tabs.functions['query'].params[0]
|
||||
self.assertEqual(model.PropertyType.OBJECT,
|
||||
object_prop.type_.property_type)
|
||||
self.assertEqual(
|
||||
["active", "highlighted", "pinned", "status", "title", "url",
|
||||
"windowId", "windowType"],
|
||||
sorted(object_prop.type_.properties.keys()))
|
||||
self.assertEqual(model.PropertyType.OBJECT, object_prop.type_.property_type)
|
||||
self.assertEqual([
|
||||
"active", "highlighted", "pinned", "status", "title", "url", "windowId",
|
||||
"windowType"
|
||||
], sorted(object_prop.type_.properties.keys()))
|
||||
|
||||
def testChoices(self):
|
||||
self.assertEqual(model.PropertyType.CHOICES,
|
||||
self.tabs.functions['move'].params[0].type_.property_type)
|
||||
self.tabs.functions['move'].params[0].type_.property_type)
|
||||
|
||||
def testPropertyNotImplemented(self):
|
||||
(self.permissions_json[0]['types'][0]
|
||||
['properties']['permissions']['type']) = 'something'
|
||||
(self.permissions_json[0]['types'][0]['properties']['permissions']['type']
|
||||
) = 'something'
|
||||
self.assertRaises(model.ParseException, self.model.AddNamespace,
|
||||
self.permissions_json[0], 'path/to/something.json')
|
||||
self.permissions_json[0], 'path/to/something.json')
|
||||
|
||||
def testDefaultSpecifiedRedundantly(self):
|
||||
test_json = CachedLoad('test/redundant_default_attribute.json')
|
||||
@ -127,20 +123,16 @@ class ModelTest(unittest.TestCase):
|
||||
' in path/to/redundant_default_attribute.json\n'
|
||||
'The attribute "optional" is specified as "False", but this is the '
|
||||
'default value if the attribute is not included\. It should be '
|
||||
'removed\.',
|
||||
self.model.AddNamespace,
|
||||
test_json[0],
|
||||
'removed\.', self.model.AddNamespace, test_json[0],
|
||||
'path/to/redundant_default_attribute.json')
|
||||
|
||||
def testReturnsAsyncMissingParametersKey(self):
|
||||
test_json = CachedLoad('test/returns_async_missing_parameters_key.json')
|
||||
self.assertRaisesRegex(
|
||||
ValueError,
|
||||
'parameters key not specified on returns_async: '
|
||||
ValueError, 'parameters key not specified on returns_async: '
|
||||
'returnsAsyncMissingParametersKey.asyncNoParametersKey in '
|
||||
'path/to/returns_async_missing_parameters_key.json',
|
||||
self.model.AddNamespace,
|
||||
test_json[0],
|
||||
self.model.AddNamespace, test_json[0],
|
||||
'path/to/returns_async_missing_parameters_key.json')
|
||||
|
||||
def testDescription(self):
|
||||
@ -168,45 +160,44 @@ class ModelTest(unittest.TestCase):
|
||||
|
||||
def testUnixName(self):
|
||||
expectations = {
|
||||
'foo': 'foo',
|
||||
'fooBar': 'foo_bar',
|
||||
'fooBarBaz': 'foo_bar_baz',
|
||||
'fooBARBaz': 'foo_bar_baz',
|
||||
'fooBAR': 'foo_bar',
|
||||
'FOO': 'foo',
|
||||
'FOOBar': 'foo_bar',
|
||||
'foo.bar': 'foo_bar',
|
||||
'foo.BAR': 'foo_bar',
|
||||
'foo.barBAZ': 'foo_bar_baz',
|
||||
'foo_Bar_Baz_box': 'foo_bar_baz_box',
|
||||
}
|
||||
'foo': 'foo',
|
||||
'fooBar': 'foo_bar',
|
||||
'fooBarBaz': 'foo_bar_baz',
|
||||
'fooBARBaz': 'foo_bar_baz',
|
||||
'fooBAR': 'foo_bar',
|
||||
'FOO': 'foo',
|
||||
'FOOBar': 'foo_bar',
|
||||
'foo.bar': 'foo_bar',
|
||||
'foo.BAR': 'foo_bar',
|
||||
'foo.barBAZ': 'foo_bar_baz',
|
||||
'foo_Bar_Baz_box': 'foo_bar_baz_box',
|
||||
}
|
||||
for name in expectations:
|
||||
self.assertEqual(expectations[name], model.UnixName(name))
|
||||
|
||||
def testCamelName(self):
|
||||
expectations = {
|
||||
'foo': 'foo',
|
||||
'fooBar': 'fooBar',
|
||||
'foo_bar_baz': 'fooBarBaz',
|
||||
'FOO_BAR': 'FOOBar',
|
||||
'FOO_bar': 'FOOBar',
|
||||
'_bar': 'Bar',
|
||||
'_bar_baz': 'BarBaz',
|
||||
'bar_': 'bar',
|
||||
'bar_baz_': 'barBaz',
|
||||
}
|
||||
'foo': 'foo',
|
||||
'fooBar': 'fooBar',
|
||||
'foo_bar_baz': 'fooBarBaz',
|
||||
'FOO_BAR': 'FOOBar',
|
||||
'FOO_bar': 'FOOBar',
|
||||
'_bar': 'Bar',
|
||||
'_bar_baz': 'BarBaz',
|
||||
'bar_': 'bar',
|
||||
'bar_baz_': 'barBaz',
|
||||
}
|
||||
for testcase, expected in expectations.items():
|
||||
self.assertEqual(expected, model.CamelName(testcase))
|
||||
|
||||
def testPlatforms(self):
|
||||
self.assertEqual([Platforms.CHROMEOS],
|
||||
self.idl_namespace_chromeos.platforms)
|
||||
self.assertEqual(
|
||||
[Platforms.CHROMEOS, Platforms.FUCHSIA, Platforms.LINUX, Platforms.MAC,
|
||||
Platforms.WIN],
|
||||
self.idl_namespace_all_platforms.platforms)
|
||||
self.assertEqual(None,
|
||||
self.idl_namespace_non_specific_platforms.platforms)
|
||||
self.assertEqual([
|
||||
Platforms.CHROMEOS, Platforms.FUCHSIA, Platforms.LINUX, Platforms.MAC,
|
||||
Platforms.WIN
|
||||
], self.idl_namespace_all_platforms.platforms)
|
||||
self.assertEqual(None, self.idl_namespace_non_specific_platforms.platforms)
|
||||
|
||||
def testInvalidNamespacePlatform(self):
|
||||
invalid_namespace_platform = Load('test/invalid_platform_namespace.idl')
|
||||
@ -225,7 +216,7 @@ class ModelTest(unittest.TestCase):
|
||||
def testPlatformsOnFunctionsIDL(self):
|
||||
function_win_linux = self.function_platforms.functions['function_win_linux']
|
||||
self.assertEqual([Platforms.WIN, Platforms.LINUX],
|
||||
function_win_linux.platforms)
|
||||
function_win_linux.platforms)
|
||||
|
||||
function_all = self.function_platforms.functions['function_all']
|
||||
self.assertIsNone(function_all.platforms)
|
||||
@ -269,5 +260,6 @@ class ModelTest(unittest.TestCase):
|
||||
self.assertIn('Enum value cannot be an empty string',
|
||||
str(context.exception))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -18,7 +18,7 @@ def _GenerateFilenames(full_namespace):
|
||||
# 4. sub_name_space.idl,
|
||||
# 5. etc.
|
||||
sub_namespaces = full_namespace.split('.')
|
||||
filenames = [ ]
|
||||
filenames = []
|
||||
basename = None
|
||||
for namespace in reversed(sub_namespaces):
|
||||
if basename is not None:
|
||||
@ -39,6 +39,7 @@ class NamespaceResolver(object):
|
||||
used when searching for types.
|
||||
- |cpp_namespace_pattern| Default namespace pattern
|
||||
'''
|
||||
|
||||
def __init__(self, root, path, include_rules, cpp_namespace_pattern):
|
||||
self._root = root
|
||||
self._include_rules = [(path, cpp_namespace_pattern)] + include_rules
|
||||
@ -53,13 +54,12 @@ class NamespaceResolver(object):
|
||||
if cpp_namespace:
|
||||
cpp_namespace_environment = CppNamespaceEnvironment(cpp_namespace)
|
||||
for filename in reversed(filenames):
|
||||
filepath = os.path.join(path, filename);
|
||||
filepath = os.path.join(path, filename)
|
||||
if os.path.exists(os.path.join(self._root, filepath)):
|
||||
schema = SchemaLoader(self._root).LoadSchema(filepath)[0]
|
||||
return Model().AddNamespace(
|
||||
schema,
|
||||
filepath,
|
||||
environment=cpp_namespace_environment)
|
||||
return Model().AddNamespace(schema,
|
||||
filepath,
|
||||
environment=cpp_namespace_environment)
|
||||
return None
|
||||
|
||||
def ResolveType(self, full_name, default_namespace):
|
||||
|
@ -17,8 +17,8 @@ import optparse
|
||||
import os
|
||||
import shlex
|
||||
import urlparse
|
||||
from highlighters import (
|
||||
pygments_highlighter, none_highlighter, hilite_me_highlighter)
|
||||
from highlighters import (pygments_highlighter, none_highlighter,
|
||||
hilite_me_highlighter)
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
||||
from cpp_namespace_environment import CppNamespaceEnvironment
|
||||
from namespace_resolver import NamespaceResolver
|
||||
@ -27,6 +27,7 @@ from namespace_resolver import NamespaceResolver
|
||||
class CompilerHandler(BaseHTTPRequestHandler):
|
||||
"""A HTTPRequestHandler that outputs the result of tools/json_schema_compiler.
|
||||
"""
|
||||
|
||||
def do_GET(self):
|
||||
parsed_url = urlparse.urlparse(self.path)
|
||||
request_path = self._GetRequestPath(parsed_url)
|
||||
@ -64,38 +65,36 @@ class CompilerHandler(BaseHTTPRequestHandler):
|
||||
|
||||
Code panes are populated via XHR after links in the nav pane are clicked.
|
||||
"""
|
||||
(head.Append('<style>')
|
||||
.Append('body {')
|
||||
.Append(' margin: 0;')
|
||||
.Append('}')
|
||||
.Append('.pane {')
|
||||
.Append(' height: 100%;')
|
||||
.Append(' overflow-x: auto;')
|
||||
.Append(' overflow-y: scroll;')
|
||||
.Append(' display: inline-block;')
|
||||
.Append('}')
|
||||
.Append('#nav_pane {')
|
||||
.Append(' width: 20%;')
|
||||
.Append('}')
|
||||
.Append('#nav_pane ul {')
|
||||
.Append(' list-style-type: none;')
|
||||
.Append(' padding: 0 0 0 1em;')
|
||||
.Append('}')
|
||||
.Append('#cc_pane {')
|
||||
.Append(' width: 40%;')
|
||||
.Append('}')
|
||||
.Append('#h_pane {')
|
||||
.Append(' width: 40%;')
|
||||
.Append('}')
|
||||
(head.Append('<style>') \
|
||||
.Append('body {') \
|
||||
.Append(' margin: 0;') \
|
||||
.Append('}') \
|
||||
.Append('.pane {') \
|
||||
.Append(' height: 100%;') \
|
||||
.Append(' overflow-x: auto;') \
|
||||
.Append(' overflow-y: scroll;') \
|
||||
.Append(' display: inline-block;') \
|
||||
.Append('}') \
|
||||
.Append('#nav_pane {') \
|
||||
.Append(' width: 20%;') \
|
||||
.Append('}') \
|
||||
.Append('#nav_pane ul {') \
|
||||
.Append(' list-style-type: none;') \
|
||||
.Append(' padding: 0 0 0 1em;') \
|
||||
.Append('}') \
|
||||
.Append('#cc_pane {') \
|
||||
.Append(' width: 40%;') \
|
||||
.Append('}') \
|
||||
.Append('#h_pane {') \
|
||||
.Append(' width: 40%;') \
|
||||
.Append('}') \
|
||||
.Append('</style>')
|
||||
)
|
||||
|
||||
body.Append(
|
||||
'<div class="pane" id="nav_pane">%s</div>'
|
||||
'<div class="pane" id="h_pane"></div>'
|
||||
'<div class="pane" id="cc_pane"></div>' %
|
||||
self._RenderNavPane(parsed_url.path[1:])
|
||||
)
|
||||
body.Append('<div class="pane" id="nav_pane">%s</div>'
|
||||
'<div class="pane" id="h_pane"></div>'
|
||||
'<div class="pane" id="cc_pane"></div>' %
|
||||
self._RenderNavPane(parsed_url.path[1:]))
|
||||
|
||||
# The Javascript that interacts with the nav pane and panes to show the
|
||||
# compiled files as the URL or highlighting options change.
|
||||
@ -187,41 +186,39 @@ updateEverything();
|
||||
(file_root, file_ext) = os.path.splitext(request_path)
|
||||
(filedir, filename) = os.path.split(file_root)
|
||||
|
||||
namespace_resolver = NamespaceResolver("./",
|
||||
filedir,
|
||||
namespace_resolver = NamespaceResolver("./", filedir,
|
||||
self.server.include_rules,
|
||||
self.server.cpp_namespace_pattern)
|
||||
try:
|
||||
# Get main file.
|
||||
namespace = namespace_resolver.ResolveNamespace(filename)
|
||||
type_generator = cpp_type_generator.CppTypeGenerator(
|
||||
api_model,
|
||||
namespace_resolver,
|
||||
namespace)
|
||||
api_model, namespace_resolver, namespace)
|
||||
|
||||
# Generate code
|
||||
if file_ext == '.h':
|
||||
cpp_code = (h_generator.HGenerator(type_generator)
|
||||
.Generate(namespace).Render())
|
||||
cpp_code = (
|
||||
h_generator.HGenerator(type_generator).Generate(namespace).Render())
|
||||
elif file_ext == '.cc':
|
||||
cpp_code = (cc_generator.CCGenerator(type_generator)
|
||||
.Generate(namespace).Render())
|
||||
cpp_code = (cc_generator.CCGenerator(type_generator).Generate(
|
||||
namespace).Render())
|
||||
else:
|
||||
self.send_error(404, "File not found: %s" % request_path)
|
||||
return
|
||||
|
||||
# Do highlighting on the generated code
|
||||
(highlighter_param, style_param) = self._GetHighlighterParams(parsed_url)
|
||||
head.Append('<style>' +
|
||||
head.Append(
|
||||
'<style>' +
|
||||
self.server.highlighters[highlighter_param].GetCSS(style_param) +
|
||||
'</style>')
|
||||
body.Append(self.server.highlighters[highlighter_param]
|
||||
.GetCodeElement(cpp_code, style_param))
|
||||
body.Append(self.server.highlighters[highlighter_param].GetCodeElement(
|
||||
cpp_code, style_param))
|
||||
except IOError:
|
||||
self.send_error(404, "File not found: %s" % request_path)
|
||||
return
|
||||
except (TypeError, KeyError, AttributeError,
|
||||
AssertionError, NotImplementedError) as error:
|
||||
except (TypeError, KeyError, AttributeError, AssertionError,
|
||||
NotImplementedError) as error:
|
||||
body.Append('<pre>')
|
||||
body.Append('compiler error: %s' % error)
|
||||
body.Append('Check server log for more details')
|
||||
@ -233,7 +230,7 @@ updateEverything();
|
||||
"""
|
||||
query_dict = urlparse.parse_qs(parsed_url.query)
|
||||
return (query_dict.get('highlighter', ['pygments'])[0],
|
||||
query_dict.get('style', ['colorful'])[0])
|
||||
query_dict.get('style', ['colorful'])[0])
|
||||
|
||||
def _RenderNavPane(self, path):
|
||||
"""Renders an HTML nav pane.
|
||||
@ -248,7 +245,7 @@ updateEverything();
|
||||
html.Append('<select id="highlighters" onChange="updateEverything()">')
|
||||
for name, highlighter in self.server.highlighters.items():
|
||||
html.Append('<option value="%s">%s</option>' %
|
||||
(name, highlighter.DisplayName()))
|
||||
(name, highlighter.DisplayName()))
|
||||
html.Append('</select>')
|
||||
|
||||
html.Append('<br/>')
|
||||
@ -289,8 +286,7 @@ updateEverything();
|
||||
html.Append('<li><a href="/%s/">%s/</a>' % (full_path, filename))
|
||||
elif file_ext in ['.json', '.idl']:
|
||||
# cc/h panes will automatically update via the hash change event.
|
||||
html.Append('<li><a href="#%s">%s</a>' %
|
||||
(filename, filename))
|
||||
html.Append('<li><a href="#%s">%s</a>' % (filename, filename))
|
||||
|
||||
html.Append('</ul>')
|
||||
|
||||
@ -298,11 +294,8 @@ updateEverything();
|
||||
|
||||
|
||||
class PreviewHTTPServer(HTTPServer, object):
|
||||
def __init__(self,
|
||||
server_address,
|
||||
handler,
|
||||
highlighters,
|
||||
include_rules,
|
||||
|
||||
def __init__(self, server_address, handler, highlighters, include_rules,
|
||||
cpp_namespace_pattern):
|
||||
super(PreviewHTTPServer, self).__init__(server_address, handler)
|
||||
self.highlighters = highlighters
|
||||
@ -314,11 +307,18 @@ if __name__ == '__main__':
|
||||
parser = optparse.OptionParser(
|
||||
description='Runs a server to preview the json_schema_compiler output.',
|
||||
usage='usage: %prog [option]...')
|
||||
parser.add_option('-p', '--port', default='8000',
|
||||
help='port to run the server on')
|
||||
parser.add_option('-n', '--namespace', default='generated_api_schemas',
|
||||
parser.add_option('-p',
|
||||
'--port',
|
||||
default='8000',
|
||||
help='port to run the server on')
|
||||
parser.add_option(
|
||||
'-n',
|
||||
'--namespace',
|
||||
default='generated_api_schemas',
|
||||
help='C++ namespace for generated files. e.g extensions::api.')
|
||||
parser.add_option('-I', '--include-rules',
|
||||
parser.add_option(
|
||||
'-I',
|
||||
'--include-rules',
|
||||
help='A list of paths to include when searching for referenced objects,'
|
||||
' with the namespace separated by a \':\'. Example: '
|
||||
'/foo/bar:Foo::Bar::%(namespace)s')
|
||||
@ -344,19 +344,16 @@ if __name__ == '__main__':
|
||||
print('')
|
||||
|
||||
highlighters = {
|
||||
'hilite': hilite_me_highlighter.HiliteMeHighlighter(),
|
||||
'none': none_highlighter.NoneHighlighter()
|
||||
'hilite': hilite_me_highlighter.HiliteMeHighlighter(),
|
||||
'none': none_highlighter.NoneHighlighter()
|
||||
}
|
||||
try:
|
||||
highlighters['pygments'] = pygments_highlighter.PygmentsHighlighter()
|
||||
except ImportError as e:
|
||||
pass
|
||||
|
||||
server = PreviewHTTPServer(('', int(opts.port)),
|
||||
CompilerHandler,
|
||||
highlighters,
|
||||
include_rules,
|
||||
opts.namespace)
|
||||
server = PreviewHTTPServer(('', int(opts.port)), CompilerHandler,
|
||||
highlighters, include_rules, opts.namespace)
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
server.socket.close()
|
||||
|
@ -8,10 +8,12 @@ import sys
|
||||
import idl_schema
|
||||
import json_schema
|
||||
|
||||
|
||||
class SchemaLoader(object):
|
||||
'''Loads a schema from a provided filename.
|
||||
|root|: path to the root directory.
|
||||
'''
|
||||
|
||||
def __init__(self, root):
|
||||
self._root = root
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
"""Utilies for the processing of schema python structures.
|
||||
"""
|
||||
|
||||
|
||||
def CapitalizeFirstLetter(value):
|
||||
return value[0].capitalize() + value[1:]
|
||||
|
||||
|
@ -7,7 +7,9 @@ from schema_util import JsFunctionNameToClassName
|
||||
from schema_util import StripNamespace
|
||||
import unittest
|
||||
|
||||
|
||||
class SchemaUtilTest(unittest.TestCase):
|
||||
|
||||
def testStripNamespace(self):
|
||||
self.assertEqual('Bar', StripNamespace('foo.Bar'))
|
||||
self.assertEqual('Baz', StripNamespace('Baz'))
|
||||
@ -15,11 +17,11 @@ class SchemaUtilTest(unittest.TestCase):
|
||||
def testJsFunctionNameToClassName(self):
|
||||
self.assertEqual('FooBar', JsFunctionNameToClassName('foo', 'bar'))
|
||||
self.assertEqual('FooBar',
|
||||
JsFunctionNameToClassName('experimental.foo', 'bar'))
|
||||
JsFunctionNameToClassName('experimental.foo', 'bar'))
|
||||
self.assertEqual('FooBarBaz', JsFunctionNameToClassName('foo.bar', 'baz'))
|
||||
self.assertEqual('FooBarBaz',
|
||||
JsFunctionNameToClassName('foo.bar', 'baz'))
|
||||
self.assertEqual('FooBarBaz',
|
||||
JsFunctionNameToClassName('experimental.foo.bar', 'baz'))
|
||||
JsFunctionNameToClassName('experimental.foo.bar', 'baz'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -1,7 +1,6 @@
|
||||
# Copyright 2023 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
"""Generator that produces a definition file for typescript.
|
||||
|
||||
Note: This is a work in progress, and generated definitions may need tweaking.
|
||||
@ -19,7 +18,6 @@ from js_util import JsUtil
|
||||
from model import *
|
||||
from schema_util import *
|
||||
|
||||
|
||||
CHROMIUM_SRC = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
|
||||
@ -59,8 +57,7 @@ class _Generator(object):
|
||||
# If events are needed, add the import.
|
||||
if self._events_required:
|
||||
main_code.Substitute(
|
||||
{"imports": "import {ChromeEvent} from './chrome_event.js';"}
|
||||
)
|
||||
{"imports": "import {ChromeEvent} from './chrome_event.js';"})
|
||||
else:
|
||||
main_code.Substitute({"imports": ""})
|
||||
main_code = self._ClangFormat(main_code)
|
||||
@ -111,10 +108,8 @@ class _Generator(object):
|
||||
type_name = self._ExtractType(prop.type_)
|
||||
# If the ref type has additional properties, do a namespace merge.
|
||||
prop_type: Type = prop.type_
|
||||
if (
|
||||
len(prop_type.properties) > 0
|
||||
and prop_type.property_type == PropertyType.REF
|
||||
):
|
||||
if (len(prop_type.properties) > 0
|
||||
and prop_type.property_type == PropertyType.REF):
|
||||
type_name = self._AppendInterfaceForProperty(c, prop, type_name)
|
||||
c.Append(f"export const {prop.name}: {type_name};")
|
||||
c.Append()
|
||||
@ -143,9 +138,8 @@ class _Generator(object):
|
||||
|
||||
# This appends an local only interface to allow for additional
|
||||
# properties on an already defined type.
|
||||
def _AppendInterfaceForProperty(
|
||||
self, c: Code, prop: Property, prop_type_name
|
||||
):
|
||||
def _AppendInterfaceForProperty(self, c: Code, prop: Property,
|
||||
prop_type_name):
|
||||
if prop.deprecated:
|
||||
return
|
||||
prop_type = prop.type_
|
||||
@ -177,16 +171,15 @@ class _Generator(object):
|
||||
# Type alias
|
||||
c.Append(f"export type {type.name} = {type.property_type.name};")
|
||||
c.Append()
|
||||
elif (type.property_type is PropertyType.ARRAY or
|
||||
type.property_type is PropertyType.CHOICES) :
|
||||
elif (type.property_type is PropertyType.ARRAY
|
||||
or type.property_type is PropertyType.CHOICES):
|
||||
ts_type = self._ExtractType(type)
|
||||
c.Append(f"export type {type.name} = {ts_type};")
|
||||
c.Append()
|
||||
else:
|
||||
# Adding this for things we may not have accounted for here.
|
||||
c.Append(
|
||||
f"// TODO({os.getlogin()}) -- {type.name}: {type.property_type.name}"
|
||||
)
|
||||
f"// TODO({os.getlogin()}) -- {type.name}: {type.property_type.name}")
|
||||
|
||||
def _AppendInterface(self, c: Code, interface: Type):
|
||||
c.Sblock(f"export interface {interface.name} {{")
|
||||
@ -231,8 +224,8 @@ class _Generator(object):
|
||||
ret_type = "void"
|
||||
if func.returns is not None:
|
||||
ret_type = self._ExtractType(func.returns)
|
||||
elif (func.returns_async is not None and
|
||||
func.returns_async.can_return_promise):
|
||||
elif (func.returns_async is not None
|
||||
and func.returns_async.can_return_promise):
|
||||
ret_type = f"Promise<{self._ExtractPromiseType(func.returns_async)}>"
|
||||
return ret_type
|
||||
|
||||
@ -328,9 +321,8 @@ class _Generator(object):
|
||||
|
||||
# When the return async isn't a promise, we append it as a return callback
|
||||
# at the end of the parameters.
|
||||
use_callback = (
|
||||
func.returns_async and not func.returns_async.can_return_promise
|
||||
)
|
||||
use_callback = (func.returns_async
|
||||
and not func.returns_async.can_return_promise)
|
||||
if use_callback:
|
||||
callback_params = self._ExtractParams(func.returns_async.params)
|
||||
if param_str:
|
||||
@ -378,24 +370,21 @@ class _Generator(object):
|
||||
def _ClangFormat(self, c: Code, level=0):
|
||||
# temp = tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".js")
|
||||
# f_name = temp.name
|
||||
with tempfile.NamedTemporaryFile(
|
||||
"w", encoding="utf-8", suffix=".js", delete=False
|
||||
) as f:
|
||||
with tempfile.NamedTemporaryFile("w",
|
||||
encoding="utf-8",
|
||||
suffix=".js",
|
||||
delete=False) as f:
|
||||
f.write(c.Render())
|
||||
f_name = f.name
|
||||
script_path = self._GetChromiumClangFormatScriptPath()
|
||||
style_path = self._GetChromiumClangFormatStylePath()
|
||||
cmd = (
|
||||
f'python3 {script_path} --fallback-style=none '
|
||||
f'--style=file:{style_path} "{f_name}"'
|
||||
)
|
||||
p = subprocess.Popen(
|
||||
cmd,
|
||||
cwd=CHROMIUM_SRC,
|
||||
encoding="utf-8",
|
||||
shell=True,
|
||||
stdout=subprocess.PIPE
|
||||
)
|
||||
cmd = (f'python3 {script_path} --fallback-style=none '
|
||||
f'--style=file:{style_path} "{f_name}"')
|
||||
p = subprocess.Popen(cmd,
|
||||
cwd=CHROMIUM_SRC,
|
||||
encoding="utf-8",
|
||||
shell=True,
|
||||
stdout=subprocess.PIPE)
|
||||
out = p.communicate()[0]
|
||||
out_code = Code()
|
||||
out_code.Append(out)
|
||||
|
@ -17,11 +17,8 @@ class TsDefinitionGeneratorTest(unittest.TestCase):
|
||||
def _GetNamespace(self, fake_content, filename):
|
||||
"""Returns a namespace object for the given content"""
|
||||
is_idl = filename.endswith('.idl')
|
||||
api_def = (
|
||||
idl_schema.Process(fake_content, filename)
|
||||
if is_idl
|
||||
else json_parse.Parse(fake_content)
|
||||
)
|
||||
api_def = (idl_schema.Process(fake_content, filename)
|
||||
if is_idl else json_parse.Parse(fake_content))
|
||||
m = model.Model()
|
||||
return m.AddNamespace(api_def[0], filename)
|
||||
|
||||
|
@ -9,14 +9,15 @@ class UtilCCHelper(object):
|
||||
"""A util class that generates code that uses
|
||||
tools/json_schema_compiler/util.cc.
|
||||
"""
|
||||
|
||||
def __init__(self, type_manager):
|
||||
self._type_manager = type_manager
|
||||
|
||||
def PopulateArrayFromListFunction(self, optional):
|
||||
"""Returns the function to turn a list into a vector.
|
||||
"""
|
||||
populate_list_fn = ('PopulateOptionalArrayFromList' if optional
|
||||
else 'PopulateArrayFromList')
|
||||
populate_list_fn = ('PopulateOptionalArrayFromList'
|
||||
if optional else 'PopulateArrayFromList')
|
||||
return ('%s::%s') % (_API_UTIL_NAMESPACE, populate_list_fn)
|
||||
|
||||
def CreateValueFromArray(self, src):
|
||||
@ -29,8 +30,8 @@ class UtilCCHelper(object):
|
||||
def AppendToContainer(self, container, value):
|
||||
"""Appends |value| to |container|.
|
||||
"""
|
||||
return '%s::AppendToContainer(%s, %s);' % (
|
||||
_API_UTIL_NAMESPACE, container, value)
|
||||
return '%s::AppendToContainer(%s, %s);' % (_API_UTIL_NAMESPACE, container,
|
||||
value)
|
||||
|
||||
def GetIncludePath(self):
|
||||
return '#include "tools/json_schema_compiler/util.h"'
|
||||
|
@ -21,9 +21,8 @@ from json_parse import OrderedDict
|
||||
|
||||
# idl_parser expects to be able to import certain files in its directory,
|
||||
# so let's set things up the way it wants.
|
||||
_idl_generators_path = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)), os.pardir, os.pardir, 'tools'
|
||||
)
|
||||
_idl_generators_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
|
||||
os.pardir, os.pardir, 'tools')
|
||||
if _idl_generators_path in sys.path:
|
||||
from idl_parser import idl_parser, idl_lexer, idl_node
|
||||
else:
|
||||
@ -40,8 +39,7 @@ class SchemaCompilerError(Exception):
|
||||
|
||||
def __init__(self, message: str, node: IDLNode):
|
||||
super().__init__(
|
||||
node.GetLogLine(f'Error processing node {node}: {message}')
|
||||
)
|
||||
node.GetLogLine(f'Error processing node {node}: {message}'))
|
||||
|
||||
|
||||
def GetChildWithName(node: IDLNode, name: str) -> Optional[IDLNode]:
|
||||
@ -56,8 +54,7 @@ def GetChildWithName(node: IDLNode, name: str) -> Optional[IDLNode]:
|
||||
name was not found.
|
||||
"""
|
||||
return next(
|
||||
(child for child in node.GetChildren() if child.GetName() == name), None
|
||||
)
|
||||
(child for child in node.GetChildren() if child.GetName() == name), None)
|
||||
|
||||
|
||||
def GetTypeName(node: IDLNode) -> str:
|
||||
@ -76,8 +73,7 @@ def GetTypeName(node: IDLNode) -> str:
|
||||
if child_node.GetClass() == 'Type':
|
||||
return child_node.GetOneOf('Typeref').GetName()
|
||||
raise SchemaCompilerError(
|
||||
'Could not find Type node when looking for Typeref name.', node
|
||||
)
|
||||
'Could not find Type node when looking for Typeref name.', node)
|
||||
|
||||
|
||||
def GetExtendedAttributes(node: IDLNode) -> Optional[List[IDLNode]]:
|
||||
@ -111,9 +107,8 @@ class Type:
|
||||
|
||||
def __init__(self, node: IDLNode, additional_properties: dict) -> None:
|
||||
assert node.GetClass() == 'Type', node.GetLogLine(
|
||||
'Attempted to process a "Type" node, but was passed a "%s" node.'
|
||||
% (node.GetClass())
|
||||
)
|
||||
'Attempted to process a "Type" node, but was passed a "%s" node.' %
|
||||
(node.GetClass()))
|
||||
self.node = node
|
||||
self.additional_properties = additional_properties
|
||||
|
||||
@ -136,13 +131,11 @@ class Type:
|
||||
properties['type'] = 'string'
|
||||
else:
|
||||
raise SchemaCompilerError(
|
||||
'Unsupported basic type found when processing type.', basic_type
|
||||
)
|
||||
'Unsupported basic type found when processing type.', basic_type)
|
||||
else:
|
||||
unknown_child = self.node.GetChildren()[0]
|
||||
raise SchemaCompilerError(
|
||||
'Unsupported type class when processing type.', unknown_child
|
||||
)
|
||||
raise SchemaCompilerError('Unsupported type class when processing type.',
|
||||
unknown_child)
|
||||
|
||||
return properties
|
||||
|
||||
@ -241,8 +234,7 @@ class IDLSchema:
|
||||
browser_node = GetChildWithName(self.idl, 'Browser')
|
||||
if browser_node is None or browser_node.GetClass() != 'Interface':
|
||||
raise SchemaCompilerError(
|
||||
'Required partial Browser interface not found in schema.', self.idl
|
||||
)
|
||||
'Required partial Browser interface not found in schema.', self.idl)
|
||||
|
||||
# The 'Browser' Interface has one attribute describing the name this API is
|
||||
# exposed on.
|
||||
@ -298,9 +290,8 @@ def Main():
|
||||
for i, char in enumerate(contents):
|
||||
if not char.isascii():
|
||||
raise Exception(
|
||||
'Non-ascii character "%s" (ord %d) found at offset %d.'
|
||||
% (char, ord(char), i)
|
||||
)
|
||||
'Non-ascii character "%s" (ord %d) found at offset %d.' %
|
||||
(char, ord(char), i))
|
||||
idl = idl_parser.IDLParser().ParseData(contents, '<stdin>')
|
||||
schema = IDLSchema(idl).process()
|
||||
print(json.dumps(schema, indent=2))
|
||||
|
@ -36,19 +36,31 @@ class WebIdlSchemaTest(unittest.TestCase):
|
||||
getReturns(schema, 'returnsVoid'),
|
||||
)
|
||||
self.assertEqual(
|
||||
{'name': 'returnsBoolean', 'type': 'boolean'},
|
||||
{
|
||||
'name': 'returnsBoolean',
|
||||
'type': 'boolean'
|
||||
},
|
||||
getReturns(schema, 'returnsBoolean'),
|
||||
)
|
||||
self.assertEqual(
|
||||
{'name': 'returnsDouble', 'type': 'number'},
|
||||
{
|
||||
'name': 'returnsDouble',
|
||||
'type': 'number'
|
||||
},
|
||||
getReturns(schema, 'returnsDouble'),
|
||||
)
|
||||
self.assertEqual(
|
||||
{'name': 'returnsLong', 'type': 'integer'},
|
||||
{
|
||||
'name': 'returnsLong',
|
||||
'type': 'integer'
|
||||
},
|
||||
getReturns(schema, 'returnsLong'),
|
||||
)
|
||||
self.assertEqual(
|
||||
{'name': 'returnsDOMString', 'type': 'string'},
|
||||
{
|
||||
'name': 'returnsDOMString',
|
||||
'type': 'string'
|
||||
},
|
||||
getReturns(schema, 'returnsDOMString'),
|
||||
)
|
||||
|
||||
@ -62,8 +74,7 @@ class WebIdlSchemaTest(unittest.TestCase):
|
||||
def testMissingBrowserInterface(self):
|
||||
expected_error_regex = (
|
||||
'.* File\(test\/web_idl\/missing_browser_interface.idl\): Required'
|
||||
' partial Browser interface not found in schema\.'
|
||||
)
|
||||
' partial Browser interface not found in schema\.')
|
||||
self.assertRaisesRegex(
|
||||
SchemaCompilerError,
|
||||
expected_error_regex,
|
||||
@ -76,8 +87,7 @@ class WebIdlSchemaTest(unittest.TestCase):
|
||||
def testMissingAttributeOnBrowser(self):
|
||||
expected_error_regex = (
|
||||
'.* Interface\(Browser\): The partial Browser interface should have'
|
||||
' exactly one attribute for the name the API will be exposed under\.'
|
||||
)
|
||||
' exactly one attribute for the name the API will be exposed under\.')
|
||||
self.assertRaisesRegex(
|
||||
Exception,
|
||||
expected_error_regex,
|
||||
@ -90,8 +100,7 @@ class WebIdlSchemaTest(unittest.TestCase):
|
||||
def testUnsupportedBasicType(self):
|
||||
expected_error_regex = (
|
||||
'.* PrimitiveType\(float\): Unsupported basic type found when'
|
||||
' processing type\.'
|
||||
)
|
||||
' processing type\.')
|
||||
self.assertRaisesRegex(
|
||||
SchemaCompilerError,
|
||||
expected_error_regex,
|
||||
@ -103,8 +112,7 @@ class WebIdlSchemaTest(unittest.TestCase):
|
||||
# doesn't support yet throws an error.
|
||||
def testUnsupportedTypeClass(self):
|
||||
expected_error_regex = (
|
||||
'.* Any\(\): Unsupported type class when processing type\.'
|
||||
)
|
||||
'.* Any\(\): Unsupported type class when processing type\.')
|
||||
self.assertRaisesRegex(
|
||||
SchemaCompilerError,
|
||||
expected_error_regex,
|
||||
|
Loading…
x
Reference in New Issue
Block a user