Chromium Code Reviews| Index: chrome/app/policy/syntax_check_policy_template_json.py |
| diff --git a/chrome/app/policy/syntax_check_policy_template_json.py b/chrome/app/policy/syntax_check_policy_template_json.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..0d927e7b6ae0239b257c57e1acb1c8db6eae685c |
| --- /dev/null |
| +++ b/chrome/app/policy/syntax_check_policy_template_json.py |
| @@ -0,0 +1,379 @@ |
| +#!/usr/bin/python2 |
| +# Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| +# Use of this source code is governed by a BSD-style license that can be |
| +# found in the LICENSE file. |
| + |
| +''' |
| +Checks a policy_templates.json file for conformity to its syntax specification. |
| +''' |
| + |
| +import json |
| +import optparse |
| +import os |
| +import re |
| +import sys |
| + |
|
gfeher
2011/01/14 11:57:26
Please insert newline.
Jakob Kummerow
2011/01/14 12:38:52
Done.
|
| +LEADING_WHITESPACE = re.compile('^([ \t]*)') |
| +TRAILING_WHITESPACE = re.compile('.*?([ \t]+)$') |
| + |
| + |
| +class PolicyTemplateChecker(object): |
| + |
| + def __init__(self): |
| + self.error_count = 0 |
| + self.warning_count = 0 |
| + self.num_policies = 0 |
| + self.num_groups = 0 |
| + self.num_policies_in_groups = 0 |
| + self.options = None |
| + |
| + def _Error(self, message, parent_element=None, identifier=None, |
| + offending_snippet=None): |
|
gfeher
2011/01/14 11:57:26
Please fix indenting.
Jakob Kummerow
2011/01/14 12:38:52
Done.
|
| + self.error_count += 1 |
| + error = '' |
| + if identifier is not None and parent_element is not None: |
| + error += 'In %s %s: ' % (parent_element, identifier) |
| + print error + 'Error: ' + message |
| + if offending_snippet is not None: |
| + print ' Offending:', json.dumps(offending_snippet, indent=2) |
| + |
| + def _CheckContains(self, container, key, value_type, |
| + optional=False, |
| + parent_element='policy', |
| + container_name=None, |
| + identifier=None, |
| + offending='__CONTAINER__'): |
| + ''' |
| + Checks |container| for presence of |key| with value of type |value_type|. |
| + If necessary, generates an error message of the form: |
| + |
| + In |parent_element| |identifier|: |
| + (if not present): |
| + Error: |container_name| must have a |value_type| named |key|. |
| + Offending snippet: |container| |
| + (if wrong type): |
| + Error: Value of |key| must be a |value_type|. |
| + 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.
|
| + |
| + ''' |
| + if identifier is None: |
| + identifier = container.get('name') |
| + if container_name is None: |
| + container_name = parent_element |
| + if offending == '__CONTAINER__': |
| + offending = container |
| + if key not in container: |
| + if optional: |
| + return |
| + else: |
| + self._Error('%s must have a %s "%s".' % |
| + (container_name.title(), value_type.__name__, key), |
| + container_name, identifier, offending) |
| + return None |
| + value = container[key] |
| + if not isinstance(value, value_type): |
| + self._Error('Value of "%s" must be a %s.' % |
| + (key, value_type.__name__), |
| + container_name, identifier, value) |
| + return value |
| + |
| + def _CheckPolicy(self, policy, may_contain_groups): |
| + if not isinstance(policy, dict): |
| + self._Error('Each policy must be a dictionary.', 'policy', None, policy) |
| + return |
| + |
| + # Each policy must have a name. |
| + self._CheckContains(policy, 'name', str) |
| + |
| + # Each policy must have a type. |
| + policy_type = self._CheckContains(policy, 'type', str) |
| + if policy_type not in ('group', 'main', 'string', 'int', 'list', 'int-enum', |
| + 'string-enum'): |
| + self._Error('Policy type must be either of: group, main, string, int, ' |
| + 'list, int-enum, string-enum', 'policy', policy, policy_type) |
| + return # Can't continue for unsupported type. |
| + if policy_type == 'group': |
| + |
| + # Groups must not be nested. |
| + if not may_contain_groups: |
| + self._Error('Policy groups must not be nested.', 'policy', policy) |
| + |
| + # Each policy group must have a list of policies. |
| + policies = self._CheckContains(policy, 'policies', list) |
| + if policies is not None: |
| + for nested_policy in policies: |
| + self._CheckPolicy(nested_policy, False) |
| + |
| + # Statistics. |
| + self.num_groups += 1 |
| + else: # type != group |
| + |
| + # Each policy must have a caption message. |
| + 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.
|
| + |
| + # Each policy must have a description message. |
| + self._CheckContains(policy, 'desc', str) |
| + |
| + # Each policy must have a supported_on list. |
| + supported_on = self._CheckContains(policy, 'supported_on', list) |
| + if supported_on is not None: |
| + for s in supported_on: |
| + if not isinstance(s, str): |
| + self._Error('Entries in "supported_on" must be strings.', 'policy', |
| + policy, supported_on) |
| + |
| + # Each policy must have a 'features' dict. |
| + self._CheckContains(policy, 'features', dict) |
| + |
| + # Each policy must have an 'example_value' of appropriate type. |
| + if policy_type == 'main': |
| + value_type = bool |
| + elif policy_type in ('string', 'string-enum'): |
| + value_type = str |
| + elif policy_type in ('int', 'int-enum'): |
| + value_type = int |
| + elif policy_type == 'list': |
| + value_type = list |
| + else: |
| + raise NotImplementedError('Unimplemented policy type: %s' % policy_type) |
| + self._CheckContains(policy, 'example_value', value_type) |
| + |
| + # Statistics. |
| + self.num_policies += 1 |
| + if not may_contain_groups: |
| + self.num_policies_in_groups += 1 |
| + |
| + # If 'deprecated' is present, it must be a bool. |
| + self._CheckContains(policy, 'deprecated', bool, True) |
| + |
| + if policy_type in ('int-enum', 'string-enum'): |
| + |
| + # Enums must contain a list of items. |
| + items = self._CheckContains(policy, 'items', list) |
| + if items is not None: |
| + if len(items) < 1: |
| + self._Error('"items" must not be empty.', 'policy', policy, items) |
| + for item in items: |
| + |
| + # Each item must have a name. |
| + self._CheckContains(item, 'name', str, container_name='item', |
| + identifier=policy.get('name')) |
| + |
| + # Each item must have a value of the correct type. |
| + self._CheckContains(item, 'value', value_type, container_name='item', |
| + identifier=policy.get('name')) |
| + |
| + # Each item must have a caption. |
| + self._CheckContains(item, 'caption', str, container_name='item', |
| + identifier=policy.get('name')) |
| + |
| + # There should not be any unknown keys in |policy|. |
| + for key in policy: |
| + if key not in ('name', 'type', 'caption', 'desc', 'supported_on', |
| + 'label', 'policies', 'items', 'example_value', 'features', |
| + 'deprecated'): |
| + self.warning_count += 1 |
| + print ('In policy %s: Warning: Unknown key: %s' % |
| + (policy.get('name'), key)) |
| + |
| + def _CheckMessage(self, key, value): |
| + # |key| must be a string, |value| a dict. |
| + if not isinstance(key, str): |
| + self._Error('Each message key must be a string.', 'message', key, key) |
| + return |
| + |
| + if not isinstance(value, dict): |
| + self._Error('Each message must be a dictionary.', 'message', key, value) |
| + return |
| + |
| + # Each message must have a desc. |
| + self._CheckContains(value, 'desc', str, parent_element='message', |
| + identifier=key) |
| + |
| + # Each message must have a text. |
| + self._CheckContains(value, 'text', str, parent_element='message', |
| + identifier=key) |
| + |
| + # There should not be any unknown keys in |value|. |
| + for vkey in value: |
| + if vkey not in ('desc', 'text'): |
| + self.warning_count += 1 |
| + print 'In message %s: Warning: Unknown key: %s' % (key, vkey) |
| + |
| + def _CheckPlaceholder(self, placeholder): |
| + if not isinstance(placeholder, dict): |
| + self._Error('Each placeholder must be a dictionary.', |
| + 'placeholder', None, placeholder) |
| + return |
| + |
| + # Each placeholder must have a 'key'. |
| + key = self._CheckContains(placeholder, 'key', str, |
| + parent_element='placeholder') |
| + |
| + # Each placeholder must have a 'value'. |
| + self._CheckContains(placeholder, 'value', str, parent_element='placeholder', |
| + identifier=key) |
| + |
| + # There should not be any unknown keys in |placeholder|. |
| + for k in placeholder: |
| + if k not in ('key', 'value'): |
| + self.warning_count += 1 |
| + name = str(placeholder.get('key'), placeholder) |
| + print 'In placeholder %s: Warning: Unknown key: %s' % (name, k) |
| + |
| + def _LeadingWhitespace(self, line): |
| + match = LEADING_WHITESPACE.match(line) |
| + if match: |
| + return match.group(1) |
| + return '' |
| + |
| + def _TrailingWhitespace(self, line): |
| + match = TRAILING_WHITESPACE.match(line) |
| + if match: |
| + return match.group(1) |
| + return '' |
| + |
| + def _LineError(self, message, line_number): |
| + self.error_count += 1 |
| + print 'In line %d: Error: %s' % (line_number, message) |
| + |
| + def _LineWarning(self, message, line_number): |
| + self.warning_count += 1 |
| + print ('In line %d: Warning: Automatically fixing formatting: %s' |
| + % (line_number, message)) |
| + |
| + def _CheckFormat(self, filename): |
| + if self.options.fix: |
| + fixed_lines = [] |
| + with open(filename) as f: |
| + indent = 0 |
| + line_number = 0 |
| + for line in f: |
| + line_number += 1 |
| + line = line.rstrip('\n') |
| + # Check for trailing whitespace. |
| + trailing_whitespace = self._TrailingWhitespace(line) |
| + if len(trailing_whitespace) > 0: |
| + if self.options.fix: |
| + line = line.rstrip() |
| + self._LineWarning('Trailing whitespace.', line_number) |
| + else: |
| + self._LineError('Trailing whitespace.', line_number) |
| + if len(line) == 0: |
| + if self.options.fix: |
| + fixed_lines += ['\n'] |
| + continue |
| + if len(line) == len(trailing_whitespace): |
| + continue |
| + # Check for correct amount of leading whitespace. |
| + leading_whitespace = self._LeadingWhitespace(line) |
| + if leading_whitespace.count('\t') > 0: |
| + if self.options.fix: |
| + line = leading_whitespace.replace('\t', ' ') + line.lstrip() |
| + self._LineWarning('Tab character found.', line_number) |
| + else: |
| + self._LineError('Tab character found.', line_number) |
| + if line[len(leading_whitespace)] in (']', '}'): |
| + indent -= 2 |
| + if line[0] != '#': # Ignore 0-indented comments. |
| + if len(leading_whitespace) != indent: |
| + if self.options.fix: |
| + line = ' ' * indent + line.lstrip() |
| + self._LineWarning('Indentation should be ' + str(indent) + |
| + ' spaces.', line_number) |
| + else: |
| + self._LineError('Bad indentation. Should be ' + str(indent) + |
| + ' spaces.', line_number) |
| + if line[-1] in ('[', '{'): |
| + indent += 2 |
| + if self.options.fix: |
| + fixed_lines.append(line + '\n') |
| + |
| + # If --fix is specified: backup the file (deleting any existing backup), |
| + # then write the fixed version with the old filename. |
| + if self.options.fix: |
| + if self.options.backup: |
| + backupfilename = filename + '.bak' |
| + if os.path.exists(backupfilename): |
| + os.remove(backupfilename) |
| + os.rename(filename, backupfilename) |
| + with open(filename, 'w') as f: |
| + f.writelines(fixed_lines) |
| + |
| + def Main(self, filename, options): |
| + try: |
| + with open(filename) as f: |
| + data = eval(f.read()) |
| + except: |
| + import traceback |
| + traceback.print_exc(file=sys.stdout) |
| + self._Error('Invalid JSON syntax.') |
| + return |
| + if data == None: |
| + self._Error('Invalid JSON syntax.') |
| + return |
| + self.options = options |
| + |
| + # First part: check JSON structure. |
| + |
| + # Check policy definitions. |
| + policy_definitions = self._CheckContains(data, 'policy_definitions', list, |
| + 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.
|
| + if policy_definitions is not None: |
| + for policy in policy_definitions: |
| + self._CheckPolicy(policy, True) |
| + |
| + # Check (non-policy-specific) message definitions. |
| + messages = self._CheckContains(data, 'messages', dict, |
| + parent_element=None, container_name='The root element', offending=None) |
| + if messages is not None: |
| + for message in messages: |
| + self._CheckMessage(message, messages[message]) |
| + |
| + # Check placeholders. |
| + placeholders = self._CheckContains(data, 'placeholders', list, |
| + parent_element=None, container_name='The root element', offending=None) |
| + if placeholders is not None: |
| + for placeholder in placeholders: |
| + self._CheckPlaceholder(placeholder) |
| + |
| + # Second part: check formatting. |
| + self._CheckFormat(filename) |
| + |
| + # Third part: summary and exit. |
| + print ('Finished. %d errors, %d warnings.' % |
| + (self.error_count, self.warning_count)) |
| + if self.options.stats: |
| + if self.num_groups > 0: |
| + print ('%d policies, %d of those in %d groups (containing on ' |
| + 'average %.1f policies).' % |
| + (self.num_policies, self.num_policies_in_groups, self.num_groups, |
| + (1.0 * self.num_policies_in_groups / self.num_groups))) |
| + else: |
| + print self.num_policies, 'policies, 0 policy groups.' |
| + if self.error_count > 0: |
| + return 1 |
| + return 0 |
| + |
| + def Run(self, argv, filename=None): |
| + parser = optparse.OptionParser( |
| + usage='usage: %prog [options] filename', |
| + description='Syntax check a policy_templates.json file.') |
| + parser.add_option('--fix', action='store_true', |
| + help='Automatically fix formatting.') |
| + parser.add_option('--backup', action='store_true', |
| + help='Create backup of original file (before fixing).') |
| + parser.add_option('--stats', action='store_true', |
| + help='Generate statistics.') |
| + (options, args) = parser.parse_args(argv) |
| + if filename is None: |
| + if len(args) != 2: |
| + parser.print_help() |
| + sys.exit(1) |
| + filename = args[1] |
| + return self.Main(filename, options) |
| + |
| + |
| +if __name__ == '__main__': |
| + checker = PolicyTemplateChecker() |
| + sys.exit(checker.Run(sys.argv)) |