Package wsatools :: Module CLI
[hide private]

Source Code for Module wsatools.CLI

  1  #------------------------------------------------------------------------------ 
  2  #$Id: CLI.py 9505 2012-11-21 11:54:27Z RossCollins $ 
  3  """ 
  4     Command-line interface tool. All scripts that require a command-line 
  5     interface should use this tool, replacing the Python getopt module. 
  6   
  7     The purpose of the command-line interface (CLI) tool is to make the 
  8     command-line interface both consistent and powerful for every VDFS script, 
  9     enabling the operator a lot of control without having to hack the script. 
 10     Furthermore, it automatically provides a lot of error checking to make the 
 11     script as robust as possible, as well as a --help/-h option documenting the 
 12     complete command-line interface for the script. 
 13   
 14     Usage 
 15     ===== 
 16   
 17     Import as:: 
 18         from wsatools.CLI import CLI 
 19   
 20     Define your list of program arguments and program options in a global part 
 21     of your module that is processed on import - this makes the options globally 
 22     accessible, and any module that imports this module will then automatically 
 23     inherit these command line options. For command-line options associated with 
 24     a class it may be clearer to define them within the class definition 
 25     following the declaration of class member variables (see e.g. 
 26     L{invocations.cu19.cu19.Cu19} class definition). To define the list program 
 27     arguments append L{CLI.Argument} objects to the CLI.progArgs list, and to 
 28     define the list of program options append L{CLI.Option} to the CLI.progOpts 
 29     list:: 
 30   
 31         CLI.progArgs.append(CLI.Argument('surveyID', example='1', 
 32                                          isValOK=lambda x: x.isdigit())) 
 33         CLI.progOpts += [ 
 34             # Example of a boolean option flag - default value is always false 
 35             CLI.Option('a', 'append', 
 36                        'append tables to any pre-existing release database ' 
 37                        'of the same name'), 
 38             # Example of an option with an argument that has a default value 
 39             CLI.Option('b', 'begin', 
 40                        'first observation date to release e.g. 2004-04-01', 
 41                        argument='DATE', 
 42                        default=str(ObsCal.earlyDefaultDateTime.date), 
 43                        isValOK=CLI.isDateOK), 
 44             # Example of an option with an argument with no default value 
 45             CLI.Option('r', 'rerun', 
 46                        'just apply to re-transfered files in this log', 
 47                        argument='FILE', isValOK=CLI.assertFileExists)] 
 48   
 49     For full details on command-line argument and option definitions please 
 50     to the L{CLI.Argument} and L{CLI.Option} class documentation respectively. 
 51   
 52     To change the default value of an inherited command-line argument:: 
 53   
 54         CLI.progArgs['comment'] = "Updating catalogue data" 
 55   
 56     To delete an inherited command-line argument/option:: 
 57   
 58         CLI.progArgs.remove('programmeID') 
 59   
 60     Then to process the command-line arguments/options, start your script with 
 61     this as the first line:: 
 62   
 63         cli = CLI("Script's name", '$Revision: 9505 $', 
 64                   "This script does stuff to the given survey.") 
 65   
 66     This line will cause the script to exit straight away if the user has 
 67     requested help, and the help screen will start with this information. It is 
 68     important to use this script documentation to document the script's 
 69     mandatory arguments, as shown in this example. The documentation can be 
 70     taken from the first sentence of a docstring by referencing the docstring 
 71     with the built-in Python C{__doc__} variable, or the docstring of a class 
 72     e.g. C{Cu19.__doc__}. This line also performs value tests on command-line 
 73     options so that errors are caught early. 
 74   
 75     To then access the command-line options/arguments use L{CLI.getArg()}, 
 76     L{CLI.getOpt()}, remembering that these functions will always return strings 
 77     (unless it is a boolean option, in which case it will be C{True}/C{False}) - 
 78     this is because the command-line can only pass strings as it has no 
 79     knowledge of Python types (and this module does no automatic conversion):: 
 80   
 81         # Example of a mandatory program argument 
 82         surveyID = int(cli.getArg('surveyID')) 
 83   
 84         # Example of a boolean flag option 
 85         appendDb = cli.getOpt('append') 
 86   
 87         # Example of an option with an argument 
 88         beginDate = utils.makeDateTime(cli.getOpt('begin')) 
 89   
 90         # Example of an option with an argument without default value 
 91         if cli.getOpt('rerun'): 
 92             filePathName = cli.getOpt('rerun') 
 93             contents = file(filePathName).readlines() 
 94   
 95     I{B{Note:}} 
 96        - Choose to add a program argument in favour of adding an option that 
 97          takes an argument, when that argument is mandatory in all use-cases of 
 98          the script. 
 99        - If an argument is not supplied for a L{CLI.Option}, it is assumed to be 
100          boolean with the default value of False. In designing boolean option 
101          flags please consider this, wording the option so that it turns 
102          something on, i.e. makes a condition true. 
103        - Default argument values must always be strings. This will ensure that 
104          calls to L{CLI.getArg()} and L{CLI.getOpt()} will always return strings 
105          for argument values (as opposed to boolean options), making the 
106          resulting type of a default value consistent with that of a 
107          user-supplied value. This also allows for consistent comparisons 
108          between a user value and a default value, as conversions may cause 
109          differences. 
110        - If a default value has no meaning for a certain program option that 
111          takes an argument, then do not supply a default value. If this option 
112          is not supplied then the argument value will be C{None}, which can be 
113          easily tested against. 
114        - Use the argument keyword when initialising an option 
115          (L{CLI.Option.__init__()}) to supply a hint as to what kind of data 
116          type the Python program is expecting, e.g. 'NUMBER' or 'FILE' etc., and 
117          supply the isValOK keyword with a function that takes a string argument 
118          and checks that supplied argument value is of the correct type and 
119          possibly value (e.g. a range check, file existance check). This check 
120          will be applied immediately upon the parsing of the command-line and so 
121          helps to spot errors early. 
122   
123     @group Errors & Exceptions: MalformedValueError, MissingArgumentError 
124   
125     @todo: Update design/documentation of classes in this module. 
126     @todo: Phase out use of getOptDef / getArgDef. This will result in a clear 
127            separation between classes that accept Python objects as arguments, 
128            and CLI objects return strings that are converted into Python objects 
129            by scripts. Would be nice if each argument could have a convertor 
130            as well as an isValOK method to perform the conversion on get. 
131     @todo: Tidy up indentation of option descriptions in help screen. 
132     @todo: Bring in an argument Type class heirachy (see trac ticket 42) to 
133            allow this module to perform automatic type conversions to Python 
134            types. 
135     @todo: Make consistent with optparse. 
136     @todo: Johann wants multiple examples on a help screen - this can be done 
137            but may lead to the development API becoming even more confusing! 
138            An alternative is to just initialise a CLI object with an additional 
139            text string containing whatever notes Johann wishes to add - only 
140            problem is it won't automatically be updated when arguments change 
141            which is the nice feature of the CLI module's help screen right now. 
142            Perhaps this is a good reason to leave these notes on a TWiki page? 
143   
144     @author: R.S. Collins 
145     @org:    WFAU, IfA, University of Edinburgh 
146  """ 
147  #------------------------------------------------------------------------------ 
148  from __future__      import division, print_function 
149  from future_builtins import map, zip 
150   
151  import getopt 
152  import mx.DateTime  # for isDateOK() 
153  import readline     # to make raw_input() output to stdout as documented 
154  from   operator import methodcaller 
155  import os 
156  import sys 
157  import time 
158   
159  import wsatools.ExternalProcess     as extp 
160  from   wsatools.SystemConstants import SystemConstants 
161  import wsatools.Utilities           as utils 
162 #------------------------------------------------------------------------------ 163 164 -class MalformedValueError(Exception):
165 """ An exception due to an incorrectly typed argument value. 166 """
167 - def __init__(self, name, value, example=None):
168 """ 169 @param name: Name of the argument. 170 @type name: str 171 @param value: Supplied value. 172 @type value: str 173 @param example: Example correctly formatted value. 174 @type example: str 175 176 """ 177 msg = "'--%s' value is unrecognised" % name 178 if example: 179 msg += ". Should be like: '%s'. When you gave: '%s'" \ 180 % (example, value) 181 else: 182 msg += ": " + value 183 184 super(MalformedValueError, self).__init__(msg)
185
186 #------------------------------------------------------------------------------ 187 188 -class MissingArgumentError(Exception):
189 """ An exception due to an incorrect number of supplied arguments. 190 """
191 - def __init__(self, argList):
192 """ 193 @param argList: List of argument values supplied. 194 @type argList: list(str) 195 196 """ 197 super(MissingArgumentError, self).__init__( 198 ' '.join(argList) + " - incorrect number of arguments")
199
200 #------------------------------------------------------------------------------ 201 202 -class ArgGroup(dict):
203 """ 204 Container to store the specified set of arguments required by the program. 205 It has separate lists for mandatory and optional argument definitions, and 206 is a dictionary of argument example values referenced by name. Supplied 207 argument values from getopt are passed through to processArgs() to provide 208 a command-line argument look-up table for the program. 209 210 """
211 - def __init__(self, argList=[]):
212 """ 213 Initialise with the list of argument definitions for this program. 214 215 @param argList: List of argument definitions for this program. 216 @type argList: list(CLI.Argument) 217 218 """ 219 self._manArgList = [arg for arg in argList if not arg.isOptional] 220 self._optArgList = [arg for arg in argList if arg.isOptional] 221 super(ArgGroup, self).__init__((arg.name, arg.example) 222 for arg in self._optArgList)
223 224 #-------------------------------------------------------------------------- 225
226 - def __add__(self, argList):
227 """ 228 Create a new container by adding a new list of argument definitions to 229 this container. 230 231 """ 232 newArgGroup = ArgGroup() 233 newArgGroup._manArgList = self._manArgList[:] 234 newArgGroup._optArgList = self._optArgList[:] 235 newArgGroup += argList 236 return newArgGroup
237 238 #-------------------------------------------------------------------------- 239
240 - def __iadd__(self, argList):
241 """ 242 Increment the list of argument definitions with additional arguments. 243 244 """ 245 self._manArgList += [arg for arg in argList if not arg.isOptional] 246 self._optArgList += [arg for arg in argList if arg.isOptional] 247 self.update(dict((arg.name, arg.example) for arg in self._optArgList)) 248 return self
249 250 #-------------------------------------------------------------------------- 251
252 - def append(self, arg):
253 """ 254 Add a single argument. 255 256 @param arg: Argument to add to group of arguments. 257 @type arg: CLI.Argument 258 259 """ 260 if arg.isOptional: 261 self._optArgList.append(arg) 262 else: 263 self._manArgList.append(arg) 264 self.update(dict((arg.name, arg.example) for arg in self._optArgList))
265 266 #-------------------------------------------------------------------------- 267
268 - def getArgStr(self):
269 """ @return: The list of program arguments in order, mandatory first. 270 @rtype: str 271 """ 272 gap = ' ' 273 argStr = gap 274 if self._manArgList: 275 argStr += gap.join(map(str, self._manArgList)) + gap 276 argStr += gap.join('[%s]' % arg for arg in self._optArgList) 277 return argStr
278 279 #-------------------------------------------------------------------------- 280
281 - def getExampleStr(self):
282 """ @return: The example usage string for typical argument values. 283 @rtype: str 284 """ 285 examples = [] 286 for arg in self._manArgList + self._optArgList: 287 example = self.get(str(arg), arg.example) 288 examples.append(repr(example) 289 if type(example) is str and ' ' in example else str(example)) 290 return ' '.join(examples)
291 292 #-------------------------------------------------------------------------- 293
294 - def processArgs(self, argVals=[]):
295 """ 296 Processes the argument values supplied on the command-line, to return 297 a look-up table to obtain argument values from their names. 298 299 @param argVals: List of argument values (e.g. from getopt) 300 @type argVals: list(str) 301 302 @return: Loop-up table for command-line argument values. 303 @rtype: dict{str : str} 304 305 """ 306 # Check that number of supplied arguments is in range 307 if len(argVals) < len(self._manArgList) or \ 308 len(argVals) > len(self._manArgList) + len(self._optArgList): 309 raise MissingArgumentError(argVals) 310 311 # Number of optional arguments supplied on the command-line 312 numOptArgs = len(argVals) - len(self._manArgList) 313 314 # Check the format of every supplied argument 315 for arg, value in zip(self._manArgList + self._optArgList[:numOptArgs], 316 argVals): 317 if not arg.isValOK(value): 318 raise MalformedValueError(str(arg), value, arg.example) 319 320 # Fill unsupplied optional argument values with default values 321 if numOptArgs < len(self._optArgList): 322 argVals += [self.get(arg.name) 323 for arg in self._optArgList[numOptArgs - len(self._optArgList):]] 324 325 # Create a look-up table and return it 326 return dict(zip( 327 [arg.name for arg in self._manArgList + self._optArgList], argVals))
328 329 #-------------------------------------------------------------------------- 330
331 - def remove(self, name):
332 """ 333 Removes specified argument from the list of supported arguments. 334 335 @param name: Argument name. 336 @type name: str 337 338 """ 339 try: 340 self._manArgList.remove(name) 341 except ValueError: 342 try: 343 self._optArgList.remove(name) 344 self.pop(name) 345 except ValueError: 346 print("<Warning> Possible programming error: Cannot remove %s " 347 "from program's command-line argument list.\n" % name + 348 "Continuing...")
349
350 #------------------------------------------------------------------------------ 351 352 -class OptGroup(dict):
353 """ 354 Container to store the specified set of command-line options specified by 355 the program. It is a dictionary of CLI.Option objects (option definitions) 356 referenced by both their long and short (single character) names. Supplied 357 options from getopt are passed through to processArgs() to provide a 358 command-line option look-up table for the program. 359 360 """
361 - def __init__(self, optList=[]):
362 """ 363 Initialise with the list of option definitions for this program. 364 365 @param optList: List of option definitions for this program. 366 @type optList: list(CLI.Option) 367 368 """ 369 super(OptGroup, self).__init__(self.__prepOptList(optList))
370 371 #-------------------------------------------------------------------------- 372
373 - def __add__(self, optList):
374 """ 375 Create a new container by adding a new list of options definitions to 376 this container. 377 378 """ 379 newOptGroup = OptGroup().update(self) 380 newOptGroup += optList 381 return newOptGroup
382 383 #-------------------------------------------------------------------------- 384
385 - def __iadd__(self, optList):
386 """ 387 Increment the list of argument definitions with additional arguments. 388 389 """ 390 self.update(dict(self.__prepOptList(optList))) 391 return self
392 393 #-------------------------------------------------------------------------- 394
395 - def __prepOptList(self, optList):
396 """ Prepares the look-up table for both short and long name references. 397 """ 398 return [(opt.char, opt) for opt in optList] + \ 399 [(opt.longName, opt) for opt in optList]
400 401 #-------------------------------------------------------------------------- 402
403 - def append(self, option):
404 """ 405 Add a single option. 406 407 @param option: Option to add to group of options. 408 @type option: CLI.Option 409 410 """ 411 self.update(dict(self.__prepOptList([option])))
412 413 #-------------------------------------------------------------------------- 414
415 - def getHelpStr(self):
416 """ @return: The program option definitions for the --help mode. 417 @rtype: str 418 """ 419 return '\n'.join(option.getHelpLine() 420 for option in sorted(set(self.values())))
421 422 #-------------------------------------------------------------------------- 423
424 - def getLongNames(self):
425 """ @return: Option long names in the format required by getopt. 426 @rtype: list(str) 427 """ 428 # Options with arguments must be suffixed with the '=' character 429 return [key + '=' for key, option in self.items() 430 if option.argument and key == option.longName] + \ 431 [key for key, option in self.items() 432 if not option.argument and key == option.longName]
433 434 #-------------------------------------------------------------------------- 435
436 - def getShortOpts(self):
437 """ @return: Option short names in the format required by getopt. 438 @rtype: str 439 """ 440 # Options with arguments must be suffixed with the ':' character 441 optsWArgs = ':'.join(key for key, option in self.items() 442 if option.argument and key == option.char) 443 if optsWArgs: 444 # Handle options with blank strings as arguments 445 optsWArgs = optsWArgs.lstrip(':') + ':' 446 447 return ''.join(key for key, option in self.items() 448 if not option.argument and key == option.char) \ 449 + optsWArgs
450 451 #-------------------------------------------------------------------------- 452
453 - def processOpts(self, opts=[]):
454 """ 455 Processes the options supplied on the command-line, to return a look-up 456 table to obtain option values from their long names. 457 458 @param opts: List of options in format (name, value), where name maybe 459 short or long name. (e.g. from getopt) 460 @type opts: list(tuple(str, str)) 461 462 @return: Loop-up table for command-line option values. 463 @rtype: dict{str : str} 464 465 """ 466 # Prepare with default values for every option 467 procOpts = dict([(key, option.default) for key, option in self.items() 468 if key == option.longName]) 469 # Then add user supplied values 470 for supName, value in opts: 471 nameStr = supName.lstrip('-') 472 # Obtain long name for supplied short or long name 473 longName = str(self.get(nameStr, nameStr)) 474 # Options supplied without arguments are flags that should be True. 475 if not value: 476 procOpts[longName] = True 477 else: 478 procOpts[longName] = value.strip() 479 480 # Check that the supplied argument format is OK 481 if not self.get(longName).isValOK(value) \ 482 or self.get(longName).argument and not value: 483 raise MalformedValueError(longName, value, 484 self.get(longName).default) 485 return procOpts
486 487 #-------------------------------------------------------------------------- 488
489 - def remove(self, name):
490 """ 491 Removes specified option from the list of supported options. 492 493 @param name: Option long name. 494 @type name: str 495 496 """ 497 try: 498 option = self.pop(name) 499 except KeyError: 500 print("<Warning> Possible programming error: Cannot remove %s " 501 "from program's command-line option list.\n" % name + 502 "Continuing...") 503 else: 504 try: 505 self.pop(option.char) 506 except KeyError: 507 pass
508
509 #------------------------------------------------------------------------------ 510 511 -class CLI(object):
512 """ Command-line interface tool - manages command line arguments. """ 513
514 - class Argument(object):
515 """ A program argument definition. """ 516
517 - def __init__(self, name, example, isOptional=False, 518 isValOK=lambda _x: True):
519 """ 520 Define this argument. 521 522 @param name: Single word name of argument for look-up table, 523 example usage help string. 524 @type name: str 525 @param example: Example value for argument for help string, also 526 default value if argument is optional. 527 @type example: str 528 @param isOptional: If True, then this argument does not need to 529 be supplied and the example becomes the default. 530 @type isOptional: bool 531 @param isValOK: A function to test if the supplied value is 532 formatted correctly, of the form "bool f(x)". 533 @type isValOK: function 534 535 """ 536 self.name = name 537 self.example = example 538 self.isOptional = isOptional 539 self.isValOK = isValOK
540
541 - def __cmp__(self, other):
542 """ Compare on name (for removing etc.). """ 543 return cmp(str(self), str(other))
544
545 - def __str__(self):
546 """ @return: Argument's name. 547 @rtype: str 548 """ 549 return self.name
550 551 #-------------------------------------------------------------------------- 552
553 - class Option(object):
554 """ A program option definition. """ 555
556 - def __init__(self, char, longName, desc, argument=None, default=None, 557 isValOK=lambda _x: True):
558 """ 559 Define this option. 560 561 @param char: The single character short name for the option, 562 will be preceded by '-' as command-line option. 563 @type char: str 564 @param longName: The long name of the option, single word, will be 565 preceded by '--' as command-line option. 566 @type longName: str 567 @param desc: Full description of option for the --help mode. 568 @type desc: str 569 @param argument: If option takes an argument, supply a hint here 570 e.g. 'NAME', for the --help mode. 571 @type argument: str 572 @param default: The default value for the option's argument. 573 @type default: str 574 @param isValOK: A function to test if the supplied value is 575 formatted correctly, of the form "bool f(x)". 576 @type isValOK: function 577 578 """ 579 self.argument = argument 580 self.char = char 581 self.default = default or (None if argument else False) 582 self.desc = desc 583 self.isValOK = isValOK 584 self.longName = longName
585
586 - def __cmp__(self, other):
587 """ Compare on option short name (for sorting etc.). """ 588 return cmp(self.char, other.char)
589
590 - def __str__(self):
591 """ @return: Option's long name. 592 @rtype: str 593 """ 594 return self.longName
595
596 - def getHelpLine(self):
597 """ @return: Option details for the --help mode. 598 @rtype: str 599 """ 600 if self.argument: 601 argStr = '=' + self.argument 602 else: 603 argStr = '' 604 if self.default: 605 if type(self.default) is str and ' ' in self.default: 606 defaultStr = ' (default: %r)' % self.default 607 else: 608 defaultStr = ' (default: %s)' % self.default 609 else: 610 defaultStr = '' 611 keyStr = ' -' + self.char + ', --' + self.longName + argStr 612 gapStr = ' ' + ' ' * (27 - len(keyStr)) 613 utils.WordWrapper.indent = 28 614 return \ 615 keyStr + gapStr + utils.WordWrapper.wrap(self.desc + defaultStr)
616 617 #-------------------------------------------------------------------------- 618 619 #: Supported program arguments. 620 progArgs = ArgGroup() 621 #: Supported program options. 622 progOpts = OptGroup([Option('h', 'help', 'display this help information')]) 623 624 #-------------------------------------------------------------------------- 625
626 - def __init__(self, progName='', progVer='', progDesc='', checkSVN=True):
627 """ 628 Parses command-line arguments. 629 630 @param progName: Name of the programme or just a class type. 631 @type progName: str or class 632 @param progVer: Version of the programme. (e.g. $Revision: 9505 $) 633 @type progVer: str 634 @param progDesc: Description of what the programme does, not necessary 635 if a class type was supplied as progName. 636 @type progDesc: str 637 @param checkSVN: Check that SVN working copy is up-to-date. 638 @type checkSVN: bool 639 640 """ 641 if not isinstance(progName, str): 642 progDesc = progDesc or progName.__doc__ 643 progName = progName.__name__ 644 645 self._progCmd = os.path.split(sys.argv[0])[-1] 646 # Take just the first sentence of the documentation and trim whitespace 647 self._progDesc = ' '.join(progDesc.split('.', 1)[0].split()) 648 self._progName = progName or self._progCmd 649 self._progVer = progVer.replace('$Revision', 'Version').rstrip('$') 650 self._supArgs = None 651 self._supOpts = None 652 try: 653 opts, args = getopt.getopt(sys.argv[1:], 654 CLI.progOpts.getShortOpts(), 655 CLI.progOpts.getLongNames()) 656 657 self._supOpts = CLI.progOpts.processOpts(opts) 658 659 if checkSVN and not self._supOpts['help']: 660 self._updateProgVer() 661 662 if self._supOpts['help']: 663 print(self.help()) 664 raise SystemExit 665 666 self._supArgs = CLI.progArgs.processArgs(args) 667 668 except (getopt.GetoptError, MissingArgumentError, 669 MalformedValueError) as error: 670 msg = self._progCmd + ": %s\n\n" % error 671 msg += self.usage() + "\n\n" 672 msg += "Try `" + self._progCmd + " --help' for more information.\n" 673 raise SystemExit(msg)
674 675 #-------------------------------------------------------------------------- 676
677 - def _updateProgVer(self):
678 """ Updates self._progVer to latest SVN version if possible. 679 """ 680 isOutdated = False 681 modString = '' 682 try: 683 cmd = "svn status -u -q " + SystemConstants.srcPath 684 685 # The stdIn handles the case of server certificate changes! 686 for line in extp.run(cmd, stdIn='p', isVerbose=False): 687 688 # Check to see if working copy is modified and up-to-date 689 if line.startswith('M') and not line.endswith(".csv"): 690 modString = (" (Modified)" if self._progCmd in line else 691 modString or " (modified)") 692 693 if line and '*' in line.split(): 694 isOutdated = True 695 696 # Find latest SVN version of working copy 697 if line.startswith("Status"): 698 progVer = line.strip().split()[-1] 699 verPrefix = "Version: " 700 if int(progVer) > int(self._progVer.replace(verPrefix, '')): 701 self._progVer = verPrefix + progVer 702 703 except Exception as error: 704 print("<Warning> %s" % error) 705 706 # Give user the option to quit script first 707 if isOutdated: 708 modString = " (OUTDATED)" + modString 709 if os.getenv("ALLOW_OUTDATED") != os.getenv("WSA_SANDBOX"): 710 try: 711 print("Your working copy is outdated. Press Ctrl-C now " 712 "and update your working copy before proceeding, " 713 "otherwise script will continue in...") 714 715 step = 4 # seconds 716 for i in reversed(xrange(0, step * 5, step)): 717 print("%s seconds" % (i + step)) 718 time.sleep(step) 719 720 except KeyboardInterrupt: 721 exit() 722 723 self._progVer += modString
724 725 #-------------------------------------------------------------------------- 726 727 @staticmethod
728 - def assertFileExists(filePathName):
729 """ 730 A command-line test to ensure a file-exists. 731 732 @param filePathName: Full path to file. 733 @type filePathName: str 734 735 @return: True, if file exists, otherwise the program quits with an 736 error message. 737 @rtype: bool 738 739 """ 740 try: 741 assert os.path.exists(filePathName), \ 742 "File %s does not exist!" % filePathName 743 except AssertionError as error: 744 print(str(error)) 745 raise SystemExit 746 else: 747 return True
748 749 #-------------------------------------------------------------------------- 750 751 @staticmethod
752 - def check64bitServer():
753 """ Prompt operator to check that they are using the server with the 754 most free memory. 755 """ 756 import platform 757 if "32bit" in platform.architecture() and not CLI.isConfirmed( 758 "This script must be run on the 64-bit curation server with the " 759 "most available system memory"): 760 exit()
761 762 #-------------------------------------------------------------------------- 763
764 - def getArg(self, name):
765 """ 766 @param name: Argument name. 767 @type name: str 768 769 @return: The argument value supplied via the command line. 770 @rtype: str or bool 771 772 """ 773 return self._supArgs.get(name)
774 775 #-------------------------------------------------------------------------- 776 777 @staticmethod
778 - def getArgDef(name):
779 """ 780 @param name: Argument name. 781 @type name: str 782 783 @return: The example value for this argument. 784 @rtype: str or bool 785 786 """ 787 return CLI.progArgs.get(name)
788 789 #-------------------------------------------------------------------------- 790
791 - def getOpt(self, name):
792 """ 793 @param name: Option long name. 794 @type name: str 795 796 @return: The option value supplied via the command line. 797 @rtype: str or bool 798 799 """ 800 return self._supOpts.get(name)
801 802 #-------------------------------------------------------------------------- 803 804 @staticmethod
805 - def getOptDef(name):
806 """ 807 @param name: Option long name. 808 @type name: str 809 810 @return: The default value for this option. 811 @rtype: str or bool 812 813 """ 814 return CLI.progOpts.get(name, CLI.Option('', '', '')).default
815 816 #-------------------------------------------------------------------------- 817
818 - def getProgDetails(self, progName=''):
819 """ 820 Returns a string giving the command-line programme's name, version 821 and parameters. 822 823 @param progName: Optional name of sub-programme being invoked by 824 this programme. 825 @type progName: str 826 827 @return: Programme name, current version, and invoked parameters. 828 @rtype: str 829 830 """ 831 msg = self._progName + ' ' + self._progVer 832 833 if not self._supOpts['help']: 834 utils.WordWrapper.indent = 26 835 gap = utils.WordWrapper.indent * ' ' 836 args = [] 837 for arg in sys.argv[1:]: 838 if ' ' not in arg: 839 args.append(arg) 840 else: 841 args.append(repr(arg)) 842 msg += '\n' + gap + \ 843 utils.WordWrapper.wrap("Parameters: " + ' '.join(args)) 844 845 if progName: 846 msg = progName + " invoked from:\n" + gap + msg 847 848 return msg
849 850 #-------------------------------------------------------------------------- 851
852 - def help(self):
853 """ @return: A help string giving option definitions and command usage. 854 @rtype: str 855 """ 856 helpStr = self.getProgDetails() + '\n' 857 helpStr += utils.WordWrapper.wrap(self._progDesc) + '\n\n' 858 helpStr += self.usage() + '\n\n' 859 helpStr += 'EXAMPLE:\n' 860 helpStr += ' ' + self._progCmd + ' ' + \ 861 CLI.progArgs.getExampleStr() + '\n\n' 862 helpStr += 'OPTIONS:\n' 863 helpStr += CLI.progOpts.getHelpStr() + '\n' 864 return helpStr
865 866 #-------------------------------------------------------------------------- 867 868 @staticmethod
869 - def isConfirmed(message):
870 """ 871 Standard user command-line confirmation request. 872 873 @param message: Message to give to user explaining why confirmation is 874 required. 875 @type message: str 876 877 @return: True, if user gives confirmation. 878 @rtype: bool 879 880 """ 881 message = "Warning: " + message.strip() 882 if not message.endswith('.'): 883 message += '.' 884 885 # @@GOTCHA: Using readline to fix a raw_input bug of writing to stderr 886 readline.get_line_buffer() 887 answer = \ 888 raw_input(message + "\nAre you sure you wish to proceed? (Y/N) > ") 889 890 return answer.upper().startswith('Y')
891 892 #-------------------------------------------------------------------------- 893 894 @staticmethod
895 - def isDateOK(dateStr):
896 """ 897 Checks to see if the supplied date string is in the correct format, 898 either YYYY-MM-DD or YYYYMMDD or a semester date like 05A. 899 900 @param dateStr: Date string to check formatting. 901 @type dateStr: str 902 903 @return: True if the date string is correctly formatted. 904 @rtype: bool 905 906 """ 907 dateStr = dateStr.strip().strip('"').strip("'") 908 if '-' in dateStr: # YYYY-MM-DD format 909 dateParts = dateStr.split('-') 910 if len(dateParts) is not 3 or len(dateParts[0]) is not 4 or \ 911 len(dateParts[1]) is not 2 or len(dateParts[2]) is not 2: 912 return False 913 elif len(dateStr) is 8: # YYYYMMDD format 914 dateParts = (dateStr[:4], dateStr[4:6], dateStr[6:8]) 915 elif len(dateStr) < 8: # semester date e.g. 05A or 05A_SV 916 allSemesters = utils.unpackList(map(methodcaller("keys"), 917 SystemConstants.obsCal.getAll("semDates"))) 918 919 return dateStr in (sem[2:] if sem.startswith("20") else sem 920 for sem in allSemesters) 921 else: 922 return False 923 924 dateFormatOK = (all(part.isdigit() for part in dateParts) and 925 int(dateParts[0]) > 1900 and int(dateParts[0]) < 3000 926 and int(dateParts[1]) >= 1 and int(dateParts[1]) <= 12 927 and int(dateParts[2]) >= 1 and int(dateParts[2]) <= 31) 928 929 # Could still be out of range for a given month 930 if dateFormatOK: 931 try: 932 utils.makeDateTime(dateStr) 933 except mx.DateTime.RangeError as error: 934 print("<ERROR> %s" % error) 935 return False 936 return dateFormatOK
937 938 #-------------------------------------------------------------------------- 939
940 - def resetOptArg(self, argName, value):
941 """ 942 Resets an optional argument to the given value. 943 944 @param argName: Name of optional argument to reset. 945 @type argName: str 946 @param value: New value for the optional argument. 947 @type value: str 948 949 @return: The original command-line supplied value. 950 @rtype: str 951 952 """ 953 cliValue = self.getArg(argName) 954 self._supArgs[argName] = value 955 return cliValue
956 957 #-------------------------------------------------------------------------- 958
959 - def usage(self):
960 """ @return: A command usage definition string for this program. 961 @rtype: str 962 """ 963 return 'usage: ' + self._progCmd + ' [options]' + \ 964 CLI.progArgs.getArgStr()
965 966 #------------------------------------------------------------------------------ 967