VirtualMailManager/cli/subcommands.py
changeset 760 b678a1c43027
parent 748 659c4476c57c
child 761 e4e656f19771
equal deleted inserted replaced
748:659c4476c57c 760:b678a1c43027
     1 # -*- coding: UTF-8 -*-
       
     2 # Copyright (c) 2007 - 2014, Pascal Volk
       
     3 # See COPYING for distribution information.
       
     4 """
       
     5     VirtualMailManager.cli.subcommands
       
     6     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
     7 
       
     8     VirtualMailManager's cli subcommands.
       
     9 """
       
    10 
       
    11 import locale
       
    12 import os
       
    13 
       
    14 from textwrap import TextWrapper
       
    15 from time import strftime, strptime
       
    16 
       
    17 from VirtualMailManager import ENCODING
       
    18 from VirtualMailManager.cli import get_winsize, prog, w_err, w_std
       
    19 from VirtualMailManager.cli.clihelp import help_msgs
       
    20 from VirtualMailManager.common import human_size, size_in_bytes, \
       
    21      version_str, format_domain_default
       
    22 from VirtualMailManager.constants import __copyright__, __date__, \
       
    23      __version__, ACCOUNT_EXISTS, ALIAS_EXISTS, ALIASDOMAIN_ISDOMAIN, \
       
    24      DOMAIN_ALIAS_EXISTS, INVALID_ARGUMENT, EX_MISSING_ARGS, \
       
    25      RELOCATED_EXISTS, TYPE_ACCOUNT, TYPE_ALIAS, TYPE_RELOCATED
       
    26 from VirtualMailManager.errors import VMMError
       
    27 from VirtualMailManager.password import list_schemes
       
    28 from VirtualMailManager.serviceset import SERVICES
       
    29 
       
    30 __all__ = (
       
    31     'Command', 'RunContext', 'cmd_map', 'usage', 'alias_add', 'alias_delete',
       
    32     'alias_info', 'aliasdomain_add', 'aliasdomain_delete', 'aliasdomain_info',
       
    33     'aliasdomain_switch', 'catchall_add', 'catchall_info', 'catchall_delete',
       
    34     'config_get', 'config_set', 'configure',
       
    35     'domain_add', 'domain_delete',  'domain_info', 'domain_quota',
       
    36     'domain_services', 'domain_transport', 'domain_note', 'get_user', 'help_',
       
    37     'list_domains', 'list_pwschemes', 'list_users', 'list_aliases',
       
    38     'list_relocated', 'list_addresses', 'relocated_add', 'relocated_delete',
       
    39     'relocated_info', 'user_add', 'user_delete', 'user_info', 'user_name',
       
    40     'user_password', 'user_quota', 'user_services', 'user_transport',
       
    41     'user_note', 'version',
       
    42 )
       
    43 
       
    44 _ = lambda msg: msg
       
    45 txt_wrpr = TextWrapper(width=get_winsize()[1] - 1)
       
    46 cmd_map = {}
       
    47 
       
    48 
       
    49 class Command(object):
       
    50     """Container class for command information."""
       
    51     __slots__ = ('name', 'alias', 'func', 'args', 'descr')
       
    52     FMT_HLP_USAGE = """
       
    53 usage: %(prog)s %(name)s %(args)s
       
    54        %(prog)s %(alias)s %(args)s
       
    55 """
       
    56 
       
    57     def __init__(self, name, alias, func, args, descr):
       
    58         """Create a new Command instance.
       
    59 
       
    60         Arguments:
       
    61 
       
    62         `name` : str
       
    63           the command name, e.g. ``addalias``
       
    64         `alias` : str
       
    65           the command's short alias, e.g. ``aa``
       
    66         `func` : callable
       
    67           the function to handle the command
       
    68         `args` : str
       
    69           argument placeholders, e.g. ``aliasaddress``
       
    70         `descr` : str
       
    71           short description of the command
       
    72         """
       
    73         self.name = name
       
    74         self.alias = alias
       
    75         self.func = func
       
    76         self.args = args
       
    77         self.descr = descr
       
    78 
       
    79     @property
       
    80     def usage(self):
       
    81         """the command's usage info."""
       
    82         return u'%s %s %s' % (prog, self.name, self.args)
       
    83 
       
    84     def help_(self):
       
    85         """Print the Command's help message to stdout."""
       
    86         old_ii = txt_wrpr.initial_indent
       
    87         old_si = txt_wrpr.subsequent_indent
       
    88 
       
    89         txt_wrpr.subsequent_indent = (len(self.name) + 2) * ' '
       
    90         w_std(txt_wrpr.fill('%s: %s' % (self.name, self.descr)))
       
    91 
       
    92         info = Command.FMT_HLP_USAGE % dict(alias=self.alias, args=self.args,
       
    93                                             name=self.name, prog=prog)
       
    94         w_std(info)
       
    95 
       
    96         txt_wrpr.initial_indent = txt_wrpr.subsequent_indent = ' '
       
    97         try:
       
    98             [w_std(txt_wrpr.fill(_(para)) + '\n') for para
       
    99                     in help_msgs[self.name]]
       
   100         except KeyError:
       
   101             w_err(1, _(u"Subcommand '%s' is not yet documented." % self.name),
       
   102                   'see also: vmm(1)')
       
   103 
       
   104 
       
   105 class RunContext(object):
       
   106     """Contains all information necessary to run a subcommand."""
       
   107     __slots__ = ('argc', 'args', 'cget', 'hdlr', 'scmd')
       
   108     plan_a_b = _(u'Plan A failed ... trying Plan B: %(subcommand)s %(object)s')
       
   109 
       
   110     def __init__(self, argv, handler, command):
       
   111         """Create a new RunContext"""
       
   112         self.argc = len(argv)
       
   113         self.args = [unicode(arg, ENCODING) for arg in argv]
       
   114         self.cget = handler.cfg_dget
       
   115         self.hdlr = handler
       
   116         self.scmd = command
       
   117 
       
   118 
       
   119 def alias_add(ctx):
       
   120     """create a new alias e-mail address"""
       
   121     if ctx.argc < 3:
       
   122         usage(EX_MISSING_ARGS, _(u'Missing alias address and destination.'),
       
   123               ctx.scmd)
       
   124     elif ctx.argc < 4:
       
   125         usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd)
       
   126     ctx.hdlr.alias_add(ctx.args[2].lower(), *ctx.args[3:])
       
   127 
       
   128 
       
   129 def alias_delete(ctx):
       
   130     """delete the specified alias e-mail address or one of its destinations"""
       
   131     if ctx.argc < 3:
       
   132         usage(EX_MISSING_ARGS, _(u'Missing alias address.'), ctx.scmd)
       
   133     elif ctx.argc < 4:
       
   134         ctx.hdlr.alias_delete(ctx.args[2].lower())
       
   135     else:
       
   136         ctx.hdlr.alias_delete(ctx.args[2].lower(), ctx.args[3:])
       
   137 
       
   138 
       
   139 def alias_info(ctx):
       
   140     """show the destination(s) of the specified alias"""
       
   141     if ctx.argc < 3:
       
   142         usage(EX_MISSING_ARGS, _(u'Missing alias address.'), ctx.scmd)
       
   143     address = ctx.args[2].lower()
       
   144     try:
       
   145         _print_aliase_info(address, ctx.hdlr.alias_info(address))
       
   146     except VMMError, err:
       
   147         if err.code is ACCOUNT_EXISTS:
       
   148             w_err(0, ctx.plan_a_b % {'subcommand': u'userinfo',
       
   149                   'object': address})
       
   150             ctx.scmd = ctx.args[1] = 'userinfo'
       
   151             user_info(ctx)
       
   152         elif err.code is RELOCATED_EXISTS:
       
   153             w_err(0, ctx.plan_a_b % {'subcommand': u'relocatedinfo',
       
   154                   'object': address})
       
   155             ctx.scmd = ctx.args[1] = 'relocatedinfo'
       
   156             relocated_info(ctx)
       
   157         else:
       
   158             raise
       
   159 
       
   160 
       
   161 def aliasdomain_add(ctx):
       
   162     """create a new alias for an existing domain"""
       
   163     if ctx.argc < 3:
       
   164         usage(EX_MISSING_ARGS, _(u'Missing alias domain name and destination '
       
   165                                  u'domain name.'), ctx.scmd)
       
   166     elif ctx.argc < 4:
       
   167         usage(EX_MISSING_ARGS, _(u'Missing destination domain name.'),
       
   168               ctx.scmd)
       
   169     ctx.hdlr.aliasdomain_add(ctx.args[2].lower(), ctx.args[3].lower())
       
   170 
       
   171 
       
   172 def aliasdomain_delete(ctx):
       
   173     """delete the specified alias domain"""
       
   174     if ctx.argc < 3:
       
   175         usage(EX_MISSING_ARGS, _(u'Missing alias domain name.'), ctx.scmd)
       
   176     ctx.hdlr.aliasdomain_delete(ctx.args[2].lower())
       
   177 
       
   178 
       
   179 def aliasdomain_info(ctx):
       
   180     """show the destination of the given alias domain"""
       
   181     if ctx.argc < 3:
       
   182         usage(EX_MISSING_ARGS, _(u'Missing alias domain name.'), ctx.scmd)
       
   183     try:
       
   184         _print_aliasdomain_info(ctx.hdlr.aliasdomain_info(ctx.args[2].lower()))
       
   185     except VMMError, err:
       
   186         if err.code is ALIASDOMAIN_ISDOMAIN:
       
   187             w_err(0, ctx.plan_a_b % {'subcommand': u'domaininfo',
       
   188                   'object': ctx.args[2].lower()})
       
   189             ctx.scmd = ctx.args[1] = 'domaininfo'
       
   190             domain_info(ctx)
       
   191         else:
       
   192             raise
       
   193 
       
   194 
       
   195 def aliasdomain_switch(ctx):
       
   196     """assign the given alias domain to an other domain"""
       
   197     if ctx.argc < 3:
       
   198         usage(EX_MISSING_ARGS, _(u'Missing alias domain name and destination '
       
   199                                  u'domain name.'), ctx.scmd)
       
   200     elif ctx.argc < 4:
       
   201         usage(EX_MISSING_ARGS, _(u'Missing destination domain name.'),
       
   202               ctx.scmd)
       
   203     ctx.hdlr.aliasdomain_switch(ctx.args[2].lower(), ctx.args[3].lower())
       
   204 
       
   205 
       
   206 def catchall_add(ctx):
       
   207     """create a new catchall alias e-mail address"""
       
   208     if ctx.argc < 3:
       
   209         usage(EX_MISSING_ARGS, _(u'Missing domain and destination.'),
       
   210               ctx.scmd)
       
   211     elif ctx.argc < 4:
       
   212         usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd)
       
   213     ctx.hdlr.catchall_add(ctx.args[2].lower(), *ctx.args[3:])
       
   214 
       
   215 
       
   216 def catchall_delete(ctx):
       
   217     """delete the specified destination or all of the catchall destination"""
       
   218     if ctx.argc < 3:
       
   219         usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
       
   220     elif ctx.argc < 4:
       
   221         ctx.hdlr.catchall_delete(ctx.args[2].lower())
       
   222     else:
       
   223         ctx.hdlr.catchall_delete(ctx.args[2].lower(), ctx.args[3:])
       
   224 
       
   225 
       
   226 def catchall_info(ctx):
       
   227     """show the catchall destination(s) of the specified domain"""
       
   228     if ctx.argc < 3:
       
   229         usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
       
   230     address = ctx.args[2].lower()
       
   231     _print_catchall_info(address, ctx.hdlr.catchall_info(address))
       
   232 
       
   233 
       
   234 def config_get(ctx):
       
   235     """show the actual value of the configuration option"""
       
   236     if ctx.argc < 3:
       
   237         usage(EX_MISSING_ARGS, _(u"Missing option name."), ctx.scmd)
       
   238 
       
   239     noop = lambda option: option
       
   240     opt_formater = {
       
   241         'misc.dovecot_version': version_str,
       
   242         'domain.quota_bytes': human_size,
       
   243     }
       
   244 
       
   245     option = ctx.args[2].lower()
       
   246     w_std('%s = %s' % (option, opt_formater.get(option,
       
   247                        noop)(ctx.cget(option))))
       
   248 
       
   249 
       
   250 def config_set(ctx):
       
   251     """set a new value for the configuration option"""
       
   252     if ctx.argc < 3:
       
   253         usage(EX_MISSING_ARGS, _(u'Missing option and new value.'), ctx.scmd)
       
   254     if ctx.argc < 4:
       
   255         usage(EX_MISSING_ARGS, _(u'Missing new configuration value.'),
       
   256               ctx.scmd)
       
   257     ctx.hdlr.cfg_set(ctx.args[2].lower(), ctx.args[3])
       
   258 
       
   259 
       
   260 def configure(ctx):
       
   261     """start interactive configuration mode"""
       
   262     if ctx.argc < 3:
       
   263         ctx.hdlr.configure()
       
   264     else:
       
   265         ctx.hdlr.configure(ctx.args[2].lower())
       
   266 
       
   267 
       
   268 def domain_add(ctx):
       
   269     """create a new domain"""
       
   270     if ctx.argc < 3:
       
   271         usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
       
   272     elif ctx.argc < 4:
       
   273         ctx.hdlr.domain_add(ctx.args[2].lower())
       
   274     else:
       
   275         ctx.hdlr.domain_add(ctx.args[2].lower(), ctx.args[3])
       
   276     if ctx.cget('domain.auto_postmaster'):
       
   277         w_std(_(u'Creating account for postmaster@%s') % ctx.args[2].lower())
       
   278         ctx.scmd = 'useradd'
       
   279         ctx.args = [prog, ctx.scmd, u'postmaster@' + ctx.args[2].lower()]
       
   280         ctx.argc = 3
       
   281         user_add(ctx)
       
   282 
       
   283 
       
   284 def domain_delete(ctx):
       
   285     """delete the given domain and all its alias domains"""
       
   286     if ctx.argc < 3:
       
   287         usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
       
   288     elif ctx.argc < 4:
       
   289         ctx.hdlr.domain_delete(ctx.args[2].lower())
       
   290     elif ctx.args[3].lower() == 'force':
       
   291         ctx.hdlr.domain_delete(ctx.args[2].lower(), True)
       
   292     else:
       
   293         usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[3],
       
   294               ctx.scmd)
       
   295 
       
   296 
       
   297 def domain_info(ctx):
       
   298     """display information about the given domain"""
       
   299     if ctx.argc < 3:
       
   300         usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
       
   301     if ctx.argc < 4:
       
   302         details = None
       
   303     else:
       
   304         details = ctx.args[3].lower()
       
   305         if details not in ('accounts', 'aliasdomains', 'aliases', 'full',
       
   306                            'relocated', 'catchall'):
       
   307             usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % details,
       
   308                   ctx.scmd)
       
   309     try:
       
   310         info = ctx.hdlr.domain_info(ctx.args[2].lower(), details)
       
   311     except VMMError, err:
       
   312         if err.code is DOMAIN_ALIAS_EXISTS:
       
   313             w_err(0, ctx.plan_a_b % {'subcommand': u'aliasdomaininfo',
       
   314                   'object': ctx.args[2].lower()})
       
   315             ctx.scmd = ctx.args[1] = 'aliasdomaininfo'
       
   316             aliasdomain_info(ctx)
       
   317         else:
       
   318             raise
       
   319     else:
       
   320         q_limit = u'Storage: %(bytes)s; Messages: %(messages)s'
       
   321         if not details:
       
   322             info['bytes'] = human_size(info['bytes'])
       
   323             info['messages'] = locale.format('%d', info['messages'],
       
   324                                              True).decode(ENCODING, 'replace')
       
   325             info['quota limit/user'] = q_limit % info
       
   326             _print_info(ctx, info, _(u'Domain'))
       
   327         else:
       
   328             info[0]['bytes'] = human_size(info[0]['bytes'])
       
   329             info[0]['messages'] = locale.format('%d', info[0]['messages'],
       
   330                                                 True).decode(ENCODING,
       
   331                                                              'replace')
       
   332             info[0]['quota limit/user'] = q_limit % info[0]
       
   333             _print_info(ctx, info[0], _(u'Domain'))
       
   334             if details == u'accounts':
       
   335                 _print_list(info[1], _(u'accounts'))
       
   336             elif details == u'aliasdomains':
       
   337                 _print_list(info[1], _(u'alias domains'))
       
   338             elif details == u'aliases':
       
   339                 _print_list(info[1], _(u'aliases'))
       
   340             elif details == u'relocated':
       
   341                 _print_list(info[1], _(u'relocated users'))
       
   342             elif details == u'catchall':
       
   343                 _print_list(info[1], _(u'catch-all destinations'))
       
   344             else:
       
   345                 _print_list(info[1], _(u'alias domains'))
       
   346                 _print_list(info[2], _(u'accounts'))
       
   347                 _print_list(info[3], _(u'aliases'))
       
   348                 _print_list(info[4], _(u'relocated users'))
       
   349                 _print_list(info[5], _(u'catch-all destinations'))
       
   350 
       
   351 
       
   352 def domain_quota(ctx):
       
   353     """update the quota limit of the specified domain"""
       
   354     if ctx.argc < 3:
       
   355         usage(EX_MISSING_ARGS, _(u'Missing domain name and storage value.'),
       
   356               ctx.scmd)
       
   357     if ctx.argc < 4:
       
   358         usage(EX_MISSING_ARGS, _(u'Missing storage value.'), ctx.scmd)
       
   359     messages = 0
       
   360     force = None
       
   361     try:
       
   362         bytes_ = size_in_bytes(ctx.args[3])
       
   363     except (ValueError, TypeError):
       
   364         usage(INVALID_ARGUMENT, _(u"Invalid storage value: '%s'") %
       
   365               ctx.args[3], ctx.scmd)
       
   366     if ctx.argc < 5:
       
   367         pass
       
   368     elif ctx.argc < 6:
       
   369         try:
       
   370             messages = int(ctx.args[4])
       
   371         except ValueError:
       
   372             if ctx.args[4].lower() != 'force':
       
   373                 usage(INVALID_ARGUMENT,
       
   374                       _(u"Neither a valid number of messages nor the keyword "
       
   375                         u"'force': '%s'") % ctx.args[4], ctx.scmd)
       
   376             force = 'force'
       
   377     else:
       
   378         try:
       
   379             messages = int(ctx.args[4])
       
   380         except ValueError:
       
   381             usage(INVALID_ARGUMENT,
       
   382                   _(u"Not a valid number of messages: '%s'") % ctx.args[4],
       
   383                   ctx.scmd)
       
   384         if ctx.args[5].lower() != 'force':
       
   385             usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[5],
       
   386                   ctx.scmd)
       
   387         force = 'force'
       
   388     ctx.hdlr.domain_quotalimit(ctx.args[2].lower(), bytes_, messages, force)
       
   389 
       
   390 
       
   391 def domain_services(ctx):
       
   392     """allow all named service and block the uncredited."""
       
   393     if ctx.argc < 3:
       
   394         usage(EX_MISSING_ARGS, _(u'Missing domain name.'), ctx.scmd)
       
   395     services = []
       
   396     force = False
       
   397     if ctx.argc is 3:
       
   398         pass
       
   399     elif ctx.argc is 4:
       
   400         arg = ctx.args[3].lower()
       
   401         if arg in SERVICES:
       
   402             services.append(arg)
       
   403         elif arg == 'force':
       
   404             force = True
       
   405         else:
       
   406             usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % arg,
       
   407                   ctx.scmd)
       
   408     else:
       
   409         services.extend([service.lower() for service in ctx.args[3:-1]])
       
   410         arg = ctx.args[-1].lower()
       
   411         if arg == 'force':
       
   412             force = True
       
   413         else:
       
   414             services.append(arg)
       
   415         unknown = [service for service in services if service not in SERVICES]
       
   416         if unknown:
       
   417             usage(INVALID_ARGUMENT, _(u'Invalid service arguments: %s') %
       
   418                   ' '.join(unknown), ctx.scmd)
       
   419     ctx.hdlr.domain_services(ctx.args[2].lower(), (None, 'force')[force],
       
   420                              *services)
       
   421 
       
   422 
       
   423 def domain_transport(ctx):
       
   424     """update the transport of the specified domain"""
       
   425     if ctx.argc < 3:
       
   426         usage(EX_MISSING_ARGS, _(u'Missing domain name and new transport.'),
       
   427               ctx.scmd)
       
   428     if ctx.argc < 4:
       
   429         usage(EX_MISSING_ARGS, _(u'Missing new transport.'), ctx.scmd)
       
   430     if ctx.argc < 5:
       
   431         ctx.hdlr.domain_transport(ctx.args[2].lower(), ctx.args[3])
       
   432     else:
       
   433         force = ctx.args[4].lower()
       
   434         if force != 'force':
       
   435             usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % force,
       
   436                   ctx.scmd)
       
   437         ctx.hdlr.domain_transport(ctx.args[2].lower(), ctx.args[3], force)
       
   438 
       
   439 
       
   440 def domain_note(ctx):
       
   441     """update the note of the given domain"""
       
   442     if ctx.argc < 3:
       
   443         usage(EX_MISSING_ARGS, _(u'Missing domain name.'),
       
   444               ctx.scmd)
       
   445     elif ctx.argc < 4:
       
   446         note = None
       
   447     else:
       
   448         note = ' '.join(ctx.args[3:])
       
   449     ctx.hdlr.domain_note(ctx.args[2].lower(), note)
       
   450 
       
   451 
       
   452 def get_user(ctx):
       
   453     """get the address of the user with the given UID"""
       
   454     if ctx.argc < 3:
       
   455         usage(EX_MISSING_ARGS, _(u'Missing UID.'), ctx.scmd)
       
   456     _print_info(ctx, ctx.hdlr.user_by_uid(ctx.args[2]), _(u'Account'))
       
   457 
       
   458 
       
   459 def help_(ctx):
       
   460     """print help messages."""
       
   461     if ctx.argc > 2:
       
   462         hlptpc = ctx.args[2].lower()
       
   463         if hlptpc in cmd_map:
       
   464             topic = hlptpc
       
   465         else:
       
   466             for scmd in cmd_map.itervalues():
       
   467                 if scmd.alias == hlptpc:
       
   468                     topic = scmd.name
       
   469                     break
       
   470             else:
       
   471                 usage(INVALID_ARGUMENT, _(u"Unknown help topic: '%s'") %
       
   472                       ctx.args[2], ctx.scmd)
       
   473         if topic != u'help':
       
   474             return cmd_map[topic].help_()
       
   475 
       
   476     old_ii = txt_wrpr.initial_indent
       
   477     old_si = txt_wrpr.subsequent_indent
       
   478     txt_wrpr.initial_indent = ' '
       
   479     # len(max(_overview.iterkeys(), key=len)) #Py25
       
   480     txt_wrpr.subsequent_indent = 20 * ' '
       
   481     order = cmd_map.keys()
       
   482     order.sort()
       
   483 
       
   484     w_std(_(u'List of available subcommands:') + '\n')
       
   485     for key in order:
       
   486         w_std('\n'.join(txt_wrpr.wrap('%-18s %s' % (key, cmd_map[key].descr))))
       
   487 
       
   488     txt_wrpr.initial_indent = old_ii
       
   489     txt_wrpr.subsequent_indent = old_si
       
   490     txt_wrpr.initial_indent = ''
       
   491 
       
   492 
       
   493 def list_domains(ctx):
       
   494     """list all domains / search domains by pattern"""
       
   495     matching = ctx.argc > 2
       
   496     if matching:
       
   497         gids, domains = ctx.hdlr.domain_list(ctx.args[2].lower())
       
   498     else:
       
   499         gids, domains = ctx.hdlr.domain_list()
       
   500     _print_domain_list(gids, domains, matching)
       
   501 
       
   502 
       
   503 def list_pwschemes(ctx_unused):
       
   504     """Prints all usable password schemes and password encoding suffixes."""
       
   505     # TODO: Remove trailing colons from keys.
       
   506     # For now it is to late, the translators has stared their work
       
   507     keys = (_(u'Usable password schemes:'), _(u'Usable encoding suffixes:'))
       
   508     old_ii, old_si = txt_wrpr.initial_indent, txt_wrpr.subsequent_indent
       
   509     txt_wrpr.initial_indent = txt_wrpr.subsequent_indent = '\t'
       
   510     txt_wrpr.width = txt_wrpr.width - 8
       
   511 
       
   512     for key, value in zip(keys, list_schemes()):
       
   513         if key.endswith(':'):  # who knows … (see TODO above)
       
   514             #key = key.rpartition(':')[0]
       
   515             key = key[:-1]  # This one is for Py24
       
   516         w_std(key, len(key) * '-')
       
   517         w_std('\n'.join(txt_wrpr.wrap(' '.join(value))), '')
       
   518 
       
   519     txt_wrpr.initial_indent, txt_wrpr.subsequent_indent = old_ii, old_si
       
   520     txt_wrpr.width = txt_wrpr.width + 8
       
   521 
       
   522 
       
   523 def list_addresses(ctx, limit=None):
       
   524     """List all addresses / search addresses by pattern. The output can be
       
   525     limited with TYPE_ACCOUNT, TYPE_ALIAS and TYPE_RELOCATED, which can be
       
   526     bitwise ORed as a combination. Not specifying a limit is the same as
       
   527     combining all three."""
       
   528     if limit is None:
       
   529         limit = TYPE_ACCOUNT | TYPE_ALIAS | TYPE_RELOCATED
       
   530     matching = ctx.argc > 2
       
   531     if matching:
       
   532         gids, addresses = ctx.hdlr.address_list(limit, ctx.args[2].lower())
       
   533     else:
       
   534         gids, addresses = ctx.hdlr.address_list(limit)
       
   535     _print_address_list(limit, gids, addresses, matching)
       
   536 
       
   537 
       
   538 def list_users(ctx):
       
   539     """list all user accounts / search user accounts by pattern"""
       
   540     return list_addresses(ctx, TYPE_ACCOUNT)
       
   541 
       
   542 
       
   543 def list_aliases(ctx):
       
   544     """list all aliases / search aliases by pattern"""
       
   545     return list_addresses(ctx, TYPE_ALIAS)
       
   546 
       
   547 
       
   548 def list_relocated(ctx):
       
   549     """list all relocated records / search relocated records by pattern"""
       
   550     return list_addresses(ctx, TYPE_RELOCATED)
       
   551 
       
   552 
       
   553 def relocated_add(ctx):
       
   554     """create a new record for a relocated user"""
       
   555     if ctx.argc < 3:
       
   556         usage(EX_MISSING_ARGS,
       
   557               _(u'Missing relocated address and destination.'), ctx.scmd)
       
   558     elif ctx.argc < 4:
       
   559         usage(EX_MISSING_ARGS, _(u'Missing destination address.'), ctx.scmd)
       
   560     ctx.hdlr.relocated_add(ctx.args[2].lower(), ctx.args[3])
       
   561 
       
   562 
       
   563 def relocated_delete(ctx):
       
   564     """delete the record of the relocated user"""
       
   565     if ctx.argc < 3:
       
   566         usage(EX_MISSING_ARGS, _(u'Missing relocated address.'), ctx.scmd)
       
   567     ctx.hdlr.relocated_delete(ctx.args[2].lower())
       
   568 
       
   569 
       
   570 def relocated_info(ctx):
       
   571     """print information about a relocated user"""
       
   572     if ctx.argc < 3:
       
   573         usage(EX_MISSING_ARGS, _(u'Missing relocated address.'), ctx.scmd)
       
   574     relocated = ctx.args[2].lower()
       
   575     try:
       
   576         _print_relocated_info(addr=relocated,
       
   577                               dest=ctx.hdlr.relocated_info(relocated))
       
   578     except VMMError, err:
       
   579         if err.code is ACCOUNT_EXISTS:
       
   580             w_err(0, ctx.plan_a_b % {'subcommand': u'userinfo',
       
   581                   'object': relocated})
       
   582             ctx.scmd = ctx.args[1] = 'userinfoi'
       
   583             user_info(ctx)
       
   584         elif err.code is ALIAS_EXISTS:
       
   585             w_err(0, ctx.plan_a_b % {'subcommand': u'aliasinfo',
       
   586                   'object': relocated})
       
   587             ctx.scmd = ctx.args[1] = 'aliasinfo'
       
   588             alias_info(ctx)
       
   589         else:
       
   590             raise
       
   591 
       
   592 
       
   593 def user_add(ctx):
       
   594     """create a new e-mail user with the given address"""
       
   595     if ctx.argc < 3:
       
   596         usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
       
   597     elif ctx.argc < 4:
       
   598         password = None
       
   599     else:
       
   600         password = ctx.args[3]
       
   601     gen_pass = ctx.hdlr.user_add(ctx.args[2].lower(), password)
       
   602     if ctx.argc < 4 and gen_pass:
       
   603         w_std(_(u"Generated password: %s") % gen_pass)
       
   604 
       
   605 
       
   606 def user_delete(ctx):
       
   607     """delete the specified user"""
       
   608     if ctx.argc < 3:
       
   609         usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
       
   610     elif ctx.argc < 4:
       
   611         ctx.hdlr.user_delete(ctx.args[2].lower())
       
   612     elif ctx.args[3].lower() == 'force':
       
   613         ctx.hdlr.user_delete(ctx.args[2].lower(), True)
       
   614     else:
       
   615         usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % ctx.args[3],
       
   616               ctx.scmd)
       
   617 
       
   618 
       
   619 def user_info(ctx):
       
   620     """display information about the given address"""
       
   621     if ctx.argc < 3:
       
   622         usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
       
   623     if ctx.argc < 4:
       
   624         details = None
       
   625     else:
       
   626         details = ctx.args[3].lower()
       
   627         if details not in ('aliases', 'du', 'full'):
       
   628             usage(INVALID_ARGUMENT, _(u"Invalid argument: '%s'") % details,
       
   629                   ctx.scmd)
       
   630     try:
       
   631         info = ctx.hdlr.user_info(ctx.args[2].lower(), details)
       
   632     except VMMError, err:
       
   633         if err.code is ALIAS_EXISTS:
       
   634             w_err(0, ctx.plan_a_b % {'subcommand': u'aliasinfo',
       
   635                   'object': ctx.args[2].lower()})
       
   636             ctx.scmd = ctx.args[1] = 'aliasinfo'
       
   637             alias_info(ctx)
       
   638         elif err.code is RELOCATED_EXISTS:
       
   639             w_err(0, ctx.plan_a_b % {'subcommand': u'relocatedinfo',
       
   640                   'object': ctx.args[2].lower()})
       
   641             ctx.scmd = ctx.args[1] = 'relocatedinfo'
       
   642             relocated_info(ctx)
       
   643         else:
       
   644             raise
       
   645     else:
       
   646         if details in (None, 'du'):
       
   647             info['quota storage'] = _format_quota_usage(info['ql_bytes'],
       
   648                     info['uq_bytes'], True, info['ql_domaindefault'])
       
   649             info['quota messages'] = \
       
   650                 _format_quota_usage(info['ql_messages'],
       
   651                                     info['uq_messages'],
       
   652                                     domaindefault=info['ql_domaindefault'])
       
   653             _print_info(ctx, info, _(u'Account'))
       
   654         else:
       
   655             info[0]['quota storage'] = _format_quota_usage(info[0]['ql_bytes'],
       
   656                     info[0]['uq_bytes'], True, info[0]['ql_domaindefault'])
       
   657             info[0]['quota messages'] = \
       
   658                 _format_quota_usage(info[0]['ql_messages'],
       
   659                                     info[0]['uq_messages'],
       
   660                                     domaindefault=info[0]['ql_domaindefault'])
       
   661             _print_info(ctx, info[0], _(u'Account'))
       
   662             _print_list(info[1], _(u'alias addresses'))
       
   663 
       
   664 
       
   665 def user_name(ctx):
       
   666     """set or update the real name for an address"""
       
   667     if ctx.argc < 3:
       
   668         usage(EX_MISSING_ARGS, _(u"Missing e-mail address and user's name."),
       
   669               ctx.scmd)
       
   670     elif ctx.argc < 4:
       
   671         name = None
       
   672     else:
       
   673         name = ctx.args[3]
       
   674     ctx.hdlr.user_name(ctx.args[2].lower(), name)
       
   675 
       
   676 
       
   677 def user_password(ctx):
       
   678     """update the password for the given address"""
       
   679     if ctx.argc < 3:
       
   680         usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
       
   681     elif ctx.argc < 4:
       
   682         password = None
       
   683     else:
       
   684         password = ctx.args[3]
       
   685     ctx.hdlr.user_password(ctx.args[2].lower(), password)
       
   686 
       
   687 
       
   688 def user_note(ctx):
       
   689     """update the note of the given address"""
       
   690     if ctx.argc < 3:
       
   691         usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'),
       
   692               ctx.scmd)
       
   693     elif ctx.argc < 4:
       
   694         note = None
       
   695     else:
       
   696         note = ' '.join(ctx.args[3:])
       
   697     ctx.hdlr.user_note(ctx.args[2].lower(), note)
       
   698 
       
   699 
       
   700 def user_quota(ctx):
       
   701     """update the quota limit for the given address"""
       
   702     if ctx.argc < 3:
       
   703         usage(EX_MISSING_ARGS, _(u'Missing e-mail address and storage value.'),
       
   704               ctx.scmd)
       
   705     elif ctx.argc < 4:
       
   706         usage(EX_MISSING_ARGS, _(u'Missing storage value.'), ctx.scmd)
       
   707     if ctx.args[3] != 'domain':
       
   708         try:
       
   709             bytes_ = size_in_bytes(ctx.args[3])
       
   710         except (ValueError, TypeError):
       
   711             usage(INVALID_ARGUMENT, _(u"Invalid storage value: '%s'") %
       
   712                   ctx.args[3], ctx.scmd)
       
   713     else:
       
   714         bytes_ = ctx.args[3]
       
   715     if ctx.argc < 5:
       
   716         messages = 0
       
   717     else:
       
   718         try:
       
   719             messages = int(ctx.args[4])
       
   720         except ValueError:
       
   721             usage(INVALID_ARGUMENT,
       
   722                   _(u"Not a valid number of messages: '%s'") % ctx.args[4],
       
   723                   ctx.scmd)
       
   724     ctx.hdlr.user_quotalimit(ctx.args[2].lower(), bytes_, messages)
       
   725 
       
   726 
       
   727 def user_services(ctx):
       
   728     """allow all named service and block the uncredited."""
       
   729     if ctx.argc < 3:
       
   730         usage(EX_MISSING_ARGS, _(u'Missing e-mail address.'), ctx.scmd)
       
   731     services = []
       
   732     if ctx.argc >= 4:
       
   733         services.extend([service.lower() for service in ctx.args[3:]])
       
   734         unknown = [service for service in services if service not in SERVICES]
       
   735         if unknown and ctx.args[3] != 'domain':
       
   736             usage(INVALID_ARGUMENT, _(u'Invalid service arguments: %s') %
       
   737                   ' '.join(unknown), ctx.scmd)
       
   738     ctx.hdlr.user_services(ctx.args[2].lower(), *services)
       
   739 
       
   740 
       
   741 def user_transport(ctx):
       
   742     """update the transport of the given address"""
       
   743     if ctx.argc < 3:
       
   744         usage(EX_MISSING_ARGS, _(u'Missing e-mail address and transport.'),
       
   745               ctx.scmd)
       
   746     if ctx.argc < 4:
       
   747         usage(EX_MISSING_ARGS, _(u'Missing transport.'), ctx.scmd)
       
   748     ctx.hdlr.user_transport(ctx.args[2].lower(), ctx.args[3])
       
   749 
       
   750 
       
   751 def usage(errno, errmsg, subcommand=None):
       
   752     """print usage message for the given command or all commands.
       
   753     When errno > 0, sys,exit(errno) will interrupt the program.
       
   754     """
       
   755     if subcommand and subcommand in cmd_map:
       
   756         w_err(errno, _(u"Error: %s") % errmsg,
       
   757               _(u"usage: ") + cmd_map[subcommand].usage)
       
   758 
       
   759     # TP: Please adjust translated words like the original text.
       
   760     # (It's a table header.) Extract from usage text:
       
   761     # usage: vmm subcommand arguments
       
   762     #   short long
       
   763     #   subcommand                arguments
       
   764     #
       
   765     #   da    domainadd           fqdn [transport]
       
   766     #   dd    domaindelete        fqdn [force]
       
   767     u_head = _(u"""usage: %s subcommand arguments
       
   768   short long
       
   769   subcommand                arguments\n""") % prog
       
   770     order = cmd_map.keys()
       
   771     order.sort()
       
   772     w_err(0, u_head)
       
   773     for key in order:
       
   774         scmd = cmd_map[key]
       
   775         w_err(0, '  %-5s %-19s %s' % (scmd.alias, scmd.name, scmd.args))
       
   776     w_err(errno, '', _(u"Error: %s") % errmsg)
       
   777 
       
   778 
       
   779 def version(ctx_unused):
       
   780     """Write version and copyright information to stdout."""
       
   781     w_std('%s, %s %s (%s %s)\nPython %s %s %s\n\n%s\n%s %s' % (prog,
       
   782     # TP: The words 'from', 'version' and 'on' are used in
       
   783     # the version information, e.g.:
       
   784     # vmm, version 0.5.2 (from 09/09/09)
       
   785     # Python 2.5.4 on FreeBSD
       
   786         _(u'version'), __version__, _(u'from'),
       
   787         strftime(locale.nl_langinfo(locale.D_FMT),
       
   788             strptime(__date__, '%Y-%m-%d')).decode(ENCODING, 'replace'),
       
   789         os.sys.version.split()[0], _(u'on'), os.uname()[0],
       
   790         __copyright__, prog,
       
   791         _(u'is free software and comes with ABSOLUTELY NO WARRANTY.')))
       
   792 
       
   793 
       
   794 def update_cmd_map():
       
   795     """Update the cmd_map, after gettext's _ was installed."""
       
   796     cmd = Command
       
   797     cmd_map.update({
       
   798     # Account commands
       
   799     'getuser': cmd('getuser', 'gu', get_user, 'uid',
       
   800                    _(u'get the address of the user with the given UID')),
       
   801     'useradd': cmd('useradd', 'ua', user_add, 'address [password]',
       
   802                    _(u'create a new e-mail user with the given address')),
       
   803     'userdelete': cmd('userdelete', 'ud', user_delete, 'address [force]',
       
   804                       _(u'delete the specified user')),
       
   805     'userinfo': cmd('userinfo', 'ui', user_info, 'address [details]',
       
   806                     _(u'display information about the given address')),
       
   807     'username': cmd('username', 'un', user_name, 'address [name]',
       
   808                     _(u'set, update or delete the real name for an address')),
       
   809     'userpassword': cmd('userpassword', 'up', user_password,
       
   810                         'address [password]',
       
   811                         _(u'update the password for the given address')),
       
   812     'userquota': cmd('userquota', 'uq', user_quota,
       
   813                      'address storage [messages] | address domain',
       
   814                      _(u'update the quota limit for the given address')),
       
   815     'userservices': cmd('userservices', 'us', user_services,
       
   816                         'address [service ...] | address domain',
       
   817                         _(u'enables the specified services and disables all '
       
   818                           u'not specified services')),
       
   819     'usertransport': cmd('usertransport', 'ut', user_transport,
       
   820                          'address transport | address domain',
       
   821                          _(u'update the transport of the given address')),
       
   822     'usernote': cmd('usernote', 'uo', user_note, 'address [note]',
       
   823                     _(u'set, update or delete the note of the given address')),
       
   824     # Alias commands
       
   825     'aliasadd': cmd('aliasadd', 'aa', alias_add, 'address destination ...',
       
   826                     _(u'create a new alias e-mail address with one or more '
       
   827                       u'destinations')),
       
   828     'aliasdelete': cmd('aliasdelete', 'ad', alias_delete,
       
   829                        'address [destination ...]',
       
   830                        _(u'delete the specified alias e-mail address or one '
       
   831                          u'of its destinations')),
       
   832     'aliasinfo': cmd('aliasinfo', 'ai', alias_info, 'address',
       
   833                      _(u'show the destination(s) of the specified alias')),
       
   834     # AliasDomain commands
       
   835     'aliasdomainadd': cmd('aliasdomainadd', 'ada', aliasdomain_add,
       
   836                           'fqdn destination',
       
   837                           _(u'create a new alias for an existing domain')),
       
   838     'aliasdomaindelete': cmd('aliasdomaindelete', 'add', aliasdomain_delete,
       
   839                              'fqdn', _(u'delete the specified alias domain')),
       
   840     'aliasdomaininfo': cmd('aliasdomaininfo', 'adi', aliasdomain_info, 'fqdn',
       
   841                          _(u'show the destination of the given alias domain')),
       
   842     'aliasdomainswitch': cmd('aliasdomainswitch', 'ads', aliasdomain_switch,
       
   843                              'fqdn destination', _(u'assign the given alias '
       
   844                              'domain to an other domain')),
       
   845     # CatchallAlias commands
       
   846     'catchalladd': cmd('catchalladd', 'caa', catchall_add,
       
   847                        'fqdn destination ...',
       
   848                        _(u'add one or more catch-all destinations for a '
       
   849                          u'domain')),
       
   850     'catchalldelete': cmd('catchalldelete', 'cad', catchall_delete,
       
   851                        'fqdn [destination ...]',
       
   852                        _(u'delete the specified catch-all destination or all '
       
   853                          u'of a domain\'s destinations')),
       
   854     'catchallinfo': cmd('catchallinfo', 'cai', catchall_info, 'fqdn',
       
   855                         _(u'show the catch-all destination(s) of the '
       
   856                           u'specified domain')),
       
   857     # Domain commands
       
   858     'domainadd': cmd('domainadd', 'da', domain_add, 'fqdn [transport]',
       
   859                      _(u'create a new domain')),
       
   860     'domaindelete': cmd('domaindelete', 'dd', domain_delete, 'fqdn [force]',
       
   861                       _(u'delete the given domain and all its alias domains')),
       
   862     'domaininfo': cmd('domaininfo', 'di', domain_info, 'fqdn [details]',
       
   863                       _(u'display information about the given domain')),
       
   864     'domainquota': cmd('domainquota', 'dq', domain_quota,
       
   865                        'fqdn storage [messages] [force]',
       
   866                        _(u'update the quota limit of the specified domain')),
       
   867     'domainservices': cmd('domainservices', 'ds', domain_services,
       
   868                           'fqdn [service ...] [force]',
       
   869                           _(u'enables the specified services and disables all '
       
   870                             u'not specified services of the given domain')),
       
   871     'domaintransport': cmd('domaintransport', 'dt', domain_transport,
       
   872                            'fqdn transport [force]',
       
   873                            _(u'update the transport of the specified domain')),
       
   874     'domainnote': cmd('domainnote', 'do', domain_note, 'fqdn [note]',
       
   875                      _(u'set, update or delete the note of the given domain')),
       
   876     # List commands
       
   877     'listdomains': cmd('listdomains', 'ld', list_domains, '[pattern]',
       
   878                       _(u'list all domains or search for domains by pattern')),
       
   879     'listaddresses': cmd('listaddresses', 'll', list_addresses, '[pattern]',
       
   880                          _(u'list all addresses or search for addresses by '
       
   881                            u'pattern')),
       
   882     'listusers': cmd('listusers', 'lu', list_users, '[pattern]',
       
   883                      _(u'list all user accounts or search for accounts by '
       
   884                        u'pattern')),
       
   885     'listaliases': cmd('listaliases', 'la', list_aliases, '[pattern]',
       
   886                       _(u'list all aliases or search for aliases by pattern')),
       
   887     'listrelocated': cmd('listrelocated', 'lr', list_relocated, '[pattern]',
       
   888                          _(u'list all relocated users or search for relocated '
       
   889                            u'users by pattern')),
       
   890     # Relocated commands
       
   891     'relocatedadd': cmd('relocatedadd', 'ra', relocated_add,
       
   892                         'address newaddress',
       
   893                         _(u'create a new record for a relocated user')),
       
   894     'relocateddelete': cmd('relocateddelete', 'rd', relocated_delete,
       
   895                            'address',
       
   896                            _(u'delete the record of the relocated user')),
       
   897     'relocatedinfo': cmd('relocatedinfo', 'ri', relocated_info, 'address',
       
   898                          _(u'print information about a relocated user')),
       
   899     # cli commands
       
   900     'configget': cmd('configget', 'cg', config_get, 'option',
       
   901                      _('show the actual value of the configuration option')),
       
   902     'configset': cmd('configset', 'cs', config_set, 'option value',
       
   903                       _('set a new value for the configuration option')),
       
   904     'configure': cmd('configure', 'cf', configure, '[section]',
       
   905                      _(u'start interactive configuration mode')),
       
   906     'listpwschemes': cmd('listpwschemes', 'lp', list_pwschemes, '',
       
   907                          _(u'lists all usable password schemes and password '
       
   908                            u'encoding suffixes')),
       
   909     'help': cmd('help', 'h', help_, '[subcommand]',
       
   910                 _(u'show a help overview or help for the given subcommand')),
       
   911     'version': cmd('version', 'v', version, '',
       
   912                    _(u'show version and copyright information')),
       
   913     })
       
   914 
       
   915 
       
   916 def _get_order(ctx):
       
   917     """returns a tuple with (key, 1||0) tuples. Used by functions, which
       
   918     get a dict from the handler."""
       
   919     order = ()
       
   920     if ctx.scmd == 'domaininfo':
       
   921         order = ((u'domain name', 0), (u'gid', 1), (u'domain directory', 0),
       
   922                  (u'quota limit/user', 0), (u'active services', 0),
       
   923                  (u'transport', 0), (u'alias domains', 0), (u'accounts', 0),
       
   924                  (u'aliases', 0), (u'relocated', 0), (u'catch-all dests', 0))
       
   925     elif ctx.scmd == 'userinfo':
       
   926         if ctx.argc == 4 and ctx.args[3] != u'aliases' or \
       
   927            ctx.cget('account.disk_usage'):
       
   928             order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1),
       
   929                      (u'home', 0), (u'mail_location', 0),
       
   930                      (u'quota storage', 0), (u'quota messages', 0),
       
   931                      (u'disk usage', 0), (u'transport', 0), (u'smtp', 1),
       
   932                      (u'pop3', 1), (u'imap', 1), (u'sieve', 1))
       
   933         else:
       
   934             order = ((u'address', 0), (u'name', 0), (u'uid', 1), (u'gid', 1),
       
   935                      (u'home', 0), (u'mail_location', 0),
       
   936                      (u'quota storage', 0), (u'quota messages', 0),
       
   937                      (u'transport', 0), (u'smtp', 1), (u'pop3', 1),
       
   938                      (u'imap', 1), (u'sieve', 1))
       
   939     elif ctx.scmd == 'getuser':
       
   940         order = ((u'uid', 1), (u'gid', 1), (u'address', 0))
       
   941     return order
       
   942 
       
   943 
       
   944 def _format_quota_usage(limit, used, human=False, domaindefault=False):
       
   945     """Put quota's limit / usage / percentage in a formatted string."""
       
   946     if human:
       
   947         q_usage = {
       
   948             'used': human_size(used),
       
   949             'limit': human_size(limit),
       
   950         }
       
   951     else:
       
   952         q_usage = {
       
   953             'used': locale.format('%d', used, True).decode(ENCODING,
       
   954                                                            'replace'),
       
   955             'limit': locale.format('%d', limit, True).decode(ENCODING,
       
   956                                                              'replace'),
       
   957         }
       
   958     if limit:
       
   959         q_usage['percent'] = locale.format('%6.2f', 100. / limit * used, True)
       
   960     else:
       
   961         q_usage['percent'] = locale.format('%6.2f', 0, True)
       
   962     #  Py25: fmt = format_domain_default if domaindefault else lambda s: s
       
   963     if domaindefault:
       
   964         fmt = format_domain_default
       
   965     else:
       
   966         fmt = lambda s: s
       
   967     # TP: e.g.: [  0.00%] 21.09 KiB/1.00 GiB
       
   968     return fmt(_(u'[%(percent)s%%] %(used)s/%(limit)s') % q_usage)
       
   969 
       
   970 
       
   971 def _print_info(ctx, info, title):
       
   972     """Print info dicts."""
       
   973     # TP: used in e.g. 'Domain information' or 'Account information'
       
   974     msg = u'%s %s' % (title, _(u'information'))
       
   975     w_std(msg, u'-' * len(msg))
       
   976     for key, upper in _get_order(ctx):
       
   977         if upper:
       
   978             w_std(u'\t%s: %s' % (key.upper().ljust(17, u'.'), info[key]))
       
   979         else:
       
   980             w_std(u'\t%s: %s' % (key.title().ljust(17, u'.'), info[key]))
       
   981     print
       
   982     note = info.get('note')
       
   983     if note:
       
   984         _print_note(note + '\n')
       
   985 
       
   986 
       
   987 def _print_note(note):
       
   988     msg = _(u'Note')
       
   989     w_std(msg, u'-' * len(msg))
       
   990     old_ii = txt_wrpr.initial_indent
       
   991     old_si = txt_wrpr.subsequent_indent
       
   992     txt_wrpr.initial_indent = txt_wrpr.subsequent_indent = '\t'
       
   993     txt_wrpr.width -= 8
       
   994     for para in note.split('\n'):
       
   995         w_std(txt_wrpr.fill(para))
       
   996     txt_wrpr.width += 8
       
   997     txt_wrpr.subsequent_indent = old_si
       
   998     txt_wrpr.initial_indent = old_ii
       
   999 
       
  1000 
       
  1001 def _print_list(alist, title):
       
  1002     """Print a list."""
       
  1003     # TP: used in e.g. 'Existing alias addresses' or 'Existing accounts'
       
  1004     msg = u'%s %s' % (_(u'Existing'), title)
       
  1005     w_std(msg, u'-' * len(msg))
       
  1006     if alist:
       
  1007         if title != _(u'alias domains'):
       
  1008             w_std(*(u'\t%s' % item for item in alist))
       
  1009         else:
       
  1010             for domain in alist:
       
  1011                 if not domain.startswith('xn--'):
       
  1012                     w_std(u'\t%s' % domain)
       
  1013                 else:
       
  1014                     w_std(u'\t%s (%s)' % (domain, domain.decode('idna')))
       
  1015         print
       
  1016     else:
       
  1017         w_std(_(u'\tNone'), '')
       
  1018 
       
  1019 
       
  1020 def _print_aliase_info(alias, destinations):
       
  1021     """Print the alias address and all its destinations"""
       
  1022     title = _(u'Alias information')
       
  1023     w_std(title, u'-' * len(title))
       
  1024     w_std(_(u'\tMail for %s will be redirected to:') % alias)
       
  1025     w_std(*(u'\t     * %s' % dest for dest in destinations))
       
  1026     print
       
  1027 
       
  1028 
       
  1029 def _print_catchall_info(domain, destinations):
       
  1030     """Print the catchall destinations of a domain"""
       
  1031     title = _(u'Catch-all information')
       
  1032     w_std(title, u'-' * len(title))
       
  1033     w_std(_(u'\tMail to unknown local-parts in domain %s will be sent to:')
       
  1034           % domain)
       
  1035     w_std(*(u'\t     * %s' % dest for dest in destinations))
       
  1036     print
       
  1037 
       
  1038 
       
  1039 def _print_relocated_info(**kwargs):
       
  1040     """Print the old and new addresses of a relocated user."""
       
  1041     title = _(u'Relocated information')
       
  1042     w_std(title, u'-' * len(title))
       
  1043     w_std(_(u"\tUser '%(addr)s' has moved to '%(dest)s'") % kwargs, '')
       
  1044 
       
  1045 
       
  1046 def _format_domain(domain, main=True):
       
  1047     """format (prefix/convert) the domain name."""
       
  1048     if domain.startswith('xn--'):
       
  1049         domain = u'%s (%s)' % (domain, domain.decode('idna'))
       
  1050     if main:
       
  1051         return u'\t[+] %s' % domain
       
  1052     return u'\t[-]     %s' % domain
       
  1053 
       
  1054 
       
  1055 def _print_domain_list(dids, domains, matching):
       
  1056     """Print a list of (matching) domains/alias domains."""
       
  1057     if matching:
       
  1058         title = _(u'Matching domains')
       
  1059     else:
       
  1060         title = _(u'Existing domains')
       
  1061     w_std(title, '-' * len(title))
       
  1062     if domains:
       
  1063         for did in dids:
       
  1064             if domains[did][0] is not None:
       
  1065                 w_std(_format_domain(domains[did][0]))
       
  1066             if len(domains[did]) > 1:
       
  1067                 w_std(*(_format_domain(a, False) for a in domains[did][1:]))
       
  1068     else:
       
  1069         w_std(_('\tNone'))
       
  1070     print
       
  1071 
       
  1072 
       
  1073 def _print_address_list(which, dids, addresses, matching):
       
  1074     """Print a list of (matching) addresses."""
       
  1075     _trans = {
       
  1076         TYPE_ACCOUNT: _('user accounts'),
       
  1077         TYPE_ALIAS: _('aliases'),
       
  1078         TYPE_RELOCATED: _('relocated users'),
       
  1079         TYPE_ACCOUNT | TYPE_ALIAS: _('user accounts and aliases'),
       
  1080         TYPE_ACCOUNT | TYPE_RELOCATED: _('user accounts and relocated users'),
       
  1081         TYPE_ALIAS | TYPE_RELOCATED: _('aliases and relocated users'),
       
  1082         TYPE_ACCOUNT | TYPE_ALIAS | TYPE_RELOCATED: _('addresses'),
       
  1083     }
       
  1084     try:
       
  1085         if matching:
       
  1086             title = _(u'Matching %s') % _trans[which]
       
  1087         else:
       
  1088             title = _(u'Existing %s') % _trans[which]
       
  1089         w_std(title, '-' * len(title))
       
  1090     except KeyError:
       
  1091         raise VMMError(_("Invalid address type for list: '%s'") % which,
       
  1092                        INVALID_ARGUMENT)
       
  1093     if addresses:
       
  1094         if which & (which - 1) == 0:
       
  1095             # only one type is requested, so no type indicator
       
  1096             _trans = {TYPE_ACCOUNT: '', TYPE_ALIAS: '', TYPE_RELOCATED: ''}
       
  1097         else:
       
  1098             _trans = {
       
  1099             # TP: the letters 'u', 'a' and 'r' are abbreviations of user,
       
  1100             # alias and relocated user
       
  1101                 TYPE_ACCOUNT: _('u'),
       
  1102                 TYPE_ALIAS: _('a'),
       
  1103                 TYPE_RELOCATED: _('r'),
       
  1104             }
       
  1105         for did in dids:
       
  1106             for addr, atype, aliasdomain in addresses[did]:
       
  1107                 if aliasdomain:
       
  1108                     leader = '[%s-]' % _trans[atype]
       
  1109                 else:
       
  1110                     leader = '[%s+]' % _trans[atype]
       
  1111                 w_std('\t%s %s' % (leader, addr))
       
  1112     else:
       
  1113         w_std(_('\tNone'))
       
  1114     print
       
  1115 
       
  1116 
       
  1117 def _print_aliasdomain_info(info):
       
  1118     """Print alias domain information."""
       
  1119     title = _(u'Alias domain information')
       
  1120     for key in ('alias', 'domain'):
       
  1121         if info[key].startswith('xn--'):
       
  1122             info[key] = u'%s (%s)' % (info[key], info[key].decode('idna'))
       
  1123     w_std(title, '-' * len(title),
       
  1124           _('\tThe alias domain %(alias)s belongs to:\n\t    * %(domain)s') %
       
  1125           info, '')
       
  1126 
       
  1127 del _