1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 from __future__ import with_statement
16
17 from ConfigParser import ConfigParser
18 from copy import deepcopy
19 import os.path
20
21 from genshi.builder import tag
22 from trac.admin import AdminCommandError, IAdminCommandProvider
23 from trac.core import *
24 from trac.util import AtomicFile, as_bool
25 from trac.util.compat import cleandoc, wait_for_file_mtime_change
26 from trac.util.text import printout, to_unicode, CRLF
27 from trac.util.translation import _, N_, tag_
28
29 __all__ = ['Configuration', 'ConfigSection', 'Option', 'BoolOption',
30 'IntOption', 'FloatOption', 'ListOption', 'ChoiceOption',
31 'PathOption', 'ExtensionOption', 'OrderedExtensionsOption',
32 'ConfigurationError']
33
34
35 _TRUE_VALUES = ('yes', 'true', 'enabled', 'on', 'aye', '1', 1, True)
36
37 _use_default = object()
41
44 """Exception raised when a value in the configuration file is not valid."""
45 title = N_('Configuration Error')
46
47 - def __init__(self, message=None, title=None, show_traceback=False):
52
55 """Thin layer over `ConfigParser` from the Python standard library.
56
57 In addition to providing some convenience methods, the class remembers
58 the last modification time of the configuration file, and reparses it
59 when the file has changed.
60 """
61 - def __init__(self, filename, params={}):
69
71 """Return whether the configuration contains a section of the given
72 name.
73 """
74 return name in self.sections()
75
77 """Return the configuration section with the specified name."""
78 if name not in self._sections:
79 self._sections[name] = Section(self, name)
80 return self._sections[name]
81
83 return '<%s %r>' % (self.__class__.__name__, self.filename)
84
85 @property
87 """Return boolean indicating configuration file existence.
88
89 :since: 1.0.11
90 """
91 return os.path.isfile(self.filename)
92
93 - def get(self, section, key, default=''):
94 """Return the value of the specified option.
95
96 Valid default input is a string. Returns a string.
97 """
98 return self[section].get(key, default)
99
100 - def getbool(self, section, key, default=''):
101 """Return the specified option as boolean value.
102
103 If the value of the option is one of "yes", "true", "enabled", "on",
104 or "1", this method wll return `True`, otherwise `False`.
105
106 Valid default input is a string or a bool. Returns a bool.
107
108 (since Trac 0.9.3, "enabled" added in 0.11)
109 """
110 return self[section].getbool(key, default)
111
112 - def getint(self, section, key, default=''):
113 """Return the value of the specified option as integer.
114
115 If the specified option can not be converted to an integer, a
116 `ConfigurationError` exception is raised.
117
118 Valid default input is a string or an int. Returns an int.
119
120 (since Trac 0.10)
121 """
122 return self[section].getint(key, default)
123
124 - def getfloat(self, section, key, default=''):
125 """Return the value of the specified option as float.
126
127 If the specified option can not be converted to a float, a
128 `ConfigurationError` exception is raised.
129
130 Valid default input is a string, float or int. Returns a float.
131
132 (since Trac 0.12)
133 """
134 return self[section].getfloat(key, default)
135
136 - def getlist(self, section, key, default='', sep=',', keep_empty=False):
137 """Return a list of values that have been specified as a single
138 comma-separated option.
139
140 A different separator can be specified using the `sep` parameter. If
141 the `keep_empty` parameter is set to `True`, empty elements are
142 included in the list.
143
144 Valid default input is a string or a list. Returns a string.
145
146 (since Trac 0.10)
147 """
148 return self[section].getlist(key, default, sep, keep_empty)
149
150 - def getpath(self, section, key, default=''):
151 """Return a configuration value as an absolute path.
152
153 Relative paths are resolved relative to the location of this
154 configuration file.
155
156 Valid default input is a string. Returns a normalized path.
157
158 (enabled since Trac 0.11.5)
159 """
160 return self[section].getpath(key, default)
161
162 - def set(self, section, key, value):
163 """Change a configuration value.
164
165 These changes are not persistent unless saved with `save()`.
166 """
167 self[section].set(key, value)
168
170 """Returns a dictionary of the default configuration values
171 (''since 0.10'').
172
173 If `compmgr` is specified, return only options declared in components
174 that are enabled in the given `ComponentManager`.
175 """
176 defaults = {}
177 for (section, key), option in Option.get_registry(compmgr).items():
178 defaults.setdefault(section, {})[key] = option.default
179 return defaults
180
181 - def options(self, section, compmgr=None):
182 """Return a list of `(name, value)` tuples for every option in the
183 specified section.
184
185 This includes options that have default values that haven't been
186 overridden. If `compmgr` is specified, only return default option
187 values for components that are enabled in the given `ComponentManager`.
188 """
189 return self[section].options(compmgr)
190
191 - def remove(self, section, key):
192 """Remove the specified option."""
193 self[section].remove(key)
194
195 - def sections(self, compmgr=None, defaults=True):
208
209 - def has_option(self, section, option, defaults=True):
210 """Returns True if option exists in section in either the project
211 trac.ini or one of the parents, or is available through the Option
212 registry.
213
214 (since Trac 0.11)
215 """
216 section_str = _to_utf8(section)
217 if self.parser.has_section(section_str):
218 if _to_utf8(option) in self.parser.options(section_str):
219 return True
220 for parent in self.parents:
221 if parent.has_option(section, option, defaults=False):
222 return True
223 return defaults and (section, option) in Option.registry
224
226 """Write the configuration options to the primary file."""
227 if not self.filename:
228 return
229
230
231 sections = []
232 for section in self.sections():
233 section_str = _to_utf8(section)
234 options = []
235 for option in self[section]:
236 default_str = None
237 for parent in self.parents:
238 if parent.has_option(section, option, defaults=False):
239 default_str = _to_utf8(parent.get(section, option))
240 break
241 option_str = _to_utf8(option)
242 current_str = False
243 if self.parser.has_option(section_str, option_str):
244 current_str = self.parser.get(section_str, option_str)
245 if current_str is not False and current_str != default_str:
246 options.append((option_str, current_str))
247 if options:
248 sections.append((section_str, sorted(options)))
249
250
251 try:
252 wait_for_file_mtime_change(self.filename)
253 with AtomicFile(self.filename, 'w') as fileobj:
254 fileobj.write('# -*- coding: utf-8 -*-\n\n')
255 for section_str, options in sections:
256 fileobj.write('[%s]\n' % section_str)
257 section = to_unicode(section_str)
258 for key_str, val_str in options:
259 if to_unicode(key_str) in self[section].overridden:
260 fileobj.write('# %s = <inherited>\n' % key_str)
261 else:
262 val_str = val_str.replace(CRLF, '\n') \
263 .replace('\n', '\n ')
264 fileobj.write('%s = %s\n' % (key_str, val_str))
265 fileobj.write('\n')
266 self._old_sections = deepcopy(self.parser._sections)
267 except Exception:
268
269 self.parser._sections = deepcopy(self._old_sections)
270 raise
271
303
308
310 """Retrieve all default values and store them explicitly in the
311 configuration, so that they can be saved to file.
312
313 Values already set in the configuration are not overridden.
314 """
315 for (section, name), option in Option.get_registry(compmgr).items():
316 if not self.parser.has_option(_to_utf8(section), _to_utf8(name)):
317 value = option.default
318 if any(parent[section].contains(name, defaults=False)
319 for parent in self.parents):
320 value = None
321 if value is not None:
322 value = option.dumps(value)
323 self.set(section, name, value)
324
327 """Proxy for a specific configuration section.
328
329 Objects of this class should not be instantiated directly.
330 """
331 __slots__ = ['config', 'name', 'overridden', '_cache']
332
338
339 - def contains(self, key, defaults=True):
346
347 __contains__ = contains
348
349 - def iterate(self, compmgr=None, defaults=True):
350 """Iterate over the options in this section.
351
352 If `compmgr` is specified, only return default option values for
353 components that are enabled in the given `ComponentManager`.
354 """
355 options = set()
356 name_str = _to_utf8(self.name)
357 if self.config.parser.has_section(name_str):
358 for option_str in self.config.parser.options(name_str):
359 option = to_unicode(option_str)
360 options.add(option.lower())
361 yield option
362 for parent in self.config.parents:
363 for option in parent[self.name].iterate(defaults=False):
364 loption = option.lower()
365 if loption not in options:
366 options.add(loption)
367 yield option
368 if defaults:
369 for section, option in Option.get_registry(compmgr).keys():
370 if section == self.name and option.lower() not in options:
371 yield option
372
373 __iter__ = iterate
374
376 return '<%s [%s]>' % (self.__class__.__name__, self.name)
377
378 - def get(self, key, default=''):
409
410 - def getbool(self, key, default=''):
411 """Return the value of the specified option as boolean.
412
413 This method returns `True` if the option value is one of "yes", "true",
414 "enabled", "on", or non-zero numbers, ignoring case. Otherwise `False`
415 is returned.
416
417 Valid default input is a string or a bool. Returns a bool.
418 """
419 return as_bool(self.get(key, default))
420
421 - def getint(self, key, default=''):
422 """Return the value of the specified option as integer.
423
424 If the specified option can not be converted to an integer, a
425 `ConfigurationError` exception is raised.
426
427 Valid default input is a string or an int. Returns an int.
428 """
429 value = self.get(key, default)
430 if not value:
431 return 0
432 try:
433 return int(value)
434 except ValueError:
435 raise ConfigurationError(
436 _('[%(section)s] %(entry)s: expected integer, got %(value)s',
437 section=self.name, entry=key, value=repr(value)))
438
440 """Return the value of the specified option as float.
441
442 If the specified option can not be converted to a float, a
443 `ConfigurationError` exception is raised.
444
445 Valid default input is a string, float or int. Returns a float.
446 """
447 value = self.get(key, default)
448 if not value:
449 return 0.0
450 try:
451 return float(value)
452 except ValueError:
453 raise ConfigurationError(
454 _('[%(section)s] %(entry)s: expected float, got %(value)s',
455 section=self.name, entry=key, value=repr(value)))
456
457 - def getlist(self, key, default='', sep=',', keep_empty=True):
458 """Return a list of values that have been specified as a single
459 comma-separated option.
460
461 A different separator can be specified using the `sep` parameter. If
462 the `keep_empty` parameter is set to `False`, empty elements are omitted
463 from the list.
464
465 Valid default input is a string or a list. Returns a list.
466 """
467 value = self.get(key, default)
468 if not value:
469 return []
470 if isinstance(value, basestring):
471 items = [item.strip() for item in value.split(sep)]
472 else:
473 items = list(value)
474 if not keep_empty:
475 items = [item for item in items if item not in (None, '')]
476 return items
477
478 - def getpath(self, key, default=''):
479 """Return the value of the specified option as a path, relative to
480 the location of this configuration file.
481
482 Valid default input is a string. Returns a normalized path.
483 """
484 path = self.get(key, default)
485 if not path:
486 return default
487 if not os.path.isabs(path):
488 path = os.path.join(os.path.dirname(self.config.filename), path)
489 return os.path.normcase(os.path.realpath(path))
490
492 """Return `(key, value)` tuples for every option in the section.
493
494 This includes options that have default values that haven't been
495 overridden. If `compmgr` is specified, only return default option
496 values for components that are enabled in the given `ComponentManager`.
497 """
498 for key in self.iterate(compmgr):
499 yield key, self.get(key)
500
501 - def set(self, key, value):
502 """Change a configuration value.
503
504 These changes are not persistent unless saved with `save()`.
505 """
506 self._cache.pop(key, None)
507 name_str = _to_utf8(self.name)
508 key_str = _to_utf8(key)
509 if not self.config.parser.has_section(name_str):
510 self.config.parser.add_section(name_str)
511 if value is None:
512 self.overridden[key] = True
513 value_str = ''
514 else:
515 value_str = _to_utf8(value)
516 return self.config.parser.set(name_str, key_str, value_str)
517
519 """Delete a key from this section.
520
521 Like for `set()`, the changes won't persist until `save()` gets called.
522 """
523 name_str = _to_utf8(self.name)
524 if self.config.parser.has_section(name_str):
525 self._cache.pop(key, None)
526 self.config.parser.remove_option(_to_utf8(self.name), _to_utf8(key))
527
530 """Return the descriptor registry.
531
532 If `compmgr` is specified, only return descriptors for components that
533 are enabled in the given `ComponentManager`.
534 """
535 if compmgr is None:
536 return cls.registry
537
538 from trac.core import ComponentMeta
539 components = {}
540 for comp in ComponentMeta._components:
541 for attr in comp.__dict__.itervalues():
542 if isinstance(attr, cls):
543 components[attr] = comp
544
545 return dict(each for each in cls.registry.iteritems()
546 if each[1] not in components
547 or compmgr.is_enabled(components[each[1]]))
548
551 """Descriptor for configuration sections."""
552
553 registry = {}
554
555 @staticmethod
557 """Return the section registry, as a `dict` mapping section names to
558 `ConfigSection` objects.
559
560 If `compmgr` is specified, only return sections for components that are
561 enabled in the given `ComponentManager`.
562 """
563 return _get_registry(ConfigSection, compmgr)
564
565 - def __init__(self, name, doc, doc_domain='tracini'):
566 """Create the configuration section."""
567 self.name = name
568 self.registry[self.name] = self
569 self.__doc__ = cleandoc(doc)
570 self.doc_domain = doc_domain
571
572 - def __get__(self, instance, owner):
578
580 return '<%s [%s]>' % (self.__class__.__name__, self.name)
581
584 """Descriptor for configuration options."""
585
586 registry = {}
587
588 - def accessor(self, section, name, default):
590
591 @staticmethod
593 """Return the option registry, as a `dict` mapping `(section, key)`
594 tuples to `Option` objects.
595
596 If `compmgr` is specified, only return options for components that are
597 enabled in the given `ComponentManager`.
598 """
599 return _get_registry(Option, compmgr)
600
601 - def __init__(self, section, name, default=None, doc='',
602 doc_domain='tracini'):
603 """Create the configuration option.
604
605 :param section: the name of the configuration section this option
606 belongs to
607 :param name: the name of the option
608 :param default: the default value for the option
609 :param doc: documentation of the option
610 """
611 self.section = section
612 self.name = name
613 self.default = default
614 self.registry[(self.section, self.name)] = self
615 self.__doc__ = cleandoc(doc)
616 self.doc_domain = doc_domain
617
618 - def __get__(self, instance, owner):
626
627 - def __set__(self, instance, value):
628 raise AttributeError(_("Setting attribute is not allowed."))
629
631 return '<%s [%s] "%s">' % (self.__class__.__name__, self.section,
632 self.name)
633
635 """Return the value as a string to write to a trac.ini file"""
636 if value is None:
637 return ''
638 if value is True:
639 return 'enabled'
640 if value is False:
641 return 'disabled'
642 if isinstance(value, unicode):
643 return value
644 return to_unicode(value)
645
648 """Descriptor for boolean configuration options."""
649
650 - def accessor(self, section, name, default):
652
655 """Descriptor for integer configuration options."""
656
657 - def accessor(self, section, name, default):
659
662 """Descriptor for float configuration options."""
663
664 - def accessor(self, section, name, default):
666
669 """Descriptor for configuration options that contain multiple values
670 separated by a specific character.
671 """
672
673 - def __init__(self, section, name, default=None, sep=',', keep_empty=False,
674 doc='', doc_domain='tracini'):
678
679 - def accessor(self, section, name, default):
681
683 if isinstance(value, (list, tuple)):
684 return self.sep.join(Option.dumps(self, v) or '' for v in value)
685 return Option.dumps(self, value)
686
689 """Descriptor for configuration options providing a choice among a list
690 of items.
691
692 The default value is the first choice in the list.
693 """
694
695 - def __init__(self, section, name, choices, doc='', doc_domain='tracini'):
696 Option.__init__(self, section, name, _to_utf8(choices[0]), doc,
697 doc_domain)
698 self.choices = set(_to_utf8(choice).strip() for choice in choices)
699
700 - def accessor(self, section, name, default):
701 value = section.get(name, default)
702 if value not in self.choices:
703 raise ConfigurationError(
704 _('[%(section)s] %(entry)s: expected one of '
705 '(%(choices)s), got %(value)s',
706 section=section.name, entry=name, value=repr(value),
707 choices=', '.join('"%s"' % c
708 for c in sorted(self.choices))))
709 return value
710
713 """Descriptor for file system path configuration options.
714
715 Relative paths are resolved to absolute paths using the directory
716 containing the configuration file as the reference.
717 """
718
719 - def accessor(self, section, name, default):
721
724 """Name of a component implementing `interface`. Raises a
725 `ConfigurationError` if the component cannot be found in the list of
726 active components implementing the interface."""
727
728 - def __init__(self, section, name, interface, default=None, doc='',
729 doc_domain='tracini'):
732
733 - def __get__(self, instance, owner):
734 if instance is None:
735 return self
736 value = Option.__get__(self, instance, owner)
737 for impl in self.xtnpt.extensions(instance):
738 if impl.__class__.__name__ == value:
739 return impl
740 raise ConfigurationError(
741 tag_("Cannot find an implementation of the %(interface)s "
742 "interface named %(implementation)s. Please check "
743 "that the Component is enabled or update the option "
744 "%(option)s in trac.ini.",
745 interface=tag.tt(self.xtnpt.interface.__name__),
746 implementation=tag.tt(value),
747 option=tag.tt("[%s] %s" % (self.section, self.name))))
748
751 """A comma separated, ordered, list of components implementing `interface`.
752 Can be empty.
753
754 If `include_missing` is true (the default) all components implementing the
755 interface are returned, with those specified by the option ordered first."""
756
757 - def __init__(self, section, name, interface, default=None,
758 include_missing=True, doc='', doc_domain='tracini'):
763
764 - def __get__(self, instance, owner):
765 if instance is None:
766 return self
767 order = ListOption.__get__(self, instance, owner)
768 components = []
769 implementing_classes = []
770 for impl in self.xtnpt.extensions(instance):
771 implementing_classes.append(impl.__class__.__name__)
772 if self.include_missing or impl.__class__.__name__ in order:
773 components.append(impl)
774 not_found = sorted(set(order) - set(implementing_classes))
775 if not_found:
776 raise ConfigurationError(
777 tag_("Cannot find implementation(s) of the %(interface)s "
778 "interface named %(implementation)s. Please check "
779 "that the Component is enabled or update the option "
780 "%(option)s in trac.ini.",
781 interface=tag.tt(self.xtnpt.interface.__name__),
782 implementation=tag(
783 (', ' if idx != 0 else None, tag.tt(impl))
784 for idx, impl in enumerate(not_found)),
785 option=tag.tt("[%s] %s" % (self.section, self.name))))
786
787 def compare(x, y):
788 x, y = x.__class__.__name__, y.__class__.__name__
789 if x not in order:
790 return int(y in order)
791 if y not in order:
792 return -int(x in order)
793 return cmp(order.index(x), order.index(y))
794 components.sort(compare)
795 return components
796
799 """trac-admin command provider for trac.ini administration."""
800
801 implements(IAdminCommandProvider)
802
803
804
806 yield ('config get', '<section> <option>',
807 'Get the value of the given option in "trac.ini"',
808 self._complete_config, self._do_get)
809 yield ('config remove', '<section> <option>',
810 'Remove the specified option from "trac.ini"',
811 self._complete_config, self._do_remove)
812 yield ('config set', '<section> <option> <value>',
813 'Set the value for the given option in "trac.ini"',
814 self._complete_config, self._do_set)
815
821
822 - def _do_get(self, section, option):
828
829 - def _do_set(self, section, option, value):
834
844