summaryrefslogtreecommitdiff
path: root/src/mailman/bin/mailman.py
blob: 6b8cb26da40a9de11aaf50b11d150f34fdecc050 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# Copyright (C) 2009-2017 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/>.

"""The 'mailman' command dispatcher."""

import click

from contextlib import ExitStack
from mailman.config import config
from mailman.core.i18n import _
from mailman.core.initialize import initialize
from mailman.database.transaction import transaction
from mailman.interfaces.command import ICLISubCommand
from mailman.utilities.modules import add_components
from mailman.version import MAILMAN_VERSION_FULL
from public import public


class Subcommands(click.MultiCommand):
    # Handle dynamic listing and loading of `mailman` subcommands.
    def __init__(self, *args, **kws):
        super().__init__(*args, **kws)
        self._commands = {}
        # Look at all modules in the mailman.bin package and if they are
        # prepared to add a subcommand, let them do so.  I'm still undecided as
        # to whether this should be pluggable or not.  If so, then we'll
        # probably have to partially parse the arguments now, then initialize
        # the system, then find the plugins.  Punt on this for now.
        add_components('mailman.commands', ICLISubCommand, self._commands)

    def list_commands(self, ctx):
        return sorted(self._commands)               # pragma: nocover

    def get_command(self, ctx, name):
        try:
            return self._commands[name].command
        except KeyError as error:
            # Returning None here signals click to report usage information
            # and a "No such command" error message.
            return None

    # This is here to hook command parsing into the Mailman database
    # transaction system.  If the subcommand succeeds, the transaction is
    # committed, otherwise it's aborted.
    def invoke(self, ctx):
        with ExitStack() as resources:
            # If given a bogus subcommand, the database won't have been
            # initialized so there's no transaction to commit.
            if config.db is not None:
                resources.enter_context(transaction())
            return super().invoke(ctx)

    # https://github.com/pallets/click/issues/834
    #
    # Note that this only handles the case for the `mailman --help` output.
    # To handle `mailman <subcommand> --help` we create a custom click.Command
    # subclass and override this method there too.  See
    # src/mailman/utilities/options.py
    def format_options(self, ctx, formatter):
        """Writes all the options into the formatter if they exist."""
        opts = []
        for param in self.get_params(ctx):
            rv = param.get_help_record(ctx)
            if rv is not None:
                part_a, part_b = rv
                opts.append((part_a, part_b.replace('\n', ' ')))
        if opts:
            with formatter.section('Options'):
                formatter.write_dl(opts)
        # Print the list of available commands.
        super().format_commands(ctx, formatter)


@click.group(
    cls=Subcommands,
    context_settings=dict(help_option_names=['-h', '--help']))
@click.pass_context
@click.option(
    '-C', '--config', 'config_file',
    envvar='MAILMAN_CONFIG_FILE',
    type=click.Path(exists=True, dir_okay=False, resolve_path=True),
    help=_("""\
    Configuration file to use.  If not given, the environment variable
    MAILMAN_CONFIG_FILE is consulted and used if set.  If neither are given, a
    default configuration file is loaded."""))
@click.version_option(MAILMAN_VERSION_FULL, message='%(version)s')
@public
def main(ctx, config_file):
    # XXX https://github.com/pallets/click/issues/303
    """\
    The GNU Mailman mailing list management system
    Copyright 1998-2017 by the Free Software Foundation, Inc.
    http://www.list.org
    """
    # Initialize the system.  Honor the -C flag if given.
    initialize(config_file)
    # click handles dispatching to the subcommand via the Subcommands class.