summaryrefslogtreecommitdiff
path: root/src/mailman/config/config.py
diff options
context:
space:
mode:
authorBarry Warsaw2015-01-04 20:20:33 -0500
committerBarry Warsaw2015-01-04 20:20:33 -0500
commit4a612db8e89afed74173b93f3b64fa567b8417a3 (patch)
tree81a687d113079a25f93279f35c7eee2aa2572510 /src/mailman/config/config.py
parent84af79988a4e916604cba31843778206efb7d1b8 (diff)
parentde181c1a40965a3a7deedd56a034a946f45b6984 (diff)
downloadmailman-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.py106
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)