Commit d55fa610 authored by Wenhan (Han) Zhang's avatar Wenhan (Han) Zhang Committed by Commit Bot

Pretty-print actions.xml with model representation

Change-Id: I658ab21b20dbb35d539ee083533c2ec956e1c860
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2258194Reviewed-by: default avatarSteven Holte <holte@chromium.org>
Commit-Queue: Wenhan (Han) Zhang <zwenhan@google.com>
Cr-Commit-Position: refs/heads/master@{#790534}
parent 76eed8f0
......@@ -75,7 +75,8 @@ group("metrics_python_tests") {
"//tools/metrics/metrics_python_tests.py",
"//tools/metrics/actions/action_utils.py",
"//tools/metrics/actions/actions_print_style.py",
"//tools/metrics/actions/actions_model.py",
"//tools/metrics/actions/actions_model_test.py",
"//tools/metrics/actions/extract_actions.py",
"//tools/metrics/actions/extract_actions_test.py",
......
# Copyright 2020 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.
"""Model objects for actions.xml contents."""
import os
import sys
import re
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'common'))
import models
_OBSOLETE_TYPE = models.TextNodeType('obsolete', single_line=True)
_OWNER_TYPE = models.TextNodeType('owner', single_line=True)
_DESCRIPTION_TYPE = models.TextNodeType('description', single_line=True)
# A key for sorting XML nodes by the value of |attribute|.
# Used for sorting tags by their name attribute
_LOWERCASE_FN = lambda attribute: (lambda node: node.get(attribute).lower())
# A constant function as the sorting key for nodes whose orderings should be
# kept as given in the XML file within their parent node.
_KEEP_ORDER = lambda node: 1
_ACTION_TYPE = models.ObjectNodeType(
'action',
attributes=[
('name', str, None),
('not_user_triggered', str, r'^$|^true|false|True|False$'),
],
required_attributes=['name'],
alphabetization=[
(_OBSOLETE_TYPE.tag, _KEEP_ORDER),
(_OWNER_TYPE.tag, _KEEP_ORDER),
(_DESCRIPTION_TYPE.tag, _KEEP_ORDER),
],
extra_newlines=(1, 1, 1),
children=[models.ChildType(_OBSOLETE_TYPE.tag,
_OBSOLETE_TYPE, multiple=False),
models.ChildType(_OWNER_TYPE.tag,
_OWNER_TYPE, multiple=True),
models.ChildType(_DESCRIPTION_TYPE.tag,
_DESCRIPTION_TYPE, multiple=False),
])
_SUFFIX_TYPE = models.ObjectNodeType(
'suffix',
attributes=[
('name', str, r'^[A-Za-z0-9.-_]*$'),
('label', str, None),
],
required_attributes=['name', 'label'],
)
_AFFECTED_ACTION_TYPE = models.ObjectNodeType(
'affected-action',
attributes=[
('name', str, r'^[A-Za-z0-9.-_]*$'),
],
required_attributes=['name'],
)
_ACTION_SUFFIX_TYPE = models.ObjectNodeType(
'action-suffix',
attributes=[
('separator', str, r'^$|^[\._]+$'),
('ordering', str, r'^$|^suffix$'),
],
required_attributes=['separator'],
alphabetization=[
(_SUFFIX_TYPE.tag, _LOWERCASE_FN('name')),
(_AFFECTED_ACTION_TYPE.tag, _LOWERCASE_FN('name')),
],
extra_newlines=(1, 1, 1),
children=[
models.ChildType(_SUFFIX_TYPE.tag, _SUFFIX_TYPE, multiple=True),
models.ChildType(_AFFECTED_ACTION_TYPE.tag,
_AFFECTED_ACTION_TYPE, multiple=True),
])
_ACTIONS_TYPE = models.ObjectNodeType(
'actions',
alphabetization=[
(_ACTION_TYPE.tag, _LOWERCASE_FN('name')),
(_ACTION_SUFFIX_TYPE.tag, lambda n: None),
],
extra_newlines=(2, 1, 1),
indent=False,
children=[
models.ChildType(_ACTION_TYPE.tag, _ACTION_TYPE, multiple=True),
models.ChildType(_ACTION_SUFFIX_TYPE.tag,
_ACTION_SUFFIX_TYPE, multiple=True),
])
ACTION_XML_TYPE = models.DocumentType(_ACTIONS_TYPE)
def PrettifyTree(minidom_doc):
"""Parses the input minidom document and return a pretty-printed
version.
Args:
minidom_doc: A minidom document.
Returns:
A pretty-printed xml string, or None if the config contains errors.
"""
actions = ACTION_XML_TYPE.Parse(minidom_doc)
return ACTION_XML_TYPE.PrettyPrint(actions)
# Copyright 2020 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.
from parameterized import parameterized
import unittest
import xml.dom.minidom
import actions_model
PRETTY_XML = """
<actions>
<action name="Action1">
<owner>owner@chromium.org</owner>
<description>Description1</description>
</action>
<action name="Action2" not_user_triggered="true">
<obsolete>Obsolete text</obsolete>
<owner>owner@chromium.org</owner>
<description>Description2</description>
</action>
<action name="Action3">
<obsolete>Obsolete text</obsolete>
<owner>owner@chromium.org</owner>
<owner>owner2@chromium.org</owner>
<description>Description3</description>
</action>
<action-suffix separator="." ordering="suffix">
<suffix name="AppMenu" label="Users opened a new incognito tab by app menu"/>
<suffix name="LongTapMenu"
label="Users opened a new incognito tab by long tap menu"/>
</action-suffix>
<action-suffix separator="." ordering="suffix">
<suffix name="LongTapMenu" label="Users closed a tab by long tap menu"/>
<affected-action name="AnAction"/>
<affected-action name="MobileMenuCloseTab"/>
</action-suffix>
</actions>
""".strip()
XML_WITH_WRONG_INDENT = """
<actions>
<action name="Action1">
<owner>owner@chromium.org</owner>
<description>Description1</description>
</action>
<action name="Action2" not_user_triggered="true">
<obsolete>Obsolete text</obsolete>
<owner>owner@chromium.org</owner>
<description>Description2</description>
</action>
<action name="Action3">
<obsolete>Obsolete text</obsolete>
<owner>owner@chromium.org</owner>
<owner>owner2@chromium.org</owner>
<description>Description3</description>
</action>
<action-suffix separator="." ordering="suffix">
<suffix name="AppMenu" label="Users opened a new incognito tab by app menu"/>
<suffix name="LongTapMenu"
label="Users opened a new incognito tab by long tap menu"/>
</action-suffix>
<action-suffix separator="." ordering="suffix">
<suffix name="LongTapMenu" label="Users closed a tab by long tap menu"/>
<affected-action name="AnAction"/>
<affected-action name="MobileMenuCloseTab"/>
</action-suffix>
</actions>
""".strip()
XML_WITH_WRONG_SINGLELINE = """
<actions>
<action name="Action1">
<owner>
owner@chromium.org
</owner>
<description>Description1</description>
</action>
<action name="Action2" not_user_triggered="true">
<obsolete>
Obsolete text
</obsolete>
<owner>owner@chromium.org</owner>
<description>Description2</description>
</action>
<action name="Action3">
<obsolete>Obsolete text</obsolete>
<owner>owner@chromium.org</owner>
<owner>owner2@chromium.org</owner>
<description>
Description3
</description>
</action>
<action-suffix separator="." ordering="suffix">
<suffix name="AppMenu" label="Users opened a new incognito tab by app menu"/>
<suffix name="LongTapMenu"
label="Users opened a new incognito tab by long tap menu"/>
</action-suffix>
<action-suffix separator="." ordering="suffix">
<suffix name="LongTapMenu" label="Users closed a tab by long tap menu"/>
<affected-action name="AnAction"/>
<affected-action name="MobileMenuCloseTab"/>
</action-suffix>
</actions>
""".strip()
XML_WITH_WRONG_LINE_BREAK = """
<actions>
<action name="Action1">
<owner>owner@chromium.org</owner>
<description>Description1</description>
</action>
<action name="Action2" not_user_triggered="true">
<obsolete>Obsolete text</obsolete>
<owner>owner@chromium.org</owner>
<description>Description2</description>
</action>
<action name="Action3">
<obsolete>Obsolete text</obsolete>
<owner>owner@chromium.org</owner>
<owner>owner2@chromium.org</owner>
<description>Description3</description>
</action>
<action-suffix separator="." ordering="suffix">
<suffix name="AppMenu" label="Users opened a new incognito tab by app menu"/>
<suffix name="LongTapMenu"
label="Users opened a new incognito tab by long tap menu"/>
</action-suffix>
<action-suffix separator="." ordering="suffix">
<suffix name="LongTapMenu"
label="Users closed a tab by long tap menu"/>
<affected-action name="AnAction"/>
<affected-action name="MobileMenuCloseTab"/>
</action-suffix>
</actions>
""".strip()
XML_WITH_WRONG_ORDER = """
<actions>
<action name="Action2" not_user_triggered="true">
<obsolete>Obsolete text</obsolete>
<owner>owner@chromium.org</owner>
<description>Description2</description>
</action>
<action name="Action1">
<owner>owner@chromium.org</owner>
<description>Description1</description>
</action>
<action name="Action3">
<obsolete>Obsolete text</obsolete>
<owner>owner@chromium.org</owner>
<owner>owner2@chromium.org</owner>
<description>Description3</description>
</action>
<action-suffix separator="." ordering="suffix">
<suffix name="LongTapMenu"
label="Users opened a new incognito tab by long tap menu"/>
<suffix name="AppMenu" label="Users opened a new incognito tab by app menu"/>
</action-suffix>
<action-suffix separator="." ordering="suffix">
<suffix name="LongTapMenu" label="Users closed a tab by long tap menu"/>
<affected-action name="MobileMenuCloseTab"/>
<affected-action name="AnAction"/>
</action-suffix>
</actions>
""".strip()
XML_WITH_WRONG_CHILDREN_ORDER = """
<actions>
<action name="Action1">
<description>Description1</description>
<owner>owner@chromium.org</owner>
</action>
<action name="Action2" not_user_triggered="true">
<owner>owner@chromium.org</owner>
<obsolete>Obsolete text</obsolete>
<description>Description2</description>
</action>
<action name="Action3">
<obsolete>Obsolete text</obsolete>
<owner>owner@chromium.org</owner>
<description>Description3</description>
<owner>owner2@chromium.org</owner>
</action>
<action-suffix separator="." ordering="suffix">
<suffix name="AppMenu" label="Users opened a new incognito tab by app menu"/>
<suffix name="LongTapMenu"
label="Users opened a new incognito tab by long tap menu"/>
</action-suffix>
<action-suffix separator="." ordering="suffix">
<affected-action name="AnAction"/>
<affected-action name="MobileMenuCloseTab"/>
<suffix name="LongTapMenu" label="Users closed a tab by long tap menu"/>
</action-suffix>
</actions>
""".strip()
XML_WITH_WRONG_ATTRIBUTE_ORDER = """
<actions>
<action name="Action1">
<owner>owner@chromium.org</owner>
<description>Description1</description>
</action>
<action not_user_triggered="true" name="Action2">
<obsolete>Obsolete text</obsolete>
<owner>owner@chromium.org</owner>
<description>Description2</description>
</action>
<action name="Action3">
<obsolete>Obsolete text</obsolete>
<owner>owner@chromium.org</owner>
<owner>owner2@chromium.org</owner>
<description>Description3</description>
</action>
<action-suffix ordering="suffix" separator=".">
<suffix name="AppMenu" label="Users opened a new incognito tab by app menu"/>
<suffix name="LongTapMenu"
label="Users opened a new incognito tab by long tap menu"/>
</action-suffix>
<action-suffix separator="." ordering="suffix">
<suffix label="Users closed a tab by long tap menu" name="LongTapMenu"/>
<affected-action name="AnAction"/>
<affected-action name="MobileMenuCloseTab"/>
</action-suffix>
</actions>
""".strip()
class ActionXmlTest(unittest.TestCase):
def __init__(self, *args, **kwargs):
super(ActionXmlTest, self).__init__(*args, **kwargs)
self.maxDiff = None
@parameterized.expand([
# Test prettify already pretty XML to verify the pretty-printed version
# is the same.
('AlreadyPrettyXml', PRETTY_XML, PRETTY_XML),
('Indent', XML_WITH_WRONG_INDENT, PRETTY_XML),
('SingleLine', XML_WITH_WRONG_SINGLELINE, PRETTY_XML),
('LineBreak', XML_WITH_WRONG_LINE_BREAK, PRETTY_XML),
('Order', XML_WITH_WRONG_ORDER, PRETTY_XML),
# The children of <action> should be sorted in the order of <obsolete>,
# <owner> and <description>
('ChildrenOrder', XML_WITH_WRONG_CHILDREN_ORDER, PRETTY_XML),
])
def testPrettify(self, _, input_xml, expected_xml):
result = actions_model.PrettifyTree(xml.dom.minidom.parseString(input_xml))
self.assertMultiLineEqual(result.strip(), expected_xml)
@parameterized.expand([
('BadAttributeBoolean', PRETTY_XML, 'true', 'hello', 'hello'),
('BadSuffixNameWithSpace', PRETTY_XML, 'AppMenu', 'App Menu', 'App Menu'),
('BadAffectedActionNameWithSpace', PRETTY_XML, 'AnAction', 'An Action',
'An Action'),
('SuffixWithBadSeparator', PRETTY_XML, '.', '-', '-'),
('BadOrdering_IllegalWord', PRETTY_XML, 'ordering="suffix"',
'ordering="hello"', 'hello'),
])
def testRegex(self, _, pretty_input_xml, original_string, bad_string,
error_string):
BAD_XML = pretty_input_xml.replace(original_string, bad_string)
with self.assertRaises(ValueError) as context:
actions_model.PrettifyTree(xml.dom.minidom.parseString(BAD_XML))
self.assertIn(error_string, str(context.exception))
self.assertIn('does not match regex', str(context.exception))
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. The *_ATTRIBUTE_ORDER maps are also
# used to determine the validity of tag names.
# { tag_name: [attribute_name, ...] }
ATTRIBUTE_ORDER = {
'action': ['name', 'not_user_triggered'],
'action-suffix': ['separator', 'ordering'],
'actions': [],
'actions-suffixes': [],
'affected-action': ['name'],
'description': [],
'obsolete': [],
'owner': [],
'suffix': ['name', 'label'],
'with-suffix': ['name'],
}
# Attribute names that must be explicitly specified on nodes that support them.
REQUIRED_ATTRIBUTES = {
'action': ['name'],
'action-suffix': ['separator'],
'actions': [],
'actions-suffixes': [],
'affected-action': ['name'],
'description': [],
'obsolete': [],
'owner': [],
'suffix': ['name', 'label'],
'with-suffix': ['name'],
}
# 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),
'action-suffix': (1, 1, 1),
}
# Tags that we allow to be squished into a single line for brevity.
TAGS_THAT_ALLOW_SINGLE_LINE = ['obsolete', 'owner', 'description']
LOWERCASE_NAME_FN = lambda n: n.get('name').lower()
# Tags whose children we want to alphabetize. The key is the parent tag name,
# and the value is a list of pairs of tag name and key functions that maps each
# child node to the desired sort key.
TAGS_ALPHABETIZATION_RULES = {
'actions': [
('action', LOWERCASE_NAME_FN),
('action-suffix', lambda n: None),
],
'action-suffix': [
('suffix', LOWERCASE_NAME_FN),
('affected-action', LOWERCASE_NAME_FN),
],
}
def GetPrintStyle():
"""Returns an XmlStyle object for pretty printing actions."""
return pretty_print_xml.XmlStyle(ATTRIBUTE_ORDER,
REQUIRED_ATTRIBUTES,
TAGS_THAT_HAVE_EXTRA_NEWLINE,
TAGS_THAT_DONT_INDENT,
TAGS_THAT_ALLOW_SINGLE_LINE,
TAGS_ALPHABETIZATION_RULES)
......@@ -34,7 +34,7 @@ import sys
from xml.dom import minidom
import action_utils
import actions_print_style
import actions_model
# Import the metrics/common module for pretty print xml.
sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'common'))
......@@ -760,7 +760,7 @@ def PrettyPrint(actions_dict, comment_nodes, suffixes):
for suffix_tag in suffixes:
actions_element.appendChild(suffix_tag)
return actions_print_style.GetPrintStyle().PrettyPrintXml(doc)
return actions_model.PrettifyTree(doc)
def UpdateXml(original_xml):
......@@ -770,12 +770,7 @@ def UpdateXml(original_xml):
AddComputedActions(actions)
AddWebUIActions(actions)
AddDevToolsActions(actions)
AddLiteralActions(actions)
# print("Scanned {0} number of files".format(number_of_files_total))
# print("Found {0} entries".format(len(actions)))
AddAutomaticResetBannerActions(actions)
AddBookmarkManagerActions(actions)
AddChromeOSActions(actions)
......
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