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 |
deleted file mode 100755 |
index f51e3e3dc5a5159f11996f1a67946b2a5a883571..0000000000000000000000000000000000000000 |
--- a/chrome/app/policy/syntax_check_policy_template_json.py |
+++ /dev/null |
@@ -1,501 +0,0 @@ |
-#!/usr/bin/env python |
-# Copyright (c) 2012 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 |
- |
- |
-LEADING_WHITESPACE = re.compile('^([ \t]*)') |
-TRAILING_WHITESPACE = re.compile('.*?([ \t]+)$') |
-# Matches all non-empty strings that contain no whitespaces. |
-NO_WHITESPACE = re.compile('[^\s]+$') |
- |
-# Convert a 'type' to its corresponding schema type. |
-TYPE_TO_SCHEMA = { |
- 'int': 'integer', |
- 'list': 'array', |
- 'dict': 'object', |
- 'main': 'boolean', |
- 'string': 'string', |
- 'int-enum': 'integer', |
- 'string-enum': 'string', |
- 'external': 'object', |
-} |
- |
-# List of boolean policies that have been introduced with negative polarity in |
-# the past and should not trigger the negative polarity check. |
-LEGACY_INVERTED_POLARITY_WHITELIST = [ |
- 'DeveloperToolsDisabled', |
- 'DeviceAutoUpdateDisabled', |
- 'Disable3DAPIs', |
- 'DisableAuthNegotiateCnameLookup', |
- 'DisablePluginFinder', |
- 'DisablePrintPreview', |
- 'DisableSafeBrowsingProceedAnyway', |
- 'DisableScreenshots', |
- 'DisableSpdy', |
- 'DisableSSLRecordSplitting', |
- 'DriveDisabled', |
- 'DriveDisabledOverCellular', |
- 'ExternalStorageDisabled', |
- 'SavingBrowserHistoryDisabled', |
- 'SyncDisabled', |
-] |
- |
-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 |
- self.features = [] |
- |
- def _Error(self, message, parent_element=None, identifier=None, |
- offending_snippet=None): |
- 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__', |
- regexp_check=None): |
- ''' |
- Checks |container| for presence of |key| with value of type |value_type|. |
- If |value_type| is string and |regexp_check| is specified, then an error is |
- reported when the value does not match the regular expression object. |
- |
- The other parameters are needed to generate, if applicable, an appropriate |
- human-readable error message of the following form: |
- |
- In |parent_element| |identifier|: |
- (if the key is not present): |
- Error: |container_name| must have a |value_type| named |key|. |
- Offending snippet: |offending| (if specified; defaults to |container|) |
- (if the value does not have the required type): |
- Error: Value of |key| must be a |value_type|. |
- Offending snippet: |container[key]| |
- |
- Returns: |container[key]| if the key is present, None otherwise. |
- ''' |
- 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) |
- if value_type == str and regexp_check and not regexp_check.match(value): |
- self._Error('Value of "%s" must match "%s".' % |
- (key, regexp_check.pattern), |
- container_name, identifier, value) |
- return value |
- |
- def _AddPolicyID(self, id, policy_ids, policy): |
- ''' |
- Adds |id| to |policy_ids|. Generates an error message if the |
- |id| exists already; |policy| is needed for this message. |
- ''' |
- if id in policy_ids: |
- self._Error('Duplicate id', 'policy', policy.get('name'), |
- id) |
- else: |
- policy_ids.add(id) |
- |
- def _CheckPolicyIDs(self, policy_ids): |
- ''' |
- Checks a set of policy_ids to make sure it contains a continuous range |
- of entries (i.e. no holes). |
- Holes would not be a technical problem, but we want to ensure that nobody |
- accidentally omits IDs. |
- ''' |
- for i in range(len(policy_ids)): |
- if (i + 1) not in policy_ids: |
- self._Error('No policy with id: %s' % (i + 1)) |
- |
- def _CheckPolicySchema(self, policy, policy_type): |
- '''Checks that the 'schema' field matches the 'type' field.''' |
- self._CheckContains(policy, 'schema', dict) |
- if isinstance(policy.get('schema'), dict): |
- self._CheckContains(policy['schema'], 'type', str) |
- schema_type = policy['schema'].get('type') |
- if schema_type != TYPE_TO_SCHEMA[policy_type]: |
- self._Error('Schema type must match the existing type for policy %s' % |
- policy.get('name')) |
- |
- # Checks that boolean policies are not negated (which makes them harder to |
- # reason about). |
- if (schema_type == 'boolean' and |
- 'disable' in policy.get('name').lower() and |
- policy.get('name') not in LEGACY_INVERTED_POLARITY_WHITELIST): |
- self._Error(('Boolean policy %s uses negative polarity, please make ' + |
- 'new boolean policies follow the XYZEnabled pattern. ' + |
- 'See also http://crbug.com/85687') % policy.get('name')) |
- |
- |
- def _CheckPolicy(self, policy, is_in_group, policy_ids): |
- if not isinstance(policy, dict): |
- self._Error('Each policy must be a dictionary.', 'policy', None, policy) |
- return |
- |
- # There should not be any unknown keys in |policy|. |
- for key in policy: |
- if key not in ('name', 'type', 'caption', 'desc', 'device_only', |
- 'supported_on', 'label', 'policies', 'items', |
- 'example_value', 'features', 'deprecated', 'future', |
- 'id', 'schema', 'max_size'): |
- self.warning_count += 1 |
- print ('In policy %s: Warning: Unknown key: %s' % |
- (policy.get('name'), key)) |
- |
- # Each policy must have a name. |
- self._CheckContains(policy, 'name', str, regexp_check=NO_WHITESPACE) |
- |
- # Each policy must have a type. |
- policy_types = ('group', 'main', 'string', 'int', 'list', 'int-enum', |
- 'string-enum', 'dict', 'external') |
- policy_type = self._CheckContains(policy, 'type', str) |
- if policy_type not in policy_types: |
- self._Error('Policy type must be one of: ' + ', '.join(policy_types), |
- 'policy', policy.get('name'), policy_type) |
- return # Can't continue for unsupported type. |
- |
- # Each policy must have a caption message. |
- self._CheckContains(policy, 'caption', str) |
- |
- # Each policy must have a description message. |
- self._CheckContains(policy, 'desc', str) |
- |
- # If 'label' is present, it must be a string. |
- self._CheckContains(policy, 'label', str, True) |
- |
- # If 'deprecated' is present, it must be a bool. |
- self._CheckContains(policy, 'deprecated', bool, True) |
- |
- # If 'future' is present, it must be a bool. |
- self._CheckContains(policy, 'future', bool, True) |
- |
- if policy_type == 'group': |
- # Groups must not be nested. |
- if is_in_group: |
- 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) |
- |
- # Check sub-policies. |
- if policies is not None: |
- for nested_policy in policies: |
- self._CheckPolicy(nested_policy, True, policy_ids) |
- |
- # Groups must not have an |id|. |
- if 'id' in policy: |
- self._Error('Policies of type "group" must not have an "id" field.', |
- 'policy', policy) |
- |
- # Statistics. |
- self.num_groups += 1 |
- |
- else: # policy_type != group |
- # Each policy must have a protobuf ID. |
- id = self._CheckContains(policy, 'id', int) |
- self._AddPolicyID(id, policy_ids, policy) |
- |
- # 'schema' is the new 'type'. |
- # TODO(joaodasilva): remove the 'type' checks once 'schema' is used |
- # everywhere. |
- self._CheckPolicySchema(policy, policy_type) |
- |
- # 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. |
- features = self._CheckContains(policy, 'features', dict) |
- |
- # All the features must have a documenting message. |
- if features: |
- for feature in features: |
- if not feature in self.features: |
- self._Error('Unknown feature "%s". Known features must have a ' |
- 'documentation string in the messages dictionary.' % |
- feature, 'policy', policy.get('name', policy)) |
- |
- # All user policies must have a per_profile feature flag. |
- if (not policy.get('device_only', False) and |
- not policy.get('deprecated', False) and |
- not filter(re.compile('^chrome_frame:.*').match, supported_on)): |
- self._CheckContains(features, 'per_profile', bool, |
- container_name='features', |
- identifier=policy.get('name')) |
- |
- # All policies must declare whether they allow changes at runtime. |
- self._CheckContains(features, 'dynamic_refresh', bool, |
- container_name='features', |
- identifier=policy.get('name')) |
- |
- # 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 |
- elif policy_type in ('dict', 'external'): |
- value_type = dict |
- else: |
- raise NotImplementedError('Unimplemented policy type: %s' % policy_type) |
- self._CheckContains(policy, 'example_value', value_type) |
- |
- # Statistics. |
- self.num_policies += 1 |
- if is_in_group: |
- self.num_policies_in_groups += 1 |
- |
- 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. |
- # Note: |policy.get('name')| is used instead of |policy['name']| |
- # because it returns None rather than failing when no key called |
- # 'name' exists. |
- self._CheckContains(item, 'name', str, container_name='item', |
- identifier=policy.get('name'), |
- regexp_check=NO_WHITESPACE) |
- |
- # 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')) |
- |
- if policy_type == 'external': |
- # Each policy referencing external data must specify a maximum data size. |
- self._CheckContains(policy, 'max_size', int) |
- |
- 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 _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 self.options.fix: |
- if len(line) == 0: |
- fixed_lines += ['\n'] |
- continue |
- else: |
- if line == trailing_whitespace: |
- # This also catches the case of an empty line. |
- continue |
- # Check for correct amount of leading whitespace. |
- leading_whitespace = self._LeadingWhitespace(line) |
- if leading_whitespace.count('\t') > 0: |
- if self.options.fix: |
- leading_whitespace = leading_whitespace.replace('\t', ' ') |
- line = leading_whitespace + 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 (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]) |
- if message.startswith('doc_feature_'): |
- self.features.append(message[12:]) |
- |
- # Check policy definitions. |
- policy_definitions = self._CheckContains(data, 'policy_definitions', list, |
- parent_element=None, |
- container_name='The root element', |
- offending=None) |
- if policy_definitions is not None: |
- policy_ids = set() |
- for policy in policy_definitions: |
- self._CheckPolicy(policy, False, policy_ids) |
- self._CheckPolicyIDs(policy_ids) |
- |
- # Second part: check formatting. |
- self._CheckFormat(filename) |
- |
- # Third part: summary and exit. |
- print ('Finished checking %s. %d errors, %d warnings.' % |
- (filename, 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__': |
- sys.exit(PolicyTemplateChecker().Run(sys.argv)) |