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.
15 from xml
.dom
import minidom
17 sys
.path
.append(os
.path
.join(os
.path
.dirname(__file__
), '..', 'common'))
23 class UserError(Exception):
24 def __init__(self
, message
):
25 Exception.__init
__(self
, message
)
36 def ReadHistogramValues(filename
, start_marker
, end_marker
):
37 """Returns a dictionary of enum values, read from a C++ file.
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.
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
59 # Exit condition: we reached last enum value
60 if END_REGEX
.match(line
):
63 # Inside enum: generate new xml entry
64 m
= ITEM_REGEX_WITH_INIT
.match(line
)
66 enum_value
= int(m
.group(2))
69 m
= ITEM_REGEX
.match(line
)
74 result
[enum_value
] = label
77 if START_REGEX
.match(line
):
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
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.
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
:
103 raise UserError('No {0} enum node found'.format(histogram_enum_name
))
108 # Add a "Generated from (...)" comment.
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
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
,
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
)
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?'):
171 with
open(HISTOGRAMS_PATH
, 'wb') as f
:
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.
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
,
193 UpdateHistogramFromDict(histogram_enum_name
, source_enum_values
,