Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/python2 | |
| 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 ''' | |
| 7 Checks a policy_templates.json file for conformity to its syntax specification. | |
| 8 ''' | |
| 9 | |
| 10 import json | |
| 11 import optparse | |
| 12 import os | |
| 13 import re | |
| 14 import sys | |
| 15 | |
|
gfeher
2011/01/14 11:57:26
Please insert newline.
Jakob Kummerow
2011/01/14 12:38:52
Done.
| |
| 16 LEADING_WHITESPACE = re.compile('^([ \t]*)') | |
| 17 TRAILING_WHITESPACE = re.compile('.*?([ \t]+)$') | |
| 18 | |
| 19 | |
| 20 class PolicyTemplateChecker(object): | |
| 21 | |
| 22 def __init__(self): | |
| 23 self.error_count = 0 | |
| 24 self.warning_count = 0 | |
| 25 self.num_policies = 0 | |
| 26 self.num_groups = 0 | |
| 27 self.num_policies_in_groups = 0 | |
| 28 self.options = None | |
| 29 | |
| 30 def _Error(self, message, parent_element=None, identifier=None, | |
| 31 offending_snippet=None): | |
|
gfeher
2011/01/14 11:57:26
Please fix indenting.
Jakob Kummerow
2011/01/14 12:38:52
Done.
| |
| 32 self.error_count += 1 | |
| 33 error = '' | |
| 34 if identifier is not None and parent_element is not None: | |
| 35 error += 'In %s %s: ' % (parent_element, identifier) | |
| 36 print error + 'Error: ' + message | |
| 37 if offending_snippet is not None: | |
| 38 print ' Offending:', json.dumps(offending_snippet, indent=2) | |
| 39 | |
| 40 def _CheckContains(self, container, key, value_type, | |
| 41 optional=False, | |
| 42 parent_element='policy', | |
| 43 container_name=None, | |
| 44 identifier=None, | |
| 45 offending='__CONTAINER__'): | |
| 46 ''' | |
| 47 Checks |container| for presence of |key| with value of type |value_type|. | |
| 48 If necessary, generates an error message of the form: | |
| 49 | |
| 50 In |parent_element| |identifier|: | |
| 51 (if not present): | |
| 52 Error: |container_name| must have a |value_type| named |key|. | |
| 53 Offending snippet: |container| | |
| 54 (if wrong type): | |
| 55 Error: Value of |key| must be a |value_type|. | |
| 56 Offending snippet: |container[key]| | |
|
gfeher
2011/01/14 11:57:26
It would help understandability of your code if yo
Jakob Kummerow
2011/01/14 12:38:52
Done.
| |
| 57 | |
| 58 ''' | |
| 59 if identifier is None: | |
| 60 identifier = container.get('name') | |
| 61 if container_name is None: | |
| 62 container_name = parent_element | |
| 63 if offending == '__CONTAINER__': | |
| 64 offending = container | |
| 65 if key not in container: | |
| 66 if optional: | |
| 67 return | |
| 68 else: | |
| 69 self._Error('%s must have a %s "%s".' % | |
| 70 (container_name.title(), value_type.__name__, key), | |
| 71 container_name, identifier, offending) | |
| 72 return None | |
| 73 value = container[key] | |
| 74 if not isinstance(value, value_type): | |
| 75 self._Error('Value of "%s" must be a %s.' % | |
| 76 (key, value_type.__name__), | |
| 77 container_name, identifier, value) | |
| 78 return value | |
| 79 | |
| 80 def _CheckPolicy(self, policy, may_contain_groups): | |
| 81 if not isinstance(policy, dict): | |
| 82 self._Error('Each policy must be a dictionary.', 'policy', None, policy) | |
| 83 return | |
| 84 | |
| 85 # Each policy must have a name. | |
| 86 self._CheckContains(policy, 'name', str) | |
| 87 | |
| 88 # Each policy must have a type. | |
| 89 policy_type = self._CheckContains(policy, 'type', str) | |
| 90 if policy_type not in ('group', 'main', 'string', 'int', 'list', 'int-enum', | |
| 91 'string-enum'): | |
| 92 self._Error('Policy type must be either of: group, main, string, int, ' | |
| 93 'list, int-enum, string-enum', 'policy', policy, policy_type) | |
| 94 return # Can't continue for unsupported type. | |
| 95 if policy_type == 'group': | |
| 96 | |
| 97 # Groups must not be nested. | |
| 98 if not may_contain_groups: | |
| 99 self._Error('Policy groups must not be nested.', 'policy', policy) | |
| 100 | |
| 101 # Each policy group must have a list of policies. | |
| 102 policies = self._CheckContains(policy, 'policies', list) | |
| 103 if policies is not None: | |
| 104 for nested_policy in policies: | |
| 105 self._CheckPolicy(nested_policy, False) | |
| 106 | |
| 107 # Statistics. | |
| 108 self.num_groups += 1 | |
| 109 else: # type != group | |
| 110 | |
| 111 # Each policy must have a caption message. | |
| 112 self._CheckContains(policy, 'caption', str) | |
|
gfeher
2011/01/14 11:57:26
Groups should also have captions and descriptions.
Jakob Kummerow
2011/01/14 12:38:52
Done. Good catch.
| |
| 113 | |
| 114 # Each policy must have a description message. | |
| 115 self._CheckContains(policy, 'desc', str) | |
| 116 | |
| 117 # Each policy must have a supported_on list. | |
| 118 supported_on = self._CheckContains(policy, 'supported_on', list) | |
| 119 if supported_on is not None: | |
| 120 for s in supported_on: | |
| 121 if not isinstance(s, str): | |
| 122 self._Error('Entries in "supported_on" must be strings.', 'policy', | |
| 123 policy, supported_on) | |
| 124 | |
| 125 # Each policy must have a 'features' dict. | |
| 126 self._CheckContains(policy, 'features', dict) | |
| 127 | |
| 128 # Each policy must have an 'example_value' of appropriate type. | |
| 129 if policy_type == 'main': | |
| 130 value_type = bool | |
| 131 elif policy_type in ('string', 'string-enum'): | |
| 132 value_type = str | |
| 133 elif policy_type in ('int', 'int-enum'): | |
| 134 value_type = int | |
| 135 elif policy_type == 'list': | |
| 136 value_type = list | |
| 137 else: | |
| 138 raise NotImplementedError('Unimplemented policy type: %s' % policy_type) | |
| 139 self._CheckContains(policy, 'example_value', value_type) | |
| 140 | |
| 141 # Statistics. | |
| 142 self.num_policies += 1 | |
| 143 if not may_contain_groups: | |
| 144 self.num_policies_in_groups += 1 | |
| 145 | |
| 146 # If 'deprecated' is present, it must be a bool. | |
| 147 self._CheckContains(policy, 'deprecated', bool, True) | |
| 148 | |
| 149 if policy_type in ('int-enum', 'string-enum'): | |
| 150 | |
| 151 # Enums must contain a list of items. | |
| 152 items = self._CheckContains(policy, 'items', list) | |
| 153 if items is not None: | |
| 154 if len(items) < 1: | |
| 155 self._Error('"items" must not be empty.', 'policy', policy, items) | |
| 156 for item in items: | |
| 157 | |
| 158 # Each item must have a name. | |
| 159 self._CheckContains(item, 'name', str, container_name='item', | |
| 160 identifier=policy.get('name')) | |
| 161 | |
| 162 # Each item must have a value of the correct type. | |
| 163 self._CheckContains(item, 'value', value_type, container_name='item', | |
| 164 identifier=policy.get('name')) | |
| 165 | |
| 166 # Each item must have a caption. | |
| 167 self._CheckContains(item, 'caption', str, container_name='item', | |
| 168 identifier=policy.get('name')) | |
| 169 | |
| 170 # There should not be any unknown keys in |policy|. | |
| 171 for key in policy: | |
| 172 if key not in ('name', 'type', 'caption', 'desc', 'supported_on', | |
| 173 'label', 'policies', 'items', 'example_value', 'features', | |
| 174 'deprecated'): | |
| 175 self.warning_count += 1 | |
| 176 print ('In policy %s: Warning: Unknown key: %s' % | |
| 177 (policy.get('name'), key)) | |
| 178 | |
| 179 def _CheckMessage(self, key, value): | |
| 180 # |key| must be a string, |value| a dict. | |
| 181 if not isinstance(key, str): | |
| 182 self._Error('Each message key must be a string.', 'message', key, key) | |
| 183 return | |
| 184 | |
| 185 if not isinstance(value, dict): | |
| 186 self._Error('Each message must be a dictionary.', 'message', key, value) | |
| 187 return | |
| 188 | |
| 189 # Each message must have a desc. | |
| 190 self._CheckContains(value, 'desc', str, parent_element='message', | |
| 191 identifier=key) | |
| 192 | |
| 193 # Each message must have a text. | |
| 194 self._CheckContains(value, 'text', str, parent_element='message', | |
| 195 identifier=key) | |
| 196 | |
| 197 # There should not be any unknown keys in |value|. | |
| 198 for vkey in value: | |
| 199 if vkey not in ('desc', 'text'): | |
| 200 self.warning_count += 1 | |
| 201 print 'In message %s: Warning: Unknown key: %s' % (key, vkey) | |
| 202 | |
| 203 def _CheckPlaceholder(self, placeholder): | |
| 204 if not isinstance(placeholder, dict): | |
| 205 self._Error('Each placeholder must be a dictionary.', | |
| 206 'placeholder', None, placeholder) | |
| 207 return | |
| 208 | |
| 209 # Each placeholder must have a 'key'. | |
| 210 key = self._CheckContains(placeholder, 'key', str, | |
| 211 parent_element='placeholder') | |
| 212 | |
| 213 # Each placeholder must have a 'value'. | |
| 214 self._CheckContains(placeholder, 'value', str, parent_element='placeholder', | |
| 215 identifier=key) | |
| 216 | |
| 217 # There should not be any unknown keys in |placeholder|. | |
| 218 for k in placeholder: | |
| 219 if k not in ('key', 'value'): | |
| 220 self.warning_count += 1 | |
| 221 name = str(placeholder.get('key'), placeholder) | |
| 222 print 'In placeholder %s: Warning: Unknown key: %s' % (name, k) | |
| 223 | |
| 224 def _LeadingWhitespace(self, line): | |
| 225 match = LEADING_WHITESPACE.match(line) | |
| 226 if match: | |
| 227 return match.group(1) | |
| 228 return '' | |
| 229 | |
| 230 def _TrailingWhitespace(self, line): | |
| 231 match = TRAILING_WHITESPACE.match(line) | |
| 232 if match: | |
| 233 return match.group(1) | |
| 234 return '' | |
| 235 | |
| 236 def _LineError(self, message, line_number): | |
| 237 self.error_count += 1 | |
| 238 print 'In line %d: Error: %s' % (line_number, message) | |
| 239 | |
| 240 def _LineWarning(self, message, line_number): | |
| 241 self.warning_count += 1 | |
| 242 print ('In line %d: Warning: Automatically fixing formatting: %s' | |
| 243 % (line_number, message)) | |
| 244 | |
| 245 def _CheckFormat(self, filename): | |
| 246 if self.options.fix: | |
| 247 fixed_lines = [] | |
| 248 with open(filename) as f: | |
| 249 indent = 0 | |
| 250 line_number = 0 | |
| 251 for line in f: | |
| 252 line_number += 1 | |
| 253 line = line.rstrip('\n') | |
| 254 # Check for trailing whitespace. | |
| 255 trailing_whitespace = self._TrailingWhitespace(line) | |
| 256 if len(trailing_whitespace) > 0: | |
| 257 if self.options.fix: | |
| 258 line = line.rstrip() | |
| 259 self._LineWarning('Trailing whitespace.', line_number) | |
| 260 else: | |
| 261 self._LineError('Trailing whitespace.', line_number) | |
| 262 if len(line) == 0: | |
| 263 if self.options.fix: | |
| 264 fixed_lines += ['\n'] | |
| 265 continue | |
| 266 if len(line) == len(trailing_whitespace): | |
| 267 continue | |
| 268 # Check for correct amount of leading whitespace. | |
| 269 leading_whitespace = self._LeadingWhitespace(line) | |
| 270 if leading_whitespace.count('\t') > 0: | |
| 271 if self.options.fix: | |
| 272 line = leading_whitespace.replace('\t', ' ') + line.lstrip() | |
| 273 self._LineWarning('Tab character found.', line_number) | |
| 274 else: | |
| 275 self._LineError('Tab character found.', line_number) | |
| 276 if line[len(leading_whitespace)] in (']', '}'): | |
| 277 indent -= 2 | |
| 278 if line[0] != '#': # Ignore 0-indented comments. | |
| 279 if len(leading_whitespace) != indent: | |
| 280 if self.options.fix: | |
| 281 line = ' ' * indent + line.lstrip() | |
| 282 self._LineWarning('Indentation should be ' + str(indent) + | |
| 283 ' spaces.', line_number) | |
| 284 else: | |
| 285 self._LineError('Bad indentation. Should be ' + str(indent) + | |
| 286 ' spaces.', line_number) | |
| 287 if line[-1] in ('[', '{'): | |
| 288 indent += 2 | |
| 289 if self.options.fix: | |
| 290 fixed_lines.append(line + '\n') | |
| 291 | |
| 292 # If --fix is specified: backup the file (deleting any existing backup), | |
| 293 # then write the fixed version with the old filename. | |
| 294 if self.options.fix: | |
| 295 if self.options.backup: | |
| 296 backupfilename = filename + '.bak' | |
| 297 if os.path.exists(backupfilename): | |
| 298 os.remove(backupfilename) | |
| 299 os.rename(filename, backupfilename) | |
| 300 with open(filename, 'w') as f: | |
| 301 f.writelines(fixed_lines) | |
| 302 | |
| 303 def Main(self, filename, options): | |
| 304 try: | |
| 305 with open(filename) as f: | |
| 306 data = eval(f.read()) | |
| 307 except: | |
| 308 import traceback | |
| 309 traceback.print_exc(file=sys.stdout) | |
| 310 self._Error('Invalid JSON syntax.') | |
| 311 return | |
| 312 if data == None: | |
| 313 self._Error('Invalid JSON syntax.') | |
| 314 return | |
| 315 self.options = options | |
| 316 | |
| 317 # First part: check JSON structure. | |
| 318 | |
| 319 # Check policy definitions. | |
| 320 policy_definitions = self._CheckContains(data, 'policy_definitions', list, | |
| 321 parent_element=None, container_name='The root element', offending=None) | |
|
gfeher
2011/01/14 11:57:26
Please take a look at (and fix indenting here):
ht
Jakob Kummerow
2011/01/14 12:38:52
Done.
Same for two more occurrences below.
| |
| 322 if policy_definitions is not None: | |
| 323 for policy in policy_definitions: | |
| 324 self._CheckPolicy(policy, True) | |
| 325 | |
| 326 # Check (non-policy-specific) message definitions. | |
| 327 messages = self._CheckContains(data, 'messages', dict, | |
| 328 parent_element=None, container_name='The root element', offending=None) | |
| 329 if messages is not None: | |
| 330 for message in messages: | |
| 331 self._CheckMessage(message, messages[message]) | |
| 332 | |
| 333 # Check placeholders. | |
| 334 placeholders = self._CheckContains(data, 'placeholders', list, | |
| 335 parent_element=None, container_name='The root element', offending=None) | |
| 336 if placeholders is not None: | |
| 337 for placeholder in placeholders: | |
| 338 self._CheckPlaceholder(placeholder) | |
| 339 | |
| 340 # Second part: check formatting. | |
| 341 self._CheckFormat(filename) | |
| 342 | |
| 343 # Third part: summary and exit. | |
| 344 print ('Finished. %d errors, %d warnings.' % | |
| 345 (self.error_count, self.warning_count)) | |
| 346 if self.options.stats: | |
| 347 if self.num_groups > 0: | |
| 348 print ('%d policies, %d of those in %d groups (containing on ' | |
| 349 'average %.1f policies).' % | |
| 350 (self.num_policies, self.num_policies_in_groups, self.num_groups, | |
| 351 (1.0 * self.num_policies_in_groups / self.num_groups))) | |
| 352 else: | |
| 353 print self.num_policies, 'policies, 0 policy groups.' | |
| 354 if self.error_count > 0: | |
| 355 return 1 | |
| 356 return 0 | |
| 357 | |
| 358 def Run(self, argv, filename=None): | |
| 359 parser = optparse.OptionParser( | |
| 360 usage='usage: %prog [options] filename', | |
| 361 description='Syntax check a policy_templates.json file.') | |
| 362 parser.add_option('--fix', action='store_true', | |
| 363 help='Automatically fix formatting.') | |
| 364 parser.add_option('--backup', action='store_true', | |
| 365 help='Create backup of original file (before fixing).') | |
| 366 parser.add_option('--stats', action='store_true', | |
| 367 help='Generate statistics.') | |
| 368 (options, args) = parser.parse_args(argv) | |
| 369 if filename is None: | |
| 370 if len(args) != 2: | |
| 371 parser.print_help() | |
| 372 sys.exit(1) | |
| 373 filename = args[1] | |
| 374 return self.Main(filename, options) | |
| 375 | |
| 376 | |
| 377 if __name__ == '__main__': | |
| 378 checker = PolicyTemplateChecker() | |
| 379 sys.exit(checker.Run(sys.argv)) | |
| OLD | NEW |