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