summaryrefslogtreecommitdiffstats
path: root/command_context.py
blob: 72bad0571dcdf1a90c7fbd6126c28812de2a0852 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# -*- coding: UTF-8 -*-
# Copyright 2014 Red Hat, Inc.
# Part of clufter project
# Licensed under GPLv2 (a copy included | http://gnu.org/licenses/gpl-2.0.txt)
"""Command context, i.e., state distributed along filters chain"""
__author__ = "Jan Pokorný <jpokorny @at@ Red Hat .dot. com>"

from collections import MutableMapping
import logging

from .error import ClufterError

log = logging.getLogger(__name__)


class CommandContextError(ClufterError):
    pass


class CommandContextBase(MutableMapping):
    """Object representing command context"""
    def __init__(self, initial=None, parent=None):
        if parent is None:
            parent = self
        self._parent = parent
        try:
            self._dict = dict(initial)
        except TypeError:
            self._dict = {}

    def __delitem__(self, key):
        del self._dict[key]

    def __getitem__(self, key):
        #return self._dict[key]
        return self._dict.get(key)  # make it soft-error (->setdefault reimpl.)

    def setdefault(self, key, default=None):
        return self._dict.setdefault(key, default)

    def __iter__(self):
        return iter(self._dict)

    def __len__(self):
        return len(self._dict)

    def __setitem__(self, key, value):
        self._dict[key] = CommandContextBase(initial=value, parent=self) \
                          if isinstance(value, dict) else value

    @property
    def parent(self):
        return self._parent


class CommandContext(CommandContextBase):
    def __init__(self, *args, **kwargs):
        super(CommandContext, self).__init__(*args, **kwargs)
        self._filters = {}

    def _wrapping_nested_context(self, obj):
        class wrapped(CommandContextBase):
            def __getattribute__(self, name):
                if name == 'ctxt_wrapped':
                    return obj
                elif name == 'ctxt_set':
                    ret = lambda self, **kwargs: self.update(kwargs)
                elif name.startswith('ctxt_'):
                    # by convention, ctxt_* methods are using second
                    # argument to pass the respective (nested) context
                    ret = obj.__getattribute__(name)
                    if callable(ret):
                        ret = \
                            lambda this, *args, **kwargs: \
                                ret(this, this, *args, **kwargs)
                else:
                    try:
                        ret = super(wrapped, self).__getattribute__(name)
                    except AttributeError:
                        ret = obj.__getattribute__(name)
                return ret

            def __setattribute__(self, name, value):
                obj.__setattribute__(name, value)
        return (wrapped(parent=self))

    def ensure_filter(self, flt):
        existing, key = self._filters, flt.__class__.name
        ret = existing.get(key, None)
        if ret:
            assert id(ret.ctxt_wrapped) == id(flt)
        else:
            ret = existing[key] = self._wrapping_nested_context(flt)
        return ret

    def ensure_filters(self, flts):
        return map(self.ensure_filter, flts)

    def filter(self, which):
        return self._filters[which]