diff options
| author | Barry Warsaw | 2015-01-04 20:20:33 -0500 |
|---|---|---|
| committer | Barry Warsaw | 2015-01-04 20:20:33 -0500 |
| commit | 4a612db8e89afed74173b93f3b64fa567b8417a3 (patch) | |
| tree | 81a687d113079a25f93279f35c7eee2aa2572510 /src/mailman/config/config.py | |
| parent | 84af79988a4e916604cba31843778206efb7d1b8 (diff) | |
| parent | de181c1a40965a3a7deedd56a034a946f45b6984 (diff) | |
| download | mailman-4a612db8e89afed74173b93f3b64fa567b8417a3.tar.gz mailman-4a612db8e89afed74173b93f3b64fa567b8417a3.tar.zst mailman-4a612db8e89afed74173b93f3b64fa567b8417a3.zip | |
Diffstat (limited to 'src/mailman/config/config.py')
| -rw-r--r-- | src/mailman/config/config.py | 106 |
1 files changed, 55 insertions, 51 deletions
diff --git a/src/mailman/config/config.py b/src/mailman/config/config.py index 7181e23e9..779fa27e5 100644 --- a/src/mailman/config/config.py +++ b/src/mailman/config/config.py @@ -17,9 +17,6 @@ """Configuration file loading and management.""" -from __future__ import absolute_import, print_function, unicode_literals - -__metaclass__ = type __all__ = [ 'Configuration', 'external_configuration', @@ -29,27 +26,28 @@ __all__ = [ import os import sys +import mailman.templates -from ConfigParser import SafeConfigParser from flufl.lock import Lock from lazr.config import ConfigSchema, as_boolean -from pkg_resources import resource_stream, resource_string -from string import Template -from zope.component import getUtility -from zope.event import notify -from zope.interface import implementer - -import mailman.templates - from mailman import version from mailman.interfaces.configuration import ( ConfigurationUpdatedEvent, IConfiguration, MissingConfigurationFileError) from mailman.interfaces.languages import ILanguageManager from mailman.utilities.filesystem import makedirs from mailman.utilities.modules import call_name, expand_path +from pkg_resources import resource_filename, resource_string as resource_bytes +from six.moves.configparser import ConfigParser, RawConfigParser +from string import Template +from unittest.mock import patch +from zope.component import getUtility +from zope.event import notify +from zope.interface import implementer SPACE = ' ' +SPACERS = '\n' + MAILMAN_CFG_TEMPLATE = """\ # AUTOMATICALLY GENERATED BY MAILMAN ON {} @@ -66,6 +64,11 @@ MAILMAN_CFG_TEMPLATE = """\ # enabled: yes # recipient: your.address@your.domain""" +class _NonStrictRawConfigParser(RawConfigParser): + def __init__(self, *args, **kws): + kws['strict'] = False + super().__init__(*args, **kws) + @implementer(IConfiguration) @@ -102,30 +105,29 @@ class Configuration: def load(self, filename=None): """Load the configuration from the schema and config files.""" - schema_file = config_file = None - try: - schema_file = resource_stream('mailman.config', 'schema.cfg') - schema = ConfigSchema('schema.cfg', schema_file) - # If a configuration file was given, load it now too. First, load - # the absolute minimum default configuration, then if a - # configuration filename was given by the user, push it. - config_file = resource_stream('mailman.config', 'mailman.cfg') - self._config = schema.loadFile(config_file, 'mailman.cfg') - if filename is not None: - self.filename = filename - with open(filename) as user_config: - self._config.push(filename, user_config.read()) - finally: - if schema_file: - schema_file.close() - if config_file: - config_file.close() - self._post_process() + schema_file = resource_filename('mailman.config', 'schema.cfg') + schema = ConfigSchema(schema_file) + # If a configuration file was given, load it now too. First, load + # the absolute minimum default configuration, then if a + # configuration filename was given by the user, push it. + config_file = resource_filename('mailman.config', 'mailman.cfg') + self._config = schema.load(config_file) + if filename is None: + self._post_process() + else: + self.filename = filename + with open(filename, 'r', encoding='utf-8') as user_config: + self.push(filename, user_config.read()) def push(self, config_name, config_string): """Push a new configuration onto the stack.""" self._clear() - self._config.push(config_name, config_string) + # In Python 3, the RawConfigParser() must be created with + # strict=False, otherwise we'll get a DuplicateSectionError. + # See https://bugs.launchpad.net/lazr.config/+bug/1397779 + with patch('lazr.config._config.RawConfigParser', + _NonStrictRawConfigParser): + self._config.push(config_name, config_string) self._post_process() def pop(self, config_name): @@ -164,6 +166,7 @@ class Configuration: # path is relative. var_dir = os.environ.get('MAILMAN_VAR_DIR', category.var_dir) substitutions = dict( + cwd = os.getcwd(), argv = bin_dir, # Directories. bin_dir = category.bin_dir, @@ -185,26 +188,32 @@ class Configuration: lock_file = category.lock_file, pid_file = category.pid_file, ) + # Add the path to the .cfg file, if one was given on the command line. + if self.filename is not None: + substitutions['cfg_file'] = self.filename # Now, perform substitutions recursively until there are no more # variables with $-vars in them, or until substitutions are not # helping any more. last_dollar_count = 0 while True: + expandables = [] # Mutate the dictionary during iteration. - dollar_count = 0 - for key in substitutions.keys(): + for key in substitutions: raw_value = substitutions[key] value = Template(raw_value).safe_substitute(substitutions) if '$' in value: # Still more work to do. - dollar_count += 1 + expandables.append((key, value)) substitutions[key] = value - if dollar_count == 0: + if len(expandables) == 0: break - if dollar_count == last_dollar_count: - print('Path expansion infloop detected', file=sys.stderr) + if len(expandables) == last_dollar_count: + print('Path expansion infloop detected:\n', + SPACERS.join('\t{}: {}'.format(key, value) + for key, value in sorted(expandables)), + file=sys.stderr) sys.exit(1) - last_dollar_count = dollar_count + last_dollar_count = len(expandables) # Ensure that all paths are normalized and made absolute. Handle the # few special cases first. Most of these are due to backward # compatibility. @@ -269,7 +278,7 @@ class Configuration: -def load_external(path, encoding=None): +def load_external(path): """Load the configuration file named by path. :param path: A string naming the location of the external configuration @@ -278,21 +287,16 @@ def load_external(path, encoding=None): value must name a ``.cfg`` file located within Python's import path, however the trailing ``.cfg`` suffix is implied (don't provide it here). - :param encoding: The encoding to apply to the data read from path. If - None, then bytes will be returned. - :return: A unicode string or bytes, depending on ``encoding``. + :return: The contents of the configuration file. + :rtype: str """ # Is the context coming from a file system or Python path? if path.startswith('python:'): resource_path = path[7:] package, dot, resource = resource_path.rpartition('.') - config_string = resource_string(package, resource + '.cfg') - else: - with open(path, 'rb') as fp: - config_string = fp.read() - if encoding is None: - return config_string - return config_string.decode(encoding) + return resource_bytes(package, resource + '.cfg').decode('utf-8') + with open(path, 'r', encoding='utf-8') as fp: + return fp.read() def external_configuration(path): @@ -308,7 +312,7 @@ def external_configuration(path): """ # Is the context coming from a file system or Python path? cfg_path = expand_path(path) - parser = SafeConfigParser() + parser = ConfigParser() files = parser.read(cfg_path) if files != [cfg_path]: raise MissingConfigurationFileError(path) |
