text: #@+leo-ver=4
  #@+node:@file task.py
  #@@language python
  #@<< Copyright >>
  #@+node:<< Copyright >>
  ############################################################################
  #   Copyright (C) 2005, 2006, 2007, 2008 by Reithinger GmbH
  #   mreithinger@web.de
  #
  #   This file is part of faces.
  #
  #   faces is free software; you can redistribute it and/or modify
  #   it under the terms of the GNU General Public License as published by
  #   the Free Software Foundation; either version 2 of the License, or
  #   (at your option) any later version.
  #
  #   faces is distributed in the hope that it will be useful,
  #   but WITHOUT ANY WARRANTY; without even the implied warranty of
  #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  #   GNU General Public License for more details.
  #
  #   You should have received a copy of the GNU General Public License
  #   along with this program; if not, write to the
  #   Free Software Foundation, Inc.,
  #   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  ############################################################################
  
  #@-node:<< Copyright >>
  #@nl
  """
  This module contains all classes for project plan objects
  """
  #@<< Imports >>
  #@+node:<< Imports >>
  import pcalendar
  import resource
  import types
  import sys
  import datetime
  import operator as op
  import warnings
  import locale
  import weakref
  import opcode
  import new
  try:
      set
  except NameError:
      from sets import Set as set
  #@-node:<< Imports >>
  #@nl
  
  _is_source = True
  
  STRICT = 3
  SLOPPY = 2
  SMART = 1
  
  #@+others
  #@+node:Exceptions
  #@+node:class AttributeError
  class AttributeError(AttributeError):
      #@	<< class AttributeError declarations >>
      #@+node:<< class AttributeError declarations >>
      is_frozen = False
  
  
      #@-node:<< class AttributeError declarations >>
      #@nl
  #@-node:class AttributeError
  #@+node:class RecursionError
  class RecursionError(Exception):
      """This exception is raised in cas of cirular dependencies
      within an project"""
      #@	<< class RecursionError declarations >>
      #@+node:<< class RecursionError declarations >>
      pass
  
  
      #@-node:<< class RecursionError declarations >>
      #@nl
  #@-node:class RecursionError
  #@+node:class _IncompleteError
  class _IncompleteError(Exception):
      """This exception is raised, when there is not enough
      data specified to calculate as task"""
      #@	@+others
      #@+node:__init__
      def __init__(self, *args):
          if isinstance(args[0], (basestring)):
              Exception.__init__(self, *args)
          else:
              Exception.__init__(self,
                                 "Not enough data for calculating task, "\
                                 "maybe you have a recursive reference.",
                                 *args)
      #@-node:__init__
      #@-others
  #@-node:class _IncompleteError
  #@-node:Exceptions
  #@+node:Proxies for self referencing
  #@+node:class _MeProxy
  class _MeProxy(object):
      """
      A Proxy class for the me attribute of tasks in the compile case
      """
      #@	<< declarations >>
      #@+node:<< declarations >>
      __slots__ = "task"
  
      #@-node:<< declarations >>
      #@nl
      #@	@+others
      #@+node:__init__
      def __init__(self, task):
          object.__setattr__(self, "task", task)
      #@-node:__init__
      #@+node:__getattr__
      def __getattr__(self, name):
          if self.task._is_frozen:
              return getattr(self.task, name)
  
          if name in ("name", "up", "root", "path",
                      "depth", "index", "calendar",
                      "children", "resource", "balance"):
              return getattr(self.task, name)
  
          value = self.task.__dict__.get(name, _NEVER_USED_)
          def make_val(default):
              if value is _NEVER_USED_: return default
              return value
  
          if name in ("start", "end"):
              return self.task._to_start(make_val("1.1.2006"))
  
          if name in ("length", "effort", "duration", "todo", "done",
                      "buffer", "performed", "performed_effort",
                      "performed_end", "performed_start",
                      "performed_work_time" ):
              return self.task._to_delta(make_val("0d"))
  
          if name in ("complete", "priority", "efficiency"):
              return make_val(0)
  
          if value is _NEVER_USED_:
              raise AttributeError("'%s' is not a valid attribute." % (name))
  
          return value
      #@-node:__getattr__
      #@+node:__setattr__
      def __setattr__(self, name, value):
          self.task._set_attrib(name, value)
      #@-node:__setattr__
      #@+node:__iter__
      def __iter__(self):
          return iter(self.task)
      #@nonl
      #@-node:__iter__
      #@+node:add_attrib
      def add_attrib(self, name_or_iter, val=None):
          if not isinstance(name_or_iter, str):
              for n, v in name_or_iter:
                  setattr(self, n, v)
          else:
              setattr(self, name_or_iter, val)
      #@-node:add_attrib
      #@-others
  #@nonl
  #@-node:class _MeProxy
  #@+node:class _MeProxyRecalc
  class _MeProxyRecalc(_MeProxy):
      """
      A Proxy class for the me attribute of tasks in the recalc case
      """
      #@	@+others
      #@+node:__setattr__
      def __setattr__(self, name, value):
          if self.task._properties.has_key(name):
              self.task._set_attrib(name, value)
      #@-node:__setattr__
      #@-others
  #@-node:class _MeProxyRecalc
  #@+node:class _MeProxyError
  class _MeProxyError(_MeProxy):
      #@	<< declarations >>
      #@+node:<< declarations >>
      __slots__ = ("task", "attrib", "exc")
  
      #@-node:<< declarations >>
      #@nl
      #@	@+others
      #@+node:__init__
      def __init__(self, task, attrib, exc):
          _MeProxy.__init__(self, task)
          object.__setattr__(self, "attrib", attrib)
          object.__setattr__(self, "exc", exc)
      #@-node:__init__
      #@+node:__setattr__
      def __setattr__(self, name, value):
          if name == self.attrib or not self.attrib:
              raise self.exc
      #@-node:__setattr__
      #@-others
  #@-node:class _MeProxyError
  #@+node:class _MeProxyWarn
  class _MeProxyWarn(_MeProxy):
      #@	<< declarations >>
      #@+node:<< declarations >>
      __slots__ = ("task", "attrib", "message")
  
      #@-node:<< declarations >>
      #@nl
      #@	@+others
      #@+node:__init__
      def __init__(self, task, attrib, message):
          _MeProxy.__init__(self, task)
          object.__setattr__(self, "attrib", attrib)
          object.__setattr__(self, "message", message)
      #@-node:__init__
      #@+node:__setattr__
      def __setattr__(self, name, value):
          if name == self.attrib or not self.attrib:
              warnings.warn(self.message, RuntimeWarning, 2)
              if not self.attrib:
                  #warn only one time!
                  object.__setattr__(self, "attrib", 1)
      #@-node:__setattr__
      #@-others
  #@-node:class _MeProxyWarn
  #@-node:Proxies for self referencing
  #@+node:Task instrumentation
  #@+doc
  # This section contains code for byte code instrumenting
  # the task functions
  #@-doc
  #@nonl
  #@+node:_int_to_arg
  def _int_to_arg(value):
      return value % 256, value / 256
  #@-node:_int_to_arg
  #@+node:_correct_labels
  def _correct_labels(old_code, new_code):
      #@    << localize dot variables >>
      #@+node:<< localize dot variables >>
      hasjrel = opcode.hasjrel
      hasjabs = opcode.hasjabs
      HAVE_ARGUMENT = opcode.HAVE_ARGUMENT
      #@nonl
      #@-node:<< localize dot variables >>
      #@nl
      #@    << loop initialization >>
      #@+node:<< loop initialization >>
      labels = {}
      old_new_map = {} # map old code offset to new code offset
      n = len(old_code)
      i = 0
      j = 0
      #@nonl
      #@-node:<< loop initialization >>
      #@nl
      while i < n:
          op = old_code[i]
          nop = new_code[j]
          old_new_map[i] = j
          i = i + 1
          j = j + 1
          if op >= HAVE_ARGUMENT:
              oparg = old_code[i] + old_code[i + 1] * 256
              i = i + 2
              j = j + 2
              if nop != op:
                  j += 3 # skip the 3 addition opcodes for attrib access
              else:
                  #@                << add label if necessary >>
                  #@+node:<< add label if necessary >>
                  label = -1
                  if op in hasjrel:
                      label = i + oparg
                  elif op in hasjabs:
                      label = oparg
                  if label >= 0:
                      labels[i] = label
                  #@nonl
                  #@-node:<< add label if necessary >>
                  #@nl
  
      for offset, label in labels.iteritems():
          new_offset = old_new_map[offset]
          new_label = old_new_map[label]
          op = new_code[new_offset - 3]
          #change jump arguments
          if op in hasjrel:
              jump = _int_to_arg(new_label - new_offset)
              new_code[new_offset - 2:new_offset] = jump
          elif op in hasjabs:
              new_code[new_offset - 2:new_offset] = _int_to_arg(new_label)
  #@nonl
  #@-node:_correct_labels
  #@+node:_instrument
  def _instrument(func):
      #@    << localize dot variables >>
      #@+node:<< localize dot variables >>
      opname = opcode.opname
      opmap = opcode.opmap
      jumps = opcode.hasjrel + opcode.hasjabs
      HAVE_ARGUMENT = opcode.HAVE_ARGUMENT
      co = func.func_code
      local_names = co.co_varnames
      all_names = list(co.co_names)
      global_names = set()
      #@-node:<< localize dot variables >>
      #@nl
      #@    << define local functions list_to_dict and is_local >>
      #@+node:<< define local functions list_to_dict and is_local >>
      def list_to_dict(l):
          return dict([(t[1], t[0]) for t in enumerate(l)])
  
      def is_local(name):
          return name[0] == "_" and name != "__constraint__"
      #@nonl
      #@-node:<< define local functions list_to_dict and is_local >>
      #@nl
  
      #convert code
      #@    << loop initialization >>
      #@+node:<< loop initialization >>
      # all_name_map maps names to the all_names index
      # (same like all_names.index())
      all_name_map = list_to_dict(all_names)
      if not all_name_map.has_key("me"):
          all_name_map["me"] = len(all_names)
          all_names.append("me")
  
      #<python 2.5>
      for ln in local_names:
          if not all_name_map.has_key(ln):
              all_name_map[ln] = len(all_names)
              all_names.append(ln)
      #</python 2.5>
  
      new_local_names = filter(is_local, local_names)
      new_local_name_map = list_to_dict(new_local_names)
  
      me_arg = _int_to_arg(all_name_map["me"])
      old_lnotab = map(ord, co.co_lnotab)
      new_lnotab = []
      tab_pos = 0
      try:
          next_tab_point = old_lnotab[0]
      except IndexError:
          next_tab_point = None
  
      last_tab_point = 0
      code = map(ord, co.co_code)
      new_code = []
      has_labels = False
      n = len(code)
      i = 0
      #@nonl
      #@-node:<< loop initialization >>
      #@nl
      while i < n:
          if i == next_tab_point:
              #@            << calculate new tab point >>
              #@+node:<< calculate new tab point >>
              increment = len(new_code) - last_tab_point
              new_lnotab.extend((increment, old_lnotab[tab_pos + 1]))
              tab_pos += 2
              try:
                  next_tab_point = i + old_lnotab[tab_pos]
                  last_tab_point = len(new_code)
              except IndexError:
                  next_tab_point = -1
              #@nonl
              #@-node:<< calculate new tab point >>
              #@nl
  
          op = code[i]
          i += 1
          if op >= HAVE_ARGUMENT:
              #@            << calculate argument >>
              #@+node:<< calculate argument >>
              arg0 = code[i]
              arg1 = code[i+1]
              oparg = arg0 + arg1 * 256
              #@nonl
              #@-node:<< calculate argument >>
              #@nl
              i += 2
  
              if opname[op] == "LOAD_GLOBAL":
                  global_names.add(oparg)
  
              elif opname[op] == "STORE_FAST":
                  #@                << change "store fast" to "store attribute" >>
                  #@+node:<< change "store fast" to "store attribute" >>
                  name = local_names[oparg]
                  if not is_local(name):
                      new_code.append(opmap["LOAD_GLOBAL"])
                      new_code.extend(me_arg)
                      op = opmap["STORE_ATTR"]
                      arg0, arg1 = _int_to_arg(all_name_map[name])
                  else:
                      arg0, arg1 = _int_to_arg(new_local_name_map[name])
                  #@nonl
                  #@-node:<< change "store fast" to "store attribute" >>
                  #@nl
  
              elif opname[op] == "LOAD_FAST":
                  #@                << change "load fast" to "load attribute" >>
                  #@+node:<< change "load fast" to "load attribute" >>
                  name = local_names[oparg]
                  if not is_local(name):
                      new_code.append(opmap["LOAD_GLOBAL"])
                      new_code.extend(me_arg)
                      op = opmap["LOAD_ATTR"]
                      arg0, arg1 = _int_to_arg(all_name_map[name])
                  else:
                      arg0, arg1 = _int_to_arg(new_local_name_map[name])
                  #@nonl
                  #@-node:<< change "load fast" to "load attribute" >>
                  #@nl
  
              elif op in jumps:
                  has_labels = True
  
              new_code.extend((op, arg0, arg1))
          else:
              new_code.append(op)
  
      if has_labels:
          _correct_labels(code, new_code)
  
      #@    << create new code and function objects and return >>
      #@+node:<< create new code and function objects and return >>
      new_code = "".join(map(chr, new_code))
      new_lnotab = "".join(map(chr, new_lnotab))
      new_co = new.code(co.co_argcount,
                        len(new_local_names),
                        max(co.co_stacksize, 2),
                        co.co_flags,
                        new_code,
                        co.co_consts,
                        tuple(all_names),
                        tuple(new_local_names),
                        co.co_filename,
                        co.co_name,
                        co.co_firstlineno,
                        new_lnotab,
                        co.co_freevars,
                        co.co_cellvars)
  
  
      func =  new.function(new_co,
                           func.func_globals,
                           func.func_name,
                           func.func_defaults,
                           func.func_closure)
      func.global_names = tuple([all_names[index] for index in global_names])
      return func
      #@nonl
      #@-node:<< create new code and function objects and return >>
      #@nl
  #@nonl
  #@-node:_instrument
  #@-node:Task instrumentation
  #@+node:Wrappers
  #@+node:class _Path
  class _Path(object):
      """
      This class represents an instrumented path, to
      a task. If it points to an attribute of a task, it
      not only returns the value of the attribute. You can also
      find out the source attribute (task and attribute name)
      of the value.
      """
      #@	@+others
      #@+node:__init__
      def __init__(self, task, path_str):
          self._task = task
          self._path_str = path_str
      #@-node:__init__
      #@+node:__getattr__
      def __getattr__(self, name):
          new = getattr(self._task, name)
          if isinstance(new, Task):
              return _Path(new, self._path_str + "." + name)
  
          return _ValueWrapper(new, [(self._task, name)])
      #@-node:__getattr__
      #@+node:__str__
      def __str__(self):
          return self._path_str
      #@-node:__str__
      #@+node:__iter__
      def __iter__(self):
          return iter(self._task)
      #@nonl
      #@-node:__iter__
      #@-others
  
  #@-node:class _Path
  #@+node:_val
  #helper functions for _ValueWrapper
  #----------------------------------
  
  def _val(val):
      if isinstance(val, _ValueWrapper):
          return val._value
  
      return val
  #@-node:_val
  #@+node:_ref
  def _ref(val):
      if isinstance(val, _ValueWrapper):
          return val._ref
  
      return []
  #@-node:_ref
  #@+node:_sref
  def _sref(val, ref):
      if isinstance(val, _ValueWrapper):
          val._ref = ref
  #@nonl
  #@-node:_sref
  #@+node:_refsum
  def _refsum(refs):
      return reduce(lambda a, b: a + b, refs, [])
  #@nonl
  #@-node:_refsum
  #@+node:class _ValueWrapper
  
  
  class _ValueWrapper(object):
      """
      This class represents a value, of a task attribute or
      a return value of a task method. It contains the value,
      and the supplier of that value
      """
      #@	@+others
      #@+node:__init__
      def __init__(self, value, ref):
          self._value = value
          self._ref = ref
      #@-node:__init__
      #@+node:unicode
      def unicode(self, *args):
          if isinstance(self._value, str):
              return unicode(self._value, *args)
  
          return unicode(self._value)
      #@nonl
      #@-node:unicode
      #@+node:_vw
      def _vw(self, operand, *args):
          refs = _refsum(map(_ref, args))
          vals = map(_val, args)
          result = operand(*vals)
          return self.__class__(result, refs)
      #@-node:_vw
      #@+node:_cmp
      def _cmp(self, operand, *args):
          refs = _refsum(map(_ref, args))
          vals = map(_val, args)
          result = operand(*vals)
          map(lambda a: _sref(a, refs), args)
          return result
      #@-node:_cmp
      #@+node:__getattr__
      def __getattr__(self, name):
          return getattr(self._value, name)
      #@-node:__getattr__
      #@+node:__getitem__
      def __getitem__(self, slice):
          return self.__class__(self._value[slice], self._ref)
      #@nonl
      #@-node:__getitem__
      #@+node:__str__
      def __str__(self): return str(self._value)
      #@-node:__str__
      #@+node:__unicode__
      def __unicode__(self): return unicode(self._value)
      #@nonl
      #@-node:__unicode__
      #@+node:__repr__
      def __repr__(self): return repr(self._value)
      #@-node:__repr__
      #@+node:__nonzero__
      def __nonzero__(self): return bool(self._value)
      #@-node:__nonzero__
      #@+node:__lt__
      def __lt__(self, other): return self._cmp(op.lt, self, other)
      #@-node:__lt__
      #@+node:__le__
      def __le__(self, other): return self._cmp(op.le, self, other)
      #@-node:__le__
      #@+node:__eq__
      def __eq__(self, other): return self._cmp(op.eq, self, other)
      #@-node:__eq__
      #@+node:__ne__
      def __ne__(self, other): return self._cmp(op.ne, self, other)
      #@-node:__ne__
      #@+node:__gt__
      def __gt__(self, other): return self._cmp(op.gt, self, other)
      #@-node:__gt__
      #@+node:__ge__
      def __ge__(self, other): return self._cmp(op.ge, self, other)
      #@-node:__ge__
      #@+node:__add__
      def __add__(self, other): return self._vw(op.add, self, other)
      #@nonl
      #@-node:__add__
      #@+node:__sub__
      def __sub__(self, other): return self._vw(op.sub, self, other)
      #@-node:__sub__
      #@+node:__mul__
      def __mul__(self, other): return self._vw(op.mul, self, other)
      #@-node:__mul__
      #@+node:__floordiv__
      def __floordiv__(self, other): return self._vw(op.floordiv, self, other)
      #@-node:__floordiv__
      #@+node:__mod__
      def __mod__(self, other): return self._vw(op.mod, self, other)
      #@-node:__mod__
      #@+node:__divmod__
      def __divmod__(self, other): return self._vw(op.divmod, self, other)
      #@-node:__divmod__
      #@+node:__pow__
      def __pow__(self, other): return self._vw(op.pow, self, other)
      #@-node:__pow__
      #@+node:__lshift__
      def __lshift__(self, other): return self._vw(op.lshift, self, other)
      #@-node:__lshift__
      #@+node:__rshift__
      def __rshift__(self, other): return self._vw(op.rshift, self, other)
      #@-node:__rshift__
      #@+node:__and__
      def __and__(self, other): return self._vw(op.and_, self, other)
      #@-node:__and__
      #@+node:__xor__
      def __xor__(self, other): return self._vw(op.xor, self, other)
      #@-node:__xor__
      #@+node:__or__
      def __or__(self, other): return self._vw(op.or_, self, other)
      #@-node:__or__
      #@+node:__div__
      def __div__(self, other): return self._vw(op.div, self, other)
      #@-node:__div__
      #@+node:__radd__
      def __radd__(self, other): return self._vw(op.add, other, self)
      #@-node:__radd__
      #@+node:__rsub__
      def __rsub__(self, other): return self._vw(op.sub, other, self)
      #@-node:__rsub__
      #@+node:__rmul__
      def __rmul__(self, other): return self._vw(op.mul, other, self)
      #@-node:__rmul__
      #@+node:__rdiv__
      def __rdiv__(self, other): return self._vw(op.div, other, self)
      #@-node:__rdiv__
      #@+node:__rtruediv__
      def __rtruediv__(self, other): return self._vw(op.truediv, other, self)
      #@-node:__rtruediv__
      #@+node:__rfloordiv__
      def __rfloordiv__(self, other): return self._vw(op.floordiv, other, self)
      #@-node:__rfloordiv__
      #@+node:__rmod__
      def __rmod__(self, other): return self._vw(op.mod, other, self)
      #@-node:__rmod__
      #@+node:__rdivmod__
      def __rdivmod__(self, other): return self._vw(op.divmod, other, self)
      #@-node:__rdivmod__
      #@+node:__rpow__
      def __rpow__(self, other): return self._vw(op.pow, other, self)
      #@-node:__rpow__
      #@+node:__rlshift__
      def __rlshift__(self, other): return self._vw(op.lshift, other, self)
      #@-node:__rlshift__
      #@+node:__rrshift__
      def __rrshift__(self, other): return self._vw(op.rshift, other, self)
      #@-node:__rrshift__
      #@+node:__rand__
      def __rand__(self, other): return self._vw(op.and_, other, self)
      #@-node:__rand__
      #@+node:__rxor__
      def __rxor__(self, other): return self._vw(op.xor, other, self)
      #@-node:__rxor__
      #@+node:__ror__
      def __ror__(self, other): return self._vw(op.or_, other, self)
      #@-node:__ror__
      #@+node:__int__
      def __int__(self): return int(self._value)
      #@-node:__int__
      #@+node:__long__
      def __long__(self): return long(self._value)
      #@-node:__long__
      #@+node:__float__
      def __float__(self): return float(self._value)
      #@-node:__float__
      #@+node:__len__
      def __len__(self): return len(self._value)
      #@-node:__len__
      #@+node:__iter__
      def __iter__(self): return iter(self._value)
      #@-node:__iter__
      #@+node:__hash__
      def __hash__(self): return hash(self._value)
      #@-node:__hash__
      #@-others
  #@-node:class _ValueWrapper
  #@-node:Wrappers
  #@+node:Utilities
  #@+node:class _NEVER_USED_
  class _NEVER_USED_:
      pass
  
  #@-node:class _NEVER_USED_
  #@+node:class _StringConverter
  class _StringConverter(object):
      """This class is a helper for the to_string mechanism
      of tasks"""
      #@	@+others
      #@+node:__init__
      def __init__(self, source, format=None):
          self.source = source
          self.format = format
      #@-node:__init__
      #@+node:__getitem__
      def __getitem__(self, format):
          return _StringConverter(self.source, format)
      #@-node:__getitem__
      #@+node:__getattr__
      def __getattr__(self, name):
          class StrWrapper(object):
              def __init__(self, value, name, source, format):
                  self._value = value
                  self.name = name
                  self.source = source
                  self.format = format
  
              def __call__(self, arg):
                  formatter = self.source.formatter(self.name,
                                                    arg,
                                                    self.format)
                  return formatter(self._value(arg))
  
          value = getattr(self.source, name)
          if callable(value):
              #for methods the wrapper has to
              return StrWrapper(value, name, self.source, self.format)
  
          formatter = self.source.formatter(name, format=self.format)
          return formatter(value)
      #@-node:__getattr__
      #@-others
  #@-node:class _StringConverter
  #@+node:Multi
  def Multi(val, **kwargs):
      """returns a directory for mutlivalued attributes"""
      return dict(_default=val, **kwargs)
  #@nonl
  #@-node:Multi
  #@+node:create_relative_path
  def create_relative_path(from_, to_):
      """
      creates a relative path from absolute path
      from_ to absolute path to_
      """
      from_ = from_.split(".")
      to_ = to_.split(".")
  
      for i, parts in enumerate(zip(from_, to_)):
          from_part, to_part = parts
          if from_part != to_part:
              break
  
      from_ = from_[i:]
      to_ = to_[i:]
      return "up." * len(from_) + ".".join(to_)
  #@nonl
  #@-node:create_relative_path
  #@+node:create_absolute_path
  def create_absolute_path(from_, to_):
      """
      creates a absolute path from absolute path
      from_ to relative path to_
      """
      from_ = from_.split(".")
      to_ = to_.split(".")
  
      for i, part in enumerate(to_):
          if part != "up":
              break
  
      from_ = from_[:-i]
      to_ = to_[i:]
      return "%s.%s" % (".".join(from_), ".".join(to_))
  
  
  #@-node:create_absolute_path
  #@+node:_split_path
  def _split_path(path):
      try:
          index = path.rindex(".")
          return path[:index], path[index + 1:]
      except:
          return path
  #@-node:_split_path
  #@+node:_to_datetime
  _to_datetime = pcalendar.to_datetime
  #@nonl
  #@-node:_to_datetime
  #@+node:_get_tasks_of_sources
  def _get_tasks_of_sources(task, attrib_filter="end,start,effort,length,duration"):
      #return all source tasks, this task is dependend on
  
      dep_tasks = {}
  
      while task:
          for dep in task._sources.values():
              for d in dep:
                  path, attrib = _split_path(d)
                  if attrib and attrib_filter.find(attrib) >= 0:
                      dep_tasks[path] = True
  
          task = task.up
  
      return dep_tasks.keys()
  #@-node:_get_tasks_of_sources
  #@+node:_build_balancing_list
  def _build_balancing_list(tasks):
      """
      Returns a specialy sorted list of tasks.
      If the tasks will allocate resources in the sorting order of that list
      correct balancing is ensured
      """
  
      # first sort the list for attributes
      index = 0
      balancing_list = [(-t.priority, t.balance, index, t) for index, t in enumerate(tasks)]
      balancing_list.sort()
  
      #print
      #for p, b, i, t  in balancing_list:
      #    print p, b, i, t.path
  
      balancing_list = [ t for p, b, i, t  in balancing_list ]
  
      #now correct the presorted list:
      #if task a is dependent on task b, b will be moved before a
  
      done_map = { }
      count = len(balancing_list)
      while len(done_map) < count:
          for i in range(count):
              to_inspect = balancing_list[i]
              if done_map.has_key(to_inspect):
                  continue
  
              done_map[to_inspect] = True
              break
          else:
              break
  
          #@        << define inspect_depends_on >>
          #@+node:<< define inspect_depends_on >>
          inspect_path = to_inspect.path + "."
          sources = _get_tasks_of_sources(to_inspect)
          sources = [ s + "." for s in sources
                      if not inspect_path.startswith(s) ]
  
          # the if in the later line ignores assignments like
          # like start = up.start (i.e. references to parents)
          # this will be handled in the second if of inspect_depends_on
          # and can cause errors otherwise
  
          def inspect_depends_on(task):
              cmp_path = task.path + "."
              for src in sources:
                  if cmp_path.startswith(src):
                      #task is a source of to_inspect
                      return True
  
              if inspect_path.startswith(cmp_path):
                  #to_inspect is a child of task
                  return True
  
              return False
          #@nonl
          #@-node:<< define inspect_depends_on >>
          #@nl
          for j in range(i + 1, count):
              check_task = balancing_list[j]
              if done_map.has_key(check_task):
                  continue
  
              if inspect_depends_on(check_task):
                  del balancing_list[j]
                  balancing_list.insert(i, check_task)
                  i += 1 # to_inspect is now at i + 1
  
  
      return balancing_list
  #@-node:_build_balancing_list
  #@+node:_as_string
  def _as_string(val):
      if isinstance(val, basestring):
          return '"""%s"""' % val.replace("\n", "\\n")
  
      if isinstance(val, pcalendar._WorkingDateBase):
          return '"%s"' % val.strftime("%Y-%m-%d %H:%M")
  
      if isinstance(val, datetime.datetime):
          return '"%s"' % val.strftime("%Y-%m-%d %H:%M")
  
      if isinstance(val, datetime.timedelta):
          return '"%id %iM"' % (val.days, val.seconds / 60)
  
      if isinstance(val, tuple):
          result = map(_as_string, val)
          return "(%s)" % ", ".join(result)
  
      if isinstance(val, list):
          result = map(_as_string, val)
          return "[%s]" % ", ".join(result)
  
      if isinstance(val, resource.Resource):
          return val._as_string()
  
      if isinstance(val, Task):
          return val.path
  
      return str(val)
  #@-node:_as_string
  #@+node:_step_tasks
  def _step_tasks(task):
      if isinstance(task, Task):
          yield task
  
      stack = [iter(task.children)]
      while stack:
          for task in stack[-1]:
              yield task
  
              if task.children:
                  stack.append(iter(task.children))
                  break
          else:
              stack.pop()
  #@-node:_step_tasks
  #@-node:Utilities
  #@+node:Cache
  instrumentation_cache = {}
  balancing_cache = {}
  
  def clear_cache():
      instrumentation_cache.clear()
      balancing_cache.clear()
  #@nonl
  #@-node:Cache
  #@+node:Resource Allocators
  #@+others
  #@+node:VariableLoad
  def VariableLoad(limit=0):
      """
      Allocates the resource with maximal possible load.
      If limit is given, a the load is at least limit or more.
      """
      try:
          balance = me.balance
      except NameError:
          balance = SLOPPY
  
      if balance != SLOPPY:
          raise RuntimeError("You may specify variable_load only with balance=SLOPPY")
  
      return -limit
  #@-node:VariableLoad
  #@+node:_calc_load
  def _calc_load(task, resource):
      #changed at the resource instance
      load = resource.__dict__.get("load")
      if load is not None: return load
  
      load = task.__dict__.get("load")
      if load is not None: return load
  
      #inherited by the task
      return min(task.load, task.max_load, resource.max_load or 100.0)
  #@-node:_calc_load
  #@+node:_calc_maxload
  def _calc_maxload(task, resource):
      #changed at the resource instance
      max_load = resource.__dict__.get("max_load")
      if max_load: return max_load
  
      #an explicit load can overwrite max_load
      load = max(resource.__dict__.get("load", 0),
                 task.__dict__.get("load"), 0)
  
      #change at the task
      max_load = task.__dict__.get("max_load")
      if max_load: return max(max_load, load)
  
      #inherited by the resource
      max_load = resource.max_load
      if max_load: return max(max_load, load)
  
      #inherited by the task
      return max(task.max_load, load)
  #@-node:_calc_maxload
  #@+node:class AllocationAlgorithm
  class AllocationAlgorithm(object):
      """This class is a base for resource allocation algorithms"""
      #@	@+others
      #@+node:test_allocation
      def test_allocation(self, task, resource):
          """This method simulates the allocation of a specific resource.
          It returns a list of values representing the state of the allocation.
          The task allocator calls test_allocation for every alternative resource.
          It compares the first items of all return lists, and allocates the
          resource with the minum first item value"""
          return (task.end, )
      #@-node:test_allocation
      #@+node:allocate
      def allocate(self, task, state):
          """This method eventually allocates a specific resource.
          State is the return list of test_allocation"""
          pass
      #@-node:allocate
      #@-others
  #@-node:class AllocationAlgorithm
  #@+node:class StrictAllocator
  class StrictAllocator(AllocationAlgorithm):
      """This class implements the STRICT resource allocation"""
      #@	@+others
      #@+node:_distribute_len_loads
      def _distribute_len_loads(self, task, resource, effort, length):
          # A special load calculation, if effort and length are given.
          # and the resources have a defined maxload, the load must be
          # individually calculated for each resource.
  
          # Formulars: r=resources, t=task
          #   effort = length * efficiency(t) * sum[load(r) * effiency(r)]
          #   ==> sum_load = sum[load(r) * effiency(r)]
          #                = effort / (length * efficiency(t))
          #
  
          sum_load = float(effort) / (task.efficiency * length)
  
          # algorithm:
          # The goal is to distribute the load (norm_load) equally
          # to all resources. If a resource has a max_load(r) < norm_load
          # the load of this resource will be max_load(r), and the other
          # resources will have another (higher) norm_load
  
          max_loads = map(lambda r: (_calc_maxload(task, r), r), resource)
          max_loads.sort()
  
          efficiency_sum = sum(map(lambda r: r.efficiency, resource))
          norm_load = sum_load / efficiency_sum
  
          loads = {}
          for max_load, r in max_loads[:-1]:
              if max_load < norm_load:
                  loads[r] = max_load
                  efficiency_sum -= r.efficiency
                  sum_load -= max_load * r.efficiency
                  norm_load = sum_load / efficiency_sum
              else:
                  loads[r] = norm_load
  
          max_load, r = max_loads[-1]
          loads[r] = norm_load
          return loads
      #@-node:_distribute_len_loads
      #@+node:test_allocation
      def test_allocation(self, task, resource):
          effort = task.__dict__.get("effort")
          to_start = task._to_start
          to_end = task._to_end
          to_delta = task._to_delta
  
          if task.performed_end:
              start = to_start(max(task.performed_end,
                                   task.root.calendar.now,
                                   task.start))
          else:
              start = task.start
              if task.root.has_actual_data and task.complete == 0:
                  start = max(start, to_start(task.root.calendar.now))
  
          base_start = to_start(task.performed_start or task.start)
          calc_load = lambda r: _calc_load(task, r)
          loads = map(lambda r: (r, calc_load(r)), resource)
  
          length = task.__dict__.get("length")
          duration = task.__dict__.get("duration")
          end = task.__dict__.get("end")
  
          #@    << correct length >>
          #@+node:<< correct length >>
          if length is not None:
              length = to_delta(max(length - (task.start - base_start), 0))
          #@nonl
          #@-node:<< correct length >>
          #@nl
          #@    << correct duration >>
          #@+node:<< correct duration >>
          if duration is not None:
              delta = task.start.to_datetime() - base_start.to_datetime()
              delta = to_delta(delta, True)
              duration = to_delta(max(duration - delta, 0), True)
          #@nonl
          #@-node:<< correct duration >>
          #@nl
          #@    << check end >>
          #@+node:<< check end >>
          if end is not None:
              length = end - start
              if length <= 0: return False
          #@nonl
          #@-node:<< check end >>
          #@nl
          #@    << correct effort and (re)calculate length >>
          #@+node:<< correct effort and (re)calculate length >>
          if effort is not None:
              effort -= task.performed_effort
              effort = to_delta(max(effort, 0))
              if effort <= 0: return False
  
              if length is not None:
                  #if length and effort is set, the load will be calculated
                  length = length or task.calendar.minimum_time_unit
                  loads = self._distribute_len_loads(task, resource,
                                                     effort, length)
                  def calc_load(res):
                      return loads[res]
              else:
                  #the length depends on the count of resources
                  factor = sum(map(lambda a: a[0].efficiency * a[1],
                                   loads)) * task.efficiency
                  length = effort / factor
          #@nonl
          #@-node:<< correct effort and (re)calculate length >>
          #@nl
          #@    << set adjust_date and delta >>
          #@+node:<< set adjust_date and delta >>
          if length is not None:
              adjust_date = lambda date: date
              delta = to_delta(length).round()
          else:
              assert(duration is not None)
              adjust_date = _to_datetime
              delta = datetime.timedelta(minutes=duration)
          #@nonl
          #@-node:<< set adjust_date and delta >>
          #@nl
  
          # find the earliest start date
          start, book_load\
                 = self.balance(task, start, delta, adjust_date,
                                calc_load, resource)
  
          end = to_end(start + delta)
          start = to_start(start)
  
          if effort is None:
              #length is frozen ==> a new effort will be calculated
              factor = sum(map(lambda a: a[1], loads))
              length = end - start
  
              effort = to_delta(length * factor\
                                + task.performed_effort).round()
  
          return (end, book_load), resource, calc_load, start, effort
      #@-node:test_allocation
      #@+node:allocate
      def allocate(self, task, state):
          # now really book the resource
          end_bl, resource, calc_load, start, effort = state
          end = end_bl[0]
          cal = task.root.calendar
          to_start = task._to_start
          to_end = task._to_end
          to_delta = task._to_delta
  
          task.start = task.performed_start \
                       and to_start(task.performed_start) \
                       or to_start(start)
  
          task.end = end
          task._unfreeze("length")
          task._unfreeze("duration")
          length = end - start
  
          for r in resource:
              book_load = calc_load(r)
              work_time = to_delta(length * book_load).round()
              r.book_task(task, start, end, book_load, work_time, False)
  
          #the following lines are important to be exactly at this
          #positions in that order:
          # done and todo are dependend on:
          #    - the existence of effort (if effort was set or not set)
          #    - book_task (they can only be calculated, if the task is booked)
          #    - booked_resource (to get the booked tasks)
          task.booked_resource = resource
          task.done = task.done
          task.todo = task.todo
          task.length = end - task.start
          task.effort = to_delta(effort + task.performed_effort)
      #@-node:allocate
      #@+node:balance
          #now effort exists always
  
  
      def balance(self, task, start, delta, adjust_date,
                  calc_load, resource):
          book_load = max(map(lambda r: r.get_load(task.start, task.scenario), resource))
          return start, book_load
      #@-node:balance
      #@-others
  #@-node:class StrictAllocator
  #@+node:class SmartAllocator
  
  
  class SmartAllocator(StrictAllocator):
      #@	@+others
      #@+node:balance
      def balance(self, task, start, delta, adjust_date,
                  calc_load, resource):
          #find the earliest start date, at which all
          #resources in the team are free
  
          cal = task.root.calendar
          to_start = task._to_start
          start = adjust_date(start)
          scenario = task.scenario
  
          while True:
              #we have finished, when all resources have the
              #same next free start date
              for r in resource:
                  max_load = _calc_maxload(task, r)
                  load = calc_load(r)
  
                  #find the next free time of the resource
                  s = r.find_free_time(start, delta, load, max_load, scenario)
                  if s != start:
                      s = to_start(s)
                      start = adjust_date(s)
                      break
              else:
                  #only one resource
                  break
  
          return start, 1.0
      #@-node:balance
      #@-others
  #@-node:class SmartAllocator
  #@+node:class SloppyAllocator
  
  
  class SloppyAllocator(AllocationAlgorithm):
      #@	@+others
      #@+node:test_allocation
      def test_allocation(self, task, resource):
          if task.__dict__.has_key("effort"):
              return self.test_allocation_effort(task, resource)
  
          return self.test_allocation_length(task, resource)
      #@-node:test_allocation
      #@+node:test_allocation_length
      def test_allocation_length(self, task, resource):
          #length is frozen ==> effort will be calculated
          to_start = task._to_start
          to_end = task._to_end
          to_delta = task._to_delta
  
          end = task.end
          if task.performed_end:
              start = to_start(max(task.performed_end,
                                   task.root.calendar.now,
                                   start))
          else:
              start = task.start
  
          base_start = to_start(task.performed_start or task.start)
          length = to_delta(max(task.length - (start - base_start), 0))
          sum_effort = 0
          intervals = []
          scenario = task.scenario
          for r in resource:
              date = start
              max_load = _calc_maxload(task, r)
              book_load = _calc_load(task, r)
  
              while date < end:
                  #find free time intervals and add them for booking
                  endi, load = r.end_of_booking_interval(date, task)
                  endi = min(endi, end)
                  endi = to_end(endi)
  
                  if book_load <= 0:
                      #variable book_load ==> calc the maxmimal possible book_load >= (the given book_load)
                      used_book_load = - book_load
                      diff_load = max_load - load
                      if diff_load and diff_load >= book_load:
                          used_book_load = diff_load
                      else:
                          used_book_load = max_load
                  else:
                      used_book_load = book_load
  
                  if max_load - load >= used_book_load:
                      intervals.append((r, used_book_load, date, endi))
                      sum_effort = (endi - date) * used_book_load
  
                  date = to_start(endi)
  
          return -sum_effort, end, resource, intervals
      #@-node:test_allocation_length
      #@+node:test_allocation_effort
      def test_allocation_effort(self, task, resource):
          #effort is frozen ==> length will be calculated
  
          to_start = task._to_start
          to_end = task._to_end
          to_delta = task._to_delta
  
          intervals = []
          effort = task.__dict__.get("effort")
  
          if task.performed_end:
              next_date = to_start(max(task.performed_end,
                                       task.root.calendar.now,
                                       task.start))
          else:
              next_date = task.start
              if task.root.has_actual_data and task.complete == 0:
                  next_date = max(next_date, to_start(task.root.calendar.now))
  
          #walks chronologicly through the booking
          #intervals of each resource, and reduces
          #the effort for each free interval
          #until it becomes 0
  
          alloc_effort = effort
          effort -= task.performed_effort
          while effort > 0:
              date = next_date
  
              interval_resource = []
              interval_end = to_start(sys.maxint)
              factor = 0
  
              for r in resource:
                  max_load = _calc_maxload(task, r)
                  book_load = _calc_load(task, r)
                  end, load = r.end_of_booking_interval(date, task)
                  interval_end = to_start(min(end, interval_end))
  
                  if book_load <= 0:
                      #variable book_load ==> calc the maxmimal possible book_load >= (the given book_load)
                      book_load = - book_load
                      diff_load = max_load - load
                      if diff_load and diff_load >= book_load:
                          book_load = diff_load
                      else:
                          book_load = max_load
  
                  if book_load + load <= max_load:
                      resource_factor = book_load * r.efficiency
                      interval_resource.append((r, book_load, resource_factor))
                      factor += resource_factor
  
  
  
              next_date = interval_end
              if factor:
                  factor *= task.efficiency
                  length = to_delta(effort / factor).round()
                  end = date + length
  
                  if interval_end >= end:
                      next_date = interval_end = end
                      effort = 0
                      book_end = end
                  else:
                      book_end = interval_end
                      length = book_end - date
                      minus_effort = length * factor
                      effort -= minus_effort
  
                  book_end = to_end(book_end)
                  intervals.append((date, book_end, length, interval_resource))
  
          return next_date, alloc_effort, resource, intervals
      #@-node:test_allocation_effort
      #@+node:allocate
      def allocate(self, task, state):
          if task.__dict__.has_key("effort"): self.allocate_effort(task, state)
          else: self.allocate_length(task, state)
      #@-node:allocate
      #@+node:allocate_length
      def allocate_length(self, task, state):
          # now really book the resource
          neg_sum_effort, end, resource, intervals = state
  
          cal = task.root.calendar
          to_start = task._to_start
          to_end = task._to_end
          to_delta = task._to_delta
  
          task.start = to_start(task.performed_start or task.start)
          task.end = to_end(end)
          task._unfreeze("length")
          task._unfreeze("duration")
  
          effort = 0
          for r, load, s, e in intervals:
              work_time = to_delta((e - s) * load).round()
              effort += work_time
              r.book_task(task, s, e, load, work_time, False)
  
          #see comment at StrictAllocator.allocate
          task.booked_resource = resource
          task.done = task.done
          task.todo = task.todo
          task.effort = to_delta(effort + task.performed_effort).round()
      #@-node:allocate_length
      #@+node:allocate_effort
      def allocate_effort(self, task, state):
          # now really book the resource
          end, effort, resource, intervals = state
          to_start = task._to_start
          to_end = task._to_end
          to_delta = task._to_delta
  
          task.start = task.performed_start \
                       and to_start(task.performed_start) \
                       or to_start(intervals[0][0])
          task.end = to_end(end)
          task._unfreeze("length")
          task._unfreeze("duration")
  
          for start, end, length, resources in intervals:
              for r, load, factor in resources:
                  work_time = to_delta(length * load)
                  r.book_task(task, start, end, load, work_time, False)
  
          task.booked_resource = resource
          task.done = task.done
          task.todo = task.todo
          task.effort = to_delta(effort)
          task.length = task.end - task.start
      #@-node:allocate_effort
      #@-others
  #@-node:class SloppyAllocator
  #@-others
  
  _smart_allocator = SmartAllocator()
  _sloppy_allocator = SloppyAllocator()
  _strict_allocator = StrictAllocator()
  _allocators = { SMART: _smart_allocator,
                  SLOPPY: _sloppy_allocator,
                  STRICT: _strict_allocator }
  
  _allocator_strings = { SMART: "SMART",
                         SLOPPY: "SLOPPY",
                         STRICT: "STRICT" }
  #@-node:Resource Allocators
  #@+node:Load Calculators
  #@+node:YearlyMax
  def YearlyMax(value):
      """
      Calculates a load parameter with a maximal yearly workload
      """
      #@    << calculate calendar and time_diff >>
      #@+node:<< calculate calendar and time_diff >>
      try:
          cal = me.calendar
      except NameError:
          cal = pcalendar._default_calendar
  
      time_diff = cal.Minutes(value)
      #@nonl
      #@-node:<< calculate calendar and time_diff >>
      #@nl
      return float(time_diff) / \
              (cal.working_days_per_year \
               * cal.working_hours_per_day \
               * 60)
  #@nonl
  #@-node:YearlyMax
  #@+node:WeeklyMax
  def WeeklyMax(value):
      """
      Calculates a load parameter with a maximal weekly workload
      """
      #@    << calculate calendar and time_diff >>
      #@+node:<< calculate calendar and time_diff >>
      try:
          cal = me.calendar
      except NameError:
          cal = pcalendar._default_calendar
  
      time_diff = cal.Minutes(value)
      #@nonl
      #@-node:<< calculate calendar and time_diff >>
      #@nl
      return float(time_diff) / \
              (cal.working_days_per_week \
               * cal.working_hours_per_day \
               * 60)
  
  #@-node:WeeklyMax
  #@+node:MonthlyMax
  def MonthlyMax(value):
      """
      Calculates a load parameter with a maximal monthly workload
      """
      #@    << calculate calendar and time_diff >>
      #@+node:<< calculate calendar and time_diff >>
      try:
          cal = me.calendar
      except NameError:
          cal = pcalendar._default_calendar
  
      time_diff = cal.Minutes(value)
      #@nonl
      #@-node:<< calculate calendar and time_diff >>
      #@nl
      return float(time_diff) / \
              (cal.working_days_per_month \
               * cal.working_hours_per_day \
               * 60)
  
  #@-node:MonthlyMax
  #@+node:DailyMax
  def DailyMax(value):
      """
      Calculates a load parameter with a maximal daily workload
      """
      #@    << calculate calendar and time_diff >>
      #@+node:<< calculate calendar and time_diff >>
      try:
          cal = me.calendar
      except NameError:
          cal = pcalendar._default_calendar
  
      time_diff = cal.Minutes(value)
      #@nonl
      #@-node:<< calculate calendar and time_diff >>
      #@nl
      return float(time_diff) / (cal.working_hours_per_day * 60)
  #@-node:DailyMax
  #@-node:Load Calculators
  #@+node:Task
  #@+node:class _TaskProperty
  class _TaskProperty(object):
      #@	@+others
      #@+node:__init__
      def __init__(self, method):
          self.method = method
      #@-node:__init__
      #@+node:__get__
      def __get__(self, instance, owner):
          if not instance:
              return None
  
          return instance._wrap_attrib(self.method)
      #@-node:__get__
      #@-others
  #@-node:class _TaskProperty
  #@+node:class _RoundingTaskProperty
  class _RoundingTaskProperty(object):
      #@	@+others
      #@+node:__init__
      def __init__(self, method, name):
          self.method = method
          self.name = name
      #@-node:__init__
      #@+node:__get__
      def __get__(self, instance, owner):
          if not instance:
              return None
  
          result = instance._wrap_attrib(self.method).round()
          if instance._is_frozen:
              #correct the attrib to the rounded value
              setattr(instance, self.name, result)
  
          return result
      #@-node:__get__
      #@-others
  #@-node:class _RoundingTaskProperty
  #@+node:class Task
  class Task(object):
      #@    << description >>
      #@+node:<< description >>
      """
      This class represents a single task in the project tree. A task
      can have other child tasks, or is a leaf of the tree. Resources
      will be allocated only to leafes. You will never create task
      objects by your self, they are created indirectly by Projects.
  
      @var root:
      Returns the root project task.
  
      @var up:
      Returns the parent task.
  
      @var title:
      Specifies an alternative more descriptive name for the task.
  
      @var start:
      The start date of the task. Valid values are expressions and
      strings specifing a datatime
  
      @var end:
      The end date of the task. Valid values are expressions and
      strings.
  
      @var effort:
      Specifies the effort needed to complete the task. Valid values
      are expressions and strings. (Todo: What happens, in case of
      specified performance data...)
  
  
      @var length:
      Specifies the time the task occupies the resources.  This is
      working time, not calendar time. 7d means 7 working days, not one
      week. Whether a day is considered a working day or not depends on
      the defined working hours and global vacations.
  
      @var duration:
      Specifies the time the task occupies the resources. This is
      calendar time, not working time. 7d means one week.
  
      @var buffer:
      Specifies the time a task can be delayed, without moving dependend
      milestones. A Task with a buffer S{<=} 0d is part of the critical
      chain.  This attribute is readonly.
  
      @var complete:
      Specifies what percentage of the task is already completed.
  
      @var todo:
      Specifies the effort, which needs to be done to complete a
      task. This is another (indirect) way to specify the ME{complete}
      attribute.
  
      @var done:
      Specifies the work effort, which has been already done. This
      attribute is readonly.
  
      @var estimated_effort:
      Specifies the estimated_effort given by setting the effort property.
  
      @var performed:
      Specifies a list of actual working times performed on the task.
      The format is: C{[ (resource, from, to, time), ... ]}
  
      @var performed_work_time:
      Specifies the sum of all working times. This attribute is
      readonly.
  
      @var performed_effort:
      Specifies the complete effort of all working times. This attribute is
      readonly.
  
      @var performed_start:
      The start date of the performed data.
  
      @var performed_end:
      The end date of the performed data.
  
      @var performed_resource:
      The resources who have already performed on the task. This attribute is readonly.
  
  
      @var balance:
      Specifies the resource allocation type. Possible values are
      CO{STRICT}, CO{SLOPPY}, CO{SMART}.
  
      @var resource:
      Specifies the possible resources, that may be allocated for the
      task.
  
      @var booked_resource:
      Specifies the allocated resources of a task. This attribute is
      readonly.
  
      @var load:
      Specifies the daily load of a resource for an allocation of the
      specified task. A load of 1.0 (default) means the resource is
      allocated for as many hours as specified by
      ME{working_hours_per_day}. A load of 0.5 means half that many
      hours.
  
      @var max_load:
      Specify the maximal allowed load sum of all simultaneously
      allocated tasks of a resource. A ME{max_load} of 1.0 (default)
      means the resource may be fully allocated. A ME{max_load} of 1.3
      means the resource may be allocated with 30% overtime.
  
      @var efficiency:
      The efficiency of a resource can be used for two purposes. First
      you can use it as a crude way to model a team. A team of 5 people
      should have an efficiency of 5.0. Keep in mind that you cannot
      track the member of the team individually if you use this
      feature. The other use is to model performance variations between
      your resources.
  
      @var milestone:
      Specified if the task is a milestone. The possible values are
      C{True} or "later". If the start date of the milestone is not
      a valid working date, the milestone will appear at the previous
      working date before the given start date. If "later" is specified
      the milestone will appear at the next valid working date.
      A milestone has always an effort of 0d.
  
      @var priority:
      Specifies a priority between 1 and 1000. A task with higher
      priority is more likely to get the requested resources.  The
      default priority is 500.
  
      @var children:
      Specifies a list of all subtasks. A task without children is
      called a leaf task index{leaf task} otherwise it is called a
      parent task index{parent task}. This attribute is readonly.
  
      @var depth:
      Specifies the depth of the task within the hierachy. This
      attribute is readonly.
  
      @var index:
      Specifies a structural index number. This attribute is readonly.
  
      @var path:
      Specifies the path.
  
      @var copy_src:
      Specifies the path to an other task.  When you set this attribute,
      all attributes (except of ME{start} and ME{end}) of copy_src will
      be copied to the current task. This is usefull if you want to
      define the same task, in diffent project definitions. It acts like
      a task link.
  
      @var scenario:
      The scenario which is currently evaluated. This attribute is readonly.
  
      @var dont_inherit:
      A list of attribute names, which will be not inherited by
      subtasks.
  
      @var calendar:
      Specifies the task calendar.
  
      @var working_days_per_week:
      Specifies the days within a working week. This value is used
      internally to convert time differences from weeks to days. The
      default value is 5 days.
  
      @var working_days_per_month:
      Specifies the days within a working month. This value is used
      internally to convert time differences from months to days. The
      default value is 20 days.
  
      @var working_days_per_year:
      Specifies the days within a working year. This value is used
      internally to convert time differences from years to days The
      default value is 200 days.
  
      @var working_hours_per_day:
      Specifies the hours within a working day. This value is used
      internally to convert time differences from are entered in days to
      hours.  The default value is 8 hours.
  
      @var minimum_time_unit:
      Specifies the minimum resolution in minutes for the task
      scheduling. The default value is 15 minutes.
  
      @var vacation:
      Specifies a public vacation for the calendar. This attribute is
      specified as a list of date literals or date literal intervals. Be
      aware that the end of an interval is excluded, i.e. it is the
      first working date.
  
      @var extra_work:
      Specifies additional worktime. This attribute is specified as a
      list of date literals or date literal intervals. Be aware that the
      end of an interval is excluded, i.e. it is the first working date.
  
      @var working_days:
      Specifies the weekly working time within calendar. The format of
      this attribute is: [ (day_range, time_range, ...), (day_range, time_range, ...), ... ].
      day_range is a comma sperated string of week days. Valid values
      are mon, tue, wed, thu, fri, sat, sun.
      time_range is string specifing a time interval like
      8:00-10:00. You can specified any number of time_ranges, following
      the first.
  
      @var now:
      Specifies the current daytime and is a date literal. ME{now} is
      used to calculate several task attributes.
  
      """
      #@nonl
      #@-node:<< description >>
      #@nl
      #@	<< declarations >>
      #@+node:<< declarations >>
      # Variables for the gui interface
      _date_completion = { "Date": 'Date("|")',
                           "max": "max(|)",
                           "min": "min(|)",
                           "Multi" : "Multi(|)" }
  
  
      _delta_completion = { "Delta" : 'Delta("|")',
                            "Multi" : "Multi(|)" }
  
  
      __attrib_completions__ = { \
          "def NewTask():" : "def |NewTask():\n",
          "milestone": 'milestone = True',
          "start": 'start = ',
          "end": 'end = ',
          "effort": 'effort = "|"',
          "duration": 'duration = "|"',
          "length": 'length = "|"',
          "todo": 'todo = "|"',
          "done": 'done = "|"',
          "title": 'title = "|"',
          "load": 'load = ',
          "max_load": 'max_load = ',
          "efficiency": 'efficiency = ',
          "complete": 'complete = ',
          "copy_src": 'copy_src =',
          "__constraint__": '__constraint__():\n|"',
          "priority": 'priority = ',
          "balance" : 'balance = ',
          "resource": 'resource = ',
          "performed"  : 'performed = [(|resource, "2002-02-01", "2002-02-05", "2H"),]',
          "add_attrib": "add_attrib(|'name', None)",
          "working_days_per_week": 'working_days_per_week = ',
          "working_days_per_month": 'working_days_per_month = ',
          "working_days_per_year": 'working_days_per_year = ',
          "working_hours_per_day": 'working_hours_per_day = ',
          "minimum_time_unit": 'minimum_time_unit = ',
          "vacation": 'vacation = [("|2002-02-01", "2002-02-05")]',
          "extra_work": 'extra_work = [("|2002-02-01", "2002-02-05")]',
          "working_days" : 'working_days = ["|mon,tue,wed,thu,fri", "8:00-12:00", "13:00-17:00"]',
          "now": 'now = "|"',
          "calendar" : 'calendar = ',
          "#load": { "YearlyMax": 'YearlyMax("|")',
                     "WeeklyMax": 'WeeklyMax("|")',
                     "MonthlyMax": 'MonthlyMax("|")',
                     "DailyMax": 'DailyMax("|")',
                     "VariableLoad" : "VariableLoad(|)"},
          "#max_load": { "YearlyMax": 'YearlyMax("|")',
                         "WeeklyMax": 'WeeklyMax("|")',
                         "MonthlyMax": 'MonthlyMax("|")',
                         "DailyMax": 'DailyMax("|")' },
          "#start": _date_completion,
          "#end": _date_completion,
          "#effort": _delta_completion,
          "#duration": _delta_completion,
          "#length": _delta_completion,
          "#todo": _delta_completion,
          "#done": _delta_completion,
          "#resource" : "get_resource_completions",
          "#calendar" : "get_calendar_completions",
          "#balance": { "STRICT": "STRICT",
                        "SMART": "SMART",
                        "SLOPPY": "SLOPPY" } }
  
  
      formats = { "start" : "%x %H:%M",
                  "end"  : "%x %H:%M",
                  "performed_start" : "%x %H:%M",
                  "performed_end" : "%x %H:%M",
                  "load" : "%.2f",
                  "length" : "%dd{ %HH}{ %MM}",
                  "effort" : "%dd{ %HH}{ %MM}",
                  "estimated_effort" : "%dd{ %HH}{ %MM}",
                  "performed_effort" : "%dd{ %HH}{ %MM}",
                  "duration" : "%dd{ %HH}{ %MM}",
                  "complete" : "%i",
                  "priority" : "%i",
                  "todo" : "%dd{ %HH}{ %MM}",
                  "done" : "%dd{ %HH}{ %MM}",
                  "efficiency" : "%.2f",
                  "buffer" : "%dd{ %HH}{ %MM}",
                  "costs" : "%.2f",
                  "sum" : "%.2f",
                  "max" : "%.2f",
                  "min" : "%.2f",
                  "milestone" : "%s",
                  "resource" : "%s",
                  "booked_resource" : "%s",
                  "performed_resource" : "%s" }
  
      _constraint = None
      _is_frozen = False
      _is_compiled = False
      _is_parent_referer = False
  
      scenario = None # only for autocompletion
      milestone = False
      performed = ()
      performed_resource = ()
      booked_resource = ()
      _performed_resource_length = ()
      _resource_length = ()
      dont_inherit = ()
      performed_start = None
      performed_end = None
      performed_work_time = pcalendar.Minutes(0)
  
      _setting_hooks = {}
      #@nonl
      #@-node:<< declarations >>
      #@nl
      #@	@+others
      #@+node:__init__
      def __init__(self, func, name, parent=None, index=1):
  
          assert(type(func) == types.FunctionType)
  
          func_key = (func.func_code, func.func_closure and id(func.func_closure))
  
          try:
              instrumented = instrumentation_cache[func_key]
          except KeyError:
              instrumented = _instrument(func)
              instrumented.org_code = func_key
              instrumentation_cache[func_key] = instrumented
  
          func.task_func = instrumented # will be used in the gui
          self._function = instrumented
          self.name = name
          self.up = parent
          self.children = []
          self._sources = {} # all tasks, I am linked to
          self._dependencies = {} # all tasks that link to me
          self._original_values = {}
          self._properties = {} # a registry of all non standard attributes
          self.title = self.name
          self.root = parent and parent.root or self
          self.scenario = self.root.scenario
          self.path = parent and parent.path + "." + name or name
          self.depth = len(self.path.split(".")) - 1
          self.index = parent and ("%s.%i" % (parent.index, index)) \
                       or str(index)
          if self.formats.has_key(name):
              raise AttributeError("Task name '%s' hides attribute of parent." \
                                   % name)
  
          cal = self.calendar
          self._to_delta = cal.Minutes
          self._to_start = cal.StartDate
          self._to_end = cal.EndDate
  
      #@-node:__init__
      #@+node:__iter__
      def __iter__(self):
          return _step_tasks(self)
      #@-node:__iter__
      #@+node:__repr__
      def __repr__(self):
          return "<Task %s>" % self.name
      #@-node:__repr__
      #@+node:__cmp__
      def __cmp__(self, other):
          try:
              return cmp(self.path, other.path)
          except Exception:
              return cmp(self.path, other)
      #@-node:__cmp__
      #@+node:__getattr__
      def __getattr__(self, name):
          try:
              if name[0] != "_":
                  parent = self.up
                  while parent:
                      if name not in parent.dont_inherit:
                          result = getattr(parent, name)
                          if not (isinstance(result, Task) and result.up == parent):
                              return result
  
                      parent = parent.up
          except AttributeError:
              pass
          except IndexError:
              raise AttributeError()
  
          exception = AttributeError("'%s' is not a valid attribute of '%s'."
                                     % (name, self.path))
          exception.is_frozen = self._is_frozen
          raise exception
      #@-node:__getattr__
      #@+node:_idendity_
      def _idendity_(self): return self.root.id + self.path[4:]
      #@-node:_idendity_
      #@+node:_set_hook
      def _set_hook(cls, attrib_name, function=None):
          if function:
              cls._setting_hooks[attrib_name] = function
          else:
              try:
                  del cls._setting_hooks[attrib_name]
              except KeyError: pass
  
  
      _set_hook = classmethod(_set_hook)
      #@nonl
      #@-node:_set_hook
      #@+node:Public methods
      #@+node:to_string
      def to_string(self): return _StringConverter(self)
      to_string = property(to_string)
      #@nonl
      #@-node:to_string
      #@+node:indent_name
      def indent_name(self, ident="    "):
          """
          returns a indented name, according to its depth in the hierachy.
          """
  
          return ident * self.depth + self.name
  
      indent_name.attrib_method = True
      indent_name.__call_completion__ = "indent_name()"
      #@-node:indent_name
      #@+node:costs
      def costs(self, cost_name, mode="ep"):
          """
          calculates the resource costs for the task.
          cost_name is the name of a rate attribute of the reosurce
          mode is character combination:
                    e calculates the estimated costs
                    p calculates the performed costs
                    ==> pe calculates all costs
          """
  
          if self.children:
              return sum([ c.costs(cost_name, mode) for c in self.children])
  
          costs = 0
          if 'e' in mode:
              costs += sum(map(lambda rl: getattr(rl[0], cost_name) * rl[1],
                               self._resource_length))
  
          if 'p' in mode:
              costs += sum(map(lambda rl: getattr(rl[0], cost_name) * rl[1],
                               self._performed_resource_length))
  
          costs /= (60.0 * self.root.calendar.working_hours_per_day)
          return round(costs, 2)
  
      costs.attrib_method = True
      costs.__call_completion__ = 'costs("|")'
      #@-node:costs
      #@+node:sum
      def sum(self, attrib_name):
          val = 0
  
          if self.children:
              val += sum(map(lambda c: c.sum(attrib_name), self.children))
              if self.is_inherited(attrib_name):
                  return val
  
              if attrib_name not in self.dont_inherit:
                  return val
  
          return val + getattr(self, attrib_name)
  
      sum.attrib_method = True
      sum.__call_completion__ = 'sum("|")'
  
      #@-node:sum
      #@+node:min
      def min(self, attrib_name):
          if self.children:
              return min(map(lambda c: c.min(attrib_name), self.children))
  
          return getattr(self, attrib_name)
  
      min.attrib_method = True
      min.__call_completion__ = 'min("|")'
  
      #@-node:min
      #@+node:max
      def max(self, attrib_name):
          if self.children:
              return max(map(lambda c: c.max(attrib_name), self.children))
  
          return getattr(self, attrib_name)
  
      max.attrib_method = True
      max.__call_completion__ = 'max("|")'
  
      #@-node:max
      #@+node:all_resources
      def all_resources(self):
          result = self._all_resources_as_dict()
          result = result.keys()
          result.sort()
          return result
      #@-node:all_resources
      #@+node:get_task
      def get_task(self, path=None):
          """
          Returns a task with the given path.
          """
  
          if not path:
              return self
  
          names = path.split(".")
          rest = ".".join(names[1:])
          result = getattr(self, names[0], None)
          return isinstance(result, Task) and result.get_task(rest) or None
      #@-node:get_task
      #@+node:snapshot
      def snapshot(self, indent="", name=None):
          text = indent + "def %s():\n" % (name or self.name)
          indent += "    "
          for name in ("priority", "balance", "complete",
                       "milestone", "end", "start", "effort", "load"):
              val = getattr(self, name, None)
              if val is None:
                  continue
  
              if name[0] == "_":
                  name = name[1:]
  
              text += "%s%s = %s\n" % (indent, name, _as_string(val))
  
          for name in self._properties:
              if name.startswith("performed"): continue
              val = getattr(self, name, None)
              try:
                  if issubclass(val, resource.Resource): continue
              except TypeError:
                  pass
              text += "%s%s = %s\n" % (indent, name, _as_string(val))
  
          resources = tuple(self._iter_booked_resources())
          if resources:
              text += "%sresource = \\\n" % indent
              def make_resource(res):
                  return "%s    %s" \
                         % (indent, res.snapshot())
  
              text += "&\\\n".join(map(make_resource, resources)) + "\n"
  
              def make_resource_booking(res):
                  def make_booking(booking):
                      return '%s    (%s, "%s", "%s", "%sM"),' \
                             % (indent, res.name,
                                booking.book_start.strftime("%Y%m%d %H:%M"),
                                booking.book_end.strftime("%Y%m%d %H:%M"),
                                booking.work_time)
  
                  return "\n".join(map(make_booking, res.get_bookings(self)))
  
  
              text += "%sperformed = [\n" % indent
              text += "\n".join(map(make_resource_booking, resources)) + "]"
  
  
          child_text = map(lambda c: c.snapshot(indent), self.children)
          text += "\n\n"
          text += "".join(child_text)
  
          return text
      #@-node:snapshot
      #@+node:is_inherited
      def is_inherited(self, attrib_name):
          return not self.__dict__.has_key(attrib_name)
      #@-node:is_inherited
      #@+node:formatter
      def formatter(self, attrib_name, arg=None, format=None):
          """returns a function which is able
          to convert the value of the given attrib_name to a string"""
  
          formats = self.formats
          format = format or formats.get(attrib_name)
  
          if attrib_name in ("start", "end", "length", "effort",
                             "done", "todo", "buffer", "estimated_effort",
                             "performed_effort", "performed_start", "performed_end"):
              def save_strftime(v):
                  try:
                      return v.strftime(format)
                  #except AttributeError: some bug avoid catching this exception
                  except Exception:
                      return str(v)
  
              return save_strftime
  
          if attrib_name == "duration":
              def save_strftime(v):
                  try:
                      return v.strftime(format, True)
                  except AttributeError:
                      return str(v)
  
              return save_strftime
  
          if attrib_name in ("booked_resource", "performed_resource"):
              def get_resource_name(v):
                  title = getattr(v, "title", None)
                  if title: return title
                  return ", ".join([r.title for r in v])
              return get_resource_name
  
          if arg and attrib_name in ("costs", "sum", "max", "min"):
              format = formats.get("%s(%s)" % (attrib_name, arg), format)
  
          if format:
              return lambda v: locale.format(format, v, True)
  
          return str
      #@-node:formatter
      #@-node:Public methods
      #@+node:Resource allocation Methods
      #@+node:_all_resources_as_dict
      def _all_resources_as_dict(self):
          if self.children:
              result = {}
              for c in self.children:
                  result.update(c._all_resources_as_dict())
  
              return result
  
          if self.resource:
              return dict(map(lambda r: (r, 1), self.resource.all_members()))
  
          return {}
      #@-node:_all_resources_as_dict
      #@+node:_test_allocation
      def _test_allocation(self, resource_state, allocator):
          resource = self.resource._get_resources(resource_state)
          if not resource:
              return False
  
          return allocator.test_allocation(self, resource)
      #@-node:_test_allocation
      #@+node:_allocate
      def _allocate(self, state, allocator):
          allocator.allocate(self, state)
          #activate cache for done and todo
  
          if self.start.to_datetime() > self.end.to_datetime():
              #this can happen when performed effort are
              #during non working time
              tmp = self.start
              self.start = self.end
              self.end = tmp
  
          for r in self.performed_resource:
              r.correct_bookings(self)
  
          self._resource_length = map(lambda r: (weakref.proxy(r), \
                                                 r.length_of(self)),
                                      self._iter_booked_resources())
      #@-node:_allocate
      #@+node:_convert_performed
      def _convert_performed(self, all_resources):
          performed = self.performed
          if not performed: return False
  
          if not isinstance(performed, (tuple, list)) \
             or not isinstance(performed[0], (tuple, list)) \
             or not len(performed[0]) >= 3:
              self._raise(TypeError("""The format of the performed attribute must be:
      [( res_name, start_literal, end_literal, working_time ),  ... ].
      """), "performed")
  
          round_down_delta = self.root.calendar.minimum_time_unit / 2
          round_down_delta = datetime.timedelta(minutes=round_down_delta)
  
          def convert_item(index):
              item = performed[index]
              res, start, end = item[:3]
              if isinstance(res, str):
                  found = filter(lambda r: r.name == res, all_resources)
                  if found: res = found[0]
  
              try:
                  if not isinstance(res, (resource.Resource,
                                          resource._MetaResource)):
                      raise ValueError("the resource '%s' is unknown." %  res)
  
                  start = _to_datetime(start)
                  end = _to_datetime(end)
  
                  if len(item) > 3:
                      working_time = self._to_delta(item[3]).round()
                  else:
                      working_time = self._to_delta(end - start, True)
  
                  return ((res, start, end, working_time), index)
              except Exception, exc:
                  self._raise(exc.__class__("Item %i: %s" \
                                            % (index + 1, str(exc))),
                              "performed")
  
          converted = dict(map(convert_item, range(len(performed))))
          converted = converted.items()
          converted.sort()
  
          #check for overlapping items
          last_res = None
          for item, index in converted:
              res, start, end, work_time = item
              if last_res == res and start < last_end:
                  self._warn("Items %i, %i:  %s and %s are overlapping." \
                             % (last_index + 1, index + 1,
                                str(performed[last_index]),
                                str(performed[index])),
                             "performed")
  
              last_res = res
              last_end = end
              last_index = index
  
          self._performed = map(lambda x: x[0], converted)
          return True
      #@-node:_convert_performed
      #@+node:_allocate_performed
      def _allocate_performed(self, performed):
          if not performed: return
  
          to_delta = self._to_delta
          to_start = self._to_start
          to_end = self._to_end
  
          last = datetime.datetime.min
          first = datetime.datetime.max
          effort = 0
          work_time_sum = 0
          zero_minutes = to_delta(0)
          minimum_time_unit = to_delta(self.calendar.minimum_time_unit)
          summary = {}
  
          for item in performed:
              res, start, end, work_time = item
              effort += work_time * self.efficiency * res.efficiency
              work_time_sum += work_time
  
              res = res()
              ss, es, wts = summary.get(res, (datetime.datetime.max,
                                              datetime.datetime.min,
                                              zero_minutes))
              summary[res] = (min(ss, start), max(es, end), wts + work_time)
  
          for r, v in summary.iteritems():
              start, end, work_time = v
              assert(start.__class__ is datetime.datetime)
              assert(end.__class__ is datetime.datetime)
  
              #the booking limits should be inside the workingtime
              #to display them correct in resource charts
              cstart = to_start(start).to_datetime()
              if cstart > start: cstart = to_end(start).to_datetime()
  
              cend = to_end(end).to_datetime()
              if cend < end: cend = to_start(end).to_datetime()
  
              if self.root.is_snapshot:
                  delta = to_end(cend) - to_start(cstart)
              else:
                  delta = to_delta(cend - cstart).round()
  
              if not delta:
                  delta = minimum_time_unit
  
              book_load = float(work_time) / delta
              r().book_task(self, cstart, cend, book_load, work_time, True)
              last = max(end, last)
              first = min(start, first)
  
          self._performed_resource_length = tuple([ (r, v[2]) for r, v in summary.iteritems() ])
          self.performed_resource = tuple(summary.keys())
          self.performed_end = last
          self.performed_start = first
          self.performed_effort = to_delta(effort)
          self.performed_work_time = to_delta(work_time_sum)
          self._check_completion()
      #@-node:_allocate_performed
      #@+node:_iter_booked_resources
      def _iter_booked_resources(self):
          result = dict(map(lambda r: (r, 1), self.performed_resource))
          result.update(dict(map(lambda r: (r, 1), self.booked_resource)))
          return result.iterkeys()
      #@-node:_iter_booked_resources
      #@-node:Resource allocation Methods
      #@+node:Compile Methods
      #@+node:_generate
      def _generate(self, deferred=None):
          do_raise = False
          deferred = deferred or [ self ]
          while deferred:
              new_deferred = []
              for task in deferred:
                  task._compile(new_deferred, do_raise)
  
              do_raise = deferred == new_deferred
              deferred = new_deferred
      #@-node:_generate
      #@+node:_recalc_properties
      def _recalc_properties(self):
          if not self._properties: return
          self.__compile_function([], False, _MeProxyRecalc(self))
          self._is_compiled = True
      #@-node:_recalc_properties
      #@+node:_compile
      def _compile(self, deferred, do_raise):
          self.dont_inherit = ()
          self._constraint = None
          self._original_values.clear()
          self._properties.clear()
  
          try:
              self.__at_compile
              #@        << raise child recursion error >>
              #@+node:<< raise child recursion error >>
              self._raise(RecursionError("A child defines a "\
                                         "recursive definition at %s" % self.path))
              #@-node:<< raise child recursion error >>
              #@nl
          except AttributeError:
              self.__at_compile = self, ""
  
          try:
              self.__compile_function(deferred, do_raise, _MeProxy(self))
          finally:
              del self.__at_compile
  
          for c in self.children:
              if not c._is_compiled:
                  c._compile(deferred, do_raise)
  
          if self._is_compiled:
              self.__check_milestone()
              self.__check_task()
              self.root.has_actual_data |= self.__dict__.has_key("performed")
  
      #@-node:_compile
      #@+node:__compile_function
      def __compile_function(self, deferred, do_raise, me_instance):
          self._is_compiled = self._is_frozen
  
          restore_globals = []
          globals_ = self._function.func_globals
  
          #@    << set function global values >>
          #@+node:<< set function global values >>
          def to_value_wrapper(a):
              if isinstance(a, _ValueWrapper):
                  return a
  
              return _ValueWrapper(a, [(None, None)])
  
          def my_max(*args):
              return max(map(to_value_wrapper, args))
  
          def my_min(*args):
              return min(map(to_value_wrapper, args))
  
          globals_["me"] = me_instance
  
          if self._is_compiled:
              globals_["up"] = self.up
              globals_["root"] = self.root
          else:
              globals_["up"] = _Path(self.up, "up")
              globals_["root"] = _Path(self.root, "root")
  
          globals_["Delta"] = self._to_delta
          globals_["Date"] = self._to_start
          globals_["max"] = my_max
          globals_["min"] = my_min
          globals_["add_attrib"] = me_instance.add_attrib
          #@nonl
          #@-node:<< set function global values >>
          #@nl
          #@    << set me in global functions >>
          #@+node:<< set me in global functions >>
          #@+at
          # Is used for functions like YearlyMax, MonthlyMax, ....
          #@-at
          #@@code
          for name in self._function.global_names:
              try:
                  obj = globals_[name]
                  if isinstance(obj, types.FunctionType):
                      fg = obj.func_globals
                      if not fg.has_key("me") and "me" in obj.func_code.co_names:
                          restore_globals.append(fg)
                          fg["me"] = me_instance
              except KeyError: continue
          #@nonl
          #@-node:<< set me in global functions >>
          #@nl
          try:
              #@        << eval function >>
              #@+node:<< eval function >>
              if do_raise:
                  try:
                      self._function()
                      self._is_compiled = True
                  except _IncompleteError, e:
                      src = e.args[1]
                      if src is not self:
                          self.__at_compile = e.args[1:]
                          src._compile([], True)
  
                      raise
              else:
                  try:
                      self._function()
                      self._is_compiled = True
                  except AttributeError, e:
                      #print "AttributeError:", e, self.name, e.is_frozen, do_raise
                      deferred.append(self)
                  except _IncompleteError:
                      #print "_IncompleteError:", id(self), self.name, do_raise
                      deferred.append(self)
                  except RecursionError:
                      self._is_parent_referer = True
                      deferred.append(self)
              #@nonl
              #@-node:<< eval function >>
              #@nl
          finally:
              for fg in restore_globals:
                  del fg["me"]
  
      #@-node:__compile_function
      #@-node:Compile Methods
      #@+node:Setting methods
      #@+node:_set_attrib
      def _set_attrib(self, name, value):
          if value is _NEVER_USED_: return
  
          try:
              value = self._setting_hooks[name](self, name, value)
          except KeyError: pass
  
          if name == "__constraint__":
              self._constraint = value
              return
  
          if type(value) == types.FunctionType:
              if value.func_code.co_argcount == 0:
                  #@            << add child task >>
                  #@+node:<< add child task >>
                  try:
                      task = self.__dict__[value.func_name]
                  except KeyError:
                      task = Task(value, value.func_name, self, len(self.children) + 1)
                      self.children.append(task)
                      setattr(self, value.func_name, task)
                  return
                  #@nonl
                  #@-node:<< add child task >>
                  #@nl
  
          if name[0] == "_":
              #private vars will not be set
              return
  
          if isinstance(value, _Path):
              value = value._task
  
          set_method = getattr(self, "_set_" + name, None)
          if set_method:
              #@        << set standard attribute >>
              #@+node:<< set standard attribute >>
              if type(value) == types.DictionaryType:
                  self.root.all_scenarios.update(value.keys())
                  value = value.get(self.scenario, value["_default"])
  
              self.__set_sources(name, value)
              self._original_values[name] = value
              set_method(_val(value))
              #@nonl
              #@-node:<< set standard attribute >>
              #@nl
          else:
              #@        << set userdefined attribute >>
              #@+node:<< set userdefined attribute >>
              if callable( getattr(self.__class__, name, None)):
                  raise NameError('You may not use "%s" as attribute' % name)
  
              setattr(self, name, value)
              self._properties[name] = True
              self.__set_sources(name, value)
              #@nonl
              #@-node:<< set userdefined attribute >>
              #@nl
      #@-node:_set_attrib
      #@+node:read only attributes
      #@+node:_set_name
      def _set_name(self, value):
          raise AttributeError("The attribute 'name' is readonly.")
      #@nonl
      #@-node:_set_name
      #@+node:_set_done
      def _set_done(self, value):
          raise AttributeError("The attribute 'done' is readonly.")
      #@nonl
      #@-node:_set_done
      #@+node:_set_performed_work_time
      def _set_performed_work_time(self, value):
          raise AttributeError("The attribute 'performed_work_time' is readonly.")
      #@nonl
      #@-node:_set_performed_work_time
      #@+node:_set_booked_resource
      def _set_booked_resource(self, value):
          raise AttributeError("The attribute 'booked_resource' is readonly.")
      #@nonl
      #@-node:_set_booked_resource
      #@+node:_set_performed_effort
      def _set_performed_effort(self, value):
          raise AttributeError("The attribute 'performed_effort' is readonly.")
      #@nonl
      #@-node:_set_performed_effort
      #@+node:_set_children
      def _set_children(self, value):
          raise AttributeError("The attribute 'children' is readonly.")
      #@nonl
      #@-node:_set_children
      #@+node:_set_depth
      def _set_depth(self, value):
          raise AttributeError("The attribute 'depth' is readonly.")
      #@nonl
      #@-node:_set_depth
      #@+node:_set_index
      def _set_index(self, value):
          raise AttributeError("The attribute 'index' is readonly.")
      #@nonl
      #@-node:_set_index
      #@+node:_set_scenario
      def _set_scenario(self, value):
          raise AttributeError("The attribute 'scenario' is readonly.")
      #@nonl
      #@-node:_set_scenario
      #@+node:_set_buffer
      def _set_buffer(self, value):
          raise AttributeError("The attribute 'buffer' is readonly.")
      #@nonl
      #@-node:_set_buffer
      #@-node:read only attributes
      #@+node:_set_start
      def _set_start(self, value):
          self.__start_class = value.__class__
          self.start = self._to_start(value).round()
      #@-node:_set_start
      #@+node:_set_end
      def _set_end(self, value):
          self.end = self._to_end(value)
      #@-node:_set_end
      #@+node:_set_max_load
      def _set_max_load(self, max_load):
          self.max_load = float(max_load)
      #@-node:_set_max_load
      #@+node:_set_load
      def _set_load(self, load):
          self.load = float(load)
      #@-node:_set_load
      #@+node:_set_length
      def _set_length(self, value):
          self.length = self._to_delta(value).round()
      #@-node:_set_length
      #@+node:_set_effort
      def _set_effort(self, value):
          self.effort = self._to_delta(value).round()
      #@-node:_set_effort
      #@+node:_set_duration
      def _set_duration(self, value):
          self.duration = self._to_delta(value, True).round()
      #@-node:_set_duration
      #@+node:_set_complete
      def _set_complete(self, value):
          self.complete = value
      #@-node:_set_complete
      #@+node:_set_done
      def _set_done(self, value):
          self.done = self._to_delta(value).round()
      #@-node:_set_done
      #@+node:_set_todo
      def _set_todo(self, value):
          self.todo = self._to_delta(value).round()
      #@-node:_set_todo
      #@+node:_set_milestone
      def _set_milestone(self, value):
          self.milestone = value
      #@-node:_set_milestone
      #@+node:_set_resource
      def _set_resource(self, value):
          if not value:
              self.resource = None
              return
  
          if isinstance(value, (tuple, list)):
              value = reduce(lambda a, b: a & b, value)
  
          self.resource = value()
  
      #@-node:_set_resource
      #@+node:_set_copy_src
      def _set_copy_src(self, value):
          if isinstance(value, _MeProxy):
              raise RuntimeError("Cannot copy me.")
  
          if not value._is_compiled:
              raise _IncompleteError(value, "copy_src")
  
          if value.resource and not self.resource:
              self.resource = value.resource
  
          if value.balance and not self.balance:
              self.balance = value.balance
  
          copy_parms = ("priority", "todo", "complete",
                        "_constraint", "load", "length",
                        "effort", "duration")
  
          for p in copy_parms:
              v = value.__dict__.get(p)
              if v: setattr(self, p, v)
  
          self.copy_src = value
          self._properties.update(value._properties)
          for k in value._properties.iterkeys():
              setattr(self, k, getattr(value, k))
      #@-node:_set_copy_src
      #@+node:__set_sources
      def __set_sources(self, attrib_name, value):
          #@    << find references >>
          #@+node:<< find references >>
          def make_ref(val):
              if isinstance(val, _ValueWrapper):
                  return val._ref
  
              if isinstance(val, Task):
                  return [(val, "")]
  
              return []
  
          if isinstance(value, (list, tuple)):
              sources = _refsum(map(make_ref, value))
          else:
              sources = make_ref(value)
          #@nonl
          #@-node:<< find references >>
          #@nl
          if not sources: return
  
          #track only dependcies within the same project
          root = self.root
          sources = [ task.path + "." + attrib
                      for task, attrib in sources
                      if task and task.root is root ]
          self._sources[attrib_name] = tuple(sources)
          attr_path = self.path + "." + attrib_name
  
          #set dependencies of my sources
          for d in sources:
              path, attrib = _split_path(d)
              task = self.get_task(path)
              r_d = task._dependencies
              d_l = r_d.setdefault(attrib, {})
              d_l[attr_path] = True
      #@-node:__set_sources
      #@+node:Calendar Setters
      #@+node:_set_calendar
      def _set_calendar(self, value):
          self.calendar = value
          self._to_delta = value.Minutes
          self._to_start = value.StartDate
          self._to_end = value.EndDate
          self.__renew_dates()
  
      #@-node:_set_calendar
      #@+node:__renew_dates
      def __renew_dates(self):
          for attrib in ("effort", "start", "end", "length", "todo"):
              try:
  
                  self._set_attrib(attrib, self._original_values[attrib])
              except KeyError:
                  pass
  
      #@-node:__renew_dates
      #@+node:__make_calendar
      def __make_calendar(self):
          if not "calendar" in self.__dict__:
              cal = self.calendar = self.calendar.clone()
              self._to_delta = cal.Minutes
              self._to_start = cal.StartDate
              self._to_end = cal.EndDate
      #@nonl
      #@-node:__make_calendar
      #@+node:_set_vacation
      def _set_vacation(self, value):
          self.__make_calendar()
          self.calendar.set_vacation(value)
          self._properties["vacation"] = True
          self.vacation = value
          self.__renew_dates()
      #@-node:_set_vacation
      #@+node:_set_extra_work
      def _set_extra_work(self, value):
          self.__make_calendar()
          self.calendar.set_extra_work(value)
          self._properties["extra_work"] = True
          self.extra_work = value
          self.__renew_dates()
      #@-node:_set_extra_work
      #@+node:_set_working_days
      def _set_working_days(self, value):
  
          if type(value[0]) is str:
              value = (value, )
  
          self.working_days = value
          self._properties["working_days"] = True
          self.__make_calendar()
  
          for v in value:
              day_range = v[0]
              tranges = tuple(v[1:])
              self.calendar.set_working_days(day_range, *tranges)
  
          self.__renew_dates()
      #@nonl
      #@-node:_set_working_days
      #@+node:_set_minimum_time_unit
      def _set_minimum_time_unit(self, value):
          self.__make_calendar()
          self.calendar.minimum_time_unit = value
          self._properties["minimum_time_unit"] = True
      #@-node:_set_minimum_time_unit
      #@+node:_get_minimum_time_unit
      def _get_minimum_time_unit(self):
          return self.calendar.minimum_time_unit
  
      minimum_time_unit = property(_get_minimum_time_unit)
      #@-node:_get_minimum_time_unit
      #@+node:_set_working_days_per_week
      def _set_working_days_per_week(self, value):
  
          self.__make_calendar()
          self.calendar.working_days_per_week = value
          self._properties["working_days_per_week"] = True
      #@-node:_set_working_days_per_week
      #@+node:_get_working_days_per_week
      def _get_working_days_per_week(self):
          return self.calendar.working_days_per_week
  
      working_days_per_week = property(_get_working_days_per_week)
      #@-node:_get_working_days_per_week
      #@+node:_set_working_days_per_month
      def _set_working_days_per_month(self, value):
          self.__make_calendar()
          self.calendar.working_days_per_month = value
          self._properties["working_days_per_month"] = True
      #@-node:_set_working_days_per_month
      #@+node:_get_working_days_per_month
      def _get_working_days_per_month(self):
          return self.calendar.working_days_per_month
  
      working_days_per_month = property(_get_working_days_per_month)
      #@-node:_get_working_days_per_month
      #@+node:_set_working_days_per_year
      def _set_working_days_per_year(self, value):
          self.__make_calendar()
          self.calendar.working_days_per_year = value
          self._properties["working_days_per_year"] = True
      #@-node:_set_working_days_per_year
      #@+node:_get_working_days_per_year
      def _get_working_days_per_year(self):
          return self.calendar.working_days_per_year
  
      working_days_per_year = property(_get_working_days_per_year)
      #@-node:_get_working_days_per_year
      #@+node:_set_working_hours_per_day
      def _set_working_hours_per_day(self, value):
          self.__make_calendar()
          self.calendar.working_hours_per_day = value
          self._properties["set_working_hours_per_day"] = True
      #@-node:_set_working_hours_per_day
      #@+node:_get_working_hours_per_day
      def _get_working_hours_per_day(self):
          return self.calendar.working_hours_per_day
  
      working_hours_per_day = property(_get_working_hours_per_day)
      #@-node:_get_working_hours_per_day
      #@+node:_set_now
      def _set_now(self, value):
          proxy = weakref.proxy
          self.calendar.now = _to_datetime(value)
      #@-node:_set_now
      #@-node:Calendar Setters
      #@-node:Setting methods
      #@+node:Freezer Methods
      #@+node:_unfreeze
      def _unfreeze(self, attrib_name):
          if self.__dict__.has_key(attrib_name):
              del self.__dict__[attrib_name]
      #@-node:_unfreeze
      #@+node:_wrap_attrib
      def _wrap_attrib(self, method):
          attrib_name = method.__name__[7:]
          recursion_attrib = "_rec" + attrib_name
  
          try:
              dest, dattr = self.__at_compile
              raise RecursionError("Recursive definition of %s(%s) and %s(%s)." \
                                   % (self.path, attrib_name, dest.path, dattr))
          except AttributeError: pass
  
          if not self._is_compiled:
              raise _IncompleteError(self, attrib_name)
  
          try:
              getattr(self, recursion_attrib)
              raise RecursionError(self, attrib_name)
          except AttributeError: pass
  
          setattr(self, recursion_attrib, True)
  
          try:
              result = method(self)
  
              if self._is_frozen:
                  setattr(self, attrib_name, result)
  
              return result
          finally:
              delattr(self, recursion_attrib)
      #@-node:_wrap_attrib
      #@+node:_find_frozen
      def _find_frozen(self, attrib_name, default=None):
          value = self.__dict__.get(attrib_name)
          if value is not None:
              return value
  
          up = self.up
          return up and up._find_frozen(attrib_name) or default
      #@-node:_find_frozen
      #@-node:Freezer Methods
      #@+node:Calculation Methods
      #@+node:__calc_performed_effort
      def __calc_performed_effort(self):
          if self.children:
              return self._to_delta(sum([ t.performed_effort for t in self.children ]))
  
          return pcalendar.Minutes(0)
  
      performed_effort = _TaskProperty(__calc_performed_effort)
      #@-node:__calc_performed_effort
      #@+node:__calc_estimated_effort
      def __calc_estimated_effort(self):
          if self.children:
              return self._to_delta(sum([ t.estimated_effort for t in self.children ]))
  
          return self.effort
  
      estimated_effort = _TaskProperty(__calc_estimated_effort)
      #@-node:__calc_estimated_effort
      #@+node:__calc_start
      def __calc_start(self):
          to_start = self._to_start
  
          if self.children:
              try:
                  return min([ to_start(t.start) for t in self.children
                               if not t._is_parent_referer ])
              except ValueError:
                  #@            << raise child recursion error >>
                  #@+node:<< raise child recursion error >>
                  self._raise(RecursionError("A child defines a "\
                                             "recursive definition at %s" % self.path))
                  #@-node:<< raise child recursion error >>
                  #@nl
  
          try:
              end = self.end
              duration = self.__dict__.get("duration")
              if duration is not None:
                  start = end.to_datetime() - datetime.timedelta(minutes=duration)
              else:
                  start = end - self.length
  
              return to_start(start)
  
          except RecursionError:
              start = self._find_frozen("start")
              if start: return to_start(start)
              #@        << raise recursion error >>
              #@+node:<< raise recursion error >>
              raise RecursionError("you have to specify a "\
                                   "start or an end at %s" % self.path)
              #@nonl
              #@-node:<< raise recursion error >>
              #@nl
  
      start = _TaskProperty(__calc_start)
  
      #@-node:__calc_start
      #@+node:__calc_end
      def __calc_end(self):
          to_end = self._to_end
  
          if self.children:
              try:
                  return max([ to_end(t.end) for t in self.children
                               if not t._is_parent_referer ])
              except ValueError:
                  #@            << raise child recursion error >>
                  #@+node:<< raise child recursion error >>
                  self._raise(RecursionError("A child defines a "\
                                             "recursive definition at %s" % self.path))
                  #@-node:<< raise child recursion error >>
                  #@nl
  
          try:
              start = self.start
              duration = self.__dict__.get("duration")
              if duration is not None:
                  end = start.to_datetime() + datetime.timedelta(minutes=duration)
              else:
                  end = start + self.length
  
              return to_end(end)
  
          except RecursionError:
              end = self._find_frozen("end")
              if end: return to_end(end)
              #@        << raise recursion error >>
              #@+node:<< raise recursion error >>
              raise RecursionError("you have to specify a "\
                                   "start or an end at %s" % self.path)
              #@nonl
              #@-node:<< raise recursion error >>
              #@nl
  
  
      end = _TaskProperty(__calc_end)
      #@-node:__calc_end
      #@+node:__calc_load
      def __calc_load(self):
          length = self.__dict__.get("length")
          effort = self.__dict__.get("effort")
  
          if length is not None and effort is not None:
              return float(effort) / (float(length) or 1.0)
  
          load = self._find_frozen("load")
          if load is not None: return load
          return 1.0
  
      load = _TaskProperty(__calc_load)
      #@-node:__calc_load
      #@+node:__calc_length
      def __calc_length(self):
          effort = self.__dict__.get("effort")
          if effort is None:
              return self.end - self.start
  
          return self._to_delta(effort / self.load)
  
      length = _RoundingTaskProperty(__calc_length, "length")
      #@-node:__calc_length
      #@+node:__calc_duration
      def __calc_duration(self):
          return self._to_delta(self.end.to_datetime()\
                                - self.start.to_datetime(), True)
  
      duration = _TaskProperty(__calc_duration)
      #@-node:__calc_duration
      #@+node:__calc_effort
      def __calc_effort(self):
          if self.children:
              return self._to_delta(sum([ t.effort for t in self.children ]))
  
          return self._to_delta(self.length * self.load)
  
      effort = _RoundingTaskProperty(__calc_effort, "effort")
      #@-node:__calc_effort
      #@+node:__calc_done
      def __calc_done(self):
          if self.children:
              dones = map(lambda t: t.done, self.children)
              return self._to_delta(sum(dones))
  
          res = self._iter_booked_resources()
          done = sum(map(lambda r: r.done_of(self), res))
  
          complete = self.__dict__.get("complete")
          todo = self.__dict__.get("todo")
  
          if not done and complete == 100 or todo == 0:
              #if now is not set
              done = self.effort
  
          return self._to_delta(done)
  
      done = _TaskProperty(__calc_done)
      #@-node:__calc_done
      #@+node:__calc_buffer
      def __calc_buffer(self):
          if self.children:
              return self._to_delta(min(map(lambda t: t.buffer, self.children)))
  
          scenario = self.scenario
          end = self.end
          old_end = self.__dict__.get("end")
  
          #@    << find all tasks, that depend on my end >>
          #@+node:<< find all tasks, that depend on my end >>
          deps = { }
          task = self
          while task:
              deps.update(task._dependencies.get("end", {}))
              task = task.up
          #@nonl
          #@-node:<< find all tasks, that depend on my end >>
          #@nl
  
          #@    << define unfreeze_parents >>
          #@+node:<< define unfreeze_parents >>
          def unfreeze_parents():
              task = self.up
              while task:
                  task._unfreeze("end")
                  task = task.up
          #@nonl
          #@-node:<< define unfreeze_parents >>
          #@nl
  
          buffers = [ ]
          for d in deps.keys():
              path, attrib = _split_path(d)
              if attrib != "start":
                  continue
  
              #@        << calculate buffer to descendant 'd' >>
              #@+node:<< calculate buffer to descendant 'd' >>
              unfreeze_parents()
  
              # the following code considers a expressione like
              # start = predecessor.end + Delta("1d") the buffer
              # calculation must be aware of the 1d delay.
              # (therefore a simple succ_start - end would be
              # incorrect)
              # Solution: Simluate a later end and calculate the
              # real delay
  
              succ_task = self.get_task(path)
              simulated_task = Task(succ_task._function,
                                    succ_task.name,
                                    succ_task.up, 1)
  
              current_start = succ_task.start
              simulated_end = current_start
              self.end = current_start
  
              simulated_task._generate()
              simulated_start = simulated_task.start
  
              unfreeze_parents()
              if old_end: self.end = old_end
              else: self._unfreeze("end")
              del simulated_task
  
              current_delay = current_start - end
              simulated_delay = simulated_start - simulated_end
              real_delay = current_delay - simulated_delay
              try:
                  buffer_ = real_delay + succ_task.buffer
              except RecursionError, err:
                  self._raise(err)
              #@nonl
              #@-node:<< calculate buffer to descendant 'd' >>
              #@nl
  
              buffers.append(buffer_)
              if not buffer_:
                  break
  
          if buffers:
              return self._to_delta(min(buffers))
  
          return not self.milestone \
                 and self.root.end - end \
                 or self._to_delta(0)
  
      buffer = _TaskProperty(__calc_buffer)
      #@-node:__calc_buffer
      #@+node:__calc_complete
      def __calc_complete(self):
          done = self.done
          todo = self.todo
          return int(100.0 * done / ((done + todo) or 1))
  
      complete = _TaskProperty(__calc_complete)
      #@-node:__calc_complete
      #@+node:__calc_todo
      def __calc_todo(self):
          complete = self.__dict__.get("complete")
          if complete:
              # effort = done + todo
              #             done               done
              # complete = ------ ==> todo = -------- - done
              #            effort            complete
              complete = float(complete)
              done = self.done
              if done:
                  done = float(done)
                  return self._to_delta(done * 100.0 / complete - done)
              return self._to_delta(self.effort * complete / 100.0)
  
          if self.children:
              todos = map(lambda t: t.todo, self.children)
              return self._to_delta(sum(todos))
  
          todo = sum(map(lambda r: r.todo_of(self), self.booked_resource))
          return self._to_delta(max(todo, self.effort - self.done))
  
      todo = _TaskProperty(__calc_todo)
      #@-node:__calc_todo
      #@-node:Calculation Methods
      #@+node:Check Methods
      #@+node:__check_task
      def __check_task(self):
          if self.children: return
  
          start = self._find_frozen("start")
          end = self._find_frozen("end")
  
          if not (start or end):
              self._raise(ValueError("You must specify either a"\
                                     " start or an end attribute"))
  
          if start and end: return
  
          length = self.__dict__.get("length")
          duration = self.__dict__.get("duration")
          effort = self.__dict__.get("effort")
          if not (effort or length or duration):
              #set a default value
              self._set_effort("1d")
              #self._raise(ValueError("You must specify either a"\
              #                       " length or a duration or "\
              #                       "an effort attribute"))
      #@-node:__check_task
      #@+node:__check_milestone
      def __check_milestone(self):
          if not self.milestone: return
  
          self.length = self._to_delta(0)
          start = self.__dict__.get("start")
          if not start:
              self._raise(ValueError("Milestone must have start attribute"),
                                     "milstone")
  
          if self.__start_class.__name__ == "edt":
              #the milestone is probably dependent on the end date of
              #an other task (see edt in pcalendar) ==> start at the end date
              self.start = self.end = self._to_end(self.start)
          else:
              self.start = self.end = self._to_start(self.start)
  
      #@-node:__check_milestone
      #@+node:_check_completion
      def _check_completion(self):
          if not self.performed_effort: return
          if self.root.is_snapshot: return
  
          # allocation is not done yet ==> self.todo, self.done,
          # self.complete cannot be calculated
          if self._find_frozen("complete", 0) < 100 \
                 and self.__dict__.get("todo", 1) > 0:
              return
  
          start = self.performed_start
          end = self.performed_end
          #ensure that self.start.to_datetime() < self.end.to_datetime()
          cstart = self._to_start(start)
          if cstart.to_datetime() > start: cstart = self._to_end(start)
  
          cend = self._to_end(end)
          if cend.to_datetime() < end: cend = self._to_start(end)
  
          self.start = cstart
          self.end = cend
  
          if self.performed_effort != self.effort:
              self.estimated_effort = self.effort
              self.effort = self.performed_effort
      #@-node:_check_completion
      #@+node:check
      def check(self):
          if self._constraint and self._is_compiled:
              globals_ = self._function.func_globals
              globals_["me"] = self
              globals_["up"] = self.up
              globals_["root"] = self.root
              globals_["assert_"] = self.__assert
              self._constraint()
      #@-node:check
      #@-node:Check Methods
      #@+node:Error Methods
      #@+node:__assert
      def __assert(self, value):
          if not value:
              warnings.warn('Assertion in scenario: "%s".' % self.scenario,
                            RuntimeWarning, 2)
      #@-node:__assert
      #@+node:_warn
      def _warn(self, message, attrib=None, level=2):
          self.__compile_function([], True, _MeProxyWarn(self, attrib, message))
      #@-node:_warn
      #@+node:_raise
      def _raise(self, exc, attrib=None):
          self.__compile_function([], True, _MeProxyError(self, attrib, exc))
          raise exc
      #@-node:_raise
      #@-node:Error Methods
      #@-others
  #@nonl
  #@-node:class Task
  #@-node:Task
  #@+node:Projects
  #@+node:class _ProjectBase
  class _ProjectBase(Task):
      """
      Base class for all projects.
      """
      #@	<< class _ProjectBase declarations >>
      #@+node:<< class _ProjectBase declarations >>
      __attrib_completions__ = { }
      __attrib_completions__.update(Task.__attrib_completions__)
      del __attrib_completions__["milestone"] #project cannot be milestones
  
      priority = 500
      efficiency = 1.0
      max_load = 1.0
      balance = 0
      resource = None
      copy_src = None
      has_actual_data = False
      is_snapshot = False
  
      #@-node:<< class _ProjectBase declarations >>
      #@nl
      #@	@+others
      #@+node:__init__
      def __init__(self, top_task, scenario="_default", id=""):
          self.calendar = pcalendar.Calendar()
          Task.__init__(self, top_task, top_task.func_name)
          self.id = id or self.name
          self.scenario = scenario
          self.all_scenarios = set(("_default",))
          self.path = "root"
          self._globals = top_task.func_globals.copy()
          self._generate()
  
      #@-node:__init__
      #@+node:_idendity_
      def _idendity_(self): return self.id
      #@-node:_idendity_
      #@+node:_restore_globals
      def _restore_globals(self):
          self._function.func_globals.clear()
          self._function.func_globals.update(self._globals)
          del self._globals
      #@-node:_restore_globals
      #@+node:free
      def free(self):
          all_resources = self.all_resources()
          for r in all_resources:
              r().unbook_tasks_of_project(self.id, self.scenario)
  
          for t in self:
              t.booked_resource = ()
  
          return all_resources
      #@-node:free
      #@+node:_get_balancing_list
      def _get_balancing_list(self):
  
          try:
              cached_list = balancing_cache[self._function.org_code]
              if len(cached_list) != len(tuple(self)):
                  # different scenarios can have different tasks
                  raise KeyError()
  
          except KeyError:
              cached_list = _build_balancing_list(self)
              balancing_cache[self._function.org_code] = cached_list
          else:
              cached_list = [ self.get_task(t.path) for t in cached_list ]
  
          return cached_list
      #@-node:_get_balancing_list
      #@+node:snapshot
      def snapshot(self, indent="", name=None):
          text = Task.snapshot(self, indent, name)
  
          lines = text.splitlines(True)
          indent += "    "
  
          def make_resource(r):
              return '%sclass %s(Resource): title = "%s"\n' \
                     % (indent, r.name, r.title)
  
          now = datetime.datetime.now().strftime("%x %H:%M")
          resource_text = map(lambda r: make_resource(r), self.all_resources())
          lines.insert(1, "%sfrom faces import Resource\n" % indent)
          lines.insert(2, "".join(resource_text) + "\n")
          lines.insert(3, '%snow = "%s"\n' % (indent, now))
          lines.insert(4, '%sis_snapshot = True\n' % indent)
          return "".join(lines)
      #@-node:snapshot
      #@-others
  #@-node:class _ProjectBase
  #@+node:class Project
  class Project(_ProjectBase):
      """
      Generates a Project without allocating resources.
  
      @param top_task: Specifies the highest function of a project definiton.
  
      @param scenario: Specifies the name of the scenario which should be scheduled.
  
      @param id: Specifiess a unique idenfication name to distinguish the project from
      other projects in the resource database. The default value for id
      is the name of top_task.
      """
      #@	<< class Project declarations >>
      #@+node:<< class Project declarations >>
      __call_completion__ = 'Project(|top_task, scenario="_default", id=None)'
  
      #@-node:<< class Project declarations >>
      #@nl
      #@	@+others
      #@+node:__init__
      def __init__(self, top_task, scenario="_default", id=None):
          _ProjectBase.__init__(self, top_task, scenario, id)
          no_snapshot = not self.is_snapshot
          for t in self:
              t._is_frozen = True
              t._recalc_properties()
              no_snapshot and t.check()
  
          self._restore_globals()
  
      #@-node:__init__
      #@-others
  
  #@-node:class Project
  #@+node:class _AllocationPoject
  class _AllocationPoject(_ProjectBase):
      #@	@+others
      #@+node:unfreeze_parents
      def unfreeze_parents(self):
          if self.has_actual_data:
              for t in filter(lambda t: t.children, self):
                  if not t._original_values.has_key("start"): t._unfreeze("start")
                  if not t._original_values.has_key("end"): t._unfreeze("end")
      #@-node:unfreeze_parents
      #@-others
  #@-node:class _AllocationPoject
  #@+node:class BalancedProject
  class BalancedProject(_AllocationPoject):
      """
      Generates a project with allocated resources. The tasks are balanced
      to fit the resources load conditions.
      """
      #@	<< class BalancedProject declarations >>
      #@+node:<< class BalancedProject declarations >>
      __call_completion__ = """BalancedProject(|top_task, scenario="_default",
      id=None, balance=SMART, performed=None)"""
  
      #@-node:<< class BalancedProject declarations >>
      #@nl
      #@	@+others
      #@+node:__init__
      def __init__(self, top_task, scenario="_default",
                   id=None, balance=SMART, performed=None):
          _AllocationPoject.__init__(self, top_task, scenario, id)
          self.balance = balance
          if performed:
              self._distribute_performed(performed)
              self.has_actual_data = True
  
          no_snapshot = not self.is_snapshot
          if no_snapshot:
              self.allocate()
          else:
              self.allocate_snapshot()
  
          for t in self:
              t._is_frozen = True
              t._recalc_properties()
              no_snapshot and t.check()
  
          self._restore_globals()
      #@nonl
      #@-node:__init__
      #@+node:allocate_snapshot
      def allocate_snapshot(self):
          all_resources = self.free()
          scenario = self.scenario
          has_actual_data = True
          for t in self:
              if not t.resource or t.milestone or t.children:
                  continue
  
              t._convert_performed(all_resources)
              t._allocate_performed(t._performed)
      #@-node:allocate_snapshot
      #@+node:allocate
      def allocate(self):
          all_resources = self.free()
          balancing_list = self._get_balancing_list()
          scenario = self.scenario
  
          #for t in balancing_list:
          #    print t.path
  
          for t in balancing_list:
              t._compile([], True)
  
              if not t.resource or t.milestone or t.children:
                  continue
  
              if t._convert_performed(all_resources):
                  has_actual_data = True
  
              try:
                  t._allocate_performed(t._performed)
              except AttributeError:
                  pass
  
              allocator = _allocators[t.balance]
              min_val = None
              min_state = None
              for p in range(t.resource._permutation_count()):
                  state = t._test_allocation(p, allocator)
  
                  if not state: continue
  
                  to_minimize = state[0]
                  if not min_val or min_val > to_minimize:
                      min_val = to_minimize
                      min_state = state
  
              if min_state:
                  t._allocate(min_state, allocator)
              elif t.performed_start:
                  # t could not be allocated ==>
                  # performance data holds all information
                  t.start = t._to_start(t.performed_start)
                  t.end = t._to_end(t.performed_end)
  
          self.unfreeze_parents()
      #@-node:allocate
      #@+node:_distribute_performed
      def _distribute_performed(self, performed):
          project_id = self._idendity_()
          plen = len(project_id)
  
          performed = filter(lambda item: item[0].startswith(project_id),
                             performed)
          performed.sort()
  
          task = None
          for item in performed:
              path = item[0]
              rpath = "root" + path[plen:]
              task = self.get_task(rpath)
  
              if not task:
                  #@            << extract task in activity path >>
                  #@+node:<< extract task in activity path >>
                  #@+at
                  # A performed path can have sub activities appended to the
                  # task path.
                  # like:
                  #
                  #   root.parent1.parent2.task.subactivity
                  #
                  # here rhe correct task path is:
                  #
                  #     root.parent1.parent2.task
                  #
                  #@-at
                  #@@code
                  orpath = rpath
                  while not task:
                      #path can specify a sub module
                      #find the correct path to the module
                      try:
                          last_dot = rpath.rindex(".", 0, len(rpath))
                      except ValueError:
                          break
  
                      rpath = rpath[:last_dot]
                      task = self.get_task(rpath)
  
                  item = list(item)
                  item.append(orpath[len(rpath):])
                  #@nonl
                  #@-node:<< extract task in activity path >>
                  #@nl
  
              if not task or task.children:
                  self._warn("The performance data contain "
                             "a task with id '%s'. But such "
                             "a task does not exist in your "
                             "project." % path)
                  continue
  
              if not isinstance(task.performed, list):
                  task.performed = list(task.performed)
  
              task.performed.append(item[1:])
      #@nonl
      #@-node:_distribute_performed
      #@-others
  #@-node:class BalancedProject
  #@+node:class AdjustedProject
  class AdjustedProject(_AllocationPoject):
      """
      Generates a project with allocated resources. The tasks are
      adjusted to the actual tracking data and balanced to fit the
      resources load conditions.
      """
      #@	<< class AdjustedProject declarations >>
      #@+node:<< class AdjustedProject declarations >>
      __call_completion__ = 'AdjustedProject(|base_project)'
  
      #@-node:<< class AdjustedProject declarations >>
      #@nl
      #@	@+others
      #@+node:__init__
      def __init__(self, base_project):
          _AllocationPoject.__init__(self, base_project._function,
                                     base_project.scenario,
                                     base_project.id)
  
          self.balance = base_project.balance
          self.has_actual_data = base_project.has_actual_data
          self.allocate(base_project)
          for t in self:
              t._is_frozen = True
              t._recalc_properties()
              t.check()
  
          self._restore_globals()
  
  
      #@-node:__init__
      #@+node:allocate
      def allocate(self, base):
          balancing_list = self._get_balancing_list()
          scenario = self.scenario
          cal = self.calendar
          now = cal.now
  
          #for t in balancing_list:
          #    print t.path
  
          #@    << free the resources, we have to rebook >>
          #@+node:<< free the resources, we have to rebook >>
          for t in balancing_list:
              src = base.get_task(t.path)
              if src.end > now or src.complete < 100:
                  for r in src._iter_booked_resources():
                      r.unbook_task(src)
          #@nonl
          #@-node:<< free the resources, we have to rebook >>
          #@nl
  
          for t in balancing_list:
              src = base.get_task(t.path)
              if src.end <= now and src.complete == 100:
                  #@            << copy the attribs of complete tasks >>
                  #@+node:<< copy the attribs of complete tasks >>
                  t.effort = src.effort
                  t.load = src.load
                  t.start = src.start
                  t.end = src.end
                  t.done = src.done
                  t.todo = src.todo
                  t.booked_resource = src.booked_resource
                  t.performed_resource = src.performed_resource
                  t._unfreeze("length")
                  t._unfreeze("duration")
                  #@nonl
                  #@-node:<< copy the attribs of complete tasks >>
                  #@nl
                  continue
  
              t._compile([], True)
              if not t.resource or t.milestone or t.children:
                  continue
  
              # now allocate the uncomplete tasks
              #@        << allocate performed data >>
              #@+node:<< allocate performed data >>
              try:
                  t._performed = src._performed
                  t._allocate_performed(t._performed)
              except AttributeError:
                  pass
              #@nonl
              #@-node:<< allocate performed data >>
              #@nl
              allocator = _allocators[t.balance]
  
              if src.start >= now:
                  #@            << allocate tasks, that have not begun yet >>
                  #@+node:<< allocate tasks, that have not begun yet >>
                  min_val = None
                  min_state = None
                  for p in range(t.resource._permutation_count()):
                      state = t._test_allocation(p, allocator)
                      if not state: continue
  
                      to_minimize = state[0]
                      if not min_val or min_val > to_minimize:
                          min_val = to_minimize
                          min_state = state
  
                  if min_state:
                      t._allocate(min_state, allocator)
                  elif t.performed_start:
                      t.start = t._to_start(t.performed_start)
                      t.end = t._to_end(t.performed_end)
                  #@-node:<< allocate tasks, that have not begun yet >>
                  #@nl
              else:
                  #@            << allocate tasks, that are allready at work >>
                  #@+node:<< allocate tasks, that are allready at work >>
                  if t.__dict__.has_key("effort"):
                      t.effort = t._to_delta(src.done + src.todo).round()
  
                  resource = src.booked_resource or src.performed_resource
                  state = allocator.test_allocation(t, resource)
                  if state:
                      t._allocate(state, allocator)
                  #@nonl
                  #@-node:<< allocate tasks, that are allready at work >>
                  #@nl
  
          self.unfreeze_parents()
      #@nonl
      #@-node:allocate
      #@-others
  #@-node:class AdjustedProject
  #@-node:Projects
  #@-others
  
  
  """
      Atttribute mit Bedeutung:
  
      calendar
      --------
      minimum_time_unit     |int in minutes|
      working_days_per_week |int in days   |
      working_days_per_month|int in days   |
      working_days_per_year |int in days   |
      working_hours_per_day |int in hours  |
      vacation              | [ one_day, (from, to), .. ] |
      working_days
      now
  
  
  
      Task
      -----
      load
      start
      end
      length
      effort
      duration
      resource
      booked_resource
  
      milestone
      complete
      done
      todo
      priority
      efficiency
      buffer
  
      children
      depth
      index
      path
      dont_inherit
  
      performed_effort
      performed_end
      performed_start
  
      sum()
      min()
      max()
      costs()
      indent_name()
      max_load
  
      copy_src (set: copy all attributes of another task
                get: reference of copy)
  
      balance
  
      for gantt
      -----
      line
      accumulate
  
  
  
  
      Resource
      ----------
      efficiency
      load
      vacation
      max_load
  
  """
  #@-node:@file task.py
  #@-leo
  
  # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
  