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  from __future__ import with_statement 
 16   
 17  from ConfigParser import ConfigParser 
 18  from copy import deepcopy 
 19  import os.path 
 20   
 21  from genshi.builder import tag 
 22  from trac.admin import AdminCommandError, IAdminCommandProvider 
 23  from trac.core import * 
 24  from trac.util import AtomicFile, as_bool 
 25  from trac.util.compat import cleandoc, wait_for_file_mtime_change 
 26  from trac.util.text import printout, to_unicode, CRLF 
 27  from trac.util.translation import _, N_, tag_ 
 28   
 29  __all__ = ['Configuration', 'ConfigSection', 'Option', 'BoolOption', 
 30             'IntOption', 'FloatOption', 'ListOption', 'ChoiceOption', 
 31             'PathOption', 'ExtensionOption', 'OrderedExtensionsOption', 
 32             'ConfigurationError'] 
 33   
 34  # Retained for backward-compatibility, use as_bool() instead 
 35  _TRUE_VALUES = ('yes', 'true', 'enabled', 'on', 'aye', '1', 1, True) 
 36   
 37  _use_default = object() 
38 39 -def _to_utf8(basestr):
40 return to_unicode(basestr).encode('utf-8')
41
42 43 -class ConfigurationError(TracError):
44 """Exception raised when a value in the configuration file is not valid.""" 45 title = N_('Configuration Error') 46
47 - def __init__(self, message=None, title=None, show_traceback=False):
48 if message is None: 49 message = _("Look in the Trac log for more information.") 50 super(ConfigurationError, self).__init__(message, title, 51 show_traceback)
52
53 54 -class Configuration(object):
55 """Thin layer over `ConfigParser` from the Python standard library. 56 57 In addition to providing some convenience methods, the class remembers 58 the last modification time of the configuration file, and reparses it 59 when the file has changed. 60 """
61 - def __init__(self, filename, params={}):
62 self.filename = filename 63 self.parser = ConfigParser() 64 self._old_sections = {} 65 self.parents = [] 66 self._lastmtime = 0 67 self._sections = {} 68 self.parse_if_needed(force=True)
69
70 - def __contains__(self, name):
71 """Return whether the configuration contains a section of the given 72 name. 73 """ 74 return name in self.sections()
75
76 - def __getitem__(self, name):
77 """Return the configuration section with the specified name.""" 78 if name not in self._sections: 79 self._sections[name] = Section(self, name) 80 return self._sections[name]
81
82 - def __repr__(self):
83 return '<%s %r>' % (self.__class__.__name__, self.filename)
84 85 @property
86 - def exists(self):
87 """Return boolean indicating configuration file existence. 88 89 :since: 1.0.11 90 """ 91 return os.path.isfile(self.filename)
92
93 - def get(self, section, key, default=''):
94 """Return the value of the specified option. 95 96 Valid default input is a string. Returns a string. 97 """ 98 return self[section].get(key, default)
99
100 - def getbool(self, section, key, default=''):
101 """Return the specified option as boolean value. 102 103 If the value of the option is one of "yes", "true", "enabled", "on", 104 or "1", this method wll return `True`, otherwise `False`. 105 106 Valid default input is a string or a bool. Returns a bool. 107 108 (since Trac 0.9.3, "enabled" added in 0.11) 109 """ 110 return self[section].getbool(key, default)
111
112 - def getint(self, section, key, default=''):
113 """Return the value of the specified option as integer. 114 115 If the specified option can not be converted to an integer, a 116 `ConfigurationError` exception is raised. 117 118 Valid default input is a string or an int. Returns an int. 119 120 (since Trac 0.10) 121 """ 122 return self[section].getint(key, default)
123
124 - def getfloat(self, section, key, default=''):
125 """Return the value of the specified option as float. 126 127 If the specified option can not be converted to a float, a 128 `ConfigurationError` exception is raised. 129 130 Valid default input is a string, float or int. Returns a float. 131 132 (since Trac 0.12) 133 """ 134 return self[section].getfloat(key, default)
135
136 - def getlist(self, section, key, default='', sep=',', keep_empty=False):
137 """Return a list of values that have been specified as a single 138 comma-separated option. 139 140 A different separator can be specified using the `sep` parameter. If 141 the `keep_empty` parameter is set to `True`, empty elements are 142 included in the list. 143 144 Valid default input is a string or a list. Returns a string. 145 146 (since Trac 0.10) 147 """ 148 return self[section].getlist(key, default, sep, keep_empty)
149
150 - def getpath(self, section, key, default=''):
151 """Return a configuration value as an absolute path. 152 153 Relative paths are resolved relative to the location of this 154 configuration file. 155 156 Valid default input is a string. Returns a normalized path. 157 158 (enabled since Trac 0.11.5) 159 """ 160 return self[section].getpath(key, default)
161
162 - def set(self, section, key, value):
163 """Change a configuration value. 164 165 These changes are not persistent unless saved with `save()`. 166 """ 167 self[section].set(key, value)
168
169 - def defaults(self, compmgr=None):
170 """Returns a dictionary of the default configuration values 171 (''since 0.10''). 172 173 If `compmgr` is specified, return only options declared in components 174 that are enabled in the given `ComponentManager`. 175 """ 176 defaults = {} 177 for (section, key), option in Option.get_registry(compmgr).items(): 178 defaults.setdefault(section, {})[key] = option.default 179 return defaults
180
181 - def options(self, section, compmgr=None):
182 """Return a list of `(name, value)` tuples for every option in the 183 specified section. 184 185 This includes options that have default values that haven't been 186 overridden. If `compmgr` is specified, only return default option 187 values for components that are enabled in the given `ComponentManager`. 188 """ 189 return self[section].options(compmgr)
190
191 - def remove(self, section, key):
192 """Remove the specified option.""" 193 self[section].remove(key)
194
195 - def sections(self, compmgr=None, defaults=True):
196 """Return a list of section names. 197 198 If `compmgr` is specified, only the section names corresponding to 199 options declared in components that are enabled in the given 200 `ComponentManager` are returned. 201 """ 202 sections = set([to_unicode(s) for s in self.parser.sections()]) 203 for parent in self.parents: 204 sections.update(parent.sections(compmgr, defaults=False)) 205 if defaults: 206 sections.update(self.defaults(compmgr)) 207 return sorted(sections)
208
209 - def has_option(self, section, option, defaults=True):
210 """Returns True if option exists in section in either the project 211 trac.ini or one of the parents, or is available through the Option 212 registry. 213 214 (since Trac 0.11) 215 """ 216 section_str = _to_utf8(section) 217 if self.parser.has_section(section_str): 218 if _to_utf8(option) in self.parser.options(section_str): 219 return True 220 for parent in self.parents: 221 if parent.has_option(section, option, defaults=False): 222 return True 223 return defaults and (section, option) in Option.registry
224
225 - def save(self):
226 """Write the configuration options to the primary file.""" 227 if not self.filename: 228 return 229 230 # Only save options that differ from the defaults 231 sections = [] 232 for section in self.sections(): 233 section_str = _to_utf8(section) 234 options = [] 235 for option in self[section]: 236 default_str = None 237 for parent in self.parents: 238 if parent.has_option(section, option, defaults=False): 239 default_str = _to_utf8(parent.get(section, option)) 240 break 241 option_str = _to_utf8(option) 242 current_str = False 243 if self.parser.has_option(section_str, option_str): 244 current_str = self.parser.get(section_str, option_str) 245 if current_str is not False and current_str != default_str: 246 options.append((option_str, current_str)) 247 if options: 248 sections.append((section_str, sorted(options))) 249 250 # At this point, all the strings in `sections` are UTF-8 encoded `str` 251 try: 252 wait_for_file_mtime_change(self.filename) 253 with AtomicFile(self.filename, 'w') as fileobj: 254 fileobj.write('# -*- coding: utf-8 -*-\n\n') 255 for section_str, options in sections: 256 fileobj.write('[%s]\n' % section_str) 257 section = to_unicode(section_str) 258 for key_str, val_str in options: 259 if to_unicode(key_str) in self[section].overridden: 260 fileobj.write('# %s = <inherited>\n' % key_str) 261 else: 262 val_str = val_str.replace(CRLF, '\n') \ 263 .replace('\n', '\n ') 264 fileobj.write('%s = %s\n' % (key_str, val_str)) 265 fileobj.write('\n') 266 self._old_sections = deepcopy(self.parser._sections) 267 except Exception: 268 # Revert all changes to avoid inconsistencies 269 self.parser._sections = deepcopy(self._old_sections) 270 raise
271
272 - def parse_if_needed(self, force=False):
273 if not self.filename or not self.exists: 274 return False 275 276 changed = False 277 modtime = os.path.getmtime(self.filename) 278 if force or modtime != self._lastmtime: 279 self.parser._sections = {} 280 if not self.parser.read(self.filename): 281 raise TracError(_("Error reading '%(file)s', make sure it is " 282 "readable.", file=self.filename)) 283 self._lastmtime = modtime 284 self._old_sections = deepcopy(self.parser._sections) 285 changed = True 286 287 if changed: 288 self.parents = [] 289 if self.parser.has_option('inherit', 'file'): 290 for filename in self.parser.get('inherit', 'file').split(','): 291 filename = to_unicode(filename.strip()) 292 if not os.path.isabs(filename): 293 filename = os.path.join(os.path.dirname(self.filename), 294 filename) 295 self.parents.append(Configuration(filename)) 296 else: 297 for parent in self.parents: 298 changed |= parent.parse_if_needed(force=force) 299 300 if changed: 301 self._sections = {} 302 return changed
303
304 - def touch(self):
305 if self.filename and self.exists \ 306 and os.access(self.filename, os.W_OK): 307 wait_for_file_mtime_change(self.filename)
308
309 - def set_defaults(self, compmgr=None):
310 """Retrieve all default values and store them explicitly in the 311 configuration, so that they can be saved to file. 312 313 Values already set in the configuration are not overridden. 314 """ 315 for (section, name), option in Option.get_registry(compmgr).items(): 316 if not self.parser.has_option(_to_utf8(section), _to_utf8(name)): 317 value = option.default 318 if any(parent[section].contains(name, defaults=False) 319 for parent in self.parents): 320 value = None 321 if value is not None: 322 value = option.dumps(value) 323 self.set(section, name, value)
324
325 326 -class Section(object):
327 """Proxy for a specific configuration section. 328 329 Objects of this class should not be instantiated directly. 330 """ 331 __slots__ = ['config', 'name', 'overridden', '_cache'] 332
333 - def __init__(self, config, name):
334 self.config = config 335 self.name = name 336 self.overridden = {} 337 self._cache = {}
338
339 - def contains(self, key, defaults=True):
340 if self.config.parser.has_option(_to_utf8(self.name), _to_utf8(key)): 341 return True 342 for parent in self.config.parents: 343 if parent[self.name].contains(key, defaults=False): 344 return True 345 return defaults and (self.name, key) in Option.registry
346 347 __contains__ = contains 348
349 - def iterate(self, compmgr=None, defaults=True):
350 """Iterate over the options in this section. 351 352 If `compmgr` is specified, only return default option values for 353 components that are enabled in the given `ComponentManager`. 354 """ 355 options = set() 356 name_str = _to_utf8(self.name) 357 if self.config.parser.has_section(name_str): 358 for option_str in self.config.parser.options(name_str): 359 option = to_unicode(option_str) 360 options.add(option.lower()) 361 yield option 362 for parent in self.config.parents: 363 for option in parent[self.name].iterate(defaults=False): 364 loption = option.lower() 365 if loption not in options: 366 options.add(loption) 367 yield option 368 if defaults: 369 for section, option in Option.get_registry(compmgr).keys(): 370 if section == self.name and option.lower() not in options: 371 yield option
372 373 __iter__ = iterate 374
375 - def __repr__(self):
376 return '<%s [%s]>' % (self.__class__.__name__, self.name)
377
378 - def get(self, key, default=''):
379 """Return the value of the specified option. 380 381 Valid default input is a string. Returns a string. 382 """ 383 cached = self._cache.get(key, _use_default) 384 if cached is not _use_default: 385 return cached 386 name_str = _to_utf8(self.name) 387 key_str = _to_utf8(key) 388 if self.config.parser.has_option(name_str, key_str): 389 value = self.config.parser.get(name_str, key_str) 390 else: 391 for parent in self.config.parents: 392 value = parent[self.name].get(key, _use_default) 393 if value is not _use_default: 394 break 395 else: 396 if default is not _use_default: 397 option = Option.registry.get((self.name, key)) 398 value = option.default if option else _use_default 399 else: 400 value = _use_default 401 if value is _use_default: 402 return default 403 if not value: 404 value = u'' 405 elif isinstance(value, basestring): 406 value = to_unicode(value) 407 self._cache[key] = value 408 return value
409
410 - def getbool(self, key, default=''):
411 """Return the value of the specified option as boolean. 412 413 This method returns `True` if the option value is one of "yes", "true", 414 "enabled", "on", or non-zero numbers, ignoring case. Otherwise `False` 415 is returned. 416 417 Valid default input is a string or a bool. Returns a bool. 418 """ 419 return as_bool(self.get(key, default))
420
421 - def getint(self, key, default=''):
422 """Return the value of the specified option as integer. 423 424 If the specified option can not be converted to an integer, a 425 `ConfigurationError` exception is raised. 426 427 Valid default input is a string or an int. Returns an int. 428 """ 429 value = self.get(key, default) 430 if not value: 431 return 0 432 try: 433 return int(value) 434 except ValueError: 435 raise ConfigurationError( 436 _('[%(section)s] %(entry)s: expected integer, got %(value)s', 437 section=self.name, entry=key, value=repr(value)))
438
439 - def getfloat(self, key, default=''):
440 """Return the value of the specified option as float. 441 442 If the specified option can not be converted to a float, a 443 `ConfigurationError` exception is raised. 444 445 Valid default input is a string, float or int. Returns a float. 446 """ 447 value = self.get(key, default) 448 if not value: 449 return 0.0 450 try: 451 return float(value) 452 except ValueError: 453 raise ConfigurationError( 454 _('[%(section)s] %(entry)s: expected float, got %(value)s', 455 section=self.name, entry=key, value=repr(value)))
456
457 - def getlist(self, key, default='', sep=',', keep_empty=True):
458 """Return a list of values that have been specified as a single 459 comma-separated option. 460 461 A different separator can be specified using the `sep` parameter. If 462 the `keep_empty` parameter is set to `False`, empty elements are omitted 463 from the list. 464 465 Valid default input is a string or a list. Returns a list. 466 """ 467 value = self.get(key, default) 468 if not value: 469 return [] 470 if isinstance(value, basestring): 471 items = [item.strip() for item in value.split(sep)] 472 else: 473 items = list(value) 474 if not keep_empty: 475 items = [item for item in items if item not in (None, '')] 476 return items
477
478 - def getpath(self, key, default=''):
479 """Return the value of the specified option as a path, relative to 480 the location of this configuration file. 481 482 Valid default input is a string. Returns a normalized path. 483 """ 484 path = self.get(key, default) 485 if not path: 486 return default 487 if not os.path.isabs(path): 488 path = os.path.join(os.path.dirname(self.config.filename), path) 489 return os.path.normcase(os.path.realpath(path))
490
491 - def options(self, compmgr=None):
492 """Return `(key, value)` tuples for every option in the section. 493 494 This includes options that have default values that haven't been 495 overridden. If `compmgr` is specified, only return default option 496 values for components that are enabled in the given `ComponentManager`. 497 """ 498 for key in self.iterate(compmgr): 499 yield key, self.get(key)
500
501 - def set(self, key, value):
502 """Change a configuration value. 503 504 These changes are not persistent unless saved with `save()`. 505 """ 506 self._cache.pop(key, None) 507 name_str = _to_utf8(self.name) 508 key_str = _to_utf8(key) 509 if not self.config.parser.has_section(name_str): 510 self.config.parser.add_section(name_str) 511 if value is None: 512 self.overridden[key] = True 513 value_str = '' 514 else: 515 value_str = _to_utf8(value) 516 return self.config.parser.set(name_str, key_str, value_str)
517
518 - def remove(self, key):
519 """Delete a key from this section. 520 521 Like for `set()`, the changes won't persist until `save()` gets called. 522 """ 523 name_str = _to_utf8(self.name) 524 if self.config.parser.has_section(name_str): 525 self._cache.pop(key, None) 526 self.config.parser.remove_option(_to_utf8(self.name), _to_utf8(key))
527
528 529 -def _get_registry(cls, compmgr=None):
530 """Return the descriptor registry. 531 532 If `compmgr` is specified, only return descriptors for components that 533 are enabled in the given `ComponentManager`. 534 """ 535 if compmgr is None: 536 return cls.registry 537 538 from trac.core import ComponentMeta 539 components = {} 540 for comp in ComponentMeta._components: 541 for attr in comp.__dict__.itervalues(): 542 if isinstance(attr, cls): 543 components[attr] = comp 544 545 return dict(each for each in cls.registry.iteritems() 546 if each[1] not in components 547 or compmgr.is_enabled(components[each[1]]))
548
549 550 -class ConfigSection(object):
551 """Descriptor for configuration sections.""" 552 553 registry = {} 554 555 @staticmethod
556 - def get_registry(compmgr=None):
557 """Return the section registry, as a `dict` mapping section names to 558 `ConfigSection` objects. 559 560 If `compmgr` is specified, only return sections for components that are 561 enabled in the given `ComponentManager`. 562 """ 563 return _get_registry(ConfigSection, compmgr)
564
565 - def __init__(self, name, doc, doc_domain='tracini'):
566 """Create the configuration section.""" 567 self.name = name 568 self.registry[self.name] = self 569 self.__doc__ = cleandoc(doc) 570 self.doc_domain = doc_domain
571
572 - def __get__(self, instance, owner):
573 if instance is None: 574 return self 575 config = getattr(instance, 'config', None) 576 if config and isinstance(config, Configuration): 577 return config[self.name]
578
579 - def __repr__(self):
580 return '<%s [%s]>' % (self.__class__.__name__, self.name)
581
582 583 -class Option(object):
584 """Descriptor for configuration options.""" 585 586 registry = {} 587
588 - def accessor(self, section, name, default):
589 return section.get(name, default)
590 591 @staticmethod
592 - def get_registry(compmgr=None):
593 """Return the option registry, as a `dict` mapping `(section, key)` 594 tuples to `Option` objects. 595 596 If `compmgr` is specified, only return options for components that are 597 enabled in the given `ComponentManager`. 598 """ 599 return _get_registry(Option, compmgr)
600
601 - def __init__(self, section, name, default=None, doc='', 602 doc_domain='tracini'):
603 """Create the configuration option. 604 605 :param section: the name of the configuration section this option 606 belongs to 607 :param name: the name of the option 608 :param default: the default value for the option 609 :param doc: documentation of the option 610 """ 611 self.section = section 612 self.name = name 613 self.default = default 614 self.registry[(self.section, self.name)] = self 615 self.__doc__ = cleandoc(doc) 616 self.doc_domain = doc_domain
617
618 - def __get__(self, instance, owner):
619 if instance is None: 620 return self 621 config = getattr(instance, 'config', None) 622 if config and isinstance(config, Configuration): 623 section = config[self.section] 624 value = self.accessor(section, self.name, self.default) 625 return value
626
627 - def __set__(self, instance, value):
628 raise AttributeError(_("Setting attribute is not allowed."))
629
630 - def __repr__(self):
631 return '<%s [%s] "%s">' % (self.__class__.__name__, self.section, 632 self.name)
633
634 - def dumps(self, value):
635 """Return the value as a string to write to a trac.ini file""" 636 if value is None: 637 return '' 638 if value is True: 639 return 'enabled' 640 if value is False: 641 return 'disabled' 642 if isinstance(value, unicode): 643 return value 644 return to_unicode(value)
645
646 647 -class BoolOption(Option):
648 """Descriptor for boolean configuration options.""" 649
650 - def accessor(self, section, name, default):
651 return section.getbool(name, default)
652
653 654 -class IntOption(Option):
655 """Descriptor for integer configuration options.""" 656
657 - def accessor(self, section, name, default):
658 return section.getint(name, default)
659
660 661 -class FloatOption(Option):
662 """Descriptor for float configuration options.""" 663
664 - def accessor(self, section, name, default):
665 return section.getfloat(name, default)
666
667 668 -class ListOption(Option):
669 """Descriptor for configuration options that contain multiple values 670 separated by a specific character. 671 """ 672
673 - def __init__(self, section, name, default=None, sep=',', keep_empty=False, 674 doc='', doc_domain='tracini'):
675 Option.__init__(self, section, name, default, doc, doc_domain) 676 self.sep = sep 677 self.keep_empty = keep_empty
678
679 - def accessor(self, section, name, default):
680 return section.getlist(name, default, self.sep, self.keep_empty)
681
682 - def dumps(self, value):
683 if isinstance(value, (list, tuple)): 684 return self.sep.join(Option.dumps(self, v) or '' for v in value) 685 return Option.dumps(self, value)
686
687 688 -class ChoiceOption(Option):
689 """Descriptor for configuration options providing a choice among a list 690 of items. 691 692 The default value is the first choice in the list. 693 """ 694
695 - def __init__(self, section, name, choices, doc='', doc_domain='tracini'):
696 Option.__init__(self, section, name, _to_utf8(choices[0]), doc, 697 doc_domain) 698 self.choices = set(_to_utf8(choice).strip() for choice in choices)
699
700 - def accessor(self, section, name, default):
701 value = section.get(name, default) 702 if value not in self.choices: 703 raise ConfigurationError( 704 _('[%(section)s] %(entry)s: expected one of ' 705 '(%(choices)s), got %(value)s', 706 section=section.name, entry=name, value=repr(value), 707 choices=', '.join('"%s"' % c 708 for c in sorted(self.choices)))) 709 return value
710
711 712 -class PathOption(Option):
713 """Descriptor for file system path configuration options. 714 715 Relative paths are resolved to absolute paths using the directory 716 containing the configuration file as the reference. 717 """ 718
719 - def accessor(self, section, name, default):
720 return section.getpath(name, default)
721
722 723 -class ExtensionOption(Option):
724 """Name of a component implementing `interface`. Raises a 725 `ConfigurationError` if the component cannot be found in the list of 726 active components implementing the interface.""" 727
728 - def __init__(self, section, name, interface, default=None, doc='', 729 doc_domain='tracini'):
730 Option.__init__(self, section, name, default, doc, doc_domain) 731 self.xtnpt = ExtensionPoint(interface)
732
733 - def __get__(self, instance, owner):
734 if instance is None: 735 return self 736 value = Option.__get__(self, instance, owner) 737 for impl in self.xtnpt.extensions(instance): 738 if impl.__class__.__name__ == value: 739 return impl 740 raise ConfigurationError( 741 tag_("Cannot find an implementation of the %(interface)s " 742 "interface named %(implementation)s. Please check " 743 "that the Component is enabled or update the option " 744 "%(option)s in trac.ini.", 745 interface=tag.tt(self.xtnpt.interface.__name__), 746 implementation=tag.tt(value), 747 option=tag.tt("[%s] %s" % (self.section, self.name))))
748
749 750 -class OrderedExtensionsOption(ListOption):
751 """A comma separated, ordered, list of components implementing `interface`. 752 Can be empty. 753 754 If `include_missing` is true (the default) all components implementing the 755 interface are returned, with those specified by the option ordered first.""" 756
757 - def __init__(self, section, name, interface, default=None, 758 include_missing=True, doc='', doc_domain='tracini'):
759 ListOption.__init__(self, section, name, default, doc=doc, 760 doc_domain=doc_domain) 761 self.xtnpt = ExtensionPoint(interface) 762 self.include_missing = include_missing
763
764 - def __get__(self, instance, owner):
765 if instance is None: 766 return self 767 order = ListOption.__get__(self, instance, owner) 768 components = [] 769 implementing_classes = [] 770 for impl in self.xtnpt.extensions(instance): 771 implementing_classes.append(impl.__class__.__name__) 772 if self.include_missing or impl.__class__.__name__ in order: 773 components.append(impl) 774 not_found = sorted(set(order) - set(implementing_classes)) 775 if not_found: 776 raise ConfigurationError( 777 tag_("Cannot find implementation(s) of the %(interface)s " 778 "interface named %(implementation)s. Please check " 779 "that the Component is enabled or update the option " 780 "%(option)s in trac.ini.", 781 interface=tag.tt(self.xtnpt.interface.__name__), 782 implementation=tag( 783 (', ' if idx != 0 else None, tag.tt(impl)) 784 for idx, impl in enumerate(not_found)), 785 option=tag.tt("[%s] %s" % (self.section, self.name)))) 786 787 def compare(x, y): 788 x, y = x.__class__.__name__, y.__class__.__name__ 789 if x not in order: 790 return int(y in order) 791 if y not in order: 792 return -int(x in order) 793 return cmp(order.index(x), order.index(y))
794 components.sort(compare) 795 return components
796
797 798 -class ConfigurationAdmin(Component):
799 """trac-admin command provider for trac.ini administration.""" 800 801 implements(IAdminCommandProvider) 802 803 # IAdminCommandProvider methods 804
805 - def get_admin_commands(self):
806 yield ('config get', '<section> <option>', 807 'Get the value of the given option in "trac.ini"', 808 self._complete_config, self._do_get) 809 yield ('config remove', '<section> <option>', 810 'Remove the specified option from "trac.ini"', 811 self._complete_config, self._do_remove) 812 yield ('config set', '<section> <option> <value>', 813 'Set the value for the given option in "trac.ini"', 814 self._complete_config, self._do_set)
815
816 - def _complete_config(self, args):
817 if len(args) == 1: 818 return self.config.sections() 819 elif len(args) == 2: 820 return [name for (name, value) in self.config[args[0]].options()]
821
822 - def _do_get(self, section, option):
823 if not self.config.has_option(section, option): 824 raise AdminCommandError( 825 _("Option '%(option)s' doesn't exist in section '%(section)s'", 826 option=option, section=section)) 827 printout(self.config.get(section, option))
828
829 - def _do_set(self, section, option, value):
830 self.config.set(section, option, value) 831 self.config.save() 832 if section == 'inherit' and option == 'file': 833 self.config.parse_if_needed(force=True) # Full reload
834
835 - def _do_remove(self, section, option):
836 if not self.config.has_option(section, option): 837 raise AdminCommandError( 838 _("Option '%(option)s' doesn't exist in section '%(section)s'", 839 option=option, section=section)) 840 self.config.remove(section, option) 841 self.config.save() 842 if section == 'inherit' and option == 'file': 843 self.config.parse_if_needed(force=True) # Full reload
844