
Node
====

Short test on superclass.
::

    >>> from zope.interface.common.mapping import IFullMapping
    >>> from zodict import Zodict
    >>> zod = Zodict()
    >>> IFullMapping.providedBy(zod)
    True

Test BBB - deprecated usage
::

    >>> from zodict.zodict import zodict as BBBzodict
    >>> BBBzodict
    <class 'zodict._zodict.Zodict'>

We have a base node which provides the default interface implementation.
::

    >>> from zodict.interfaces import INode
    >>> from zodict import Node
    >>> root = Node('root')
    >>> root
    <Node object 'root' at ...>

    >>> INode.providedBy(root)
    True

Location related attributes.
::

    >>> root.__name__
    'root'
    
    >>> root.__parent__
    
    >>> root.path
    ['root']

Tree node index.
::

    >>> root.index
    <zodict.node.NodeIndex object at ...>
    
    >>> from zope.interface.common.mapping import IReadMapping
    >>> IReadMapping.providedBy(root.index)
    True
    
    >>> root.index[root.uuid]
    <Node object 'root' at ...>
    
    >>> root.index.get(root.uuid)
    <Node object 'root' at ...>
    
    >>> root.uuid in root.index
    True
    
    >>> len(root.index._index)
    1

Add some children and check node containment stuff.
::

    >>> root['child'] = Node()
    >>> root['child'].path
    ['root', 'child']
    
    >>> root.index._index is root['child'].index._index
    True
    
    >>> len(root.index._index)
    2
    
    >>> root['child']['subchild'] = Node()
    >>> root['child']['subchild'].path
    ['root', 'child', 'subchild']
    
    >>> len(root.index._index)
    3
  
    >>> root['child']['subchild2'] = Node()
    >>> root.keys()
    ['child']
    
    >>> len(root.index._index)
    4
    
    >>> root['child'].keys()
    ['subchild', 'subchild2']
    
    >>> root['child'].items()
    [('subchild', <Node object 'subchild' at ...>), 
    ('subchild2', <Node object 'subchild2' at ...>)]
    
    >>> child = root['child']
    >>> child.__name__
    'child'
  
    >>> child.__parent__
    <Node object 'root' at ...>
    
    >>> len(root['child'].keys())
    2
    
    >>> root.printtree()
    <class 'zodict.node.Node'>: root
      <class 'zodict.node.Node'>: child
        <class 'zodict.node.Node'>: subchild
        <class 'zodict.node.Node'>: subchild2

Node uses ``ILocation``, so attribute ``__name__`` is set. A python class object 
itself uses this attribute too, here it stores the class name. If you set a 
class derived from Node (others would be catched earlier) as a value in a Node 
you'd get odd effects, so we don't allow it!
::

    >>> class SomeClass(Node):
    ...     """for testing"""
    >>> root['aclasshere'] = SomeClass
    Traceback (most recent call last):
      ...
    ValueError: It isn't allowed to use classes as values.

Test filtereditems function.
::

    >>> from zope.interface import Interface
    >>> from zope.interface import alsoProvides
    >>> class IMarker(Interface): pass
    >>> alsoProvides(root['child']['subchild'], IMarker)
    >>> IMarker.providedBy(root['child']['subchild'])
    True
    
    >>> for item in root['child'].filtereditems(IMarker):
    ...     print item.path
    ['root', 'child', 'subchild']

Check UUID stuff.
::

    >>> len(root._index.keys())
    4
    
    >>> uuid = root['child']['subchild'].uuid
    >>> uuid
    UUID('...')
    
    >>> root.node(uuid).path
    ['root', 'child', 'subchild']
    
    >>> root.uuid = uuid
    Traceback (most recent call last):
      ...
    ValueError: Given uuid was already used for another Node
    
    >>> import uuid
    >>> newuuid = uuid.uuid4()
    
    >>> root.uuid = newuuid
    >>> root['child'].node(newuuid).path
    ['root']
    
    >>> len(root._index.keys())
    4

Store the uuids of the nodes which are expected to be deleted from index if
child is deleted.
::

    >>> delindexes = [
    ...     int(root['child'].uuid),
    ...     int(root['child']['subchild'].uuid),
    ...     int(root['child']['subchild2'].uuid),
    ... ]

Read the uuid index and check containment in index.
::

    >>> iuuids = root._index.keys()
    >>> len(iuuids)
    4
    
    >>> delindexes[0] in iuuids
    True
    
    >>> delindexes[1] in iuuids
    True
    
    >>> delindexes[2] in iuuids
    True

Delete child. All checked uuids above must be deleted from index.
::
  
    >>> del root['child']
    >>> root.keys()
    []
    
    >>> uuids = root._index.keys()
    >>> len(uuids)
    1
    
    >>> root.index[root.uuid] is root
    True
    
    >>> delindexes[0] in uuids
    False
    
    >>> delindexes[1] in uuids
    False
    
    >>> delindexes[2] in uuids
    False
    
    >>> root.printtree()
    <class 'zodict.node.Node'>: root

