blob: 23b77c6d1402dba8ee88cf0d33b7f78b28b0ef81 [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]116f0a3a2012-04-19 04:22:3845 - |properties| a map of property names to their model.Property
[email protected]15f08dd2012-01-27 07:29:4846 """
47 def __init__(self, json, source_file):
48 self.name = json['namespace']
[email protected]116f0a3a2012-04-19 04:22:3849 self.unix_name = _UnixName(self.name)
[email protected]15f08dd2012-01-27 07:29:4850 self.source_file = source_file
51 self.source_file_dir, self.source_file_filename = os.path.split(source_file)
[email protected]feba21e2012-03-02 15:05:2752 self.parent = None
[email protected]116f0a3a2012-04-19 04:22:3853 _AddTypes(self, json)
54 _AddFunctions(self, json)
55 _AddProperties(self, json)
[email protected]15f08dd2012-01-27 07:29:4856
57class Type(object):
58 """A Type defined in the json.
[email protected]cfe484902012-02-15 14:52:3259
60 Properties:
61 - |name| the type name
62 - |description| the description of the type (if provided)
[email protected]feba21e2012-03-02 15:05:2763 - |properties| a map of property unix_names to their model.Property
64 - |functions| a map of function names to their model.Function
[email protected]25cbf6012012-02-28 05:51:4465 - |from_client| indicates that instances of the Type can originate from the
66 users of generated code, such as top-level types and function results
67 - |from_json| indicates that instances of the Type can originate from the
68 JSON (as described by the schema), such as top-level types and function
69 parameters
[email protected]cf6d0b32012-04-12 04:30:2270 - |type_| the PropertyType of this Type
71 - |item_type| if this is an array, the type of items in the array
[email protected]15f08dd2012-01-27 07:29:4872 """
[email protected]feba21e2012-03-02 15:05:2773 def __init__(self, parent, name, json):
[email protected]cf6d0b32012-04-12 04:30:2274 if json.get('type') == 'array':
75 self.type_ = PropertyType.ARRAY
76 self.item_type = Property(self, name + "Element", json['items'],
77 from_json=True,
78 from_client=True)
[email protected]0ef7f5a12012-05-01 03:58:0679 elif json.get('type') == 'string':
80 self.type_ = PropertyType.STRING
[email protected]cf6d0b32012-04-12 04:30:2281 else:
82 if not (
83 'properties' in json or
84 'additionalProperties' in json or
85 'functions' in json):
[email protected]116f0a3a2012-04-19 04:22:3886 raise ParseException(self, name + " has no properties or functions")
[email protected]cf6d0b32012-04-12 04:30:2287 self.type_ = PropertyType.OBJECT
[email protected]feba21e2012-03-02 15:05:2788 self.name = name
[email protected]15f08dd2012-01-27 07:29:4889 self.description = json.get('description')
[email protected]25cbf6012012-02-28 05:51:4490 self.from_json = True
91 self.from_client = True
[email protected]feba21e2012-03-02 15:05:2792 self.parent = parent
[email protected]116f0a3a2012-04-19 04:22:3893 _AddFunctions(self, json)
94 _AddProperties(self, json, from_json=True, from_client=True)
[email protected]feba21e2012-03-02 15:05:2795
[email protected]116f0a3a2012-04-19 04:22:3896 additional_properties_key = 'additionalProperties'
97 additional_properties = json.get(additional_properties_key)
[email protected]feba21e2012-03-02 15:05:2798 if additional_properties:
[email protected]116f0a3a2012-04-19 04:22:3899 self.properties[additional_properties_key] = Property(
100 self,
101 additional_properties_key,
102 additional_properties,
103 is_additional_properties=True)
[email protected]15f08dd2012-01-27 07:29:48104
105class Callback(object):
106 """A callback parameter to a Function.
[email protected]cfe484902012-02-15 14:52:32107
108 Properties:
109 - |params| the parameters to this callback.
[email protected]15f08dd2012-01-27 07:29:48110 """
[email protected]feba21e2012-03-02 15:05:27111 def __init__(self, parent, json):
[email protected]15f08dd2012-01-27 07:29:48112 params = json['parameters']
[email protected]feba21e2012-03-02 15:05:27113 self.parent = parent
[email protected]cfe484902012-02-15 14:52:32114 self.params = []
[email protected]15f08dd2012-01-27 07:29:48115 if len(params) == 0:
[email protected]cfe484902012-02-15 14:52:32116 return
[email protected]15f08dd2012-01-27 07:29:48117 elif len(params) == 1:
118 param = params[0]
[email protected]feba21e2012-03-02 15:05:27119 self.params.append(Property(self, param['name'], param,
[email protected]25cbf6012012-02-28 05:51:44120 from_client=True))
[email protected]15f08dd2012-01-27 07:29:48121 else:
[email protected]116f0a3a2012-04-19 04:22:38122 raise ParseException(
123 self,
124 "Callbacks can have at most a single parameter")
[email protected]15f08dd2012-01-27 07:29:48125
126class Function(object):
127 """A Function defined in the API.
[email protected]cfe484902012-02-15 14:52:32128
129 Properties:
130 - |name| the function name
131 - |params| a list of parameters to the function (order matters). A separate
132 parameter is used for each choice of a 'choices' parameter.
133 - |description| a description of the function (if provided)
134 - |callback| the callback parameter to the function. There should be exactly
135 one
[email protected]15f08dd2012-01-27 07:29:48136 """
[email protected]feba21e2012-03-02 15:05:27137 def __init__(self, parent, json):
[email protected]15f08dd2012-01-27 07:29:48138 self.name = json['name']
139 self.params = []
[email protected]feba21e2012-03-02 15:05:27140 self.description = json.get('description')
[email protected]15f08dd2012-01-27 07:29:48141 self.callback = None
[email protected]feba21e2012-03-02 15:05:27142 self.parent = parent
[email protected]a9ead752012-05-24 02:08:45143 self.nocompile = json.get('nocompile')
[email protected]15f08dd2012-01-27 07:29:48144 for param in json['parameters']:
145 if param.get('type') == 'function':
[email protected]feba21e2012-03-02 15:05:27146 if self.callback:
[email protected]116f0a3a2012-04-19 04:22:38147 raise ParseException(self, self.name + " has more than one callback")
[email protected]feba21e2012-03-02 15:05:27148 self.callback = Callback(self, param)
[email protected]15f08dd2012-01-27 07:29:48149 else:
[email protected]feba21e2012-03-02 15:05:27150 self.params.append(Property(self, param['name'], param,
[email protected]25cbf6012012-02-28 05:51:44151 from_json=True))
[email protected]15f08dd2012-01-27 07:29:48152
[email protected]15f08dd2012-01-27 07:29:48153class Property(object):
154 """A property of a type OR a parameter to a function.
155
[email protected]cfe484902012-02-15 14:52:32156 Properties:
157 - |name| name of the property as in the json. This shouldn't change since
158 it is the key used to access DictionaryValues
159 - |unix_name| the unix_style_name of the property. Used as variable name
160 - |optional| a boolean representing whether the property is optional
161 - |description| a description of the property (if provided)
162 - |type_| the model.PropertyType of this property
163 - |ref_type| the type that the REF property is referencing. Can be used to
164 map to its model.Type
165 - |item_type| a model.Property representing the type of each element in an
166 ARRAY
167 - |properties| the properties of an OBJECT parameter
[email protected]15f08dd2012-01-27 07:29:48168 """
[email protected]feba21e2012-03-02 15:05:27169
170 def __init__(self, parent, name, json, is_additional_properties=False,
171 from_json=False, from_client=False):
[email protected]25cbf6012012-02-28 05:51:44172 """
173 Parameters:
174 - |from_json| indicates that instances of the Type can originate from the
175 JSON (as described by the schema), such as top-level types and function
176 parameters
177 - |from_client| indicates that instances of the Type can originate from the
178 users of generated code, such as top-level types and function results
179 """
[email protected]15f08dd2012-01-27 07:29:48180 self.name = name
[email protected]116f0a3a2012-04-19 04:22:38181 self._unix_name = _UnixName(self.name)
[email protected]cfe484902012-02-15 14:52:32182 self._unix_name_used = False
[email protected]15f08dd2012-01-27 07:29:48183 self.optional = json.get('optional', False)
[email protected]116f0a3a2012-04-19 04:22:38184 self.has_value = False
[email protected]15f08dd2012-01-27 07:29:48185 self.description = json.get('description')
[email protected]feba21e2012-03-02 15:05:27186 self.parent = parent
[email protected]116f0a3a2012-04-19 04:22:38187 _AddProperties(self, json)
[email protected]feba21e2012-03-02 15:05:27188 if is_additional_properties:
189 self.type_ = PropertyType.ADDITIONAL_PROPERTIES
190 elif '$ref' in json:
[email protected]15f08dd2012-01-27 07:29:48191 self.ref_type = json['$ref']
192 self.type_ = PropertyType.REF
[email protected]2111fff9b2012-02-22 12:06:51193 elif 'enum' in json:
194 self.enum_values = []
195 for value in json['enum']:
196 self.enum_values.append(value)
197 self.type_ = PropertyType.ENUM
[email protected]15f08dd2012-01-27 07:29:48198 elif 'type' in json:
199 json_type = json['type']
200 if json_type == 'string':
201 self.type_ = PropertyType.STRING
[email protected]cfe484902012-02-15 14:52:32202 elif json_type == 'any':
203 self.type_ = PropertyType.ANY
204 elif json_type == 'boolean':
[email protected]15f08dd2012-01-27 07:29:48205 self.type_ = PropertyType.BOOLEAN
206 elif json_type == 'integer':
207 self.type_ = PropertyType.INTEGER
[email protected]cfe484902012-02-15 14:52:32208 elif json_type == 'number':
[email protected]15f08dd2012-01-27 07:29:48209 self.type_ = PropertyType.DOUBLE
210 elif json_type == 'array':
[email protected]feba21e2012-03-02 15:05:27211 self.item_type = Property(self, name + "Element", json['items'],
212 from_json=from_json,
213 from_client=from_client)
[email protected]15f08dd2012-01-27 07:29:48214 self.type_ = PropertyType.ARRAY
215 elif json_type == 'object':
[email protected]15f08dd2012-01-27 07:29:48216 self.type_ = PropertyType.OBJECT
[email protected]25cbf6012012-02-28 05:51:44217 # These members are read when this OBJECT Property is used as a Type
[email protected]25cbf6012012-02-28 05:51:44218 self.from_json = from_json
219 self.from_client = from_client
[email protected]feba21e2012-03-02 15:05:27220 type_ = Type(self, self.name, json)
[email protected]116f0a3a2012-04-19 04:22:38221 # self.properties will already have some value from |_AddProperties|.
222 self.properties.update(type_.properties)
[email protected]feba21e2012-03-02 15:05:27223 self.functions = type_.functions
[email protected]bc69ec1a2012-04-24 03:17:19224 elif json_type == 'binary':
225 self.type_ = PropertyType.BINARY
[email protected]15f08dd2012-01-27 07:29:48226 else:
[email protected]feba21e2012-03-02 15:05:27227 raise ParseException(self, 'type ' + json_type + ' not recognized')
[email protected]15f08dd2012-01-27 07:29:48228 elif 'choices' in json:
[email protected]feba21e2012-03-02 15:05:27229 if not json['choices']:
[email protected]116f0a3a2012-04-19 04:22:38230 raise ParseException(self, 'Choices has no choices')
[email protected]15f08dd2012-01-27 07:29:48231 self.choices = {}
[email protected]cfe484902012-02-15 14:52:32232 self.type_ = PropertyType.CHOICES
233 for choice_json in json['choices']:
[email protected]feba21e2012-03-02 15:05:27234 choice = Property(self, self.name, choice_json,
235 from_json=from_json,
236 from_client=from_client)
[email protected]cfe484902012-02-15 14:52:32237 # A choice gets its unix_name set in
238 # cpp_type_generator.GetExpandedChoicesInParams
239 choice._unix_name = None
240 # The existence of any single choice is optional
241 choice.optional = True
242 self.choices[choice.type_] = choice
[email protected]116f0a3a2012-04-19 04:22:38243 elif 'value' in json:
244 self.has_value = True
245 self.value = json['value']
246 if type(self.value) == int:
247 self.type_ = PropertyType.INTEGER
248 else:
249 # TODO(kalman): support more types as necessary.
250 raise ParseException(
251 self, '"%s" is not a supported type' % type(self.value))
[email protected]cfe484902012-02-15 14:52:32252 else:
[email protected]116f0a3a2012-04-19 04:22:38253 raise ParseException(
254 self, 'Property has no type, $ref, choices, or value')
[email protected]cfe484902012-02-15 14:52:32255
256 def GetUnixName(self):
257 """Gets the property's unix_name. Raises AttributeError if not set.
258 """
[email protected]116f0a3a2012-04-19 04:22:38259 if not self._unix_name:
[email protected]cfe484902012-02-15 14:52:32260 raise AttributeError('No unix_name set on %s' % self.name)
261 self._unix_name_used = True
262 return self._unix_name
263
264 def SetUnixName(self, unix_name):
265 """Set the property's unix_name. Raises AttributeError if the unix_name has
266 already been used (GetUnixName has been called).
267 """
268 if unix_name == self._unix_name:
269 return
270 if self._unix_name_used:
271 raise AttributeError(
272 'Cannot set the unix_name on %s; '
273 'it is already used elsewhere as %s' %
274 (self.name, self._unix_name))
275 self._unix_name = unix_name
276
[email protected]712eca0f2012-02-21 01:13:07277 def Copy(self):
278 """Makes a copy of this model.Property object and allow the unix_name to be
279 set again.
280 """
281 property_copy = copy.copy(self)
282 property_copy._unix_name_used = False
283 return property_copy
284
[email protected]cfe484902012-02-15 14:52:32285 unix_name = property(GetUnixName, SetUnixName)
[email protected]15f08dd2012-01-27 07:29:48286
287class PropertyType(object):
288 """Enum of different types of properties/parameters.
289 """
290 class _Info(object):
[email protected]cfe484902012-02-15 14:52:32291 def __init__(self, is_fundamental, name):
[email protected]15f08dd2012-01-27 07:29:48292 self.is_fundamental = is_fundamental
[email protected]cfe484902012-02-15 14:52:32293 self.name = name
[email protected]15f08dd2012-01-27 07:29:48294
[email protected]cfe484902012-02-15 14:52:32295 def __repr__(self):
296 return self.name
297
298 INTEGER = _Info(True, "INTEGER")
299 DOUBLE = _Info(True, "DOUBLE")
300 BOOLEAN = _Info(True, "BOOLEAN")
301 STRING = _Info(True, "STRING")
[email protected]2111fff9b2012-02-22 12:06:51302 ENUM = _Info(False, "ENUM")
[email protected]cfe484902012-02-15 14:52:32303 ARRAY = _Info(False, "ARRAY")
304 REF = _Info(False, "REF")
305 CHOICES = _Info(False, "CHOICES")
306 OBJECT = _Info(False, "OBJECT")
[email protected]bc69ec1a2012-04-24 03:17:19307 BINARY = _Info(False, "BINARY")
[email protected]cfe484902012-02-15 14:52:32308 ANY = _Info(False, "ANY")
[email protected]feba21e2012-03-02 15:05:27309 ADDITIONAL_PROPERTIES = _Info(False, "ADDITIONAL_PROPERTIES")
[email protected]cfe484902012-02-15 14:52:32310
[email protected]116f0a3a2012-04-19 04:22:38311def _UnixName(name):
[email protected]712eca0f2012-02-21 01:13:07312 """Returns the unix_style name for a given lowerCamelCase string.
[email protected]cfe484902012-02-15 14:52:32313 """
[email protected]df74dc42012-04-03 07:08:04314 # First replace any lowerUpper patterns with lower_Upper.
315 s1 = re.sub('([a-z])([A-Z])', r'\1_\2', name)
316 # Now replace any ACMEWidgets patterns with ACME_Widgets
317 s2 = re.sub('([A-Z]+)([A-Z][a-z])', r'\1_\2', s1)
318 # Finally, replace any remaining periods, and make lowercase.
319 return s2.replace('.', '_').lower()
[email protected]cfe484902012-02-15 14:52:32320
[email protected]116f0a3a2012-04-19 04:22:38321def _GetModelHierarchy(entity):
[email protected]feba21e2012-03-02 15:05:27322 """Returns the hierarchy of the given model entity."""
323 hierarchy = []
324 while entity:
325 try:
326 hierarchy.append(entity.name)
327 except AttributeError:
328 hierarchy.append(repr(entity))
329 entity = entity.parent
330 hierarchy.reverse()
331 return hierarchy
[email protected]116f0a3a2012-04-19 04:22:38332
333def _AddTypes(model, json):
334 """Adds Type objects to |model| contained in the 'types' field of |json|.
335 """
336 model.types = {}
337 for type_json in json.get('types', []):
338 type_ = Type(model, type_json['id'], type_json)
339 model.types[type_.name] = type_
340
341def _AddFunctions(model, json):
342 """Adds Function objects to |model| contained in the 'types' field of |json|.
343 """
344 model.functions = {}
345 for function_json in json.get('functions', []):
346 function = Function(model, function_json)
347 model.functions[function.name] = function
348
349def _AddProperties(model, json, from_json=False, from_client=False):
350 """Adds model.Property objects to |model| contained in the 'properties' field
351 of |json|.
352 """
353 model.properties = {}
354 for name, property_json in json.get('properties', {}).items():
355 # TODO(calamity): support functions (callbacks) as properties. The model
356 # doesn't support it yet because the h/cc generators don't -- this is
357 # because we'd need to hook it into a base::Callback or something.
358 #
359 # However, pragmatically it's not necessary to support them anyway, since
360 # the instances of functions-on-properties in the extension APIs are all
361 # handled in pure Javascript on the render process (and .: never reach
362 # C++ let alone the browser).
363 if property_json.get('type') == 'function':
364 continue
365 model.properties[name] = Property(
366 model,
367 name,
368 property_json,
369 from_json=from_json,
370 from_client=from_client)