Package trac :: Module config

Source Code for Module trac.config

   1  # -*- coding: utf-8 -*- 
   2  # 
   3  # Copyright (C) 2005-2023 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 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() 
34 35 36 -def _getint(value):
37 return int(value or 0)
38
39 40 -def _getfloat(value):
41 return float(value or 0.0)
42
43 44 -def _getlist(value, sep, keep_empty):
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
59 60 -def _getdoc(option_or_section):
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
67 68 -class ConfigurationError(TracError):
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):
73 if message is None: 74 message = _("Look in the Trac log for more information.") 75 super(ConfigurationError, self).__init__(message, title, 76 show_traceback)
77
78 79 -class UnicodeConfigParser(ConfigParser):
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 # All of the methods of ConfigParser are overridden except 85 # `getboolean`, `getint`, `getfloat`, `defaults`, `read`, `readfp`, 86 # `optionxform` and `write`. `getboolean`, `getint` and `getfloat` 87 # call `get`, so it isn't necessary to reimplement them. 88 # The base class `RawConfigParser` doesn't inherit from `object` 89 # so we can't use `super`. 90
91 - def __init__(self, ignorecase_option=True, **kwargs):
92 self._ignorecase_option = ignorecase_option 93 ConfigParser.__init__(self, **kwargs)
94
95 - def optionxform(self, option):
96 if self._ignorecase_option: 97 option = option.lower() 98 return option
99
100 - def sections(self):
101 return map(to_unicode, ConfigParser.sections(self))
102
103 - def add_section(self, section):
104 section_str = to_utf8(section) 105 ConfigParser.add_section(self, section_str)
106
107 - def has_section(self, section):
108 section_str = to_utf8(section) 109 return ConfigParser.has_section(self, section_str)
110
111 - def options(self, section):
112 section_str = to_utf8(section) 113 return map(to_unicode, ConfigParser.options(self, section_str))
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
126 - def has_option(self, section, option):
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
137 - def remove_option(self, section, option):
138 section_str = to_utf8(section) 139 option_str = to_utf8(option) 140 ConfigParser.remove_option(self, section_str, option_str)
141
142 - def remove_section(self, section):
143 section_str = to_utf8(section) 144 ConfigParser.remove_section(self, section_str)
145
146 - def __copy__(self):
147 parser = self.__class__() 148 parser._sections = copy.copy(self._sections) 149 return parser
150
151 - def __deepcopy__(self, memo):
152 parser = self.__class__() 153 parser._sections = copy.deepcopy(self._sections) 154 return parser
155
156 157 -class Configuration(object):
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={}):
165 self.filename = filename 166 self.parser = UnicodeConfigParser() 167 self._pristine_parser = None 168 self.parents = [] 169 self._lastmtime = 0 170 self._sections = {} 171 self.parse_if_needed(force=True)
172
173 - def __repr__(self):
174 return '<%s %r>' % (self.__class__.__name__, self.filename)
175
176 - def __contains__(self, name):
177 """Return whether the configuration contains a section of the given 178 name. 179 """ 180 return name in self.sections()
181
182 - def __getitem__(self, name):
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
188 - def __delitem__(self, name):
189 self._sections.pop(name, None) 190 self.parser.remove_section(name)
191 192 @property
193 - def exists(self):
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
267 - def defaults(self, compmgr=None):
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):
299 """Return a list of section names. 300 301 If `compmgr` is specified, only the section names corresponding to 302 options declared in components that are enabled in the given 303 `ComponentManager` are returned. 304 305 :param empty: If `True`, include sections from the registry that 306 contain no options. 307 """ 308 sections = set(self.parser.sections()) 309 for parent in self.parents: 310 sections.update(parent.sections(compmgr, defaults=False)) 311 if defaults: 312 sections.update(self.defaults(compmgr)) 313 if empty: 314 sections.update(ConfigSection.get_registry(compmgr)) 315 return sorted(sections)
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
324 - def save(self):
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 # Prepare new file contents to write to disk. 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 # Revert all changes to avoid inconsistencies 364 self.parser = copy.deepcopy(self._pristine_parser) 365 raise 366 else: 367 self._pristine_parser = copy.deepcopy(self.parser)
368
369 - def parse_if_needed(self, force=False):
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
397 - def touch(self):
398 if self.filename and self.exists \ 399 and os.access(self.filename, os.W_OK): 400 wait_for_file_mtime_change(self.filename)
401
402 - def set_defaults(self, compmgr=None, component=None):
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
431 - def _get_parents(self):
432 _parents = [] 433 if self.parser.has_option('inherit', 'file'): 434 for filename in self.parser.get('inherit', 'file').split(','): 435 filename = filename.strip() 436 if not os.path.isabs(filename): 437 filename = os.path.join(os.path.dirname(self.filename), 438 filename) 439 _parents.append(Configuration(filename)) 440 return _parents
441
442 - def _write(self, parser):
443 if not self.filename: 444 return 445 wait_for_file_mtime_change(self.filename) 446 with AtomicFile(self.filename, 'w') as fd: 447 fd.writelines(['# -*- coding: utf-8 -*-\n', '\n']) 448 parser.write(fd)
449
450 451 -class Section(object):
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
458 - def __init__(self, config, name):
459 self.config = config 460 self.name = name 461 self._cache = {}
462
463 - def __repr__(self):
464 return '<%s [%s]>' % (self.__class__.__name__, self.name)
465
466 - def contains(self, key, defaults=True):
467 if self.config.parser.has_option(self.name, key): 468 return True 469 for parent in self.config.parents: 470 if parent[self.name].contains(key, defaults=False): 471 return True 472 return defaults and (self.name, key) in Option.registry
473 474 __contains__ = contains 475
476 - def iterate(self, compmgr=None, defaults=True):
477 """Iterate over the options in this section. 478 479 If `compmgr` is specified, only return default option values for 480 components that are enabled in the given `ComponentManager`. 481 """ 482 options = set() 483 if self.config.parser.has_section(self.name): 484 for option in self.config.parser.options(self.name): 485 options.add(option.lower()) 486 yield option 487 for parent in self.config.parents: 488 for option in parent[self.name].iterate(defaults=False): 489 loption = option.lower() 490 if loption not in options: 491 options.add(loption) 492 yield option 493 if defaults: 494 for section, option in Option.get_registry(compmgr).iterkeys(): 495 if section == self.name and option.lower() not in options: 496 yield option
497 498 __iter__ = iterate 499
500 - def get(self, key, default=''):
501 """Return the value of the specified option. 502 503 Valid default input is a string. Returns a string. 504 """ 505 cached = self._cache.get(key, _use_default) 506 if cached is not _use_default: 507 return cached 508 if self.config.parser.has_option(self.name, key): 509 value = self.config.parser.get(self.name, key) 510 else: 511 for parent in self.config.parents: 512 value = parent[self.name].get(key, _use_default) 513 if value is not _use_default: 514 break 515 else: 516 if default is not _use_default: 517 option = Option.registry.get((self.name, key)) 518 value = option.dumps(option.default) if option \ 519 else _use_default 520 else: 521 value = _use_default 522 if value is _use_default: 523 return default 524 self._cache[key] = value 525 return value
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
555 - def getfloat(self, key, default=''):
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
598 - def options(self, compmgr=None):
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
618 - def remove(self, key):
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
630 631 -def _get_registry(cls, compmgr=None):
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
651 652 -class ConfigSection(object):
653 """Descriptor for configuration sections.""" 654 655 registry = {} 656 657 @staticmethod
658 - def get_registry(compmgr=None):
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):
676 if instance is None: 677 return self 678 config = getattr(instance, 'config', None) 679 if config and isinstance(config, Configuration): 680 return config[self.name]
681
682 - def __repr__(self):
683 return '<%s [%s]>' % (self.__class__.__name__, self.name)
684 685 @property
686 - def doc(self):
687 """Return localized document of the section""" 688 return _getdoc(self)
689
690 691 -class Option(object):
692 """Descriptor for configuration options.""" 693 694 registry = {} 695
696 - def accessor(self, section, name, default):
697 return section.get(name, default)
698 699 @staticmethod
700 - def get_registry(compmgr=None):
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):
728 if instance is None: 729 return self 730 config = getattr(instance, 'config', None) 731 if config and isinstance(config, Configuration): 732 section = config[self.section] 733 value = self.accessor(section, self.name, self.default) 734 return value
735
736 - def __set__(self, instance, value):
737 raise AttributeError(_("Setting attribute is not allowed."))
738
739 - def __repr__(self):
740 return '<%s [%s] %r>' % (self.__class__.__name__, self.section, 741 self.name)
742 743 @property
744 - def doc(self):
745 """Return localized document of the option""" 746 return _getdoc(self)
747
748 - def dumps(self, value):
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
760 - def normalize(self, value):
761 """Normalize the given value to write to a trac.ini file""" 762 return self.dumps(value)
763
764 765 -class BoolOption(Option):
766 """Descriptor for boolean configuration options.""" 767
768 - def accessor(self, section, name, default):
769 return section.getbool(name, default)
770
771 - def normalize(self, value):
772 if value not in (True, False): 773 value = as_bool(value) 774 return self.dumps(value)
775
776 777 -class IntOption(Option):
778 """Descriptor for integer configuration options.""" 779
780 - def accessor(self, section, name, default):
781 return section.getint(name, default)
782
783 - def normalize(self, value):
784 try: 785 value = _getint(value) 786 except ValueError: 787 pass 788 return self.dumps(value)
789
790 791 -class FloatOption(Option):
792 """Descriptor for float configuration options.""" 793
794 - def accessor(self, section, name, default):
795 return section.getfloat(name, default)
796
797 - def normalize(self, value):
798 try: 799 value = _getfloat(value) 800 except ValueError: 801 pass 802 return self.dumps(value)
803
804 805 -class ListOption(Option):
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):
812 self.sep = sep 813 self.keep_empty = keep_empty 814 Option.__init__(self, section, name, default, doc, doc_domain, 815 doc_args)
816
817 - def accessor(self, section, name, default):
818 return section.getlist(name, default, self.sep, self.keep_empty)
819
820 - def dumps(self, value):
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
828 - def normalize(self, value):
829 return self.dumps(_getlist(value, self.sep, self.keep_empty))
830
831 832 -class ChoiceOption(Option):
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):
841 Option.__init__(self, section, name, to_unicode(choices[0]), doc, 842 doc_domain, doc_args) 843 self.choices = list({to_unicode(c).strip() for c in choices}) 844 self.case_sensitive = case_sensitive
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
863 864 -class PathOption(Option):
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):
872 return section.getpath(name, default)
873
874 875 -class ExtensionOption(Option):
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):
882 Option.__init__(self, section, name, default, doc, doc_domain, 883 doc_args) 884 self.xtnpt = ExtensionPoint(interface)
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
902 903 -class OrderedExtensionsOption(ListOption):
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):
914 ListOption.__init__(self, section, name, default, doc=doc, 915 doc_domain=doc_domain, doc_args=doc_args) 916 self.xtnpt = ExtensionPoint(interface) 917 self.include_missing = include_missing
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
950 951 -class ConfigurationAdmin(Component):
952 """trac-admin command provider for trac.ini administration.""" 953 954 implements(IAdminCommandProvider) 955 956 # IAdminCommandProvider methods 957
958 - def get_admin_commands(self):
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
969 - def _complete_config(self, args):
970 if len(args) == 1: 971 return self.config.sections(empty=True) 972 elif len(args) == 2: 973 return [name for (name, value) in self.config[args[0]].options()]
974
975 - def _do_get(self, section, option):
976 if not self.config.has_option(section, option): 977 raise AdminCommandError( 978 _("Option '%(option)s' doesn't exist in section" 979 " '%(section)s'", option=option, section=section)) 980 printout(self.config.get(section, option))
981
982 - def _do_set(self, section, option, value):
983 self.config.set(section, option, value) 984 if section == 'components' and as_bool(value): 985 self.config.set_defaults(component=option) 986 self.config.save() 987 if section == 'inherit' and option == 'file': 988 self.config.parse_if_needed(force=True) # Full reload
989
990 - def _do_remove(self, section, option=None):
991 if option and not self.config.has_option(section, option): 992 raise AdminCommandError( 993 _("Option '%(option)s' doesn't exist in section" 994 " '%(section)s'", option=option, section=section)) 995 elif section not in self.config: 996 raise AdminCommandError( 997 _("Section '%(section)s' doesn't exist", section=section)) 998 self.config.remove(section, option) 999 self.config.save() 1000 if section == 'inherit' and option == 'file': 1001 self.config.parse_if_needed(force=True) # Full reload
1002
1003 1004 -def get_configinfo(env):
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