# Copyright 2015 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. """A utility module for parsing and applying action suffixes in actions.xml. Note: There is a copy of this file used internally by the UMA processing infrastructure. Any changes to this file should also be done (manually) to the internal copy. Please contact tools/metrics/OWNERS for more details. """ class Error(Exception): pass class UndefinedActionItemError(Error): pass class InvalidOrderingAttributeError(Error): pass class SuffixNameEmptyError(Error): pass class InvalidAffecteddActionNameError(Error): pass class Action(object): """Represents Chrome user action. Attributes: name: name of the action. description: description of the action. owners: list of action owners not_user_triggered: if action is not user triggered obsolete: explanation on why user action is not being used anymore """ def __init__(self, name, description, owners, not_user_triggered=False, obsolete=None): self.name = name self.description = description self.owners = owners self.not_user_triggered = not_user_triggered self.obsolete = obsolete class Suffix(object): """Action suffix in actions.xml. Attributes: name: name of the suffix. description: description of the suffix. separator: the separator between affected action name and suffix name. ordering: 'suffix' or 'prefix'. if set to prefix, suffix name will be inserted after the first dot separator of affected action name. """ def __init__(self, name, description, separator, ordering): if not name: raise SuffixNameEmptyError('Suffix name cannot be empty.') if ordering != 'suffix' and ordering != 'prefix': raise InvalidOrderingAttributeError("Ordering has to be either 'prefix' " "or 'suffix'.") self.name = name self.description = description self.separator = separator self.ordering = ordering def __repr__(self): return '<%s, %s, %s, %s>' % (self.name, self.description, self.separator, self.ordering) def CreateActionsFromSuffixes(actions_dict, action_suffix_nodes): """Creates new actions from suffixes and adds them to actions_dict. Args: actions_dict: dict of existing action name to Action object. action_suffix_nodes: a list of action-suffix nodes Returns: A dictionary of action name to list of Suffix objects for that action. Raises: UndefinedActionItemError: if an affected action name can't be found """ action_to_suffixes_dict = _CreateActionToSuffixesDict(action_suffix_nodes) # Some actions in action_to_suffixes_dict keys may yet to be created. # Therefore, while new actions can be created and added to the existing # actions keep calling _CreateActionsFromSuffixes. while _CreateActionsFromSuffixes(actions_dict, action_to_suffixes_dict): pass # If action_to_suffixes_dict is not empty by the end, we have missing actions. if action_to_suffixes_dict: raise UndefinedActionItemError('Following actions are missing: %s.' %(action_to_suffixes_dict.keys())) def _CreateActionToSuffixesDict(action_suffix_nodes): """Creates a dict of action name to list of Suffix objects for that action. Args: action_suffix_nodes: a list of action-suffix nodes Returns: A dictionary of action name to list of Suffix objects for that action. """ action_to_suffixes_dict = {} for action_suffix_node in action_suffix_nodes: separator = _GetAttribute(action_suffix_node, 'separator', '_') ordering = _GetAttribute(action_suffix_node, 'ordering', 'suffix') suffixes = [Suffix(suffix_node.getAttribute('name'), suffix_node.getAttribute('label'), separator, ordering) for suffix_node in action_suffix_node.getElementsByTagName('suffix')] action_nodes = action_suffix_node.getElementsByTagName('affected-action') for action_node in action_nodes: action_name = action_node.getAttribute('name') # If has child nodes, only those suffixes # should be used with that action. filter the list of suffix names if so. action_suffix_names = [suffix_node.getAttribute('name') for suffix_node in action_node.getElementsByTagName('with-suffix')] if action_suffix_names: action_suffixes = [suffix for suffix in suffixes if suffix.name in action_suffix_names] else: action_suffixes = list(suffixes) if action_name in action_to_suffixes_dict: action_to_suffixes_dict[action_name] += action_suffixes else: action_to_suffixes_dict[action_name] = action_suffixes return action_to_suffixes_dict def _GetAttribute(node, attribute_name, default_value): """Returns the attribute's value or default_value if attribute doesn't exist. Args: node: an XML dom element. attribute_name: name of the attribute. default_value: default value to return if attribute doesn't exist. Returns: The value of the attribute or default_value if attribute doesn't exist. """ if node.hasAttribute(attribute_name): return node.getAttribute(attribute_name) else: return default_value def _CreateActionsFromSuffixes(actions_dict, action_to_suffixes_dict): """Creates new actions with action-suffix pairs and adds them to actions_dict. For every key (action name) in action_to_suffixes_dict, This function looks to see wether it exists in actions_dict. If so it combines the Action object from actions_dict with all the Suffix objects from action_to_suffixes_dict to create new Action objects. New Action objects are added to actions_dict and the action name is removed from action_to_suffixes_dict. Args: actions_dict: dict of existing action name to Action object. action_to_suffixes_dict: dict of action name to list of Suffix objects it will combine with. Returns: True if any new action was added, False otherwise. """ expanded_actions = set() for action_name, suffixes in action_to_suffixes_dict.iteritems(): if action_name in actions_dict: existing_action = actions_dict[action_name] for suffix in suffixes: _CreateActionFromSuffix(actions_dict, existing_action, suffix) expanded_actions.add(action_name) for action_name in expanded_actions: del action_to_suffixes_dict[action_name] return bool(expanded_actions) def _CreateActionFromSuffix(actions_dict, action, suffix): """Creates a new action with action and suffix and adds it to actions_dict. Args: actions_dict: dict of existing action name to Action object. action: an Action object to combine with suffix. suffix: a suffix object to combine with action. Returns: None. Raises: InvalidAffecteddActionNameError: if the action name does not contain a dot """ if suffix.ordering == 'suffix': new_action_name = action.name + suffix.separator + suffix.name else: (before, dot, after) = action.name.partition('.') if not after: raise InvalidAffecteddActionNameError("Action name '%s' must contain a " "'.'.", action.name) new_action_name = before + dot + suffix.name + suffix.separator + after new_action_description = action.description + ' ' + suffix.description actions_dict[new_action_name] = Action(new_action_name, new_action_description, list(action.owners), action.not_user_triggered, action.obsolete)