blob: 98d70acde103caa884d8de5c8a5cc6de904058a6 [file] [log] [blame]
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Updates enums in histograms.xml file with values read from provided C++ enum.
If the file was pretty-printed, the updated version is pretty-printed too.
"""
import logging
import os
import print_style
import re
import sys
from xml.dom import minidom
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'common'))
from diff_util import PromptUserToAcceptDiff
class UserError(Exception):
def __init__(self, message):
Exception.__init__(self, message)
@property
def message(self):
return self.args[0]
def Log(message):
logging.info(message)
def ReadHistogramValues(filename, start_marker, end_marker):
"""Reads in values from |filename|, returning a dictionary mapping value to
label corresponding to the enum framed by |start_marker| and |end_marker|.
"""
# Read the file as a list of lines
with open(filename) as f:
content = f.readlines()
START_REGEX = re.compile(start_marker)
ITEM_REGEX = re.compile(r'^(\w+)')
ITEM_REGEX_WITH_INIT = re.compile(r'(\w+)\s*=\s*(\d+)')
END_REGEX = re.compile(end_marker)
# Locate the enum definition and collect all entries in it
inside_enum = False # We haven't found the enum definition yet
result = {}
for line in content:
line = line.strip()
if inside_enum:
# Exit condition: we reached last enum value
if END_REGEX.match(line):
inside_enum = False
else:
# Inside enum: generate new xml entry
m = ITEM_REGEX_WITH_INIT.match(line)
if m:
enum_value = int(m.group(2))
label = m.group(1)
else:
m = ITEM_REGEX.match(line)
if m:
label = m.group(1)
else:
continue
result[enum_value] = label
enum_value += 1
else:
if START_REGEX.match(line):
inside_enum = True
enum_value = 0
return result
def CreateEnumItemNode(document, value, label):
"""Creates an int element to append to an enum."""
item_node = document.createElement('int')
item_node.attributes['value'] = str(value)
item_node.attributes['label'] = label
return item_node
def UpdateHistogramDefinitions(histogram_enum_name, source_enum_values,
source_enum_path, document):
"""Updates the enum node named |histogram_enum_name| based on the definition
stored in |source_enum_values|. Existing items for which |source_enum_values|
doesn't contain any corresponding data will be preserved. |source_enum_path|
will be used to insert a comment.
"""
# Get a dom of <enum name=|name| ...> node in |document|.
for enum_node in document.getElementsByTagName('enum'):
if enum_node.attributes['name'].value == histogram_enum_name:
break
else:
raise UserError('No {0} enum node found'.format(name))
new_item_nodes = {}
new_comments = []
# Add a "Generated from (...)" comment.
new_comments.append(
document.createComment(' Generated from {0} '.format(source_enum_path)))
# Create item nodes for each of the enum values.
for value, label in source_enum_values.iteritems():
new_item_nodes[value] = CreateEnumItemNode(document, value, label)
# Scan existing nodes in |enum_node| for old values and preserve them.
# - Preserve comments other than the 'Generated from' comment. NOTE:
# this does not preserve the order of the comments in relation to the
# old values.
# - Drop anything else.
SOURCE_COMMENT_REGEX = re.compile('^ Generated from ')
for child in enum_node.childNodes:
if child.nodeName == 'int':
value = int(child.attributes['value'].value)
if not source_enum_values.has_key(value):
new_item_nodes[value] = child
# Preserve existing non-generated comments.
elif (child.nodeType == minidom.Node.COMMENT_NODE and
SOURCE_COMMENT_REGEX.match(child.data) is None):
new_comments.append(child)
# Update |enum_node|. First, remove everything existing.
while enum_node.hasChildNodes():
enum_node.removeChild(enum_node.lastChild)
# Add comments at the top.
for comment in new_comments:
enum_node.appendChild(comment)
# Add in the new enums.
for value in sorted(new_item_nodes.iterkeys()):
enum_node.appendChild(new_item_nodes[value])
def UpdateHistogramFromDict(histogram_enum_name, source_enum_values,
source_enum_path):
"""Updates |histogram_enum_name| enum in histograms.xml file with values
from the {value: 'key'} dictionary |source_enum_values|. A comment is added
to histograms.xml citing that the values in |histogram_enum_name| were
sourced from |source_enum_path|.
"""
# TODO(ahernandez.miralles): The line below is present in nearly every
# file in this directory; factor out into a central location
HISTOGRAMS_PATH = 'histograms.xml'
Log('Reading existing histograms from "{0}".'.format(HISTOGRAMS_PATH))
with open(HISTOGRAMS_PATH, 'rb') as f:
histograms_doc = minidom.parse(f)
f.seek(0)
xml = f.read()
Log('Comparing histograms enum with new enum definition.')
UpdateHistogramDefinitions(histogram_enum_name, source_enum_values,
source_enum_path, histograms_doc)
Log('Writing out new histograms file.')
new_xml = print_style.GetPrintStyle().PrettyPrintNode(histograms_doc)
if not PromptUserToAcceptDiff(
xml, new_xml, 'Is the updated version acceptable?'):
Log('Cancelled.')
return
with open(HISTOGRAMS_PATH, 'wb') as f:
f.write(new_xml)
Log('Done.')
def UpdateHistogramEnum(histogram_enum_name, source_enum_path,
start_marker, end_marker):
"""Updates |histogram_enum_name| enum in histograms.xml file with values
read from |source_enum_path|, where |start_marker| and |end_marker| indicate
the beginning and end of the source enum definition, respectively.
"""
Log('Reading histogram enum definition from "{0}".'.format(source_enum_path))
source_enum_values = ReadHistogramValues(source_enum_path, start_marker,
end_marker)
UpdateHistogramFromDict(histogram_enum_name, source_enum_values,
source_enum_path)