VirtualMailManager/cli/subcommands.py
branchv0.6.x
changeset 340 4515afec62e5
child 341 6709d0faf2f5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/VirtualMailManager/cli/subcommands.py	Thu Aug 05 05:08:02 2010 +0000
@@ -0,0 +1,700 @@
+# -*- coding: UTF-8 -*-
+# Copyright 2007 - 2010, Pascal Volk
+# See COPYING for distribution information.
+"""
+    VirtualMailManager.cli.subcommands
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    VirtualMailManager's cli subcommands.
+"""
+
+import locale
+import os
+
+from textwrap import TextWrapper
+from time import strftime, strptime
+
+from VirtualMailManager import ENCODING
+from VirtualMailManager.account import SERVICES
+from VirtualMailManager.cli import get_winsize, prog, w_err, w_std
+from VirtualMailManager.constants import __copyright__, __date__, \
+     __version__, ACCOUNT_EXISTS, ALIAS_EXISTS, ALIASDOMAIN_ISDOMAIN, \
+     DOMAIN_ALIAS_EXISTS, INVALID_ARGUMENT, EX_MISSING_ARGS, RELOCATED_EXISTS
+from VirtualMailManager.errors import VMMError
+
+__all__ = (
+    'Command', 'RunContext', 'cmd_map', 'usage', 'alias_add', 'alias_delete',
+    'alias_info', 'aliasdomain_add', 'aliasdomain_delete', 'aliasdomain_info',
+    'aliasdomain_switch', 'configure', 'domain_add', 'domain_delete',
+    'domain_info', 'domain_transport', 'get_user', 'help_', 'list_domains',
+    'relocated_add', 'relocated_delete', 'relocated_info', 'user_add',
+    'user_delete', 'user_disable', 'user_enable', 'user_info', 'user_name',
+    'user_password', 'user_transport', 'version',
+)
+
+_ = lambda msg: msg
+txt_wrpr = TextWrapper(width=get_winsize()[1] - 1)
+
+
+class Command(object):
+    """Container class for command information."""
+    __slots__ = ('name', 'alias', 'func', 'args', 'descr')
+
+    def __init__(self, name, alias, func, args, descr):
+        """Create a new Command instance.
+
+        Arguments:
+
+        `name` : str
+          the command name, e.g. ``addalias``
+        `alias` : str
+          the command's short alias, e.g. ``aa``
+        `func` : callable
+          the function to handle the command
+        `args` : str
+          argument placeholders, e.g. ``aliasaddress``
+        `descr` : str
+          short description of the command
+        """
+        self.name = name
+        self.alias = alias
+        self.func = func
+        self.args = args
+        self.descr = descr
+
+    @property
+    def usage(self):
+        """the command's usage info."""
+        return u'%s %s %s' % (prog, self.name, self.args)
+
+
+class RunContext(object):
+    """Contains all information necessary to run a subcommand."""
+    __slots__ = ('argc', 'args', 'cget', 'hdlr', 'scmd')
+    plan_a_b = _(u'Plan A failed ... trying Plan B: %(subcommand)s %(object)s')
+
+    def __init__(self, argv, handler, command):
+        """Create a new RunContext"""
+        self.argc = len(argv)
+        self.args = [unicode(arg, ENCODING) for arg in argv]
+        self.cget = handler.cfg_dget
+        self.hdlr = handler
+        self.scmd = command
+
+
+def alias_add(ctx):
+    """create a new alias e-mail address"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing alias address and destination.'),
+              ctx.scmd)
+    elif ctx.argc < 4:
+        usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd)
+    ctx.hdlr.alias_add(ctx.args[2].lower(), *ctx.args[3:])
+
+
+def alias_delete(ctx):
+    """delete the specified alias e-mail address or one of its destinations"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing alias address.'), ctx.scmd)
+    elif ctx.argc < 4:
+        ctx.hdlr.alias_delete(ctx.args[2].lower())
+    else:
+        ctx.hdlr.alias_delete(ctx.args[2].lower(), ctx.args[3])
+
+
+def alias_info(ctx):
+    """show the destination(s) of the specified alias"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing alias address.'), ctx.scmd)
+    address = ctx.args[2].lower()
+    try:
+        _print_aliase_info(address, ctx.hdlr.alias_info(address))
+    except VMMError, err:
+        if err.code is ACCOUNT_EXISTS:
+            w_err(0, ctx.plan_a_b % {'subcommand': u'userinfo',
+                  'object': address})
+            ctx.args[1] = 'userinfo'
+            user_info(ctx)
+        elif err.code is RELOCATED_EXISTS:
+            w_std(0, ctx.plan_a_b % {'subcommand': u'relocatedinfo',
+                  'object': address})
+            ctx.args[1] = 'relocatedinfo'
+            relocated_info(ctx)
+        else:
+            raise
+
+
+def aliasdomain_add(ctx):
+    """create a new alias for an existing domain"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing alias domain name and destination '
+                                 u'domain name.'), ctx.scmd)
+    elif ctx.argc < 4:
+        usage(EX_MISSING_ARGS, _(u'Missing destination domain name.'),
+              ctx.scmd)
+    ctx.hdlr.aliasdomain_add(ctx.args[2].lower(), ctx.args[3].lower())
+
+
+def aliasdomain_delete(ctx):
+    """delete the specified alias domain"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing alias domain name.'), ctx.scmd)
+    ctx.hdlr.aliasdomain_delete(ctx.args[2].lower())
+
+
+def aliasdomain_info(ctx):
+    """show the destination of the given alias domain"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing alias domain name.'), ctx.scmd)
+    try:
+        _print_aliasdomain_info(ctx.hdlr.aliasdomain_info(ctx.args[2].lower()))
+    except VMMError, err:
+        if err.code is ALIASDOMAIN_ISDOMAIN:
+            w_err(0, ctx.plan_a_b % {'subcommand': u'domaininfo',
+                  'object': ctx.args[2].lower()})
+            ctx.args[1] = 'domaininfo'
+            domain_info(ctx)
+        else:
+            raise
+
+
+def aliasdomain_switch(ctx):
+    """assign the given alias domain to an other domain"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing alias domain name and destination '
+                                 u'domain name.'), ctx.scmd)
+    elif ctx.argc < 4:
+        usage(EX_MISSING_ARGS, _(u'Missing destination domain name.'),
+              ctx.scmd)
+    ctx.hdlr.aliasdomain_switch(ctx.args[2].lower(), ctx.args[3].lower())
+
+
+def configure(ctx):
+    """start interactive configuration modus"""
+    if ctx.argc < 3:
+        ctx.hdlr.configure()
+    else:
+        ctx.hdlr.configure(ctx.args[2].lower())
+
+
+def domain_add(ctx):
+    """create a new domain"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
+    elif ctx.argc < 4:
+        ctx.hdlr.domain_add(ctx.args[2].lower())
+    else:
+        ctx.hdlr.domain_add(ctx.args[2].lower(), ctx.args[3])
+
+
+def domain_delete(ctx):
+    """delete the given domain and all its alias domains"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
+    elif ctx.argc < 4:
+        ctx.hdlr.domain_delete(ctx.args[2].lower())
+    elif ctx.args[3].lower() == 'force':
+        ctx.hdlr.domain_delete(ctx.args[2].lower(), True)
+    else:
+        usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[3],
+              ctx.scmd)
+
+
+def domain_info(ctx):
+    """display information about the given domain"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
+    if ctx.argc < 4:
+        details = None
+    else:
+        details = ctx.args[3].lower()
+        if details not in ('accounts', 'aliasdomains', 'aliases', 'full',
+                           'relocated'):
+            usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % details,
+                  ctx.scmd)
+    try:
+        info = ctx.hdlr.domain_info(ctx.args[2].lower(), details)
+    except VMMError, err:
+        if err.code is DOMAIN_ALIAS_EXISTS:
+            w_err(0, ctx.plan_a_b % {'subcommand': u'aliasdomaininfo',
+                  'object': ctx.args[2].lower()})
+            ctx.args[1] = 'aliasdomaininfo'
+            aliasdomain_info(ctx)
+        else:
+            raise
+    else:
+        if not details:
+            _print_info(ctx, info, _(u'Domain'))
+        else:
+            _print_info(ctx, info[0], _(u'Domain'))
+            if details == u'accounts':
+                _print_list(info[1], _(u'accounts'))
+            elif details == u'aliasdomains':
+                _print_list(info[1], _(u'alias domains'))
+            elif details == u'aliases':
+                _print_list(info[1], _(u'aliases'))
+            elif details == u'relocated':
+                _print_list(info[1], _(u'relocated users'))
+            else:
+                _print_list(info[1], _(u'alias domains'))
+                _print_list(info[2], _(u'accounts'))
+                _print_list(info[3], _(u'aliases'))
+                _print_list(info[4], _(u'relocated users'))
+
+
+def domain_transport(ctx):
+    """update the transport of the specified domain"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing domain name and new transport.'),
+              ctx.scmd)
+    if ctx.argc < 4:
+        usage(EX_MISSING_ARGS, _(u'Missing new transport.'), ctx.scmd)
+    if ctx.argc < 5:
+        ctx.hdlr.domain_transport(ctx.args[2].lower(), ctx.args[3])
+    else:
+        force = ctx.args[4].lower()
+        if force != 'force':
+            usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % force,
+                  ctx.scmd)
+        ctx.hdlr.domain_transport(ctx.args[2].lower(), ctx.args[3], force)
+
+
+def get_user(ctx):
+    """get the address of the user with the given UID"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing userid.'), ctx.scmd)
+    _print_info(ctx, ctx.hdlr.user_by_uid(ctx.args[2]), _(u'Account'))
+
+
+def help_(ctx):
+    """print help messgaes."""
+    if ctx.argc > 2:
+        hlptpc = ctx.args[2].lower()
+        if hlptpc in cmd_map:
+            topic = hlptpc
+        else:
+            for scmd in cmd_map.itervalues():
+                if scmd.alias == hlptpc:
+                    topic = scmd.name
+                    break
+            else:
+                usage(INVALID_ARGUMENT, _(u"Unknown help topic: '%s'") %
+                      ctx.args[2], ctx.scmd)
+        # FIXME
+        w_err(1, "'help %s' not yet implemented." % topic, 'see also: vmm(1)')
+
+    old_ii = txt_wrpr.initial_indent
+    old_si = txt_wrpr.subsequent_indent
+    txt_wrpr.initial_indent = ' '
+    # len(max(_overview.iterkeys(), key=len)) #Py25
+    txt_wrpr.subsequent_indent = 20 * ' '
+    order = cmd_map.keys()
+    order.sort()
+
+    w_std(_(u'List of available subcommands:') + '\n')
+    for key in order:
+        w_std('\n'.join(txt_wrpr.wrap('%-18s %s' % (key, cmd_map[key].descr))))
+
+    txt_wrpr.initial_indent = old_ii
+    txt_wrpr.subsequent_indent = old_si
+    txt_wrpr.initial_indent = ''
+
+
+def list_domains(ctx):
+    """list all domains / search domains by pattern"""
+    matching = ctx.argc > 2
+    if matching:
+        gids, domains = ctx.hdlr.domain_list(ctx.args[2].lower())
+    else:
+        gids, domains = ctx.hdlr.domain_list()
+    _print_domain_list(gids, domains, matching)
+
+
+def relocated_add(ctx):
+    """create a new record for a relocated user"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS,
+              _(u'Missing relocated address and destination.'), ctx.scmd)
+    elif ctx.argc < 4:
+        usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd)
+    ctx.hdlr.relocated_add(ctx.args[2].lower(), ctx.args[3])
+
+
+def relocated_delete(ctx):
+    """delete the record of the relocated user"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing relocated address.'), ctx.scmd)
+    ctx.hdlr.relocated_delete(ctx.args[2].lower())
+
+
+def relocated_info(ctx):
+    """print information about a relocated user"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing relocated address.'), ctx.scmd)
+    relocated = ctx.args[2].lower()
+    try:
+        _print_relocated_info(addr=relocated,
+                              dest=ctx.hdlr.relocated_info(relocated))
+    except VMMError, err:
+        if err.code is ACCOUNT_EXISTS:
+            w_err(0, ctx.plan_a_b % {'subcommand': u'userinfo',
+                  'object': relocated})
+            ctx.args[1] = 'userinfoi'
+            user_info(ctx)
+        elif err.code is ALIAS_EXISTS:
+            w_err(0, ctx.plan_a_b % {'subcommand': u'aliasinfo',
+                  'object': relocated})
+            ctx.args[1] = 'aliasinfo'
+            alias_info(ctx)
+        else:
+            raise
+
+
+def user_add(ctx):
+    """create a new e-mail user with the given address"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
+    elif ctx.argc < 4:
+        password = None
+    else:
+        password = ctx.args[3]
+    ctx.hdlr.user_add(ctx.args[2].lower(), password)
+
+
+def user_delete(ctx):
+    """delete the specified user"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
+    elif ctx.argc < 4:
+        ctx.hdlr.user_delete(ctx.args[2].lower())
+    elif ctx.args[3].lower() == 'force':
+        ctx.hdlr.user_delete(ctx.args[2].lower(), True)
+    else:
+        usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[3],
+              ctx.scmd)
+
+
+def user_disable(ctx):
+    """deactivate all/the given service(s) for a user"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
+    elif ctx.argc < 4:
+        ctx.hdlr.user_disable(ctx.args[2].lower())
+    else:
+        services = [service.lower() for service in ctx.args[3:]]
+        unknown = [service for service in services if service not in SERVICES]
+        if unknown:
+            usage(INVALID_ARGUMENT, _(u"Invalid service arguments: %s") %
+                  ' '.join(unknown), ctx.scmd)
+        ctx.hdlr.user_disable(ctx.args[2].lower(), services)
+
+
+def user_enable(ctx):
+    """activate all or the given service(s) for a user"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
+    elif ctx.argc < 4:
+        ctx.hdlr.user_enable(ctx.args[2].lower())
+    else:
+        services = [service.lower() for service in ctx.args[3:]]
+        unknown = [service for service in services if service not in SERVICES]
+        if unknown:
+            usage(INVALID_ARGUMENT, _(u"Invalid service arguments: %s") %
+                  ' '.join(unknown), ctx.scmd)
+        ctx.hdlr.user_enable(ctx.args[2].lower(), services)
+
+
+def user_info(ctx):
+    """display information about the given address"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
+    if ctx.argc < 4:
+        details = None
+    else:
+        details = ctx.args[3].lower()
+        if details not in ('aliases', 'du', 'full'):
+            usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % details,
+                  ctx.scmd)
+    try:
+        info = ctx.hdlr.user_info(ctx.args[2].lower(), details)
+    except VMMError, err:
+        if err.code is ALIAS_EXISTS:
+            w_err(0, ctx.plan_a_b % {'subcommand': u'aliasinfo',
+                  'object': ctx.args[2].lower()})
+            ctx.args[1] = 'aliasinfo'
+            alias_info(ctx)
+        elif err.code is RELOCATED_EXISTS:
+            w_err(0, ctx.plan_a_b % {'subcommand': u'relocatedinfo',
+                  'object': ctx.args[2].lower()})
+            ctx.args[1] = 'relocatedinfo'
+            relocated_info(ctx)
+        else:
+            raise
+    else:
+        if details in (None, 'du'):
+            _print_info(ctx, info, _(u'Account'))
+        else:
+            _print_info(ctx, info[0], _(u'Account'))
+            _print_list(info[1], _(u'alias addresses'))
+
+
+def user_name(ctx):
+    """set or update the real name for an address"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing e-mail address and user’s name.'),
+              ctx.scmd)
+    if ctx.argc < 4:
+        usage(EX_MISSING_ARGS, _(u'Missing user’s name.'), ctx.scmd)
+    ctx.hdlr.user_name(ctx.args[2].lower(), ctx.args[3])
+
+
+def user_password(ctx):
+    """update the password for the given address"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
+    elif ctx.argc < 4:
+        password = None
+    else:
+        password = ctx.args[3]
+    ctx.hdlr.user_password(ctx.args[2].lower(), password)
+
+
+def user_transport(ctx):
+    """update the transport of the given address"""
+    if ctx.argc < 3:
+        usage(EX_MISSING_ARGS, _(u'Missing e-mail address and transport.'),
+              ctx.scmd)
+    if ctx.argc < 4:
+        usage(EX_MISSING_ARGS, _(u'Missing transport.'), ctx.scmd)
+    ctx.hdlr.user_transport(ctx.args[2].lower(), ctx.args[3])
+
+
+def usage(errno, errmsg, subcommand=None):
+    """print usage message for the given command or all commands.
+    When errno > 0, sys,exit(errno) will interrupt the program.
+    """
+    if subcommand and subcommand in cmd_map:
+        w_err(errno, _(u"Error: %s") % errmsg,
+              _(u"usage: ") + cmd_map[subcommand].usage)
+
+    # TP: Please adjust translated words like the original text.
+    # (It's a table header.) Extract from usage text:
+    # usage: vmm subcommand arguments
+    #   short long
+    #   subcommand                arguments
+    #
+    #   da    domainadd           fqdn [transport]
+    #   dd    domaindelete        fqdn [force]
+    u_head = _(u"""usage: %s subcommand arguments
+  short long
+  subcommand                arguments\n""") % prog
+    order = cmd_map.keys()
+    order.sort()
+    w_err(0, u_head)
+    for key in order:
+        scmd = cmd_map[key]
+        w_err(0, '  %-5s %-19s %s' % (scmd.alias, scmd.name, scmd.args))
+    w_err(errno, '', _(u"Error: %s") % errmsg)
+
+
+def version(ctx):
+    """Write version and copyright information to stdout."""
+    w_std('%s, %s %s (%s %s)\nPython %s %s %s\n\n%s\n%s %s' % (prog,
+    # TP: The words 'from', 'version' and 'on' are used in
+    # the version information, e.g.:
+    # vmm, version 0.5.2 (from 09/09/09)
+    # Python 2.5.4 on FreeBSD
+        _(u'version'), __version__, _(u'from'),
+        strftime(locale.nl_langinfo(locale.D_FMT),
+            strptime(__date__, '%Y-%m-%d')).decode(ENCODING, 'replace'),
+        os.sys.version.split()[0], _(u'on'), os.uname()[0],
+        __copyright__, prog,
+        _(u'is free software and comes with ABSOLUTELY NO WARRANTY.')))
+
+cmd = Command
+cmd_map = {  # {{{
+    # Account commands
+    'getuser': cmd('getuser', 'gu', get_user, 'uid',
+                   _(u'get the address of the user with the given UID')),
+    'useradd': cmd('useradd', 'ua', user_add, 'address [password]',
+                   _(u'create a new e-mail user with the given address')),
+    'userdelete': cmd('userdelete', 'ud', user_delete, 'address [force]',
+                      _(u'delete the specified user')),
+    'userdisable': cmd('userdisable', 'u0', user_disable,
+                       'address [service ...]',
+                       _(u'deactivate all/the given service(s) for a user')),
+    'userenable': cmd('userenable', 'u1', user_enable, 'address [service ...]',
+                      _(u'activate all or the given service(s) for a user')),
+    'userinfo': cmd('userinfo', 'ui', user_info, 'address [details]',
+                    _(u'display information about the given address')),
+    'username': cmd('username', 'un', user_name, 'address name',
+                    _(u'set or update the real name for an address')),
+    'userpassword': cmd('userpassword', 'up', user_password,
+                        'address [password]',
+                        _(u'update the password for the given address')),
+    'usertransport': cmd('usertransport', 'ut', user_transport,
+                         'address transport',
+                         _(u'update the transport of the given address')),
+    # Alias commands
+    'aliasadd': cmd('aliasadd', 'aa', alias_add, 'address destination ...',
+                    _(u'create a new alias e-mail address')),
+    'aliasdelete': cmd('aliasdelete', 'ad', alias_delete,
+                       'address [destination]',
+                       _(u'delete the specified alias e-mail address or one '
+                         u'of its destinations')),
+    'aliasinfo': cmd('aliasinfo', 'ai', alias_info, 'address',
+                     _(u'show the destination(s) of the specified alias')),
+    # AliasDomain commands
+    'aliasdomainadd': cmd('aliasdomainadd', 'ada', aliasdomain_add,
+                          'fqdn destination',
+                          _(u'create a new alias for an existing domain')),
+    'aliasdomaindelete': cmd('aliasdomaindelete', 'add', aliasdomain_delete,
+                             'fqdn', _(u'delete the specified alias domain')),
+    'aliasdomaininfo': cmd('aliasdomaininfo', 'adi', aliasdomain_info, 'fqdn',
+                         _(u'show the destination of the given alias domain')),
+    'aliasdomainswitch': cmd('aliasdomainswitch', 'ads', aliasdomain_switch,
+                             'fqdn destination',
+                       _(u'assign the given alias domain to an other domain')),
+    # Domain commands
+    'domainadd': cmd('domainadd', 'da', domain_add, 'fqdn [transport]',
+                     _(u'create a new domain')),
+    'domaindelete': cmd('domaindelete', 'dd', domain_delete, 'fqdn [force]',
+                      _(u'delete the given domain and all its alias domains')),
+    'domaininfo': cmd('domaininfo', 'di', domain_info, 'fqdn [details]',
+                      _(u'display information about the given domain')),
+    'domaintransport': cmd('domaintransport', 'dt', domain_transport,
+                           'fqdn transport [force]',
+                           _(u'update the transport of the specified domain')),
+    'listdomains': cmd('listdomains', 'ld', list_domains, '[pattern]',
+                       _(u'list all domains / search domains by pattern')),
+    # Relocated commands
+    'relocatedadd': cmd('relocatedadd', 'ra', relocated_add,
+                        'address newaddress',
+                        _(u'create a new record for a relocated user')),
+    'relocateddelete': cmd('relocateddelete', 'rd', relocated_delete,
+                           'address',
+                           _(u'delete the record of the relocated user')),
+    'relocatedinfo': cmd('relocatedinfo', 'ri', relocated_info, 'address',
+                         _(u'print information about a relocated user')),
+    # cli commands
+    'configure': cmd('configure', 'cf', configure, '[section]',
+                     _(u'start interactive configuration modus')),
+    'help': cmd('help', 'h', help_, '[subcommand]',
+                _(u'show a help overview or help for the given subcommand')),
+    'version': cmd('version', 'v', version, '',
+                   _(u'show version and copyright information')),
+}  # }}}
+
+
+def _get_order(ctx):
+    """returns a tuple with (key, 1||0) tuples. Used by functions, which
+    get a dict from the handler."""
+    order = ()
+    if ctx.scmd == 'domaininfo':
+        order = ((u'domainname', 0), (u'gid', 1), (u'transport', 0),
+                 (u'domaindir', 0), (u'aliasdomains', 0), (u'accounts', 0),
+                 (u'aliases', 0), (u'relocated', 0))
+    elif ctx.scmd == 'userinfo':
+        dc12 = ctx.cget('misc.dovecot_version') >= 0x10200b02
+        sieve = (u'managesieve', u'sieve')[dc12]
+        if ctx.argc == 4 and ctx.args[3] != u'aliases' or \
+           ctx.cget('account.disk_usage'):
+            order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1),
+                     (u'home', 0), (u'mail_location', 0), (u'disk usage', 0),
+                     (u'transport', 0), (u'smtp', 1), (u'pop3', 1),
+                     (u'imap', 1), (sieve, 1))
+        else:
+            order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1),
+                     (u'home', 0), (u'mail_location', 0), (u'transport', 0),
+                     (u'smtp', 1), (u'pop3', 1), (u'imap', 1), (sieve, 1))
+    elif ctx.scmd == 'getuser':
+        order = ((u'uid', 1), (u'gid', 1), (u'address', 0))
+    return order
+
+
+def _print_info(ctx, info, title):
+    """Print info dicts."""
+    # TP: used in e.g. 'Domain information' or 'Account information'
+    msg = u'%s %s' % (title, _(u'information'))
+    w_std(msg, u'-' * len(msg))
+    for key, upper in _get_order(ctx):
+        if upper:
+            w_std(u'\t%s: %s' % (key.upper().ljust(15, u'.'), info[key]))
+        else:
+            w_std(u'\t%s: %s' % (key.title().ljust(15, u'.'), info[key]))
+    print
+
+
+def _print_list(alist, title):
+    """Print a list."""
+    # TP: used in e.g. 'Available alias addresses' or 'Available accounts'
+    msg = u'%s %s' % (_(u'Available'), title)
+    w_std(msg, u'-' * len(msg))
+    if alist:
+        if title != _(u'alias domains'):
+            w_std(*(u'\t%s' % item for item in alist))
+        else:
+            for domain in alist:
+                if not domain.startswith('xn--'):
+                    w_std(u'\t%s' % domain)
+                else:
+                    w_std(u'\t%s (%s)' % (domain, domain.decode('idna')))
+        print
+    else:
+        w_std(_(u'\tNone'), '')
+
+
+def _print_aliase_info(alias, destinations):
+    """Print the alias address and all its destinations"""
+    title = _(u'Alias information')
+    w_std(title, u'-' * len(title))
+    w_std(_(u'\tMail for %s will be redirected to:') % alias)
+    w_std(*(u'\t     * %s' % dest for dest in destinations))
+    print
+
+
+def _print_relocated_info(**kwargs):
+    """Print the old and new addresses of a relocated user."""
+    title = _(u'Relocated information')
+    w_std(title, u'-' * len(title))
+    w_std(_(u"\tUser '%(addr)s' has moved to '%(dest)s'") % kwargs, '')
+
+
+def _format_domain(domain, main=True):
+    """format (prefix/convert) the domain name."""
+    if domain.startswith('xn--'):
+        domain = u'%s (%s)' % (domain, domain.decode('idna'))
+    if main:
+        return u'\t[+] %s' % domain
+    return u'\t[-]     %s' % domain
+
+
+def _print_domain_list(dids, domains, matching):
+    """Print a list of (matching) domains/alias domains."""
+    if matching:
+        title = _(u'Matching domains')
+    else:
+        title = _(u'Available domains')
+    w_std(title, '-' * len(title))
+    if domains:
+        for did in dids:
+            if domains[did][0] is not None:
+                w_std(_format_domain(domains[did][0]))
+            if len(domains[did]) > 1:
+                w_std(*(_format_domain(a, False) for a in domains[did][1:]))
+    else:
+        w_std(_('\tNone'))
+    print
+
+
+def _print_aliasdomain_info(info):
+    """Print alias domain information."""
+    title = _(u'Alias domain information')
+    for key in ('alias', 'domain'):
+        if info[key].startswith('xn--'):
+            info[key] = u'%s (%s)' % (info[key], info[key].decode('idna'))
+    w_std(title, '-' * len(title),
+          _('\tThe alias domain %(alias)s belongs to:\n\t    * %(domain)s') %
+          info, '')
+
+del _