| Index: tools/json_schema_compiler/model.py
|
| diff --git a/tools/json_schema_compiler/model.py b/tools/json_schema_compiler/model.py
|
| index d839ab977be318601d9a79232b1903271fc3ad2e..de392d21a7776f764029a60e13eadbb645570127 100644
|
| --- a/tools/json_schema_compiler/model.py
|
| +++ b/tools/json_schema_compiler/model.py
|
| @@ -40,18 +40,22 @@ class Namespace(object):
|
| """
|
| 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(type_json)
|
| + type_ = Type(self, type_json['id'], type_json)
|
| self.types[type_.name] = type_
|
| for function_json in json.get('functions', []):
|
| if not function_json.get('nocompile', False):
|
| - function = Function(function_json)
|
| - self.functions[function.name] = function
|
| + self.functions[function_json['name']] = Function(self, function_json)
|
|
|
| class Type(object):
|
| """A Type defined in the json.
|
| @@ -59,23 +63,57 @@ class Type(object):
|
| Properties:
|
| - |name| the type name
|
| - |description| the description of the type (if provided)
|
| - - |properties| a map of property names to their model.Property
|
| + - |properties| a map of property unix_names to their model.Property
|
| + - |functions| a map of function names to their model.Function
|
| - |from_client| indicates that instances of the Type can originate from the
|
| users of generated code, such as top-level types and function results
|
| - |from_json| indicates that instances of the Type can originate from the
|
| JSON (as described by the schema), such as top-level types and function
|
| parameters
|
| """
|
| - def __init__(self, json):
|
| - self.name = json['id']
|
| + def __init__(self, parent, name, json):
|
| + if not (
|
| + 'properties' in json or
|
| + 'additionalProperties' in json or
|
| + 'functions' in json):
|
| + raise ParseException(name + " has no properties or functions")
|
| + self.name = name
|
| self.description = json.get('description')
|
| self.from_json = True
|
| self.from_client = True
|
| self.properties = {}
|
| - for prop_name, prop_json in json['properties'].items():
|
| - self.properties[prop_name] = Property(prop_name, prop_json,
|
| + self.functions = {}
|
| + self.parent = parent
|
| + for function_json in json.get('functions', []):
|
| + if not function_json.get('nocompile', False):
|
| + 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 to 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)
|
| + from_client=True))
|
| +
|
| + additional_properties = json.get('additionalProperties')
|
| + 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
|
|
|
| class Callback(object):
|
| """A callback parameter to a Function.
|
| @@ -83,17 +121,18 @@ class Callback(object):
|
| Properties:
|
| - |params| the parameters to this callback.
|
| """
|
| - def __init__(self, json):
|
| + def __init__(self, parent, json):
|
| params = json['parameters']
|
| + self.parent = parent
|
| self.params = []
|
| if len(params) == 0:
|
| return
|
| elif len(params) == 1:
|
| param = params[0]
|
| - self.params.append(Property(param['name'], param,
|
| + self.params.append(Property(self, param['name'], param,
|
| from_client=True))
|
| else:
|
| - raise AssertionError("Callbacks can have at most a single parameter")
|
| + raise ParseException("Callbacks can have at most a single parameter")
|
|
|
| class Function(object):
|
| """A Function defined in the API.
|
| @@ -106,17 +145,19 @@ class Function(object):
|
| - |callback| the callback parameter to the function. There should be exactly
|
| one
|
| """
|
| - def __init__(self, json):
|
| + def __init__(self, parent, json):
|
| self.name = json['name']
|
| self.params = []
|
| - self.description = json['description']
|
| + self.description = json.get('description')
|
| self.callback = None
|
| + self.parent = parent
|
| for param in json['parameters']:
|
| if param.get('type') == 'function':
|
| - assert (not self.callback), self.name + " has more than one callback"
|
| - self.callback = Callback(param)
|
| + if self.callback:
|
| + raise ParseException(self.name + " has more than one callback")
|
| + self.callback = Callback(self, param)
|
| else:
|
| - self.params.append(Property(param['name'], param,
|
| + self.params.append(Property(self, param['name'], param,
|
| from_json=True))
|
|
|
| class Property(object):
|
| @@ -135,9 +176,9 @@ class Property(object):
|
| ARRAY
|
| - |properties| the properties of an OBJECT parameter
|
| """
|
| - def __init__(self, name, json,
|
| - from_json=False,
|
| - from_client=False):
|
| +
|
| + def __init__(self, parent, name, json, is_additional_properties=False,
|
| + from_json=False, from_client=False):
|
| """
|
| Parameters:
|
| - |from_json| indicates that instances of the Type can originate from the
|
| @@ -147,11 +188,14 @@ 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.description = json.get('description')
|
| - if '$ref' in json:
|
| + self.parent = parent
|
| + if is_additional_properties:
|
| + self.type_ = PropertyType.ADDITIONAL_PROPERTIES
|
| + elif '$ref' in json:
|
| self.ref_type = json['$ref']
|
| self.type_ = PropertyType.REF
|
| elif 'enum' in json:
|
| @@ -172,9 +216,9 @@ class Property(object):
|
| elif json_type == 'number':
|
| self.type_ = PropertyType.DOUBLE
|
| elif json_type == 'array':
|
| - self.item_type = Property(name + "Element", json['items'],
|
| - from_json,
|
| - from_client)
|
| + self.item_type = Property(self, name + "Element", json['items'],
|
| + from_json=from_json,
|
| + from_client=from_client)
|
| self.type_ = PropertyType.ARRAY
|
| elif json_type == 'object':
|
| self.type_ = PropertyType.OBJECT
|
| @@ -182,20 +226,20 @@ class Property(object):
|
| self.properties = {}
|
| self.from_json = from_json
|
| self.from_client = from_client
|
| - for key, val in json.get('properties', {}).items():
|
| - self.properties[key] = Property(key, val,
|
| - from_json,
|
| - from_client)
|
| + type_ = Type(self, self.name, json)
|
| + self.properties = type_.properties
|
| + self.functions = type_.functions
|
| else:
|
| - raise NotImplementedError(json_type)
|
| + raise ParseException(self, 'type ' + json_type + ' not recognized')
|
| elif 'choices' in json:
|
| - assert len(json['choices']), 'Choices has no choices\n%s' % json
|
| + if not json['choices']:
|
| + raise ParseException('Choices has no choices')
|
| self.choices = {}
|
| self.type_ = PropertyType.CHOICES
|
| for choice_json in json['choices']:
|
| - choice = Property(self.name, choice_json,
|
| - from_json,
|
| - from_client)
|
| + choice = Property(self, self.name, choice_json,
|
| + from_json=from_json,
|
| + from_client=from_client)
|
| # A choice gets its unix_name set in
|
| # cpp_type_generator.GetExpandedChoicesInParams
|
| choice._unix_name = None
|
| @@ -203,7 +247,7 @@ class Property(object):
|
| choice.optional = True
|
| self.choices[choice.type_] = choice
|
| else:
|
| - raise NotImplementedError(json)
|
| + raise ParseException('Property has no type, $ref or choices')
|
|
|
| def GetUnixName(self):
|
| """Gets the property's unix_name. Raises AttributeError if not set.
|
| @@ -257,10 +301,31 @@ class PropertyType(object):
|
| CHOICES = _Info(False, "CHOICES")
|
| OBJECT = _Info(False, "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.
|
| """
|
| return '_'.join([x.lower()
|
| for x in re.findall('[A-Z][a-z_]*', name[0].upper() + name[1:])])
|
|
|
| +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):
|
| + """Returns the hierarchy of the given model entity."""
|
| + hierarchy = []
|
| + while entity:
|
| + try:
|
| + hierarchy.append(entity.name)
|
| + except AttributeError:
|
| + hierarchy.append(repr(entity))
|
| + entity = entity.parent
|
| + hierarchy.reverse()
|
| + return hierarchy
|
| +
|
|
|