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

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: 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
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))
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