Node insertion. There exist an ``insertbefore`` and ``insertafter`` function.
::
  
    >>> root['child1'] = Node()
    >>> root['child2'] = Node()
    >>> root.printtree() 
    <class 'zodict.node.Node'>: root
      <class 'zodict.node.Node'>: child1
      <class 'zodict.node.Node'>: child2
    
    >>> node = Node()
    >>> root.insertbefore(node, root['child1'])
    Traceback (most recent call last):
      ...
    ValueError: Given node has no __name__ set.
    
    >>> root.insertbefore(root['child2'], root['child1'])
    Traceback (most recent call last):
      ...
    KeyError: u'Given node already contained in tree.'
    
    >>> node.__name__ = 'child3'
    >>> root.insertbefore(node, root['child2'])
    >>> root.printtree()
    <class 'zodict.node.Node'>: root
      <class 'zodict.node.Node'>: child1
      <class 'zodict.node.Node'>: child3
      <class 'zodict.node.Node'>: child2
    
    >>> node = Node('child4')
    >>> root.insertafter(node, root['child3'])
    >>> root.printtree()
    <class 'zodict.node.Node'>: root
      <class 'zodict.node.Node'>: child1
      <class 'zodict.node.Node'>: child3
      <class 'zodict.node.Node'>: child4
      <class 'zodict.node.Node'>: child2
    
    >>> node = Node('child5')
    >>> root.insertafter(node, root['child3'])
    >>> root.printtree()
    <class 'zodict.node.Node'>: root
      <class 'zodict.node.Node'>: child1
      <class 'zodict.node.Node'>: child3
      <class 'zodict.node.Node'>: child5
      <class 'zodict.node.Node'>: child4
      <class 'zodict.node.Node'>: child2

Move a node. Therefor we first need to detach the node we want to move from
tree. Then insert the detached node elsewhere. In general, you can insert the
detached node or subtree to a complete different tree.
::

    >>> len(root._index.keys())
    6
    
    >>> node = root.detach('child4')
    >>> node
    <Node object 'child4' at ...>
    
    >>> len(node._index.keys())
    1
    >>> len(root._index.keys())
    5
    
    >>> len(root.values())
    4
    
    >>> root.insertbefore(node, root['child1'])
    >>> root.printtree()
    <class 'zodict.node.Node'>: root
      <class 'zodict.node.Node'>: child4
      <class 'zodict.node.Node'>: child1
      <class 'zodict.node.Node'>: child3
      <class 'zodict.node.Node'>: child5
      <class 'zodict.node.Node'>: child2

Merge 2 Node Trees.
::

    >>> tree1 = Node()
    >>> tree1['a'] = Node()
    >>> tree1['b'] = Node()
    >>> tree2 = Node()
    >>> tree2['d'] = Node()
    >>> tree2['e'] = Node()
    >>> tree1._index is tree2._index
    False
  
    >>> len(tree1._index.keys())
    3
    
    >>> tree1.printtree()
    <class 'zodict.node.Node'>: None
      <class 'zodict.node.Node'>: a
      <class 'zodict.node.Node'>: b
    
    >>> len(tree2._index.keys())
    3
    
    >>> tree2.printtree()
    <class 'zodict.node.Node'>: None
      <class 'zodict.node.Node'>: d
      <class 'zodict.node.Node'>: e
    
    >>> tree1['c'] = tree2
    >>> len(tree1._index.keys())
    6
    
    >> sorted(tree1._index.values(), key=lambda x: x.__name__)
    
    >>> tree1._index is tree2._index
    True
    
    >>> tree1.printtree()
    <class 'zodict.node.Node'>: None
      <class 'zodict.node.Node'>: a
      <class 'zodict.node.Node'>: b
      <class 'zodict.node.Node'>: c
        <class 'zodict.node.Node'>: d
        <class 'zodict.node.Node'>: e

Detach subtree and insert elsewhere.
::

    >>> sub = tree1.detach('c')
    >>> sub.printtree()
    <class 'zodict.node.Node'>: c
      <class 'zodict.node.Node'>: d
      <class 'zodict.node.Node'>: e
    
    >>> tree1._index is sub._index
    False
    
    >>> sub._index is sub['d']._index is sub['e']._index
    True
    
    >>> len(sub._index.keys())
    3
    
    >>> tree1.printtree()
    <class 'zodict.node.Node'>: None
      <class 'zodict.node.Node'>: a
      <class 'zodict.node.Node'>: b
    
    >>> len(tree1._index.keys())
    3
    
    >>> sub.__name__ = 'x'
    >>> tree1.insertbefore(sub, tree1['a'])
    >>> tree1.printtree()
    <class 'zodict.node.Node'>: None
      <class 'zodict.node.Node'>: x
        <class 'zodict.node.Node'>: d
        <class 'zodict.node.Node'>: e
      <class 'zodict.node.Node'>: a
      <class 'zodict.node.Node'>: b
    
    >>> tree1._index is sub._index
    True
    
    >>> len(tree1._index.keys())
    6
    
    >>> tree1.insertbefore(sub, tree1['a'])
    Traceback (most recent call last):
      ...
    KeyError: u'Given node already contained in tree.'

Check Node attributes
::

    >>> from zodict.node import LifecycleNode # XXX: provide attributes on
    ...                                       #      base nodes as well
    
    >>> node = LifecycleNode()
    >>> node.attrs.foo = 'bar'
    >>> node.attrs['foo']
    'bar'
    
    >>> node.attrs['bar'] = 'baz'
    >>> node.attrs.bar
    'baz'

Check bool evaluation
::

    >>> node = Node()
    >>> node and True or False
    False
    
    >>> if not node:
    ...     print False
    False
    
    >>> node['1'] = Node()
    >>> node and True or False
    True
    
    >>> if node:
    ...     print True
    True