Chromium Code Reviews| 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, { | |
|
Nikita (slow)
2015/02/25 18:21:59
As discussed, add optional comment field (or add T
dzhioev (left Google)
2015/02/26 14:01:39
Done.
| |
| 20 'imports': (False, (list, unicode)), | |
| 21 'type': (True, unicode), | |
| 22 'children': (False, (list, (dict, { | |
| 23 'id': (True, unicode), | |
| 24 'type': (True, unicode) | |
| 25 }))), | |
| 26 'context': (False, (list, (dict, { | |
| 27 'name': (True, unicode), | |
| 28 'type': (False, unicode), | |
| 29 'default': (False, object) | |
| 30 }))), | |
| 31 'events': (False, (list, unicode)), | |
|
Nikita (slow)
2015/02/25 18:21:59
As discussed, make this list of dicts.
dzhioev (left Google)
2015/02/26 14:01:39
Done.
| |
| 32 'strings': (False, (list, unicode)) | |
|
Nikita (slow)
2015/02/25 18:21:59
As discussed, make this list of dicts.
dzhioev (left Google)
2015/02/26 14:01:39
Done.
| |
| 33 }) | |
| 34 | |
| 35 # Returns (True,) if |obj| matches |schema|. | |
| 36 # Otherwise returns (False, msg), where |msg| is a diagnostic message. | |
| 37 def MatchSchema(obj, schema): | |
| 38 expected_type = schema[0] if isinstance(schema, tuple) else schema | |
| 39 if not isinstance(obj, expected_type): | |
| 40 return (False, 'Wrong type, expected %s, got %s.' % | |
| 41 (expected_type, type(obj))) | |
| 42 if expected_type == dict: | |
| 43 obj_keys = set(obj.iterkeys()) | |
| 44 allowed_keys = set(schema[1].iterkeys()) | |
| 45 required_keys = set(kv[0] for kv in schema[1].iteritems() if kv[1][0]) | |
| 46 if not obj_keys.issuperset(required_keys): | |
| 47 missing_keys = required_keys.difference(obj_keys) | |
| 48 return (False, 'Missing required key%s %s.' % | |
| 49 ('s' if len(missing_keys) > 1 else '', | |
| 50 ', '.join('\'%s\'' % k for k in missing_keys))) | |
| 51 if not obj_keys.issubset(allowed_keys): | |
| 52 unknown_keys = obj_keys.difference(allowed_keys) | |
| 53 return (False, 'Unknown key%s %s.' % | |
| 54 ('s' if len(unknown_keys) > 1 else '', | |
| 55 ', '.join('\'%s\'' % k for k in unknown_keys))) | |
| 56 for key in obj: | |
| 57 match = MatchSchema(obj[key], schema[1][key][1]) | |
| 58 if not match[0]: | |
| 59 return (False, ('[\'%s\'] ' % key) + match[1]) | |
| 60 elif expected_type == list: | |
| 61 for i, item in enumerate(obj): | |
| 62 match = MatchSchema(item, schema[1]) | |
| 63 if not match[0]: | |
| 64 return (False, ('[%d] ' % i) + match[1]) | |
| 65 return (True,) | |
| 66 | |
| 67 def CheckDeclarationIsValid(declaration): | |
| 68 match = MatchSchema(declaration, DECLARATION_SCHEMA) | |
| 69 if not match[0]: | |
| 70 raise Exception('Declaration is not valid: ' + match[1]) | |
| 71 | |
| 72 | |
| 73 class FieldDeclaration(object): | |
| 74 ALLOWED_TYPES = [ | |
| 75 'boolean', | |
| 76 'integer', | |
| 77 'double', | |
| 78 'string', | |
| 79 'string_list' | |
| 80 ] | |
| 81 | |
| 82 DEFAULTS = { | |
| 83 'boolean': False, | |
| 84 'integer': 0, | |
| 85 'double': 0.0, | |
| 86 'string': u'', | |
| 87 'string_list': [] | |
| 88 } | |
| 89 | |
| 90 def __init__(self, declaration): | |
| 91 self.name = declaration['name'] | |
| 92 self.id = util.ToLowerCamelCase(self.name) | |
| 93 self.type = declaration['type'] if 'type' in declaration else 'string' | |
| 94 if self.type not in self.ALLOWED_TYPES: | |
| 95 raise Exception('Unknown type of context field "%s": "%s"' % | |
| 96 (self.name, self.type)) | |
| 97 self.default_value = declaration['default'] if 'default' in declaration \ | |
| 98 else self.DEFAULTS[self.type] | |
| 99 if type(self.default_value) != type(self.DEFAULTS[self.type]): | |
| 100 raise Exception('Wrong type of default for field "%s": ' | |
| 101 'expected "%s", got "%s"' % | |
| 102 (self.name, | |
| 103 type(self.DEFAULTS[self.type]), | |
| 104 type(self.default_value))) | |
| 105 | |
| 106 class Declaration(object): | |
| 107 class DeclarationsStorage(object): | |
| 108 def __init__(self): | |
| 109 self.by_path = {} | |
| 110 self.by_type = {} | |
| 111 | |
| 112 def Add(self, declaration): | |
| 113 assert declaration.path not in self.by_path | |
| 114 self.by_path[declaration.path] = declaration | |
| 115 if declaration.type in self.by_type: | |
| 116 raise Exception( | |
| 117 'Redefinition of "%s" in "%s". Previous definition was in "%s".' % \ | |
| 118 (declaration.type, declaration.path, | |
| 119 self.by_type[declaration.type].path)) | |
| 120 self.by_type[declaration.type] = declaration | |
| 121 | |
| 122 def HasPath(self, path): | |
| 123 return path in self.by_path | |
| 124 | |
| 125 def GetByPath(self, path): | |
| 126 return self.by_path[path] | |
| 127 | |
| 128 def GetByType(self, type): | |
| 129 return self.by_type[type] | |
| 130 | |
| 131 def GetKnownPathes(self): | |
| 132 return self.by_path.keys() | |
| 133 | |
| 134 | |
| 135 def __init__(self, path, known_declarations=None): | |
| 136 if known_declarations is None: | |
| 137 known_declarations = Declaration.DeclarationsStorage() | |
| 138 self.path = path | |
| 139 self.data = json.load(open(path, 'r')) | |
| 140 CheckDeclarationIsValid(self.data) | |
| 141 known_declarations.Add(self) | |
| 142 if 'imports' in self.data: | |
| 143 for import_path in self.data['imports']: | |
|
Nikita (slow)
2015/02/25 18:21:59
Add TODO to eliminate possibility of circular refe
dzhioev (left Google)
2015/02/26 14:01:39
Done.
| |
| 144 if not known_declarations.HasPath(import_path): | |
| 145 Declaration(import_path, known_declarations) | |
| 146 self.children = {} | |
| 147 if 'children' in self.data: | |
| 148 for child_data in self.data['children']: | |
| 149 self.children[child_data['id']] = \ | |
| 150 known_declarations.GetByType(child_data['type']) | |
|
Nikita (slow)
2015/02/25 18:21:59
nit: Check that child declaration is known.
dzhioev (left Google)
2015/02/26 14:01:39
Done.
| |
| 151 if 'strings' not in self.data: | |
| 152 self.data['strings'] = {} | |
| 153 if 'context' not in self.data: | |
| 154 self.data['context'] = [] | |
| 155 if 'namespace' not in self.data: | |
|
Nikita (slow)
2015/02/25 18:21:59
As discussed, remove namespace.
dzhioev (left Google)
2015/02/26 14:01:39
Done.
| |
| 156 self.data['namespace'] = 'gen' | |
| 157 if 'events' not in self.data: | |
| 158 self.data['events'] = [] | |
| 159 self.fields = [FieldDeclaration(d) for d in self.data['context']] | |
| 160 fields_names = [field.name for field in self.fields] | |
| 161 if len(fields_names) > len(set(fields_names)): | |
| 162 raise Exception('Duplicate fields in declaration.') | |
| 163 self.known_declarations = known_declarations | |
| 164 | |
| 165 @property | |
| 166 def type(self): | |
| 167 return self.data['type'] | |
| 168 | |
| 169 @property | |
| 170 def namespace(self): | |
|
Nikita (slow)
2015/02/25 18:21:59
nit: drop this one
dzhioev (left Google)
2015/02/26 14:01:39
Done.
| |
| 171 return self.data['namespace'] | |
| 172 | |
| 173 @property | |
| 174 def strings(self): | |
| 175 return self.data['strings'] | |
| 176 | |
| 177 @property | |
| 178 def events(self): | |
| 179 return self.data['events'] | |
| 180 | |
| 181 @property | |
| 182 def webui_view_basename(self): | |
| 183 return self.type + '_web_ui_view' | |
| 184 | |
| 185 @property | |
| 186 def webui_view_h_name(self): | |
| 187 return self.webui_view_basename + '.h' | |
| 188 | |
| 189 @property | |
| 190 def webui_view_cc_name(self): | |
| 191 return self.webui_view_basename + '.cc' | |
| 192 | |
| 193 @property | |
| 194 def webui_view_include_path(self): | |
| 195 return os.path.join(os.path.dirname(self.path), self.webui_view_h_name) | |
| 196 | |
| 197 @property | |
| 198 def export_h_name(self): | |
| 199 return self.type + '_export.h' | |
| 200 | |
| 201 @property | |
| 202 def component_export_macro(self): | |
| 203 return "WUG_" + self.type.upper() + "_EXPORT" | |
| 204 | |
| 205 @property | |
| 206 def component_impl_macro(self): | |
| 207 return "WUG_" + self.type.upper() + "_IMPLEMENTATION" | |
| 208 | |
| 209 @property | |
| 210 def export_h_include_path(self): | |
| 211 return os.path.join(os.path.dirname(self.path), self.export_h_name) | |
| 212 | |
| 213 @property | |
| 214 def view_model_basename(self): | |
| 215 return self.type + '_view_model' | |
| 216 | |
| 217 @property | |
| 218 def view_model_h_name(self): | |
| 219 return self.view_model_basename + '.h' | |
| 220 | |
| 221 @property | |
| 222 def view_model_cc_name(self): | |
| 223 return self.view_model_basename + '.cc' | |
| 224 | |
| 225 @property | |
| 226 def view_model_include_path(self): | |
| 227 return os.path.join(os.path.dirname(self.path), self.view_model_h_name) | |
| 228 | |
| 229 @property | |
| 230 def html_view_basename(self): | |
| 231 return '%s-view' % self.type.replace('_', '-') | |
|
Nikita (slow)
2015/02/25 18:21:59
Please use single naming guideline.
dzhioev (left Google)
2015/02/26 14:01:39
I started discussion on chromium-polymer@chromium.
| |
| 232 | |
| 233 @property | |
| 234 def html_view_html_name(self): | |
| 235 return self.html_view_basename + '.html' | |
| 236 | |
| 237 @property | |
| 238 def html_view_js_name(self): | |
| 239 return self.html_view_basename + '.js' | |
| 240 | |
| 241 @property | |
| 242 def html_view_html_include_path(self): | |
| 243 return os.path.join(os.path.dirname(self.path), self.html_view_html_name) | |
| 244 | |
| 245 @property | |
| 246 def html_view_js_include_path(self): | |
| 247 return os.path.join(os.path.dirname(self.path), self.html_view_js_name) | |
| 248 | |
| 249 @property | |
| 250 def build_target(self): | |
| 251 return self.type + "_wug_generated" | |
| 252 | |
| 253 @property | |
| 254 def webui_view_class(self): | |
| 255 return util.ToUpperCamelCase(self.type) + 'WebUIView' | |
| 256 | |
| 257 @property | |
| 258 def view_model_class(self): | |
| 259 return util.ToUpperCamelCase(self.type) + 'ViewModel' | |
| 260 | |
| 261 @property | |
| 262 def imports(self): | |
| 263 res = set(self.known_declarations.GetKnownPathes()) | |
| 264 res.remove(self.path) | |
| 265 return res | |
| 266 | |
| 267 | |
| 268 | |
| OLD | NEW |