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