xref: /petsc/config/BuildSystem/nargs.py (revision 030f984af8d8bb4c203755d35bded3c05b3d83ce)
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    elif isinstance(self.value, list):
125      return str(map(str, self.value))
126    return str(self.value)
127
128  def getKey(self):
129    '''Returns the key. SHOULD MAKE THIS A PROPERTY'''
130    return self.key
131
132  def setKey(self, key):
133    '''Set the key. SHOULD MAKE THIS A PROPERTY'''
134    self.key = key
135    return
136
137  def getValue(self):
138    '''Returns the value. SHOULD MAKE THIS A PROPERTY'''
139    if not self.isValueSet():
140      raise KeyError('Could not find value for key '+str(self.key))
141    return self.value
142
143  def checkKey(self):
144    if self.deprecated:
145      if isinstance(self.deprecated, str):
146        raise KeyError('Deprecated option '+self.key+' should be '+self.deprecated)
147      raise KeyError('Deprecated option '+self.key)
148    return
149
150  def setValue(self, value):
151    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
152    self.checkKey()
153    self.value = value
154    return
155
156class ArgBool(Arg):
157  '''Arguments that represent boolean values'''
158  def __init__(self, key, value = None, help = '', isTemporary = 0, deprecated = False):
159    Arg.__init__(self, key, value, help, isTemporary, deprecated)
160    return
161
162  def setValue(self, value):
163    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
164    self.checkKey()
165    try:
166      if   value == 'no':    value = 0
167      elif value == 'yes':   value = 1
168      elif value == 'true':  value = 1
169      elif value == 'false': value = 0
170      elif value == 'True':  value = 1
171      elif value == 'False': value = 0
172      else:                  value = int(value)
173    except:
174      raise TypeError('Invalid boolean value: '+str(value)+' for key '+str(self.key))
175    self.value = value
176    return
177
178class ArgFuzzyBool(Arg):
179  '''Arguments that represent boolean values of an extended set'''
180  def __init__(self, key, value = None, help = '', isTemporary = 0, deprecated = False):
181    Arg.__init__(self, key, value, help, isTemporary, deprecated)
182    return
183
184  def valueName(self, value):
185    if value == 0:
186      return 'no'
187    elif value == 1:
188      return 'yes'
189    elif value == 2:
190      return 'ifneeded'
191    return str(value)
192
193  def __str__(self):
194    if not self.isValueSet():
195      return 'Empty '+str(self.__class__)
196    elif isinstance(self.value, list):
197      return str(map(self.valueName, self.value))
198    return self.valueName(self.value)
199
200  def setValue(self, value):
201    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
202    self.checkKey()
203    try:
204      if   value == '0':        value = 0
205      elif value == '1':        value = 1
206      elif value == 'no':       value = 0
207      elif value == 'yes':      value = 1
208      elif value == 'false':    value = 0
209      elif value == 'true':     value = 1
210      elif value == 'maybe':    value = 2
211      elif value == 'ifneeded': value = 2
212      elif value == 'client':   value = 2
213      elif value == 'server':   value = 3
214      else:                     value = int(value)
215    except:
216      raise TypeError('Invalid fuzzy boolean value: '+str(value)+' for key '+str(self.key))
217    self.value = value
218    return
219
220class ArgInt(Arg):
221  '''Arguments that represent integer numbers'''
222  def __init__(self, key, value = None, help = '', min = -2147483647, max = 2147483648, isTemporary = 0, deprecated = False):
223    self.min = min
224    self.max = max
225    Arg.__init__(self, key, value, help, isTemporary, deprecated)
226    return
227
228  def setValue(self, value):
229    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
230    self.checkKey()
231    try:
232      value = int(value)
233    except:
234      raise TypeError('Invalid integer number: '+str(value)+' for key '+str(self.key))
235    if value < self.min or value >= self.max:
236      raise ValueError('Number out of range: '+str(value)+' not in ['+str(self.min)+','+str(self.max)+')'+' for key '+str(self.key))
237    self.value = value
238    return
239
240class ArgReal(Arg):
241  '''Arguments that represent floating point numbers'''
242  def __init__(self, key, value = None, help = '', min = -1.7976931348623157e308, max = 1.7976931348623157e308, isTemporary = 0, deprecated = False):
243    self.min = min
244    self.max = max
245    Arg.__init__(self, key, value, help, isTemporary, deprecated)
246    return
247
248  def setValue(self, value):
249    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
250    self.checkKey()
251    try:
252      value = float(value)
253    except:
254      raise TypeError('Invalid floating point number: '+str(value)+' for key '+str(self.key))
255    if value < self.min or value >= self.max:
256      raise ValueError('Number out of range: '+str(value)+' not in ['+str(self.min)+','+str(self.max)+')'+' for key '+str(self.key))
257    self.value = value
258    return
259
260class ArgDir(Arg):
261  '''Arguments that represent directories'''
262  def __init__(self, key, value = None, help = '', mustExist = 1, isTemporary = 0, deprecated = False):
263    self.mustExist = mustExist
264    Arg.__init__(self, key, value, help, isTemporary, deprecated)
265    return
266
267  def getValue(self):
268    '''Returns the value. SHOULD MAKE THIS A PROPERTY'''
269    if not self.isValueSet():
270      return Arg.getValue(self)
271    return self.value
272
273  def setValue(self, value):
274    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
275    import os
276    self.checkKey()
277    # Should check whether it is a well-formed path
278    if not isinstance(value, str):
279      raise TypeError('Invalid directory: '+str(value)+' for key '+str(self.key))
280    value = os.path.expanduser(value)
281    value = os.path.abspath(value)
282    if self.mustExist and value and not os.path.isdir(value):
283      raise ValueError('Nonexistent directory: '+str(value)+' for key '+str(self.key))
284    self.value = value
285    return
286
287class ArgDirList(Arg):
288  '''Arguments that represent directory lists'''
289  def __init__(self, key, value = None, help = '', mustExist = 1, isTemporary = 0, deprecated = False):
290    self.mustExist = mustExist
291    Arg.__init__(self, key, value, help, isTemporary, deprecated)
292    return
293
294  def getValue(self):
295    '''Returns the value. SHOULD MAKE THIS A PROPERTY'''
296    if not self.isValueSet():
297      return Arg.getValue(self)
298    return self.value
299
300  def setValue(self, value):
301    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
302    import os
303    self.checkKey()
304    if not isinstance(value, list):
305      value = value.split(':')
306    # Should check whether it is a well-formed path
307    nvalue = []
308    for dir in value:
309      if dir:
310        nvalue.append(os.path.expanduser(dir))
311    value = nvalue
312    for dir in value:
313      if self.mustExist and not os.path.isdir(dir):
314        raise ValueError('Invalid directory: '+str(dir)+' for key '+str(self.key))
315    self.value = value
316    return
317
318class ArgFile(Arg):
319  '''Arguments that represent a file'''
320  def __init__(self, key, value = None, help = '', mustExist = 1, isTemporary = 0, deprecated = False):
321    self.mustExist = mustExist
322    Arg.__init__(self, key, value, help, isTemporary, deprecated)
323    return
324
325  def getValue(self):
326    '''Returns the value. SHOULD MAKE THIS A PROPERTY'''
327    if not self.isValueSet():
328      return Arg.getValue(self)
329    return self.value
330
331  def setValue(self, value):
332    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
333    import os
334    self.checkKey()
335    # Should check whether it is a well-formed path
336    if not isinstance(value, str):
337      raise TypeError('Invalid file: '+str(value)+' for key '+str(self.key))
338    value = os.path.expanduser(value)
339    value = os.path.abspath(value)
340    if self.mustExist and value and not os.path.isfile(value):
341      raise ValueError('Nonexistent file: '+str(value)+' for key '+str(self.key))
342    self.value = value
343    return
344
345class ArgFileList(Arg):
346  '''Arguments that represent file lists'''
347  def __init__(self, key, value = None, help = '', mustExist = 1, isTemporary = 0, deprecated = False):
348    self.mustExist = mustExist
349    Arg.__init__(self, key, value, help, isTemporary, deprecated)
350    return
351
352  def getValue(self):
353    '''Returns the value. SHOULD MAKE THIS A PROPERTY'''
354    if not self.isValueSet():
355      return Arg.getValue(self)
356    return self.value
357
358  def setValue(self, value):
359    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
360    import os
361    self.checkKey()
362    if not isinstance(value, list):
363      value = value.split(':')
364    # Should check whether it is a well-formed path
365    nvalue = []
366    for file in value:
367      if file:
368        nvalue.append(os.path.expanduser(file))
369    value = nvalue
370    for file in value:
371      if self.mustExist and not os.path.isfile(file):
372        raise ValueError('Invalid file: '+str(file)+' for key '+str(self.key))
373    self.value = value
374    return
375
376class ArgLibrary(Arg):
377  '''Arguments that represent libraries'''
378  def __init__(self, key, value = None, help = '', mustExist = 1, isTemporary = 0, deprecated = False):
379    self.mustExist = mustExist
380    Arg.__init__(self, key, value, help, isTemporary, deprecated)
381    return
382
383  def getValue(self):
384    '''Returns the value. SHOULD MAKE THIS A PROPERTY'''
385    if not self.isValueSet():
386      return Arg.getValue(self)
387    return self.value
388
389  def setValue(self, value):
390    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
391    import os
392    self.checkKey()
393    # Should check whether it is a well-formed path and an archive or shared object
394    if self.mustExist:
395      if not isinstance(value, list):
396        value = value.split(' ')
397    self.value = value
398    return
399
400class ArgExecutable(Arg):
401  '''Arguments that represent executables'''
402  def __init__(self, key, value = None, help = '', mustExist = 1, isTemporary = 0, deprecated = False):
403    self.mustExist = mustExist
404    Arg.__init__(self, key, value, help, isTemporary, deprecated)
405    return
406
407  def getValue(self):
408    '''Returns the value. SHOULD MAKE THIS A PROPERTY'''
409    if not self.isValueSet():
410      return Arg.getValue(self)
411    return self.value
412
413  def checkExecutable(self, dir, name):
414    import os
415    prog = os.path.join(dir, name)
416    return os.path.isfile(prog) and os.access(prog, os.X_OK)
417
418  def setValue(self, value):
419    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
420    import os
421    self.checkKey()
422    # Should check whether it is a well-formed path
423    if self.mustExist:
424      index = value.find(' ')
425      if index >= 0:
426        options = value[index:]
427        value   = value[:index]
428      else:
429        options = ''
430      found = self.checkExecutable('', value)
431      if not found:
432        for dir in os.environ['PATH'].split(os.path.pathsep):
433          if self.checkExecutable(dir, value):
434            found = 1
435            break
436      if not found:
437        raise ValueError('Invalid executable: '+str(value)+' for key '+str(self.key))
438    self.value = value+options
439    return
440
441class ArgString(Arg):
442  '''Arguments that represent strings satisfying a given regular expression'''
443  def __init__(self, key, value = None, help = '', regExp = None, isTemporary = 0, deprecated = False):
444    self.regExp = regExp
445    if self.regExp:
446      import re
447      self.re = re.compile(self.regExp)
448    Arg.__init__(self, key, value, help, isTemporary, deprecated)
449    return
450
451  def setValue(self, value):
452    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
453    self.checkKey()
454    if self.regExp and not self.re.match(value):
455      raise ValueError('Invalid string '+str(value)+'. You must give a string satisfying "'+str(self.regExp)+'"'+' for key '+str(self.key))
456    self.value = value
457    return
458
459class ArgDownload(Arg):
460  '''Arguments that represent software downloads'''
461  def __init__(self, key, value = None, help = '', isTemporary = 0, deprecated = False):
462    Arg.__init__(self, key, value, help, isTemporary, deprecated)
463    return
464
465  def valueName(self, value):
466    if value == 0:
467      return 'no'
468    elif value == 1:
469      return 'yes'
470    return str(value)
471
472  def __str__(self):
473    if not self.isValueSet():
474      return 'Empty '+str(self.__class__)
475    elif isinstance(self.value, list):
476      return str(map(self.valueName, self.value))
477    return self.valueName(self.value)
478
479  def setValue(self, value):
480    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
481    import os
482    self.checkKey()
483    try:
484      if   value == '0':        value = 0
485      elif value == '1':        value = 1
486      elif value == 'no':       value = 0
487      elif value == 'yes':      value = 1
488      elif value == 'false':    value = 0
489      elif value == 'true':     value = 1
490      elif not isinstance(value, int):
491        value = str(value)
492    except:
493      raise TypeError('Invalid download value: '+str(value)+' for key '+str(self.key))
494    if isinstance(value, str):
495      try:
496        import urlparse as urlparse_local # novermin
497      except ImportError:
498        from urllib import parse as urlparse_local
499      if not urlparse_local.urlparse(value)[0] and not os.path.exists(value):
500        raise ValueError('Invalid download location: '+str(value)+' for key '+str(self.key))
501    self.value = value
502    return
503