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