xref: /petsc/config/BuildSystem/nargs.py (revision 44b85a236d0c752951b0573ba76bfb3134d48c1e)
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      return Arg.getValue(self)
312    return self.value
313
314  def setValue(self, value):
315    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
316    import os
317    self.checkKey()
318    # Should check whether it is a well-formed path
319    if not isinstance(value, str):
320      raise TypeError('Invalid directory: '+str(value)+' for key '+str(self.key))
321    value = os.path.expanduser(value)
322    value = os.path.abspath(value)
323    if self.mustExist and value and not os.path.isdir(value):
324      raise ValueError('Nonexistent directory: '+str(value)+' for key '+str(self.key))
325    self.value = value
326    return
327
328class ArgDirList(Arg):
329  '''Arguments that represent directory lists'''
330  def __init__(self, key, value = None, help = '', mustExist = 1, isTemporary = 0, deprecated = False):
331    self.mustExist = mustExist
332    Arg.__init__(self, key, value, help, isTemporary, deprecated)
333    return
334
335  def getEntryPrompt(self):
336    return 'Please enter directory list for '+str(self.key)+': '
337
338  def getValue(self):
339    '''Returns the value. SHOULD MAKE THIS A PROPERTY'''
340    if not self.isValueSet():
341      checkInteractive(self.key)
342      return Arg.getValue(self)
343    return self.value
344
345  def setValue(self, value):
346    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
347    import os
348    self.checkKey()
349    if not isinstance(value, list):
350      value = [value]
351    # Should check whether it is a well-formed path
352    nvalue = []
353    for dir in value:
354      nvalue.append(os.path.expanduser(dir))
355    value = nvalue
356    for dir in value:
357      if self.mustExist and not os.path.isdir(dir):
358        raise ValueError('Invalid directory: '+str(dir)+' for key '+str(self.key))
359    self.value = value
360    return
361
362class ArgLibrary(Arg):
363  '''Arguments that represent libraries'''
364  def __init__(self, key, value = None, help = '', mustExist = 1, isTemporary = 0, deprecated = False):
365    self.mustExist = mustExist
366    Arg.__init__(self, key, value, help, isTemporary, deprecated)
367    return
368
369  def getEntryPrompt(self):
370    return 'Please enter library for '+str(self.key)+': '
371
372  def getValue(self):
373    '''Returns the value. SHOULD MAKE THIS A PROPERTY'''
374    if not self.isValueSet():
375      checkInteractive(self.key)
376      return Arg.getValue(self)
377    return self.value
378
379  def setValue(self, value):
380    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
381    import os
382    self.checkKey()
383    # Should check whether it is a well-formed path and an archive or shared object
384    if self.mustExist:
385      if not isinstance(value, list):
386        value = value.split(' ')
387    self.value = value
388    return
389
390class ArgExecutable(Arg):
391  '''Arguments that represent executables'''
392  def __init__(self, key, value = None, help = '', mustExist = 1, isTemporary = 0, deprecated = False):
393    self.mustExist = mustExist
394    Arg.__init__(self, key, value, help, isTemporary, deprecated)
395    return
396
397  def getEntryPrompt(self):
398    return 'Please enter executable for '+str(self.key)+': '
399
400  def getValue(self):
401    '''Returns the value. SHOULD MAKE THIS A PROPERTY'''
402    if not self.isValueSet():
403      checkInteractive(self.key)
404      return Arg.getValue(self)
405    return self.value
406
407  def checkExecutable(self, dir, name):
408    import os
409    prog = os.path.join(dir, name)
410    return os.path.isfile(prog) and os.access(prog, os.X_OK)
411
412  def setValue(self, value):
413    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
414    import os
415    self.checkKey()
416    # Should check whether it is a well-formed path
417    if self.mustExist:
418      index = value.find(' ')
419      if index >= 0:
420        options = value[index:]
421        value   = value[:index]
422      else:
423        options = ''
424      found = self.checkExecutable('', value)
425      if not found:
426        for dir in os.environ['PATH'].split(os.path.pathsep):
427          if self.checkExecutable(dir, value):
428            found = 1
429            break
430      if not found:
431        raise ValueError('Invalid executable: '+str(value)+' for key '+str(self.key))
432    self.value = value+options
433    return
434
435class ArgString(Arg):
436  '''Arguments that represent strings satisfying a given regular expression'''
437  def __init__(self, key, value = None, help = '', regExp = None, isTemporary = 0, deprecated = False):
438    self.regExp = regExp
439    if self.regExp:
440      import re
441      self.re = re.compile(self.regExp)
442    Arg.__init__(self, key, value, help, isTemporary, deprecated)
443    return
444
445  def setValue(self, value):
446    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
447    self.checkKey()
448    if self.regExp and not self.re.match(value):
449      raise ValueError('Invalid string '+str(value)+'. You must give a string satisfying "'+str(self.regExp)+'"'+' for key '+str(self.key))
450    self.value = value
451    return
452
453class ArgDownload(Arg):
454  '''Arguments that represent software downloads'''
455  def __init__(self, key, value = None, help = '', isTemporary = 0, deprecated = False):
456    Arg.__init__(self, key, value, help, isTemporary, deprecated)
457    return
458
459  def valueName(self, value):
460    if value == 0:
461      return 'no'
462    elif value == 1:
463      return 'yes'
464    return str(value)
465
466  def __str__(self):
467    if not self.isValueSet():
468      return 'Empty '+str(self.__class__)
469    elif isinstance(self.value, list):
470      return str(map(self.valueName, self.value))
471    return self.valueName(self.value)
472
473  def getEntryPrompt(self):
474    return 'Please enter download value for '+str(self.key)+': '
475
476  def setValue(self, value):
477    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
478    import os
479    self.checkKey()
480    try:
481      if   value == '0':        value = 0
482      elif value == '1':        value = 1
483      elif value == 'no':       value = 0
484      elif value == 'yes':      value = 1
485      elif value == 'false':    value = 0
486      elif value == 'true':     value = 1
487      elif not isinstance(value, int):
488        value = str(value)
489    except:
490      raise TypeError('Invalid download value: '+str(value)+' for key '+str(self.key))
491    if isinstance(value, str):
492      import urlparse
493      if not urlparse.urlparse(value)[0]: # how do we check if the URL is invalid?
494        if os.path.isfile(value):
495          value = 'file://'+os.path.abspath(value)
496        else:
497          raise ValueError('Invalid download location: '+str(value)+' for key '+str(self.key))
498    self.value = value
499    return
500