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.
16 from xml
.dom
import minidom
18 sys
.path
.append(os
.path
.join(os
.path
.dirname(__file__
), '..', 'common'))
19 from diff_util
import PromptUserToAcceptDiff
21 class UserError(Exception):
22 def __init__(self
, message
):
23 Exception.__init
__(self
, message
)
34 def ReadHistogramValues(filename
, start_marker
, end_marker
):
35 """Reads in values from |filename|, returning a dictionary mapping value to
36 label corresponding to the enum framed by |start_marker| and |end_marker|.
38 # Read the file as a list of lines
39 with
open(filename
) as f
:
40 content
= f
.readlines()
42 START_REGEX
= re
.compile(start_marker
)
43 ITEM_REGEX
= re
.compile(r
'^(\w+)')
44 ITEM_REGEX_WITH_INIT
= re
.compile(r
'(\w+)\s*=\s*(\d+)')
45 END_REGEX
= re
.compile(end_marker
)
47 # Locate the enum definition and collect all entries in it
48 inside_enum
= False # We haven't found the enum definition yet
53 # Exit condition: we reached last enum value
54 if END_REGEX
.match(line
):
57 # Inside enum: generate new xml entry
58 m
= ITEM_REGEX_WITH_INIT
.match(line
)
60 enum_value
= int(m
.group(2))
63 m
= ITEM_REGEX
.match(line
)
68 result
[enum_value
] = label
71 if START_REGEX
.match(line
):
77 def CreateEnumItemNode(document
, value
, label
):
78 """Creates an int element to append to an enum."""
79 item_node
= document
.createElement('int')
80 item_node
.attributes
['value'] = str(value
)
81 item_node
.attributes
['label'] = label
85 def UpdateHistogramDefinitions(histogram_enum_name
, source_enum_values
,
86 source_enum_path
, document
):
87 """Updates the enum node named |histogram_enum_name| based on the definition
88 stored in |source_enum_values|. Existing items for which |source_enum_values|
89 doesn't contain any corresponding data will be preserved. |source_enum_path|
90 will be used to insert a comment.
92 # Get a dom of <enum name=|name| ...> node in |document|.
93 for enum_node
in document
.getElementsByTagName('enum'):
94 if enum_node
.attributes
['name'].value
== histogram_enum_name
:
97 raise UserError('No {0} enum node found'.format(name
))
102 # Add a "Generated from (...)" comment.
104 document
.createComment(' Generated from {0} '.format(source_enum_path
)))
106 # Create item nodes for each of the enum values.
107 for value
, label
in source_enum_values
.iteritems():
108 new_item_nodes
[value
] = CreateEnumItemNode(document
, value
, label
)
110 # Scan existing nodes in |enum_node| for old values and preserve them.
111 # - Preserve comments other than the 'Generated from' comment. NOTE:
112 # this does not preserve the order of the comments in relation to the
114 # - Drop anything else.
115 SOURCE_COMMENT_REGEX
= re
.compile('^ Generated from ')
116 for child
in enum_node
.childNodes
:
117 if child
.nodeName
== 'int':
118 value
= int(child
.attributes
['value'].value
)
119 if not source_enum_values
.has_key(value
):
120 new_item_nodes
[value
] = child
121 # Preserve existing non-generated comments.
122 elif (child
.nodeType
== minidom
.Node
.COMMENT_NODE
and
123 SOURCE_COMMENT_REGEX
.match(child
.data
) is None):
124 new_comments
.append(child
)
126 # Update |enum_node|. First, remove everything existing.
127 while enum_node
.hasChildNodes():
128 enum_node
.removeChild(enum_node
.lastChild
)
130 # Add comments at the top.
131 for comment
in new_comments
:
132 enum_node
.appendChild(comment
)
134 # Add in the new enums.
135 for value
in sorted(new_item_nodes
.iterkeys()):
136 enum_node
.appendChild(new_item_nodes
[value
])
139 def UpdateHistogramFromDict(histogram_enum_name
, source_enum_values
,
141 """Updates |histogram_enum_name| enum in histograms.xml file with values
142 from the {value: 'key'} dictionary |source_enum_values|. A comment is added
143 to histograms.xml citing that the values in |histogram_enum_name| were
144 sourced from |source_enum_path|.
146 # TODO(ahernandez.miralles): The line below is present in nearly every
147 # file in this directory; factor out into a central location
148 HISTOGRAMS_PATH
= 'histograms.xml'
150 Log('Reading existing histograms from "{0}".'.format(HISTOGRAMS_PATH
))
151 with
open(HISTOGRAMS_PATH
, 'rb') as f
:
152 histograms_doc
= minidom
.parse(f
)
156 Log('Comparing histograms enum with new enum definition.')
157 UpdateHistogramDefinitions(histogram_enum_name
, source_enum_values
,
158 source_enum_path
, histograms_doc
)
160 Log('Writing out new histograms file.')
161 new_xml
= print_style
.GetPrintStyle().PrettyPrintNode(histograms_doc
)
162 if not PromptUserToAcceptDiff(
163 xml
, new_xml
, 'Is the updated version acceptable?'):
167 with
open(HISTOGRAMS_PATH
, 'wb') as f
:
173 def UpdateHistogramEnum(histogram_enum_name
, source_enum_path
,
174 start_marker
, end_marker
):
175 """Updates |histogram_enum_name| enum in histograms.xml file with values
176 read from |source_enum_path|, where |start_marker| and |end_marker| indicate
177 the beginning and end of the source enum definition, respectively.
180 Log('Reading histogram enum definition from "{0}".'.format(source_enum_path
))
181 source_enum_values
= ReadHistogramValues(source_enum_path
, start_marker
,
184 UpdateHistogramFromDict(histogram_enum_name
, source_enum_values
,