OLD | NEW |
(Empty) | |
| 1 # Copyright 2015 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 |
| 5 import json |
| 6 import os.path |
| 7 import re |
| 8 import util |
| 9 |
| 10 # Schema is described as follows: |
| 11 # * for simple type: |
| 12 # %simple_type% |
| 13 # * for list: |
| 14 # (list, %items_schema%) |
| 15 # * for dict: |
| 16 # (dict, { '%key_name%': (%is_key_required%, %value_schema%), |
| 17 # ... |
| 18 # }) |
| 19 DECLARATION_SCHEMA = (dict, { |
| 20 'imports': (False, (list, unicode)), |
| 21 'type': (True, unicode), |
| 22 'children': (False, (list, (dict, { |
| 23 'id': (True, unicode), |
| 24 'type': (True, unicode), |
| 25 'comment': (False, unicode) |
| 26 }))), |
| 27 'context': (False, (list, (dict, { |
| 28 'name': (True, unicode), |
| 29 'type': (False, unicode), |
| 30 'default': (False, object), |
| 31 'comment': (False, unicode) |
| 32 }))), |
| 33 'events': (False, (list, (dict, { |
| 34 'name': (True, unicode), |
| 35 'comment': (False, unicode) |
| 36 }))), |
| 37 'strings': (False, (list, (dict, { |
| 38 'name': (True, unicode), |
| 39 'comment': (False, unicode) |
| 40 }))), |
| 41 'comment': (False, unicode) |
| 42 }) |
| 43 |
| 44 # Returns (True,) if |obj| matches |schema|. |
| 45 # Otherwise returns (False, msg), where |msg| is a diagnostic message. |
| 46 def MatchSchema(obj, schema): |
| 47 expected_type = schema[0] if isinstance(schema, tuple) else schema |
| 48 if not isinstance(obj, expected_type): |
| 49 return (False, 'Wrong type, expected %s, got %s.' % |
| 50 (expected_type, type(obj))) |
| 51 if expected_type == dict: |
| 52 obj_keys = set(obj.iterkeys()) |
| 53 allowed_keys = set(schema[1].iterkeys()) |
| 54 required_keys = set(kv[0] for kv in schema[1].iteritems() if kv[1][0]) |
| 55 if not obj_keys.issuperset(required_keys): |
| 56 missing_keys = required_keys.difference(obj_keys) |
| 57 return (False, 'Missing required key%s %s.' % |
| 58 ('s' if len(missing_keys) > 1 else '', |
| 59 ', '.join('\'%s\'' % k for k in missing_keys))) |
| 60 if not obj_keys.issubset(allowed_keys): |
| 61 unknown_keys = obj_keys.difference(allowed_keys) |
| 62 return (False, 'Unknown key%s %s.' % |
| 63 ('s' if len(unknown_keys) > 1 else '', |
| 64 ', '.join('\'%s\'' % k for k in unknown_keys))) |
| 65 for key in obj: |
| 66 match = MatchSchema(obj[key], schema[1][key][1]) |
| 67 if not match[0]: |
| 68 return (False, ('[\'%s\'] ' % key) + match[1]) |
| 69 elif expected_type == list: |
| 70 for i, item in enumerate(obj): |
| 71 match = MatchSchema(item, schema[1]) |
| 72 if not match[0]: |
| 73 return (False, ('[%d] ' % i) + match[1]) |
| 74 return (True,) |
| 75 |
| 76 def CheckDeclarationIsValid(declaration): |
| 77 match = MatchSchema(declaration, DECLARATION_SCHEMA) |
| 78 if not match[0]: |
| 79 raise Exception('Declaration is not valid: ' + match[1]) |
| 80 |
| 81 |
| 82 class FieldDeclaration(object): |
| 83 ALLOWED_TYPES = [ |
| 84 'boolean', |
| 85 'integer', |
| 86 'double', |
| 87 'string', |
| 88 'string_list' |
| 89 ] |
| 90 |
| 91 DEFAULTS = { |
| 92 'boolean': False, |
| 93 'integer': 0, |
| 94 'double': 0.0, |
| 95 'string': u'', |
| 96 'string_list': [] |
| 97 } |
| 98 |
| 99 def __init__(self, declaration): |
| 100 self.name = declaration['name'] |
| 101 self.id = util.ToLowerCamelCase(self.name) |
| 102 self.type = declaration['type'] if 'type' in declaration else 'string' |
| 103 if self.type not in self.ALLOWED_TYPES: |
| 104 raise Exception('Unknown type of context field "%s": "%s"' % |
| 105 (self.name, self.type)) |
| 106 self.default_value = declaration['default'] if 'default' in declaration \ |
| 107 else self.DEFAULTS[self.type] |
| 108 if type(self.default_value) != type(self.DEFAULTS[self.type]): |
| 109 raise Exception('Wrong type of default for field "%s": ' |
| 110 'expected "%s", got "%s"' % |
| 111 (self.name, |
| 112 type(self.DEFAULTS[self.type]), |
| 113 type(self.default_value))) |
| 114 |
| 115 class Declaration(object): |
| 116 class InvalidDeclaration(Exception): |
| 117 def __init__(self, path, message): |
| 118 super(Exception, self).__init__( |
| 119 'Invalid declaration file "%s": %s' % (path, message)) |
| 120 |
| 121 class DeclarationsStorage(object): |
| 122 def __init__(self): |
| 123 self.by_path = {} |
| 124 self.by_type = {} |
| 125 |
| 126 def Add(self, declaration): |
| 127 assert declaration.path not in self.by_path |
| 128 self.by_path[declaration.path] = declaration |
| 129 if declaration.type in self.by_type: |
| 130 raise Exception( |
| 131 'Redefinition of type "%s". ' \ |
| 132 'Previous definition was in "%s".' % \ |
| 133 (declaration.type, self.by_type[declaration.type].path)) |
| 134 self.by_type[declaration.type] = declaration |
| 135 |
| 136 def HasPath(self, path): |
| 137 return path in self.by_path |
| 138 |
| 139 def HasType(self, type): |
| 140 return type in self.by_type |
| 141 |
| 142 def GetByPath(self, path): |
| 143 return self.by_path[path] |
| 144 |
| 145 def GetByType(self, type): |
| 146 return self.by_type[type] |
| 147 |
| 148 def GetKnownPathes(self): |
| 149 return self.by_path.keys() |
| 150 |
| 151 |
| 152 def __init__(self, path, known_declarations=None): |
| 153 if known_declarations is None: |
| 154 known_declarations = Declaration.DeclarationsStorage() |
| 155 self.path = path |
| 156 try: |
| 157 self.data = json.load(open(path, 'r')) |
| 158 CheckDeclarationIsValid(self.data) |
| 159 filename_wo_ext = os.path.splitext(os.path.basename(self.path))[0] |
| 160 if self.data['type'] != filename_wo_ext: |
| 161 raise Exception( |
| 162 'File with declaration of type "%s" should be named "%s.json"' % |
| 163 (self.data['type'], filename_wo_ext)) |
| 164 |
| 165 known_declarations.Add(self) |
| 166 if 'imports' in self.data: |
| 167 for import_path in self.data['imports']: |
| 168 #TODO(dzhioev): detect circular dependencies. |
| 169 if not known_declarations.HasPath(import_path): |
| 170 Declaration(import_path, known_declarations) |
| 171 self.children = {} |
| 172 if 'children' in self.data: |
| 173 for child_data in self.data['children']: |
| 174 child_type = child_data['type'] |
| 175 child_id = child_data['id'] |
| 176 if not known_declarations.HasType(child_type): |
| 177 raise Exception('Unknown type "%s" for child "%s"' % |
| 178 (child_type, child_id)) |
| 179 self.children[child_id] = known_declarations.GetByType(child_type) |
| 180 if 'strings' not in self.data: |
| 181 self.data['strings'] = {} |
| 182 if 'context' not in self.data: |
| 183 self.data['context'] = [] |
| 184 if 'events' not in self.data: |
| 185 self.data['events'] = [] |
| 186 self.fields = [FieldDeclaration(d) for d in self.data['context']] |
| 187 fields_names = [field.name for field in self.fields] |
| 188 if len(fields_names) > len(set(fields_names)): |
| 189 raise Exception('Duplicate fields in declaration.') |
| 190 self.known_declarations = known_declarations |
| 191 except Declaration.InvalidDeclaration: |
| 192 raise |
| 193 except Exception as e: |
| 194 raise Declaration.InvalidDeclaration(self.path, e.message) |
| 195 |
| 196 @property |
| 197 def type(self): |
| 198 return self.data['type'] |
| 199 |
| 200 @property |
| 201 def strings(self): |
| 202 return (string['name'] for string in self.data['strings']) |
| 203 |
| 204 @property |
| 205 def events(self): |
| 206 return (event['name'] for event in self.data['events']) |
| 207 |
| 208 @property |
| 209 def webui_view_basename(self): |
| 210 return self.type + '_web_ui_view' |
| 211 |
| 212 @property |
| 213 def webui_view_h_name(self): |
| 214 return self.webui_view_basename + '.h' |
| 215 |
| 216 @property |
| 217 def webui_view_cc_name(self): |
| 218 return self.webui_view_basename + '.cc' |
| 219 |
| 220 @property |
| 221 def webui_view_include_path(self): |
| 222 return os.path.join(os.path.dirname(self.path), self.webui_view_h_name) |
| 223 |
| 224 @property |
| 225 def export_h_name(self): |
| 226 return self.type + '_export.h' |
| 227 |
| 228 @property |
| 229 def component_export_macro(self): |
| 230 return "WUG_" + self.type.upper() + "_EXPORT" |
| 231 |
| 232 @property |
| 233 def component_impl_macro(self): |
| 234 return "WUG_" + self.type.upper() + "_IMPLEMENTATION" |
| 235 |
| 236 @property |
| 237 def export_h_include_path(self): |
| 238 return os.path.join(os.path.dirname(self.path), self.export_h_name) |
| 239 |
| 240 @property |
| 241 def view_model_basename(self): |
| 242 return self.type + '_view_model' |
| 243 |
| 244 @property |
| 245 def view_model_h_name(self): |
| 246 return self.view_model_basename + '.h' |
| 247 |
| 248 @property |
| 249 def view_model_cc_name(self): |
| 250 return self.view_model_basename + '.cc' |
| 251 |
| 252 @property |
| 253 def view_model_include_path(self): |
| 254 return os.path.join(os.path.dirname(self.path), self.view_model_h_name) |
| 255 |
| 256 @property |
| 257 def html_view_basename(self): |
| 258 return '%s-view' % self.type.replace('_', '-') |
| 259 |
| 260 @property |
| 261 def html_view_html_name(self): |
| 262 return self.html_view_basename + '.html' |
| 263 |
| 264 @property |
| 265 def html_view_js_name(self): |
| 266 return self.html_view_basename + '.js' |
| 267 |
| 268 @property |
| 269 def html_view_html_include_path(self): |
| 270 return os.path.join(os.path.dirname(self.path), self.html_view_html_name) |
| 271 |
| 272 @property |
| 273 def html_view_js_include_path(self): |
| 274 return os.path.join(os.path.dirname(self.path), self.html_view_js_name) |
| 275 |
| 276 @property |
| 277 def build_target(self): |
| 278 return self.type + "_wug_generated" |
| 279 |
| 280 @property |
| 281 def webui_view_class(self): |
| 282 return util.ToUpperCamelCase(self.type) + 'WebUIView' |
| 283 |
| 284 @property |
| 285 def view_model_class(self): |
| 286 return util.ToUpperCamelCase(self.type) + 'ViewModel' |
| 287 |
| 288 @property |
| 289 def imports(self): |
| 290 res = set(self.known_declarations.GetKnownPathes()) |
| 291 res.remove(self.path) |
| 292 return res |
| 293 |
| 294 |
| 295 |
OLD | NEW |