1from __future__ import print_function 2try: 3 import readline 4except ImportError: pass 5 6def getInteractive(): 7 return isInteractive 8 9def setInteractive(interactive): 10 global isInteractive 11 isInteractive = interactive 12 return 13 14def checkInteractive(key): 15 if not isInteractive: 16 raise ValueError('Value not set for key '+str(key)) 17 return 18setInteractive(1) 19 20class Arg(object): 21 '''This is the base class for all objects contained in RDict. Access to the raw argument values is 22provided by getValue() and setValue(). These objects can be thought of as type objects for the 23values themselves. It is possible to set an Arg in the RDict which has not yet been assigned a value 24in order to declare the type of that option. 25 26Inputs which cannot be converted to the correct type will cause TypeError, those failing validation 27tests will cause ValueError. 28''' 29 def __init__(self, key, value = None, help = '', isTemporary = False, deprecated = False): 30 self.key = key 31 self.help = help 32 self.isTemporary = isTemporary 33 self.deprecated = False 34 if not value is None: 35 self.setValue(value) 36 self.deprecated = deprecated 37 return 38 39 def isValueSet(self): 40 '''Determines whether the value of this argument has been set''' 41 return hasattr(self, 'value') 42 43 def getTemporary(self): 44 '''Retrieve the flag indicating whether the item should be persistent''' 45 return self.isTemporary 46 47 def setTemporary(self, isTemporary): 48 '''Set the flag indicating whether the item should be persistent''' 49 self.isTemporary = isTemporary 50 return 51 52 def parseValue(arg): 53 '''Return the object represented by the value portion of a string argument''' 54 # Should I replace this with a lexer? 55 if arg: arg = arg.strip() 56 if arg and arg[0] == '[' and arg[-1] == ']': 57 if len(arg) > 2: value = arg[1:-1].split(',') 58 else: value = [] 59 elif arg and arg[0] == '{' and arg[-1] == '}': 60 value = {} 61 idx = 1 62 oldIdx = idx 63 while idx < len(arg)-1: 64 if arg[oldIdx] == ',': 65 oldIdx += 1 66 while not arg[idx] == ':': idx += 1 67 key = arg[oldIdx:idx] 68 idx += 1 69 oldIdx = idx 70 nesting = 0 71 while not (arg[idx] == ',' or arg[idx] == '}') or nesting: 72 if arg[idx] == '[': 73 nesting += 1 74 elif arg[idx] == ']': 75 nesting -= 1 76 idx += 1 77 value[key] = Arg.parseValue(arg[oldIdx:idx]) 78 oldIdx = idx 79 else: 80 value = arg 81 return value 82 parseValue = staticmethod(parseValue) 83 84 def parseArgument(arg, ignoreDouble = 0): 85 '''Split an argument into a (key, value) tuple, stripping off the leading dashes. Return (None, None) on failure.''' 86 start = 0 87 if arg and arg[0] == '-': 88 start = 1 89 if arg[1] == '-' and not ignoreDouble: 90 start = 2 91 if arg.find('=') >= 0: 92 (key, value) = arg[start:].split('=', 1) 93 else: 94 if start == 0: 95 (key, value) = (None, arg) 96 else: 97 (key, value) = (arg[start:], '1') 98 return (key, Arg.parseValue(value)) 99 100 parseArgument = staticmethod(parseArgument) 101 102 def findArgument(key, argList): 103 '''Locate an argument with the given key in argList, returning the value or None on failure 104 - This is generally used to process arguments which must take effect before canonical argument parsing''' 105 if not isinstance(argList, list): return None 106 # Reverse the list so that we preserve the semantics which state that the last 107 # argument with a given key takes effect 108 l = argList[:] 109 l.reverse() 110 for arg in l: 111 (k, value) = Arg.parseArgument(arg) 112 if k == key: 113 return value 114 return None 115 findArgument = staticmethod(findArgument) 116 117 def processAlternatePrefixes(argList): 118 '''Convert alternate prefixes to our normal form''' 119 for l in range(0, len(argList)): 120 name = argList[l] 121 if name.find('enable-') >= 0: 122 argList[l] = name.replace('enable-','with-') 123 if name.find('=') == -1: argList[l] = argList[l]+'=1' 124 if name.find('disable-') >= 0: 125 argList[l] = name.replace('disable-','with-') 126 if name.find('=') == -1: argList[l] = argList[l]+'=0' 127 elif name.endswith('=1'): argList[l].replace('=1','=0') 128 if name.find('without-') >= 0: 129 argList[l] = name.replace('without-','with-') 130 if name.find('=') == -1: argList[l] = argList[l]+'=0' 131 elif name.endswith('=1'): argList[l].replace('=1','=0') 132 return 133 processAlternatePrefixes = staticmethod(processAlternatePrefixes) 134 135 def __str__(self): 136 if not self.isValueSet(): 137 return 'Empty '+str(self.__class__) 138 elif isinstance(self.value, list): 139 return str(map(str, self.value)) 140 return str(self.value) 141 142 def getEntryPrompt(self): 143 return 'Please enter value for '+str(self.key)+': ' 144 145 def getKey(self): 146 '''Returns the key. SHOULD MAKE THIS A PROPERTY''' 147 return self.key 148 149 def setKey(self, key): 150 '''Set the key. SHOULD MAKE THIS A PROPERTY''' 151 self.key = key 152 return 153 154 def getValue(self): 155 '''Returns the value. SHOULD MAKE THIS A PROPERTY''' 156 if not self.isValueSet(): 157 checkInteractive(self.key) 158 if self.help: print(self.help) 159 while 1: 160 try: 161 self.setValue(Arg.parseValue(raw_input(self.getEntryPrompt()))) 162 break 163 except KeyboardInterrupt: 164 raise KeyError('Could not find value for key '+str(self.key)) 165 except TypeError as e: 166 print(str(e)) 167 return self.value 168 169 def checkKey(self): 170 if self.deprecated: 171 if isinstance(self.deprecated, str): 172 raise KeyError('Deprecated option '+self.key+' should be '+self.deprecated) 173 raise KeyError('Deprecated option '+self.key) 174 return 175 176 def setValue(self, value): 177 '''Set the value. SHOULD MAKE THIS A PROPERTY''' 178 self.checkKey() 179 self.value = value 180 return 181 182class ArgBool(Arg): 183 '''Arguments that represent boolean values''' 184 def __init__(self, key, value = None, help = '', isTemporary = 0, deprecated = False): 185 Arg.__init__(self, key, value, help, isTemporary, deprecated) 186 return 187 188 def getEntryPrompt(self): 189 return 'Please enter boolean value for '+str(self.key)+': ' 190 191 def setValue(self, value): 192 '''Set the value. SHOULD MAKE THIS A PROPERTY''' 193 self.checkKey() 194 try: 195 if value == 'no': value = 0 196 elif value == 'yes': value = 1 197 elif value == 'true': value = 1 198 elif value == 'false': value = 0 199 elif value == 'True': value = 1 200 elif value == 'False': value = 0 201 else: value = int(value) 202 except: 203 raise TypeError('Invalid boolean value: '+str(value)+' for key '+str(self.key)) 204 self.value = value 205 return 206 207class ArgFuzzyBool(Arg): 208 '''Arguments that represent boolean values of an extended set''' 209 def __init__(self, key, value = None, help = '', isTemporary = 0, deprecated = False): 210 Arg.__init__(self, key, value, help, isTemporary, deprecated) 211 return 212 213 def valueName(self, value): 214 if value == 0: 215 return 'no' 216 elif value == 1: 217 return 'yes' 218 elif value == 2: 219 return 'ifneeded' 220 return str(value) 221 222 def __str__(self): 223 if not self.isValueSet(): 224 return 'Empty '+str(self.__class__) 225 elif isinstance(self.value, list): 226 return str(map(self.valueName, self.value)) 227 return self.valueName(self.value) 228 229 def getEntryPrompt(self): 230 return 'Please enter fuzzy boolean value for '+str(self.key)+': ' 231 232 def setValue(self, value): 233 '''Set the value. SHOULD MAKE THIS A PROPERTY''' 234 self.checkKey() 235 try: 236 if value == '0': value = 0 237 elif value == '1': value = 1 238 elif value == 'no': value = 0 239 elif value == 'yes': value = 1 240 elif value == 'false': value = 0 241 elif value == 'true': value = 1 242 elif value == 'maybe': value = 2 243 elif value == 'ifneeded': value = 2 244 elif value == 'client': value = 2 245 elif value == 'server': value = 3 246 else: value = int(value) 247 except: 248 raise TypeError('Invalid fuzzy boolean value: '+str(value)+' for key '+str(self.key)) 249 self.value = value 250 return 251 252class ArgInt(Arg): 253 '''Arguments that represent integer numbers''' 254 def __init__(self, key, value = None, help = '', min = -2147483647, max = 2147483648, isTemporary = 0, deprecated = False): 255 self.min = min 256 self.max = max 257 Arg.__init__(self, key, value, help, isTemporary, deprecated) 258 return 259 260 def getEntryPrompt(self): 261 return 'Please enter integer value for '+str(self.key)+': ' 262 263 def setValue(self, value): 264 '''Set the value. SHOULD MAKE THIS A PROPERTY''' 265 self.checkKey() 266 try: 267 value = int(value) 268 except: 269 raise TypeError('Invalid integer number: '+str(value)+' for key '+str(self.key)) 270 if value < self.min or value >= self.max: 271 raise ValueError('Number out of range: '+str(value)+' not in ['+str(self.min)+','+str(self.max)+')'+' for key '+str(self.key)) 272 self.value = value 273 return 274 275class ArgReal(Arg): 276 '''Arguments that represent floating point numbers''' 277 def __init__(self, key, value = None, help = '', min = -1.7976931348623157e308, max = 1.7976931348623157e308, isTemporary = 0, deprecated = False): 278 self.min = min 279 self.max = max 280 Arg.__init__(self, key, value, help, isTemporary, deprecated) 281 return 282 283 def getEntryPrompt(self): 284 return 'Please enter floating point value for '+str(self.key)+': ' 285 286 def setValue(self, value): 287 '''Set the value. SHOULD MAKE THIS A PROPERTY''' 288 self.checkKey() 289 try: 290 value = float(value) 291 except: 292 raise TypeError('Invalid floating point number: '+str(value)+' for key '+str(self.key)) 293 if value < self.min or value >= self.max: 294 raise ValueError('Number out of range: '+str(value)+' not in ['+str(self.min)+','+str(self.max)+')'+' for key '+str(self.key)) 295 self.value = value 296 return 297 298class ArgDir(Arg): 299 '''Arguments that represent directories''' 300 def __init__(self, key, value = None, help = '', mustExist = 1, isTemporary = 0, deprecated = False): 301 self.mustExist = mustExist 302 Arg.__init__(self, key, value, help, isTemporary, deprecated) 303 return 304 305 def getEntryPrompt(self): 306 return 'Please enter directory for '+str(self.key)+': ' 307 308 def getValue(self): 309 '''Returns the value. SHOULD MAKE THIS A PROPERTY''' 310 if not self.isValueSet(): 311 checkInteractive(self.key) 312 return Arg.getValue(self) 313 return self.value 314 315 def setValue(self, value): 316 '''Set the value. SHOULD MAKE THIS A PROPERTY''' 317 import os 318 self.checkKey() 319 # Should check whether it is a well-formed path 320 if not isinstance(value, str): 321 raise TypeError('Invalid directory: '+str(value)+' for key '+str(self.key)) 322 value = os.path.expanduser(value) 323 value = os.path.abspath(value) 324 if self.mustExist and value and not os.path.isdir(value): 325 raise ValueError('Nonexistent directory: '+str(value)+' for key '+str(self.key)) 326 self.value = value 327 return 328 329class ArgDirList(Arg): 330 '''Arguments that represent directory lists''' 331 def __init__(self, key, value = None, help = '', mustExist = 1, isTemporary = 0, deprecated = False): 332 self.mustExist = mustExist 333 Arg.__init__(self, key, value, help, isTemporary, deprecated) 334 return 335 336 def getEntryPrompt(self): 337 return 'Please enter directory list for '+str(self.key)+': ' 338 339 def getValue(self): 340 '''Returns the value. SHOULD MAKE THIS A PROPERTY''' 341 if not self.isValueSet(): 342 checkInteractive(self.key) 343 return Arg.getValue(self) 344 return self.value 345 346 def setValue(self, value): 347 '''Set the value. SHOULD MAKE THIS A PROPERTY''' 348 import os 349 self.checkKey() 350 if not isinstance(value, list): 351 value = value.split(':') 352 # Should check whether it is a well-formed path 353 nvalue = [] 354 for dir in value: 355 if dir: 356 nvalue.append(os.path.expanduser(dir)) 357 value = nvalue 358 for dir in value: 359 if self.mustExist and not os.path.isdir(dir): 360 raise ValueError('Invalid directory: '+str(dir)+' for key '+str(self.key)) 361 self.value = value 362 return 363 364class ArgFile(Arg): 365 '''Arguments that represent a file''' 366 def __init__(self, key, value = None, help = '', mustExist = 1, isTemporary = 0, deprecated = False): 367 self.mustExist = mustExist 368 Arg.__init__(self, key, value, help, isTemporary, deprecated) 369 return 370 371 def getEntryPrompt(self): 372 return 'Please enter file path for '+str(self.key)+': ' 373 374 def getValue(self): 375 '''Returns the value. SHOULD MAKE THIS A PROPERTY''' 376 if not self.isValueSet(): 377 checkInteractive(self.key) 378 return Arg.getValue(self) 379 return self.value 380 381 def setValue(self, value): 382 '''Set the value. SHOULD MAKE THIS A PROPERTY''' 383 import os 384 self.checkKey() 385 # Should check whether it is a well-formed path 386 if not isinstance(value, str): 387 raise TypeError('Invalid file: '+str(value)+' for key '+str(self.key)) 388 value = os.path.expanduser(value) 389 value = os.path.abspath(value) 390 if self.mustExist and value and not os.path.isfile(value): 391 raise ValueError('Nonexistent file: '+str(value)+' for key '+str(self.key)) 392 self.value = value 393 return 394 395class ArgFileList(Arg): 396 '''Arguments that represent file lists''' 397 def __init__(self, key, value = None, help = '', mustExist = 1, isTemporary = 0, deprecated = False): 398 self.mustExist = mustExist 399 Arg.__init__(self, key, value, help, isTemporary, deprecated) 400 return 401 402 def getEntryPrompt(self): 403 return 'Please enter file list for '+str(self.key)+': ' 404 405 def getValue(self): 406 '''Returns the value. SHOULD MAKE THIS A PROPERTY''' 407 if not self.isValueSet(): 408 checkInteractive(self.key) 409 return Arg.getValue(self) 410 return self.value 411 412 def setValue(self, value): 413 '''Set the value. SHOULD MAKE THIS A PROPERTY''' 414 import os 415 self.checkKey() 416 if not isinstance(value, list): 417 value = value.split(':') 418 # Should check whether it is a well-formed path 419 nvalue = [] 420 for file in value: 421 if file: 422 nvalue.append(os.path.expanduser(file)) 423 value = nvalue 424 for file in value: 425 if self.mustExist and not os.path.isfile(file): 426 raise ValueError('Invalid file: '+str(file)+' for key '+str(self.key)) 427 self.value = value 428 return 429 430class ArgLibrary(Arg): 431 '''Arguments that represent libraries''' 432 def __init__(self, key, value = None, help = '', mustExist = 1, isTemporary = 0, deprecated = False): 433 self.mustExist = mustExist 434 Arg.__init__(self, key, value, help, isTemporary, deprecated) 435 return 436 437 def getEntryPrompt(self): 438 return 'Please enter library for '+str(self.key)+': ' 439 440 def getValue(self): 441 '''Returns the value. SHOULD MAKE THIS A PROPERTY''' 442 if not self.isValueSet(): 443 checkInteractive(self.key) 444 return Arg.getValue(self) 445 return self.value 446 447 def setValue(self, value): 448 '''Set the value. SHOULD MAKE THIS A PROPERTY''' 449 import os 450 self.checkKey() 451 # Should check whether it is a well-formed path and an archive or shared object 452 if self.mustExist: 453 if not isinstance(value, list): 454 value = value.split(' ') 455 self.value = value 456 return 457 458class ArgExecutable(Arg): 459 '''Arguments that represent executables''' 460 def __init__(self, key, value = None, help = '', mustExist = 1, isTemporary = 0, deprecated = False): 461 self.mustExist = mustExist 462 Arg.__init__(self, key, value, help, isTemporary, deprecated) 463 return 464 465 def getEntryPrompt(self): 466 return 'Please enter executable for '+str(self.key)+': ' 467 468 def getValue(self): 469 '''Returns the value. SHOULD MAKE THIS A PROPERTY''' 470 if not self.isValueSet(): 471 checkInteractive(self.key) 472 return Arg.getValue(self) 473 return self.value 474 475 def checkExecutable(self, dir, name): 476 import os 477 prog = os.path.join(dir, name) 478 return os.path.isfile(prog) and os.access(prog, os.X_OK) 479 480 def setValue(self, value): 481 '''Set the value. SHOULD MAKE THIS A PROPERTY''' 482 import os 483 self.checkKey() 484 # Should check whether it is a well-formed path 485 if self.mustExist: 486 index = value.find(' ') 487 if index >= 0: 488 options = value[index:] 489 value = value[:index] 490 else: 491 options = '' 492 found = self.checkExecutable('', value) 493 if not found: 494 for dir in os.environ['PATH'].split(os.path.pathsep): 495 if self.checkExecutable(dir, value): 496 found = 1 497 break 498 if not found: 499 raise ValueError('Invalid executable: '+str(value)+' for key '+str(self.key)) 500 self.value = value+options 501 return 502 503class ArgString(Arg): 504 '''Arguments that represent strings satisfying a given regular expression''' 505 def __init__(self, key, value = None, help = '', regExp = None, isTemporary = 0, deprecated = False): 506 self.regExp = regExp 507 if self.regExp: 508 import re 509 self.re = re.compile(self.regExp) 510 Arg.__init__(self, key, value, help, isTemporary, deprecated) 511 return 512 513 def setValue(self, value): 514 '''Set the value. SHOULD MAKE THIS A PROPERTY''' 515 self.checkKey() 516 if self.regExp and not self.re.match(value): 517 raise ValueError('Invalid string '+str(value)+'. You must give a string satisfying "'+str(self.regExp)+'"'+' for key '+str(self.key)) 518 self.value = value 519 return 520 521class ArgDownload(Arg): 522 '''Arguments that represent software downloads''' 523 def __init__(self, key, value = None, help = '', isTemporary = 0, deprecated = False): 524 Arg.__init__(self, key, value, help, isTemporary, deprecated) 525 return 526 527 def valueName(self, value): 528 if value == 0: 529 return 'no' 530 elif value == 1: 531 return 'yes' 532 return str(value) 533 534 def __str__(self): 535 if not self.isValueSet(): 536 return 'Empty '+str(self.__class__) 537 elif isinstance(self.value, list): 538 return str(map(self.valueName, self.value)) 539 return self.valueName(self.value) 540 541 def getEntryPrompt(self): 542 return 'Please enter download value for '+str(self.key)+': ' 543 544 def setValue(self, value): 545 '''Set the value. SHOULD MAKE THIS A PROPERTY''' 546 import os 547 self.checkKey() 548 try: 549 if value == '0': value = 0 550 elif value == '1': value = 1 551 elif value == 'no': value = 0 552 elif value == 'yes': value = 1 553 elif value == 'false': value = 0 554 elif value == 'true': value = 1 555 elif not isinstance(value, int): 556 value = str(value) 557 except: 558 raise TypeError('Invalid download value: '+str(value)+' for key '+str(self.key)) 559 if isinstance(value, str): 560 try: 561 import urlparse 562 except ImportError: 563 from urllib import parse as urlparse 564 if not urlparse.urlparse(value)[0]: # how do we check if the URL is invalid? 565 if os.path.isfile(value): 566 value = 'file://'+os.path.abspath(value) 567 elif os.path.isdir(value): 568 if os.path.isdir(os.path.join(value,'.git')): 569 value = 'git://'+os.path.abspath(value) 570 else: 571 value = 'dir://'+os.path.abspath(value) 572 else: 573 raise ValueError('Invalid download location: '+str(value)+' for key '+str(self.key)) 574 self.value = value 575 return 576