blob: de392d21a7776f764029a60e13eadbb645570127 [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
5import os.path
[email protected]cfe484902012-02-15 14:52:326import re
[email protected]712eca0f2012-02-21 01:13:077import copy
[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]cfe484902012-02-15 14:52:3219 """Add a namespace's json to the model if it doesn't have "nocompile"
20 property set to true. Returns the new namespace or None if a namespace
21 wasn't added.
[email protected]15f08dd2012-01-27 07:29:4822 """
[email protected]cfe484902012-02-15 14:52:3223 if json.get('nocompile', False):
[email protected]15f08dd2012-01-27 07:29:4824 return None
25 namespace = Namespace(json, source_file)
26 self.namespaces[namespace.name] = namespace
27 return namespace
28
29class Namespace(object):
30 """An API namespace.
[email protected]cfe484902012-02-15 14:52:3231
32 Properties:
33 - |name| the name of the namespace
[email protected]712eca0f2012-02-21 01:13:0734 - |unix_name| the unix_name of the namespace
35 - |source_file| the file that contained the namespace definition
36 - |source_file_dir| the directory component of |source_file|
37 - |source_file_filename| the filename component of |source_file|
[email protected]cfe484902012-02-15 14:52:3238 - |types| a map of type names to their model.Type
39 - |functions| a map of function names to their model.Function
[email protected]15f08dd2012-01-27 07:29:4840 """
41 def __init__(self, json, source_file):
42 self.name = json['namespace']
[email protected]feba21e2012-03-02 15:05:2743 self.unix_name = UnixName(self.name)
[email protected]15f08dd2012-01-27 07:29:4844 self.source_file = source_file
45 self.source_file_dir, self.source_file_filename = os.path.split(source_file)
[email protected]15f08dd2012-01-27 07:29:4846 self.types = {}
47 self.functions = {}
[email protected]feba21e2012-03-02 15:05:2748 self.parent = None
49 # TODO(calamity): Implement properties on namespaces for shared structures
50 # or constants across a namespace (e.g Windows::WINDOW_ID_NONE).
51 for property_json in json.get('properties', []):
52 pass
[email protected]712eca0f2012-02-21 01:13:0753 for type_json in json.get('types', []):
[email protected]feba21e2012-03-02 15:05:2754 type_ = Type(self, type_json['id'], type_json)
[email protected]15f08dd2012-01-27 07:29:4855 self.types[type_.name] = type_
[email protected]712eca0f2012-02-21 01:13:0756 for function_json in json.get('functions', []):
[email protected]cfe484902012-02-15 14:52:3257 if not function_json.get('nocompile', False):
[email protected]feba21e2012-03-02 15:05:2758 self.functions[function_json['name']] = Function(self, function_json)
[email protected]15f08dd2012-01-27 07:29:4859
60class Type(object):
61 """A Type defined in the json.
[email protected]cfe484902012-02-15 14:52:3262
63 Properties:
64 - |name| the type name
65 - |description| the description of the type (if provided)
[email protected]feba21e2012-03-02 15:05:2766 - |properties| a map of property unix_names to their model.Property
67 - |functions| a map of function names to their model.Function
[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]15f08dd2012-01-27 07:29:4873 """
[email protected]feba21e2012-03-02 15:05:2774 def __init__(self, parent, name, json):
75 if not (
76 'properties' in json or
77 'additionalProperties' in json or
78 'functions' in json):
79 raise ParseException(name + " has no properties or functions")
80 self.name = name
[email protected]15f08dd2012-01-27 07:29:4881 self.description = json.get('description')
[email protected]25cbf6012012-02-28 05:51:4482 self.from_json = True
83 self.from_client = True
[email protected]15f08dd2012-01-27 07:29:4884 self.properties = {}
[email protected]feba21e2012-03-02 15:05:2785 self.functions = {}
86 self.parent = parent
87 for function_json in json.get('functions', []):
88 if not function_json.get('nocompile', False):
89 self.functions[function_json['name']] = Function(self, function_json)
90 props = []
91 for prop_name, prop_json in json.get('properties', {}).items():
92 # TODO(calamity): support functions (callbacks) as properties. The model
93 # doesn't support it yet because to h/cc generators don't -- this is
94 # because we'd need to hook it into a base::Callback or something.
95 #
96 # However, pragmatically it's not necessary to support them anyway, since
97 # the instances of functions-on-properties in the extension APIs are all
98 # handled in pure Javascript on the render process (and .: never reach
99 # C++ let alone the browser).
100 if prop_json.get('type') == 'function':
101 continue
102 props.append(Property(self, prop_name, prop_json,
[email protected]25cbf6012012-02-28 05:51:44103 from_json=True,
[email protected]feba21e2012-03-02 15:05:27104 from_client=True))
105
106 additional_properties = json.get('additionalProperties')
107 if additional_properties:
108 props.append(Property(self, 'additionalProperties', additional_properties,
109 is_additional_properties=True))
110
111 for prop in props:
112 if prop.unix_name in self.properties:
113 raise ParseException(
114 self.properties[prop.unix_name].name + ' and ' + prop.name +
115 ' are both named ' + prop.unix_name)
116 self.properties[prop.unix_name] = prop
[email protected]15f08dd2012-01-27 07:29:48117
118class Callback(object):
119 """A callback parameter to a Function.
[email protected]cfe484902012-02-15 14:52:32120
121 Properties:
122 - |params| the parameters to this callback.
[email protected]15f08dd2012-01-27 07:29:48123 """
[email protected]feba21e2012-03-02 15:05:27124 def __init__(self, parent, json):
[email protected]15f08dd2012-01-27 07:29:48125 params = json['parameters']
[email protected]feba21e2012-03-02 15:05:27126 self.parent = parent
[email protected]cfe484902012-02-15 14:52:32127 self.params = []
[email protected]15f08dd2012-01-27 07:29:48128 if len(params) == 0:
[email protected]cfe484902012-02-15 14:52:32129 return
[email protected]15f08dd2012-01-27 07:29:48130 elif len(params) == 1:
131 param = params[0]
[email protected]feba21e2012-03-02 15:05:27132 self.params.append(Property(self, param['name'], param,
[email protected]25cbf6012012-02-28 05:51:44133 from_client=True))
[email protected]15f08dd2012-01-27 07:29:48134 else:
[email protected]feba21e2012-03-02 15:05:27135 raise ParseException("Callbacks can have at most a single parameter")
[email protected]15f08dd2012-01-27 07:29:48136
137class Function(object):
138 """A Function defined in the API.
[email protected]cfe484902012-02-15 14:52:32139
140 Properties:
141 - |name| the function name
142 - |params| a list of parameters to the function (order matters). A separate
143 parameter is used for each choice of a 'choices' parameter.
144 - |description| a description of the function (if provided)
145 - |callback| the callback parameter to the function. There should be exactly
146 one
[email protected]15f08dd2012-01-27 07:29:48147 """
[email protected]feba21e2012-03-02 15:05:27148 def __init__(self, parent, json):
[email protected]15f08dd2012-01-27 07:29:48149 self.name = json['name']
150 self.params = []
[email protected]feba21e2012-03-02 15:05:27151 self.description = json.get('description')
[email protected]15f08dd2012-01-27 07:29:48152 self.callback = None
[email protected]feba21e2012-03-02 15:05:27153 self.parent = parent
[email protected]15f08dd2012-01-27 07:29:48154 for param in json['parameters']:
155 if param.get('type') == 'function':
[email protected]feba21e2012-03-02 15:05:27156 if self.callback:
157 raise ParseException(self.name + " has more than one callback")
158 self.callback = Callback(self, param)
[email protected]15f08dd2012-01-27 07:29:48159 else:
[email protected]feba21e2012-03-02 15:05:27160 self.params.append(Property(self, param['name'], param,
[email protected]25cbf6012012-02-28 05:51:44161 from_json=True))
[email protected]15f08dd2012-01-27 07:29:48162
[email protected]15f08dd2012-01-27 07:29:48163class Property(object):
164 """A property of a type OR a parameter to a function.
165
[email protected]cfe484902012-02-15 14:52:32166 Properties:
167 - |name| name of the property as in the json. This shouldn't change since
168 it is the key used to access DictionaryValues
169 - |unix_name| the unix_style_name of the property. Used as variable name
170 - |optional| a boolean representing whether the property is optional
171 - |description| a description of the property (if provided)
172 - |type_| the model.PropertyType of this property
173 - |ref_type| the type that the REF property is referencing. Can be used to
174 map to its model.Type
175 - |item_type| a model.Property representing the type of each element in an
176 ARRAY
177 - |properties| the properties of an OBJECT parameter
[email protected]15f08dd2012-01-27 07:29:48178 """
[email protected]feba21e2012-03-02 15:05:27179
180 def __init__(self, parent, name, json, is_additional_properties=False,
181 from_json=False, from_client=False):
[email protected]25cbf6012012-02-28 05:51:44182 """
183 Parameters:
184 - |from_json| indicates that instances of the Type can originate from the
185 JSON (as described by the schema), such as top-level types and function
186 parameters
187 - |from_client| indicates that instances of the Type can originate from the
188 users of generated code, such as top-level types and function results
189 """
[email protected]15f08dd2012-01-27 07:29:48190 self.name = name
[email protected]feba21e2012-03-02 15:05:27191 self._unix_name = UnixName(self.name)
[email protected]cfe484902012-02-15 14:52:32192 self._unix_name_used = False
[email protected]15f08dd2012-01-27 07:29:48193 self.optional = json.get('optional', False)
194 self.description = json.get('description')
[email protected]feba21e2012-03-02 15:05:27195 self.parent = parent
196 if is_additional_properties:
197 self.type_ = PropertyType.ADDITIONAL_PROPERTIES
198 elif '$ref' in json:
[email protected]15f08dd2012-01-27 07:29:48199 self.ref_type = json['$ref']
200 self.type_ = PropertyType.REF
[email protected]2111fff9b2012-02-22 12:06:51201 elif 'enum' in json:
202 self.enum_values = []
203 for value in json['enum']:
204 self.enum_values.append(value)
205 self.type_ = PropertyType.ENUM
[email protected]15f08dd2012-01-27 07:29:48206 elif 'type' in json:
207 json_type = json['type']
208 if json_type == 'string':
209 self.type_ = PropertyType.STRING
[email protected]cfe484902012-02-15 14:52:32210 elif json_type == 'any':
211 self.type_ = PropertyType.ANY
212 elif json_type == 'boolean':
[email protected]15f08dd2012-01-27 07:29:48213 self.type_ = PropertyType.BOOLEAN
214 elif json_type == 'integer':
215 self.type_ = PropertyType.INTEGER
[email protected]cfe484902012-02-15 14:52:32216 elif json_type == 'number':
[email protected]15f08dd2012-01-27 07:29:48217 self.type_ = PropertyType.DOUBLE
218 elif json_type == 'array':
[email protected]feba21e2012-03-02 15:05:27219 self.item_type = Property(self, name + "Element", json['items'],
220 from_json=from_json,
221 from_client=from_client)
[email protected]15f08dd2012-01-27 07:29:48222 self.type_ = PropertyType.ARRAY
223 elif json_type == 'object':
[email protected]15f08dd2012-01-27 07:29:48224 self.type_ = PropertyType.OBJECT
[email protected]25cbf6012012-02-28 05:51:44225 # These members are read when this OBJECT Property is used as a Type
226 self.properties = {}
227 self.from_json = from_json
228 self.from_client = from_client
[email protected]feba21e2012-03-02 15:05:27229 type_ = Type(self, self.name, json)
230 self.properties = type_.properties
231 self.functions = type_.functions
[email protected]15f08dd2012-01-27 07:29:48232 else:
[email protected]feba21e2012-03-02 15:05:27233 raise ParseException(self, 'type ' + json_type + ' not recognized')
[email protected]15f08dd2012-01-27 07:29:48234 elif 'choices' in json:
[email protected]feba21e2012-03-02 15:05:27235 if not json['choices']:
236 raise ParseException('Choices has no choices')
[email protected]15f08dd2012-01-27 07:29:48237 self.choices = {}
[email protected]cfe484902012-02-15 14:52:32238 self.type_ = PropertyType.CHOICES
239 for choice_json in json['choices']:
[email protected]feba21e2012-03-02 15:05:27240 choice = Property(self, self.name, choice_json,
241 from_json=from_json,
242 from_client=from_client)
[email protected]cfe484902012-02-15 14:52:32243 # A choice gets its unix_name set in
244 # cpp_type_generator.GetExpandedChoicesInParams
245 choice._unix_name = None
246 # The existence of any single choice is optional
247 choice.optional = True
248 self.choices[choice.type_] = choice
249 else:
[email protected]feba21e2012-03-02 15:05:27250 raise ParseException('Property has no type, $ref or choices')
[email protected]cfe484902012-02-15 14:52:32251
252 def GetUnixName(self):
253 """Gets the property's unix_name. Raises AttributeError if not set.
254 """
255 if self._unix_name is None:
256 raise AttributeError('No unix_name set on %s' % self.name)
257 self._unix_name_used = True
258 return self._unix_name
259
260 def SetUnixName(self, unix_name):
261 """Set the property's unix_name. Raises AttributeError if the unix_name has
262 already been used (GetUnixName has been called).
263 """
264 if unix_name == self._unix_name:
265 return
266 if self._unix_name_used:
267 raise AttributeError(
268 'Cannot set the unix_name on %s; '
269 'it is already used elsewhere as %s' %
270 (self.name, self._unix_name))
271 self._unix_name = unix_name
272
[email protected]712eca0f2012-02-21 01:13:07273 def Copy(self):
274 """Makes a copy of this model.Property object and allow the unix_name to be
275 set again.
276 """
277 property_copy = copy.copy(self)
278 property_copy._unix_name_used = False
279 return property_copy
280
[email protected]cfe484902012-02-15 14:52:32281 unix_name = property(GetUnixName, SetUnixName)
[email protected]15f08dd2012-01-27 07:29:48282
283class PropertyType(object):
284 """Enum of different types of properties/parameters.
285 """
286 class _Info(object):
[email protected]cfe484902012-02-15 14:52:32287 def __init__(self, is_fundamental, name):
[email protected]15f08dd2012-01-27 07:29:48288 self.is_fundamental = is_fundamental
[email protected]cfe484902012-02-15 14:52:32289 self.name = name
[email protected]15f08dd2012-01-27 07:29:48290
[email protected]cfe484902012-02-15 14:52:32291 def __repr__(self):
292 return self.name
293
294 INTEGER = _Info(True, "INTEGER")
295 DOUBLE = _Info(True, "DOUBLE")
296 BOOLEAN = _Info(True, "BOOLEAN")
297 STRING = _Info(True, "STRING")
[email protected]2111fff9b2012-02-22 12:06:51298 ENUM = _Info(False, "ENUM")
[email protected]cfe484902012-02-15 14:52:32299 ARRAY = _Info(False, "ARRAY")
300 REF = _Info(False, "REF")
301 CHOICES = _Info(False, "CHOICES")
302 OBJECT = _Info(False, "OBJECT")
303 ANY = _Info(False, "ANY")
[email protected]feba21e2012-03-02 15:05:27304 ADDITIONAL_PROPERTIES = _Info(False, "ADDITIONAL_PROPERTIES")
[email protected]cfe484902012-02-15 14:52:32305
[email protected]feba21e2012-03-02 15:05:27306def UnixName(name):
[email protected]712eca0f2012-02-21 01:13:07307 """Returns the unix_style name for a given lowerCamelCase string.
[email protected]cfe484902012-02-15 14:52:32308 """
309 return '_'.join([x.lower()
310 for x in re.findall('[A-Z][a-z_]*', name[0].upper() + name[1:])])
311
[email protected]feba21e2012-03-02 15:05:27312class ParseException(Exception):
313 """Thrown when data in the model is invalid."""
314 def __init__(self, parent, message):
315 hierarchy = GetModelHierarchy(parent)
316 hierarchy.append(message)
317 Exception.__init__(
318 self, 'Model parse exception at:\n' + '\n'.join(hierarchy))
319
320def GetModelHierarchy(entity):
321 """Returns the hierarchy of the given model entity."""
322 hierarchy = []
323 while entity:
324 try:
325 hierarchy.append(entity.name)
326 except AttributeError:
327 hierarchy.append(repr(entity))
328 entity = entity.parent
329 hierarchy.reverse()
330 return hierarchy
331