1
""" A preferences node that adds the notion of preferences scopes. """
4
# Standard library imports.
5
from os.path import join
7
# Enthought library imports.
8
from enthought.etsconfig.api import ETSConfig
9
from enthought.traits.api import List, Str, Undefined
12
from i_preferences import IPreferences
13
from preferences import Preferences
16
class ScopedPreferences(Preferences):
17
""" A preferences node that adds the notion of preferences scopes.
19
Path names passed to the node can either contain scope information or can
20
simply be a preference path. In the latter case, the operation takes place
21
in the first scope in the node's list of scopes.
23
The syntax of a fully qualified path name is::
25
scope_name/some/arbitrary/scope/context/path.to.a.preference
27
The scope is up to the first '/'. The scope context (if any) is from the
28
first '/' to the last '/', and the actual preference path is everything
31
e.g. A preference path might look like this::
33
'project/My Project/my.plugin.id/acme.ui.bgcolor'
35
The scope is 'project'.
36
The scope context is 'My Project/my.plugin.id'
37
The preference path is 'acme.ui.bgcolor'
39
There is one drawback to this scheme. If you want to access a scope node
40
itself via the 'clear', 'keys', 'node', 'node_exists' or 'node_names'
41
methods then you have to append a trailing '/' to the path. Without that,
42
the node would try to perform the operation in the first scope.
44
e.g. To get the names of the children of the 'application' scope, use::
46
scoped.node_names('application/')
50
scoped.node_names('application')
52
Then the node would get the first scope and try to find its child node
55
Of course you can just get the scope via::
57
scoped.get_scope('application')
59
and then call whatever methods you like on it!
63
#### 'ScopedPreferences' interface ########################################
65
# The scopes (in the order that they should be searched when looking for
67
scopes = List(IPreferences)
69
###########################################################################
70
# 'IPreferences' interface.
71
###########################################################################
73
#### Methods where 'path' refers to a preference ####
75
def get(self, path, default=None, inherit=False):
76
""" Get the value of the preference at the specified path. """
79
raise ValueError('empty path')
81
# If the path contains a specific scope then lookup the preference in
83
if self._path_contains_scope(path):
84
scope_name, path = self._parse_path(path)
85
nodes = [self._get_scope(scope_name)]
87
# Otherwise, try each scope in turn.
91
# Try all nodes first (without inheritance even if specified).
92
value = self._get(path, Undefined, nodes, inherit=False)
93
if value is Undefined:
95
value = self._get(path, default, nodes, inherit=True)
102
def remove(self, path):
103
""" Remove the preference at the specified path. """
106
raise ValueError('empty path')
108
# If the path contains a specific scope then remove the preference from
110
if self._path_contains_scope(path):
111
scope_name, path = self._parse_path(path)
112
node = self._get_scope(scope_name)
114
# Otherwise, remove the preference from the first scope.
116
node = self.scopes[0]
122
def set(self, path, value):
123
""" Set the value of the preference at the specified path. """
126
raise ValueError('empty path')
128
# If the path contains a specific scope then set the value in that
130
if self._path_contains_scope(path):
131
scope_name, path = self._parse_path(path)
132
node = self._get_scope(scope_name)
134
# Otherwise, set the value in the first scope.
136
node = self.scopes[0]
138
node.set(path, value)
142
#### Methods where 'path' refers to a node ####
144
def clear(self, path=''):
145
""" Remove all preference from the node at the specified path. """
147
# If the path contains a specific scope then remove the preferences
148
# from a node in that scope.
149
if self._path_contains_scope(path):
150
scope_name, path = self._parse_path(path)
151
node = self._get_scope(scope_name)
153
# Otherwise, remove the preferences from a node in the first scope.
155
node = self.scopes[0]
157
return node.clear(path)
159
def keys(self, path=''):
160
""" Return the preference keys of the node at the specified path. """
162
# If the path contains a specific scope then get the keys of the node
164
if self._path_contains_scope(path):
165
scope_name, path = self._parse_path(path)
166
nodes = [self._get_scope(scope_name)]
168
# Otherwise, merge the keys of the node in all scopes.
174
keys.update(node.node(path).keys())
178
def node(self, path=''):
179
""" Return the node at the specified path. """
185
# If the path contains a specific scope then we get the node that
187
if self._path_contains_scope(path):
188
scope_name, path = self._parse_path(path)
189
node = self._get_scope(scope_name)
191
# Otherwise, get the node from the first scope.
193
node = self.scopes[0]
195
node = node.node(path)
199
def node_exists(self, path=''):
200
""" Return True if the node at the specified path exists. """
202
# If the path contains a specific scope then look for the node in that
204
if self._path_contains_scope(path):
205
scope_name, path = self._parse_path(path)
206
node = self._get_scope(scope_name)
208
# Otherwise, look for the node in the first scope.
210
node = self.scopes[0]
212
return node.node_exists(path)
214
def node_names(self, path=''):
215
""" Return the names of the children of the node at the specified path.
219
# If the path contains a specific scope then get the names of the
220
# children of the node in that scope.
221
if self._path_contains_scope(path):
222
scope_name, path = self._parse_path(path)
223
nodes = [self._get_scope(scope_name)]
225
# Otherwise, merge the names of the children of the node in all scopes.
231
names.update(node.node(path).node_names())
235
###########################################################################
236
# 'Preferences' interface.
237
###########################################################################
239
#### Listener methods ####
241
def add_preferences_listener(self, listener, path=''):
242
""" Add a listener for changes to a node's preferences. """
244
# If the path contains a specific scope then add a preferences listener
245
# to the node in that scope.
246
if self._path_contains_scope(path):
247
scope_name, path = self._parse_path(path)
248
nodes = [self._get_scope(scope_name)]
250
# Otherwise, add a preferences listener to the node in all scopes.
255
node.add_preferences_listener(listener, path)
259
def remove_preferences_listener(self, listener, path=''):
260
""" Remove a listener for changes to a node's preferences. """
262
# If the path contains a specific scope then remove a preferences
263
# listener from the node in that scope.
264
if self._path_contains_scope(path):
265
scope_name, path = self._parse_path(path)
266
nodes = [self._get_scope(scope_name)]
268
# Otherwise, remove a preferences listener from the node in all scopes.
273
node.remove_preferences_listener(listener, path)
277
#### Persistence methods ####
279
def load(self, file_or_filename=None):
280
""" Load preferences from a file.
282
This loads the preferences into the first scope.
286
if file_or_filename is None and len(self.filename) > 0:
287
file_or_filename = self.filename
289
node = self.scopes[0]
290
node.load(file_or_filename)
294
def save(self, file_or_filename=None):
295
""" Save the node's preferences to a file.
297
This asks each scope in turn to save its preferences.
299
If a file or filename is specified then it is only passed to the first
304
if file_or_filename is None and len(self.filename) > 0:
305
file_or_filename = self.filename
307
self.scopes[0].save(file_or_filename)
308
for scope in self.scopes[1:]:
313
###########################################################################
314
# 'ScopedPreferences' interface.
315
###########################################################################
317
def _scopes_default(self):
318
""" Trait initializer. """
320
# The application scope is a persistent scope.
321
application_scope = Preferences(
322
name = 'application',
323
filename = join(ETSConfig.get_application_home(create=False),
327
# The default scope is a transient scope.
328
default_scope = Preferences(name='default')
330
return [application_scope, default_scope]
332
def get_scope(self, scope_name):
333
""" Return the scope with the specified name.
335
Return None if no such scope exists.
339
for scope in self.scopes:
340
if scope_name == scope.name:
348
###########################################################################
350
###########################################################################
352
def _get(self, path, default, nodes, inherit):
353
""" Get a preference from a list of nodes. """
356
value = node.get(path, Undefined, inherit)
357
if value is not Undefined:
365
def _get_scope(self, scope_name):
366
""" Return the scope with the specified name.
368
Raise a 'ValueError' is no such scope exists.
372
scope = self.get_scope(scope_name)
374
raise ValueError('no such scope %s' % scope_name)
378
def _path_contains_scope(self, path):
379
""" Return True if the path contains a scope component. """
383
def _parse_path(self, path):
384
""" 'Parse' the path into two parts, the scope name and the rest! """
386
components = path.split('/')
388
return components[0], '/'.join(components[1:])
390
###########################################################################
391
# Debugging interface.
392
###########################################################################
394
def dump(self, indent=''):
395
""" Dump the preferences hierarchy to stdout. """
400
print indent, 'Node(%s)' % self.name, self._preferences
403
for child in self.scopes:
408
#### EOF ######################################################################