<?xml version="1.0"?>
<paste-with-annotations>
  <paste>
    <number>
      <integer>81455</integer>
    </number>
    <user>
      <string>sbp</string>
    </user>
    <title>
      <string>fieldtree.py</string>
    </title>
    <contents>
      <string>#!/usr/bin/env python
&quot;&quot;&quot;
fieldtree.py - Field Tree Object
Author: Sean B. Palmer, inamidst.com

The FieldTree object is a kind of OrderedDict. The name Field refers
to a label and value pair, usually called an item in Python.

There are two main extra features that a FieldTree provides over an
OrderedDict. The first is that when a field is set, it returns a Key
object. This provides a kind of permanent link to the field. The
second is that there are methods that allow for the access of any
FieldTree objects nested inside a FieldTree.

New FieldTree objects can be constructed without arguments:

   &gt;&gt;&gt; person = FieldTree()

You can add fields and keep the resulting keys:

   &gt;&gt;&gt; a = person.add('gender', 'male')
   &gt;&gt;&gt; b = person.add('name', 'John Smith')
   &gt;&gt;&gt; c = person.add('website', 'example.com')

Or you can add fields and discard the keys:

   &gt;&gt;&gt; person['phone'] = '123-456-7890'
   &gt;&gt;&gt; person['email'] = 'john@example.com'

You can then access fields by key or by label:

   &gt;&gt;&gt; person[b]
   'John Smith'
   &gt;&gt;&gt; person['website']
   'example.com'

The useful thing about keys is that you can use them to access fields
that you might then want to modify, and they'll be available after
those modifications. So if we make a template from a FieldTree:

   &gt;&gt;&gt; def format(person): 
   ...    args = person[b], person[a], person[c]
   ...    return 'Name: %s, Sex: %s, Website: %s' % args

This works in the expected way:

   &gt;&gt;&gt; format(person)
   'Name: John Smith, Sex: male, Website: example.com'

But then even if you start adding and modifying values:

   &gt;&gt;&gt; f = person.before(c, 'company', 'Acme Ltd.')
   &gt;&gt;&gt; person.changelabel(a, 'sex')

So that the data has a different label and a new order:

   &gt;&gt;&gt; for field in person.fields(): 
   ...    print field
   ... 
   ('sex', 'male')
   ('name', 'John Smith')
   ('company', 'Acme Ltd.')
   ('website', 'example.com')
   ('phone', '123-456-7890')
   ('email', 'john@example.com')

The template still works!

   &gt;&gt;&gt; format(person)
   'Name: John Smith, Sex: male, Website: example.com'

Now, let's say we want to use a FieldTree structure for the name so
that we can store the forename and surname separately. We'll make
this FieldTree by passing some field arguments:

   &gt;&gt;&gt; name = FieldTree(('forename', 'John'), ('surname', 'Smith'))

And then we can query the new object for its keys:

   &gt;&gt;&gt; g = name.get_key('forename')
   &gt;&gt;&gt; h = name.get_key('surname')

We can then change the existing value of the 'name' label:

   &gt;&gt;&gt; person['name'] = name

And because this is a field tree, we can use the keys we got from the
nested FieldTree to access its values from the parent tree:

   &gt;&gt;&gt; person[g]
   'John'
   &gt;&gt;&gt; person['name'][h]
   'Smith'

But of course, we can't use labels to access nested values:

   &gt;&gt;&gt; person['forename']
   Traceback (most recent call last):
      ...
   KeyError: &quot;no 'forename' label&quot;

Using labels in the regular way does work, though:

   &gt;&gt;&gt; person['name']['forename']
   'John'

Printing out all the fields iterates also over the nested fields, but
it skips any FieldTree values themselves:

   &gt;&gt;&gt; for field in person: 
   ...    print field
   ... 
   ('sex', 'male')
   ('forename', 'John')
   ('surname', 'Smith')
   ('company', 'Acme Ltd.')
   ('website', 'example.com')
   ('phone', '123-456-7890')
   ('email', 'john@example.com')

With FieldTree objects there is a general principle that you can use
labels only on the current FieldTree object, but you can use keys in
a nested way. So for example, you can check whether a FieldTree or
any of its nested FieldTree objects contains a key:

   &gt;&gt;&gt; a in person # gender/sex
   True
   &gt;&gt;&gt; g in person # forename
   True

But you can only check for labels in a non-nested way:

   &gt;&gt;&gt; 'sex' in person
   True
   &gt;&gt;&gt; 'name' in person
   True
   &gt;&gt;&gt; 'forename' in person
   False

