summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--setup.py3
-rw-r--r--src/mailman/bin/__init__.py61
-rw-r--r--src/mailman/bin/list_lists.py104
-rw-r--r--src/mailman/bin/mailman.py79
-rw-r--r--src/mailman/bin/master.py5
-rw-r--r--src/mailman/bin/qrunner.py10
-rw-r--r--src/mailman/bin/version.py48
-rw-r--r--src/mailman/commands/docs/lists.txt127
-rw-r--r--src/mailman/commands/lists.py102
-rw-r--r--src/mailman/i18n.py107
-rw-r--r--src/mailman/interfaces/command.py19
11 files changed, 404 insertions, 261 deletions
diff --git a/setup.py b/setup.py
index 9ca967124..9c3e89c84 100644
--- a/setup.py
+++ b/setup.py
@@ -23,7 +23,6 @@ from string import Template
sys.path.insert(0, 'src')
-import mailman.bin
from mailman.version import VERSION as __version__
from setuptools import setup, find_packages
@@ -62,7 +61,7 @@ for dirpath, dirnames, filenames in os.walk(start_dir):
template = Template('$script = mailman.bin.$script:main')
scripts = set(
template.substitute(script=script)
- for script in mailman.bin.__all__
+ for script in ('mailman', 'mailmanctl', 'qrunner', 'master')
)
diff --git a/src/mailman/bin/__init__.py b/src/mailman/bin/__init__.py
index d61693c5e..e69de29bb 100644
--- a/src/mailman/bin/__init__.py
+++ b/src/mailman/bin/__init__.py
@@ -1,61 +0,0 @@
-# Copyright (C) 2007-2009 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/>.
-
-__all__ = [
- 'add_members',
- 'arch',
- 'bounces',
- 'bumpdigests',
- 'check_perms',
- 'checkdbs',
- 'cleanarch',
- 'config_list',
- 'confirm',
- 'create_list',
- 'disabled',
- 'dumpdb',
- 'export',
- 'find_member',
- 'gate_news',
- 'genaliases',
- 'import',
- 'inject',
- 'join',
- 'leave',
- 'list_lists',
- 'list_members',
- 'list_owners',
- 'mailmanctl',
- 'make_instance',
- 'master',
- 'mmsitepass',
- 'nightly_gzip',
- 'owner',
- 'post',
- 'qrunner',
- 'remove_list',
- 'request',
- 'senddigests',
- 'set_members',
- 'show_config',
- 'show_qfiles',
- 'testall',
- 'unshunt',
- 'update',
- 'version',
- 'withlist',
- ]
diff --git a/src/mailman/bin/list_lists.py b/src/mailman/bin/list_lists.py
deleted file mode 100644
index ea1640910..000000000
--- a/src/mailman/bin/list_lists.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# Copyright (C) 1998-2009 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/>.
-
-from mailman.config import config
-from mailman.i18n import _
-from mailman.options import Options
-
-
-
-class ScriptOptions(Options):
- usage = _("""\
-%prog [options]
-
-List all mailing lists.""")
-
- def add_options(self):
- super(ScriptOptions, self).add_options()
- self.parser.add_option(
- '-a', '--advertised',
- default=False, action='store_true',
- help=_("""\
-List only those mailing lists that are publicly advertised"""))
- self.parser.add_option(
- '-b', '--bare',
- default=False, action='store_true',
- help=_("""\
-Displays only the list name, with no description."""))
- self.parser.add_option(
- '-d', '--domain',
- default=[], type='string', action='append',
- dest='domains', help=_("""\
-List only those mailing lists that match the given virtual domain, which may
-be either the email host or the url host name. Multiple -d options may be
-given."""))
- self.parser.add_option(
- '-f', '--full',
- default=False, action='store_true',
- help=_("""\
-Print the full list name, including the posting address."""))
-
- def sanity_check(self):
- if len(self.arguments) > 0:
- self.parser.error(_('Unexpected arguments'))
-
-
-
-def main():
- options = ScriptOptions()
- options.initialize()
-
- mlists = []
- longest = 0
-
- listmgr = config.db.list_manager
- for fqdn_name in sorted(listmgr.names):
- mlist = listmgr.get(fqdn_name)
- if options.options.advertised and not mlist.advertised:
- continue
- if options.options.domains:
- for domain in options.options.domains:
- if domain in mlist.web_page_url or domain == mlist.host_name:
- mlists.append(mlist)
- break
- else:
- mlists.append(mlist)
- if options.options.full:
- name = mlist.fqdn_listname
- else:
- name = mlist.real_name
- longest = max(len(name), longest)
-
- if not mlists and not options.options.bare:
- print _('No matching mailing lists found')
- return
-
- if not options.options.bare:
- num_mlists = len(mlists)
- print _('$num_mlists matching mailing lists found:')
-
- format = '%%%ds - %%.%ds' % (longest, 77 - longest)
- for mlist in mlists:
- if options.options.full:
- name = mlist.fqdn_listname
- else:
- name = mlist.real_name
- if options.options.bare:
- print name
- else:
- description = mlist.description or _('[no description available]')
- print ' ', format % (name, description)
diff --git a/src/mailman/bin/mailman.py b/src/mailman/bin/mailman.py
new file mode 100644
index 000000000..42b145e67
--- /dev/null
+++ b/src/mailman/bin/mailman.py
@@ -0,0 +1,79 @@
+# Copyright (C) 2009 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."""
+
+from __future__ import absolute_import, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'main',
+ ]
+
+
+import os
+import argparse
+
+from zope.interface.verify import verifyObject
+
+from mailman.app.finder import find_components
+from mailman.core.initialize import initialize
+from mailman.i18n import _
+from mailman.interfaces.command import ICLISubCommand
+from mailman.version import MAILMAN_VERSION_FULL
+
+
+
+def main():
+ # Create the basic parser and add all globally common options.
+ parser = argparse.ArgumentParser(
+ description=_("""\
+ The GNU Mailman mailing list management system
+ Copyright 1998-2009 by the Free Software Foundation, Inc.
+ http://www.list.org
+ """),
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ version=MAILMAN_VERSION_FULL)
+ parser.add_argument(
+ '-C', '--config',
+ 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."""))
+ # 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.
+ subparser = parser.add_subparsers(title='Commands')
+ for command_class in find_components('mailman.commands', ICLISubCommand):
+ command = command_class()
+ verifyObject(ICLISubCommand, command)
+ command.add(subparser)
+ args = parser.parse_args()
+ if len(args.__dict__) == 0:
+ # No arguments or subcommands were given.
+ parser.print_help()
+ parser.exit()
+ # Before actually performing the subcommand, we need to initialize the
+ # Mailman system, and in particular, we must read the configuration file.
+ config_file = os.getenv('MAILMAN_CONFIG_FILE')
+ if config_file is None:
+ config_file = args.config
+ initialize(config_file)
+ # Perform the subcommand option.
+ args.func(args)
diff --git a/src/mailman/bin/master.py b/src/mailman/bin/master.py
index 58e0776bd..829468883 100644
--- a/src/mailman/bin/master.py
+++ b/src/mailman/bin/master.py
@@ -17,10 +17,11 @@
"""Master sub-process watcher."""
+from __future__ import absolute_import, unicode_literals
+
__metaclass__ = type
__all__ = [
- 'Loop',
- 'get_lock_data',
+ 'main',
]
diff --git a/src/mailman/bin/qrunner.py b/src/mailman/bin/qrunner.py
index bcda52ca6..73e701f16 100644
--- a/src/mailman/bin/qrunner.py
+++ b/src/mailman/bin/qrunner.py
@@ -15,6 +15,16 @@
# 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 queue runner."""
+
+from __future__ import absolute_import, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'main',
+ ]
+
+
import sys
import signal
import logging
diff --git a/src/mailman/bin/version.py b/src/mailman/bin/version.py
deleted file mode 100644
index 31fc33ec2..000000000
--- a/src/mailman/bin/version.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# Copyright (C) 1998-2009 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 version."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'main',
- ]
-
-
-# pylint: disable-msg=W0611
-from mailman.core.system import system
-from mailman.i18n import _
-from mailman.options import Options
-
-
-
-class ScriptOptions(Options):
- """See `Options`."""
- usage = _("""\
-%prog
-
-Print the Mailman version and exit.""")
-
-
-
-def main():
- """Main entry point."""
- options = ScriptOptions()
- options.initialize()
- print _('Using $system.mailman_version')
diff --git a/src/mailman/commands/docs/lists.txt b/src/mailman/commands/docs/lists.txt
new file mode 100644
index 000000000..85fe7edc2
--- /dev/null
+++ b/src/mailman/commands/docs/lists.txt
@@ -0,0 +1,127 @@
+=========================
+Command line list display
+=========================
+
+A system administrator can display all the mailing lists via the command
+line. When there are no mailing lists, a helpful message is displayed.
+
+ >>> class FakeArgs:
+ ... advertised = False
+ ... bare = False
+ ... domains = None
+ ... full = False
+
+ >>> from mailman.commands.lists import Lists
+ >>> command = Lists()
+ >>> command.process(FakeArgs)
+ No matching mailing lists found
+
+When there are a few mailing lists, they are shown in alphabetical order by
+their fully qualified list names, with a description.
+
+ >>> from mailman.config import config
+ >>> from mailman.interfaces.domain import IDomainManager
+ >>> domain_mgr = IDomainManager(config)
+ >>> domain_mgr.add('example.net')
+ <Domain example.net...>
+
+ >>> from mailman.app.lifecycle import create_list
+ >>> mlist_1 = create_list('list-one@example.com')
+ >>> mlist_1.description = 'List One'
+
+ >>> mlist_2 = create_list('list-two@example.com')
+ >>> mlist_2.description = 'List Two'
+
+ >>> mlist_3 = create_list('list-one@example.net')
+ >>> mlist_3.description = 'List One in Example.Net'
+ >>> transaction.commit()
+
+ >>> command.process(FakeArgs)
+ 3 matching mailing lists found:
+ List-one - List One
+ List-one - List One in Example.Net
+ List-two - List Two
+
+
+Full names
+==========
+
+You can display the mailing lists' full names, i.e. their posting addresses,
+with the --full switch.
+
+ >>> FakeArgs.full = True
+ >>> command.process(FakeArgs)
+ 3 matching mailing lists found:
+ list-one@example.com - List One
+ list-one@example.net - List One in Example.Net
+ list-two@example.com - List Two
+
+
+Bare names
+==========
+
+You can print less verbose output, such that only the mailing list's name is
+shown, with the --bare option.
+
+ >>> FakeArgs.bare = True
+ >>> FakeArgs.full = False
+ >>> command.process(FakeArgs)
+ List-one
+ List-one
+ List-two
+
+--full and --bare can be combined.
+
+ >>> FakeArgs.full = True
+ >>> command.process(FakeArgs)
+ list-one@example.com
+ list-one@example.net
+ list-two@example.com
+
+
+Specific domain
+===============
+
+You can narrow the search down to a specific domain with the --domain option.
+A helpful message is displayed if no matching domains are given.
+
+ >>> FakeArgs.bare = False
+ >>> FakeArgs.domains = ['example.org']
+ >>> command.process(FakeArgs)
+ No matching mailing lists found
+
+But if a matching domain is given, only mailing lists in that domain are
+shown.
+
+ >>> FakeArgs.domains = ['example.net']
+ >>> command.process(FakeArgs)
+ 1 matching mailing lists found:
+ list-one@example.net - List One in Example.Net
+
+More than one --domain argument can be given; then all mailing lists in
+matching domains are shown.
+
+ >>> FakeArgs.domains = ['example.com', 'example.net']
+ >>> command.process(FakeArgs)
+ 3 matching mailing lists found:
+ list-one@example.com - List One
+ list-one@example.net - List One in Example.Net
+ list-two@example.com - List Two
+
+
+Advertised lists
+================
+
+Mailing lists can be 'advertised' meaning their existence is public
+knowledge. Non-advertised lists are considered private. Display through the
+command line can select on this attribute.
+
+ >>> FakeArgs.domains = []
+ >>> FakeArgs.advertised = True
+ >>> mlist_1.advertised = False
+ >>> transaction.commit()
+
+ >>> command.process(FakeArgs)
+ 2 matching mailing lists found:
+ list-one@example.net - List One in Example.Net
+ list-two@example.com - List Two
diff --git a/src/mailman/commands/lists.py b/src/mailman/commands/lists.py
new file mode 100644
index 000000000..6757099d9
--- /dev/null
+++ b/src/mailman/commands/lists.py
@@ -0,0 +1,102 @@
+# Copyright (C) 2009 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 'lists' subcommand."""
+
+from __future__ import absolute_import, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'Lists',
+ ]
+
+
+from zope.interface import implements
+
+from mailman.config import config
+from mailman.i18n import _
+from mailman.interfaces.command import ICLISubCommand
+
+
+
+class Lists:
+ """The `lists` subcommand."""
+
+ implements(ICLISubCommand)
+
+ def add(self, subparser):
+ """See `ICLISubCommand`."""
+ lists_parser = subparser.add_parser(
+ 'lists', help=_('List all mailing lists'))
+ lists_parser.add_argument(
+ '-a', '--advertised',
+ default=False, action='store_true',
+ help=_(
+ 'List only those mailing lists that are publicly advertised'))
+ lists_parser.add_argument(
+ '-b', '--bare',
+ default=False, action='store_true',
+ help=_('Show only the list name, with no description'))
+ lists_parser.add_argument(
+ '-d', '--domain',
+ action='append', help=_("""\
+ List only those mailing lists hosted on the given domain, which
+ must be the email host name. Multiple -d options may be given.
+ """))
+ lists_parser.add_argument(
+ '-f', '--full',
+ default=False, action='store_true',
+ help=_(
+ 'Show the full mailing list name (i.e. the posting address'))
+ lists_parser.set_defaults(func=self.process)
+
+ def process(self, args):
+ """See `ICLISubCommand`."""
+ mailing_lists = []
+ list_manager = config.db.list_manager
+ # Gather the matching mailing lists.
+ for fqdn_name in sorted(list_manager.names):
+ mlist = list_manager.get(fqdn_name)
+ if args.advertised and not mlist.advertised:
+ continue
+ if args.domains and mlist.host_name not in args.domains:
+ continue
+ mailing_lists.append(mlist)
+ # Maybe no mailing lists matched.
+ if len(mailing_lists) == 0:
+ if not args.bare:
+ print _('No matching mailing lists found')
+ return
+ if not args.bare:
+ count = len(mailing_lists)
+ print _('$count matching mailing lists found:')
+ # Calculate the longest mailing list name.
+ longest = len(
+ max(mlist.fqdn_listname for mlist in mailing_lists)
+ if args.full else
+ max(mlist.real_name for mlist in mailing_lists))
+ # Print it out.
+ for mlist in mailing_lists:
+ name = (mlist.fqdn_listname if args.full else mlist.real_name)
+ if args.bare:
+ print name
+ else:
+ description = (mlist.description
+ if mlist.description is not None
+ else _('[no description available]'))
+ print '{0:{2}} - {1:{3}}'.format(
+ name, description, longest, 77 - longest)
diff --git a/src/mailman/i18n.py b/src/mailman/i18n.py
index 51330ef2e..7d63a2b86 100644
--- a/src/mailman/i18n.py
+++ b/src/mailman/i18n.py
@@ -17,10 +17,11 @@
"""Internationalization support."""
-from __future__ import unicode_literals
+from __future__ import absolute_import, unicode_literals
__metaclass__ = type
__all__ = [
+ 'Translator',
'_',
'get_translation',
'set_language',
@@ -35,6 +36,8 @@ import time
import string
import gettext
+from textwrap import dedent
+
import mailman.messages
from mailman.utilities.string import expand
@@ -127,48 +130,68 @@ if _translation is None:
-def _(s):
- """Translate the string.
+class Translator:
+ def __init__(self, dedent=True):
+ """Create a translation context.
- :param s: The string to transate
- :type s: string
- :return: The translated string
- :rtype: string
- """
- if s == '':
- return ''
- assert s, 'Cannot translate: {0}'.format(s)
- # Do translation of the given string into the current language, and do PEP
- # 292 style $-string interpolation into the resulting string.
- #
- # This lets you write something like:
- #
- # now = time.ctime(time.time())
- # print _('The current time is: $now')
- #
- # and have it Just Work. Note that the lookup order for keys in the
- # original string is 1) locals dictionary, 2) globals dictionary.
- #
- # Get the frame of the caller.
- # pylint: disable-msg=W0212
- frame = sys._getframe(1)
- # A `safe' dictionary is used so we won't get an exception if there's a
- # missing key in the dictionary.
- raw_dict = frame.f_globals.copy()
- raw_dict.update(frame.f_locals)
- # Mailman must be unicode safe internally (i.e. all strings inside Mailman
- # must be unicodes). The translation service is one boundary to the
- # outside world, so to honor this constraint, make sure that all strings
- # to come out of _() are unicodes, even if the translated string or
- # dictionary values are 8-bit strings.
- tns = _translation.ugettext(s)
- charset = _translation.charset() or 'us-ascii'
- # Python requires ** dictionaries to have str, not unicode keys. For our
- # purposes, keys should always be ascii. Values though should be unicode.
- translated_string = expand(tns, attrdict(raw_dict), Template)
- if isinstance(translated_string, str):
- translated_string = unicode(translated_string, charset)
- return translated_string
+ :param dedent: Whether the input string should be dedented.
+ :type dedent: bool
+ """
+ self.dedent = dedent
+
+ def _(self, original):
+ """Translate the string.
+
+ :param original: The original string to translate.
+ :type original: string
+ :return: The translated string.
+ :rtype: string
+ """
+ if original == '':
+ return ''
+ assert original, 'Cannot translate: {0}'.format(s)
+ # Because the original string is what the text extractors put into the
+ # catalog, we must first look up the original unadulterated string in
+ # the catalog. Use the global translation context for this.
+ #
+ # Mailman must be unicode safe internally (i.e. all strings inside
+ # Mailman are unicodes). The translation service is one boundary to
+ # the outside world, so to honor this constraint, make sure that all
+ # strings to come out of _() are unicodes, even if the translated
+ # string or dictionary values are 8-bit strings.
+ tns = _translation.ugettext(original)
+ charset = _translation.charset() or 'us-ascii'
+ # Do PEP 292 style $-string interpolation into the resulting string.
+ #
+ # This lets you write something like:
+ #
+ # now = time.ctime(time.time())
+ # print _('The current time is: $now')
+ #
+ # and have it Just Work. Note that the lookup order for keys in the
+ # original string is 1) locals dictionary, 2) globals dictionary.
+ #
+ # Get the frame of the caller.
+ # pylint: disable-msg=W0212
+ frame = sys._getframe(1)
+ # A 'safe' dictionary is used so we won't get an exception if there's
+ # a missing key in the dictionary.
+ raw_dict = frame.f_globals.copy()
+ raw_dict.update(frame.f_locals)
+ # Python requires ** dictionaries to have str, not unicode keys. For
+ # our purposes, keys should always be ascii. Values though should be
+ # unicode.
+ translated_string = expand(tns, attrdict(raw_dict), Template)
+ if isinstance(translated_string, str):
+ translated_string = unicode(translated_string, charset)
+ # Dedent the string if so desired.
+ if self.dedent:
+ translated_string = dedent(translated_string)
+ return translated_string
+
+
+# Global defaults.
+_ = Translator()._
diff --git a/src/mailman/interfaces/command.py b/src/mailman/interfaces/command.py
index 9995b80a9..14e7f9dd6 100644
--- a/src/mailman/interfaces/command.py
+++ b/src/mailman/interfaces/command.py
@@ -22,6 +22,7 @@ from __future__ import absolute_import, unicode_literals
__metaclass__ = type
__all__ = [
'ContinueProcessing',
+ 'ICLISubCommand',
'IEmailCommand',
'IEmailResults',
]
@@ -69,5 +70,19 @@ class IEmailCommand(Interface):
-class IBinCommand(Interface):
- """A command line (i.e. bin) command."""
+class ICLISubCommand(Interface):
+ """A command line interface subcommand."""
+
+ def add(subparser):
+ """Add the subcommand to the subparser.
+
+ :param subparser: The argument subparser.
+ :type subparser: `argparse.ArgumentParser`
+ """
+
+ def process(args):
+ """Process the subcommand.
+
+ :param args: The namespace, as passed in by argparse.
+ :type args: `argparse.Namespace`
+ """