diff options
author | kalman@chromium.org <kalman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-19 04:22:38 +0000 |
---|---|---|
committer | kalman@chromium.org <kalman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-19 04:22:38 +0000 |
commit | 116f0a3aa63c5b508ce7d5e9aad1bbb7ab2921a6 (patch) | |
tree | 4051fe248e9794c97d8450dcc1c53c6f0cd13c44 /tools/json_schema_compiler | |
parent | 5eae551d867c6da7e1ed363110f02f89f99970d9 (diff) | |
download | chromium_src-116f0a3aa63c5b508ce7d5e9aad1bbb7ab2921a6.zip chromium_src-116f0a3aa63c5b508ce7d5e9aad1bbb7ab2921a6.tar.gz chromium_src-116f0a3aa63c5b508ce7d5e9aad1bbb7ab2921a6.tar.bz2 |
Generate storage API constants using json_schema_compiler.
TEST=unit_tests --gtest_filter=*JsonSchema*
Review URL: http://codereview.chromium.org/10116015
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@132945 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/json_schema_compiler')
-rw-r--r-- | tools/json_schema_compiler/cc_generator.py | 45 | ||||
-rw-r--r-- | tools/json_schema_compiler/cpp_type_generator.py | 42 | ||||
-rw-r--r-- | tools/json_schema_compiler/h_generator.py | 32 | ||||
-rw-r--r-- | tools/json_schema_compiler/model.py | 147 | ||||
-rwxr-xr-x | tools/json_schema_compiler/model_test.py | 8 |
5 files changed, 184 insertions, 90 deletions
diff --git a/tools/json_schema_compiler/cc_generator.py b/tools/json_schema_compiler/cc_generator.py index 138f6bf..7f7cb56 100644 --- a/tools/json_schema_compiler/cc_generator.py +++ b/tools/json_schema_compiler/cc_generator.py @@ -2,9 +2,9 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +from code import Code from model import PropertyType import any_helper -import code import cpp_util import model import sys @@ -23,9 +23,9 @@ class CCGenerator(object): self._any_helper = any_helper.AnyHelper() def Generate(self): - """Generates a code.Code object with the .cc for a single namespace. + """Generates a Code object with the .cc for a single namespace. """ - c = code.Code() + c = Code() (c.Append(cpp_util.CHROMIUM_LICENSE) .Append() .Append(cpp_util.GENERATED_FILE_MESSAGE % self._namespace.source_file) @@ -50,6 +50,19 @@ class CCGenerator(object): .Concat(self._cpp_type_generator.GetNamespaceStart()) .Append() ) + if self._namespace.properties: + (c.Append('//') + .Append('// Properties') + .Append('//') + .Append() + ) + for property in self._namespace.properties.values(): + property_code = self._cpp_type_generator.GeneratePropertyValues( + property, + 'const %(type)s %(name)s = %(value)s;', + nodoc=True) + if property_code: + c.Concat(property_code).Append() if self._namespace.types: (c.Append('//') .Append('// Types') @@ -82,7 +95,7 @@ class CCGenerator(object): """Generates the function definitions for a type. """ classname = cpp_util.Classname(type_.name) - c = code.Code() + c = Code() if type_.functions: # Types with functions are not instantiable in C++ because they are @@ -151,7 +164,7 @@ class CCGenerator(object): else: s = '' s = s + ' {}' - return code.Code().Append(s) + return Code().Append(s) def _GenerateTypePopulate(self, cpp_namespace, type_): """Generates the function for populating a type given a pointer to it. @@ -159,7 +172,7 @@ class CCGenerator(object): E.g for type "Foo", generates Foo::Populate() """ classname = cpp_util.Classname(type_.name) - c = code.Code() + c = Code() (c.Append('// static') .Sblock('bool %(namespace)s::Populate' '(const Value& value, %(name)s* out) {') @@ -194,7 +207,7 @@ class CCGenerator(object): src: DictionaryValue* dst: Type* """ - c = code.Code() + c = Code() value_var = prop.unix_name + '_value' c.Append('Value* %(value_var)s = NULL;') if prop.optional: @@ -221,7 +234,7 @@ class CCGenerator(object): E.g. for type "Foo" generates Foo::ToValue() """ - c = code.Code() + c = Code() (c.Sblock('scoped_ptr<DictionaryValue> %s::ToValue() const {' % cpp_namespace) .Append('scoped_ptr<DictionaryValue> value(new DictionaryValue());') @@ -252,7 +265,7 @@ class CCGenerator(object): def _GenerateFunction(self, cpp_namespace, function): """Generates the definitions for function structs. """ - c = code.Code() + c = Code() # Params::Populate function if function.params: @@ -277,7 +290,7 @@ class CCGenerator(object): """Generates CreateEnumValue() that returns the |StringValue| representation of an enum. """ - c = code.Code() + c = Code() c.Append('// static') c.Sblock('scoped_ptr<Value> %(cpp_namespace)s::CreateEnumValue(%(arg)s) {') c.Sblock('switch (%s) {' % prop.unix_name) @@ -353,7 +366,7 @@ class CCGenerator(object): """Generates a check for the correct number of arguments when creating Params. """ - c = code.Code() + c = Code() num_required = 0 for param in function.params: if not param.optional: @@ -379,7 +392,7 @@ class CCGenerator(object): E.g for function "Bar", generate Bar::Params::Create() """ - c = code.Code() + c = Code() (c.Append('// static') .Sblock('scoped_ptr<%(cpp_namespace)s::Params> ' '%(cpp_namespace)s::Params::Create(const ListValue& args) {') @@ -434,7 +447,7 @@ class CCGenerator(object): |value_var| check_type: if true, will check if |value_var| is the correct Value::Type """ - c = code.Code() + c = Code() c.Sblock('{') if check_type and prop.type_ not in ( @@ -544,7 +557,7 @@ class CCGenerator(object): """Generate the functions for structures generated by a property such as CreateEnumValue for ENUMs and Populate/ToValue for Params/Result objects. """ - c = code.Code() + c = Code() for param in params: if param.type_ == PropertyType.OBJECT: c.Concat(self._GenerateType( @@ -564,7 +577,7 @@ class CCGenerator(object): E.g for function "Bar", generate Bar::Result::Create """ - c = code.Code() + c = Code() params = function.callback.params if not params: @@ -609,7 +622,7 @@ class CCGenerator(object): dst: Type* """ - c = code.Code() + c = Code() if prop.type_ in (PropertyType.ENUM, PropertyType.CHOICES): if prop.optional: prop_name = prop.unix_name diff --git a/tools/json_schema_compiler/cpp_type_generator.py b/tools/json_schema_compiler/cpp_type_generator.py index c73589e..a8719af 100644 --- a/tools/json_schema_compiler/cpp_type_generator.py +++ b/tools/json_schema_compiler/cpp_type_generator.py @@ -263,3 +263,45 @@ class CppTypeGenerator(object): for p in prop.properties.values(): deps |= self._PropertyTypeDependencies(p) return deps + + def GeneratePropertyValues(self, property, line, nodoc=False): + """Generates the Code to display all value-containing properties. + """ + c = Code() + if not nodoc: + c.Comment(property.description) + + if property.has_value: + c.Append(line % { + "type": self._GetPrimitiveType(property.type_), + "name": property.name, + "value": property.value + }) + else: + has_child_code = False + c.Sblock('namespace %s {' % property.name) + for child_property in property.properties.values(): + child_code = self.GeneratePropertyValues( + child_property, + line, + nodoc=nodoc) + if child_code: + has_child_code = True + c.Concat(child_code) + c.Eblock('} // namespace %s' % property.name) + if not has_child_code: + c = None + return c + + def _GetPrimitiveType(self, type_): + """Like |GetType| but only accepts and returns C++ primitive types. + """ + if type_ == PropertyType.BOOLEAN: + return 'bool' + elif type_ == PropertyType.INTEGER: + return 'int' + elif type_ == PropertyType.DOUBLE: + return 'double' + elif type_ == PropertyType.STRING: + return 'char*' + raise Exception(type_ + ' is not primitive') diff --git a/tools/json_schema_compiler/h_generator.py b/tools/json_schema_compiler/h_generator.py index dd07f4f..35e8248 100644 --- a/tools/json_schema_compiler/h_generator.py +++ b/tools/json_schema_compiler/h_generator.py @@ -2,8 +2,8 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +from code import Code from model import PropertyType -import code import cpp_util import model import os @@ -18,9 +18,9 @@ class HGenerator(object): self._cpp_type_generator.GetCppNamespaceName(self._namespace)) def Generate(self): - """Generates a code.Code object with the .h for a single namespace. + """Generates a Code object with the .h for a single namespace. """ - c = code.Code() + c = Code() (c.Append(cpp_util.CHROMIUM_LICENSE) .Append() .Append(cpp_util.GENERATED_FILE_MESSAGE % self._namespace.source_file) @@ -59,6 +59,18 @@ class HGenerator(object): c.Concat(self._cpp_type_generator.GetNamespaceStart()) c.Append() + if self._namespace.properties: + (c.Append('//') + .Append('// Properties') + .Append('//') + .Append() + ) + for property in self._namespace.properties.values(): + property_code = self._cpp_type_generator.GeneratePropertyValues( + property, + 'extern const %(type)s %(name)s;') + if property_code: + c.Concat(property_code).Append() if self._namespace.types: (c.Append('//') .Append('// Types') @@ -111,7 +123,7 @@ class HGenerator(object): """Generate the declaration of a C++ enum for the given property and values. """ - c = code.Code() + c = Code() c.Sblock('enum %s {' % enum_name) if prop.optional: c.Append(self._cpp_type_generator.GetEnumNoneValue(prop) + ',') @@ -125,7 +137,7 @@ class HGenerator(object): def _GenerateFields(self, props): """Generates the field declarations when declaring a type. """ - c = code.Code() + c = Code() # Generate the enums needed for any fields with "choices" for prop in props: if prop.type_ == PropertyType.CHOICES: @@ -145,7 +157,7 @@ class HGenerator(object): """Generates a struct for a type. """ classname = cpp_util.Classname(type_.name) - c = code.Code() + c = Code() if type_.functions: # Types with functions are not instantiable in C++ because they are @@ -203,7 +215,7 @@ class HGenerator(object): def _GenerateFunction(self, function): """Generates the structs for a function. """ - c = code.Code() + c = Code() (c.Sblock('namespace %s {' % cpp_util.Classname(function.name)) .Concat(self._GenerateFunctionParams(function)) .Append() @@ -219,7 +231,7 @@ class HGenerator(object): def _GenerateFunctionParams(self, function): """Generates the struct for passing parameters into a function. """ - c = code.Code() + c = Code() if function.params: (c.Sblock('struct Params {') @@ -242,7 +254,7 @@ class HGenerator(object): """Generate the structures required by a property such as OBJECT classes and enums. """ - c = code.Code() + c = Code() for prop in props: if prop.type_ == PropertyType.OBJECT: c.Concat(self._GenerateType(prop)) @@ -266,7 +278,7 @@ class HGenerator(object): def _GenerateFunctionResult(self, function): """Generates functions for passing a function's result back. """ - c = code.Code() + c = Code() c.Sblock('namespace Result {') params = function.callback.params diff --git a/tools/json_schema_compiler/model.py b/tools/json_schema_compiler/model.py index 9a5c55f..6dc20fe 100644 --- a/tools/json_schema_compiler/model.py +++ b/tools/json_schema_compiler/model.py @@ -6,6 +6,15 @@ import copy import os.path import re +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)) + class Model(object): """Model of all namespaces that comprise an API. @@ -33,24 +42,17 @@ class Namespace(object): - |source_file_filename| the filename component of |source_file| - |types| a map of type names to their model.Type - |functions| a map of function names to their model.Function + - |properties| a map of property names to their model.Property """ def __init__(self, json, source_file): self.name = json['namespace'] - self.unix_name = UnixName(self.name) + self.unix_name = _UnixName(self.name) self.source_file = source_file self.source_file_dir, self.source_file_filename = os.path.split(source_file) - self.types = {} - self.functions = {} self.parent = None - # TODO(calamity): Implement properties on namespaces for shared structures - # or constants across a namespace (e.g Windows::WINDOW_ID_NONE). - for property_json in json.get('properties', []): - pass - for type_json in json.get('types', []): - type_ = Type(self, type_json['id'], type_json) - self.types[type_.name] = type_ - for function_json in json.get('functions', []): - self.functions[function_json['name']] = Function(self, function_json) + _AddTypes(self, json) + _AddFunctions(self, json) + _AddProperties(self, json) class Type(object): """A Type defined in the json. @@ -79,44 +81,24 @@ class Type(object): 'properties' in json or 'additionalProperties' in json or 'functions' in json): - raise ParseException(name + " has no properties or functions") + raise ParseException(self, name + " has no properties or functions") self.type_ = PropertyType.OBJECT self.name = name self.description = json.get('description') self.from_json = True self.from_client = True - self.properties = {} - self.functions = {} self.parent = parent - for function_json in json.get('functions', []): - self.functions[function_json['name']] = Function(self, function_json) - props = [] - for prop_name, prop_json in json.get('properties', {}).items(): - # TODO(calamity): support functions (callbacks) as properties. The model - # doesn't support it yet because the h/cc generators don't -- this is - # because we'd need to hook it into a base::Callback or something. - # - # However, pragmatically it's not necessary to support them anyway, since - # the instances of functions-on-properties in the extension APIs are all - # handled in pure Javascript on the render process (and .: never reach - # C++ let alone the browser). - if prop_json.get('type') == 'function': - continue - props.append(Property(self, prop_name, prop_json, - from_json=True, - from_client=True)) + _AddFunctions(self, json) + _AddProperties(self, json, from_json=True, from_client=True) - additional_properties = json.get('additionalProperties') + additional_properties_key = 'additionalProperties' + additional_properties = json.get(additional_properties_key) if additional_properties: - props.append(Property(self, 'additionalProperties', additional_properties, - is_additional_properties=True)) - - for prop in props: - if prop.unix_name in self.properties: - raise ParseException( - self.properties[prop.unix_name].name + ' and ' + prop.name + - ' are both named ' + prop.unix_name) - self.properties[prop.unix_name] = prop + self.properties[additional_properties_key] = Property( + self, + additional_properties_key, + additional_properties, + is_additional_properties=True) class Callback(object): """A callback parameter to a Function. @@ -135,7 +117,9 @@ class Callback(object): self.params.append(Property(self, param['name'], param, from_client=True)) else: - raise ParseException("Callbacks can have at most a single parameter") + raise ParseException( + self, + "Callbacks can have at most a single parameter") class Function(object): """A Function defined in the API. @@ -157,7 +141,7 @@ class Function(object): for param in json['parameters']: if param.get('type') == 'function': if self.callback: - raise ParseException(self.name + " has more than one callback") + raise ParseException(self, self.name + " has more than one callback") self.callback = Callback(self, param) else: self.params.append(Property(self, param['name'], param, @@ -191,11 +175,13 @@ class Property(object): users of generated code, such as top-level types and function results """ self.name = name - self._unix_name = UnixName(self.name) + self._unix_name = _UnixName(self.name) self._unix_name_used = False self.optional = json.get('optional', False) + self.has_value = False self.description = json.get('description') self.parent = parent + _AddProperties(self, json) if is_additional_properties: self.type_ = PropertyType.ADDITIONAL_PROPERTIES elif '$ref' in json: @@ -226,17 +212,17 @@ class Property(object): elif json_type == 'object': self.type_ = PropertyType.OBJECT # These members are read when this OBJECT Property is used as a Type - self.properties = {} self.from_json = from_json self.from_client = from_client type_ = Type(self, self.name, json) - self.properties = type_.properties + # self.properties will already have some value from |_AddProperties|. + self.properties.update(type_.properties) self.functions = type_.functions else: raise ParseException(self, 'type ' + json_type + ' not recognized') elif 'choices' in json: if not json['choices']: - raise ParseException('Choices has no choices') + raise ParseException(self, 'Choices has no choices') self.choices = {} self.type_ = PropertyType.CHOICES for choice_json in json['choices']: @@ -249,13 +235,23 @@ class Property(object): # The existence of any single choice is optional choice.optional = True self.choices[choice.type_] = choice + elif 'value' in json: + self.has_value = True + self.value = json['value'] + if type(self.value) == int: + self.type_ = PropertyType.INTEGER + else: + # TODO(kalman): support more types as necessary. + raise ParseException( + self, '"%s" is not a supported type' % type(self.value)) else: - raise ParseException('Property has no type, $ref or choices') + raise ParseException( + self, 'Property has no type, $ref, choices, or value') def GetUnixName(self): """Gets the property's unix_name. Raises AttributeError if not set. """ - if self._unix_name is None: + if not self._unix_name: raise AttributeError('No unix_name set on %s' % self.name) self._unix_name_used = True return self._unix_name @@ -306,7 +302,7 @@ class PropertyType(object): ANY = _Info(False, "ANY") ADDITIONAL_PROPERTIES = _Info(False, "ADDITIONAL_PROPERTIES") -def UnixName(name): +def _UnixName(name): """Returns the unix_style name for a given lowerCamelCase string. """ # First replace any lowerUpper patterns with lower_Upper. @@ -316,15 +312,7 @@ def UnixName(name): # Finally, replace any remaining periods, and make lowercase. return s2.replace('.', '_').lower() -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)) - -def GetModelHierarchy(entity): +def _GetModelHierarchy(entity): """Returns the hierarchy of the given model entity.""" hierarchy = [] while entity: @@ -335,3 +323,42 @@ def GetModelHierarchy(entity): entity = entity.parent hierarchy.reverse() return hierarchy + +def _AddTypes(model, json): + """Adds Type objects to |model| contained in the 'types' field of |json|. + """ + model.types = {} + for type_json in json.get('types', []): + type_ = Type(model, type_json['id'], type_json) + model.types[type_.name] = type_ + +def _AddFunctions(model, json): + """Adds Function objects to |model| contained in the 'types' field of |json|. + """ + model.functions = {} + for function_json in json.get('functions', []): + function = Function(model, function_json) + model.functions[function.name] = function + +def _AddProperties(model, json, from_json=False, from_client=False): + """Adds model.Property objects to |model| contained in the 'properties' field + of |json|. + """ + model.properties = {} + for name, property_json in json.get('properties', {}).items(): + # TODO(calamity): support functions (callbacks) as properties. The model + # doesn't support it yet because the h/cc generators don't -- this is + # because we'd need to hook it into a base::Callback or something. + # + # However, pragmatically it's not necessary to support them anyway, since + # the instances of functions-on-properties in the extension APIs are all + # handled in pure Javascript on the render process (and .: never reach + # C++ let alone the browser). + if property_json.get('type') == 'function': + continue + model.properties[name] = Property( + model, + name, + property_json, + from_json=from_json, + from_client=from_client) diff --git a/tools/json_schema_compiler/model_test.py b/tools/json_schema_compiler/model_test.py index 10f8656..577b3ca 100755 --- a/tools/json_schema_compiler/model_test.py +++ b/tools/json_schema_compiler/model_test.py @@ -37,9 +37,9 @@ class ModelTest(unittest.TestCase): self.assertEquals(['Window'], self.windows.types.keys()) def testHasProperties(self): - self.assertEquals(["active", "fav_icon_url", "highlighted", "id", + self.assertEquals(["active", "favIconUrl", "highlighted", "id", "incognito", "index", "pinned", "selected", "status", "title", "url", - "window_id"], + "windowId"], sorted(self.tabs.types['Tab'].properties.keys())) def testProperties(self): @@ -55,7 +55,7 @@ class ModelTest(unittest.TestCase): self.assertEquals(model.PropertyType.OBJECT, object_prop.type_) self.assertEquals( ["active", "highlighted", "pinned", "status", "title", "url", - "window_id", "window_type"], + "windowId", "windowType"], sorted(object_prop.properties.keys())) def testChoices(self): @@ -100,7 +100,7 @@ class ModelTest(unittest.TestCase): 'foo.barBAZ': 'foo_bar_baz' } for name in expectations: - self.assertEquals(expectations[name], model.UnixName(name)); + self.assertEquals(expectations[name], model._UnixName(name)); if __name__ == '__main__': unittest.main() |