You can iterate over all kinds of combinations of fields, labels,
keys, and values using the iteration methods:

   &gt;&gt;&gt; import itertools
   &gt;&gt;&gt; def show(iter, num): 
   ...    return list(itertools.islice(iter, num))

To, for example, note that keys are a subclass of int:

   &gt;&gt;&gt; show(person.keys(), 3)
   [Key(1), Key(2), Key(6)]

And that labels (but not keys) referring to FieldTree object values
are skipped, so that the following doesn't have 'name':

   &gt;&gt;&gt; show(person.tree_labels(), 3)
   ['sex', 'forename', 'surname']

Another feature of FieldTree objects is that you can set values that
don't have any labels. This means that you can only refer to this
value using its key:

   &gt;&gt;&gt; i = person.add('John Smith plays the trumpet')
   &gt;&gt;&gt; person[i]
   'John Smith plays the trumpet'
   &gt;&gt;&gt; person.get_field(i)
   (Empty(), 'John Smith plays the trumpet')

You can also delete values. You can't remove a whole field entirely,
but delete replaces the current value with an Empty() object:

   &gt;&gt;&gt; del person['phone']
   &gt;&gt;&gt; person['phone']
   Empty()

You can remove a field entirely, but it's not really recommended:

   &gt;&gt;&gt; example = FieldTree(('a', 'b'), ('p', 'q'))
   &gt;&gt;&gt; first = example.get_key('a')
   &gt;&gt;&gt; example.remove(first)
   &gt;&gt;&gt; len(example)
   1
   &gt;&gt;&gt; example['a']
   Traceback (most recent call last):
      ...
   KeyError: &quot;no 'a' label&quot;

There are some other convenience functions for looking up various
kinds of fields, keys, labels, and values:

   &gt;&gt;&gt; person.get_field(g)
   ('forename', 'John')
   &gt;&gt;&gt; person.get_label(g)
   'forename'
   &gt;&gt;&gt; person.get_value(g)
   'John'

&quot;&quot;&quot;

import itertools

class Key(int): 
   __counter = itertools.count(1)

   def __repr__(self): 
      return 'Key(%s)' % int(self)

   @staticmethod
   def next(): 
      num = Key.__counter.next()
      return Key(num)

class Empty(object): 
   def __repr__(self): 
      return 'Empty()'

