diff options
-rw-r--r-- | chrome/browser/extensions/settings/settings_frontend.cc | 44 | ||||
-rw-r--r-- | chrome/common/extensions/api/api.gyp | 1 | ||||
-rw-r--r-- | chrome/common/extensions/api/storage.json | 12 | ||||
-rw-r--r-- | chrome/common/extensions/api/windows.json | 6 | ||||
-rw-r--r-- | chrome/common/extensions/docs/js/api_page_generator.js | 33 | ||||
-rw-r--r-- | chrome/renderer/resources/extensions/event.js | 2 | ||||
-rw-r--r-- | chrome/renderer/resources/extensions/json_schema.js | 4 | ||||
-rw-r--r-- | chrome/renderer/resources/extensions/schema_generated_bindings.js | 19 | ||||
-rw-r--r-- | chrome/renderer/resources/extensions/send_request.js | 4 | ||||
-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 |
14 files changed, 239 insertions, 160 deletions
diff --git a/chrome/browser/extensions/settings/settings_frontend.cc b/chrome/browser/extensions/settings/settings_frontend.cc index 0c2c51d..f20e58d 100644 --- a/chrome/browser/extensions/settings/settings_frontend.cc +++ b/chrome/browser/extensions/settings/settings_frontend.cc @@ -15,7 +15,7 @@ #include "chrome/browser/extensions/settings/settings_leveldb_storage.h" #include "chrome/browser/extensions/settings/weak_unlimited_settings_storage.h" #include "chrome/browser/profiles/profile.h" -#include "chrome/common/extensions/api/extension_api.h" +#include "chrome/common/extensions/api/storage.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_service.h" @@ -88,36 +88,20 @@ void CallbackWithUnlimitedStorage( callback.Run(&unlimited_storage); } -// Returns the integer at |path| in |dict| as a size_t, or a default value if -// there's nothing found at that path. -size_t GetStringAsInteger( - const DictionaryValue& dict, const std::string& path, size_t default_size) { - std::string as_string; - if (!dict.GetString(path, &as_string)) - return default_size; - size_t as_integer = default_size; - CHECK(base::StringToSizeT(as_string, &as_integer)); - return as_integer; +SettingsStorageQuotaEnforcer::Limits GetLocalLimits() { + SettingsStorageQuotaEnforcer::Limits limits = { + api::storage::local::QUOTA_BYTES, + UINT_MAX, + UINT_MAX + }; + return limits; } -// Constructs a |Limits| configuration by looking up the QUOTA_BYTES, -// QUOTA_BYTES_PER_ITEM, and MAX_ITEMS properties of a storage area defined -// in chrome/common/extensions/api/storage.json (via ExtensionAPI). -SettingsStorageQuotaEnforcer::Limits GetLimitsFromExtensionAPI( - const std::string& storage_area_id) { - const DictionaryValue* storage_schema = - ExtensionAPI::GetSharedInstance()->GetSchema("storage"); - CHECK(storage_schema); - - DictionaryValue* properties = NULL; - storage_schema->GetDictionary( - "properties." + storage_area_id + ".properties", &properties); - CHECK(properties); - +SettingsStorageQuotaEnforcer::Limits GetSyncLimits() { SettingsStorageQuotaEnforcer::Limits limits = { - GetStringAsInteger(*properties, "QUOTA_BYTES.value", UINT_MAX), - GetStringAsInteger(*properties, "QUOTA_BYTES_PER_ITEM.value", UINT_MAX), - GetStringAsInteger(*properties, "MAX_ITEMS.value", UINT_MAX), + api::storage::sync::QUOTA_BYTES, + api::storage::sync::QUOTA_BYTES_PER_ITEM, + api::storage::sync::MAX_ITEMS }; return limits; } @@ -232,8 +216,8 @@ SettingsFrontend* SettingsFrontend::Create( SettingsFrontend::SettingsFrontend( const scoped_refptr<SettingsStorageFactory>& factory, Profile* profile) - : local_quota_limit_(GetLimitsFromExtensionAPI("local")), - sync_quota_limit_(GetLimitsFromExtensionAPI("sync")), + : local_quota_limit_(GetLocalLimits()), + sync_quota_limit_(GetSyncLimits()), profile_(profile), observers_(new SettingsObserverList()), profile_observer_(new DefaultObserver(profile)) { diff --git a/chrome/common/extensions/api/api.gyp b/chrome/common/extensions/api/api.gyp index 17e773a..7646399 100644 --- a/chrome/common/extensions/api/api.gyp +++ b/chrome/common/extensions/api/api.gyp @@ -21,6 +21,7 @@ 'browserAction.json', 'experimental.declarative.json', 'permissions.json', + 'storage.json', 'tabs.json', 'windows.json', ], diff --git a/chrome/common/extensions/api/storage.json b/chrome/common/extensions/api/storage.json index 998ac6b..1b9b298 100644 --- a/chrome/common/extensions/api/storage.json +++ b/chrome/common/extensions/api/storage.json @@ -175,18 +175,15 @@ "value": [ "sync" ], "properties": { "QUOTA_BYTES": { - "type": "integer", - "value": "102400", + "value": 102400, "description": "The maximum total amount (in bytes) of data that can be stored in sync storage." }, "QUOTA_BYTES_PER_ITEM": { - "type": "integer", - "value": "2048", + "value": 2048, "description": "The maximum size (in bytes) of each individual item in sync storage." }, "MAX_ITEMS": { - "type": "integer", - "value": "512", + "value": 512, "description": "The maximum number of items that can be stored in sync storage." } } @@ -197,8 +194,7 @@ "value": [ "local" ], "properties": { "QUOTA_BYTES": { - "type": "integer", - "value": "5242880", + "value": 5242880, "description": "The maximum amount (in bytes) of data that can be stored in local storage. This value will be ignored if the extension has the <code>unlimitedStorage</code> permission." } } diff --git a/chrome/common/extensions/api/windows.json b/chrome/common/extensions/api/windows.json index 573a93c..80fe9cf 100644 --- a/chrome/common/extensions/api/windows.json +++ b/chrome/common/extensions/api/windows.json @@ -35,13 +35,11 @@ ], "properties": { "WINDOW_ID_NONE": { - "type": "integer", - "value": "-1", + "value": -1, "description": "The windowId value that represents the absence of a chrome browser window." }, "WINDOW_ID_CURRENT": { - "type": "integer", - "value": "-2", + "value": -2, "description": "The windowId value that represents the <a href='windows.html#current-window'>current window</a>." } }, diff --git a/chrome/common/extensions/docs/js/api_page_generator.js b/chrome/common/extensions/docs/js/api_page_generator.js index 1569f32..70d7545 100644 --- a/chrome/common/extensions/docs/js/api_page_generator.js +++ b/chrome/common/extensions/docs/js/api_page_generator.js @@ -645,25 +645,28 @@ function getTypeName(schema) { } function hasPrimitiveValue(schema) { - return typeof(schema.value) === 'string'; + var type = typeof(schema.value); + return type === 'string' || type === 'number'; } function getPrimitiveValue(schema) { - if (schema.type === 'string') { - return '"' + schema.value + '"'; - } else if (schema.type === 'integer') { - // Comma-separate large numbers (e.g. 5,000,000), easier to read. - var value = String(schema.value); - var groupsOfThree = []; - while (value.length > 3) { - groupsOfThree.unshift(value.slice(value.length - 3)); - value = value.slice(0, value.length - 3); - } - groupsOfThree.unshift(value); - return groupsOfThree.join(','); - } else { - return schema.value; + switch (typeof(schema.value)) { + case 'string': + return '"' + schema.value + '"'; + + case 'number': + // Comma-separate large numbers (e.g. 5,000,000), easier to read. + var value = String(schema.value); + var groupsOfThree = []; + while (value.length > 3) { + groupsOfThree.unshift(value.slice(value.length - 3)); + value = value.slice(0, value.length - 3); + } + groupsOfThree.unshift(value); + return groupsOfThree.join(','); } + + return undefined; } function getSignatureString(parameters) { diff --git a/chrome/renderer/resources/extensions/event.js b/chrome/renderer/resources/extensions/event.js index 63b7d7f..5db2a59 100644 --- a/chrome/renderer/resources/extensions/event.js +++ b/chrome/renderer/resources/extensions/event.js @@ -205,7 +205,7 @@ results.push(result); } catch (e) { console.error("Error in event handler for '" + this.eventName_ + - "': " + e.stack); + "': " + e.message + ' ' + e.stack); } } if (results.length) diff --git a/chrome/renderer/resources/extensions/json_schema.js b/chrome/renderer/resources/extensions/json_schema.js index bc51d97..ec4b48d 100644 --- a/chrome/renderer/resources/extensions/json_schema.js +++ b/chrome/renderer/resources/extensions/json_schema.js @@ -137,8 +137,8 @@ chromeHidden.JSONSchemaValidator.getType = function(value) { */ chromeHidden.JSONSchemaValidator.prototype.addTypes = function(typeOrTypeList) { function addType(validator, type) { - if(!type.id) - throw "Attempt to addType with missing 'id' property"; + if (!type.id) + throw new Error("Attempt to addType with missing 'id' property"); validator.types[type.id] = type; } diff --git a/chrome/renderer/resources/extensions/schema_generated_bindings.js b/chrome/renderer/resources/extensions/schema_generated_bindings.js index 4da0dba..66c193a 100644 --- a/chrome/renderer/resources/extensions/schema_generated_bindings.js +++ b/chrome/renderer/resources/extensions/schema_generated_bindings.js @@ -522,9 +522,14 @@ var value = propertyDef.value; if (value) { - if (propertyDef.type === 'integer') { + // Values may just have raw types as defined in the JSON, such + // as "WINDOW_ID_NONE": { "value": -1 }. We handle this here. + // TODO(kalman): enforce that things with a "value" property can't + // define their own types. + var type = propertyDef.type || typeof(value); + if (type === 'integer' || type === 'number') { value = parseInt(value); - } else if (propertyDef.type === 'boolean') { + } else if (type === 'boolean') { value = value === "true"; } else if (propertyDef["$ref"]) { var constructor = customTypes[propertyDef["$ref"]]; @@ -539,15 +544,13 @@ constructor.apply(value, args); // Recursively add properties. addProperties(value, propertyDef); - } else if (propertyDef.type === 'object') { + } else if (type === 'object') { // Recursively add properties. addProperties(value, propertyDef); - } else if (propertyDef.type !== 'string') { - throw "NOT IMPLEMENTED (extension_api.json error): Cannot " + - "parse values for type \"" + propertyDef.type + "\""; + } else if (type !== 'string') { + throw new Error("NOT IMPLEMENTED (extension_api.json error): " + + "Cannot parse values for type \"" + type + "\""); } - } - if (value) { m[propertyName] = value; } }); diff --git a/chrome/renderer/resources/extensions/send_request.js b/chrome/renderer/resources/extensions/send_request.js index fff9c90..3e47f40 100644 --- a/chrome/renderer/resources/extensions/send_request.js +++ b/chrome/renderer/resources/extensions/send_request.js @@ -38,11 +38,11 @@ chromeHidden.handleResponse = function(requestId, name, if (chromeHidden.validateCallbacks && !error) { try { if (!request.callbackSchema.parameters) { - throw "No callback schemas defined"; + throw new Error("No callback schemas defined"); } if (request.callbackSchema.parameters.length > 1) { - throw "Callbacks may only define one parameter"; + throw new Error("Callbacks may only define one parameter"); } chromeHidden.validate(callbackArgs, 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() |