diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/commands/cli_conf.py | 140 | ||||
| -rw-r--r-- | src/mailman/commands/docs/conf.rst | 68 | ||||
| -rw-r--r-- | src/mailman/commands/tests/test_conf.py | 101 | ||||
| -rw-r--r-- | src/mailman/docs/ACKNOWLEDGMENTS.rst | 1 |
4 files changed, 310 insertions, 0 deletions
diff --git a/src/mailman/commands/cli_conf.py b/src/mailman/commands/cli_conf.py new file mode 100644 index 000000000..1f8095d92 --- /dev/null +++ b/src/mailman/commands/cli_conf.py @@ -0,0 +1,140 @@ +# Copyright (C) 2013 by the Free Software Foundation, Inc. +# +# This file is part of GNU Mailman. +# +# GNU Mailman is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. + +"""Print the mailman configuration.""" + +from __future__ import absolute_import, print_function, unicode_literals + +__metaclass__ = type +__all__ = [ + 'Conf' + ] + + +import sys + +from contextlib import closing +from lazr.config._config import Section +from zope.interface import implementer + +from mailman.config import config +from mailman.core.i18n import _ +from mailman.interfaces.command import ICLISubCommand + + + +@implementer(ICLISubCommand) +class Conf: + """Print the mailman configuration.""" + + name = 'conf' + + def add(self, parser, command_parser): + """See `ICLISubCommand`.""" + + self.parser = parser + command_parser.add_argument( + '-o', '--output', + action='store', help=_("""\ + File to send the output to. If not given, or if '-' is given, + standard output is used.""")) + command_parser.add_argument( + '-s', '--section', + action='store', help=_("""\ + Section to use for the lookup. If no key is given, all the + key-value pairs of the given section will be displayed. + """)) + command_parser.add_argument( + '-k', '--key', + action='store', help=_("""\ + Key to use for the lookup. If no section is given, all the + key-values pair from any section matching the given key will be + displayed. + """)) + + def _get_value(self, section, key): + return getattr(getattr(config, section), key) + + def _print_full_syntax(self, section, key, value, output): + print('[{}] {}: {}'.format(section, key, value), file=output) + + def _show_key_error(self, section, key): + self.parser.error('Section {}: No such key: {}'.format(section, key)) + + def _show_section_error(self, section): + self.parser.error('No such section: {}'.format(section)) + + def _print_values_for_section(self, section, output): + current_section = getattr(config, section) + for key in current_section: + self._print_full_syntax(section, key, + self._get_value(section, key), output) + + def _section_exists(self, section): + # Not all of the attributes in config are actual sections, so we have + # to check the section's type. + return (hasattr(config, section) and + isinstance(getattr(config, section), Section)) + + def _inner_process(self, args, output): + # Process the command, ignoring the closing of the output file. + section = args.section + key = args.key + # Case 1: Both section and key are given, so we can directly look up + # the value. + if section is not None and key is not None: + if not self._section_exists(section): + self._show_section_error(section) + elif not hasattr(getattr(config, section), key): + self._show_key_error(section, key) + else: + print(self._get_value(section, key), file=output) + # Case 2: Section is given, key is not given. + elif section is not None and key is None: + if self._section_exists(section): + self._print_values_for_section(section, output) + else: + self._show_section_error(section) + # Case 3: Section is not given, key is given. + elif section is None and key is not None: + for current_section in config.schema._section_schemas: + # We have to ensure that the current section actually exists + # and that it contains the given key. + if (self._section_exists(current_section) and + hasattr(getattr(config, current_section), key)): + # Then... + self._print_full_syntax( + current_section, key, + self._get_value(current_section, key), + output) + # Case 4: Neither section nor key are given, just display all the + # sections and their corresponding key/value pairs. + elif section is None and key is None: + for current_section in config.schema._section_schemas: + # However, we have to make sure that the current sections and + # key which are being looked up actually exist before trying + # to print them. + if self._section_exists(current_section): + self._print_values_for_section(current_section, output) + + def process(self, args): + """See `ICLISubCommand`.""" + if args.output is None or args.output == '-': + self._inner_process(args, sys.stdout) + else: + with closing(open(args.output, 'w')) as output: + self._inner_process(args, output) diff --git a/src/mailman/commands/docs/conf.rst b/src/mailman/commands/docs/conf.rst new file mode 100644 index 000000000..6e458fb54 --- /dev/null +++ b/src/mailman/commands/docs/conf.rst @@ -0,0 +1,68 @@ +============================ +Display configuration values +============================ + +Just like the `Postfix command postconf(1)`_, the ``bin/mailman conf`` command +lets you dump one or more Mailman configuration variables to standard output +or a file. + +Mailman's configuration is divided in multiple sections which contain multiple +key-value pairs. The ``bin/mailman conf`` command allows you to display +a specific key-value pair, or several key-value pairs. + + >>> class FakeArgs: + ... key = None + ... section = None + ... output = None + >>> from mailman.commands.cli_conf import Conf + >>> command = Conf() + +To get a list of all key-value pairs of any section, you need to call the +command without any options. + + >>> command.process(FakeArgs) + [logging.archiver] path: mailman.log + ... + [passwords] password_length: 8 + ... + [mailman] site_owner: noreply@example.com + ... + +You can list all the key-value pairs of a specific section. + + >>> FakeArgs.section = 'shell' + >>> command.process(FakeArgs) + [shell] use_ipython: no + [shell] banner: Welcome to the GNU Mailman shell + [shell] prompt: >>> + +You can also pass a key and display all key-value pairs matching the given +key, along with the names of the corresponding sections. + + >>> FakeArgs.section = None + >>> FakeArgs.key = 'path' + >>> command.process(FakeArgs) + [logging.archiver] path: mailman.log + [logging.locks] path: mailman.log + [logging.mischief] path: mailman.log + [logging.config] path: mailman.log + [logging.error] path: mailman.log + [logging.smtp] path: smtp.log + [logging.http] path: mailman.log + [logging.root] path: mailman.log + [logging.fromusenet] path: mailman.log + [logging.bounce] path: bounce.log + [logging.vette] path: mailman.log + [logging.runner] path: mailman.log + [logging.subscribe] path: mailman.log + [logging.debug] path: debug.log + +If you specify both a section and a key, you will get the corresponding value. + + >>> FakeArgs.section = 'mailman' + >>> FakeArgs.key = 'site_owner' + >>> command.process(FakeArgs) + noreply@example.com + + +.. _`Postfix command postconf(1)`: http://www.postfix.org/postconf.1.html diff --git a/src/mailman/commands/tests/test_conf.py b/src/mailman/commands/tests/test_conf.py new file mode 100644 index 000000000..bca7fe72f --- /dev/null +++ b/src/mailman/commands/tests/test_conf.py @@ -0,0 +1,101 @@ +# Copyright (C) 2013 by the Free Software Foundation, Inc. +# +# This file is part of GNU Mailman. +# +# GNU Mailman is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. + +"""Test the conf subcommand.""" + +from __future__ import absolute_import, print_function, unicode_literals + +__metaclass__ = type +__all__ = [ + 'TestConf', + ] + + +import os +import sys +import mock +import tempfile +import unittest + +from mailman.commands.cli_conf import Conf +from mailman.testing.layers import ConfigLayer + + + +class FakeArgs: + section = None + key = None + output = None + + +class FakeParser: + def __init__(self): + self.message = None + + def error(self, message): + self.message = message + sys.exit(1) + + + +class TestConf(unittest.TestCase): + """Test the conf subcommand.""" + + layer = ConfigLayer + + def setUp(self): + self.command = Conf() + self.command.parser = FakeParser() + self.args = FakeArgs() + + def test_cannot_access_nonexistent_section(self): + self.args.section = 'thissectiondoesnotexist' + self.args.key = None + with self.assertRaises(SystemExit): + self.command.process(self.args) + self.assertEqual(self.command.parser.message, + 'No such section: thissectiondoesnotexist') + + def test_cannot_access_nonexistent_key(self): + self.args.section = "mailman" + self.args.key = 'thiskeydoesnotexist' + with self.assertRaises(SystemExit): + self.command.process(self.args) + self.assertEqual(self.command.parser.message, + 'Section mailman: No such key: thiskeydoesnotexist') + + def test_output_to_explicit_stdout(self): + self.args.output = '-' + self.args.section = 'shell' + self.args.key = 'use_ipython' + with mock.patch('sys.stdout') as mock_object: + self.command.process(self.args) + mock_object.write.assert_has_calls( + [mock.call('no'), mock.call('\n')]) + + def test_output_to_file(self): + self.args.section = 'shell' + self.args.key = 'use_ipython' + fd, filename = tempfile.mkstemp() + try: + self.args.output = filename + self.command.process(self.args) + with open(filename, 'r') as fp: + contents = fp.read() + finally: + os.remove(filename) + self.assertEqual(contents, 'no\n') diff --git a/src/mailman/docs/ACKNOWLEDGMENTS.rst b/src/mailman/docs/ACKNOWLEDGMENTS.rst index e0929e2c4..55a746ac7 100644 --- a/src/mailman/docs/ACKNOWLEDGMENTS.rst +++ b/src/mailman/docs/ACKNOWLEDGMENTS.rst @@ -246,6 +246,7 @@ left off the list! * Pasi Sjöholm * Chris Snell * Mikhail Sobolev +* David Soto * Greg Stein * Dale Stimson * Students of HIT <mailman-cn@mail.cs.hit.edu.cn> |