class FieldTree(object): 
   def __init__(self, *args): 
      self.__fields = {} # {key: (label, value)}
      self.__values = {} # {label: (key, value)}
      self.__order = [] # [keys]
      self.__trees = set() # [field-tree-values]

      for label, value in args: 
         self[label] = value

   def __repr__(self): 
      return 'FieldTree%s' % (tuple(self.fields()),)

   def __len__(self): 
      return len(self.__order)

   def __contains__(self, obj): 
      if isinstance(obj, Key): 
         return self.has_tree_key(obj)
      else: return self.has_label(obj)

   def has_key(self, key): 
      return self.__fields.has_key(key)

   def has_tree_key(self, key): 
      for tree in [self] + list(self.__trees): 
         if tree.has_key(key): 
            return True
      return False

   def has_label(self, label): 
      return self.__values.has_key(label)

   def __iter__(self): 
      return self.tree_fields()

   def fields(self): 
      for key in self.__order: 
         yield self.__fields[key]

   def tree_fields(self): 
      for key in self.__order: 
         label, value = self.__fields[key]

         if isinstance(value, FieldTree): 
            for field in value.tree_fields(): 
               yield field
         else: yield label, value

   def keys(self): 
      for key in self.__order: 
         yield key

   def tree_keys(self): 
      for key in self.__order: 
         label, value = self.__fields[key]

         if isinstance(value, FieldTree): 
            for vkey in value.tree_keys(): 
               yield vkey
            else: yield key

   def labels(self): 
      for label, value in self.fields(): 
         yield label

   def tree_labels(self): 
      for label, value in self.tree_fields(): 
         yield label

   def values(self): 
      for label, value in self.fields(): 
         yield value

   def tree_values(self): 
      for label, value in self.tree_fields(): 
         yield value

   def __getitem__(self, item): 
      return self.get_value(item)

   def get_field(self, key): 
      &quot;key -&gt; field (deep)&quot;
      assert isinstance(key, Key)
      if self.has_key(key): 
         return self.__fields[key]
      for tree in self.__trees: 
         try: return tree.get_field(key)
         except KeyError: continue
      raise KeyError(&quot;no %r key&quot; % key)

   def get_key(self, label): 
      &quot;label -&gt; key (shallow)&quot;
      if self.has_label(label): 
         return self.__values[label][0]
      else: raise KeyError(&quot;no %r label&quot; % label)

   def get_label(self, key): 
      &quot;key -&gt; label (deep)&quot;
      return self.get_field(key)[0]

   def get_value(self, obj): 
      &quot;key -&gt; value (deep) or label -&gt; value (shallow)&quot;
      if isinstance(obj, Key): 
         return self.get_key_value(obj)
      else: return self.get_label_value(obj)

   def get_key_value(self, key): 
      &quot;key -&gt; value (deep)&quot;
      return self.get_field(key)[1]

   def get_label_value(self, label): 
      &quot;label -&gt; value (shallow)&quot;
      if self.has_label(label): 
         return self.__values[label][1]
      else: raise KeyError(&quot;no %r label&quot; % label)

   def __setitem__(self, obj, value): 
      if not (obj in self): 
         self.add(obj, value)
      else: self.changevalue(obj, value)

   def __arguments(self, args): 
      if len(args) == 1: 
         args = Empty(), args[0]
      elif len(args) != 2: 
         raise ValueError(&quot;Expected one or two args&quot;)

      if any(isinstance(arg, Key) for arg in args): 
         raise ValueError(&quot;FieldTrees can't contain Keys&quot;)
      return args

   def __set(self, args): 
      label, value = self.__arguments(args)

      if self.has_label(label): 
         key, oldvalue = self.__values[label]
         if isinstance(oldvalue, FieldTree): 
            self.__trees.remove(oldvalue)
      else: key = Key.next()

      self.__fields[key] = (label, value)
      if not isinstance(label, Empty): 
         self.__values[label] = (key, value)
      self.changed(key)
      return key

   def add(self, *args): 
      key = self.__set(args)
      self.__order.append(key)
      return key

   def update(self, args): 
      for label, value in args: 
         self[label] = value

   def before(self, next, *args): 
      if not self.has_key(next): 
         raise KeyError(&quot;no %r key&quot; % next)
      key = self.__set(args)

      index = self.__order.index(next)
      self.__order.insert(index, key)
      return key

   def __delitem__(self, obj): 
      self.delete(obj)

   def delete(self, obj): 
      self.changevalue(obj, Empty())

   def remove(self, key): 
      assert isinstance(key, Key)
      label, value = self.get_field(key)
      del self.__fields[key]
      del self.__values[label]
      self.__order.remove(key)
      if isinstance(value, FieldTree): 
         self.__trees.remove(value)
      self.changed(key)

   def changelabel(self, key, label): 
      if not self.has_key(key): 
         raise KeyError(&quot;no %r key&quot; % key)
      oldlabel, value = self.__fields[key]

      self.__fields[key] = (label, value)
      self.__values[label] = (key, value)
      self.changed(key)

   def changevalue(self, obj, value): 
      if isinstance(obj, Key) and self.has_key(obj): 
         key, (label, oldvalue) = obj, self.__fields[obj]
      elif self.has_label(obj): 
         label, (key, oldvalue) = obj, self.__values[obj]
      else: raise KeyError(&quot;%r&quot; % obj)

      self.__fields[key] = (label, value)
      self.__values[label] = (key, value)

      if isinstance(oldvalue, FieldTree): 
         self.__trees.remove(oldvalue)
      if isinstance(value, FieldTree): 
         self.__trees.add(value)
      self.changed(key)

   def changefield(self, key, label, value): 
      self.changelabel(key, label)
      self.changevalue(key, value)
      self.changed(key)

   def changed(self, *keys): 
      pass

def test(): 
   import doctest
   Documentation = type('Documentation', (object,), {'__doc__': __doc__})
   doctest.run_docstring_examples(Documentation, globals(), verbose=True)

def summary(): 
   import sys, StringIO
   stdout = sys.stdout
   sys.stdout = StringIO.StringIO()
   test()
   buffer = sys.stdout
   sys.stdout = stdout

   buffer.seek(0)
   success, failure = 0, 0
   for line in buffer: 
      if line.startswith('ok'): 
         success += 1
      elif line.startswith('Fail'): 
         failure += 1
   print &quot;%s/%s Tests Passed&quot; % (success, success + failure)

def main(): 
   summary()

if __name__ == '__main__': 
   main()
</string>
    </contents>
    <universal-time>
      <integer>3453291255</integer>
    </universal-time>
    <channel>
      <string>#swhack</string>
    </channel>
    <colorization-mode>
      <string>None</string>
    </colorization-mode>
    <maybe-spam>
      <null/>
    </maybe-spam>
    <is-unicode>
      <keyword>TRUE</keyword>
    </is-unicode>
    <deletion-requested>
      <null/>
    </deletion-requested>
    <deletion-requested-email>
      <null/>
    </deletion-requested-email>
    <expiration-time>
      <null/>
    </expiration-time>
  </paste>
</paste-with-annotations>
