diff options
Diffstat (limited to 'tools/json_schema_compiler/model.py')
-rw-r--r-- | tools/json_schema_compiler/model.py | 147 |
1 files changed, 87 insertions, 60 deletions
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) |