blob: 76b27b20556e2009feb65bf822dc726391bd378b [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]116f0a3a2012-04-19 04:22:3854 _AddTypes(self, json)
55 _AddFunctions(self, json)
[email protected]b741e8f2012-07-16 21:47:2456 _AddEvents(self, json)
[email protected]116f0a3a2012-04-19 04:22:3857 _AddProperties(self, json)
[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]15f08dd2012-01-27 07:29:4875 """
[email protected]feba21e2012-03-02 15:05:2776 def __init__(self, parent, name, json):
[email protected]cf6d0b32012-04-12 04:30:2277 if json.get('type') == 'array':
78 self.type_ = PropertyType.ARRAY
79 self.item_type = Property(self, name + "Element", json['items'],
80 from_json=True,
81 from_client=True)
[email protected]0ef7f5a12012-05-01 03:58:0682 elif json.get('type') == 'string':
83 self.type_ = PropertyType.STRING
[email protected]cf6d0b32012-04-12 04:30:2284 else:
85 if not (
86 'properties' in json or
87 'additionalProperties' in json or
[email protected]e975d112012-07-31 22:12:4388 'functions' in json or
89 'events' in json):
[email protected]116f0a3a2012-04-19 04:22:3890 raise ParseException(self, name + " has no properties or functions")
[email protected]cf6d0b32012-04-12 04:30:2291 self.type_ = PropertyType.OBJECT
[email protected]feba21e2012-03-02 15:05:2792 self.name = name
[email protected]15f08dd2012-01-27 07:29:4893 self.description = json.get('description')
[email protected]25cbf6012012-02-28 05:51:4494 self.from_json = True
95 self.from_client = True
[email protected]feba21e2012-03-02 15:05:2796 self.parent = parent
[email protected]491e60d32012-07-20 01:03:1397 self.instance_of = json.get('isInstanceOf', None)
[email protected]116f0a3a2012-04-19 04:22:3898 _AddFunctions(self, json)
[email protected]d32b8e52012-07-23 18:40:0899 _AddEvents(self, json)
[email protected]116f0a3a2012-04-19 04:22:38100 _AddProperties(self, json, from_json=True, from_client=True)
[email protected]feba21e2012-03-02 15:05:27101
[email protected]116f0a3a2012-04-19 04:22:38102 additional_properties_key = 'additionalProperties'
103 additional_properties = json.get(additional_properties_key)
[email protected]feba21e2012-03-02 15:05:27104 if additional_properties:
[email protected]116f0a3a2012-04-19 04:22:38105 self.properties[additional_properties_key] = Property(
106 self,
107 additional_properties_key,
108 additional_properties,
109 is_additional_properties=True)
[email protected]15f08dd2012-01-27 07:29:48110
[email protected]15f08dd2012-01-27 07:29:48111class Function(object):
112 """A Function defined in the API.
[email protected]cfe484902012-02-15 14:52:32113
114 Properties:
115 - |name| the function name
116 - |params| a list of parameters to the function (order matters). A separate
117 parameter is used for each choice of a 'choices' parameter.
118 - |description| a description of the function (if provided)
119 - |callback| the callback parameter to the function. There should be exactly
120 one
[email protected]4b3f7852012-07-17 06:33:30121 - |optional| whether the Function is "optional"; this only makes sense to be
122 present when the Function is representing a callback property.
[email protected]15f08dd2012-01-27 07:29:48123 """
[email protected]b741e8f2012-07-16 21:47:24124 def __init__(self, parent, json, from_json=False, from_client=False):
[email protected]15f08dd2012-01-27 07:29:48125 self.name = json['name']
126 self.params = []
[email protected]feba21e2012-03-02 15:05:27127 self.description = json.get('description')
[email protected]15f08dd2012-01-27 07:29:48128 self.callback = None
[email protected]4b3f7852012-07-17 06:33:30129 self.optional = json.get('optional', False)
[email protected]feba21e2012-03-02 15:05:27130 self.parent = parent
[email protected]a9ead752012-05-24 02:08:45131 self.nocompile = json.get('nocompile')
[email protected]db943992012-08-02 14:02:54132
133 callback_param = None
[email protected]b741e8f2012-07-16 21:47:24134 for param in json.get('parameters', []):
[email protected]db943992012-08-02 14:02:54135 def GeneratePropertyFromParam(p):
136 return Property(self,
137 p['name'], p,
138 from_json=from_json,
139 from_client=from_client)
140
[email protected]15f08dd2012-01-27 07:29:48141 if param.get('type') == 'function':
[email protected]db943992012-08-02 14:02:54142 if callback_param:
143 # No ParseException because the webstore has this.
144 # Instead, pretend all intermediate callbacks are properties.
145 self.params.append(GeneratePropertyFromParam(callback_param))
146 callback_param = param
[email protected]15f08dd2012-01-27 07:29:48147 else:
[email protected]db943992012-08-02 14:02:54148 self.params.append(GeneratePropertyFromParam(param))
149
150 if callback_param:
151 self.callback = Function(self, callback_param, from_client=True)
152
[email protected]491e60d32012-07-20 01:03:13153 self.returns = None
154 if 'returns' in json:
155 self.returns = Property(self, 'return', json['returns'])
[email protected]15f08dd2012-01-27 07:29:48156
[email protected]15f08dd2012-01-27 07:29:48157class Property(object):
158 """A property of a type OR a parameter to a function.
159
[email protected]cfe484902012-02-15 14:52:32160 Properties:
161 - |name| name of the property as in the json. This shouldn't change since
162 it is the key used to access DictionaryValues
163 - |unix_name| the unix_style_name of the property. Used as variable name
164 - |optional| a boolean representing whether the property is optional
165 - |description| a description of the property (if provided)
166 - |type_| the model.PropertyType of this property
[email protected]1d0f57e2012-07-31 22:47:50167 - |compiled_type| the model.PropertyType that this property should be
168 compiled to from the JSON. Defaults to |type_|.
[email protected]cfe484902012-02-15 14:52:32169 - |ref_type| the type that the REF property is referencing. Can be used to
170 map to its model.Type
171 - |item_type| a model.Property representing the type of each element in an
172 ARRAY
173 - |properties| the properties of an OBJECT parameter
[email protected]b741e8f2012-07-16 21:47:24174 - |from_client| indicates that instances of the Type can originate from the
175 users of generated code, such as top-level types and function results
176 - |from_json| indicates that instances of the Type can originate from the
177 JSON (as described by the schema), such as top-level types and function
178 parameters
[email protected]15f08dd2012-01-27 07:29:48179 """
[email protected]feba21e2012-03-02 15:05:27180
181 def __init__(self, parent, name, json, is_additional_properties=False,
182 from_json=False, from_client=False):
[email protected]15f08dd2012-01-27 07:29:48183 self.name = name
[email protected]4ba6bdc2012-06-18 20:40:14184 self._unix_name = UnixName(self.name)
[email protected]cfe484902012-02-15 14:52:32185 self._unix_name_used = False
[email protected]15f08dd2012-01-27 07:29:48186 self.optional = json.get('optional', False)
[email protected]3072d072012-07-19 00:35:33187 self.functions = {}
[email protected]116f0a3a2012-04-19 04:22:38188 self.has_value = False
[email protected]15f08dd2012-01-27 07:29:48189 self.description = json.get('description')
[email protected]feba21e2012-03-02 15:05:27190 self.parent = parent
[email protected]b741e8f2012-07-16 21:47:24191 self.from_json = from_json
192 self.from_client = from_client
[email protected]491e60d32012-07-20 01:03:13193 self.instance_of = json.get('isInstanceOf', None)
[email protected]116f0a3a2012-04-19 04:22:38194 _AddProperties(self, json)
[email protected]feba21e2012-03-02 15:05:27195 if is_additional_properties:
196 self.type_ = PropertyType.ADDITIONAL_PROPERTIES
197 elif '$ref' in json:
[email protected]15f08dd2012-01-27 07:29:48198 self.ref_type = json['$ref']
199 self.type_ = PropertyType.REF
[email protected]9d273182012-07-11 21:03:26200 elif 'enum' in json and json.get('type') == 'string':
201 # Non-string enums (as in the case of [legalValues=(1,2)]) should fall
202 # through to the next elif.
[email protected]2111fff9b2012-02-22 12:06:51203 self.enum_values = []
204 for value in json['enum']:
205 self.enum_values.append(value)
206 self.type_ = PropertyType.ENUM
[email protected]15f08dd2012-01-27 07:29:48207 elif 'type' in json:
[email protected]1d0f57e2012-07-31 22:47:50208 self.type_ = self._JsonTypeToPropertyType(json['type'])
209 if self.type_ == PropertyType.ARRAY:
[email protected]feba21e2012-03-02 15:05:27210 self.item_type = Property(self, name + "Element", json['items'],
211 from_json=from_json,
212 from_client=from_client)
[email protected]1d0f57e2012-07-31 22:47:50213 elif self.type_ == PropertyType.OBJECT:
[email protected]25cbf6012012-02-28 05:51:44214 # These members are read when this OBJECT Property is used as a Type
[email protected]feba21e2012-03-02 15:05:27215 type_ = Type(self, self.name, json)
[email protected]116f0a3a2012-04-19 04:22:38216 # self.properties will already have some value from |_AddProperties|.
217 self.properties.update(type_.properties)
[email protected]feba21e2012-03-02 15:05:27218 self.functions = type_.functions
[email protected]15f08dd2012-01-27 07:29:48219 elif 'choices' in json:
[email protected]b741e8f2012-07-16 21:47:24220 if not json['choices'] or len(json['choices']) == 0:
[email protected]116f0a3a2012-04-19 04:22:38221 raise ParseException(self, 'Choices has no choices')
[email protected]15f08dd2012-01-27 07:29:48222 self.choices = {}
[email protected]cfe484902012-02-15 14:52:32223 self.type_ = PropertyType.CHOICES
[email protected]1d0f57e2012-07-31 22:47:50224 self.compiled_type = self.type_
[email protected]cfe484902012-02-15 14:52:32225 for choice_json in json['choices']:
[email protected]feba21e2012-03-02 15:05:27226 choice = Property(self, self.name, choice_json,
227 from_json=from_json,
228 from_client=from_client)
[email protected]b741e8f2012-07-16 21:47:24229 choice.unix_name = UnixName(self.name + choice.type_.name)
[email protected]cfe484902012-02-15 14:52:32230 # The existence of any single choice is optional
231 choice.optional = True
232 self.choices[choice.type_] = choice
[email protected]116f0a3a2012-04-19 04:22:38233 elif 'value' in json:
234 self.has_value = True
235 self.value = json['value']
236 if type(self.value) == int:
237 self.type_ = PropertyType.INTEGER
[email protected]1d0f57e2012-07-31 22:47:50238 self.compiled_type = self.type_
[email protected]116f0a3a2012-04-19 04:22:38239 else:
240 # TODO(kalman): support more types as necessary.
241 raise ParseException(
242 self, '"%s" is not a supported type' % type(self.value))
[email protected]cfe484902012-02-15 14:52:32243 else:
[email protected]116f0a3a2012-04-19 04:22:38244 raise ParseException(
245 self, 'Property has no type, $ref, choices, or value')
[email protected]1d0f57e2012-07-31 22:47:50246 if 'compiled_type' in json:
247 if 'type' in json:
248 self.compiled_type = self._JsonTypeToPropertyType(json['compiled_type'])
249 else:
250 raise ParseException(self, 'Property has compiled_type but no type')
251 else:
252 self.compiled_type = self.type_
253
254 def _JsonTypeToPropertyType(self, json_type):
255 try:
256 return {
257 'any': PropertyType.ANY,
258 'array': PropertyType.ARRAY,
259 'binary': PropertyType.BINARY,
260 'boolean': PropertyType.BOOLEAN,
261 'integer': PropertyType.INTEGER,
262 'int64': PropertyType.INT64,
263 'function': PropertyType.FUNCTION,
264 'number': PropertyType.DOUBLE,
265 'object': PropertyType.OBJECT,
266 'string': PropertyType.STRING,
267 }[json_type]
268 except KeyError:
269 raise NotImplementedError('Type %s not recognized' % json_type)
[email protected]cfe484902012-02-15 14:52:32270
271 def GetUnixName(self):
272 """Gets the property's unix_name. Raises AttributeError if not set.
273 """
[email protected]116f0a3a2012-04-19 04:22:38274 if not self._unix_name:
[email protected]cfe484902012-02-15 14:52:32275 raise AttributeError('No unix_name set on %s' % self.name)
276 self._unix_name_used = True
277 return self._unix_name
278
279 def SetUnixName(self, unix_name):
280 """Set the property's unix_name. Raises AttributeError if the unix_name has
281 already been used (GetUnixName has been called).
282 """
283 if unix_name == self._unix_name:
284 return
285 if self._unix_name_used:
286 raise AttributeError(
287 'Cannot set the unix_name on %s; '
288 'it is already used elsewhere as %s' %
289 (self.name, self._unix_name))
290 self._unix_name = unix_name
291
[email protected]712eca0f2012-02-21 01:13:07292 def Copy(self):
293 """Makes a copy of this model.Property object and allow the unix_name to be
294 set again.
295 """
296 property_copy = copy.copy(self)
297 property_copy._unix_name_used = False
298 return property_copy
299
[email protected]cfe484902012-02-15 14:52:32300 unix_name = property(GetUnixName, SetUnixName)
[email protected]15f08dd2012-01-27 07:29:48301
302class PropertyType(object):
303 """Enum of different types of properties/parameters.
304 """
305 class _Info(object):
[email protected]cfe484902012-02-15 14:52:32306 def __init__(self, is_fundamental, name):
[email protected]15f08dd2012-01-27 07:29:48307 self.is_fundamental = is_fundamental
[email protected]cfe484902012-02-15 14:52:32308 self.name = name
[email protected]15f08dd2012-01-27 07:29:48309
[email protected]cfe484902012-02-15 14:52:32310 def __repr__(self):
311 return self.name
312
313 INTEGER = _Info(True, "INTEGER")
[email protected]1d0f57e2012-07-31 22:47:50314 INT64 = _Info(True, "INT64")
[email protected]cfe484902012-02-15 14:52:32315 DOUBLE = _Info(True, "DOUBLE")
316 BOOLEAN = _Info(True, "BOOLEAN")
317 STRING = _Info(True, "STRING")
[email protected]2111fff9b2012-02-22 12:06:51318 ENUM = _Info(False, "ENUM")
[email protected]cfe484902012-02-15 14:52:32319 ARRAY = _Info(False, "ARRAY")
320 REF = _Info(False, "REF")
321 CHOICES = _Info(False, "CHOICES")
322 OBJECT = _Info(False, "OBJECT")
[email protected]3c6d1862012-07-26 02:18:15323 FUNCTION = _Info(False, "FUNCTION")
[email protected]bc69ec1a2012-04-24 03:17:19324 BINARY = _Info(False, "BINARY")
[email protected]cfe484902012-02-15 14:52:32325 ANY = _Info(False, "ANY")
[email protected]feba21e2012-03-02 15:05:27326 ADDITIONAL_PROPERTIES = _Info(False, "ADDITIONAL_PROPERTIES")
[email protected]cfe484902012-02-15 14:52:32327
[email protected]4ba6bdc2012-06-18 20:40:14328def UnixName(name):
[email protected]712eca0f2012-02-21 01:13:07329 """Returns the unix_style name for a given lowerCamelCase string.
[email protected]cfe484902012-02-15 14:52:32330 """
[email protected]df74dc42012-04-03 07:08:04331 # First replace any lowerUpper patterns with lower_Upper.
332 s1 = re.sub('([a-z])([A-Z])', r'\1_\2', name)
333 # Now replace any ACMEWidgets patterns with ACME_Widgets
334 s2 = re.sub('([A-Z]+)([A-Z][a-z])', r'\1_\2', s1)
335 # Finally, replace any remaining periods, and make lowercase.
336 return s2.replace('.', '_').lower()
[email protected]cfe484902012-02-15 14:52:32337
[email protected]116f0a3a2012-04-19 04:22:38338def _GetModelHierarchy(entity):
[email protected]feba21e2012-03-02 15:05:27339 """Returns the hierarchy of the given model entity."""
340 hierarchy = []
341 while entity:
342 try:
343 hierarchy.append(entity.name)
344 except AttributeError:
345 hierarchy.append(repr(entity))
346 entity = entity.parent
347 hierarchy.reverse()
348 return hierarchy
[email protected]116f0a3a2012-04-19 04:22:38349
350def _AddTypes(model, json):
351 """Adds Type objects to |model| contained in the 'types' field of |json|.
352 """
353 model.types = {}
354 for type_json in json.get('types', []):
355 type_ = Type(model, type_json['id'], type_json)
356 model.types[type_.name] = type_
357
358def _AddFunctions(model, json):
[email protected]b741e8f2012-07-16 21:47:24359 """Adds Function objects to |model| contained in the 'functions' field of
360 |json|.
[email protected]116f0a3a2012-04-19 04:22:38361 """
362 model.functions = {}
363 for function_json in json.get('functions', []):
[email protected]b741e8f2012-07-16 21:47:24364 function = Function(model, function_json, from_json=True)
[email protected]116f0a3a2012-04-19 04:22:38365 model.functions[function.name] = function
366
[email protected]b741e8f2012-07-16 21:47:24367def _AddEvents(model, json):
368 """Adds Function objects to |model| contained in the 'events' field of |json|.
369 """
370 model.events = {}
371 for event_json in json.get('events', []):
372 event = Function(model, event_json, from_client=True)
373 model.events[event.name] = event
374
[email protected]116f0a3a2012-04-19 04:22:38375def _AddProperties(model, json, from_json=False, from_client=False):
376 """Adds model.Property objects to |model| contained in the 'properties' field
377 of |json|.
378 """
379 model.properties = {}
380 for name, property_json in json.get('properties', {}).items():
[email protected]116f0a3a2012-04-19 04:22:38381 model.properties[name] = Property(
382 model,
383 name,
384 property_json,
385 from_json=from_json,
386 from_client=from_client)