File indexing completed on 2024-04-21 05:44:50

0001 # -*- coding: UTF-8 -*-
0002 
0003 """
0004 Handle subcommands and their parameters.
0005 
0006 Subcommands are putting main command into different modes of operation.
0007 Main commands with subcommands are typical of package managers, version
0008 control systems, etc. This module provides a handler to conveniently load
0009 subcommands on demand, and parser to extract and route parameters to them
0010 from the command line.
0011 
0012 The command line interface consists of having subcommand a free parameter,
0013 and a special collector-option to collect parameters for the subcommand::
0014 
0015     $ cmd -a -b -c \  # command and any usual options
0016           subcmd \    # subcommand
0017           -s foo \    # subcommand parameter 'foo', without value (flag)
0018           -s bar:xyz  # subcommand parameter 'bar', with the value 'xyz'
0019 
0020 where C{-s} is the collector-option, repeated for as many subcommand
0021 parameters as needed. The collector-option can be freely positioned in
0022 the command line, before or after the subcommand name, and mixed with
0023 other options.
0024 
0025 The format of subcommand parameter is either C{param} for flag parameters, C{param:value} for parameters taking a value, or C{param:value1,value2,...}
0026 for parameters taking a list of values. Instead of, or in addition to using comma-separated string to represent the list, some parameters can be repeated
0027 on the command line, and all the values collected to make the list.
0028 
0029 Several subcommands may be given too, in which case a each subcommand
0030 parameter is routed to every subcommand which expects it. This means that
0031 all those subcommands should place the same semantics into the same-named
0032 parameter they are using.
0033 
0034 @note: For any of the methods in this module, the order of keyword parameters
0035 is not guaranteed. Always name them in calls.
0036 
0037 @author: Chusslove Illich (Часлав Илић) <caslav.ilic@gmx.net>
0038 @license: GPLv3
0039 """
0040 
0041 # NOTE: The original code for this module was taken from the Divergloss
0042 # glossary processor, and reduced and retouched for the needs in Pology.
0043 # Actually, the main reason for not using Divergloss' module directly
0044 # is to avoid dependency.
0045 
0046 import fnmatch
0047 import locale
0048 import os
0049 import re
0050 import sys
0051 
0052 from pology import PologyError, _, n_
0053 from pology.colors import cjoin, cinterp
0054 from pology.fsops import term_width
0055 from pology.report import format_item_list
0056 from pology.wrap import wrap_text
0057 from functools import reduce
0058 
0059 
0060 class ParamParser (object):
0061     """
0062     Parser for subcommand parameters.
0063     """
0064 
0065     def __init__ (self):
0066         """
0067         Constructor.
0068         """
0069 
0070         self._scviews = {}
0071 
0072 
0073     def add_subcmd (self, subcmd, desc=None):
0074         """
0075         Add a subcommand for which the parameters may be added afterwards.
0076 
0077         Use double-newline in the description for splitting into paragraphs.
0078         The description can also be set later, using C{set_desc} method of
0079         subcommand view.
0080 
0081         @param subcmd: subcommand name
0082         @type subcmd: string
0083         @param desc: description of the subcommand
0084         @type desc: string or C{None}
0085 
0086         @return: subcommand view
0087         @rtype: L{SubcmdView}
0088         """
0089 
0090         if subcmd in self._scviews:
0091             raise SubcmdError(
0092                 _("@info",
0093                   "Trying to add subcommand '%(cmd)s' more than once.",
0094                   cmd=subcmd))
0095 
0096         self._scviews[subcmd] = SubcmdView(self, subcmd, desc)
0097 
0098         return self._scviews[subcmd]
0099 
0100 
0101     def get_view (self, subcmd):
0102         """
0103         The view into previously defined subcommand.
0104 
0105         @param subcmd: subcommand name
0106         @type subcmd: string
0107 
0108         @return: subcommand view
0109         @rtype: L{SubcmdView}
0110         """
0111 
0112         scview = self._scviews.get(subcmd, None)
0113         if scview is None:
0114             raise SubcmdError(
0115                 _("@info",
0116                   "Trying to get a view for an unknown subcommand '%(cmd)s'.",
0117                   cmd=subcmd))
0118         return scview
0119 
0120 
0121     def help (self, subcmds=None, wcol=None, stream=sys.stdout):
0122         """
0123         Formatted help for subcommands.
0124 
0125         @param subcmds: subcommand names (all subcommands if C{None})
0126         @type subcmds: list of strings
0127         @param wcol: column to wrap text at (<= 0 for no wrapping,
0128             C{None} for automatic according to output stream)
0129         @type wcol: int
0130         @param stream: intended output stream for the text
0131         @type stream: file
0132 
0133         @return: formatted help
0134         @rtype: string
0135         """
0136 
0137         if subcmds is None:
0138             subcmds = list(self._scviews.keys())
0139             subcmds.sort()
0140 
0141         fmts = []
0142         for subcmd in subcmds:
0143             scview = self._scviews.get(subcmd, None)
0144             if scview is None:
0145                 raise SubcmdError(
0146                     _("@info",
0147                       "Trying to get help for an unknown subcommand '%(cmd)s'.",
0148                       cmd=subcmd))
0149             fmts.append(scview.help(wcol, stream))
0150             fmts.append("")
0151 
0152         return cjoin(fmts, "\n")
0153 
0154 
0155     def listcmd (self, subcmds=None, wcol=None, stream=sys.stdout):
0156         """
0157         Formatted listing of subcommands with short descriptions.
0158 
0159         @param subcmds: subcommand names (all subcommands if C{None})
0160         @type subcmds: list of strings
0161         @param wcol: column to wrap text at (<= 0 for no wrapping,
0162             C{None} for automatic according to output stream)
0163         @type wcol: int
0164         @param stream: intended output stream for the text
0165         @type stream: file
0166 
0167         @return: formatted listing
0168         @rtype: string
0169         """
0170 
0171         if subcmds is None:
0172             subcmds = list(self._scviews.keys())
0173             subcmds.sort()
0174 
0175         maxsclen = max([len(x) for x in subcmds])
0176 
0177         ndsep = _("@item:intext splitter between a subcommand name "
0178                    "and its description",
0179                    " - ")
0180 
0181         flead = " " * 2
0182         lead = flead + " " * (maxsclen + 3)
0183         if wcol is None:
0184             wcol = (term_width(stream=stream) or 80) - 1
0185         fmts = []
0186         for subcmd in subcmds:
0187             scview = self._scviews.get(subcmd, None)
0188             if scview is None:
0189                 raise SubcmdError(
0190                     _("@info",
0191                       "Trying to include an unknown subcommand '%(cmd)s' "
0192                       "into listing.",
0193                       cmd=subcmd))
0194             desc = scview.shdesc()
0195             if desc:
0196                 name = cinterp("%%-%ds" % maxsclen, subcmd)
0197                 s = name + ndsep + desc
0198             else:
0199                 s = name
0200             lines = wrap_text(s, wcol=wcol, flead=flead, lead=lead, endl="")
0201             fmts.extend(lines)
0202 
0203         return cjoin(fmts, "\n")
0204 
0205 
0206     def cmdnames (self):
0207         """
0208         Get the list of all defined subcommands by name.
0209 
0210         @returns: list of subcommands
0211         @rtype: [string]
0212         """
0213 
0214         return sorted(self._scviews.keys())
0215 
0216 
0217     def cmdviews (self):
0218         """
0219         Get the list of all defined subcommand views.
0220 
0221         @returns: list of subcommand views
0222         @rtype: [L{SubcmdView}]
0223         """
0224 
0225         return [x[1] for x in sorted(self._scviews.items())]
0226 
0227 
0228     def parse (self, rawpars, subcmds):
0229         """
0230         Parse the list of parameters collected from the command line.
0231 
0232         If the command line had parameters specified as::
0233 
0234             -sfoo -sbar:xyz -sbaz:10
0235 
0236         then the function call should get the list::
0237 
0238             rawpars=['foo', 'bar:xyz', 'baz:10']
0239 
0240         Result of parsing will be a dictionary of objects by subcommand name,
0241         where each object has attributes named like subcommand parameters.
0242         If attribute name has not been explicitly defined for a parameter,
0243         its parameter name will be used; if not a valid identifier by itself,
0244         it will be normalized by replacing all troublesome characters with
0245         an underscore, collapsing contiguous underscore sequences to a single
0246         underscore, and prepending an 'x' if it does not start with a letter.
0247 
0248         If a parameter is parsed which is not accepted by any of the given
0249         subcommands, its name is added to list of non-accepted parameters,
0250         which is the second element of the return tuple.
0251 
0252         @param rawpars: raw parameters
0253         @type rawpars: list of strings
0254         @param subcmds: names of issued subcommands
0255         @type subcmds: list of strings
0256 
0257         @return: objects with parameters as attributes, and
0258             list of parameter names not accepted by any of subcommands
0259         @rtype: dict of objects by subcommand name and list of strings
0260         """
0261 
0262         # Assure only registered subcommands have been issued.
0263         for subcmd in subcmds:
0264             if subcmd not in self._scviews:
0265                 raise SubcmdError(
0266                     _("@info",
0267                       "Unregistered subcommand '%(cmd)s' issued.",
0268                       cmd=subcmd))
0269 
0270         # Parse all given parameters and collect their values.
0271         param_vals = dict([(x, {}) for x in subcmds])
0272         nacc_params = []
0273         for opstr in rawpars:
0274             lst = opstr.split(":", 1)
0275             lst += [None] * (2 - len(lst))
0276             param, strval = lst
0277 
0278             param_accepted = False
0279             for subcmd in subcmds:
0280                 scview = self._scviews[subcmd]
0281                 if param not in scview._ptypes:
0282                     # Current subcommand does not have this parameter, skip.
0283                     continue
0284 
0285                 if param in param_vals[subcmd] and not scview._multivals[param]:
0286                     raise SubcmdError(
0287                         _("@info",
0288                           "Parameter '%(par)s' repeated more than once.",
0289                           par=param))
0290 
0291                 ptype = scview._ptypes[param]
0292                 if ptype is bool and strval is not None:
0293                     raise SubcmdError(
0294                         _("@info",
0295                           "Parameter '%(par)s' is a flag, no value expected.",
0296                           par=param))
0297                 if ptype is not bool and strval is None:
0298                     raise SubcmdError(
0299                         _("@info",
0300                           "Value expected for parameter '%(par)s'.",
0301                           par=param))
0302 
0303                 val = scview._defvals[param]
0304                 if ptype is bool:
0305                     val = not val
0306 
0307                 val_lst = []
0308                 if strval is not None:
0309                     if not scview._seplists[param]:
0310                         try:
0311                             val = ptype(strval)
0312                         except:
0313                             raise SubcmdError(
0314                                 _("@info",
0315                                   "Cannot convert value '%(val)s' to "
0316                                   "parameter '%(par)s' into expected "
0317                                   "type '%(type)s'.",
0318                                   val=strval, par=param, type=ptype))
0319                         val_lst = [val]
0320                     else:
0321                         tmplst = strval.split(",")
0322                         try:
0323                             val = [ptype(x) for x in tmplst]
0324                         except:
0325                             raise SubcmdError(
0326                                 _("@info",
0327                                   "Cannot convert value '%(val)s' to "
0328                                   "parameter '%(par)s' into list of "
0329                                   "elements of expected type '%(type)s'.",
0330                                   val=strval, par=param, type=ptype))
0331                         val_lst = val
0332 
0333                 # Assure admissibility of parameter values.
0334                 admvals = scview._admvals[param]
0335                 if admvals is not None:
0336                     for val in val_lst:
0337                         if val not in admvals:
0338                             raise SubcmdError(
0339                                 _("@info",
0340                                   "Value '%(val)s' to parameter '%(par)s' "
0341                                   "not in the admissible set: %(vallist)s.",
0342                                   val=strval, par=param,
0343                                   vallist=format_item_list(admvals)))
0344 
0345                 param_accepted = True
0346                 if scview._multivals[param] or scview._seplists[param]:
0347                     if param not in param_vals[subcmd]:
0348                         param_vals[subcmd][param] = []
0349                     param_vals[subcmd][param].extend(val_lst)
0350                 else:
0351                     param_vals[subcmd][param] = val
0352 
0353             if not param_accepted and param not in nacc_params:
0354                 nacc_params.append(param)
0355 
0356         # Assure that all mandatory parameters have been supplied to each
0357         # issued subcommand, and set defaults for all optional parameters.
0358         for subcmd in subcmds:
0359             scview = self._scviews[subcmd]
0360 
0361             for param in scview._ptypes:
0362                 if param in param_vals[subcmd]:
0363                     # Option explicitly given, skip.
0364                     continue
0365 
0366                 if scview._mandatorys[param]:
0367                     raise SubcmdError(
0368                         _("@info",
0369                           "Mandatory parameter '%(par)s' to subcommand "
0370                           "'%(cmd)s' not issued.",
0371                           par=param, cmd=subcmd))
0372 
0373                 param_vals[subcmd][param] = scview._defvals[param]
0374 
0375         # Create dictionary of parameter objects.
0376         class ParamsTemp (object): pass
0377         params = {}
0378         for subcmd in subcmds:
0379             scview = self._scviews[subcmd]
0380             params[subcmd] = ParamsTemp()
0381             for param, val in param_vals[subcmd].items():
0382                 # Construct valid attribute name out of parameter name.
0383                 to_attr_rx = re.compile(r"[^a-z0-9]+", re.I|re.U)
0384                 attr = scview._attrnames[param]
0385                 if not attr:
0386                     attr = to_attr_rx.sub("_", param)
0387                     if not attr[:1].isalpha():
0388                         attr = "x" + attr
0389                 params[subcmd].__dict__[attr] = val
0390 
0391         return params, nacc_params
0392 
0393 
0394 class SubcmdView (object):
0395     """
0396     The view of a particular subcommand in a parameter parser.
0397     """
0398 
0399     def __init__ (self, parent, subcmd, desc=None, shdesc=None):
0400         """
0401         Constructor.
0402 
0403         @param parent: the parent parameter parser.
0404         @type parent: L{ParamParser}
0405         @param subcmd: subcommand name
0406         @type subcmd: string
0407         @param desc: subcommand description
0408         @type desc: string
0409         @param shdesc: short subcommand description
0410         @type shdesc: string
0411         """
0412 
0413         self._parent = parent
0414         self._subcmd = subcmd
0415         self._desc = desc
0416         self._shdesc = shdesc
0417 
0418         # Maps by parameter name.
0419         self._ptypes = {}
0420         self._mandatorys = {}
0421         self._defvals = {}
0422         self._admvals = {}
0423         self._multivals = {}
0424         self._seplists = {}
0425         self._metavars = {}
0426         self._descs = {}
0427         self._attrnames = {}
0428 
0429         # Parameter names in the order in which they were added.
0430         self._ordered = []
0431 
0432 
0433     def set_desc (self, desc):
0434         """
0435         Set description of the subcommand.
0436         """
0437 
0438         self._desc = desc
0439 
0440 
0441     def set_shdesc (self, shdesc):
0442         """
0443         Set short description of the subcommand.
0444         """
0445 
0446         self._shdesc = shdesc
0447 
0448 
0449     def add_param (self, name, ptype, mandatory=False, attrname=None,
0450                    defval=None, admvals=None, multival=False, seplist=False,
0451                    metavar=None, desc=None):
0452         """
0453         Define a parameter.
0454 
0455         A parameter is at minimum defined by its name and value type,
0456         and may be optional or mandatory. Optional parameter will be set
0457         to the supplied default value if not encountered during parsing.
0458 
0459         Default value must be of the given parameter type (in the sense of
0460         C{isinstance()}) or C{None}. Default value of C{None} can be used
0461         to be able to check if the parameter has been parsed at all.
0462         If parameter type is boolean, then the default value has a special
0463         meaning: the parameter is always parsed without an argument (a flag),
0464         and its value will become negation of the default value.
0465         If parameter value is not arbitrary for the given type, the set
0466         of admissible values can be defined too.
0467 
0468         Parameter can be used to collect a list of values, in two ways,
0469         or both combined. One is by repeating the parameter several times
0470         with different values, and another by a single parameter value itself
0471         being a comma-separated list of values (in which case the values are
0472         parsed into elements of requested type). For such parameters
0473         the default value should be a list too (or C{None}).
0474 
0475         For help purposes, parameter may be given a description and
0476         metavariable to represent its value.
0477 
0478         If the parameter being added to current subcommand has the name
0479         same as a previously defined parameter to another subcommand,
0480         then the current parameter shares semantics with the old one.
0481         This means that the type and list nature of current parameter must
0482         match that of the previous one (i.e. C{ptype}, C{multival}, and
0483         C{seplist} must have same values).
0484 
0485         Double-newline in description string splits text into paragraphs.
0486 
0487         @param name: parameter name
0488         @type name: string
0489         @param ptype: type of the expected argument
0490         @type ptype: type
0491         @param mandatory: whether parameter is mandatory
0492         @type mandatory: bool
0493         @param attrname: explicit name for the object attribute under which
0494             the parsed parameter value is stored (auto-derived if C{None})
0495         @type attrname: string
0496         @param defval: default value for the argument
0497         @type defval: instance of C{ptype} or C{None}
0498         @param admvals: admissible values for the argument
0499         @type admvals: list of C{ptype} elements or C{None}
0500         @param multival: whether parameter can be repeated for list of values
0501         @type multival: bool
0502         @param seplist: whether parameter is a comma-separated list of values
0503         @type seplist: bool
0504         @param metavar: name for parameter's value
0505         @type metavar: string or C{None}
0506         @param desc: description of the parameter
0507         @type desc: string or C{None}
0508         """
0509 
0510         param = name
0511         islist = multival or seplist
0512 
0513         if defval is not None and not islist and not isinstance(defval, ptype):
0514             raise SubcmdError(
0515                 _("@info",
0516                   "Trying to add parameter '%(par)s' to "
0517                   "subcommand '%(cmd)s' with default value '%(val)s' "
0518                   "different from its stated type '%(type)s'.",
0519                   par=param, cmd=self._subcmd, val=defval, type=ptype))
0520 
0521         if defval is not None and islist and not _isinstance_els(defval, ptype):
0522             raise SubcmdError(
0523                 _("@info",
0524                   "Trying to add parameter '%(par)s' to "
0525                   "subcommand '%(cmd)s' with default value '%(val)s' "
0526                   "which contains some elements different from their "
0527                   "stated type '%(type)s'.",
0528                   par=param, cmd=self._subcmd, val=defval, type=ptype))
0529 
0530         if defval is not None and admvals is not None and defval not in admvals:
0531             raise SubcmdError(
0532                 _("@info",
0533                   "Trying to add parameter '%(par)s' to "
0534                   "subcommand '%(cmd)s' with default value '%(val)s' "
0535                   "not from the admissible set: %(vallist)s.",
0536                   par=param, cmd=self._subcmd, val=defval,
0537                   vallist=format_item_list(admvals)))
0538 
0539         if param in self._ptypes:
0540             raise SubcmdError(
0541                 _("@info",
0542                   "Trying to add parameter '%(par)s' to subcommand "
0543                   "'%(cmd)s' more than once.",
0544                   par=param, cmd=self._subcmd))
0545 
0546         if islist and not isinstance(defval, (type(None), tuple, list)):
0547             raise SubcmdError(
0548                 _("@info",
0549                   "Parameter '%(par)s' to subcommand '%(cmd)s' "
0550                   "is stated to be list-valued, but the default value "
0551                   "is not given as a list or tuple.",
0552                   par=param, cmd=self._subcmd))
0553 
0554         general_ptype = None
0555         general_multival = None
0556         general_seplist = None
0557         for scview in self._parent._scviews.values():
0558             general_ptype = scview._ptypes.get(param)
0559             general_multival = scview._multivals.get(param)
0560             general_seplist = scview._seplists.get(param)
0561 
0562         if general_ptype is not None and ptype is not general_ptype:
0563             raise SubcmdError(
0564                 _("@info",
0565                   "Trying to add parameter '%(par)s' to "
0566                   "subcommand '%(cmd)s' with '%(field)s' field "
0567                   "different from the same parameter in other subcommands.",
0568                   par=param, cmd=self._subcmd, field="ptype"))
0569 
0570         if general_multival is not None and multival != general_multival:
0571             raise SubcmdError(
0572                 _("@info",
0573                   "Trying to add parameter '%(par)s' to "
0574                   "subcommand '%(cmd)s' with '%(field)s' field "
0575                   "different from the same parameter in other subcommands.",
0576                   par=param, cmd=self._subcmd, field="multival"))
0577 
0578         if general_seplist is not None and seplist != general_seplist:
0579             raise SubcmdError(
0580                 _("@info",
0581                   "Trying to add parameter '%(par)s' to "
0582                   "subcommand '%(cmd)s' with '%(field)s' field "
0583                   "different from the same parameter in other subcommands.",
0584                   par=param, cmd=self._subcmd, field="seplist"))
0585 
0586         self._ptypes[param] = ptype
0587         self._mandatorys[param] = mandatory
0588         self._defvals[param] = defval
0589         self._admvals[param] = admvals
0590         self._multivals[param] = multival
0591         self._seplists[param] = seplist
0592         self._metavars[param] = metavar
0593         self._descs[param] = desc
0594         self._attrnames[param] = attrname
0595 
0596         self._ordered.append(param)
0597 
0598 
0599     def help (self, wcol=None, stream=sys.stdout):
0600         """
0601         Formatted help for the subcommand.
0602 
0603         @param wcol: column to wrap text at (<= 0 for no wrapping,
0604             C{None} for automatic according to output stream)
0605         @type wcol: int
0606         @param stream: intended output stream for the text
0607         @type stream: file
0608 
0609         @return: formatted help
0610         @rtype: string
0611         """
0612 
0613         # Split parameters into mandatory and optional.
0614         m_params = []
0615         o_params = []
0616         for param in self._ordered:
0617             if self._mandatorys[param]:
0618                 m_params.append(param)
0619             else:
0620                 o_params.append(param)
0621 
0622         # Format output.
0623 
0624         if wcol is None:
0625             wcol = (term_width(stream=stream) or 80) - 1
0626 
0627         def fmt_wrap (text, indent=""):
0628             paras = text.split("\n\n")
0629             fmtparas = []
0630             for para in paras:
0631                 lines = wrap_text(para, wcol=wcol, flead=indent, lead=indent,
0632                                   endl="")
0633                 fmtparas.append(cjoin(lines, "\n"))
0634             return cjoin(fmtparas, "\n\n")
0635 
0636         def fmt_par (param, indent=""):
0637             s = ""
0638             s += indent + "  " + param
0639             ptype = self._ptypes[param]
0640             if ptype is bool:
0641                 s += " "*1 +_("@item:intext indicator that the parameter "
0642                               "is a flag",
0643                               "[flag]")
0644             else:
0645                 metavar = self._metavars[param]
0646                 if metavar is None:
0647                     metavar = _("@item:intext default placehodler for "
0648                                 "the parameter argument",
0649                                 "ARG")
0650                 s += cinterp(":%s", metavar)
0651             defval = self._defvals[param]
0652             admvals = self._admvals[param]
0653             if ptype is not bool and defval is not None and str(defval):
0654                 cpos = len(s) - s.rfind("\n") - 1
0655                 s += " "*1 + _("@item:intext default value for the argument",
0656                                "[default %(arg)s=%(val)s]",
0657                                arg=metavar, val=defval)
0658                 if admvals is not None:
0659                     s += "\n" + (" " * cpos)
0660             if ptype is not bool and admvals is not None:
0661                 s += " "*1 + _("@item:intext admissible argument values",
0662                                "[%(arg)s is one of: %(vallist)s]",
0663                                arg=metavar, vallist=format_item_list(admvals))
0664             s += "\n"
0665             desc = self._descs[param]
0666             if desc:
0667                 fmt_desc = fmt_wrap(desc, indent + "      ")
0668                 s += fmt_desc
0669                 ## Wrap current parameter with empty lines if
0670                 ## the description spanned several lines.
0671                 #if "\n\n" in fmt_desc:
0672                    #s = "\n" + s + "\n"
0673                 s += "\n" # empty line after description
0674             return s
0675 
0676         ls = []
0677         ls += ["  " + self._subcmd]
0678         ls += ["  " + "=" * len(ls[-1].strip())]
0679         ls += [""]
0680         desc = self._desc
0681         if not desc:
0682             desc = _("@info", "No description available.")
0683         ls += [fmt_wrap(desc, "    ")]
0684 
0685         if m_params:
0686             ls += [""]
0687             ls += ["  " + _("@info", "Mandatory parameters:")]
0688             ls += [""]
0689             for param in m_params:
0690                 ls += [fmt_par(param, "  ")]
0691 
0692         if o_params:
0693             ls += [""]
0694             ls += ["  " + _("@info", "Optional parameters:")]
0695             ls += [""]
0696             for param in o_params:
0697                 ls += [fmt_par(param, "  ")]
0698 
0699         return cjoin(ls, "\n").strip("\n")
0700 
0701 
0702     def name (self):
0703         """
0704         Get subcommand name.
0705 
0706         @returns: subcommand name
0707         @rtype: string
0708         """
0709 
0710         return self._subcmd
0711 
0712 
0713     def shdesc (self):
0714         """
0715         Get short description of the subcommand.
0716 
0717         Short description was either explicitly provided on construction,
0718         or it is taken as the first sentence of the first paragraph of
0719         the full description.
0720 
0721         @return: short description
0722         @rtype: string
0723         """
0724 
0725         if self._shdesc is not None:
0726             return self._shdesc
0727         else:
0728             p1 = self._desc.find("\n\n")
0729             if p1 < 0: p1 = len(self._desc)
0730             p2 = self._desc.find(". ")
0731             if p2 < 0: p2 = len(self._desc)
0732             shdesc = self._desc[:min(p1, p2)].strip()
0733             if shdesc.endswith("."):
0734                 shdesc = shdesc[:-1]
0735             return shdesc
0736 
0737 
0738     def params (self, addcol=False):
0739         """
0740         Get the list of subcommand parameters.
0741 
0742         @param addcol: append colon (C{:}) to non-flag parameters
0743         @type addcol: bool
0744 
0745         @returns: list of subcommand parameters
0746         @rtype: [string]
0747         """
0748 
0749         pnames = list(self._ptypes.keys())
0750         fmtnames = dict(list(zip(pnames, pnames)))
0751 
0752         if addcol:
0753             for pname in pnames:
0754                 if self._ptypes[pname] is not bool:
0755                     fmtnames[pname] += ":"
0756 
0757         return [x[1] for x in sorted(fmtnames.items())]
0758 
0759 
0760 # Check if all elements in a list are instances of given type
0761 def _isinstance_els (lst, typ):
0762 
0763     return reduce(lambda x, y: x and isinstance(y, typ), lst, True)
0764 
0765 
0766 class SubcmdError (PologyError):
0767     """
0768     Exception for errors on defining subcommands and parsing their parameters.
0769     """
0770 
0771     def __init__ (self, msg):
0772         """
0773         Constructor.
0774 
0775         All the parameters are made available as instance variables.
0776 
0777         @param msg: a description of what went wrong
0778         @type msg: string
0779         """
0780 
0781         self.msg = msg
0782 
0783         PologyError.__init__(self, msg)
0784