1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 import copy
16 import os.path
17 import re
18 from ConfigParser import ConfigParser, ParsingError
19
20 from genshi.builder import tag
21
22 from trac.admin import AdminCommandError, IAdminCommandProvider
23 from trac.core import Component, ExtensionPoint, TracError, implements
24 from trac.util import AtomicFile, as_bool
25 from trac.util.compat import OrderedDict, wait_for_file_mtime_change
26 from trac.util.text import cleandoc, printout, to_unicode, to_utf8
27 from trac.util.translation import N_, _, dgettext, tag_
28
29 __all__ = ['Configuration', 'ConfigSection', 'Option', 'BoolOption',
30 'IntOption', 'FloatOption', 'ListOption', 'ChoiceOption',
31 'PathOption', 'ExtensionOption', 'OrderedExtensionsOption',
32 'ConfigurationError']
33
34 _use_default = object()
38 return int(value or 0)
39
42 return float(value or 0.0)
43
46 if not value:
47 return []
48 if isinstance(value, basestring):
49 if isinstance(sep, (list, tuple)):
50 splitted = re.split('|'.join(map(re.escape, sep)), value)
51 else:
52 splitted = value.split(sep)
53 items = [item.strip() for item in splitted]
54 else:
55 items = list(value)
56 if not keep_empty:
57 items = [item for item in items if item not in (None, '')]
58 return items
59
62 doc = to_unicode(option_or_section.__doc__)
63 if doc:
64 doc = dgettext(option_or_section.doc_domain, doc,
65 **(option_or_section.doc_args or {}))
66 return doc
67
70 """Exception raised when a value in the configuration file is not valid."""
71 title = N_("Configuration Error")
72
73 - def __init__(self, message=None, title=None, show_traceback=False):
78
81 """A Unicode-aware version of ConfigParser. Arguments are encoded to
82 UTF-8 and return values are decoded from UTF-8.
83 """
84
85
86
87
88
89
90
91
92 - def __init__(self, ignorecase_option=True, **kwargs):
93 self._ignorecase_option = ignorecase_option
94 dict_type = kwargs.pop('dict_type', None) or OrderedDict
95 ConfigParser.__init__(self, dict_type=dict_type, **kwargs)
96
101
104
106 section_str = to_utf8(section)
107 ConfigParser.add_section(self, section_str)
108
110 section_str = to_utf8(section)
111 return ConfigParser.has_section(self, section_str)
112
116
117 - def get(self, section, option, raw=False, vars=None):
118 section_str = to_utf8(section)
119 option_str = to_utf8(option)
120 return to_unicode(ConfigParser.get(self, section_str,
121 option_str, raw, vars))
122
123 - def items(self, section, raw=False, vars=None):
124 section_str = to_utf8(section)
125 return [(to_unicode(k), to_unicode(v))
126 for k, v in ConfigParser.items(self, section_str, raw, vars)]
127
129 section_str = to_utf8(section)
130 option_str = to_utf8(option)
131 return ConfigParser.has_option(self, section_str, option_str)
132
133 - def set(self, section, option, value=None):
134 section_str = to_utf8(section)
135 option_str = to_utf8(option)
136 value_str = to_utf8(value) if value is not None else ''
137 ConfigParser.set(self, section_str, option_str, value_str)
138
140 section_str = to_utf8(section)
141 option_str = to_utf8(option)
142 ConfigParser.remove_option(self, section_str, option_str)
143
145 section_str = to_utf8(section)
146 ConfigParser.remove_section(self, section_str)
147
149 parser = self.__class__()
150 parser._sections = copy.copy(self._sections)
151 return parser
152
154 parser = self.__class__()
155 parser._sections = copy.deepcopy(self._sections)
156 return parser
157
160 """Thin layer over `ConfigParser` from the Python standard library.
161
162 In addition to providing some convenience methods, the class remembers
163 the last modification time of the configuration file, and reparses it
164 when the file has changed.
165 """
166 - def __init__(self, filename, params={}):
174
176 return '<%s %r>' % (self.__class__.__name__, self.filename)
177
179 """Return whether the configuration contains a section of the given
180 name.
181 """
182 return name in self.sections()
183
185 """Return the configuration section with the specified name."""
186 if name not in self._sections:
187 self._sections[name] = Section(self, name)
188 return self._sections[name]
189
190 @property
192 """Return boolean indicating configuration file existence.
193
194 :since: 1.0.11
195 """
196 return os.path.isfile(self.filename)
197
198 - def get(self, section, key, default=''):
199 """Return the value of the specified option.
200
201 Valid default input is a string. Returns a string.
202 """
203 return self[section].get(key, default)
204
205 - def getbool(self, section, key, default=''):
206 """Return the specified option as boolean value.
207
208 If the value of the option is one of "yes", "true", "enabled", "on",
209 or "1", this method wll return `True`, otherwise `False`.
210
211 Valid default input is a string or a bool. Returns a bool.
212 """
213 return self[section].getbool(key, default)
214
215 - def getint(self, section, key, default=''):
216 """Return the value of the specified option as integer.
217
218 If the specified option can not be converted to an integer, a
219 `ConfigurationError` exception is raised.
220
221 Valid default input is a string or an int. Returns an int.
222 """
223 return self[section].getint(key, default)
224
225 - def getfloat(self, section, key, default=''):
226 """Return the value of the specified option as float.
227
228 If the specified option can not be converted to a float, a
229 `ConfigurationError` exception is raised.
230
231 Valid default input is a string, float or int. Returns a float.
232 """
233 return self[section].getfloat(key, default)
234
235 - def getlist(self, section, key, default='', sep=',', keep_empty=False):
236 """Return a list of values that have been specified as a single
237 comma-separated option.
238
239 A different separator can be specified using the `sep` parameter. The
240 `sep` parameter can specify multiple values using a list or a tuple.
241 If the `keep_empty` parameter is set to `True`, empty elements are
242 included in the list.
243
244 Valid default input is a string or a list. Returns a string.
245 """
246 return self[section].getlist(key, default, sep, keep_empty)
247
248 - def getpath(self, section, key, default=''):
249 """Return a configuration value as an absolute path.
250
251 Relative paths are resolved relative to the location of this
252 configuration file.
253
254 Valid default input is a string. Returns a normalized path.
255 """
256 return self[section].getpath(key, default)
257
258 - def set(self, section, key, value):
259 """Change a configuration value.
260
261 These changes are not persistent unless saved with `save()`.
262 """
263 self[section].set(key, value)
264
266 """Returns a dictionary of the default configuration values.
267
268 If `compmgr` is specified, return only options declared in components
269 that are enabled in the given `ComponentManager`.
270 """
271 defaults = {}
272 for (section, key), option in \
273 Option.get_registry(compmgr).iteritems():
274 defaults.setdefault(section, {})[key] = \
275 option.dumps(option.default)
276 return defaults
277
278 - def options(self, section, compmgr=None):
279 """Return a list of `(name, value)` tuples for every option in the
280 specified section.
281
282 This includes options that have default values that haven't been
283 overridden. If `compmgr` is specified, only return default option
284 values for components that are enabled in the given
285 `ComponentManager`.
286 """
287 return self[section].options(compmgr)
288
289 - def remove(self, section, key):
290 """Remove the specified option."""
291 self[section].remove(key)
292
293 - def sections(self, compmgr=None, defaults=True, empty=False):
311
312 - def has_option(self, section, option, defaults=True):
313 """Returns True if option exists in section in either the project
314 trac.ini or one of the parents, or is available through the Option
315 registry.
316 """
317 return self[section].contains(option, defaults)
318
320 """Write the configuration options to the primary file."""
321
322 all_options = {}
323 for (section, name), option in Option.get_registry().iteritems():
324 all_options.setdefault(section, {})[name] = option
325
326 def normalize(section, name, value):
327 option = all_options.get(section, {}).get(name)
328 return option.normalize(value) if option else value
329
330 sections = []
331 for section in self.sections():
332 options = []
333 for option in self[section]:
334 default = None
335 for parent in self.parents:
336 if parent.has_option(section, option, defaults=False):
337 default = normalize(section, option,
338 parent.get(section, option))
339 break
340 if self.parser.has_option(section, option):
341 current = normalize(section, option,
342 self.parser.get(section, option))
343 if current != default:
344 options.append((option, current))
345 if options:
346 sections.append((section, sorted(options)))
347
348
349 parser = UnicodeConfigParser()
350 for section, options in sections:
351 parser.add_section(section)
352 for key, val in options:
353 parser.set(section, key, val)
354
355 try:
356 self._write(parser)
357 except Exception:
358
359 self.parser = copy.deepcopy(self._pristine_parser)
360 raise
361 else:
362 self._pristine_parser = copy.deepcopy(self.parser)
363
365 if not self.filename or not self.exists:
366 return False
367
368 changed = False
369 modtime = os.path.getmtime(self.filename)
370 if force or modtime != self._lastmtime:
371 self.parser = UnicodeConfigParser()
372 try:
373 if not self.parser.read(self.filename):
374 raise TracError(_("Error reading '%(file)s', make sure "
375 "it is readable.", file=self.filename))
376 except ParsingError as e:
377 raise TracError(e)
378 self._lastmtime = modtime
379 self._pristine_parser = copy.deepcopy(self.parser)
380 changed = True
381
382 if changed:
383 self.parents = self._get_parents()
384 else:
385 for parent in self.parents:
386 changed |= parent.parse_if_needed(force=force)
387
388 if changed:
389 self._sections = {}
390 return changed
391
396
398 """Retrieve all default values and store them explicitly in the
399 configuration, so that they can be saved to file.
400
401 Values already set in the configuration are not overwritten.
402 """
403 def set_option_default(option):
404 section = option.section
405 name = option.name
406 if not self.has_option(section, name, defaults=False):
407 value = option.dumps(option.default)
408 self.set(section, name, value)
409
410 if component:
411 if component.endswith('.*'):
412 component = component[:-2]
413 component = component.lower().split('.')
414 from trac.core import ComponentMeta
415 for cls in ComponentMeta._components:
416 clsname = (cls.__module__ + '.' + cls.__name__).lower() \
417 .split('.')
418 if clsname[:len(component)] == component:
419 for option in cls.__dict__.itervalues():
420 if isinstance(option, Option):
421 set_option_default(option)
422 else:
423 for option in Option.get_registry(compmgr).itervalues():
424 set_option_default(option)
425
436
444
447 """Proxy for a specific configuration section.
448
449 Objects of this class should not be instantiated directly.
450 """
451 __slots__ = ['config', 'name', '_cache']
452
457
459 return '<%s [%s]>' % (self.__class__.__name__, self.name)
460
461 - def contains(self, key, defaults=True):
468
469 __contains__ = contains
470
471 - def iterate(self, compmgr=None, defaults=True):
492
493 __iter__ = iterate
494
495 - def get(self, key, default=''):
521
522 - def getbool(self, key, default=''):
523 """Return the value of the specified option as boolean.
524
525 This method returns `True` if the option value is one of "yes",
526 "true", "enabled", "on", or non-zero numbers, ignoring case.
527 Otherwise `False` is returned.
528
529 Valid default input is a string or a bool. Returns a bool.
530 """
531 return as_bool(self.get(key, default))
532
533 - def getint(self, key, default=''):
534 """Return the value of the specified option as integer.
535
536 If the specified option can not be converted to an integer, a
537 `ConfigurationError` exception is raised.
538
539 Valid default input is a string or an int. Returns an int.
540 """
541 value = self.get(key, default)
542 try:
543 return _getint(value)
544 except ValueError:
545 raise ConfigurationError(
546 _('[%(section)s] %(entry)s: expected integer,'
547 ' got %(value)s', section=self.name, entry=key,
548 value=repr(value)))
549
551 """Return the value of the specified option as float.
552
553 If the specified option can not be converted to a float, a
554 `ConfigurationError` exception is raised.
555
556 Valid default input is a string, float or int. Returns a float.
557 """
558 value = self.get(key, default)
559 try:
560 return _getfloat(value)
561 except ValueError:
562 raise ConfigurationError(
563 _('[%(section)s] %(entry)s: expected float,'
564 ' got %(value)s', section=self.name, entry=key,
565 value=repr(value)))
566
567 - def getlist(self, key, default='', sep=',', keep_empty=True):
568 """Return a list of values that have been specified as a single
569 comma-separated option.
570
571 A different separator can be specified using the `sep` parameter. The
572 `sep` parameter can specify multiple values using a list or a tuple.
573 If the `keep_empty` parameter is set to `True`, empty elements are
574 included in the list.
575
576 Valid default input is a string or a list. Returns a list.
577 """
578 return _getlist(self.get(key, default), sep, keep_empty)
579
580 - def getpath(self, key, default=''):
581 """Return the value of the specified option as a path, relative to
582 the location of this configuration file.
583
584 Valid default input is a string. Returns a normalized path.
585 """
586 path = self.get(key, default)
587 if not path:
588 return default
589 if not os.path.isabs(path):
590 path = os.path.join(os.path.dirname(self.config.filename), path)
591 return os.path.normcase(os.path.realpath(path))
592
594 """Return `(key, value)` tuples for every option in the section.
595
596 This includes options that have default values that haven't been
597 overridden. If `compmgr` is specified, only return default option
598 values for components that are enabled in the given `ComponentManager`.
599 """
600 for key in self.iterate(compmgr):
601 yield key, self.get(key)
602
603 - def set(self, key, value):
604 """Change a configuration value.
605
606 These changes are not persistent unless saved with `save()`.
607 """
608 self._cache.pop(key, None)
609 if not self.config.parser.has_section(self.name):
610 self.config.parser.add_section(self.name)
611 return self.config.parser.set(self.name, key, value)
612
614 """Delete a key from this section.
615
616 Like for `set()`, the changes won't persist until `save()` gets
617 called.
618 """
619 if self.config.parser.has_section(self.name):
620 self._cache.pop(key, None)
621 self.config.parser.remove_option(self.name, key)
622
625 """Return the descriptor registry.
626
627 If `compmgr` is specified, only return descriptors for components that
628 are enabled in the given `ComponentManager`.
629 """
630 if compmgr is None:
631 return cls.registry
632
633 from trac.core import ComponentMeta
634 components = {}
635 for comp in ComponentMeta._components:
636 for attr in comp.__dict__.itervalues():
637 if isinstance(attr, cls):
638 components[attr] = comp
639
640 return dict(each for each in cls.registry.iteritems()
641 if each[1] not in components
642 or compmgr.is_enabled(components[each[1]]))
643
646 """Descriptor for configuration sections."""
647
648 registry = {}
649
650 @staticmethod
652 """Return the section registry, as a `dict` mapping section names to
653 `ConfigSection` objects.
654
655 If `compmgr` is specified, only return sections for components that
656 are enabled in the given `ComponentManager`.
657 """
658 return _get_registry(ConfigSection, compmgr)
659
660 - def __init__(self, name, doc, doc_domain='tracini', doc_args=None):
661 """Create the configuration section."""
662 self.name = name
663 self.registry[self.name] = self
664 self.__doc__ = cleandoc(doc)
665 self.doc_domain = doc_domain
666 self.doc_args = doc_args
667
668 - def __get__(self, instance, owner):
674
676 return '<%s [%s]>' % (self.__class__.__name__, self.name)
677
678 @property
680 """Return localized document of the section"""
681 return _getdoc(self)
682
685 """Descriptor for configuration options."""
686
687 registry = {}
688
689 - def accessor(self, section, name, default):
691
692 @staticmethod
694 """Return the option registry, as a `dict` mapping `(section, key)`
695 tuples to `Option` objects.
696
697 If `compmgr` is specified, only return options for components that are
698 enabled in the given `ComponentManager`.
699 """
700 return _get_registry(Option, compmgr)
701
702 - def __init__(self, section, name, default=None, doc='',
703 doc_domain='tracini', doc_args=None):
704 """Create the configuration option.
705
706 :param section: the name of the configuration section this option
707 belongs to
708 :param name: the name of the option
709 :param default: the default value for the option
710 :param doc: documentation of the option
711 """
712 self.section = section
713 self.name = name
714 self.default = self.normalize(default)
715 self.registry[(self.section, self.name)] = self
716 self.__doc__ = cleandoc(doc)
717 self.doc_domain = doc_domain
718 self.doc_args = doc_args
719
720 - def __get__(self, instance, owner):
728
729 - def __set__(self, instance, value):
730 raise AttributeError(_("Setting attribute is not allowed."))
731
733 return '<%s [%s] %r>' % (self.__class__.__name__, self.section,
734 self.name)
735
736 @property
738 """Return localized document of the option"""
739 return _getdoc(self)
740
742 """Return the value as a string to write to a trac.ini file"""
743 if value is None:
744 return ''
745 if value is True:
746 return 'enabled'
747 if value is False:
748 return 'disabled'
749 if isinstance(value, unicode):
750 return value
751 return to_unicode(value)
752
754 """Normalize the given value to write to a trac.ini file"""
755 return self.dumps(value)
756
759 """Descriptor for boolean configuration options."""
760
761 - def accessor(self, section, name, default):
763
765 if value not in (True, False):
766 value = as_bool(value)
767 return self.dumps(value)
768
771 """Descriptor for integer configuration options."""
772
773 - def accessor(self, section, name, default):
775
777 try:
778 value = _getint(value)
779 except ValueError:
780 pass
781 return self.dumps(value)
782
785 """Descriptor for float configuration options."""
786
787 - def accessor(self, section, name, default):
789
791 try:
792 value = _getfloat(value)
793 except ValueError:
794 pass
795 return self.dumps(value)
796
799 """Descriptor for configuration options that contain multiple values
800 separated by a specific character.
801 """
802
803 - def __init__(self, section, name, default=None, sep=',', keep_empty=False,
804 doc='', doc_domain='tracini', doc_args=None):
809
810 - def accessor(self, section, name, default):
812
814 if isinstance(value, (list, tuple)):
815 sep = self.sep
816 if isinstance(sep, (list, tuple)):
817 sep = sep[0]
818 return sep.join(Option.dumps(self, v) or '' for v in value)
819 return Option.dumps(self, value)
820
822 return self.dumps(_getlist(value, self.sep, self.keep_empty))
823
826 """Descriptor for configuration options providing a choice among a list
827 of items.
828
829 The default value is the first choice in the list.
830 """
831
832 - def __init__(self, section, name, choices, doc='', doc_domain='tracini',
833 doc_args=None, case_sensitive=True):
838
839 - def accessor(self, section, name, default):
840 value = section.get(name, default)
841 choices = self.choices[:]
842 if not self.case_sensitive:
843 choices = map(unicode.lower, choices)
844 value = value.lower()
845 try:
846 idx = choices.index(value)
847 except ValueError:
848 raise ConfigurationError(
849 _('[%(section)s] %(entry)s: expected one of '
850 '(%(choices)s), got %(value)s',
851 section=section.name, entry=name, value=repr(value),
852 choices=', '.join('"%s"' % c
853 for c in sorted(self.choices))))
854 return self.choices[idx]
855
858 """Descriptor for file system path configuration options.
859
860 Relative paths are resolved to absolute paths using the directory
861 containing the configuration file as the reference.
862 """
863
864 - def accessor(self, section, name, default):
866
869 """Name of a component implementing `interface`. Raises a
870 `ConfigurationError` if the component cannot be found in the list of
871 active components implementing the interface."""
872
873 - def __init__(self, section, name, interface, default=None, doc='',
874 doc_domain='tracini', doc_args=None):
878
879 - def __get__(self, instance, owner):
880 if instance is None:
881 return self
882 value = Option.__get__(self, instance, owner)
883 for impl in self.xtnpt.extensions(instance):
884 if impl.__class__.__name__ == value:
885 return impl
886 raise ConfigurationError(
887 tag_("Cannot find an implementation of the %(interface)s "
888 "interface named %(implementation)s. Please check "
889 "that the Component is enabled or update the option "
890 "%(option)s in trac.ini.",
891 interface=tag.code(self.xtnpt.interface.__name__),
892 implementation=tag.code(value),
893 option=tag.code("[%s] %s" % (self.section, self.name))))
894
897 """A comma separated, ordered, list of components implementing
898 `interface`. Can be empty.
899
900 If `include_missing` is true (the default) all components implementing the
901 interface are returned, with those specified by the option ordered first.
902 """
903
904 - def __init__(self, section, name, interface, default=None,
905 include_missing=True, doc='', doc_domain='tracini',
906 doc_args=None):
911
912 - def __get__(self, instance, owner):
913 if instance is None:
914 return self
915 order = ListOption.__get__(self, instance, owner)
916 components = []
917 implementing_classes = []
918 for impl in self.xtnpt.extensions(instance):
919 implementing_classes.append(impl.__class__.__name__)
920 if self.include_missing or impl.__class__.__name__ in order:
921 components.append(impl)
922 not_found = sorted(set(order) - set(implementing_classes))
923 if not_found:
924 raise ConfigurationError(
925 tag_("Cannot find implementation(s) of the %(interface)s "
926 "interface named %(implementation)s. Please check "
927 "that the Component is enabled or update the option "
928 "%(option)s in trac.ini.",
929 interface=tag.code(self.xtnpt.interface.__name__),
930 implementation=tag(
931 (', ' if idx != 0 else None, tag.code(impl))
932 for idx, impl in enumerate(not_found)),
933 option=tag.code("[%s] %s" % (self.section, self.name))))
934
935 def compare(x, y):
936 x, y = x.__class__.__name__, y.__class__.__name__
937 if x not in order:
938 return int(y in order)
939 if y not in order:
940 return -int(x in order)
941 return cmp(order.index(x), order.index(y))
942 components.sort(compare)
943 return components
944
947 """trac-admin command provider for trac.ini administration."""
948
949 implements(IAdminCommandProvider)
950
951
952
954 yield ('config get', '<section> <option>',
955 'Get the value of the given option in "trac.ini"',
956 self._complete_config, self._do_get)
957 yield ('config remove', '<section> <option>',
958 'Remove the specified option from "trac.ini"',
959 self._complete_config, self._do_remove)
960 yield ('config set', '<section> <option> <value>',
961 'Set the value for the given option in "trac.ini"',
962 self._complete_config, self._do_set)
963
969
970 - def _do_get(self, section, option):
976
977 - def _do_set(self, section, option, value):
984
994
997 """Returns a list of dictionaries containing the `name` and `options`
998 of each configuration section. The value of `options` is a list of
999 dictionaries containing the `name`, `value` and `modified` state of
1000 each configuration option. The `modified` value is True if the value
1001 differs from its default.
1002
1003 :since: version 1.1.2
1004 """
1005 all_options = {}
1006 for (section, name), option in \
1007 Option.get_registry(env.compmgr).iteritems():
1008 all_options.setdefault(section, {})[name] = option
1009 sections = []
1010 for section in env.config.sections(env.compmgr):
1011 options = []
1012 for name, value in env.config.options(section, env.compmgr):
1013 registered = all_options.get(section, {}).get(name)
1014 if registered:
1015 default = registered.default
1016 normalized = registered.normalize(value)
1017 else:
1018 default = u''
1019 normalized = unicode(value)
1020 options.append({'name': name, 'value': value,
1021 'modified': normalized != default})
1022 options.sort(key=lambda o: o['name'])
1023 sections.append({'name': section, 'options': options})
1024 sections.sort(key=lambda s: s['name'])
1025 return sections
1026