Package lepl :: Package support :: Module context
[hide private]
[frames] | no frames]

Source Code for Module lepl.support.context

  1   
  2  # The contents of this file are subject to the Mozilla Public License 
  3  # (MPL) Version 1.1 (the "License"); you may not use this file except 
  4  # in compliance with the License. You may obtain a copy of the License 
  5  # at http://www.mozilla.org/MPL/ 
  6  # 
  7  # Software distributed under the License is distributed on an "AS IS" 
  8  # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 
  9  # the License for the specific language governing rights and 
 10  # limitations under the License. 
 11  # 
 12  # The Original Code is LEPL (http://www.acooke.org/lepl) 
 13  # The Initial Developer of the Original Code is Andrew Cooke. 
 14  # Portions created by the Initial Developer are Copyright (C) 2009-2010 
 15  # Andrew Cooke (andrew@acooke.org). All Rights Reserved. 
 16  # 
 17  # Alternatively, the contents of this file may be used under the terms 
 18  # of the LGPL license (the GNU Lesser General Public License, 
 19  # http://www.gnu.org/licenses/lgpl.html), in which case the provisions 
 20  # of the LGPL License are applicable instead of those above. 
 21  # 
 22  # If you wish to allow use of your version of this file only under the 
 23  # terms of the LGPL License and not to allow others to use your version 
 24  # of this file under the MPL, indicate your decision by deleting the 
 25  # provisions above and replace them with the notice and other provisions 
 26  # required by the LGPL License.  If you do not delete the provisions 
 27  # above, a recipient may use your version of this file under either the 
 28  # MPL or the LGPL License. 
 29   
 30  ''' 
 31  Allow global per-thread values to be defined within a certain scope in a 
 32  way that supports multiple values, temporary changes inside with contexts, 
 33  etc. 
 34   
 35  This is implemented in two layers.  The base layer is a map from keys 
 36  to values which isolates different, broad, functionalities.  Despite the name, 
 37  a NamespaceMap can map from any key to any value - it's just a thread-local 
 38  map.  However, typically is it used with Namespaces because they have support 
 39  for some useful idioms. 
 40   
 41  A Namespace is, as described above, associated with a name in the thread's 
 42  NamespaceMap.  It manages state for some functionality, so is another map, 
 43  forming the second layer.  The motivating example of a Namespace is the 
 44  OperatorNamespace, which maps from operators to matchers.  This uses the 
 45  support in Namespace that allows values to be over-ridden within a certain 
 46  scope to support overriding matchers for matching spaces. 
 47  ''' 
 48   
 49  from collections import deque 
 50  #from logging import getLogger 
 51  from threading import local 
 52   
 53  from lepl.support.lib import singleton, fmt 
 54   
 55   
56 -class ContextError(Exception):
57 ''' 58 Exception raised on problems with context. 59 ''' 60 pass
61 62 63 # pylint: disable-msg=R0903
64 -class NamespaceMap(local):
65 ''' 66 A store for namespaces. 67 68 This subclasses threading.local so each thread effectively has its own 69 instance (see test). 70 ''' 71
72 - def __init__(self):
73 super(NamespaceMap, self).__init__() 74 self.__map = {}
75
76 - def get(self, name, default=None):
77 ''' 78 This gets the namespace associated with the name, creating a new 79 namespace from the second argument if necessary. 80 ''' 81 from lepl.matchers.operators import OperatorNamespace 82 if default is None: 83 default = OperatorNamespace 84 if name not in self.__map: 85 self.__map[name] = default() 86 return self.__map[name]
87 88
89 -class Namespace(object):
90 ''' 91 A store for global definitions. 92 ''' 93
94 - def __init__(self, base=None):
95 self.__stack = deque([{} if base is None else base])
96
97 - def push(self, extra=None):
98 ''' 99 Copy the current state to the stack and modify it. Values in extra 100 that map to None are ignored. 101 ''' 102 self.__stack.append(dict(self.current())) 103 extra = {} if extra is None else extra 104 for name in extra: 105 self.set_if_not_none(name, extra[name])
106
107 - def pop(self):
108 ''' 109 Return the previous state from the stack. 110 ''' 111 self.__stack.pop()
112
113 - def __enter__(self):
114 ''' 115 Allow use within a with context by duplicating the current state 116 and saving to the stack. Returns self to allow manipulation via set. 117 ''' 118 self.push() 119 return self
120
121 - def __exit__(self, *_args):
122 ''' 123 Restore the previous state from the stack on leaving the context. 124 ''' 125 self.pop()
126
127 - def current(self):
128 ''' 129 The current state (a map from names to operator implementations). 130 ''' 131 return self.__stack[-1]
132
133 - def set(self, name, value):
134 ''' 135 Set a value. 136 ''' 137 self.current()[name] = value
138
139 - def set_if_not_none(self, name, value):
140 ''' 141 Set a value if it is not None. 142 ''' 143 if value != None: 144 self.set(name, value)
145
146 - def get(self, name, default):
147 ''' 148 Get a value if defined, else the default. 149 ''' 150 return self.current().get(name, default)
151 152
153 -class OnceOnlyNamespace(Namespace):
154 ''' 155 Allow some values to be set only once. 156 ''' 157
158 - def __init__(self, base=None, once_only=None):
159 super(OnceOnlyNamespace, self).__init__(base) 160 self.__once_only = set() if once_only is None else once_only
161
162 - def once_only(self, name):
163 ''' 164 The given name can be set only once. 165 ''' 166 self.__once_only.add(name)
167
168 - def set(self, name, value):
169 ''' 170 Set a value (if it has not already been set). 171 ''' 172 if name in self.__once_only and self.get(name, None) is not None: 173 raise ContextError(fmt('{0} can only be set once', name)) 174 else: 175 super(OnceOnlyNamespace, self).set(name, value)
176 177 178 # pylint: disable-msg=C0103, W0603
179 -def Global(name, default=None):
180 ''' 181 Global (per-thread) binding from operator name to implementation, by 182 namespace. 183 ''' 184 # Delay creation to handle circular dependencies. 185 assert name 186 namespace_map = singleton(NamespaceMap) 187 return namespace_map.get(name, default)
188 189
190 -class NamespaceMixin(object):
191 ''' 192 Allow access to global (per-thread) values. 193 ''' 194
195 - def __init__(self, name, namespace):
196 super(NamespaceMixin, self).__init__() 197 self.__name = name 198 self.__namespace = namespace
199
200 - def _lookup(self, name, default=None):
201 ''' 202 Retrieve the named namespace from the global (per thread) store. 203 ''' 204 return Global(self.__name, self.__namespace).get(name, default)
205 206
207 -class Scope(object):
208 ''' 209 Base class supporting dedicated syntax for particular options. 210 ''' 211
212 - def __init__(self, name, namespace, frame):
213 self.__name = name 214 self.__namespace = namespace 215 self.__frame = frame
216
217 - def __enter__(self):
218 ''' 219 On entering the context, add the new definitions. 220 ''' 221 Global(self.__name, self.__namespace).push(self.__frame)
222
223 - def __exit__(self, *_args):
224 ''' 225 On leaving the context, return to previous definition. 226 ''' 227 Global(self.__name, self.__namespace).pop()
228