diff options
| author | Barry Warsaw | 2017-07-22 03:02:05 +0000 |
|---|---|---|
| committer | Barry Warsaw | 2017-07-22 03:02:05 +0000 |
| commit | f00b94f18e1d82d1488cbcee6053f03423bc2f49 (patch) | |
| tree | 1a8e56dff0eab71e58e5fc9ecc5f3c614d7edca7 /src/mailman/commands/cli_members.py | |
| parent | f54c045519300f6f70947d1114f46c2b8ae0d368 (diff) | |
| download | mailman-f00b94f18e1d82d1488cbcee6053f03423bc2f49.tar.gz mailman-f00b94f18e1d82d1488cbcee6053f03423bc2f49.tar.zst mailman-f00b94f18e1d82d1488cbcee6053f03423bc2f49.zip | |
Diffstat (limited to 'src/mailman/commands/cli_members.py')
| -rw-r--r-- | src/mailman/commands/cli_members.py | 349 |
1 files changed, 154 insertions, 195 deletions
diff --git a/src/mailman/commands/cli_members.py b/src/mailman/commands/cli_members.py index a3e4be980..99df11488 100644 --- a/src/mailman/commands/cli_members.py +++ b/src/mailman/commands/cli_members.py @@ -17,9 +17,8 @@ """The 'members' subcommand.""" -import sys +import click -from contextlib import ExitStack from email.utils import formataddr, parseaddr from mailman.app.membership import add_member from mailman.core.i18n import _ @@ -29,208 +28,168 @@ from mailman.interfaces.listmanager import IListManager from mailman.interfaces.member import ( AlreadySubscribedError, DeliveryMode, DeliveryStatus, MemberRole) from mailman.interfaces.subscriptions import RequestRecord +from mailman.utilities.options import I18nCommand from operator import attrgetter from public import public from zope.component import getUtility from zope.interface import implementer -@public -@implementer(ICLISubCommand) -class Members: - """Manage list memberships. With no arguments, list all members.""" - - name = 'members' - - def add(self, parser, command_parser): - """See `ICLISubCommand`.""" - self.parser = parser - command_parser.add_argument( - '-a', '--add', - dest='input_filename', metavar='FILENAME', - help=_("""\ - Add all member addresses in FILENAME. FILENAME can be '-' to - indicate standard input. Blank lines and lines That start with a - '#' are ignored. Without this option, this command displays - mailing list members.""")) - command_parser.add_argument( - '-o', '--output', - dest='output_filename', metavar='FILENAME', - help=_("""Display output to FILENAME instead of stdout. FILENAME - can be '-' to indicate standard output.""")) - command_parser.add_argument( - '-R', '--role', - default=None, metavar='ROLE', - choices=('any', 'owner', 'moderator', 'nonmember', 'member', - 'administrator'), - help=_("""Display only members with a given ROLE. The role may be - 'any', 'member', 'nonmember', 'owner', 'moderator', or - 'administrator' (i.e. owners and moderators). If not - given, then delivery members are used. """)) - command_parser.add_argument( - '-r', '--regular', - default=None, action='store_true', - help=_('Display only regular delivery members.')) - command_parser.add_argument( - '-d', '--digest', - default=None, metavar='KIND', - # BAW 2010-01-23 summary digests are not really supported yet. - choices=('any', 'plaintext', 'mime'), - help=_("""Display only digest members of KIND. 'any' means any - digest type, 'plaintext' means only plain text (RFC 1153) type - digests, 'mime' means MIME type digests.""")) - command_parser.add_argument( - '-n', '--nomail', - default=None, metavar='WHY', - choices=('enabled', 'any', 'unknown' - 'byadmin', 'byuser', 'bybounces'), - help=_("""Display only members with a given delivery - status. 'enabled' means all members whose delivery is enabled, - 'any' means members whose delivery is disabled for any reason, - 'byuser' means that the member disabled their own delivery, - 'bybounces' means that delivery was disabled by the automated - bounce processor, 'byadmin' means delivery was disabled by the - list administrator or moderator, and 'unknown' means that delivery - was disabled for unknown (legacy) reasons.""")) - # Required positional argument. - command_parser.add_argument( - 'list', metavar='LIST', nargs=1, - help=_("""\ - The list to operate on. This can be the fully qualified list - name', i.e. the posting address of the mailing list or the - List-ID.""")) - command_parser.epilog = _( - """Display a mailing list's members, with filtering along various - criteria.""") - - def process(self, args): - """See `ICLISubCommand`.""" - assert len(args.list) == 1, 'Missing mailing list name' - list_spec = args.list[0] - list_manager = getUtility(IListManager) - if '@' in list_spec: - mlist = list_manager.get(list_spec) - else: - mlist = list_manager.get_by_list_id(list_spec) - if mlist is None: - self.parser.error(_('No such list: $list_spec')) - if args.input_filename is None: - self.display_members(mlist, args) - else: - self.add_members(mlist, args) - - def display_members(self, mlist, args): - """Display the members of a mailing list. +def display_members(ctx, mlist, role, regular, digest, nomail, outfp): + # Which type of digest recipients should we display? + if digest == 'any': + digest_types = [ + DeliveryMode.plaintext_digests, + DeliveryMode.mime_digests, + DeliveryMode.summary_digests, + ] + elif digest is not None: + digest_types = [DeliveryMode[digest + '_digests']] + else: + # Don't filter on digest type. + pass + # Which members with delivery disabled should we display? + if nomail is None: + # Don't filter on delivery status. + pass + elif nomail == 'byadmin': + status_types = [DeliveryStatus.by_moderator] + elif nomail.startswith('by'): + status_types = [DeliveryStatus['by_' + nomail[2:]]] + elif nomail == 'enabled': + status_types = [DeliveryStatus.enabled] + elif nomail == 'unknown': + status_types = [DeliveryStatus.unknown] + elif nomail == 'any': + status_types = [ + DeliveryStatus.by_user, + DeliveryStatus.by_bounces, + DeliveryStatus.by_moderator, + DeliveryStatus.unknown, + ] + else: # pragma: nocover + # click should enforce a valid nomail option. + raise AssertionError(nomail) + # Which roles should we display? + if role is None: + # By default, filter on members. + roster = mlist.members + elif role == 'administrator': + roster = mlist.administrators + elif role == 'any': + roster = mlist.subscribers + else: + # click should enforce a valid member role. + roster = mlist.get_roster(MemberRole[role]) + # Print; outfp will be either the file or stdout to print to. + addresses = list(roster.addresses) + if len(addresses) == 0: + print(_('$mlist.list_id has no members'), file=outfp) + return + for address in sorted(addresses, key=attrgetter('email')): + if regular: + member = roster.get_member(address.email) + if member.delivery_mode != DeliveryMode.regular: + continue + if digest is not None: + member = roster.get_member(address.email) + if member.delivery_mode not in digest_types: + continue + if nomail is not None: + member = roster.get_member(address.email) + if member.delivery_status not in status_types: + continue + print(formataddr((address.display_name, address.original_email)), + file=outfp) - :param mlist: The mailing list to operate on. - :type mlist: `IMailingList` - :param args: The command line arguments. - :type args: `argparse.Namespace` - """ - if args.digest == 'any': - digest_types = [ - DeliveryMode.plaintext_digests, - DeliveryMode.mime_digests, - DeliveryMode.summary_digests, - ] - elif args.digest is not None: - digest_types = [DeliveryMode[args.digest + '_digests']] - else: - # Don't filter on digest type. - pass - if args.nomail is None: - # Don't filter on delivery status. - pass - elif args.nomail == 'byadmin': - status_types = [DeliveryStatus.by_moderator] - elif args.nomail.startswith('by'): - status_types = [DeliveryStatus['by_' + args.nomail[2:]]] - elif args.nomail == 'enabled': - status_types = [DeliveryStatus.enabled] - elif args.nomail == 'unknown': - status_types = [DeliveryStatus.unknown] - elif args.nomail == 'any': - status_types = [ - DeliveryStatus.by_user, - DeliveryStatus.by_bounces, - DeliveryStatus.by_moderator, - DeliveryStatus.unknown, - ] - else: - self.parser.error(_('Unknown delivery status: $args.nomail')) +@transactional +def add_members(mlist, infp): + for line in infp: + # Ignore blank lines and lines that start with a '#'. + if line.startswith('#') or len(line.strip()) == 0: + continue + # Parse the line and ensure that the values are unicodes. + display_name, email = parseaddr(line) + try: + add_member(mlist, + RequestRecord(email, display_name, + DeliveryMode.regular, + mlist.preferred_language.code)) + except AlreadySubscribedError: + # It's okay if the address is already subscribed, just print a + # warning and continue. + if not display_name: + print(_('Already subscribed (skipping): $email')) + else: + print(_('Already subscribed (skipping): ' + '$display_name <$email>')) - if args.role is None: - # By default, filter on members. - roster = mlist.members - elif args.role == 'administrator': - roster = mlist.administrators - elif args.role == 'any': - roster = mlist.subscribers - else: - try: - roster = mlist.get_roster(MemberRole[args.role]) - except KeyError: - self.parser.error(_('Unknown member role: $args.role')) - with ExitStack() as resources: - if args.output_filename == '-' or args.output_filename is None: - fp = sys.stdout - else: - fp = resources.enter_context( - open(args.output_filename, 'w', encoding='utf-8')) - addresses = list(roster.addresses) - if len(addresses) == 0: - print(_('$mlist.list_id has no members'), file=fp) - return - for address in sorted(addresses, key=attrgetter('email')): - if args.regular: - member = roster.get_member(address.email) - if member.delivery_mode != DeliveryMode.regular: - continue - if args.digest is not None: - member = roster.get_member(address.email) - if member.delivery_mode not in digest_types: - continue - if args.nomail is not None: - member = roster.get_member(address.email) - if member.delivery_status not in status_types: - continue - print( - formataddr((address.display_name, address.original_email)), - file=fp) +@click.command( + cls=I18nCommand, + help=_("""\ + Display a mailing list's members, with filtering along various criteria. + """)) +@click.option( + '--add', '-a', 'infp', metavar='FILENAME', + type=click.File(encoding='utf-8'), + help=_("""\ + Add all member addresses in FILENAME. FILENAME can be '-' to + indicate standard input. Blank lines and lines That start with a + '#' are ignored. Without this option, this command displays + mailing list members.""")) +@click.option( + '--output', '-o', 'outfp', metavar='FILENAME', + type=click.File(mode='w', encoding='utf-8', atomic=True), + help=_("""Display output to FILENAME instead of stdout. FILENAME + can be '-' to indicate standard output.""")) +@click.option( + '--role', '-R', + type=click.Choice(('any', 'owner', 'moderator', 'nonmember', 'member', + 'administrator')), + help=_("""\ + Display only members with a given ROLE. The role may be 'any', 'member', + 'nonmember', 'owner', 'moderator', or 'administrator' (i.e. owners and + moderators). If not given, then delivery members are used. """)) +@click.option( + '--regular', '-r', + is_flag=True, default=False, + help=_('Display only regular delivery members.')) +@click.option( + '--digest', '-d', metavar='kind', + # baw 2010-01-23 summary digests are not really supported yet. + type=click.Choice(('any', 'plaintext', 'mime')), + help=_("""\ + Display only digest members of kind. 'any' means any digest type, + 'plaintext' means only plain text (rfc 1153) type digests, 'mime' means + mime type digests.""")) +@click.option( + '--nomail', '-n', metavar='WHY', + type=click.Choice(('enabled', 'any', 'unknown', + 'byadmin', 'byuser', 'bybounces')), + help=_("""\ + Display only members with a given delivery status. 'enabled' means all + members whose delivery is enabled, 'any' means members whose delivery is + disabled for any reason, 'byuser' means that the member disabled their own + delivery, 'bybounces' means that delivery was disabled by the automated + bounce processor, 'byadmin' means delivery was disabled by the list + administrator or moderator, and 'unknown' means that delivery was disabled + for unknown (legacy) reasons.""")) +@click.argument('listspec') +@click.pass_context +def members(ctx, infp, outfp, role, regular, digest, nomail, listspec): + mlist = getUtility(IListManager).get(listspec) + if mlist is None: + ctx.fail(_('No such list: $listspec')) + if infp is None: + display_members(ctx, mlist, role, regular, digest, nomail, outfp) + else: + add_members(mlist, infp) - @transactional - def add_members(self, mlist, args): - """Add the members in a file to a mailing list. - :param mlist: The mailing list to operate on. - :type mlist: `IMailingList` - :param args: The command line arguments. - :type args: `argparse.Namespace` - """ - with ExitStack() as resources: - if args.input_filename == '-': - fp = sys.stdin - else: - fp = resources.enter_context( - open(args.input_filename, 'r', encoding='utf-8')) - for line in fp: - # Ignore blank lines and lines that start with a '#'. - if line.startswith('#') or len(line.strip()) == 0: - continue - # Parse the line and ensure that the values are unicodes. - display_name, email = parseaddr(line) - try: - add_member(mlist, - RequestRecord(email, display_name, - DeliveryMode.regular, - mlist.preferred_language.code)) - except AlreadySubscribedError: - # It's okay if the address is already subscribed, just - # print a warning and continue. - if not display_name: - print(_('Already subscribed (skipping): $email')) - else: - print(_('Already subscribed (skipping): ' - '$display_name <$email>')) +@public +@implementer(ICLISubCommand) +class Members: + name = 'members' + command = members |
