blob: 9a5c55f07b54f56797e01cc763ef6715a0450a70 [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
9class Model(object):
10 """Model of all namespaces that comprise an API.
[email protected]cfe484902012-02-15 14:52:3211
12 Properties:
13 - |namespaces| a map of a namespace name to its model.Namespace
[email protected]15f08dd2012-01-27 07:29:4814 """
15 def __init__(self):
16 self.namespaces = {}
17
18 def AddNamespace(self, json, source_file):
[email protected]a1f774972012-04-17 02:11:0919 """Add a namespace's json to the model and returns the namespace.
[email protected]15f08dd2012-01-27 07:29:4820 """
[email protected]15f08dd2012-01-27 07:29:4821 namespace = Namespace(json, source_file)
22 self.namespaces[namespace.name] = namespace
23 return namespace
24
25class Namespace(object):
26 """An API namespace.
[email protected]cfe484902012-02-15 14:52:3227
28 Properties:
29 - |name| the name of the namespace
[email protected]712eca0f2012-02-21 01:13:0730 - |unix_name| the unix_name of the namespace
31 - |source_file| the file that contained the namespace definition
32 - |source_file_dir| the directory component of |source_file|
33 - |source_file_filename| the filename component of |source_file|
[email protected]cfe484902012-02-15 14:52:3234 - |types| a map of type names to their model.Type
35 - |functions| a map of function names to their model.Function
[email protected]15f08dd2012-01-27 07:29:4836 """
37 def __init__(self, json, source_file):
38 self.name = json['namespace']
[email protected]feba21e2012-03-02 15:05:2739 self.unix_name = UnixName(self.name)
[email protected]15f08dd2012-01-27 07:29:4840 self.source_file = source_file
41 self.source_file_dir, self.source_file_filename = os.path.split(source_file)
[email protected]15f08dd2012-01-27 07:29:4842 self.types = {}
43 self.functions = {}
[email protected]feba21e2012-03-02 15:05:2744 self.parent = None
45 # TODO(calamity): Implement properties on namespaces for shared structures
46 # or constants across a namespace (e.g Windows::WINDOW_ID_NONE).
47 for property_json in json.get('properties', []):
48 pass
[email protected]712eca0f2012-02-21 01:13:0749 for type_json in json.get('types', []):
[email protected]feba21e2012-03-02 15:05:2750 type_ = Type(self, type_json['id'], type_json)
[email protected]15f08dd2012-01-27 07:29:4851 self.types[type_.name] = type_
[email protected]712eca0f2012-02-21 01:13:0752 for function_json in json.get('functions', []):
[email protected]a1f774972012-04-17 02:11:0953 self.functions[function_json['name']] = Function(self, function_json)
[email protected]15f08dd2012-01-27 07:29:4854
55class Type(object):
56 """A Type defined in the json.
[email protected]cfe484902012-02-15 14:52:3257
58 Properties:
59 - |name| the type name
60 - |description| the description of the type (if provided)
[email protected]feba21e2012-03-02 15:05:2761 - |properties| a map of property unix_names to their model.Property
62 - |functions| a map of function names to their model.Function
[email protected]25cbf6012012-02-28 05:51:4463 - |from_client| indicates that instances of the Type can originate from the
64 users of generated code, such as top-level types and function results
65 - |from_json| indicates that instances of the Type can originate from the
66 JSON (as described by the schema), such as top-level types and function
67 parameters
[email protected]cf6d0b32012-04-12 04:30:2268 - |type_| the PropertyType of this Type
69 - |item_type| if this is an array, the type of items in the array
[email protected]15f08dd2012-01-27 07:29:4870 """
[email protected]feba21e2012-03-02 15:05:2771 def __init__(self, parent, name, json):
[email protected]cf6d0b32012-04-12 04:30:2272 if json.get('type') == 'array':
73 self.type_ = PropertyType.ARRAY
74 self.item_type = Property(self, name + "Element", json['items'],
75 from_json=True,
76 from_client=True)
77 else:
78 if not (
79 'properties' in json or
80 'additionalProperties' in json or
81 'functions' in json):
82 raise ParseException(name + " has no properties or functions")
83 self.type_ = PropertyType.OBJECT
[email protected]feba21e2012-03-02 15:05:2784 self.name = name
[email protected]15f08dd2012-01-27 07:29:4885 self.description = json.get('description')
[email protected]25cbf6012012-02-28 05:51:4486 self.from_json = True
87 self.from_client = True
[email protected]15f08dd2012-01-27 07:29:4888 self.properties = {}
[email protected]feba21e2012-03-02 15:05:2789 self.functions = {}
90 self.parent = parent
91 for function_json in json.get('functions', []):
[email protected]a1f774972012-04-17 02:11:0992 self.functions[function_json['name']] = Function(self, function_json)
[email protected]feba21e2012-03-02 15:05:2793 props = []
94 for prop_name, prop_json in json.get('properties', {}).items():
95 # TODO(calamity): support functions (callbacks) as properties. The model
[email protected]750b8362012-03-07 02:33:3596 # doesn't support it yet because the h/cc generators don't -- this is
[email protected]feba21e2012-03-02 15:05:2797 # because we'd need to hook it into a base::Callback or something.
98 #
99 # However, pragmatically it's not necessary to support them anyway, since
100 # the instances of functions-on-properties in the extension APIs are all
101 # handled in pure Javascript on the render process (and .: never reach
102 # C++ let alone the browser).
103 if prop_json.get('type') == 'function':
104 continue
105 props.append(Property(self, prop_name, prop_json,
[email protected]25cbf6012012-02-28 05:51:44106 from_json=True,
[email protected]feba21e2012-03-02 15:05:27107 from_client=True))
108
109 additional_properties = json.get('additionalProperties')
110 if additional_properties:
111 props.append(Property(self, 'additionalProperties', additional_properties,
112 is_additional_properties=True))
113
114 for prop in props:
115 if prop.unix_name in self.properties:
116 raise ParseException(
117 self.properties[prop.unix_name].name + ' and ' + prop.name +
118 ' are both named ' + prop.unix_name)
119 self.properties[prop.unix_name] = prop
[email protected]15f08dd2012-01-27 07:29:48120
121class Callback(object):
122 """A callback parameter to a Function.
[email protected]cfe484902012-02-15 14:52:32123
124 Properties:
125 - |params| the parameters to this callback.
[email protected]15f08dd2012-01-27 07:29:48126 """
[email protected]feba21e2012-03-02 15:05:27127 def __init__(self, parent, json):
[email protected]15f08dd2012-01-27 07:29:48128 params = json['parameters']
[email protected]feba21e2012-03-02 15:05:27129 self.parent = parent
[email protected]cfe484902012-02-15 14:52:32130 self.params = []
[email protected]15f08dd2012-01-27 07:29:48131 if len(params) == 0:
[email protected]cfe484902012-02-15 14:52:32132 return
[email protected]15f08dd2012-01-27 07:29:48133 elif len(params) == 1:
134 param = params[0]
[email protected]feba21e2012-03-02 15:05:27135 self.params.append(Property(self, param['name'], param,
[email protected]25cbf6012012-02-28 05:51:44136 from_client=True))
[email protected]15f08dd2012-01-27 07:29:48137 else:
[email protected]feba21e2012-03-02 15:05:27138 raise ParseException("Callbacks can have at most a single parameter")
[email protected]15f08dd2012-01-27 07:29:48139
140class Function(object):
141 """A Function defined in the API.
[email protected]cfe484902012-02-15 14:52:32142
143 Properties:
144 - |name| the function name
145 - |params| a list of parameters to the function (order matters). A separate
146 parameter is used for each choice of a 'choices' parameter.
147 - |description| a description of the function (if provided)
148 - |callback| the callback parameter to the function. There should be exactly
149 one
[email protected]15f08dd2012-01-27 07:29:48150 """
[email protected]feba21e2012-03-02 15:05:27151 def __init__(self, parent, json):
[email protected]15f08dd2012-01-27 07:29:48152 self.name = json['name']
153 self.params = []
[email protected]feba21e2012-03-02 15:05:27154 self.description = json.get('description')
[email protected]15f08dd2012-01-27 07:29:48155 self.callback = None
[email protected]feba21e2012-03-02 15:05:27156 self.parent = parent
[email protected]15f08dd2012-01-27 07:29:48157 for param in json['parameters']:
158 if param.get('type') == 'function':
[email protected]feba21e2012-03-02 15:05:27159 if self.callback:
160 raise ParseException(self.name + " has more than one callback")
161 self.callback = Callback(self, param)
[email protected]15f08dd2012-01-27 07:29:48162 else:
[email protected]feba21e2012-03-02 15:05:27163 self.params.append(Property(self, param['name'], param,
[email protected]25cbf6012012-02-28 05:51:44164 from_json=True))
[email protected]15f08dd2012-01-27 07:29:48165
[email protected]15f08dd2012-01-27 07:29:48166class Property(object):
167 """A property of a type OR a parameter to a function.
168
[email protected]cfe484902012-02-15 14:52:32169 Properties:
170 - |name| name of the property as in the json. This shouldn't change since
171 it is the key used to access DictionaryValues
172 - |unix_name| the unix_style_name of the property. Used as variable name
173 - |optional| a boolean representing whether the property is optional
174 - |description| a description of the property (if provided)
175 - |type_| the model.PropertyType of this property
176 - |ref_type| the type that the REF property is referencing. Can be used to
177 map to its model.Type
178 - |item_type| a model.Property representing the type of each element in an
179 ARRAY
180 - |properties| the properties of an OBJECT parameter
[email protected]15f08dd2012-01-27 07:29:48181 """
[email protected]feba21e2012-03-02 15:05:27182
183 def __init__(self, parent, name, json, is_additional_properties=False,
184 from_json=False, from_client=False):
[email protected]25cbf6012012-02-28 05:51:44185 """
186 Parameters:
187 - |from_json| indicates that instances of the Type can originate from the
188 JSON (as described by the schema), such as top-level types and function
189 parameters
190 - |from_client| indicates that instances of the Type can originate from the
191 users of generated code, such as top-level types and function results
192 """
[email protected]15f08dd2012-01-27 07:29:48193 self.name = name
[email protected]feba21e2012-03-02 15:05:27194 self._unix_name = UnixName(self.name)
[email protected]cfe484902012-02-15 14:52:32195 self._unix_name_used = False
[email protected]15f08dd2012-01-27 07:29:48196 self.optional = json.get('optional', False)
197 self.description = json.get('description')
[email protected]feba21e2012-03-02 15:05:27198 self.parent = parent
199 if is_additional_properties:
200 self.type_ = PropertyType.ADDITIONAL_PROPERTIES
201 elif '$ref' in json:
[email protected]15f08dd2012-01-27 07:29:48202 self.ref_type = json['$ref']
203 self.type_ = PropertyType.REF
[email protected]2111fff9b2012-02-22 12:06:51204 elif 'enum' in json:
205 self.enum_values = []
206 for value in json['enum']:
207 self.enum_values.append(value)
208 self.type_ = PropertyType.ENUM
[email protected]15f08dd2012-01-27 07:29:48209 elif 'type' in json:
210 json_type = json['type']
211 if json_type == 'string':
212 self.type_ = PropertyType.STRING
[email protected]cfe484902012-02-15 14:52:32213 elif json_type == 'any':
214 self.type_ = PropertyType.ANY
215 elif json_type == 'boolean':
[email protected]15f08dd2012-01-27 07:29:48216 self.type_ = PropertyType.BOOLEAN
217 elif json_type == 'integer':
218 self.type_ = PropertyType.INTEGER
[email protected]cfe484902012-02-15 14:52:32219 elif json_type == 'number':
[email protected]15f08dd2012-01-27 07:29:48220 self.type_ = PropertyType.DOUBLE
221 elif json_type == 'array':
[email protected]feba21e2012-03-02 15:05:27222 self.item_type = Property(self, name + "Element", json['items'],
223 from_json=from_json,
224 from_client=from_client)
[email protected]15f08dd2012-01-27 07:29:48225 self.type_ = PropertyType.ARRAY
226 elif json_type == 'object':
[email protected]15f08dd2012-01-27 07:29:48227 self.type_ = PropertyType.OBJECT
[email protected]25cbf6012012-02-28 05:51:44228 # These members are read when this OBJECT Property is used as a Type
229 self.properties = {}
230 self.from_json = from_json
231 self.from_client = from_client
[email protected]feba21e2012-03-02 15:05:27232 type_ = Type(self, self.name, json)
233 self.properties = type_.properties
234 self.functions = type_.functions
[email protected]15f08dd2012-01-27 07:29:48235 else:
[email protected]feba21e2012-03-02 15:05:27236 raise ParseException(self, 'type ' + json_type + ' not recognized')
[email protected]15f08dd2012-01-27 07:29:48237 elif 'choices' in json:
[email protected]feba21e2012-03-02 15:05:27238 if not json['choices']:
239 raise ParseException('Choices has no choices')
[email protected]15f08dd2012-01-27 07:29:48240 self.choices = {}
[email protected]cfe484902012-02-15 14:52:32241 self.type_ = PropertyType.CHOICES
242 for choice_json in json['choices']:
[email protected]feba21e2012-03-02 15:05:27243 choice = Property(self, self.name, choice_json,
244 from_json=from_json,
245 from_client=from_client)
[email protected]cfe484902012-02-15 14:52:32246 # A choice gets its unix_name set in
247 # cpp_type_generator.GetExpandedChoicesInParams
248 choice._unix_name = None
249 # The existence of any single choice is optional
250 choice.optional = True
251 self.choices[choice.type_] = choice
252 else:
[email protected]feba21e2012-03-02 15:05:27253 raise ParseException('Property has no type, $ref or choices')
[email protected]cfe484902012-02-15 14:52:32254
255 def GetUnixName(self):
256 """Gets the property's unix_name. Raises AttributeError if not set.
257 """
258 if self._unix_name is None:
259 raise AttributeError('No unix_name set on %s' % self.name)
260 self._unix_name_used = True
261 return self._unix_name
262
263 def SetUnixName(self, unix_name):
264 """Set the property's unix_name. Raises AttributeError if the unix_name has
265 already been used (GetUnixName has been called).
266 """
267 if unix_name == self._unix_name:
268 return
269 if self._unix_name_used:
270 raise AttributeError(
271 'Cannot set the unix_name on %s; '
272 'it is already used elsewhere as %s' %
273 (self.name, self._unix_name))
274 self._unix_name = unix_name
275
[email protected]712eca0f2012-02-21 01:13:07276 def Copy(self):
277 """Makes a copy of this model.Property object and allow the unix_name to be
278 set again.
279 """
280 property_copy = copy.copy(self)
281 property_copy._unix_name_used = False
282 return property_copy
283
[email protected]cfe484902012-02-15 14:52:32284 unix_name = property(GetUnixName, SetUnixName)
[email protected]15f08dd2012-01-27 07:29:48285
286class PropertyType(object):
287 """Enum of different types of properties/parameters.
288 """
289 class _Info(object):
[email protected]cfe484902012-02-15 14:52:32290 def __init__(self, is_fundamental, name):
[email protected]15f08dd2012-01-27 07:29:48291 self.is_fundamental = is_fundamental
[email protected]cfe484902012-02-15 14:52:32292 self.name = name
[email protected]15f08dd2012-01-27 07:29:48293
[email protected]cfe484902012-02-15 14:52:32294 def __repr__(self):
295 return self.name
296
297 INTEGER = _Info(True, "INTEGER")
298 DOUBLE = _Info(True, "DOUBLE")
299 BOOLEAN = _Info(True, "BOOLEAN")
300 STRING = _Info(True, "STRING")
[email protected]2111fff9b2012-02-22 12:06:51301 ENUM = _Info(False, "ENUM")
[email protected]cfe484902012-02-15 14:52:32302 ARRAY = _Info(False, "ARRAY")
303 REF = _Info(False, "REF")
304 CHOICES = _Info(False, "CHOICES")
305 OBJECT = _Info(False, "OBJECT")
306 ANY = _Info(False, "ANY")
[email protected]feba21e2012-03-02 15:05:27307 ADDITIONAL_PROPERTIES = _Info(False, "ADDITIONAL_PROPERTIES")
[email protected]cfe484902012-02-15 14:52:32308
[email protected]feba21e2012-03-02 15:05:27309def UnixName(name):
[email protected]712eca0f2012-02-21 01:13:07310 """Returns the unix_style name for a given lowerCamelCase string.
[email protected]cfe484902012-02-15 14:52:32311 """
[email protected]df74dc42012-04-03 07:08:04312 # First replace any lowerUpper patterns with lower_Upper.
313 s1 = re.sub('([a-z])([A-Z])', r'\1_\2', name)
314 # Now replace any ACMEWidgets patterns with ACME_Widgets
315 s2 = re.sub('([A-Z]+)([A-Z][a-z])', r'\1_\2', s1)
316 # Finally, replace any remaining periods, and make lowercase.
317 return s2.replace('.', '_').lower()
[email protected]cfe484902012-02-15 14:52:32318
[email protected]feba21e2012-03-02 15:05:27319class ParseException(Exception):
320 """Thrown when data in the model is invalid."""
321 def __init__(self, parent, message):
322 hierarchy = GetModelHierarchy(parent)
323 hierarchy.append(message)
324 Exception.__init__(
325 self, 'Model parse exception at:\n' + '\n'.join(hierarchy))
326
327def GetModelHierarchy(entity):
328 """Returns the hierarchy of the given model entity."""
329 hierarchy = []
330 while entity:
331 try:
332 hierarchy.append(entity.name)
333 except AttributeError:
334 hierarchy.append(repr(entity))
335 entity = entity.parent
336 hierarchy.reverse()
337 return hierarchy