blob: 9e29726577e40e9f19655bbefeaa40a9911f84e1 [file] [log] [blame]
[email protected]15f08dd2012-01-27 07:29:481# Copyright (c) 2012 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
[email protected]df74dc42012-04-03 07:08:045import copy
[email protected]15f08dd2012-01-27 07:29:486import os.path
[email protected]cfe484902012-02-15 14:52:327import re
[email protected]15f08dd2012-01-27 07:29:488
[email protected]116f0a3a2012-04-19 04:22:389class ParseException(Exception):
10 """Thrown when data in the model is invalid.
11 """
12 def __init__(self, parent, message):
13 hierarchy = _GetModelHierarchy(parent)
14 hierarchy.append(message)
15 Exception.__init__(
16 self, 'Model parse exception at:\n' + '\n'.join(hierarchy))
17
[email protected]15f08dd2012-01-27 07:29:4818class Model(object):
19 """Model of all namespaces that comprise an API.
[email protected]cfe484902012-02-15 14:52:3220
21 Properties:
22 - |namespaces| a map of a namespace name to its model.Namespace
[email protected]15f08dd2012-01-27 07:29:4823 """
24 def __init__(self):
25 self.namespaces = {}
26
27 def AddNamespace(self, json, source_file):
[email protected]a1f774972012-04-17 02:11:0928 """Add a namespace's json to the model and returns the namespace.
[email protected]15f08dd2012-01-27 07:29:4829 """
[email protected]15f08dd2012-01-27 07:29:4830 namespace = Namespace(json, source_file)
31 self.namespaces[namespace.name] = namespace
32 return namespace
33
34class Namespace(object):
35 """An API namespace.
[email protected]cfe484902012-02-15 14:52:3236
37 Properties:
38 - |name| the name of the namespace
[email protected]712eca0f2012-02-21 01:13:0739 - |unix_name| the unix_name of the namespace
40 - |source_file| the file that contained the namespace definition
41 - |source_file_dir| the directory component of |source_file|
42 - |source_file_filename| the filename component of |source_file|
[email protected]cfe484902012-02-15 14:52:3243 - |types| a map of type names to their model.Type
44 - |functions| a map of function names to their model.Function
[email protected]b741e8f2012-07-16 21:47:2445 - |events| a map of event names to their model.Function
[email protected]116f0a3a2012-04-19 04:22:3846 - |properties| a map of property names to their model.Property
[email protected]15f08dd2012-01-27 07:29:4847 """
48 def __init__(self, json, source_file):
49 self.name = json['namespace']
[email protected]4ba6bdc2012-06-18 20:40:1450 self.unix_name = UnixName(self.name)
[email protected]15f08dd2012-01-27 07:29:4851 self.source_file = source_file
52 self.source_file_dir, self.source_file_filename = os.path.split(source_file)
[email protected]feba21e2012-03-02 15:05:2753 self.parent = None
[email protected]0b255f002012-10-05 01:58:4754 _AddTypes(self, json, self)
55 _AddFunctions(self, json, self)
56 _AddEvents(self, json, self)
57 _AddProperties(self, json, self)
[email protected]15f08dd2012-01-27 07:29:4858
59class Type(object):
60 """A Type defined in the json.
[email protected]cfe484902012-02-15 14:52:3261
62 Properties:
63 - |name| the type name
64 - |description| the description of the type (if provided)
[email protected]feba21e2012-03-02 15:05:2765 - |properties| a map of property unix_names to their model.Property
66 - |functions| a map of function names to their model.Function
[email protected]d32b8e52012-07-23 18:40:0867 - |events| a map of event names to their model.Event
[email protected]25cbf6012012-02-28 05:51:4468 - |from_client| indicates that instances of the Type can originate from the
69 users of generated code, such as top-level types and function results
70 - |from_json| indicates that instances of the Type can originate from the
71 JSON (as described by the schema), such as top-level types and function
72 parameters
[email protected]cf6d0b32012-04-12 04:30:2273 - |type_| the PropertyType of this Type
74 - |item_type| if this is an array, the type of items in the array
[email protected]0b255f002012-10-05 01:58:4775 - |simple_name| the name of this Type without a namespace
[email protected]15f08dd2012-01-27 07:29:4876 """
[email protected]0b255f002012-10-05 01:58:4777 def __init__(self, parent, name, json, namespace):
[email protected]cf6d0b32012-04-12 04:30:2278 if json.get('type') == 'array':
79 self.type_ = PropertyType.ARRAY
[email protected]0b255f002012-10-05 01:58:4780 self.item_type = Property(self,
81 name + "Element",
82 json['items'],
83 namespace,
[email protected]cf6d0b32012-04-12 04:30:2284 from_json=True,
85 from_client=True)
[email protected]e7ae70172012-08-29 04:07:1386 elif 'enum' in json:
87 self.enum_values = []
88 for value in json['enum']:
89 self.enum_values.append(value)
90 self.type_ = PropertyType.ENUM
[email protected]0ba7b4f2012-09-25 01:00:4191 elif json.get('type') == 'string':
92 self.type_ = PropertyType.STRING
[email protected]cf6d0b32012-04-12 04:30:2293 else:
94 if not (
95 'properties' in json or
96 'additionalProperties' in json or
[email protected]e975d112012-07-31 22:12:4397 'functions' in json or
98 'events' in json):
[email protected]116f0a3a2012-04-19 04:22:3899 raise ParseException(self, name + " has no properties or functions")
[email protected]cf6d0b32012-04-12 04:30:22100 self.type_ = PropertyType.OBJECT
[email protected]feba21e2012-03-02 15:05:27101 self.name = name
[email protected]0b255f002012-10-05 01:58:47102 self.simple_name = _StripNamespace(self.name, namespace)
[email protected]0ba7b4f2012-09-25 01:00:41103 self.unix_name = UnixName(self.name)
[email protected]15f08dd2012-01-27 07:29:48104 self.description = json.get('description')
[email protected]25cbf6012012-02-28 05:51:44105 self.from_json = True
106 self.from_client = True
[email protected]feba21e2012-03-02 15:05:27107 self.parent = parent
[email protected]491e60d32012-07-20 01:03:13108 self.instance_of = json.get('isInstanceOf', None)
[email protected]0b255f002012-10-05 01:58:47109 _AddFunctions(self, json, namespace)
110 _AddEvents(self, json, namespace)
111 _AddProperties(self, json, namespace, from_json=True, from_client=True)
[email protected]feba21e2012-03-02 15:05:27112
[email protected]116f0a3a2012-04-19 04:22:38113 additional_properties_key = 'additionalProperties'
114 additional_properties = json.get(additional_properties_key)
[email protected]feba21e2012-03-02 15:05:27115 if additional_properties:
[email protected]116f0a3a2012-04-19 04:22:38116 self.properties[additional_properties_key] = Property(
117 self,
118 additional_properties_key,
119 additional_properties,
[email protected]0b255f002012-10-05 01:58:47120 namespace,
[email protected]116f0a3a2012-04-19 04:22:38121 is_additional_properties=True)
[email protected]15f08dd2012-01-27 07:29:48122
[email protected]15f08dd2012-01-27 07:29:48123class Function(object):
124 """A Function defined in the API.
[email protected]cfe484902012-02-15 14:52:32125
126 Properties:
127 - |name| the function name
128 - |params| a list of parameters to the function (order matters). A separate
129 parameter is used for each choice of a 'choices' parameter.
130 - |description| a description of the function (if provided)
131 - |callback| the callback parameter to the function. There should be exactly
132 one
[email protected]4b3f7852012-07-17 06:33:30133 - |optional| whether the Function is "optional"; this only makes sense to be
134 present when the Function is representing a callback property.
[email protected]0b255f002012-10-05 01:58:47135 - |simple_name| the name of this Function without a namespace
[email protected]15f08dd2012-01-27 07:29:48136 """
[email protected]0b255f002012-10-05 01:58:47137 def __init__(self,
138 parent,
139 json,
140 namespace,
141 from_json=False,
142 from_client=False):
[email protected]15f08dd2012-01-27 07:29:48143 self.name = json['name']
[email protected]0b255f002012-10-05 01:58:47144 self.simple_name = _StripNamespace(self.name, namespace)
[email protected]15f08dd2012-01-27 07:29:48145 self.params = []
[email protected]feba21e2012-03-02 15:05:27146 self.description = json.get('description')
[email protected]15f08dd2012-01-27 07:29:48147 self.callback = None
[email protected]4b3f7852012-07-17 06:33:30148 self.optional = json.get('optional', False)
[email protected]feba21e2012-03-02 15:05:27149 self.parent = parent
[email protected]a9ead752012-05-24 02:08:45150 self.nocompile = json.get('nocompile')
[email protected]c0718352012-08-21 02:34:24151 options = json.get('options', {})
152 self.conditions = options.get('conditions', [])
153 self.actions = options.get('actions', [])
154 self.supports_listeners = options.get('supportsListeners', True)
155 self.supports_rules = options.get('supportsRules', False)
156 def GeneratePropertyFromParam(p):
157 return Property(self,
158 p['name'], p,
[email protected]0b255f002012-10-05 01:58:47159 namespace,
[email protected]c0718352012-08-21 02:34:24160 from_json=from_json,
161 from_client=from_client)
[email protected]db943992012-08-02 14:02:54162
[email protected]c0718352012-08-21 02:34:24163 self.filters = [GeneratePropertyFromParam(filter)
164 for filter in json.get('filters', [])]
[email protected]db943992012-08-02 14:02:54165 callback_param = None
[email protected]b741e8f2012-07-16 21:47:24166 for param in json.get('parameters', []):
[email protected]db943992012-08-02 14:02:54167
[email protected]15f08dd2012-01-27 07:29:48168 if param.get('type') == 'function':
[email protected]db943992012-08-02 14:02:54169 if callback_param:
170 # No ParseException because the webstore has this.
171 # Instead, pretend all intermediate callbacks are properties.
172 self.params.append(GeneratePropertyFromParam(callback_param))
173 callback_param = param
[email protected]15f08dd2012-01-27 07:29:48174 else:
[email protected]db943992012-08-02 14:02:54175 self.params.append(GeneratePropertyFromParam(param))
176
177 if callback_param:
[email protected]0b255f002012-10-05 01:58:47178 self.callback = Function(self,
179 callback_param,
180 namespace,
181 from_client=True)
[email protected]db943992012-08-02 14:02:54182
[email protected]491e60d32012-07-20 01:03:13183 self.returns = None
184 if 'returns' in json:
[email protected]0b255f002012-10-05 01:58:47185 self.returns = Property(self, 'return', json['returns'], namespace)
[email protected]15f08dd2012-01-27 07:29:48186
[email protected]15f08dd2012-01-27 07:29:48187class Property(object):
188 """A property of a type OR a parameter to a function.
189
[email protected]cfe484902012-02-15 14:52:32190 Properties:
191 - |name| name of the property as in the json. This shouldn't change since
192 it is the key used to access DictionaryValues
193 - |unix_name| the unix_style_name of the property. Used as variable name
194 - |optional| a boolean representing whether the property is optional
195 - |description| a description of the property (if provided)
196 - |type_| the model.PropertyType of this property
[email protected]1d0f57e2012-07-31 22:47:50197 - |compiled_type| the model.PropertyType that this property should be
198 compiled to from the JSON. Defaults to |type_|.
[email protected]cfe484902012-02-15 14:52:32199 - |ref_type| the type that the REF property is referencing. Can be used to
200 map to its model.Type
201 - |item_type| a model.Property representing the type of each element in an
202 ARRAY
203 - |properties| the properties of an OBJECT parameter
[email protected]b741e8f2012-07-16 21:47:24204 - |from_client| indicates that instances of the Type can originate from the
205 users of generated code, such as top-level types and function results
206 - |from_json| indicates that instances of the Type can originate from the
207 JSON (as described by the schema), such as top-level types and function
208 parameters
[email protected]0b255f002012-10-05 01:58:47209 - |simple_name| the name of this Property without a namespace
[email protected]15f08dd2012-01-27 07:29:48210 """
[email protected]feba21e2012-03-02 15:05:27211
[email protected]0b255f002012-10-05 01:58:47212 def __init__(self,
213 parent,
214 name,
215 json,
216 namespace,
217 is_additional_properties=False,
218 from_json=False,
219 from_client=False):
[email protected]15f08dd2012-01-27 07:29:48220 self.name = name
[email protected]0b255f002012-10-05 01:58:47221 self.simple_name = _StripNamespace(self.name, namespace)
[email protected]4ba6bdc2012-06-18 20:40:14222 self._unix_name = UnixName(self.name)
[email protected]cfe484902012-02-15 14:52:32223 self._unix_name_used = False
[email protected]15f08dd2012-01-27 07:29:48224 self.optional = json.get('optional', False)
[email protected]3072d072012-07-19 00:35:33225 self.functions = {}
[email protected]116f0a3a2012-04-19 04:22:38226 self.has_value = False
[email protected]15f08dd2012-01-27 07:29:48227 self.description = json.get('description')
[email protected]feba21e2012-03-02 15:05:27228 self.parent = parent
[email protected]b741e8f2012-07-16 21:47:24229 self.from_json = from_json
230 self.from_client = from_client
[email protected]491e60d32012-07-20 01:03:13231 self.instance_of = json.get('isInstanceOf', None)
[email protected]0b255f002012-10-05 01:58:47232 _AddProperties(self, json, namespace)
[email protected]feba21e2012-03-02 15:05:27233 if is_additional_properties:
234 self.type_ = PropertyType.ADDITIONAL_PROPERTIES
235 elif '$ref' in json:
[email protected]15f08dd2012-01-27 07:29:48236 self.ref_type = json['$ref']
237 self.type_ = PropertyType.REF
[email protected]9d273182012-07-11 21:03:26238 elif 'enum' in json and json.get('type') == 'string':
239 # Non-string enums (as in the case of [legalValues=(1,2)]) should fall
240 # through to the next elif.
[email protected]2111fff9b2012-02-22 12:06:51241 self.enum_values = []
242 for value in json['enum']:
243 self.enum_values.append(value)
244 self.type_ = PropertyType.ENUM
[email protected]15f08dd2012-01-27 07:29:48245 elif 'type' in json:
[email protected]1d0f57e2012-07-31 22:47:50246 self.type_ = self._JsonTypeToPropertyType(json['type'])
247 if self.type_ == PropertyType.ARRAY:
[email protected]0b255f002012-10-05 01:58:47248 self.item_type = Property(self,
249 name + "Element",
250 json['items'],
251 namespace,
252 from_json=from_json,
253 from_client=from_client)
[email protected]1d0f57e2012-07-31 22:47:50254 elif self.type_ == PropertyType.OBJECT:
[email protected]25cbf6012012-02-28 05:51:44255 # These members are read when this OBJECT Property is used as a Type
[email protected]0b255f002012-10-05 01:58:47256 type_ = Type(self, self.name, json, namespace)
[email protected]116f0a3a2012-04-19 04:22:38257 # self.properties will already have some value from |_AddProperties|.
258 self.properties.update(type_.properties)
[email protected]feba21e2012-03-02 15:05:27259 self.functions = type_.functions
[email protected]15f08dd2012-01-27 07:29:48260 elif 'choices' in json:
[email protected]b741e8f2012-07-16 21:47:24261 if not json['choices'] or len(json['choices']) == 0:
[email protected]116f0a3a2012-04-19 04:22:38262 raise ParseException(self, 'Choices has no choices')
[email protected]15f08dd2012-01-27 07:29:48263 self.choices = {}
[email protected]cfe484902012-02-15 14:52:32264 self.type_ = PropertyType.CHOICES
[email protected]1d0f57e2012-07-31 22:47:50265 self.compiled_type = self.type_
[email protected]cfe484902012-02-15 14:52:32266 for choice_json in json['choices']:
[email protected]0b255f002012-10-05 01:58:47267 choice = Property(self,
268 self.name,
269 choice_json,
270 namespace,
271 from_json=from_json,
272 from_client=from_client)
[email protected]b741e8f2012-07-16 21:47:24273 choice.unix_name = UnixName(self.name + choice.type_.name)
[email protected]cfe484902012-02-15 14:52:32274 # The existence of any single choice is optional
275 choice.optional = True
276 self.choices[choice.type_] = choice
[email protected]116f0a3a2012-04-19 04:22:38277 elif 'value' in json:
278 self.has_value = True
279 self.value = json['value']
280 if type(self.value) == int:
281 self.type_ = PropertyType.INTEGER
[email protected]1d0f57e2012-07-31 22:47:50282 self.compiled_type = self.type_
[email protected]116f0a3a2012-04-19 04:22:38283 else:
284 # TODO(kalman): support more types as necessary.
285 raise ParseException(
286 self, '"%s" is not a supported type' % type(self.value))
[email protected]cfe484902012-02-15 14:52:32287 else:
[email protected]116f0a3a2012-04-19 04:22:38288 raise ParseException(
289 self, 'Property has no type, $ref, choices, or value')
[email protected]1d0f57e2012-07-31 22:47:50290 if 'compiled_type' in json:
291 if 'type' in json:
292 self.compiled_type = self._JsonTypeToPropertyType(json['compiled_type'])
293 else:
294 raise ParseException(self, 'Property has compiled_type but no type')
295 else:
296 self.compiled_type = self.type_
297
298 def _JsonTypeToPropertyType(self, json_type):
299 try:
300 return {
301 'any': PropertyType.ANY,
302 'array': PropertyType.ARRAY,
303 'binary': PropertyType.BINARY,
304 'boolean': PropertyType.BOOLEAN,
305 'integer': PropertyType.INTEGER,
306 'int64': PropertyType.INT64,
307 'function': PropertyType.FUNCTION,
308 'number': PropertyType.DOUBLE,
309 'object': PropertyType.OBJECT,
310 'string': PropertyType.STRING,
311 }[json_type]
312 except KeyError:
313 raise NotImplementedError('Type %s not recognized' % json_type)
[email protected]cfe484902012-02-15 14:52:32314
315 def GetUnixName(self):
316 """Gets the property's unix_name. Raises AttributeError if not set.
317 """
[email protected]116f0a3a2012-04-19 04:22:38318 if not self._unix_name:
[email protected]cfe484902012-02-15 14:52:32319 raise AttributeError('No unix_name set on %s' % self.name)
320 self._unix_name_used = True
321 return self._unix_name
322
323 def SetUnixName(self, unix_name):
324 """Set the property's unix_name. Raises AttributeError if the unix_name has
325 already been used (GetUnixName has been called).
326 """
327 if unix_name == self._unix_name:
328 return
329 if self._unix_name_used:
330 raise AttributeError(
331 'Cannot set the unix_name on %s; '
332 'it is already used elsewhere as %s' %
333 (self.name, self._unix_name))
334 self._unix_name = unix_name
335
[email protected]712eca0f2012-02-21 01:13:07336 def Copy(self):
337 """Makes a copy of this model.Property object and allow the unix_name to be
338 set again.
339 """
340 property_copy = copy.copy(self)
341 property_copy._unix_name_used = False
342 return property_copy
343
[email protected]cfe484902012-02-15 14:52:32344 unix_name = property(GetUnixName, SetUnixName)
[email protected]15f08dd2012-01-27 07:29:48345
[email protected]fe027682012-08-21 01:51:34346class _PropertyTypeInfo(object):
347 """This class is not an inner class of |PropertyType| so it can be pickled.
348 """
349 def __init__(self, is_fundamental, name):
350 self.is_fundamental = is_fundamental
351 self.name = name
352
353 def __repr__(self):
354 return self.name
355
[email protected]dc5f2fecf2012-08-31 01:55:51356 def __eq__(self, other):
357 return isinstance(other, _PropertyTypeInfo) and self.name == other.name
358
[email protected]15f08dd2012-01-27 07:29:48359class PropertyType(object):
360 """Enum of different types of properties/parameters.
361 """
[email protected]fe027682012-08-21 01:51:34362 INTEGER = _PropertyTypeInfo(True, "INTEGER")
363 INT64 = _PropertyTypeInfo(True, "INT64")
364 DOUBLE = _PropertyTypeInfo(True, "DOUBLE")
365 BOOLEAN = _PropertyTypeInfo(True, "BOOLEAN")
366 STRING = _PropertyTypeInfo(True, "STRING")
367 ENUM = _PropertyTypeInfo(False, "ENUM")
368 ARRAY = _PropertyTypeInfo(False, "ARRAY")
369 REF = _PropertyTypeInfo(False, "REF")
370 CHOICES = _PropertyTypeInfo(False, "CHOICES")
371 OBJECT = _PropertyTypeInfo(False, "OBJECT")
372 FUNCTION = _PropertyTypeInfo(False, "FUNCTION")
373 BINARY = _PropertyTypeInfo(False, "BINARY")
374 ANY = _PropertyTypeInfo(False, "ANY")
375 ADDITIONAL_PROPERTIES = _PropertyTypeInfo(False, "ADDITIONAL_PROPERTIES")
[email protected]cfe484902012-02-15 14:52:32376
[email protected]4ba6bdc2012-06-18 20:40:14377def UnixName(name):
[email protected]712eca0f2012-02-21 01:13:07378 """Returns the unix_style name for a given lowerCamelCase string.
[email protected]cfe484902012-02-15 14:52:32379 """
[email protected]df74dc42012-04-03 07:08:04380 # First replace any lowerUpper patterns with lower_Upper.
381 s1 = re.sub('([a-z])([A-Z])', r'\1_\2', name)
382 # Now replace any ACMEWidgets patterns with ACME_Widgets
383 s2 = re.sub('([A-Z]+)([A-Z][a-z])', r'\1_\2', s1)
384 # Finally, replace any remaining periods, and make lowercase.
385 return s2.replace('.', '_').lower()
[email protected]cfe484902012-02-15 14:52:32386
[email protected]0b255f002012-10-05 01:58:47387def _StripNamespace(name, namespace):
388 if name.startswith(namespace.name + '.'):
389 return name[len(namespace.name + '.'):]
390 return name
391
[email protected]116f0a3a2012-04-19 04:22:38392def _GetModelHierarchy(entity):
[email protected]feba21e2012-03-02 15:05:27393 """Returns the hierarchy of the given model entity."""
394 hierarchy = []
395 while entity:
396 try:
397 hierarchy.append(entity.name)
398 except AttributeError:
399 hierarchy.append(repr(entity))
400 entity = entity.parent
401 hierarchy.reverse()
402 return hierarchy
[email protected]116f0a3a2012-04-19 04:22:38403
[email protected]0b255f002012-10-05 01:58:47404def _AddTypes(model, json, namespace):
[email protected]116f0a3a2012-04-19 04:22:38405 """Adds Type objects to |model| contained in the 'types' field of |json|.
406 """
407 model.types = {}
408 for type_json in json.get('types', []):
[email protected]0b255f002012-10-05 01:58:47409 type_ = Type(model, type_json['id'], type_json, namespace)
[email protected]116f0a3a2012-04-19 04:22:38410 model.types[type_.name] = type_
411
[email protected]0b255f002012-10-05 01:58:47412def _AddFunctions(model, json, namespace):
[email protected]b741e8f2012-07-16 21:47:24413 """Adds Function objects to |model| contained in the 'functions' field of
414 |json|.
[email protected]116f0a3a2012-04-19 04:22:38415 """
416 model.functions = {}
417 for function_json in json.get('functions', []):
[email protected]0b255f002012-10-05 01:58:47418 function = Function(model, function_json, namespace, from_json=True)
[email protected]116f0a3a2012-04-19 04:22:38419 model.functions[function.name] = function
420
[email protected]0b255f002012-10-05 01:58:47421def _AddEvents(model, json, namespace):
[email protected]b741e8f2012-07-16 21:47:24422 """Adds Function objects to |model| contained in the 'events' field of |json|.
423 """
424 model.events = {}
425 for event_json in json.get('events', []):
[email protected]0b255f002012-10-05 01:58:47426 event = Function(model, event_json, namespace, from_client=True)
[email protected]b741e8f2012-07-16 21:47:24427 model.events[event.name] = event
428
[email protected]0b255f002012-10-05 01:58:47429def _AddProperties(model,
430 json,
431 namespace,
432 from_json=False,
433 from_client=False):
[email protected]116f0a3a2012-04-19 04:22:38434 """Adds model.Property objects to |model| contained in the 'properties' field
435 of |json|.
436 """
437 model.properties = {}
438 for name, property_json in json.get('properties', {}).items():
[email protected]116f0a3a2012-04-19 04:22:38439 model.properties[name] = Property(
440 model,
441 name,
442 property_json,
[email protected]0b255f002012-10-05 01:58:47443 namespace,
[email protected]116f0a3a2012-04-19 04:22:38444 from_json=from_json,
445 from_client=from_client)