# -*- coding: iso-8859-15 -*-


# Glasnost
# By: Odile Bénassy <obenassy@entrouvert.com>
#     Romain Chantereau <rchantereau@entrouvert.com>
#     Nicolas Clapiès <nclapies@easter-eggs.org>
#     Pierre-Antoine Dejace <padejace@entrouvert.be>
#     Thierry Dulieu <tdulieu@easter-eggs.com>
#     Florent Monnier <monnier@codelutin.com>
#     Cédric Musso <cmusso@easter-eggs.org>
#     Frédéric Péters <fpeters@entrouvert.be>
#     Benjamin Poussin <poussin@codelutin.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#     Sébastien Régnier <regnier@codelutin.com>
#     Emmanuel Saracco <esaracco@easter-eggs.com>
#
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
#     Emmanuel Saracco & Théridion
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
#     Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
#     Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
#     Régnier, Emmanuel Saracco, Théridion & Vecam
#
# This program 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.
#
# This program 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

"""Glasnost Context Handling Module.

This module contains all context related functions.

"""

__version__ = '$Revision$'[11:-2]


import threading


class _Context:
    """The context.

    An instance of this class is a Glasnost context.
    
    The constructor needs a dictionnay (empty or not) in order to intialize his
    *vars* dictionnary.
    
    Attributes
    ==========

    *vars*:
        The context attributes dictionnary. This is where all the context
        variables are saved.

    *next*:
        A context class instance aggregation. This permit to link contexts.
    """
    
    next = None
    vars = None

    def __init__(self, vars):
        self.vars = vars

    def delVar(self, name):
        """Remove an variable from the context.

        If the variable is defined in this context, it is removed, otherwise
        nothing is done.
        """

        if self.vars.has_key(name):
            del self.vars[name]

    def getVar(self, name, default = None):
        """Return the first value of an variable in the context stack.

        If no corresponding variable is found, the default value is returned.
        If no default value is specified, None is returned.

        """

        if self.vars.has_key(name):
            return self.vars[name]
        elif self.next is None:
            return default
        else:
            return self.next.getVar(name, default = default)

    def getVarNames(self):
        """Return the names of all variables defined in the context stack."""

        return self.getVarNames2([])

    def getVarNames2(self, varNames):
        for name in self.vars.keys():
            if not name in varNames:
                varNames.append(name)
        if self.next is None:
            return varNames
        else:
            return self.next.getVarNames2(varNames)

    def setVar(self, name, value):
        """Set an variable in the context."""

        self.vars[name] = value


def clear():
    """Empty the context stack."""
    
    threading.currentThread()._glasnostContext = None
    

def delVar(name):
    """Remove an attribute from the top-level context.
    
    If the attribute is defined in the top-level context, it is removed from
    it, otherwise nothing is done.
    
    Keyword argument
    ================

    *name*:
        The name of the attribute to delete.
    
    """

    context = get()
    context.delVar(name)


def get(**signatureVars):
    """Get a context in the context stack.

    If signatureVars is empty, it returns the first context.
    Otherwise, it returns the first context which contains all signature
    variables (and values).
    
    """

    thread = threading.currentThread()
    context = thread._glasnostContext
    if not signatureVars:
        return context
    while context is not None:
        vars = context.vars
        for name, value in signatureVars.items():
            if not vars.has_key(name) or vars[name] != value:
                break
        else:
            return context
        context = context.next
    return None


def getVar(name, default = None):
    """Return the value of the wanted attribute.
    
    If no corresponding attribute found, the default value is returned, if no
    default value is specified, None is returned.

    Keyword arguments
    =================

    *name*:
        Wanted attribute value title.

    *default*:
        Default value if no corresponding attribute was found. (default: None).
        
    """
    
    context = get()
    while context is not None:
        if context.vars.has_key(name):
            return context.vars[name]
        context = context.next
    return default
    return context.getVar(name, default = default)


def getVarNames():
    """Get the context attribute name sequence.
    
    Return all the attribute name stacked in the current context in a sequence.
    
    """
    
    context = get()
    return context.getVarNames()


def initFromOther(otherContext):
    """Set a new current context with the given context as next.

    The given context is aggregated within the new empty current context. All
    the given context attributes are used by default until an other value is
    set in the current context.

    Keyword argument
    ================

    *otherContext*:
        The context to aggregate within the new current context.
    
    """
    
    context = _Context({})
    context.next = otherContext
    threading.currentThread()._glasnostContext = context
    

def initThread(thread):
    """Initialise a new thread context.

    The given thread context is initialized from the the current context, as
    the *initFromOther* function does.

    Keyword argument
    ================

    *thread*:
        The thread instance to initialize.
        
    """
    
    context = _Context({})
    context.next = threading.currentThread()._glasnostContext
    thread._glasnostContext = context


def isClear():
    """Test if the current context does not exist

    If the current thread has the context attribute and it is set to none, the
    current thread is clear.

    Return
    ======

    *1*:
        The current thread is clear'

    *0*:
        The current context already exists.
        
    """
    
    thread = threading.currentThread()
    return hasattr(thread, '_glasnostContext') \
           and thread._glasnostContext is None


def pull(**signatureVars):
    """Remove the first context or contexts from the stack.

    If signatureVars is empty, it removes the first context.
    Otherwise, it removes all the contexts until there is a context which
    contains all signature variables (and values).
    
    """
    
    thread = threading.currentThread()
    context = thread._glasnostContext
    while context is not None:
        vars = context.vars
        for name, value in signatureVars.items():
            if not vars.has_key(name) or vars[name] != value:
                break
        else:
            thread._glasnostContext = context.next
            return
        context = context.next
    raise Exception('Unable to find signature in context.')


def push(**contextVars):
    """Set top level context attributes.

    A new top level context is instanciated with the given attributes as his
    values.
    All the other context are push down from one level. So the current context
    is aggregated in the new pushed context.

    Keyword argument
    ================

    *contextVars*:
        The new context variables dictionnary, the key is the variable name,
        the value, his value.
        
    """
    
    thread = threading.currentThread()
    context = _Context(contextVars)
    context.next = thread._glasnostContext
    thread._glasnostContext = context
    return context


def set(context):
    """Set the current context."""
    
    threading.currentThread()._glasnostContext = context


def setVar(name, value):
    """Set a top level context attribute value.
    
    If the attribute does not exist, it is created.

    Keyword arguments
    =================

    *name*:
        The variable title.

    *value*:
        The variable value.
        
    """
    
    context = get()
    context.setVar(name, value)

