summaryrefslogtreecommitdiffstats
path: root/tools/json_schema_compiler
diff options
context:
space:
mode:
authorkalman@chromium.org <kalman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-19 04:22:38 +0000
committerkalman@chromium.org <kalman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-19 04:22:38 +0000
commit116f0a3aa63c5b508ce7d5e9aad1bbb7ab2921a6 (patch)
tree4051fe248e9794c97d8450dcc1c53c6f0cd13c44 /tools/json_schema_compiler
parent5eae551d867c6da7e1ed363110f02f89f99970d9 (diff)
downloadchromium_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.py45
-rw-r--r--tools/json_schema_compiler/cpp_type_generator.py42
-rw-r--r--tools/json_schema_compiler/h_generator.py32
-rw-r--r--tools/json_schema_compiler/model.py147
-rwxr-xr-xtools/json_schema_compiler/model_test.py8
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()