Software Freedom Law Center

root/trunk/trac/trac/config.py

Revision 103, 17.3 kB (checked in by bkuhn, 8 months ago)

r129@hughes: bkuhn | 2008-05-01 21:46:34 -0400

  • Merged upstream trac via: svk smerge /loblaw/local/branches/trac.upstream-r6969 .
Line 
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright (C) 2005-2008 Edgewall Software
4 # Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
5 # All rights reserved.
6 #
7 # This software is licensed as described in the file COPYING, which
8 # you should have received as part of this distribution. The terms
9 # are also available at http://trac.edgewall.org/wiki/TracLicense.
10 #
11 # This software consists of voluntary contributions made by many
12 # individuals. For the exact contribution history, see the revision
13 # history and logs, available at http://trac.edgewall.org/log/.
14
15 from ConfigParser import ConfigParser
16 import os
17
18 from trac.core import ExtensionPoint, TracError
19 from trac.util.compat import set, sorted
20 from trac.util.text import to_unicode, CRLF
21
22 __all__ = ['Configuration', 'Option', 'BoolOption', 'IntOption', 'ListOption',
23            'PathOption', 'ExtensionOption', 'OrderedExtensionsOption',
24            'ConfigurationError']
25
26 _TRUE_VALUES = ('yes', 'true', 'enabled', 'on', 'aye', '1', 1, True)
27
28
29 class ConfigurationError(TracError):
30     """Exception raised when a value in the configuration file is not valid."""
31
32
33 class Configuration(object):
34     """Thin layer over `ConfigParser` from the Python standard library.
35
36     In addition to providing some convenience methods, the class remembers
37     the last modification time of the configuration file, and reparses it
38     when the file has changed.
39     """
40     def __init__(self, filename):
41         self.filename = filename
42         self.parser = ConfigParser()
43         self.parent = None
44         self._lastmtime = 0
45         self._sections = {}
46         self.parse_if_needed()
47
48     def __contains__(self, name):
49         """Return whether the configuration contains a section of the given
50         name.
51         """
52         return name in self.sections()
53
54     def __getitem__(self, name):
55         """Return the configuration section with the specified name."""
56         if name not in self._sections:
57             self._sections[name] = Section(self, name)
58         return self._sections[name]
59
60     def __repr__(self):
61         return '<%s %r>' % (self.__class__.__name__, self.filename)
62
63     def get(self, section, name, default=''):
64         """Return the value of the specified option.
65        
66         Valid default input is a string. Returns a string.
67         """
68         return self[section].get(name, default)
69
70     def getbool(self, section, name, default=''):
71         """Return the specified option as boolean value.
72        
73         If the value of the option is one of "yes", "true", "enabled", "on",
74         or "1", this method wll return `True`, otherwise `False`.
75        
76         Valid default input is a string or a bool. Returns a bool.
77        
78         (since Trac 0.9.3, "enabled" added in 0.11)
79         """
80         return self[section].getbool(name, default)
81
82     def getint(self, section, name, default=''):
83         """Return the value of the specified option as integer.
84        
85         If the specified option can not be converted to an integer, a
86         `ConfigurationError` exception is raised.
87        
88         Valid default input is a string or an int. Returns an int.
89        
90         (since Trac 0.10)
91         """
92         return self[section].getint(name, default)
93
94     def getlist(self, section, name, default='', sep=',', keep_empty=False):
95         """Return a list of values that have been specified as a single
96         comma-separated option.
97        
98         A different separator can be specified using the `sep` parameter. If
99         the `keep_empty` parameter is set to `True`, empty elements are
100         included in the list.
101        
102         Valid default input is a string or a list. Returns a string.
103        
104         (since Trac 0.10)
105         """
106         return self[section].getlist(name, default, sep, keep_empty)
107
108     def set(self, section, name, value):
109         """Change a configuration value.
110        
111         These changes are not persistent unless saved with `save()`.
112         """
113         self[section].set(name, value)
114
115     def defaults(self):
116         """Returns a dictionary of the default configuration values.
117        
118         (since Trac 0.10)
119         """
120         defaults = {}
121         for (section, name), option in Option.registry.items():
122             defaults.setdefault(section, {})[name] = option.default
123         return defaults
124
125     def options(self, section):
126         """Return a list of `(name, value)` tuples for every option in the
127         specified section.
128        
129         This includes options that have default values that haven't been
130         overridden.
131         """
132         return self[section].options()
133
134     def remove(self, section, name):
135         """Remove the specified option."""
136         if self.parser.has_section(section):
137             self.parser.remove_option(section, name)
138
139     def sections(self):
140         """Return a list of section names."""
141         sections = set(self.parser.sections())
142         parent = self.parent
143         while parent:
144             sections |= set(parent.parser.sections())
145             parent = parent.parent
146         return sorted(sections)
147
148     def has_option(self, section, option):
149         """Returns True if option exists in section in either project or
150         parent trac.ini, or available through the Option registry.
151        
152         (since Trac 0.11)
153         """
154         # Check project trac.ini
155         for file_option, val in self.options(section):
156             if file_option == option:
157                 return True
158         # Check parent trac.ini
159         if self.parent:
160             for parent_option, val in self.parent.options(section):
161                 if parent_option == option:
162                     return True
163         # Check the registry
164         if (section, option) in Option.registry:
165             return True
166         # Not found
167         return False
168
169     def save(self):
170         """Write the configuration options to the primary file."""
171         if not self.filename:
172             return
173
174         # Only save options that differ from the defaults
175         sections = []
176         for section in self.sections():
177             options = []
178             for option in self[section]:
179                 default = None
180                 if self.parent:
181                     default = self.parent.get(section, option)
182                 current = self.parser.has_option(section, option) and \
183                           to_unicode(self.parser.get(section, option))
184                 if current is not False and current != default:
185                     options.append((option, current))
186             if options:
187                 sections.append((section, sorted(options)))
188
189         fileobj = open(self.filename, 'w')
190         try:
191             fileobj.write('# -*- coding: utf-8 -*-\n\n')
192             for section, options in sections:
193                 fileobj.write('[%s]\n' % section)
194                 for key, val in options:
195                     if key in self[section].overridden:
196                         fileobj.write('# %s = <inherited>\n' % key)
197                     else:
198                         val = val.replace(CRLF, '\n').replace('\n', '\n ')
199                         fileobj.write('%s = %s\n' % (key, val.encode('utf-8')))
200                 fileobj.write('\n')
201         finally:
202             fileobj.close()
203
204     def parse_if_needed(self):
205         if not self.filename or not os.path.isfile(self.filename):
206             return False
207
208         changed = False
209         modtime = os.path.getmtime(self.filename)
210         if modtime > self._lastmtime:
211             self.parser._sections = {}
212             self.parser.read(self.filename)
213             self._lastmtime = modtime
214             changed = True
215
216         if self.parser.has_option('inherit', 'file'):
217             filename = self.parser.get('inherit', 'file')
218             if not os.path.isabs(filename):
219                 filename = os.path.join(os.path.dirname(self.filename),
220                                         filename)
221             if not self.parent or self.parent.filename != filename:
222                 self.parent = Configuration(filename)
223                 changed = True
224             else:
225                 changed |= self.parent.parse_if_needed()
226         elif self.parent:
227             changed = True
228             self.parent = None
229
230         return changed
231
232     def touch(self):
233         if self.filename and os.path.isfile(self.filename) \
234            and os.access(self.filename, os.W_OK):
235             os.utime(self.filename, None)
236
237
238 class Section(object):
239     """Proxy for a specific configuration section.
240    
241     Objects of this class should not be instantiated directly.
242     """
243     __slots__ = ['config', 'name', 'overridden']
244
245     def __init__(self, config, name):
246         self.config = config
247         self.name = name
248         self.overridden = {}
249
250     def __contains__(self, name):
251         if self.config.parser.has_option(self.name, name):
252             return True
253         if self.config.parent:
254             return name in self.config.parent[self.name]
255         return False
256
257     def __iter__(self):
258         options = set()
259         if self.config.parser.has_section(self.name):
260             for option in self.config.parser.options(self.name):
261                 options.add(option.lower())
262                 yield option
263         if self.config.parent:
264             for option in self.config.parent[self.name]:
265                 if option.lower() not in options:
266                     yield option
267
268     def __repr__(self):
269         return '<Section [%s]>' % (self.name)
270
271     def get(self, name, default=''):
272         """Return the value of the specified option.
273        
274         Valid default input is a string. Returns a string.
275         """
276         if self.config.parser.has_option(self.name, name):
277             value = self.config.parser.get(self.name, name)
278         elif self.config.parent:
279             value = self.config.parent[self.name].get(name, default)
280         else:
281             option = Option.registry.get((self.name, name))
282             if option:
283                 value = option.default or default
284             else:
285                 value = default
286         if not value:
287             return u''
288         elif isinstance(value, basestring):
289             return to_unicode(value)
290         else:
291             return value
292
293     def getbool(self, name, default=''):
294         """Return the value of the specified option as boolean.
295        
296         This method returns `True` if the option value is one of "yes", "true",
297         "enabled", "on", or "1", ignoring case. Otherwise `False` is returned.
298
299         Valid default input is a string or a bool. Returns a bool.
300         """
301         value = self.get(name, default)
302         if isinstance(value, basestring):
303             value = value.lower() in _TRUE_VALUES
304         return bool(value)
305
306     def getint(self, name, default=''):
307         """Return the value of the specified option as integer.
308        
309         If the specified option can not be converted to an integer, a
310         `ConfigurationError` exception is raised.
311        
312         Valid default input is a string or an int. Returns an int.
313         """
314         value = self.get(name, default)
315         if not value:
316             return 0
317         try:
318             return int(value)
319         except ValueError:
320             raise ConfigurationError('expected integer, got %s' % repr(value))
321
322     def getlist(self, name, default='', sep=',', keep_empty=True):
323         """Return a list of values that have been specified as a single
324         comma-separated option.
325        
326         A different separator can be specified using the `sep` parameter. If
327         the `keep_empty` parameter is set to `False`, empty elements are omitted
328         from the list.
329        
330         Valid default input is a string or a list. Returns a list.
331         """
332         value = self.get(name, default)
333         if not value:
334             return []
335         if isinstance(value, basestring):
336             items = [item.strip() for item in value.split(sep)]
337         else:
338             items = list(value)
339         if not keep_empty:
340             items = filter(None, items)
341         return items
342
343     def getpath(self, name, default=''):
344         """Return the value of the specified option as a path name, relative to
345         the location of the configuration file the option is defined in.
346
347         Valid default input is a string. Returns a string with normalised path.
348         """
349         if self.config.parser.has_option(self.name, name):
350             path = self.config.parser.get(self.name, name)
351             if not path:
352                 return default
353             if not os.path.isabs(path):
354                 path = os.path.join(os.path.dirname(self.config.filename),
355                                     path)
356             return os.path.normcase(os.path.realpath(path))
357         elif self.config.parent:
358             return self.config.parent[self.name].getpath(name, default)
359         else:
360             return default
361
362     def options(self):
363         """Return `(name, value)` tuples for every option in the section."""
364         for name in self:
365             yield name, self.get(name)
366
367     def set(self, name, value):
368         """Change a configuration value.
369        
370         These changes are not persistent unless saved with `save()`.
371         """
372         if not self.config.parser.has_section(self.name):
373             self.config.parser.add_section(self.name)
374         if value is None:
375             self.overridden[name] = True
376             value = ''
377         else:
378             value = to_unicode(value).encode('utf-8')
379         return self.config.parser.set(self.name, name, value)
380
381
382 class Option(object):
383     """Descriptor for configuration options on `Configurable` subclasses."""
384
385     registry = {}
386     accessor = Section.get
387
388     def __init__(self, section, name, default=None, doc=''):
389         """Create the extension point.
390        
391         @param section: the name of the configuration section this option
392             belongs to
393         @param name: the name of the option
394         @param default: the default value for the option
395         @param doc: documentation of the option
396         """
397         self.section = section
398         self.name = name
399         self.default = default
400         self.registry[(self.section, self.name)] = self
401         self.__doc__ = doc
402
403     def __get__(self, instance, owner):
404         if instance is None:
405             return self
406         config = getattr(instance, 'config', None)
407         if config and isinstance(config, Configuration):
408             section = config[self.section]
409             value = self.accessor(section, self.name, self.default)
410             return value
411         return None
412
413     def __set__(self, instance, value):
414         raise AttributeError, 'can\'t set attribute'
415
416     def __repr__(self):
417         return '<%s [%s] "%s">' % (self.__class__.__name__, self.section,
418                                    self.name)
419
420
421 class BoolOption(Option):
422     """Descriptor for boolean configuration options."""
423     accessor = Section.getbool
424
425
426 class IntOption(Option):
427     """Descriptor for integer configuration options."""
428     accessor = Section.getint
429
430
431 class ListOption(Option):
432     """Descriptor for configuration options that contain multiple values
433     separated by a specific character."""
434
435     def __init__(self, section, name, default=None, sep=',', keep_empty=False,
436                  doc=''):
437         Option.__init__(self, section, name, default, doc)
438         self.sep = sep
439         self.keep_empty = keep_empty
440
441     def accessor(self, section, name, default):
442         return section.getlist(name, default, self.sep, self.keep_empty)
443
444 class PathOption(Option):
445     """Descriptor for file system path configuration options."""
446     accessor = Section.getpath
447
448
449 class ExtensionOption(Option):
450
451     def __init__(self, section, name, interface, default=None, doc=''):
452         Option.__init__(self, section, name, default, doc)
453         self.xtnpt = ExtensionPoint(interface)
454
455     def __get__(self, instance, owner):
456         if instance is None:
457             return self
458         value = Option.__get__(self, instance, owner)
459         for impl in self.xtnpt.extensions(instance):
460             if impl.__class__.__name__ == value:
461                 return impl
462         raise AttributeError('Cannot find an implementation of the "%s" '
463                              'interface named "%s".  Please update the option '
464                              '%s.%s in trac.ini.'
465                              % (self.xtnpt.interface.__name__, value,
466                                 self.section, self.name))
467
468
469 class OrderedExtensionsOption(ListOption):
470     """A comma separated, ordered, list of components implementing `interface`.
471     Can be empty.
472
473     If `include_missing` is true (the default) all components implementing the
474     interface are returned, with those specified by the option ordered first."""
475
476     def __init__(self, section, name, interface, default=None,
477                  include_missing=True, doc=''):
478         ListOption.__init__(self, section, name, default, doc=doc)
479         self.xtnpt = ExtensionPoint(interface)
480         self.include_missing = include_missing
481
482     def __get__(self, instance, owner):
483         if instance is None:
484             return self
485         order = ListOption.__get__(self, instance, owner)
486         components = []
487         for impl in self.xtnpt.extensions(instance):
488             if self.include_missing or impl.__class__.__name__ in order:
489                 components.append(impl)
490
491         def compare(x, y):
492             x, y = x.__class__.__name__, y.__class__.__name__
493             if x not in order:
494                 return int(y in order)
495             if y not in order:
496                 return -int(x in order)
497             return cmp(order.index(x), order.index(y))
498         components.sort(compare)
499         return components
Note: See TracBrowser for help on using the browser.

SFLC Main Page

[frdm] Support SFLC