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 trac.admin import AdminCommandError, IAdminCommandProvider
21 from trac.core import Component, ExtensionPoint, TracError, implements
22 from trac.util import AtomicFile, as_bool
23 from trac.util.compat import wait_for_file_mtime_change
24 from trac.util.html import tag
25 from trac.util.text import cleandoc, printout, to_unicode, to_utf8
26 from trac.util.translation import N_, _, dgettext, tag_
27
28 __all__ = ['Configuration', 'ConfigSection', 'Option', 'BoolOption',
29 'IntOption', 'FloatOption', 'ListOption', 'ChoiceOption',
30 'PathOption', 'ExtensionOption', 'OrderedExtensionsOption',
31 'ConfigurationError']
32
33 _use_default = object()
37 return int(value or 0)
38
41 return float(value or 0.0)
42
45 if not value:
46 return []
47 if isinstance(value, basestring):
48 if isinstance(sep, (list, tuple)):
49 splitted = re.split('|'.join(map(re.escape, sep)), value)
50 else:
51 splitted = value.split(sep)
52 items = [item.strip() for item in splitted]
53 else:
54 items = list(value)
55 if not keep_empty:
56 items = [item for item in items if item not in (None, '')]
57 return items
58
61 doc = to_unicode(option_or_section.__doc__)
62 if doc:
63 doc = dgettext(option_or_section.doc_domain, doc,
64 **(option_or_section.doc_args or {}))
65 return doc
66
69 """Exception raised when a value in the configuration file is not valid."""
70 title = N_("Configuration Error")
71
72 - def __init__(self, message=None, title=None, show_traceback=False):
77
80 """A Unicode-aware version of ConfigParser. Arguments are encoded to
81 UTF-8 and return values are decoded from UTF-8.
82 """
83
84
85
86
87
88
89
90
91 - def __init__(self, ignorecase_option=True, **kwargs):
92 self._ignorecase_option = ignorecase_option
93 ConfigParser.__init__(self, **kwargs)
94
99
102
104 section_str = to_utf8(section)
105 ConfigParser.add_section(self, section_str)
106
108 section_str = to_utf8(section)
109 return ConfigParser.has_section(self, section_str)
110
114
115 - def get(self, section, option, raw=False, vars=None):
116 section_str = to_utf8(section)
117 option_str = to_utf8(option)
118 return to_unicode(ConfigParser.get(self, section_str,
119 option_str, raw, vars))
120
121 - def items(self, section, raw=False, vars=None):
122 section_str = to_utf8(section)
123 return [(to_unicode(k), to_unicode(v))
124 for k, v in ConfigParser.items(self, section_str, raw, vars)]
125
127 section_str = to_utf8(section)
128 option_str = to_utf8(option)
129 return ConfigParser.has_option(self, section_str, option_str)
130
131 - def set(self, section, option, value=None):
132 section_str = to_utf8(section)
133 option_str = to_utf8(option)
134 value_str = to_utf8(value) if value is not None else ''
135 ConfigParser.set(self, section_str, option_str, value_str)
136
138 section_str = to_utf8(section)
139 option_str = to_utf8(option)
140 ConfigParser.remove_option(self, section_str, option_str)
141
143 section_str = to_utf8(section)
144 ConfigParser.remove_section(self, section_str)
145
147 parser = self.__class__()
148 parser._sections = copy.copy(self._sections)
149 return parser
150
152 parser = self.__class__()
153 parser._sections = copy.deepcopy(self._sections)
154 return parser
155
158 """Thin layer over `ConfigParser` from the Python standard library.
159
160 In addition to providing some convenience methods, the class remembers
161 the last modification time of the configuration file, and reparses it
162 when the file has changed.
163 """
164 - def __init__(self, filename, params={}):
172
174 return '<%s %r>' % (self.__class__.__name__, self.filename)
175
177 """Return whether the configuration contains a section of the given
178 name.
179 """
180 return name in self.sections()
181
183 """Return the configuration section with the specified name."""
184 if name not in self._sections:
185 self._sections[name] = Section(self, name)
186 return self._sections[name]
187
189 self._sections.pop(name, None)
190 self.parser.remove_section(name)
191
192 @property
194 """Return boolean indicating configuration file existence.
195
196 :since: 1.0.11
197 """
198 return os.path.isfile(self.filename)
199
200 - def get(self, section, key, default=''):
201 """Return the value of the specified option.
202
203 Valid default input is a string. Returns a string.
204 """
205 return self[section].get(key, default)
206
207 - def getbool(self, section, key, default=''):
208 """Return the specified option as boolean value.
209
210 If the value of the option is one of "yes", "true", "enabled", "on",
211 or "1", this method wll return `True`, otherwise `False`.
212
213 Valid default input is a string or a bool. Returns a bool.
214 """
215 return self[section].getbool(key, default)
216
217 - def getint(self, section, key, default=''):
218 """Return the value of the specified option as integer.
219
220 If the specified option can not be converted to an integer, a
221 `ConfigurationError` exception is raised.
222
223 Valid default input is a string or an int. Returns an int.
224 """
225 return self[section].getint(key, default)
226
227 - def getfloat(self, section, key, default=''):
228 """Return the value of the specified option as float.
229
230 If the specified option can not be converted to a float, a
231 `ConfigurationError` exception is raised.
232
233 Valid default input is a string, float or int. Returns a float.
234 """
235 return self[section].getfloat(key, default)
236
237 - def getlist(self, section, key, default='', sep=',', keep_empty=False):
238 """Return a list of values that have been specified as a single
239 comma-separated option.
240
241 A different separator can be specified using the `sep` parameter. The
242 `sep` parameter can specify multiple values using a list or a tuple.
243 If the `keep_empty` parameter is set to `True`, empty elements are
244 included in the list.
245
246 Valid default input is a string or a list. Returns a string.
247 """
248 return self[section].getlist(key, default, sep, keep_empty)
249
250 - def getpath(self, section, key, default=''):
251 """Return a configuration value as an absolute path.
252
253 Relative paths are resolved relative to the location of this
254 configuration file.
255
256 Valid default input is a string. Returns a normalized path.
257 """
258 return self[section].getpath(key, default)
259
260 - def set(self, section, key, value):
261 """Change a configuration value.
262
263 These changes are not persistent unless saved with `save()`.
264 """
265 self[section].set(key, value)
266
268 """Returns a dictionary of the default configuration values.
269
270 If `compmgr` is specified, return only options declared in components
271 that are enabled in the given `ComponentManager`.
272 """
273 defaults = {}
274 for (section, key), option in \
275 Option.get_registry(compmgr).iteritems():
276 defaults.setdefault(section, {})[key] = \
277 option.dumps(option.default)
278 return defaults
279
280 - def options(self, section, compmgr=None):
281 """Return a list of `(name, value)` tuples for every option in the
282 specified section.
283
284 This includes options that have default values that haven't been
285 overridden. If `compmgr` is specified, only return default option
286 values for components that are enabled in the given
287 `ComponentManager`.
288 """
289 return self[section].options(compmgr)
290
291 - def remove(self, section, key=None):
292 """Remove the specified option or section."""
293 if key:
294 self[section].remove(key)
295 else:
296 del self[section]
297
298 - def sections(self, compmgr=None, defaults=True, empty=False):
316
317 - def has_option(self, section, option, defaults=True):
318 """Returns True if option exists in section in either the project
319 trac.ini or one of the parents, or is available through the Option
320 registry.
321 """
322 return self[section].contains(option, defaults)
323
325 """Write the configuration options to the primary file."""
326
327 all_options = {}
328 for (section, name), option in Option.get_registry().iteritems():
329 all_options.setdefault(section, {})[name] = option
330
331 def normalize(section, name, value):
332 option = all_options.get(section, {}).get(name)
333 return option.normalize(value) if option else value
334
335 sections = []
336 for section in self.sections():
337 options = []
338 for option in self[section]:
339 default = None
340 for parent in self.parents:
341 if parent.has_option(section, option, defaults=False):
342 default = normalize(section, option,
343 parent.get(section, option))
344 break
345 if self.parser.has_option(section, option):
346 current = normalize(section, option,
347 self.parser.get(section, option))
348 if current != default:
349 options.append((option, current))
350 if options:
351 sections.append((section, sorted(options)))
352
353
354 parser = UnicodeConfigParser()
355 for section, options in sections:
356 parser.add_section(section)
357 for key, val in options:
358 parser.set(section, key, val)
359
360 try:
361 self._write(parser)
362 except Exception:
363
364 self.parser = copy.deepcopy(self._pristine_parser)
365 raise
366 else:
367 self._pristine_parser = copy.deepcopy(self.parser)
368
370 if not self.filename or not self.exists:
371 return False
372
373 changed = False
374 modtime = os.path.getmtime(self.filename)
375 if force or modtime != self._lastmtime:
376 self.parser = UnicodeConfigParser()
377 try:
378 if not self.parser.read(self.filename):
379 raise TracError(_("Error reading '%(file)s', make sure "
380 "it is readable.", file=self.filename))
381 except ParsingError as e:
382 raise TracError(e)
383 self._lastmtime = modtime
384 self._pristine_parser = copy.deepcopy(self.parser)
385 changed = True
386
387 if changed:
388 self.parents = self._get_parents()
389 else:
390 for parent in self.parents:
391 changed |= parent.parse_if_needed(force=force)
392
393 if changed:
394 self._sections = {}
395 return changed
396
401
403 """Retrieve all default values and store them explicitly in the
404 configuration, so that they can be saved to file.
405
406 Values already set in the configuration are not overwritten.
407 """
408 def set_option_default(option):
409 section = option.section
410 name = option.name
411 if not self.has_option(section, name, defaults=False):
412 value = option.dumps(option.default)
413 self.set(section, name, value)
414
415 if component:
416 if component.endswith('.*'):
417 component = component[:-2]
418 component = component.lower().split('.')
419 from trac.core import ComponentMeta
420 for cls in ComponentMeta._components:
421 clsname = (cls.__module__ + '.' + cls.__name__).lower() \
422 .split('.')
423 if clsname[:len(component)] == component:
424 for option in cls.__dict__.itervalues():
425 if isinstance(option, Option):
426 set_option_default(option)
427 else:
428 for option in Option.get_registry(compmgr).itervalues():
429 set_option_default(option)
430
441
449
452 """Proxy for a specific configuration section.
453
454 Objects of this class should not be instantiated directly.
455 """
456 __slots__ = ['config', 'name', '_cache']
457
462
464 return '<%s [%s]>' % (self.__class__.__name__, self.name)
465
466 - def contains(self, key, defaults=True):
473
474 __contains__ = contains
475
476 - def iterate(self, compmgr=None, defaults=True):
497
498 __iter__ = iterate
499
500 - def get(self, key, default=''):
526
527 - def getbool(self, key, default=''):
528 """Return the value of the specified option as boolean.
529
530 This method returns `True` if the option value is one of "yes",
531 "true", "enabled", "on", or non-zero numbers, ignoring case.
532 Otherwise `False` is returned.
533
534 Valid default input is a string or a bool. Returns a bool.
535 """
536 return as_bool(self.get(key, default))
537
538 - def getint(self, key, default=''):
539 """Return the value of the specified option as integer.
540
541 If the specified option can not be converted to an integer, a
542 `ConfigurationError` exception is raised.
543
544 Valid default input is a string or an int. Returns an int.
545 """
546 value = self.get(key, default)
547 try:
548 return _getint(value)
549 except ValueError:
550 raise ConfigurationError(
551 _('[%(section)s] %(entry)s: expected integer,'
552 ' got %(value)s', section=self.name, entry=key,
553 value=repr(value)))
554
556 """Return the value of the specified option as float.
557
558 If the specified option can not be converted to a float, a
559 `ConfigurationError` exception is raised.
560
561 Valid default input is a string, float or int. Returns a float.
562 """
563 value = self.get(key, default)
564 try:
565 return _getfloat(value)
566 except ValueError:
567 raise ConfigurationError(
568 _('[%(section)s] %(entry)s: expected float,'
569 ' got %(value)s', section=self.name, entry=key,
570 value=repr(value)))
571
572 - def getlist(self, key, default='', sep=',', keep_empty=True):
573 """Return a list of values that have been specified as a single
574 comma-separated option.
575
576 A different separator can be specified using the `sep` parameter. The
577 `sep` parameter can specify multiple values using a list or a tuple.
578 If the `keep_empty` parameter is set to `True`, empty elements are
579 included in the list.
580
581 Valid default input is a string or a list. Returns a list.
582 """
583 return _getlist(self.get(key, default), sep, keep_empty)
584
585 - def getpath(self, key, default=''):
586 """Return the value of the specified option as a path, relative to
587 the location of this configuration file.
588
589 Valid default input is a string. Returns a normalized path.
590 """
591 path = self.get(key, default)
592 if not path:
593 return default
594 if not os.path.isabs(path):
595 path = os.path.join(os.path.dirname(self.config.filename), path)
596 return os.path.normcase(os.path.realpath(path))
597
599 """Return `(key, value)` tuples for every option in the section.
600
601 This includes options that have default values that haven't been
602 overridden. If `compmgr` is specified, only return default option
603 values for components that are enabled in the given `ComponentManager`.
604 """
605 for key in self.iterate(compmgr):
606 yield key, self.get(key)
607
608 - def set(self, key, value):
609 """Change a configuration value.
610
611 These changes are not persistent unless saved with `save()`.
612 """
613 self._cache.pop(key, None)
614 if not self.config.parser.has_section(self.name):
615 self.config.parser.add_section(self.name)
616 return self.config.parser.set(self.name, key, value)
617
619 """Delete a key from this section.
620
621 Like for `set()`, the changes won't persist until `save()` gets
622 called.
623 """
624 self._cache.pop(key, None)
625 if self.config.parser.has_section(self.name):
626 self.config.parser.remove_option(self.name, key)
627 if not self.config.parser.options(self.name):
628 del self.config[self.name]
629
632 """Return the descriptor registry.
633
634 If `compmgr` is specified, only return descriptors for components that
635 are enabled in the given `ComponentManager`.
636 """
637 if compmgr is None:
638 return cls.registry
639
640 from trac.core import ComponentMeta
641 components = {}
642 for comp in ComponentMeta._components:
643 for attr in comp.__dict__.itervalues():
644 if isinstance(attr, cls):
645 components[attr] = comp
646
647 return dict(each for each in cls.registry.iteritems()
648 if each[1] not in components
649 or compmgr.is_enabled(components[each[1]]))
650
653 """Descriptor for configuration sections."""
654
655 registry = {}
656
657 @staticmethod
659 """Return the section registry, as a `dict` mapping section names to
660 `ConfigSection` objects.
661
662 If `compmgr` is specified, only return sections for components that
663 are enabled in the given `ComponentManager`.
664 """
665 return _get_registry(ConfigSection, compmgr)
666
667 - def __init__(self, name, doc, doc_domain='tracini', doc_args=None):
668 """Create the configuration section."""
669 self.name = name
670 self.registry[self.name] = self
671 self.__doc__ = cleandoc(doc)
672 self.doc_domain = doc_domain
673 self.doc_args = doc_args
674
675 - def __get__(self, instance, owner):
681
683 return '<%s [%s]>' % (self.__class__.__name__, self.name)
684
685 @property
687 """Return localized document of the section"""
688 return _getdoc(self)
689
692 """Descriptor for configuration options."""
693
694 registry = {}
695
696 - def accessor(self, section, name, default):
698
699 @staticmethod
701 """Return the option registry, as a `dict` mapping `(section, key)`
702 tuples to `Option` objects.
703
704 If `compmgr` is specified, only return options for components that are
705 enabled in the given `ComponentManager`.
706 """
707 return _get_registry(Option, compmgr)
708
709 - def __init__(self, section, name, default=None, doc='',
710 doc_domain='tracini', doc_args=None):
711 """Create the configuration option.
712
713 :param section: the name of the configuration section this option
714 belongs to
715 :param name: the name of the option
716 :param default: the default value for the option
717 :param doc: documentation of the option
718 """
719 self.section = section
720 self.name = name
721 self.default = self.normalize(default)
722 self.registry[(self.section, self.name)] = self
723 self.__doc__ = cleandoc(doc)
724 self.doc_domain = doc_domain
725 self.doc_args = doc_args
726
727 - def __get__(self, instance, owner):
735
736 - def __set__(self, instance, value):
737 raise AttributeError(_("Setting attribute is not allowed."))
738
740 return '<%s [%s] %r>' % (self.__class__.__name__, self.section,
741 self.name)
742
743 @property
745 """Return localized document of the option"""
746 return _getdoc(self)
747
749 """Return the value as a string to write to a trac.ini file"""
750 if value is None:
751 return ''
752 if value is True:
753 return 'enabled'
754 if value is False:
755 return 'disabled'
756 if isinstance(value, unicode):
757 return value
758 return to_unicode(value)
759
761 """Normalize the given value to write to a trac.ini file"""
762 return self.dumps(value)
763
766 """Descriptor for boolean configuration options."""
767
768 - def accessor(self, section, name, default):
770
772 if value not in (True, False):
773 value = as_bool(value)
774 return self.dumps(value)
775
778 """Descriptor for integer configuration options."""
779
780 - def accessor(self, section, name, default):
782
784 try:
785 value = _getint(value)
786 except ValueError:
787 pass
788 return self.dumps(value)
789
792 """Descriptor for float configuration options."""
793
794 - def accessor(self, section, name, default):
796
798 try:
799 value = _getfloat(value)
800 except ValueError:
801 pass
802 return self.dumps(value)
803
806 """Descriptor for configuration options that contain multiple values
807 separated by a specific character.
808 """
809
810 - def __init__(self, section, name, default=None, sep=',', keep_empty=False,
811 doc='', doc_domain='tracini', doc_args=None):
816
817 - def accessor(self, section, name, default):
819
821 if isinstance(value, (list, tuple)):
822 sep = self.sep
823 if isinstance(sep, (list, tuple)):
824 sep = sep[0]
825 return sep.join(Option.dumps(self, v) or '' for v in value)
826 return Option.dumps(self, value)
827
829 return self.dumps(_getlist(value, self.sep, self.keep_empty))
830
833 """Descriptor for configuration options providing a choice among a list
834 of items.
835
836 The default value is the first choice in the list.
837 """
838
839 - def __init__(self, section, name, choices, doc='', doc_domain='tracini',
840 doc_args=None, case_sensitive=True):
845
846 - def accessor(self, section, name, default):
847 value = section.get(name, default)
848 choices = self.choices[:]
849 if not self.case_sensitive:
850 choices = map(unicode.lower, choices)
851 value = value.lower()
852 try:
853 idx = choices.index(value)
854 except ValueError:
855 raise ConfigurationError(
856 _('[%(section)s] %(entry)s: expected one of '
857 '(%(choices)s), got %(value)s',
858 section=section.name, entry=name, value=repr(value),
859 choices=', '.join('"%s"' % c
860 for c in sorted(self.choices))))
861 return self.choices[idx]
862
865 """Descriptor for file system path configuration options.
866
867 Relative paths are resolved to absolute paths using the directory
868 containing the configuration file as the reference.
869 """
870
871 - def accessor(self, section, name, default):
873
876 """Name of a component implementing `interface`. Raises a
877 `ConfigurationError` if the component cannot be found in the list of
878 active components implementing the interface."""
879
880 - def __init__(self, section, name, interface, default=None, doc='',
881 doc_domain='tracini', doc_args=None):
885
886 - def __get__(self, instance, owner):
887 if instance is None:
888 return self
889 value = Option.__get__(self, instance, owner)
890 for impl in self.xtnpt.extensions(instance):
891 if impl.__class__.__name__ == value:
892 return impl
893 raise ConfigurationError(
894 tag_("Cannot find an implementation of the %(interface)s "
895 "interface named %(implementation)s. Please check "
896 "that the Component is enabled or update the option "
897 "%(option)s in trac.ini.",
898 interface=tag.code(self.xtnpt.interface.__name__),
899 implementation=tag.code(value),
900 option=tag.code("[%s] %s" % (self.section, self.name))))
901
904 """A comma separated, ordered, list of components implementing
905 `interface`. Can be empty.
906
907 If `include_missing` is true (the default) all components implementing the
908 interface are returned, with those specified by the option ordered first.
909 """
910
911 - def __init__(self, section, name, interface, default=None,
912 include_missing=True, doc='', doc_domain='tracini',
913 doc_args=None):
918
919 - def __get__(self, instance, owner):
920 if instance is None:
921 return self
922 order = ListOption.__get__(self, instance, owner)
923 components = []
924 implementing_classes = []
925 for impl in self.xtnpt.extensions(instance):
926 implementing_classes.append(impl.__class__.__name__)
927 if self.include_missing or impl.__class__.__name__ in order:
928 components.append(impl)
929 not_found = sorted(set(order) - set(implementing_classes))
930 if not_found:
931 raise ConfigurationError(
932 tag_("Cannot find implementation(s) of the %(interface)s "
933 "interface named %(implementation)s. Please check "
934 "that the Component is enabled or update the option "
935 "%(option)s in trac.ini.",
936 interface=tag.code(self.xtnpt.interface.__name__),
937 implementation=tag(
938 (', ' if idx != 0 else None, tag.code(impl))
939 for idx, impl in enumerate(not_found)),
940 option=tag.code("[%s] %s" % (self.section, self.name))))
941
942 def key(impl):
943 name = impl.__class__.__name__
944 if name in order:
945 return 0, order.index(name)
946 else:
947 return 1, components.index(impl)
948 return sorted(components, key=key)
949
952 """trac-admin command provider for trac.ini administration."""
953
954 implements(IAdminCommandProvider)
955
956
957
959 yield ('config get', '<section> <option>',
960 'Get the value of the given option in "trac.ini"',
961 self._complete_config, self._do_get)
962 yield ('config remove', '<section> [<option>]',
963 'Remove the specified option or section from "trac.ini"',
964 self._complete_config, self._do_remove)
965 yield ('config set', '<section> <option> <value>',
966 'Set the value for the given option in "trac.ini"',
967 self._complete_config, self._do_set)
968
974
975 - def _do_get(self, section, option):
981
982 - def _do_set(self, section, option, value):
989
1002
1005 """Returns a list of dictionaries containing the `name` and `options`
1006 of each configuration section. The value of `options` is a list of
1007 dictionaries containing the `name`, `value` and `modified` state of
1008 each configuration option. The `modified` value is True if the value
1009 differs from its default.
1010
1011 :since: version 1.1.2
1012 """
1013 all_options = {}
1014 for (section, name), option in \
1015 Option.get_registry(env.compmgr).iteritems():
1016 all_options.setdefault(section, {})[name] = option
1017 sections = []
1018 for section in env.config.sections(env.compmgr):
1019 options = []
1020 for name, value in env.config.options(section, env.compmgr):
1021 registered = all_options.get(section, {}).get(name)
1022 if registered:
1023 default = registered.default
1024 normalized = registered.normalize(value)
1025 else:
1026 default = u''
1027 normalized = unicode(value)
1028 options.append({'name': name, 'value': value,
1029 'modified': normalized != default})
1030 options.sort(key=lambda o: o['name'])
1031 sections.append({'name': section, 'options': options})
1032 sections.sort(key=lambda s: s['name'])
1033 return sections
1034