Commit 29a529e7 authored by yiyaoliu@chromium.org's avatar yiyaoliu@chromium.org

Change the user action file format from .txt to .xml.

In this way, more information can be added (currently added 'description' and 'owner' for each action) 

A few functions are moved from tools/metrics/histograms to tools/metrics/common to be shared by tools/metrics and tools/histograms.

BUG=340735
NOTRY=true

Review URL: https://codereview.chromium.org/149503005

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@255357 0039d316-1c4b-4281-b951-d872f2087c98
parent bf0c0d52
......@@ -1014,28 +1014,32 @@ def _CheckCygwinShell(input_api, output_api):
def _CheckUserActionUpdate(input_api, output_api):
"""Checks if any new user action has been added."""
if any('chromeactions.txt' == input_api.os_path.basename(f) for f in
if any('actions.xml' == input_api.os_path.basename(f) for f in
input_api.LocalPaths()):
# If chromeactions.txt is already included in the changelist, the PRESUBMIT
# for chromeactions.txt will do a more complete presubmit check.
# If actions.xml is already included in the changelist, the PRESUBMIT
# for actions.xml will do a more complete presubmit check.
return []
with open('tools/metrics/actions/chromeactions.txt') as f:
current_actions = f.read()
file_filter = lambda f: f.LocalPath().endswith(('.cc', '.mm'))
action_re = r'[^a-zA-Z]UserMetricsAction\("([^"]*)'
current_actions = None
for f in input_api.AffectedFiles(file_filter=file_filter):
for line_num, line in f.ChangedContents():
match = input_api.re.search(action_re, line)
if match:
# Loads contents in tools/metrics/actions/actions.xml to memory. It's
# loaded only once.
if not current_actions:
with open('tools/metrics/actions/actions.xml') as actions_f:
current_actions = actions_f.read()
# Search for the matched user action name in |current_actions|.
for action_name in match.groups():
name_pattern = r'\t%s\n' % action_name
if name_pattern not in current_actions:
action = 'name="{0}"'.format(action_name)
if action not in current_actions:
return [output_api.PresubmitPromptWarning(
'File %s line %d: %s is missing in '
'tools/metrics/actions/chromeactions.txt. Please run '
'tools/metrics/actions/extract_actions.py --hash to update.'
'tools/metrics/actions/actions.xml. Please run '
'tools/metrics/actions/extract_actions.py to update.'
% (f.LocalPath(), line_num, action_name))]
return []
......
# per-file rules:
per-file chromeactions.txt=*
# 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.
"""Presubmit script for actions.xml.
See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
for more details on the presubmit API built into gcl.
"""
def CheckChange(input_api, output_api):
"""Checks that actions.xml is up to date and pretty-printed."""
for f in input_api.AffectedTextFiles():
p = f.AbsoluteLocalPath()
if (input_api.basename(p) == 'actions.xml'
and input_api.os_path.dirname(p) == input_api.PresubmitLocalPath()):
cwd = input_api.os_path.dirname(p)
exit_code = input_api.subprocess.call(
['python', 'extract_actions.py', '--presubmit'], cwd=cwd)
if exit_code != 0:
return [output_api.PresubmitError(
'actions.xml is not up to date or is not formatted correctly; '
'run extract_actions.py to fix')]
return []
def CheckChangeOnUpload(input_api, output_api):
return CheckChange(input_api, output_api)
def CheckChangeOnCommit(input_api, output_api):
return CheckChange(input_api, output_api)
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
#!/usr/bin/env python
# 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.
import unittest
import extract_actions
# Empty value to be inserted to |ACTIONS_MOCK|.
NO_VALUE = ''
ONE_OWNER = '<owner>name1@google.com</owner>\n'
TWO_OWNERS = """
<owner>name1@google.com</owner>\n
<owner>name2@google.com</owner>\n
"""
DESCRIPTION = '<description>Description.</description>\n'
TWO_DESCRIPTIONS = """
<description>Description.</description>\n
<description>Description2.</description>\n
"""
OBSOLETE = '<obsolete>Not used anymore. Replaced by action2.</obsolete>\n'
TWO_OBSOLETE = '<obsolete>Obsolete1.</obsolete><obsolete>Obsolete2.</obsolete>'
COMMENT = '<!--comment-->'
# A format string to mock the input action.xml file.
ACTIONS_XML = """
{comment}
<actions>
<action name="action1">
{owners}{obsolete}{description}
</action>
</actions>"""
NO_OWNER_EXPECTED_XML = (
'<actions>\n\n'
'<action name="action1">\n'
' <owner>Please list the metric\'s owners. '
'Add more owner tags as needed.</owner>\n'
' <description>Description.</description>\n'
'</action>\n\n'
'</actions>\n'
)
ONE_OWNER_EXPECTED_XML = (
'<actions>\n\n'
'<action name="action1">\n'
' <owner>name1@google.com</owner>\n'
' <description>Description.</description>\n'
'</action>\n\n'
'</actions>\n'
)
TWO_OWNERS_EXPECTED_XML = (
'<actions>\n\n'
'<action name="action1">\n'
' <owner>name1@google.com</owner>\n'
' <owner>name2@google.com</owner>\n'
' <description>Description.</description>\n'
'</action>\n\n'
'</actions>\n'
)
NO_DESCRIPTION_EXPECTED_XML = (
'<actions>\n\n'
'<action name="action1">\n'
' <owner>name1@google.com</owner>\n'
' <owner>name2@google.com</owner>\n'
' <description>Please enter the description of the metric.</description>\n'
'</action>\n\n'
'</actions>\n'
)
OBSOLETE_EXPECTED_XML = (
'<actions>\n\n'
'<action name="action1">\n'
' <owner>name1@google.com</owner>\n'
' <owner>name2@google.com</owner>\n'
' <description>Description.</description>\n'
' <obsolete>Not used anymore. Replaced by action2.</obsolete>\n'
'</action>\n\n'
'</actions>\n'
)
ADD_ACTION_EXPECTED_XML = (
'<actions>\n\n'
'<action name="action1">\n'
' <owner>name1@google.com</owner>\n'
' <owner>name2@google.com</owner>\n'
' <description>Description.</description>\n'
'</action>\n\n'
'<action name="action2">\n'
' <owner>Please list the metric\'s owners.'
' Add more owner tags as needed.</owner>\n'
' <description>Please enter the description of the metric.</description>\n'
'</action>\n\n'
'</actions>\n'
)
COMMENT_EXPECTED_XML = (
'<!--comment-->\n\n'
'<actions>\n\n'
'<action name="action1">\n'
' <owner>name1@google.com</owner>\n'
' <owner>name2@google.com</owner>\n'
' <description>Description.</description>\n'
'</action>\n\n'
'</actions>\n'
)
class ActionXmlTest(unittest.TestCase):
def _GetProcessedAction(self, owner, description, obsolete, new_actions=[],
comment=NO_VALUE):
"""Forms an actions XML string and returns it after processing.
It parses the original XML string, adds new user actions (if there is any),
and pretty prints it.
Args:
owner: the owner tag to be inserted in the original XML string.
description: the description tag to be inserted in the original XML
string.
obsolete: the obsolete tag to be inserted in the original XML string.
new_actions: optional. List of new user actions' names to be inserted.
comment: the comment tag to be inserted in the original XML string.
Returns:
An updated and pretty-printed action XML string.
"""
# Form the actions.xml mock content based on the input parameters.
current_xml = ACTIONS_XML.format(owners=owner, description=description,
obsolete=obsolete, comment=comment)
actions, actions_dict, comments = extract_actions.ParseActionFile(
current_xml)
for new_action in new_actions:
actions.add(new_action)
return extract_actions.PrettyPrint(actions, actions_dict, comments)
def testNoOwner(self):
xml_result = self._GetProcessedAction(NO_VALUE, DESCRIPTION, NO_VALUE)
self.assertEqual(NO_OWNER_EXPECTED_XML, xml_result)
def testOneOwnerOneDescription(self):
xml_result = self._GetProcessedAction(ONE_OWNER, DESCRIPTION, NO_VALUE)
self.assertEqual(ONE_OWNER_EXPECTED_XML, xml_result)
def testTwoOwners(self):
xml_result = self._GetProcessedAction(TWO_OWNERS, DESCRIPTION, NO_VALUE)
self.assertEqual(TWO_OWNERS_EXPECTED_XML, xml_result)
def testNoDescription(self):
xml_result = self._GetProcessedAction(TWO_OWNERS, NO_VALUE, NO_VALUE)
self.assertEqual(NO_DESCRIPTION_EXPECTED_XML, xml_result)
def testTwoDescriptions(self):
current_xml = ACTIONS_XML.format(owners=TWO_OWNERS, obsolete=NO_VALUE,
description=TWO_DESCRIPTIONS,
comment=NO_VALUE)
# Since there are two description tags, the function ParseActionFile will
# raise SystemExit with exit code 1.
with self.assertRaises(SystemExit) as cm:
_, _ = extract_actions.ParseActionFile(current_xml)
self.assertEqual(cm.exception.code, 1)
def testObsolete(self):
xml_result = self._GetProcessedAction(TWO_OWNERS, DESCRIPTION, OBSOLETE)
self.assertEqual(OBSOLETE_EXPECTED_XML, xml_result)
def testTwoObsoletes(self):
current_xml = ACTIONS_XML.format(owners=TWO_OWNERS, obsolete=TWO_OBSOLETE,
description=DESCRIPTION, comment=NO_VALUE)
# Since there are two obsolete tags, the function ParseActionFile will
# raise SystemExit with exit code 1.
with self.assertRaises(SystemExit) as cm:
_, _ = extract_actions.ParseActionFile(current_xml)
self.assertEqual(cm.exception.code, 1)
def testAddNewActions(self):
xml_result = self._GetProcessedAction(TWO_OWNERS, DESCRIPTION, NO_VALUE,
new_actions=['action2'])
self.assertEqual(ADD_ACTION_EXPECTED_XML, xml_result)
def testComment(self):
xml_result = self._GetProcessedAction(TWO_OWNERS, DESCRIPTION, NO_VALUE,
comment=COMMENT)
self.assertEqual(COMMENT_EXPECTED_XML, xml_result)
if __name__ == '__main__':
unittest.main()
# 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.
"""Holds the constants for pretty printing actions.xml."""
import os
import sys
# Import the metrics/common module for pretty print xml.
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'common'))
import pretty_print_xml
# Desired order for tag and tag attributes.
# { tag_name: [attribute_name, ...] }
ATTRIBUTE_ORDER = {
'action': ['name'],
'owner': [],
'description': [],
'obsolete': [],
}
# Tag names for top-level nodes whose children we don't want to indent.
TAGS_THAT_DONT_INDENT = ['actions']
# Extra vertical spacing rules for special tag names.
# {tag_name: (newlines_after_open, newlines_before_close, newlines_after_close)}
TAGS_THAT_HAVE_EXTRA_NEWLINE = {
'actions': (2, 1, 1),
'action': (1, 1, 1),
}
# Tags that we allow to be squished into a single line for brevity.
TAGS_THAT_ALLOW_SINGLE_LINE = ['owner', 'description', 'obsolete']
def GetPrintStyle():
"""Returns an XmlStyle object for pretty printing actions."""
return pretty_print_xml.XmlStyle(ATTRIBUTE_ORDER,
TAGS_THAT_HAVE_EXTRA_NEWLINE,
TAGS_THAT_DONT_INDENT,
TAGS_THAT_ALLOW_SINGLE_LINE)
# Copyright 2013 The Chromium Authors. All rights reserved.
# 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.
......
# 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.
"""Utility file for pretty print xml file.
The function PrettyPrintNode will be used for formatting both histograms.xml
and actions.xml.
"""
import logging
import textwrap
import xml.dom.minidom
WRAP_COLUMN = 80
class Error(Exception):
pass
def LastLineLength(s):
"""Returns the length of the last line in s.
Args:
s: A multi-line string, including newlines.
Returns:
The length of the last line in s, in characters.
"""
if s.rfind('\n') == -1: return len(s)
return len(s) - s.rfind('\n') - len('\n')
def XmlEscape(s):
"""XML-escapes the given string, replacing magic characters (&<>") with their
escaped equivalents."""
s = s.replace("&", "&amp;").replace("<", "&lt;")
s = s.replace("\"", "&quot;").replace(">", "&gt;")
return s
class XmlStyle(object):
"""A class that stores all style specification for an output xml file."""
def __init__(self, attribute_order, tags_that_have_extra_newline,
tags_that_dont_indent, tags_that_allow_single_line):
# List of tag names for top-level nodes whose children are not indented.
self.attribute_order = attribute_order
self.tags_that_have_extra_newline = tags_that_have_extra_newline
self.tags_that_dont_indent = tags_that_dont_indent
self.tags_that_allow_single_line = tags_that_allow_single_line
def PrettyPrintNode(self, node, indent=0):
"""Pretty-prints the given XML node at the given indent level.
Args:
node: The minidom node to pretty-print.
indent: The current indent level.
Returns:
The pretty-printed string (including embedded newlines).
Raises:
Error if the XML has unknown tags or attributes.
"""
# Handle the top-level document node.
if node.nodeType == xml.dom.minidom.Node.DOCUMENT_NODE:
return '\n'.join([self.PrettyPrintNode(n) for n in node.childNodes])
# Handle text nodes.
if node.nodeType == xml.dom.minidom.Node.TEXT_NODE:
# Wrap each paragraph in the text to fit in the 80 column limit.
wrapper = textwrap.TextWrapper()
wrapper.initial_indent = ' ' * indent
wrapper.subsequent_indent = ' ' * indent
wrapper.break_on_hyphens = False
wrapper.break_long_words = False
wrapper.width = WRAP_COLUMN
text = XmlEscape(node.data)
# Remove any common indent.
text = textwrap.dedent(text.strip('\n'))
lines = text.split('\n')
# Split the text into paragraphs at blank line boundaries.
paragraphs = [[]]
for l in lines:
if len(l.strip()) == 0 and len(paragraphs[-1]) > 0:
paragraphs.append([])
else:
paragraphs[-1].append(l)
# Remove trailing empty paragraph if present.
if len(paragraphs) > 0 and len(paragraphs[-1]) == 0:
paragraphs = paragraphs[:-1]
# Wrap each paragraph and separate with two newlines.
return '\n\n'.join([wrapper.fill('\n'.join(p)) for p in paragraphs])
# Handle element nodes.
if node.nodeType == xml.dom.minidom.Node.ELEMENT_NODE:
newlines_after_open, newlines_before_close, newlines_after_close = (
self.tags_that_have_extra_newline.get(node.tagName, (1, 1, 0)))
# Open the tag.
s = ' ' * indent + '<' + node.tagName
# Calculate how much space to allow for the '>' or '/>'.
closing_chars = 1
if not node.childNodes:
closing_chars = 2
# Pretty-print the attributes.
attributes = node.attributes.keys()
if attributes:
# Reorder the attributes.
if node.tagName not in self.attribute_order:
unrecognized_attributes = attributes
else:
unrecognized_attributes = (
[a for a in attributes
if a not in self.attribute_order[node.tagName]])
attributes = [a for a in self.attribute_order[node.tagName]
if a in attributes]
for a in unrecognized_attributes:
logging.error(
'Unrecognized attribute "%s" in tag "%s"' % (a, node.tagName))
if unrecognized_attributes:
raise Error()
for a in attributes:
value = XmlEscape(node.attributes[a].value)
# Replace sequences of whitespace with single spaces.
words = value.split()
a_str = ' %s="%s"' % (a, ' '.join(words))
# Start a new line if the attribute will make this line too long.
if LastLineLength(s) + len(a_str) + closing_chars > WRAP_COLUMN:
s += '\n' + ' ' * (indent + 3)
# Output everything up to the first quote.
s += ' %s="' % (a)
value_indent_level = LastLineLength(s)
# Output one word at a time, splitting to the next line where
# necessary.
column = value_indent_level
for i, word in enumerate(words):
# This is slightly too conservative since not every word will be
# followed by the closing characters...
if i > 0 and (column + len(word) + 1 + closing_chars > WRAP_COLUMN):
s = s.rstrip() # remove any trailing whitespace
s += '\n' + ' ' * value_indent_level
column = value_indent_level
s += word + ' '
column += len(word) + 1
s = s.rstrip() # remove any trailing whitespace
s += '"'
s = s.rstrip() # remove any trailing whitespace
# Pretty-print the child nodes.
if node.childNodes:
s += '>'
# Calculate the new indent level for child nodes.
new_indent = indent
if node.tagName not in self.tags_that_dont_indent:
new_indent += 2
child_nodes = node.childNodes
# Recursively pretty-print the child nodes.
child_nodes = [self.PrettyPrintNode(n, indent=new_indent)
for n in child_nodes]
child_nodes = [c for c in child_nodes if len(c.strip()) > 0]
# Determine whether we can fit the entire node on a single line.
close_tag = '</%s>' % node.tagName
space_left = WRAP_COLUMN - LastLineLength(s) - len(close_tag)
if (node.tagName in self.tags_that_allow_single_line and
len(child_nodes) == 1 and
len(child_nodes[0].strip()) <= space_left):
s += child_nodes[0].strip()
else:
s += '\n' * newlines_after_open + '\n'.join(child_nodes)
s += '\n' * newlines_before_close + ' ' * indent
s += close_tag
else:
s += '/>'
s += '\n' * newlines_after_close
return s
# Handle comment nodes.
if node.nodeType == xml.dom.minidom.Node.COMMENT_NODE:
return '<!--%s-->\n' % node.data
# Ignore other node types. This could be a processing instruction
# (<? ... ?>) or cdata section (<![CDATA[...]]!>), neither of which are
# legal in the histograms XML at present.
logging.error('Ignoring unrecognized node data: %s' % node.toxml())
raise Error()
......@@ -14,58 +14,20 @@ and wrapping text nodes, so we implement our own full custom XML pretty-printer.
from __future__ import with_statement
import diffutil
import json
import logging
import os
import shutil
import sys
import textwrap
import xml.dom.minidom
import print_style
sys.path.insert(1, os.path.join(sys.path[0], '..', '..', 'python'))
from google import path_utils
WRAP_COLUMN = 80
# Desired order for tag attributes; attributes listed here will appear first,
# and in the same order as in these lists.
# { tag_name: [attribute_name, ...] }
ATTRIBUTE_ORDER = {
'enum': ['name', 'type'],
'histogram': ['name', 'enum', 'units'],
'int': ['value', 'label'],
'fieldtrial': ['name', 'separator', 'ordering'],
'group': ['name', 'label'],
'affected-histogram': ['name'],
'with-group': ['name'],
}
# Tag names for top-level nodes whose children we don't want to indent.
TAGS_THAT_DONT_INDENT = [
'histogram-configuration',
'histograms',
'fieldtrials',
'enums'
]
# Extra vertical spacing rules for special tag names.
# {tag_name: (newlines_after_open, newlines_before_close, newlines_after_close)}
TAGS_THAT_HAVE_EXTRA_NEWLINE = {
'histogram-configuration': (2, 1, 1),
'histograms': (2, 1, 1),
'fieldtrials': (2, 1, 1),
'enums': (2, 1, 1),
'histogram': (1, 1, 1),
'enum': (1, 1, 1),
'fieldtrial': (1, 1, 1),
}
# Tags that we allow to be squished into a single line for brevity.
TAGS_THAT_ALLOW_SINGLE_LINE = [
'summary',
'int',
]
# Import the metrics/common module.
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'common'))
import diff_util
# Tags whose children we want to alphabetize. The key is the parent tag name,
# and the value is a pair of the tag name of the children we want to sort,
......@@ -84,165 +46,6 @@ class Error(Exception):
pass
def LastLineLength(s):
"""Returns the length of the last line in s.
Args:
s: A multi-line string, including newlines.
Returns:
The length of the last line in s, in characters.
"""
if s.rfind('\n') == -1: return len(s)
return len(s) - s.rfind('\n') - len('\n')
def XmlEscape(s):
"""XML-escapes the given string, replacing magic characters (&<>") with their
escaped equivalents."""
s = s.replace("&", "&amp;").replace("<", "&lt;")
s = s.replace("\"", "&quot;").replace(">", "&gt;")
return s
def PrettyPrintNode(node, indent=0):
"""Pretty-prints the given XML node at the given indent level.
Args:
node: The minidom node to pretty-print.
indent: The current indent level.
Returns:
The pretty-printed string (including embedded newlines).
Raises:
Error if the XML has unknown tags or attributes.
"""
# Handle the top-level document node.
if node.nodeType == xml.dom.minidom.Node.DOCUMENT_NODE:
return '\n'.join([PrettyPrintNode(n) for n in node.childNodes])
# Handle text nodes.
if node.nodeType == xml.dom.minidom.Node.TEXT_NODE:
# Wrap each paragraph in the text to fit in the 80 column limit.
wrapper = textwrap.TextWrapper()
wrapper.initial_indent = ' ' * indent
wrapper.subsequent_indent = ' ' * indent
wrapper.break_on_hyphens = False
wrapper.break_long_words = False
wrapper.width = WRAP_COLUMN
text = XmlEscape(node.data)
# Remove any common indent.
text = textwrap.dedent(text.strip('\n'))
lines = text.split('\n')
# Split the text into paragraphs at blank line boundaries.
paragraphs = [[]]
for l in lines:
if len(l.strip()) == 0 and len(paragraphs[-1]) > 0:
paragraphs.append([])
else:
paragraphs[-1].append(l)
# Remove trailing empty paragraph if present.
if len(paragraphs) > 0 and len(paragraphs[-1]) == 0:
paragraphs = paragraphs[:-1]
# Wrap each paragraph and separate with two newlines.
return '\n\n'.join([wrapper.fill('\n'.join(p)) for p in paragraphs])
# Handle element nodes.
if node.nodeType == xml.dom.minidom.Node.ELEMENT_NODE:
newlines_after_open, newlines_before_close, newlines_after_close = (
TAGS_THAT_HAVE_EXTRA_NEWLINE.get(node.tagName, (1, 1, 0)))
# Open the tag.
s = ' ' * indent + '<' + node.tagName
# Calculate how much space to allow for the '>' or '/>'.
closing_chars = 1
if not node.childNodes:
closing_chars = 2
# Pretty-print the attributes.
attributes = node.attributes.keys()
if attributes:
# Reorder the attributes.
if not node.tagName in ATTRIBUTE_ORDER:
unrecognized_attributes = attributes;
else:
unrecognized_attributes = (
[a for a in attributes if not a in ATTRIBUTE_ORDER[node.tagName]])
attributes = (
[a for a in ATTRIBUTE_ORDER[node.tagName] if a in attributes])
for a in unrecognized_attributes:
logging.error(
'Unrecognized attribute "%s" in tag "%s"' % (a, node.tagName))
if unrecognized_attributes:
raise Error()
for a in attributes:
value = XmlEscape(node.attributes[a].value)
# Replace sequences of whitespace with single spaces.
words = value.split()
a_str = ' %s="%s"' % (a, ' '.join(words))
# Start a new line if the attribute will make this line too long.
if LastLineLength(s) + len(a_str) + closing_chars > WRAP_COLUMN:
s += '\n' + ' ' * (indent + 3)
# Output everything up to the first quote.
s += ' %s="' % (a)
value_indent_level = LastLineLength(s)
# Output one word at a time, splitting to the next line where necessary.
column = value_indent_level
for i, word in enumerate(words):
# This is slightly too conservative since not every word will be
# followed by the closing characters...
if i > 0 and (column + len(word) + 1 + closing_chars > WRAP_COLUMN):
s = s.rstrip() # remove any trailing whitespace
s += '\n' + ' ' * value_indent_level
column = value_indent_level
s += word + ' '
column += len(word) + 1
s = s.rstrip() # remove any trailing whitespace
s += '"'
s = s.rstrip() # remove any trailing whitespace
# Pretty-print the child nodes.
if node.childNodes:
s += '>'
# Calculate the new indent level for child nodes.
new_indent = indent
if node.tagName not in TAGS_THAT_DONT_INDENT:
new_indent += 2
child_nodes = node.childNodes
# Recursively pretty-print the child nodes.
child_nodes = [PrettyPrintNode(n, indent=new_indent) for n in child_nodes]
child_nodes = [c for c in child_nodes if len(c.strip()) > 0]
# Determine whether we can fit the entire node on a single line.
close_tag = '</%s>' % node.tagName
space_left = WRAP_COLUMN - LastLineLength(s) - len(close_tag)
if (node.tagName in TAGS_THAT_ALLOW_SINGLE_LINE and
len(child_nodes) == 1 and len(child_nodes[0].strip()) <= space_left):
s += child_nodes[0].strip()
else:
s += '\n' * newlines_after_open + '\n'.join(child_nodes)
s += '\n' * newlines_before_close + ' ' * indent
s += close_tag
else:
s += '/>'
s += '\n' * newlines_after_close
return s
# Handle comment nodes.
if node.nodeType == xml.dom.minidom.Node.COMMENT_NODE:
return '<!--%s-->\n' % node.data
# Ignore other node types. This could be a processing instruction (<? ... ?>)
# or cdata section (<![CDATA[...]]!>), neither of which are legal in the
# histograms XML at present.
logging.error('Ignoring unrecognized node data: %s' % node.toxml())
raise Error()
def unsafeAppendChild(parent, child):
"""Append child to parent's list of children, ignoring the possibility that it
is already in another node's childNodes list. Requires that the previous
......@@ -304,14 +107,14 @@ def PrettyPrint(raw_xml):
"""Pretty-print the given XML.
Args:
xml: The contents of the histograms XML file, as a string.
raw_xml: The contents of the histograms XML file, as a string.
Returns:
The pretty-printed version.
"""
tree = xml.dom.minidom.parseString(raw_xml)
tree = TransformByAlphabetizing(tree)
return PrettyPrintNode(tree)
return print_style.GetPrintStyle().PrettyPrintNode(tree)
def main():
......@@ -356,7 +159,7 @@ def main():
logging.info('%s is not formatted correctly; run pretty_print.py to fix.' %
histograms_filename)
sys.exit(1)
if not diffutil.PromptUserToAcceptDiff(
if not diff_util.PromptUserToAcceptDiff(
xml, pretty,
'Is the prettified version acceptable?'):
logging.error('Aborting')
......
# 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.
"""Holds the constants for pretty printing histograms.xml."""
import os
import sys
# Import the metrics/common module for pretty print xml.
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'common'))
import pretty_print_xml
# Desired order for tag attributes; attributes listed here will appear first,
# and in the same order as in these lists.
# { tag_name: [attribute_name, ...] }
ATTRIBUTE_ORDER = {
'enum': ['name', 'type'],
'histogram': ['name', 'enum', 'units'],
'int': ['value', 'label'],
'fieldtrial': ['name', 'separator', 'ordering'],
'group': ['name', 'label'],
'affected-histogram': ['name'],
'with-group': ['name'],
}
# Tag names for top-level nodes whose children we don't want to indent.
TAGS_THAT_DONT_INDENT = [
'histogram-configuration',
'histograms',
'fieldtrials',
'enums'
]
# Extra vertical spacing rules for special tag names.
# {tag_name: (newlines_after_open, newlines_before_close, newlines_after_close)}
TAGS_THAT_HAVE_EXTRA_NEWLINE = {
'histogram-configuration': (2, 1, 1),
'histograms': (2, 1, 1),
'fieldtrials': (2, 1, 1),
'enums': (2, 1, 1),
'histogram': (1, 1, 1),
'enum': (1, 1, 1),
'fieldtrial': (1, 1, 1),
}
# Tags that we allow to be squished into a single line for brevity.
TAGS_THAT_ALLOW_SINGLE_LINE = [
'summary',
'int',
]
......@@ -13,9 +13,11 @@ import re
import sys
from xml.dom import minidom
import print_style
from diffutil import PromptUserToAcceptDiff
from pretty_print import PrettyPrintNode
# Import the metrics/common module.
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'common'))
from diff_util import PromptUserToAcceptDiff
HISTOGRAMS_PATH = 'histograms.xml'
ENUM_NAME = 'ExtensionFunctions'
......@@ -136,7 +138,8 @@ def main():
UpdateHistogramDefinitions(histogram_values, histograms_doc)
Log('Writing out new histograms file.')
new_xml = PrettyPrintNode(histograms_doc)
new_xml = print_style.GetPrintStyle().PrettyPrintNode(histograms_doc)
if PromptUserToAcceptDiff(xml, new_xml, 'Is the updated version acceptable?'):
with open(HISTOGRAMS_PATH, 'wb') as f:
f.write(new_xml)
......
......@@ -8,6 +8,7 @@ definitions read from policy_templates.json.
If the file was pretty-printed, the updated version is pretty-printed too.
"""
import os
import re
import sys
......@@ -15,12 +16,15 @@ from ast import literal_eval
from optparse import OptionParser
from xml.dom import minidom
from diffutil import PromptUserToAcceptDiff
from pretty_print import PrettyPrintNode
import print_style
# Import the metrics/common module.
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'common'))
from diff_util import PromptUserToAcceptDiff
HISTOGRAMS_PATH = 'histograms.xml'
POLICY_TEMPLATES_PATH =
'../../../components/policy/resources/policy_templates.json'
POLICY_TEMPLATES_PATH = (
'../../../components/policy/resources/policy_templates.json')
ENUM_NAME = 'EnterprisePolicies'
class UserError(Exception):
......@@ -118,8 +122,7 @@ def main():
xml = f.read()
UpdateHistogramDefinitions(policy_templates, histograms_doc)
new_xml = PrettyPrintNode(histograms_doc)
new_xml = print_style.GetPrintStyle().PrettyPrintNode(histograms_doc)
if PromptUserToAcceptDiff(xml, new_xml, 'Is the updated version acceptable?'):
with open(HISTOGRAMS_PATH, 'wb') as f:
f.write(new_xml)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment