Package trac :: Module config

Source Code for Module trac.config

   1  # -*- coding: utf-8 -*- 
   2  # 
   3  # Copyright (C) 2005-2020 Edgewall Software 
   4  # Copyright (C) 2005-2007 Christopher Lenz <[email protected]> 
   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 https://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 https://trac.edgewall.org/log/. 
  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() 
35 36 37 -def _getint(value):
38 return int(value or 0)
39
40 41 -def _getfloat(value):
42 return float(value or 0.0)
43
44 45 -def _getlist(value, sep, keep_empty):
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
60 61 -def _getdoc(option_or_section):
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
68 69 -class ConfigurationError(TracError):
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):
74 if message is None: 75 message = _("Look in the Trac log for more information.") 76 super(ConfigurationError, self).__init__(message, title, 77 show_traceback)
78
79 80 -class UnicodeConfigParser(ConfigParser):
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 # All of the methods of ConfigParser are overridden except 86 # `getboolean`, `getint`, `getfloat`, `defaults`, `read`, `readfp`, 87 # `optionxform` and `write`. `getboolean`, `getint` and `getfloat` 88 # call `get`, so it isn't necessary to reimplement them. 89 # The base class `RawConfigParser` doesn't inherit from `object` 90 # so we can't use `super`. 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
97 - def optionxform(self, option):
98 if self._ignorecase_option: 99 option = option.lower() 100 return option
101
102 - def sections(self):
103 return map(to_unicode, ConfigParser.sections(self))
104
105 - def add_section(self, section):
106 section_str = to_utf8(section) 107 ConfigParser.add_section(self, section_str)
108
109 - def has_section(self, section):
110 section_str = to_utf8(section) 111 return ConfigParser.has_section(self, section_str)
112
113 - def options(self, section):
114 section_str = to_utf8(section) 115 return map(to_unicode, ConfigParser.options(self, section_str))
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
128 - def has_option(self, section, option):
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
139 - def remove_option(self, section, option):
140 section_str = to_utf8(section) 141 option_str = to_utf8(option) 142 ConfigParser.remove_option(self, section_str, option_str)
143
144 - def remove_section(self, section):
145 section_str = to_utf8(section) 146 ConfigParser.remove_section(self, section_str)
147
148 - def __copy__(self):
149 parser = self.__class__() 150 parser._sections = copy.copy(self._sections) 151 return parser
152
153 - def __deepcopy__(self, memo):
154 parser = self.__class__() 155 parser._sections = copy.deepcopy(self._sections) 156 return parser
157
158 159 -class Configuration(object):
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={}):
167 self.filename = filename 168 self.parser = UnicodeConfigParser() 169 self._pristine_parser = None 170 self.parents = [] 171 self._lastmtime = 0 172 self._sections = {} 173 self.parse_if_needed(force=True)
174
175 - def __repr__(self):
176 return '<%s %r>' % (self.__class__.__name__, self.filename)
177
178 - def __contains__(self, name):
179 """Return whether the configuration contains a section of the given 180 name. 181 """ 182 return name in self.sections()
183
184 - def __getitem__(self, name):
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
191 - def exists(self):
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
265 - def defaults(self, compmgr=None):
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):
294 """Return a list of section names. 295 296 If `compmgr` is specified, only the section names corresponding to 297 options declared in components that are enabled in the given 298 `ComponentManager` are returned. 299 300 :param empty: If `True`, include sections from the registry that 301 contain no options. 302 """ 303 sections = set(self.parser.sections()) 304 for parent in self.parents: 305 sections.update(parent.sections(compmgr, defaults=False)) 306 if defaults: 307 sections.update(self.defaults(compmgr)) 308 if empty: 309 sections.update(ConfigSection.get_registry(compmgr)) 310 return sorted(sections)
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
319 - def save(self):
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 # Prepare new file contents to write to disk. 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 # Revert all changes to avoid inconsistencies 359 self.parser = copy.deepcopy(self._pristine_parser) 360 raise 361 else: 362 self._pristine_parser = copy.deepcopy(self.parser)
363
364 - def parse_if_needed(self, force=False):
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
392 - def touch(self):
393 if self.filename and self.exists \ 394 and os.access(self.filename, os.W_OK): 395 wait_for_file_mtime_change(self.filename)
396
397 - def set_defaults(self, compmgr=None, component=None):
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
426 - def _get_parents(self):
427 _parents = [] 428 if self.parser.has_option('inherit', 'file'): 429 for filename in self.parser.get('inherit', 'file').split(','): 430 filename = filename.strip() 431 if not os.path.isabs(filename): 432 filename = os.path.join(os.path.dirname(self.filename), 433 filename) 434 _parents.append(Configuration(filename)) 435 return _parents
436
437 - def _write(self, parser):
438 if not self.filename: 439 return 440 wait_for_file_mtime_change(self.filename) 441 with AtomicFile(self.filename, 'w') as fd: 442 fd.writelines(['# -*- coding: utf-8 -*-\n', '\n']) 443 parser.write(fd)
444
445 446 -class Section(object):
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
453 - def __init__(self, config, name):
454 self.config = config 455 self.name = name 456 self._cache = {}
457
458 - def __repr__(self):
459 return '<%s [%s]>' % (self.__class__.__name__, self.name)
460
461 - def contains(self, key, defaults=True):
462 if self.config.parser.has_option(self.name, key): 463 return True 464 for parent in self.config.parents: 465 if parent[self.name].contains(key, defaults=False): 466 return True 467 return defaults and (self.name, key) in Option.registry
468 469 __contains__ = contains 470
471 - def iterate(self, compmgr=None, defaults=True):
472 """Iterate over the options in this section. 473 474 If `compmgr` is specified, only return default option values for 475 components that are enabled in the given `ComponentManager`. 476 """ 477 options = set() 478 if self.config.parser.has_section(self.name): 479 for option in self.config.parser.options(self.name): 480 options.add(option.lower()) 481 yield option 482 for parent in self.config.parents: 483 for option in parent[self.name].iterate(defaults=False): 484 loption = option.lower() 485 if loption not in options: 486 options.add(loption) 487 yield option 488 if defaults: 489 for section, option in Option.get_registry(compmgr).iterkeys(): 490 if section == self.name and option.lower() not in options: 491 yield option
492 493 __iter__ = iterate 494
495 - def get(self, key, default=''):
496 """Return the value of the specified option. 497 498 Valid default input is a string. Returns a string. 499 """ 500 cached = self._cache.get(key, _use_default) 501 if cached is not _use_default: 502 return cached 503 if self.config.parser.has_option(self.name, key): 504 value = self.config.parser.get(self.name, key) 505 else: 506 for parent in self.config.parents: 507 value = parent[self.name].get(key, _use_default) 508 if value is not _use_default: 509 break 510 else: 511 if default is not _use_default: 512 option = Option.registry.get((self.name, key)) 513 value = option.dumps(option.default) if option \ 514 else _use_default 515 else: 516 value = _use_default 517 if value is _use_default: 518 return default 519 self._cache[key] = value 520 return value
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
550 - def getfloat(self, key, default=''):
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
593 - def options(self, compmgr=None):
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
613 - def remove(self, key):
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
623 624 -def _get_registry(cls, compmgr=None):
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
644 645 -class ConfigSection(object):
646 """Descriptor for configuration sections.""" 647 648 registry = {} 649 650 @staticmethod
651 - def get_registry(compmgr=None):
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):
669 if instance is None: 670 return self 671 config = getattr(instance, 'config', None) 672 if config and isinstance(config, Configuration): 673 return config[self.name]
674
675 - def __repr__(self):
676 return '<%s [%s]>' % (self.__class__.__name__, self.name)
677 678 @property
679 - def doc(self):
680 """Return localized document of the section""" 681 return _getdoc(self)
682
683 684 -class Option(object):
685 """Descriptor for configuration options.""" 686 687 registry = {} 688
689 - def accessor(self, section, name, default):
690 return section.get(name, default)
691 692 @staticmethod
693 - def get_registry(compmgr=None):
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):
721 if instance is None: 722 return self 723 config = getattr(instance, 'config', None) 724 if config and isinstance(config, Configuration): 725 section = config[self.section] 726 value = self.accessor(section, self.name, self.default) 727 return value
728
729 - def __set__(self, instance, value):
730 raise AttributeError(_("Setting attribute is not allowed."))
731
732 - def __repr__(self):
733 return '<%s [%s] %r>' % (self.__class__.__name__, self.section, 734 self.name)
735 736 @property
737 - def doc(self):
738 """Return localized document of the option""" 739 return _getdoc(self)
740
741 - def dumps(self, value):
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
753 - def normalize(self, value):
754 """Normalize the given value to write to a trac.ini file""" 755 return self.dumps(value)
756
757 758 -class BoolOption(Option):
759 """Descriptor for boolean configuration options.""" 760
761 - def accessor(self, section, name, default):
762 return section.getbool(name, default)
763
764 - def normalize(self, value):
765 if value not in (True, False): 766 value = as_bool(value) 767 return self.dumps(value)
768
769 770 -class IntOption(Option):
771 """Descriptor for integer configuration options.""" 772
773 - def accessor(self, section, name, default):
774 return section.getint(name, default)
775
776 - def normalize(self, value):
777 try: 778 value = _getint(value) 779 except ValueError: 780 pass 781 return self.dumps(value)
782
783 784 -class FloatOption(Option):
785 """Descriptor for float configuration options.""" 786
787 - def accessor(self, section, name, default):
788 return section.getfloat(name, default)
789
790 - def normalize(self, value):
791 try: 792 value = _getfloat(value) 793 except ValueError: 794 pass 795 return self.dumps(value)
796
797 798 -class ListOption(Option):
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):
805 self.sep = sep 806 self.keep_empty = keep_empty 807 Option.__init__(self, section, name, default, doc, doc_domain, 808 doc_args)
809
810 - def accessor(self, section, name, default):
811 return section.getlist(name, default, self.sep, self.keep_empty)
812
813 - def dumps(self, value):
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
821 - def normalize(self, value):
822 return self.dumps(_getlist(value, self.sep, self.keep_empty))
823
824 825 -class ChoiceOption(Option):
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):
834 Option.__init__(self, section, name, to_unicode(choices[0]), doc, 835 doc_domain, doc_args) 836 self.choices = list(set(to_unicode(c).strip() for c in choices)) 837 self.case_sensitive = case_sensitive
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
856 857 -class PathOption(Option):
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):
865 return section.getpath(name, default)
866
867 868 -class ExtensionOption(Option):
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):
875 Option.__init__(self, section, name, default, doc, doc_domain, 876 doc_args) 877 self.xtnpt = ExtensionPoint(interface)
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
895 896 -class OrderedExtensionsOption(ListOption):
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):
907 ListOption.__init__(self, section, name, default, doc=doc, 908 doc_domain=doc_domain, doc_args=doc_args) 909 self.xtnpt = ExtensionPoint(interface) 910 self.include_missing = include_missing
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
945 946 -class ConfigurationAdmin(Component):
947 """trac-admin command provider for trac.ini administration.""" 948 949 implements(IAdminCommandProvider) 950 951 # IAdminCommandProvider methods 952
953 - def get_admin_commands(self):
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
964 - def _complete_config(self, args):
965 if len(args) == 1: 966 return self.config.sections(empty=True) 967 elif len(args) == 2: 968 return [name for (name, value) in self.config[args[0]].options()]
969
970 - def _do_get(self, section, option):
971 if not self.config.has_option(section, option): 972 raise AdminCommandError( 973 _("Option '%(option)s' doesn't exist in section" 974 " '%(section)s'", option=option, section=section)) 975 printout(self.config.get(section, option))
976
977 - def _do_set(self, section, option, value):
978 self.config.set(section, option, value) 979 if section == 'components' and as_bool(value): 980 self.config.set_defaults(component=option) 981 self.config.save() 982 if section == 'inherit' and option == 'file': 983 self.config.parse_if_needed(force=True) # Full reload
984
985 - def _do_remove(self, section, option):
986 if not self.config.has_option(section, option): 987 raise AdminCommandError( 988 _("Option '%(option)s' doesn't exist in section" 989 " '%(section)s'", option=option, section=section)) 990 self.config.remove(section, option) 991 self.config.save() 992 if section == 'inherit' and option == 'file': 993 self.config.parse_if_needed(force=True) # Full reload
994
995 996 -def get_configinfo(env):
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