OLD | NEW |
---|---|
1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """Updates enums in histograms.xml file with values read from provided C++ enum. | 5 """Updates enums in histograms.xml file with values read from provided C++ enum. |
6 | 6 |
7 If the file was pretty-printed, the updated version is pretty-printed too. | 7 If the file was pretty-printed, the updated version is pretty-printed too. |
8 """ | 8 """ |
9 | 9 |
10 import logging | 10 import logging |
11 import os | 11 import os |
12 import re | 12 import re |
13 import sys | 13 import sys |
14 | 14 |
15 from xml.dom import minidom | 15 from xml.dom import minidom |
16 | 16 |
17 sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'common')) | 17 sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'common')) |
18 import diff_util | 18 import diff_util |
19 import path_util | 19 import path_util |
20 | 20 |
21 import print_style | 21 import print_style |
22 | 22 |
23 | 23 |
24 HISTOGRAMS_PATH = path_util.GetHistogramsFile() | 24 HISTOGRAMS_PATH = path_util.GetHistogramsFile() |
25 | 25 |
26 | 26 |
27 class UserError(Exception): | 27 class UserError(Exception): |
28 def __init__(self, message): | 28 def __init__(self, message): |
Ilya Sherman
2017/04/26 00:18:50
Hmm, why did you add extra indentation to all of t
lunalu1
2017/04/27 18:31:42
My bad, I didn't realize chromium and Blink had di
| |
29 Exception.__init__(self, message) | 29 Exception.__init__(self, message) |
30 | 30 |
31 @property | 31 @property |
32 def message(self): | 32 def message(self): |
33 return self.args[0] | 33 return self.args[0] |
34 | |
Ilya Sherman
2017/04/26 00:18:50
nit: Please leave two blank lines between top-leve
| |
35 class DuplicatedValue(Exception): | |
36 """Exception raised for duplicated enum values. | |
37 | |
38 Attributes: | |
39 first_label: First enum label that shares the duplicated enum value. | |
40 second_label: Second enum label that shares the duplicated enum value. | |
41 """ | |
42 def __init__(self, first_label, second_label): | |
43 self.first_label = first_label | |
44 self.second_label = second_label | |
34 | 45 |
35 | 46 |
36 def Log(message): | 47 def Log(message): |
37 logging.info(message) | 48 logging.info(message) |
38 | 49 |
39 | 50 |
40 def ReadHistogramValues(filename, start_marker, end_marker, strip_k_prefix): | 51 def ReadHistogramValues(filename, start_marker, end_marker, strip_k_prefix): |
41 """Returns a dictionary of enum values and a pair of labels that have the same | 52 """Creates a dictionary of enum values, read from a C++ file. |
42 enum values, read from a C++ file. | 53 |
43 | 54 Args: |
44 Args: | 55 filename: The unix-style path (relative to src/) of the file to open. |
45 filename: The unix-style path (relative to src/) of the file to open. | 56 start_marker: A regex that signifies the start of the enum values. |
46 start_marker: A regex that signifies the start of the enum values. | 57 end_marker: A regex that signifies the end of the enum values. |
47 end_marker: A regex that signifies the end of the enum values. | 58 strip_k_prefix: Set to True if enum values are declared as kFoo and the |
48 strip_k_prefix: Set to True if enum values are declared as kFoo and the | 59 'k' should be stripped. |
49 'k' should be stripped. | 60 |
61 Returns: | |
62 A dict mapping enum labels to the corresponding enum values. | |
63 | |
64 Raises: | |
65 DuplicatedValue: An error when two enum labels share the same value. | |
50 """ | 66 """ |
51 # Read the file as a list of lines | 67 # Read the file as a list of lines |
52 with open(path_util.GetInputFile(filename)) as f: | 68 with open(path_util.GetInputFile(filename)) as f: |
53 content = f.readlines() | 69 content = f.readlines() |
54 | 70 |
55 START_REGEX = re.compile(start_marker) | 71 START_REGEX = re.compile(start_marker) |
56 ITEM_REGEX = re.compile(r'^(\w+)') | 72 ITEM_REGEX = re.compile(r'^(\w+)') |
57 ITEM_REGEX_WITH_INIT = re.compile(r'(\w+)\s*=\s*(\d*)') | 73 ITEM_REGEX_WITH_INIT = re.compile(r'(\w+)\s*=\s*(\d*)') |
58 WRAPPED_INIT = re.compile(r'(\d+)') | 74 WRAPPED_INIT = re.compile(r'(\d+)') |
59 END_REGEX = re.compile(end_marker) | 75 END_REGEX = re.compile(end_marker) |
60 | 76 |
61 iterator = iter(content) | 77 iterator = iter(content) |
62 # Find the start of the enum | 78 # Find the start of the enum |
63 for line in iterator: | 79 for line in iterator: |
64 if START_REGEX.match(line.strip()): | 80 if START_REGEX.match(line.strip()): |
65 break | 81 break |
66 | 82 |
67 enum_value = 0 | 83 enum_value = 0 |
68 result = {} | 84 result = {} |
69 for line in iterator: | 85 for line in iterator: |
70 line = line.strip() | 86 line = line.strip() |
71 # Exit condition: we reached last enum value | 87 # Exit condition: we reached last enum value |
72 if END_REGEX.match(line): | 88 if END_REGEX.match(line): |
73 break | 89 break |
74 # Inside enum: generate new xml entry | 90 # Inside enum: generate new xml entry |
75 m = ITEM_REGEX_WITH_INIT.match(line) | 91 m = ITEM_REGEX_WITH_INIT.match(line) |
76 if m: | 92 if m: |
77 label = m.group(1) | 93 label = m.group(1) |
78 if m.group(2): | 94 if m.group(2): |
79 enum_value = int(m.group(2)) | 95 enum_value = int(m.group(2)) |
80 else: | 96 else: |
81 # Enum name is so long that the value wrapped to the next line | 97 # Enum name is so long that the value wrapped to the next line |
82 next_line = next(iterator).strip() | 98 next_line = next(iterator).strip() |
83 enum_value = int(WRAPPED_INIT.match(next_line).group(1)) | 99 enum_value = int(WRAPPED_INIT.match(next_line).group(1)) |
84 else: | 100 else: |
85 m = ITEM_REGEX.match(line) | 101 m = ITEM_REGEX.match(line) |
86 if m: | 102 if m: |
87 label = m.group(1) | 103 label = m.group(1) |
88 else: | 104 else: |
89 continue | 105 continue |
90 # If two enum labels have the same value | 106 # If two enum labels have the same value |
91 if enum_value in result: | 107 if enum_value in result: |
92 return result, (result[enum_value], label) | 108 raise DuplicatedValue(result[enum_value], label) |
93 if strip_k_prefix: | 109 if strip_k_prefix: |
94 assert label.startswith('k'), "Enum " + label + " should start with 'k'." | 110 assert label.startswith('k'), ("Enum " + label + |
95 label = label[1:] | 111 " should start with 'k'.") |
96 result[enum_value] = label | 112 label = label[1:] |
97 enum_value += 1 | 113 result[enum_value] = label |
98 return result, None | 114 enum_value += 1 |
115 return result | |
99 | 116 |
100 | 117 |
101 def CreateEnumItemNode(document, value, label): | 118 def CreateEnumItemNode(document, value, label): |
102 """Creates an int element to append to an enum.""" | 119 """Creates an int element to append to an enum.""" |
103 item_node = document.createElement('int') | 120 item_node = document.createElement('int') |
104 item_node.attributes['value'] = str(value) | 121 item_node.attributes['value'] = str(value) |
105 item_node.attributes['label'] = label | 122 item_node.attributes['label'] = label |
106 return item_node | 123 return item_node |
107 | 124 |
108 | 125 |
109 def UpdateHistogramDefinitions(histogram_enum_name, source_enum_values, | 126 def UpdateHistogramDefinitions(histogram_enum_name, source_enum_values, |
110 source_enum_path, document): | 127 source_enum_path, document): |
111 """Updates the enum node named |histogram_enum_name| based on the definition | 128 """Updates the enum node named |histogram_enum_name| based on the definition |
112 stored in |source_enum_values|. Existing items for which |source_enum_values| | 129 stored in |source_enum_values|. Existing items for which |
113 doesn't contain any corresponding data will be preserved. |source_enum_path| | 130 |source_enum_values| doesn't contain any corresponding data will be |
114 will be used to insert a comment. | 131 preserved. |source_enum_path| will be used to insert a comment. |
115 """ | 132 """ |
116 # Get a dom of <enum name=|histogram_enum_name| ...> node in |document|. | 133 # Get a dom of <enum name=|histogram_enum_name| ...> node in |document|. |
117 for enum_node in document.getElementsByTagName('enum'): | 134 for enum_node in document.getElementsByTagName('enum'): |
118 if enum_node.attributes['name'].value == histogram_enum_name: | 135 if enum_node.attributes['name'].value == histogram_enum_name: |
119 break | 136 break |
120 else: | 137 else: |
121 raise UserError('No {0} enum node found'.format(histogram_enum_name)) | 138 raise UserError('No {0} enum node found'.format(histogram_enum_name)) |
122 | 139 |
123 new_item_nodes = {} | 140 new_item_nodes = {} |
124 new_comments = [] | 141 new_comments = [] |
125 | 142 |
126 # Add a "Generated from (...)" comment. | 143 # Add a "Generated from (...)" comment. |
127 new_comments.append( | 144 new_comments.append( |
128 document.createComment(' Generated from {0} '.format(source_enum_path))) | 145 document.createComment(' Generated from {0} '.format(source_enum_path))) |
129 | 146 |
130 # Create item nodes for each of the enum values. | 147 # Create item nodes for each of the enum values. |
131 for value, label in source_enum_values.iteritems(): | 148 for value, label in source_enum_values.iteritems(): |
132 new_item_nodes[value] = CreateEnumItemNode(document, value, label) | 149 new_item_nodes[value] = CreateEnumItemNode(document, value, label) |
133 | 150 |
134 # Scan existing nodes in |enum_node| for old values and preserve them. | 151 # Scan existing nodes in |enum_node| for old values and preserve them. |
135 # - Preserve comments other than the 'Generated from' comment. NOTE: | 152 # - Preserve comments other than the 'Generated from' comment. NOTE: |
136 # this does not preserve the order of the comments in relation to the | 153 # this does not preserve the order of the comments in relation to the |
137 # old values. | 154 # old values. |
138 # - Drop anything else. | 155 # - Drop anything else. |
139 SOURCE_COMMENT_REGEX = re.compile('^ Generated from ') | 156 SOURCE_COMMENT_REGEX = re.compile('^ Generated from ') |
140 for child in enum_node.childNodes: | 157 for child in enum_node.childNodes: |
141 if child.nodeName == 'int': | 158 if child.nodeName == 'int': |
142 value = int(child.attributes['value'].value) | 159 value = int(child.attributes['value'].value) |
143 if not source_enum_values.has_key(value): | 160 if not source_enum_values.has_key(value): |
144 new_item_nodes[value] = child | 161 new_item_nodes[value] = child |
145 # Preserve existing non-generated comments. | 162 # Preserve existing non-generated comments. |
146 elif (child.nodeType == minidom.Node.COMMENT_NODE and | 163 elif (child.nodeType == minidom.Node.COMMENT_NODE and |
147 SOURCE_COMMENT_REGEX.match(child.data) is None): | 164 SOURCE_COMMENT_REGEX.match(child.data) is None): |
148 new_comments.append(child) | 165 new_comments.append(child) |
149 | 166 |
150 # Update |enum_node|. First, remove everything existing. | 167 # Update |enum_node|. First, remove everything existing. |
151 while enum_node.hasChildNodes(): | 168 while enum_node.hasChildNodes(): |
152 enum_node.removeChild(enum_node.lastChild) | 169 enum_node.removeChild(enum_node.lastChild) |
153 | 170 |
154 # Add comments at the top. | 171 # Add comments at the top. |
155 for comment in new_comments: | 172 for comment in new_comments: |
156 enum_node.appendChild(comment) | 173 enum_node.appendChild(comment) |
157 | 174 |
158 # Add in the new enums. | 175 # Add in the new enums. |
159 for value in sorted(new_item_nodes.iterkeys()): | 176 for value in sorted(new_item_nodes.iterkeys()): |
160 enum_node.appendChild(new_item_nodes[value]) | 177 enum_node.appendChild(new_item_nodes[value]) |
161 | 178 |
162 | 179 |
163 def _GetOldAndUpdatedXml(histogram_enum_name, source_enum_values, | 180 def _GetOldAndUpdatedXml(histogram_enum_name, source_enum_values, |
164 source_enum_path): | 181 source_enum_path): |
165 """Reads old histogram from |histogram_enum_name| from |HISTOGRAMS_PATH|, and | 182 """Reads old histogram from |histogram_enum_name| from |HISTOGRAMS_PATH|, |
166 calculates new histogram from |source_enum_values| from |source_enum_path|, | 183 and calculates new histogram from |source_enum_values| from |
167 and returns both in XML format. | 184 |source_enum_path|, and returns both in XML format. |
168 """ | 185 """ |
169 Log('Reading existing histograms from "{0}".'.format(HISTOGRAMS_PATH)) | 186 Log('Reading existing histograms from "{0}".'.format(HISTOGRAMS_PATH)) |
170 with open(HISTOGRAMS_PATH, 'rb') as f: | 187 with open(HISTOGRAMS_PATH, 'rb') as f: |
171 histograms_doc = minidom.parse(f) | 188 histograms_doc = minidom.parse(f) |
172 f.seek(0) | 189 f.seek(0) |
173 xml = f.read() | 190 xml = f.read() |
174 | 191 |
175 Log('Comparing histograms enum with new enum definition.') | 192 Log('Comparing histograms enum with new enum definition.') |
176 UpdateHistogramDefinitions(histogram_enum_name, source_enum_values, | 193 UpdateHistogramDefinitions(histogram_enum_name, source_enum_values, |
177 source_enum_path, histograms_doc) | 194 source_enum_path, histograms_doc) |
178 | 195 |
179 new_xml = print_style.GetPrintStyle().PrettyPrintNode(histograms_doc) | 196 new_xml = print_style.GetPrintStyle().PrettyPrintNode(histograms_doc) |
180 return (xml, new_xml) | 197 return (xml, new_xml) |
181 | 198 |
182 | 199 |
183 def HistogramNeedsUpdate(histogram_enum_name, source_enum_path, start_marker, | 200 def HistogramNeedsUpdate(histogram_enum_name, source_enum_path, start_marker, |
184 end_marker, strip_k_prefix = False): | 201 end_marker, strip_k_prefix = False): |
185 """Reads a C++ enum from a .h file and does a dry run of updating | 202 """Reads a C++ enum from a .h file and does a dry run of updating |
186 histograms.xml to match. Returns true if the histograms.xml file would be | 203 histograms.xml to match. |
187 changed. | 204 |
188 | 205 Args: |
189 Args: | 206 histogram_enum_name: The name of the XML <enum> attribute to update. |
190 histogram_enum_name: The name of the XML <enum> attribute to update. | 207 source_enum_path: A unix-style path, relative to src/, giving |
191 source_enum_path: A unix-style path, relative to src/, giving | 208 the C++ header file from which to read the enum. |
192 the C++ header file from which to read the enum. | 209 start_marker: A regular expression that matches the start of the C++ |
193 start_marker: A regular expression that matches the start of the C++ enum. | 210 enum. |
194 end_marker: A regular expression that matches the end of the C++ enum. | 211 end_marker: A regular expression that matches the end of the C++ enum. |
195 strip_k_prefix: Set to True if enum values are declared as kFoo and the | 212 strip_k_prefix: Set to True if enum values are declared as kFoo and the |
196 'k' should be stripped. | 213 'k' should be stripped. |
197 """ | 214 |
198 Log('Reading histogram enum definition from "{0}".'.format(source_enum_path)) | 215 Returns: |
199 source_enum_values, duplicated_values = ReadHistogramValues( | 216 A boolean indicating whether the histograms.xml file would be changed. |
200 source_enum_path, start_marker, end_marker, strip_k_prefix) | 217 |
201 if duplicated_values: | 218 Raises: |
202 return False, duplicated_values | 219 DuplicatedValue: An error when two enum labels share the same value. |
203 | 220 """ |
204 (xml, new_xml) = _GetOldAndUpdatedXml(histogram_enum_name, source_enum_values, | 221 Log('Reading histogram enum definition from "{0}".'.format( |
205 source_enum_path) | 222 source_enum_path)) |
206 return xml != new_xml, None | 223 try: |
224 source_enum_values, duplicated_values = ReadHistogramValues( | |
225 source_enum_path, start_marker, end_marker, strip_k_prefix) | |
226 except DuplicatedValue: | |
227 raise | |
Ilya Sherman
2017/04/26 00:18:50
There's no need to use a try/except here. The nat
| |
228 | |
229 (xml, new_xml) = _GetOldAndUpdatedXml(histogram_enum_name, | |
230 source_enum_values, | |
231 source_enum_path) | |
232 return xml != new_xml | |
207 | 233 |
208 | 234 |
209 def UpdateHistogramFromDict(histogram_enum_name, source_enum_values, | 235 def UpdateHistogramFromDict(histogram_enum_name, source_enum_values, |
210 source_enum_path): | 236 source_enum_path): |
211 """Updates |histogram_enum_name| enum in histograms.xml file with values | 237 """Updates |histogram_enum_name| enum in histograms.xml file with values |
212 from the {value: 'key'} dictionary |source_enum_values|. A comment is added | 238 from the {value: 'key'} dictionary |source_enum_values|. A comment is added |
213 to histograms.xml citing that the values in |histogram_enum_name| were | 239 to histograms.xml citing that the values in |histogram_enum_name| were |
214 sourced from |source_enum_path|. | 240 sourced from |source_enum_path|. |
215 """ | 241 """ |
216 (xml, new_xml) = _GetOldAndUpdatedXml(histogram_enum_name, source_enum_values, | 242 (xml, new_xml) = _GetOldAndUpdatedXml(histogram_enum_name, |
217 source_enum_path) | 243 source_enum_values, |
218 if not diff_util.PromptUserToAcceptDiff( | 244 source_enum_path) |
219 xml, new_xml, 'Is the updated version acceptable?'): | 245 if not diff_util.PromptUserToAcceptDiff( |
220 Log('Cancelled.') | 246 xml, new_xml, 'Is the updated version acceptable?'): |
221 return | 247 Log('Cancelled.') |
222 | 248 return |
223 with open(HISTOGRAMS_PATH, 'wb') as f: | 249 |
224 f.write(new_xml) | 250 with open(HISTOGRAMS_PATH, 'wb') as f: |
225 | 251 f.write(new_xml) |
226 Log('Done.') | 252 |
253 Log('Done.') | |
227 | 254 |
228 | 255 |
229 def UpdateHistogramEnum(histogram_enum_name, source_enum_path, | 256 def UpdateHistogramEnum(histogram_enum_name, source_enum_path, |
230 start_marker, end_marker, strip_k_prefix = False): | 257 start_marker, end_marker, strip_k_prefix = False): |
231 """Reads a C++ enum from a .h file and updates histograms.xml to match. | 258 """Reads a C++ enum from a .h file and updates histograms.xml to match. |
232 | 259 |
233 Args: | 260 Args: |
234 histogram_enum_name: The name of the XML <enum> attribute to update. | 261 histogram_enum_name: The name of the XML <enum> attribute to update. |
235 source_enum_path: A unix-style path, relative to src/, giving | 262 source_enum_path: A unix-style path, relative to src/, giving |
236 the C++ header file from which to read the enum. | 263 the C++ header file from which to read the enum. |
237 start_marker: A regular expression that matches the start of the C++ enum. | 264 start_marker: A regular expression that matches the start of the C++ |
238 end_marker: A regular expression that matches the end of the C++ enum. | 265 enum. |
239 strip_k_prefix: Set to True if enum values are declared as kFoo and the | 266 end_marker: A regular expression that matches the end of the C++ enum. |
240 'k' should be stripped. | 267 strip_k_prefix: Set to True if enum values are declared as kFoo and the |
241 """ | 268 'k' should be stripped. |
242 | 269 """ |
243 Log('Reading histogram enum definition from "{0}".'.format(source_enum_path)) | 270 |
244 source_enum_values, ignored = ReadHistogramValues(source_enum_path, | 271 Log('Reading histogram enum definition from "{0}".'.format( |
245 start_marker, end_marker, strip_k_prefix) | 272 source_enum_path)) |
246 | 273 source_enum_values, ignored = ReadHistogramValues(source_enum_path, |
247 UpdateHistogramFromDict(histogram_enum_name, source_enum_values, | 274 start_marker, |
248 source_enum_path) | 275 end_marker, |
276 strip_k_prefix) | |
277 | |
278 UpdateHistogramFromDict(Histogram_enum_name, source_enum_values, | |
Ilya Sherman
2017/04/26 00:18:50
nit: s/Histogram/histogram (text editor keyboard s
| |
279 source_enum_path) | |
OLD | NEW |