summaryrefslogtreecommitdiff
path: root/src/mailman/bin/mailman.py
blob: ece2231800ab7b49a09c005dc66b2cb6d3da6df3 (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
112
113
114
115
116
117
118
# 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.plugins import add_pluggable_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 = {}
        self._loaded = False

    def _load(self):
        # Load commands lazily as pluggable commands need a parsed config to
        # find plugins.
        if not self._loaded:
            add_pluggable_components('commands', ICLISubCommand,
                                     self._commands)
            self._loaded = True

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

    def get_command(self, ctx, name):
        self._load()
        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)


def initialize_config(ctx, param, value):
    if ctx.resilient_parsing:
        return
    initialize(value)


@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."""),
    is_eager=True, callback=initialize_config)
@click.group(
    cls=Subcommands,
    context_settings=dict(help_option_names=['-h', '--help']))
@click.pass_context
@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
    """
    # click handles dispatching to the subcommand via the Subcommands class.