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] 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 ArgLibrary(Arg): 365 '''Arguments that represent libraries''' 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 library 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 and an archive or shared object 386 if self.mustExist: 387 if not isinstance(value, list): 388 value = value.split(' ') 389 self.value = value 390 return 391 392class ArgExecutable(Arg): 393 '''Arguments that represent executables''' 394 def __init__(self, key, value = None, help = '', mustExist = 1, isTemporary = 0, deprecated = False): 395 self.mustExist = mustExist 396 Arg.__init__(self, key, value, help, isTemporary, deprecated) 397 return 398 399 def getEntryPrompt(self): 400 return 'Please enter executable for '+str(self.key)+': ' 401 402 def getValue(self): 403 '''Returns the value. SHOULD MAKE THIS A PROPERTY''' 404 if not self.isValueSet(): 405 checkInteractive(self.key) 406 return Arg.getValue(self) 407 return self.value 408 409 def checkExecutable(self, dir, name): 410 import os 411 prog = os.path.join(dir, name) 412 return os.path.isfile(prog) and os.access(prog, os.X_OK) 413 414 def setValue(self, value): 415 '''Set the value. SHOULD MAKE THIS A PROPERTY''' 416 import os 417 self.checkKey() 418 # Should check whether it is a well-formed path 419 if self.mustExist: 420 index = value.find(' ') 421 if index >= 0: 422 options = value[index:] 423 value = value[:index] 424 else: 425 options = '' 426 found = self.checkExecutable('', value) 427 if not found: 428 for dir in os.environ['PATH'].split(os.path.pathsep): 429 if self.checkExecutable(dir, value): 430 found = 1 431 break 432 if not found: 433 raise ValueError('Invalid executable: '+str(value)+' for key '+str(self.key)) 434 self.value = value+options 435 return 436 437class ArgString(Arg): 438 '''Arguments that represent strings satisfying a given regular expression''' 439 def __init__(self, key, value = None, help = '', regExp = None, isTemporary = 0, deprecated = False): 440 self.regExp = regExp 441 if self.regExp: 442 import re 443 self.re = re.compile(self.regExp) 444 Arg.__init__(self, key, value, help, isTemporary, deprecated) 445 return 446 447 def setValue(self, value): 448 '''Set the value. SHOULD MAKE THIS A PROPERTY''' 449 self.checkKey() 450 if self.regExp and not self.re.match(value): 451 raise ValueError('Invalid string '+str(value)+'. You must give a string satisfying "'+str(self.regExp)+'"'+' for key '+str(self.key)) 452 self.value = value 453 return 454 455class ArgDownload(Arg): 456 '''Arguments that represent software downloads''' 457 def __init__(self, key, value = None, help = '', isTemporary = 0, deprecated = False): 458 Arg.__init__(self, key, value, help, isTemporary, deprecated) 459 return 460 461 def valueName(self, value): 462 if value == 0: 463 return 'no' 464 elif value == 1: 465 return 'yes' 466 return str(value) 467 468 def __str__(self): 469 if not self.isValueSet(): 470 return 'Empty '+str(self.__class__) 471 elif isinstance(self.value, list): 472 return str(map(self.valueName, self.value)) 473 return self.valueName(self.value) 474 475 def getEntryPrompt(self): 476 return 'Please enter download value for '+str(self.key)+': ' 477 478 def setValue(self, value): 479 '''Set the value. SHOULD MAKE THIS A PROPERTY''' 480 import os 481 self.checkKey() 482 try: 483 if value == '0': value = 0 484 elif value == '1': value = 1 485 elif value == 'no': value = 0 486 elif value == 'yes': value = 1 487 elif value == 'false': value = 0 488 elif value == 'true': value = 1 489 elif not isinstance(value, int): 490 value = str(value) 491 except: 492 raise TypeError('Invalid download value: '+str(value)+' for key '+str(self.key)) 493 if isinstance(value, str): 494 import urlparse 495 if not urlparse.urlparse(value)[0]: # how do we check if the URL is invalid? 496 if os.path.isfile(value): 497 value = 'file://'+os.path.abspath(value) 498 elif os.path.isdir(value): 499 value = 'dir://'+os.path.abspath(value) 500 else: 501 raise ValueError('Invalid download location: '+str(value)+' for key '+str(self.key)) 502 self.value = value 503 return 504