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