[refactor] More post-NSS WebCrypto cleanups (utility functions).
[chromium-blink-merge.git] / tools / metrics / histograms / update_histogram_enum.py
blobba975996719ce541a07445cf4a3a4c4cefb4cd23
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
3 # found in the LICENSE file.
5 """Updates enums in histograms.xml file with values read from provided C++ enum.
7 If the file was pretty-printed, the updated version is pretty-printed too.
8 """
10 import logging
11 import os
12 import re
13 import sys
15 from xml.dom import minidom
17 sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'common'))
18 import diff_util
19 import path_util
21 import print_style
23 class UserError(Exception):
24 def __init__(self, message):
25 Exception.__init__(self, message)
27 @property
28 def message(self):
29 return self.args[0]
32 def Log(message):
33 logging.info(message)
36 def ReadHistogramValues(filename, start_marker, end_marker):
37 """Returns a dictionary of enum values, read from a C++ file.
39 Args:
40 filename: The unix-style path (relative to src/) of the file to open.
41 start_marker: A regex that signifies the start of the enum values.
42 end_marker: A regex that signifies the end of the enum values.
43 """
44 # Read the file as a list of lines
45 with open(path_util.GetInputFile(filename)) as f:
46 content = f.readlines()
48 START_REGEX = re.compile(start_marker)
49 ITEM_REGEX = re.compile(r'^(\w+)')
50 ITEM_REGEX_WITH_INIT = re.compile(r'(\w+)\s*=\s*(\d+)')
51 END_REGEX = re.compile(end_marker)
53 # Locate the enum definition and collect all entries in it
54 inside_enum = False # We haven't found the enum definition yet
55 result = {}
56 for line in content:
57 line = line.strip()
58 if inside_enum:
59 # Exit condition: we reached last enum value
60 if END_REGEX.match(line):
61 inside_enum = False
62 else:
63 # Inside enum: generate new xml entry
64 m = ITEM_REGEX_WITH_INIT.match(line)
65 if m:
66 enum_value = int(m.group(2))
67 label = m.group(1)
68 else:
69 m = ITEM_REGEX.match(line)
70 if m:
71 label = m.group(1)
72 else:
73 continue
74 result[enum_value] = label
75 enum_value += 1
76 else:
77 if START_REGEX.match(line):
78 inside_enum = True
79 enum_value = 0
80 return result
83 def CreateEnumItemNode(document, value, label):
84 """Creates an int element to append to an enum."""
85 item_node = document.createElement('int')
86 item_node.attributes['value'] = str(value)
87 item_node.attributes['label'] = label
88 return item_node
91 def UpdateHistogramDefinitions(histogram_enum_name, source_enum_values,
92 source_enum_path, document):
93 """Updates the enum node named |histogram_enum_name| based on the definition
94 stored in |source_enum_values|. Existing items for which |source_enum_values|
95 doesn't contain any corresponding data will be preserved. |source_enum_path|
96 will be used to insert a comment.
97 """
98 # Get a dom of <enum name=|histogram_enum_name| ...> node in |document|.
99 for enum_node in document.getElementsByTagName('enum'):
100 if enum_node.attributes['name'].value == histogram_enum_name:
101 break
102 else:
103 raise UserError('No {0} enum node found'.format(histogram_enum_name))
105 new_item_nodes = {}
106 new_comments = []
108 # Add a "Generated from (...)" comment.
109 new_comments.append(
110 document.createComment(' Generated from {0} '.format(source_enum_path)))
112 # Create item nodes for each of the enum values.
113 for value, label in source_enum_values.iteritems():
114 new_item_nodes[value] = CreateEnumItemNode(document, value, label)
116 # Scan existing nodes in |enum_node| for old values and preserve them.
117 # - Preserve comments other than the 'Generated from' comment. NOTE:
118 # this does not preserve the order of the comments in relation to the
119 # old values.
120 # - Drop anything else.
121 SOURCE_COMMENT_REGEX = re.compile('^ Generated from ')
122 for child in enum_node.childNodes:
123 if child.nodeName == 'int':
124 value = int(child.attributes['value'].value)
125 if not source_enum_values.has_key(value):
126 new_item_nodes[value] = child
127 # Preserve existing non-generated comments.
128 elif (child.nodeType == minidom.Node.COMMENT_NODE and
129 SOURCE_COMMENT_REGEX.match(child.data) is None):
130 new_comments.append(child)
132 # Update |enum_node|. First, remove everything existing.
133 while enum_node.hasChildNodes():
134 enum_node.removeChild(enum_node.lastChild)
136 # Add comments at the top.
137 for comment in new_comments:
138 enum_node.appendChild(comment)
140 # Add in the new enums.
141 for value in sorted(new_item_nodes.iterkeys()):
142 enum_node.appendChild(new_item_nodes[value])
145 def UpdateHistogramFromDict(histogram_enum_name, source_enum_values,
146 source_enum_path):
147 """Updates |histogram_enum_name| enum in histograms.xml file with values
148 from the {value: 'key'} dictionary |source_enum_values|. A comment is added
149 to histograms.xml citing that the values in |histogram_enum_name| were
150 sourced from |source_enum_path|.
152 HISTOGRAMS_PATH = path_util.GetHistogramsFile()
154 Log('Reading existing histograms from "{0}".'.format(HISTOGRAMS_PATH))
155 with open(HISTOGRAMS_PATH, 'rb') as f:
156 histograms_doc = minidom.parse(f)
157 f.seek(0)
158 xml = f.read()
160 Log('Comparing histograms enum with new enum definition.')
161 UpdateHistogramDefinitions(histogram_enum_name, source_enum_values,
162 source_enum_path, histograms_doc)
164 Log('Writing out new histograms file.')
165 new_xml = print_style.GetPrintStyle().PrettyPrintNode(histograms_doc)
166 if not diff_util.PromptUserToAcceptDiff(
167 xml, new_xml, 'Is the updated version acceptable?'):
168 Log('Cancelled.')
169 return
171 with open(HISTOGRAMS_PATH, 'wb') as f:
172 f.write(new_xml)
174 Log('Done.')
177 def UpdateHistogramEnum(histogram_enum_name, source_enum_path,
178 start_marker, end_marker):
179 """Reads a C++ enum from a .h file and updates histograms.xml to match.
181 Args:
182 histogram_enum_name: The name of the XML <enum> attribute to update.
183 source_enum_path: A unix-style path, relative to src/, giving
184 the C++ header file from which to read the enum.
185 start_marker: A regular expression that matches the start of the C++ enum.
186 end_marker: A regular expression that matches the end of the C++ enum.
189 Log('Reading histogram enum definition from "{0}".'.format(source_enum_path))
190 source_enum_values = ReadHistogramValues(source_enum_path, start_marker,
191 end_marker)
193 UpdateHistogramFromDict(histogram_enum_name, source_enum_values,
194 source_enum_path)