xref: /petsc/config/BuildSystem/nargs.py (revision 5b6bfdb9644f185dbf5e5a09b808ec241507e1e7)
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