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