Home | History | Annotate | Download | only in histograms
      1 #!/usr/bin/env python
      2 # Copyright 2013 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """Pretty-prints the histograms.xml file, alphabetizing tags, wrapping text
      7 at 80 chars, enforcing standard attribute ordering, and standardizing
      8 indentation.
      9 
     10 This is quite a bit more complicated than just calling tree.toprettyxml();
     11 we need additional customization, like special attribute ordering in tags
     12 and wrapping text nodes, so we implement our own full custom XML pretty-printer.
     13 """
     14 
     15 from __future__ import with_statement
     16 
     17 import logging
     18 import os
     19 import shutil
     20 import sys
     21 import xml.dom.minidom
     22 
     23 import print_style
     24 
     25 sys.path.insert(1, os.path.join(sys.path[0], '..', '..', 'python'))
     26 from google import path_utils
     27 
     28 # Import the metrics/common module.
     29 sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'common'))
     30 import diff_util
     31 
     32 # Tags whose children we want to alphabetize. The key is the parent tag name,
     33 # and the value is a pair of the tag name of the children we want to sort,
     34 # and a key function that maps each child node to the desired sort key.
     35 ALPHABETIZATION_RULES = {
     36   'histograms': ('histogram', lambda n: n.attributes['name'].value.lower()),
     37   'enums': ('enum', lambda n: n.attributes['name'].value.lower()),
     38   'enum': ('int', lambda n: int(n.attributes['value'].value)),
     39   'histogram_suffixes_list': (
     40       'histogram_suffixes', lambda n: n.attributes['name'].value.lower()),
     41   'histogram_suffixes': ('affected-histogram',
     42                          lambda n: n.attributes['name'].value.lower()),
     43 }
     44 
     45 
     46 class Error(Exception):
     47   pass
     48 
     49 
     50 def unsafeAppendChild(parent, child):
     51   """Append child to parent's list of children, ignoring the possibility that it
     52   is already in another node's childNodes list.  Requires that the previous
     53   parent of child is discarded (to avoid non-tree DOM graphs).
     54   This can provide a significant speedup as O(n^2) operations are removed (in
     55   particular, each child insertion avoids the need to traverse the old parent's
     56   entire list of children)."""
     57   child.parentNode = None
     58   parent.appendChild(child)
     59   child.parentNode = parent
     60 
     61 
     62 def TransformByAlphabetizing(node):
     63   """Transform the given XML by alphabetizing specific node types according to
     64   the rules in ALPHABETIZATION_RULES.
     65 
     66   Args:
     67     node: The minidom node to transform.
     68 
     69   Returns:
     70     The minidom node, with children appropriately alphabetized. Note that the
     71     transformation is done in-place, i.e. the original minidom tree is modified
     72     directly.
     73   """
     74   if node.nodeType != xml.dom.minidom.Node.ELEMENT_NODE:
     75     for c in node.childNodes: TransformByAlphabetizing(c)
     76     return node
     77 
     78   # Element node with a tag name that we alphabetize the children of?
     79   if node.tagName in ALPHABETIZATION_RULES:
     80     # Put subnodes in a list of node,key pairs to allow for custom sorting.
     81     subtag, key_function = ALPHABETIZATION_RULES[node.tagName]
     82     subnodes = []
     83     sort_key = -1
     84     pending_node_indices = []
     85     for c in node.childNodes:
     86       if (c.nodeType == xml.dom.minidom.Node.ELEMENT_NODE and
     87           c.tagName == subtag):
     88         sort_key = key_function(c)
     89         # Replace sort keys for delayed nodes.
     90         for idx in pending_node_indices:
     91           subnodes[idx][1] = sort_key
     92         pending_node_indices = []
     93       else:
     94         # Subnodes that we don't want to rearrange use the next node's key,
     95         # so they stay in the same relative position.
     96         # Therefore we delay setting key until the next node is found.
     97         pending_node_indices.append(len(subnodes))
     98 
     99       subnodes.append( [c, sort_key] )
    100 
    101     # Use last sort key for trailing unknown nodes.
    102     for idx in pending_node_indices:
    103       subnodes[idx][1] = sort_key
    104 
    105     # Sort the subnode list.
    106     subnodes.sort(key=lambda pair: pair[1])
    107 
    108     # Re-add the subnodes, transforming each recursively.
    109     while node.firstChild:
    110       node.removeChild(node.firstChild)
    111     for (c, _) in subnodes:
    112       unsafeAppendChild(node, TransformByAlphabetizing(c))
    113     return node
    114 
    115   # Recursively handle other element nodes and other node types.
    116   for c in node.childNodes: TransformByAlphabetizing(c)
    117   return node
    118 
    119 
    120 def PrettyPrint(raw_xml):
    121   """Pretty-print the given XML.
    122 
    123   Args:
    124     raw_xml: The contents of the histograms XML file, as a string.
    125 
    126   Returns:
    127     The pretty-printed version.
    128   """
    129   tree = xml.dom.minidom.parseString(raw_xml)
    130   tree = TransformByAlphabetizing(tree)
    131   return print_style.GetPrintStyle().PrettyPrintNode(tree)
    132 
    133 
    134 def main():
    135   logging.basicConfig(level=logging.INFO)
    136 
    137   presubmit = ('--presubmit' in sys.argv)
    138 
    139   histograms_filename = 'histograms.xml'
    140   histograms_backup_filename = 'histograms.before.pretty-print.xml'
    141 
    142   # If there is a histograms.xml in the current working directory, use that.
    143   # Otherwise, use the one residing in the same directory as this script.
    144   histograms_dir = os.getcwd()
    145   if not os.path.isfile(os.path.join(histograms_dir, histograms_filename)):
    146     histograms_dir = path_utils.ScriptDir()
    147 
    148   histograms_pathname = os.path.join(histograms_dir, histograms_filename)
    149   histograms_backup_pathname = os.path.join(histograms_dir,
    150                                             histograms_backup_filename)
    151 
    152   logging.info('Loading %s...' % os.path.relpath(histograms_pathname))
    153   with open(histograms_pathname, 'rb') as f:
    154     xml = f.read()
    155 
    156   # Check there are no CR ('\r') characters in the file.
    157   if '\r' in xml:
    158     logging.info('DOS-style line endings (CR characters) detected - these are '
    159                  'not allowed. Please run dos2unix %s' % histograms_filename)
    160     sys.exit(1)
    161 
    162   logging.info('Pretty-printing...')
    163   try:
    164     pretty = PrettyPrint(xml)
    165   except Error:
    166     logging.error('Aborting parsing due to fatal errors.')
    167     sys.exit(1)
    168 
    169   if xml == pretty:
    170     logging.info('%s is correctly pretty-printed.' % histograms_filename)
    171     sys.exit(0)
    172   if presubmit:
    173     logging.info('%s is not formatted correctly; run pretty_print.py to fix.' %
    174                  histograms_filename)
    175     sys.exit(1)
    176   if not diff_util.PromptUserToAcceptDiff(
    177       xml, pretty,
    178       'Is the prettified version acceptable?'):
    179     logging.error('Aborting')
    180     return
    181 
    182   logging.info('Creating backup file %s' % histograms_backup_filename)
    183   shutil.move(histograms_pathname, histograms_backup_pathname)
    184 
    185   logging.info('Writing new %s file' % histograms_filename)
    186   with open(histograms_pathname, 'wb') as f:
    187     f.write(pretty)
    188 
    189 
    190 if __name__ == '__main__':
    191   main()
    192