OLD | NEW |
| (Empty) |
1 # Copyright (C) 2011 Google Inc. All rights reserved. | |
2 # | |
3 # Redistribution and use in source and binary forms, with or without | |
4 # modification, are permitted provided that the following conditions are | |
5 # met: | |
6 # | |
7 # * Redistributions of source code must retain the above copyright | |
8 # notice, this list of conditions and the following disclaimer. | |
9 # * Redistributions in binary form must reproduce the above | |
10 # copyright notice, this list of conditions and the following disclaimer | |
11 # in the documentation and/or other materials provided with the | |
12 # distribution. | |
13 # * Neither the name of Google Inc. nor the names of its | |
14 # contributors may be used to endorse or promote products derived from | |
15 # this software without specific prior written permission. | |
16 # | |
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
28 | |
29 | |
30 import difflib | |
31 import logging | |
32 import re | |
33 | |
34 from webkitpy.common.watchlist.amountchangedpattern import AmountChangedPattern | |
35 from webkitpy.common.watchlist.changedlinepattern import ChangedLinePattern | |
36 from webkitpy.common.watchlist.filenamepattern import FilenamePattern | |
37 from webkitpy.common.watchlist.watchlist import WatchList | |
38 from webkitpy.common.watchlist.watchlistrule import WatchListRule | |
39 from webkitpy.common.config.committers import CommitterList | |
40 | |
41 | |
42 _log = logging.getLogger(__name__) | |
43 | |
44 | |
45 class WatchListParser(object): | |
46 _DEFINITIONS = 'DEFINITIONS' | |
47 _CC_RULES = 'CC_RULES' | |
48 _MESSAGE_RULES = 'MESSAGE_RULES' | |
49 _INVALID_DEFINITION_NAME_REGEX = r'\|' | |
50 | |
51 def __init__(self, log_error=None): | |
52 self._log_error = log_error or _log.error | |
53 self._section_parsers = { | |
54 self._DEFINITIONS: self._parse_definition_section, | |
55 self._CC_RULES: self._parse_cc_rules, | |
56 self._MESSAGE_RULES: self._parse_message_rules, | |
57 } | |
58 self._definition_pattern_parsers = { | |
59 'filename': FilenamePattern, | |
60 'in_added_lines': (lambda compiled_regex: ChangedLinePattern(compile
d_regex, 0)), | |
61 'in_deleted_lines': (lambda compiled_regex: ChangedLinePattern(compi
led_regex, 1)), | |
62 'less': (lambda compiled_regex: AmountChangedPattern(compiled_regex,
1)), | |
63 'more': (lambda compiled_regex: AmountChangedPattern(compiled_regex,
0)), | |
64 } | |
65 | |
66 def parse(self, watch_list_contents): | |
67 watch_list = WatchList() | |
68 | |
69 # Change the watch list text into a dictionary. | |
70 dictionary = self._eval_watch_list(watch_list_contents) | |
71 | |
72 # Parse the top level sections in the watch list. | |
73 for section in dictionary: | |
74 parser = self._section_parsers.get(section) | |
75 if not parser: | |
76 self._log_error(('Unknown section "%s" in watch list.' | |
77 + self._suggest_words(section, self._section_par
sers.keys())) | |
78 % section) | |
79 continue | |
80 parser(dictionary[section], watch_list) | |
81 | |
82 self._validate(watch_list) | |
83 return watch_list | |
84 | |
85 def _eval_watch_list(self, watch_list_contents): | |
86 return eval(watch_list_contents, {'__builtins__': None}, None) | |
87 | |
88 def _suggest_words(self, invalid_word, valid_words): | |
89 close_matches = difflib.get_close_matches(invalid_word, valid_words) | |
90 if not close_matches: | |
91 return '' | |
92 return '\n\nPerhaps it should be %s.' % (' or '.join(close_matches)) | |
93 | |
94 def _parse_definition_section(self, definition_section, watch_list): | |
95 definitions = {} | |
96 for name in definition_section: | |
97 invalid_character = re.search(self._INVALID_DEFINITION_NAME_REGEX, n
ame) | |
98 if invalid_character: | |
99 self._log_error('Invalid character "%s" in definition "%s".' % (
invalid_character.group(0), name)) | |
100 continue | |
101 | |
102 definition = definition_section[name] | |
103 definitions[name] = [] | |
104 for pattern_type in definition: | |
105 pattern_parser = self._definition_pattern_parsers.get(pattern_ty
pe) | |
106 if not pattern_parser: | |
107 self._log_error(('Unknown pattern type "%s" in definition "%
s".' | |
108 + self._suggest_words(pattern_type, self._d
efinition_pattern_parsers.keys())) | |
109 % (pattern_type, name)) | |
110 continue | |
111 | |
112 try: | |
113 compiled_regex = re.compile(definition[pattern_type]) | |
114 except Exception, e: | |
115 self._log_error('The regex "%s" is invalid due to "%s".' % (
definition[pattern_type], str(e))) | |
116 continue | |
117 | |
118 pattern = pattern_parser(compiled_regex) | |
119 definitions[name].append(pattern) | |
120 if not definitions[name]: | |
121 self._log_error('The definition "%s" has no patterns, so it shou
ld be deleted.' % name) | |
122 continue | |
123 watch_list.definitions = definitions | |
124 | |
125 def _parse_rules(self, rules_section): | |
126 rules = [] | |
127 for complex_definition in rules_section: | |
128 instructions = rules_section[complex_definition] | |
129 if not instructions: | |
130 self._log_error('A rule for definition "%s" is empty, so it shou
ld be deleted.' % complex_definition) | |
131 continue | |
132 rules.append(WatchListRule(complex_definition, instructions)) | |
133 return rules | |
134 | |
135 def _parse_cc_rules(self, cc_section, watch_list): | |
136 watch_list.cc_rules = self._parse_rules(cc_section) | |
137 | |
138 def _parse_message_rules(self, message_section, watch_list): | |
139 watch_list.message_rules = self._parse_rules(message_section) | |
140 | |
141 def _validate(self, watch_list): | |
142 cc_definitions_set = self._rule_definitions_as_set(watch_list.cc_rules) | |
143 messages_definitions_set = self._rule_definitions_as_set(watch_list.mess
age_rules) | |
144 self._verify_all_definitions_are_used(watch_list, cc_definitions_set.uni
on(messages_definitions_set)) | |
145 | |
146 self._validate_definitions(cc_definitions_set, self._CC_RULES, watch_lis
t) | |
147 self._validate_definitions(messages_definitions_set, self._MESSAGE_RULES
, watch_list) | |
148 | |
149 accounts = CommitterList() | |
150 for cc_rule in watch_list.cc_rules: | |
151 # Copy the instructions since we'll be remove items from the origina
l list and | |
152 # modifying a list while iterating through it leads to undefined beh
avior. | |
153 intructions_copy = cc_rule.instructions()[:] | |
154 for email in intructions_copy: | |
155 if not accounts.account_by_email(email): | |
156 cc_rule.remove_instruction(email) | |
157 self._log_error("The email alias %s which is in the watchlis
t is not listed as a contributor in committers.py" % email) | |
158 continue | |
159 | |
160 def _verify_all_definitions_are_used(self, watch_list, used_definitions): | |
161 definitions_not_used = set(watch_list.definitions.keys()) | |
162 definitions_not_used.difference_update(used_definitions) | |
163 if definitions_not_used: | |
164 self._log_error('The following definitions are not used and should b
e removed: %s' % (', '.join(definitions_not_used))) | |
165 | |
166 def _validate_definitions(self, definitions, rules_section_name, watch_list)
: | |
167 declared_definitions = watch_list.definitions.keys() | |
168 definition_set = set(definitions) | |
169 definition_set.difference_update(declared_definitions) | |
170 | |
171 if definition_set: | |
172 suggestions = '' | |
173 if len(definition_set) == 1: | |
174 suggestions = self._suggest_words(set().union(definition_set).po
p(), declared_definitions) | |
175 self._log_error('In section "%s", the following definitions are not
used and should be removed: %s%s' % (rules_section_name, ', '.join(definition_se
t), suggestions)) | |
176 | |
177 def _rule_definitions_as_set(self, rules): | |
178 definition_set = set() | |
179 for rule in rules: | |
180 definition_set = definition_set.union(rule.definitions_to_match) | |
181 return definition_set | |
OLD | NEW |