Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(37)

Side by Side Diff: chrome/app/policy/syntax_check_policy_template_json.py

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

Powered by Google App Engine
This is Rietveld 408576698