OLD | NEW |
---|---|
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 import os.path | 5 import os.path |
6 import re | |
6 | 7 |
7 class Model(object): | 8 class Model(object): |
8 """Model of all namespaces that comprise an API. | 9 """Model of all namespaces that comprise an API. |
10 | |
11 Properties: | |
12 - |namespaces| a map of a namespace name to its model.Namespace | |
9 """ | 13 """ |
10 def __init__(self): | 14 def __init__(self): |
11 self.namespaces = {} | 15 self.namespaces = {} |
12 | 16 |
13 def AddNamespace(self, json, source_file): | 17 def AddNamespace(self, json, source_file): |
14 """Add a namespace's json to the model if it has a "compile" property set | 18 """Add a namespace's json to the model if it doesn't have "nocompile" |
15 to true. Returns the new namespace or None if a namespace wasn't added. | 19 property set to true. Returns the new namespace or None if a namespace |
20 wasn't added. | |
16 """ | 21 """ |
17 if not json.get('compile'): | 22 if json.get('nocompile', False): |
18 return None | 23 return None |
19 namespace = Namespace(json, source_file) | 24 namespace = Namespace(json, source_file) |
20 self.namespaces[namespace.name] = namespace | 25 self.namespaces[namespace.name] = namespace |
21 return namespace | 26 return namespace |
22 | 27 |
23 class Namespace(object): | 28 class Namespace(object): |
24 """An API namespace. | 29 """An API namespace. |
30 | |
31 Properties: | |
32 - |name| the name of the namespace | |
33 - |source_file| the file that contained the namespace definition | |
Yoyo Zhou
2012/02/08 19:04:12
This seems redundant given the 2 below it.
calamity
2012/02/09 00:56:52
It's really just for convenience, otherwise, I'd n
| |
34 - |source_file_dir| the directory component of |source_file| | |
35 - |source_file_filename| the filename component of |source_file| | |
36 - |types| a map of type names to their model.Type | |
37 - |functions| a map of function names to their model.Function | |
25 """ | 38 """ |
26 def __init__(self, json, source_file): | 39 def __init__(self, json, source_file): |
27 self.name = json['namespace'] | 40 self.name = json['namespace'] |
28 self.source_file = source_file | 41 self.source_file = source_file |
29 self.source_file_dir, self.source_file_filename = os.path.split(source_file) | 42 self.source_file_dir, self.source_file_filename = os.path.split(source_file) |
30 self.type_dependencies = {} | |
31 self.types = {} | 43 self.types = {} |
32 self.functions = {} | 44 self.functions = {} |
33 for type_json in json['types']: | 45 for type_json in json['types']: |
34 type_ = Type(type_json) | 46 type_ = Type(type_json) |
35 self.types[type_.name] = type_ | 47 self.types[type_.name] = type_ |
36 for function_json in json['functions']: | 48 for function_json in json['functions']: |
37 if not function_json.get('nocompile'): | 49 if not function_json.get('nocompile', False): |
38 function = Function(function_json) | 50 function = Function(function_json) |
39 self.functions[function.name] = function | 51 self.functions[function.name] = function |
40 | 52 |
41 class Type(object): | 53 class Type(object): |
42 """A Type defined in the json. | 54 """A Type defined in the json. |
55 | |
56 Properties: | |
57 - |name| the type name | |
58 - |description| the description of the type (if provided) | |
59 - |properties| a map of property names to their model.Property | |
43 """ | 60 """ |
44 def __init__(self, json): | 61 def __init__(self, json): |
45 self.name = json['id'] | 62 self.name = json['id'] |
46 self.description = json.get('description') | 63 self.description = json.get('description') |
47 self.properties = {} | 64 self.properties = {} |
48 for prop_name, prop_json in json['properties'].items(): | 65 for prop_name, prop_json in json['properties'].items(): |
49 self.properties[prop_name] = Property(prop_name, prop_json) | 66 self.properties[prop_name] = Property(prop_name, prop_json) |
50 | 67 |
51 class Callback(object): | 68 class Callback(object): |
52 """A callback parameter to a Function. | 69 """A callback parameter to a Function. |
70 | |
71 Properties: | |
72 - |params| the parameters to this callback. | |
53 """ | 73 """ |
54 def __init__(self, json): | 74 def __init__(self, json): |
55 params = json['parameters'] | 75 params = json['parameters'] |
76 self.params = [] | |
56 if len(params) == 0: | 77 if len(params) == 0: |
57 self.param = None | 78 return |
58 elif len(params) == 1: | 79 elif len(params) == 1: |
59 param = params[0] | 80 param = params[0] |
60 self.param = Property(param['name'], param) | 81 self.params.append(Property(param['name'], param)) |
61 else: | 82 else: |
62 raise AssertionError("Callbacks can have at most a single parameter") | 83 raise AssertionError("Callbacks can have at most a single parameter") |
63 | 84 |
64 class Function(object): | 85 class Function(object): |
65 """A Function defined in the API. | 86 """A Function defined in the API. |
87 | |
88 Properties: | |
89 - |name| the function name | |
90 - |params| a list of parameters to the function (order matters). A separate | |
91 parameter is used for each choice of a 'choices' parameter. | |
92 - |description| a description of the function (if provided) | |
93 - |callback| the callback parameter to the function. There should be exactly | |
94 one | |
66 """ | 95 """ |
67 def __init__(self, json): | 96 def __init__(self, json): |
68 self.name = json['name'] | 97 self.name = json['name'] |
69 self.params = [] | 98 self.params = [] |
70 self.description = json['description'] | 99 self.description = json['description'] |
71 self.callback = None | 100 self.callback = None |
72 self.type_dependencies = {} | |
73 for param in json['parameters']: | 101 for param in json['parameters']: |
74 if param.get('type') == 'function': | 102 if param.get('type') == 'function': |
75 assert (not self.callback), "Function has more than one callback" | 103 assert (not self.callback), self.name + " has more than one callback" |
76 self.callback = Callback(param) | 104 self.callback = Callback(param) |
77 else: | 105 else: |
78 self.params.append(Property(param['name'], param)) | 106 self.params.append(Property(param['name'], param)) |
79 assert (self.callback), "Function does not support callback" | 107 assert (self.callback), self.name + " does not support callback" |
80 | 108 |
81 # TODO(calamity): handle Enum/choices | 109 # TODO(calamity): handle Enum |
82 class Property(object): | 110 class Property(object): |
83 """A property of a type OR a parameter to a function. | 111 """A property of a type OR a parameter to a function. |
84 | 112 |
85 Members will change based on PropertyType. Check self.type_ to determine which | 113 Properties: |
86 members actually exist. | 114 - |name| name of the property as in the json. This shouldn't change since |
115 it is the key used to access DictionaryValues | |
116 - |unix_name| the unix_style_name of the property. Used as variable name | |
117 - |optional| a boolean representing whether the property is optional | |
118 - |description| a description of the property (if provided) | |
119 - |type_| the model.PropertyType of this property | |
120 - |ref_type| the type that the REF property is referencing. Can be used to | |
121 map to its model.Type | |
122 - |item_type| a model.Property representing the type of each element in an | |
123 ARRAY | |
124 - |properties| the properties of an OBJECT parameter | |
87 """ | 125 """ |
88 def __init__(self, name, json): | 126 def __init__(self, name, json): |
127 if not re.match('^[a-z][a-zA-Z0-9]*$', name): | |
128 raise AssertionError('Name %s must be lowerCamelCase' % name) | |
89 self.name = name | 129 self.name = name |
130 self._unix_name = _UnixName(self.name) | |
131 self._unix_name_used = False | |
90 self.optional = json.get('optional', False) | 132 self.optional = json.get('optional', False) |
91 self.description = json.get('description') | 133 self.description = json.get('description') |
92 # TODO(calamity) maybe check for circular refs? could that be a problem? | |
93 if '$ref' in json: | 134 if '$ref' in json: |
94 self.ref_type = json['$ref'] | 135 self.ref_type = json['$ref'] |
95 self.type_ = PropertyType.REF | 136 self.type_ = PropertyType.REF |
96 elif 'type' in json: | 137 elif 'type' in json: |
97 json_type = json['type'] | 138 json_type = json['type'] |
98 if json_type == 'string': | 139 if json_type == 'string': |
99 self.type_ = PropertyType.STRING | 140 self.type_ = PropertyType.STRING |
100 elif json_type == 'boolean': | 141 elif json_type == 'any': |
142 self.type_ = PropertyType.ANY | |
143 elif json_type == 'boolean': | |
101 self.type_ = PropertyType.BOOLEAN | 144 self.type_ = PropertyType.BOOLEAN |
102 elif json_type == 'integer': | 145 elif json_type == 'integer': |
103 self.type_ = PropertyType.INTEGER | 146 self.type_ = PropertyType.INTEGER |
104 elif json_type == 'double': | 147 elif json_type == 'number': |
105 self.type_ = PropertyType.DOUBLE | 148 self.type_ = PropertyType.DOUBLE |
106 elif json_type == 'array': | 149 elif json_type == 'array': |
107 self.item_type = Property(name + "_inner", json['items']) | 150 self.item_type = Property(name + "Element", json['items']) |
108 self.type_ = PropertyType.ARRAY | 151 self.type_ = PropertyType.ARRAY |
109 elif json_type == 'object': | 152 elif json_type == 'object': |
110 self.properties = {} | 153 self.properties = {} |
111 self.type_ = PropertyType.OBJECT | 154 self.type_ = PropertyType.OBJECT |
112 for key, val in json['properties'].items(): | 155 for key, val in json['properties'].items(): |
113 self.properties[key] = Property(key, val) | 156 self.properties[key] = Property(key, val) |
114 else: | 157 else: |
115 raise NotImplementedError(json_type) | 158 raise NotImplementedError(json_type) |
116 elif 'choices' in json: | 159 elif 'choices' in json: |
160 assert len(json['choices']), 'Choices has no choices\n%s' % json | |
161 self.choices = {} | |
117 self.type_ = PropertyType.CHOICES | 162 self.type_ = PropertyType.CHOICES |
118 self.choices = {} | 163 for choice_json in json['choices']: |
164 choice = Property(self.name, choice_json) | |
165 # A choice needs to have its unix_name set elsewhere | |
Yoyo Zhou
2012/02/08 19:04:12
Can you discuss where SetUnixName might be called
calamity
2012/02/09 00:56:52
unix_name is set as a property of this class (line
| |
166 choice._unix_name = None | |
167 # The existence of any single choice is optional | |
168 choice.optional = True | |
169 self.choices[choice.type_] = choice | |
170 else: | |
171 raise NotImplementedError(json) | |
172 | |
173 def GetUnixName(self): | |
174 """Gets the property's unix_name. Raises AttributeError if not set. | |
175 """ | |
176 if self._unix_name is None: | |
177 raise AttributeError('No unix_name set on %s' % self.name) | |
178 self._unix_name_used = True | |
179 return self._unix_name | |
180 | |
181 def SetUnixName(self, unix_name): | |
182 """Set the property's unix_name. Raises AttributeError if the unix_name has | |
183 already been used (GetUnixName has been called). | |
184 """ | |
185 if self._unix_name_used: | |
186 raise AttributeError( | |
187 'Cannot set the unix_name on %s; it is already used elsewhere as %s') | |
Yoyo Zhou
2012/02/08 19:04:12
missing substitutions here?
calamity
2012/02/09 00:56:52
Done.
| |
188 self._unix_name = unix_name | |
189 unix_name = property(GetUnixName, SetUnixName) | |
not at google - send to devlin
2012/02/08 05:02:08
Getter/Setter can be a bit simpler to take advanta
calamity
2012/02/08 07:01:18
If the property is a choice, we unset it. GetUnixN
| |
119 | 190 |
120 class PropertyType(object): | 191 class PropertyType(object): |
121 """Enum of different types of properties/parameters. | 192 """Enum of different types of properties/parameters. |
122 """ | 193 """ |
123 class _Info(object): | 194 class _Info(object): |
124 def __init__(self, is_fundamental): | 195 def __init__(self, is_fundamental, name): |
125 self.is_fundamental = is_fundamental | 196 self.is_fundamental = is_fundamental |
197 self.name = name | |
126 | 198 |
127 INTEGER = _Info(True) | 199 def __repr__(self): |
128 DOUBLE = _Info(True) | 200 return self.name |
129 BOOLEAN = _Info(True) | 201 |
130 STRING = _Info(True) | 202 INTEGER = _Info(True, "INTEGER") |
131 ARRAY = _Info(False) | 203 DOUBLE = _Info(True, "DOUBLE") |
132 REF = _Info(False) | 204 BOOLEAN = _Info(True, "BOOLEAN") |
133 CHOICES = _Info(False) | 205 STRING = _Info(True, "STRING") |
134 OBJECT = _Info(False) | 206 ARRAY = _Info(False, "ARRAY") |
207 REF = _Info(False, "REF") | |
208 CHOICES = _Info(False, "CHOICES") | |
209 OBJECT = _Info(False, "OBJECT") | |
210 ANY = _Info(False, "ANY") | |
211 | |
212 def _UnixName(name): | |
213 """Returns the unix_style name for a given string. | |
Yoyo Zhou
2012/02/08 19:04:12
Comment here that the name is expected to be lower
calamity
2012/02/09 00:56:52
Done.
| |
214 """ | |
215 return '_'.join([x.lower() | |
216 for x in re.findall('[A-Z][a-z_]*', name[0].upper() + name[1:])]) | |
217 | |
OLD | NEW |