#!/usr/bin/python
#
# Copyright (c) 2011 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.

""" Hierarchical property system for IDL AST """
import re
import sys

from idl_log import ErrOut, InfoOut, WarnOut
from idl_option import GetOption, Option, ParseOptions

#
# IDLPropertyNode
#
# A property node is a hierarchically aware system for mapping
# keys to values, such that a local dictionary is search first,
# followed by parent dictionaries in order.
#
class IDLPropertyNode(object):
  def __init__(self):
    self.parents = []
    self.property_map = {}

  def Error(self, msg):
    name = self.GetProperty('NAME', 'Unknown')
    parents = [parent.GetProperty('NAME', '???') for parent in self.parents]
    ErrOut.Log('%s [%s] : %s' % (name, ' '.join(parents), msg))

  def AddParent(self, parent):
    assert parent
    self.parents.append(parent)

  def SetProperty(self, name, val):
    self.property_map[name] = val

  def _GetProperty_(self, name):
    # Check locally for the property, and return it if found.
    prop = self.property_map.get(name, None)
    if prop is not None: return prop
    # If not, seach parents in order
    for parent in self.parents:
      prop = parent.GetProperty(name)
      if prop is not None: return prop
    # Otherwise, it can not be found.
    return None

  def GetProperty(self, name, default=None):
    prop = self._GetProperty_(name)
    if prop is None:
      return default
    else:
      return prop

  def GetPropertyLocal(self, name, default=None):
    # Search for the property, but only locally, returning the
    # default if not found.
    prop = self.property_map.get(name, default)
    return prop

  # Regular expression to parse property keys in a string such that a string
  #  "My string $NAME$" will find the key "NAME".
  regex_var = re.compile('(?P<src>[^\\$]+)|(?P<key>\\$\\w+\\$)')

  def GetPropertyList(self):
    return self.property_map.keys()

  # Recursively expands text keys in the form of $KEY$ with the value
  # of the property of the same name.  Since this is done recursively
  # one property can be defined in terms of another.
  def Replace(self, text):
    itr = IDLPropertyNode.regex_var.finditer(text)
    out = ''
    for m in itr:
      (start, stop) = m.span()
      if m.lastgroup == 'src':
        out += text[start:stop]
      if m.lastgroup == 'key':
        key = text[start+1:stop-1]
        val = self.GetProperty(key, None)
        if not val:
          self.Error('No property "%s"' % key)
        out += self.Replace(str(val))
    return out


#
# Testing functions
#

# Build a property node, setting the properties including a name, and
# associate the children with this new node.
#
def BuildNode(name, props, children=[], parents=[]):
  node = IDLPropertyNode()
  node.SetProperty('NAME', name)
  for prop in props:
    toks = prop.split('=')
    node.SetProperty(toks[0], toks[1])
  for child in children:
    child.AddParent(node)
  for parent in parents:
    node.AddParent(parent)
  return node

def ExpectProp(node, name, val):
  found = node.GetProperty(name)
  if found != val:
    ErrOut.Log('Got property %s expecting %s' % (found, val))
    return 1
  return 0

#
# Verify property inheritance
#
def PropertyTest():
  errors = 0
  left = BuildNode('Left', ['Left=Left'])
  right = BuildNode('Right', ['Right=Right'])
  top = BuildNode('Top', ['Left=Top', 'Right=Top'], [left, right])

  errors += ExpectProp(top, 'Left', 'Top')
  errors += ExpectProp(top, 'Right', 'Top')

  errors += ExpectProp(left, 'Left', 'Left')
  errors += ExpectProp(left, 'Right', 'Top')

  errors += ExpectProp(right, 'Left', 'Top')
  errors += ExpectProp(right, 'Right', 'Right')

  if not errors: InfoOut.Log('Passed PropertyTest')
  return errors


def ExpectText(node, text, val):
  found = node.Replace(text)
  if found != val:
    ErrOut.Log('Got replacement %s expecting %s' % (found, val))
    return 1
  return 0

#
# Verify text replacement
#
def ReplaceTest():
  errors = 0
  left = BuildNode('Left', ['Left=Left'])
  right = BuildNode('Right', ['Right=Right'])
  top = BuildNode('Top', ['Left=Top', 'Right=Top'], [left, right])

  errors += ExpectText(top, '$Left$', 'Top')
  errors += ExpectText(top, '$Right$', 'Top')

  errors += ExpectText(left, '$Left$', 'Left')
  errors += ExpectText(left, '$Right$', 'Top')

  errors += ExpectText(right, '$Left$', 'Top')
  errors += ExpectText(right, '$Right$', 'Right')

  if not errors: InfoOut.Log('Passed ReplaceTest')
  return errors


def MultiParentTest():
  errors = 0

  parent1 = BuildNode('parent1', ['PARENT1=parent1', 'TOPMOST=$TOP$'])
  parent2 = BuildNode('parent2', ['PARENT1=parent2', 'PARENT2=parent2'])
  child = BuildNode('child', ['CHILD=child'], parents=[parent1, parent2])
  BuildNode('top', ['TOP=top'], children=[parent1])

  errors += ExpectText(child, '$CHILD$', 'child')
  errors += ExpectText(child, '$PARENT1$', 'parent1')
  errors += ExpectText(child, '$PARENT2$', 'parent2')

  # Verify recursive resolution
  errors += ExpectText(child, '$TOPMOST$', 'top')

  if not errors: InfoOut.Log('Passed MultiParentTest')
  return errors

def Main():
  errors = 0
  errors += PropertyTest()
  errors += ReplaceTest()
  errors += MultiParentTest()

  if errors:
    ErrOut.Log('IDLNode failed with %d errors.' % errors)
    return  -1
  return 0

if __name__ == '__main__':
  sys.exit(Main())


Split property system into it's own file.