1# Copyright (c) 2004 Divmod.
2# See LICENSE for details.
3
4from zope.interface import implements, providedBy
5
6from formless.iformless import IConfigurable, IActionableType, IBinding
7from formless.annotate import Argument, ElementBinding, GroupBinding, Object, TypedInterface
8
9from nevow import inevow
10from nevow.context import WovenContext
11
12class Configurable(object):
13    implements(IConfigurable)
14
15    bindingDict = None
16
17    def __init__(self, original):
18        self.original = original
19        self.boundTo = self
20
21    def getBindingNames(self, context):
22        ## Todo: remove this getattr
23        ifs = providedBy(getattr(self, 'boundTo', self))
24        ifs = [
25            x for x in ifs if x is not IConfigurable and x is not TypedInterface
26        ]
27        bindingNames = []
28        self.bindingDict = bindingDict = {}
29        for interface in ifs:
30            ## TypedInterfaces have a __spec__ attribute which is a list of all Typed properties and
31            ## autocallable methods
32            for binding in getattr(interface, '__spec__', []):
33                bindingDict[binding.name] = binding
34                if binding.name not in bindingNames:
35                    bindingNames.append(binding.name)
36                if IActionableType.providedBy(binding.typedValue):
37                    acts = binding.typedValue.actions
38                    if acts is None:
39                        acts = []
40                    for action in acts:
41                        bindingDict[action.name] = action
42        return bindingNames
43
44    def getDefault(self, forBinding):
45        ## TODO: Clean this up, it's a mess
46        if not isinstance(forBinding, Argument):
47            name = forBinding.name
48            if hasattr(self, name):
49                return getattr(self, name)
50            ## Todo: Only do this in ConfigurableAdapter instead of Configurable
51            if hasattr(self.boundTo, name):
52                return getattr(self.boundTo, name)
53            if self.original is not self.boundTo and hasattr(self.original, name):
54                return getattr(self.original, name)
55        return forBinding.default
56
57    def getBinding(self, context, name):
58        if self.bindingDict is None:
59            self.getBindingNames(context)
60        if self.bindingDict is None:
61            self.bindingDict = {}
62        binding = getattr(self, 'bind_%s' % name, getattr(self.boundTo, 'bind_%s' % name, None))
63        if binding is not None:
64            binding = binding(context)
65        else:
66            try:
67                binding = self.bindingDict[name]
68            except KeyError:
69                raise RuntimeError, "%s is not an exposed binding on object %s." % (name, self.boundTo)
70        binding.boundTo = self.boundTo
71        return binding
72
73    def postForm(self, ctx, bindingName, args):
74        """Accept a form post to the given bindingName. The bindingName
75        can be dotted to indicate an attribute of this Configurable, eg
76        addresses.0.changeEmail. The post arguments are given in args.
77        Return a Resource which will be rendered in response.
78        """
79        from formless import iformless
80        from nevow.tags import invisible
81        request = ctx.locate(inevow.IRequest)
82        pathSegs = bindingName.split('.')
83        configurable = self
84
85        cf = ctx.locate(iformless.IConfigurableFactory)
86        ## Get the first binding
87        firstSeg = pathSegs.pop(0)
88        binding = configurable.getBinding(ctx, firstSeg)
89        ctx.remember(binding, IBinding)
90        ctx.remember(configurable, IConfigurable)
91        ## I don't think this works right now, it needs to be fixed.
92        ## Most cases it won't be triggered, because we're just traversing a
93        ## single binding name
94        for seg in pathSegs:
95            assert 1 == 0, "Sorry, this doesn't work right now"
96            binding = configurable.getBinding(ctx, seg)
97            child = self.boundTo
98            if not isinstance(binding, GroupBinding):
99                accessor = inevow.IContainer(configurable.boundTo, None)
100                if accessor is None:
101                    child = getattr(configurable.boundTo, binding.name)
102                else:
103                    child = accessor.child(ctx, binding.name)
104            ## If it's a groupbinding, we don't do anything at all for this path segment
105
106            ## This won't work right now. We need to push the previous configurable
107            ## as the configurableFactory somehow and ask that for hte next binding
108            ## we also need to support deferreds coming back from locateConfigurable
109            assert 'black' is 'white', "Deferred support is pending"
110            configurable = cf.locateConfigurable(ctx, child)
111            ctx = WovenContext(ctx, invisible(key=seg))
112            ctx.remember(binding, IBinding)
113            ctx.remember(configurable, IConfigurable)
114
115        bindingProcessor = iformless.IInputProcessor(binding)
116        rv = bindingProcessor.process(ctx, binding.boundTo, args)
117        ctx.remember(rv, inevow.IHand)
118        ctx.remember('%r success.' % bindingName, inevow.IStatusMessage)
119        return rv
120
121    def summary(self):
122        return "An instance of %s" % self.__class__.__name__
123
124    postLocation = None
125
126class NotFoundConfigurable(Configurable):
127    def getBinding(self, context, name):
128        raise RuntimeError, self.original
129
130
131class TypedInterfaceConfigurable(Configurable):
132    def __init__(self, original):
133        self.original = original
134        self.boundTo = original
135
136    def summary(self):
137        return "An instance of %s" % self.original.__class__.__name__
138
139    def __repr__(self):
140        return "TypedInterfaceConfigurable(%r)" % self.original
141
142
143class ListConfigurable(TypedInterfaceConfigurable):
144    def getBinding(self, context, name):
145        eb = ElementBinding(name, Object())
146        eb.boundTo = self.original
147        return eb
148
149
150class GroupConfigurable(TypedInterfaceConfigurable):
151    def __init__(self, original, groupInterface):
152        TypedInterfaceConfigurable.__init__(self, original)
153        self.groupInterface = groupInterface
154
155    bindingDict = None
156
157    def getBindingNames(self, context):
158        bindingNames = []
159        self.bindingDict = bindingDict = {}
160        interface = self.groupInterface
161        for binding in getattr(interface, '__spec__', []):
162            bindingDict[binding.name] = binding
163            if binding.name not in bindingNames:
164                bindingNames.append(binding.name)
165            if IActionableType.providedBy(binding.typedValue):
166                acts = binding.typedValue.actions
167                if acts is None:
168                    acts = []
169                for action in acts:
170                    bindingDict[action.name] = action
171        return bindingNames
172
173
174