summaryrefslogtreecommitdiff
path: root/src/mailman/commands/cli_members.py
diff options
context:
space:
mode:
authorBarry Warsaw2017-07-22 03:02:05 +0000
committerBarry Warsaw2017-07-22 03:02:05 +0000
commitf00b94f18e1d82d1488cbcee6053f03423bc2f49 (patch)
tree1a8e56dff0eab71e58e5fc9ecc5f3c614d7edca7 /src/mailman/commands/cli_members.py
parentf54c045519300f6f70947d1114f46c2b8ae0d368 (diff)
downloadmailman-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.py349
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