xref: /petsc/config/BuildSystem/nargs.py (revision c4762a1b19cd2af06abeed90e8f9d34fb975dd94)
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.split(':')
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 ArgFile(Arg):
365  '''Arguments that represent a file'''
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 file path 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
386    if not isinstance(value, str):
387      raise TypeError('Invalid file: '+str(value)+' for key '+str(self.key))
388    value = os.path.expanduser(value)
389    value = os.path.abspath(value)
390    if self.mustExist and value and not os.path.isfile(value):
391      raise ValueError('Nonexistent file: '+str(value)+' for key '+str(self.key))
392    self.value = value
393    return
394
395class ArgFileList(Arg):
396  '''Arguments that represent file lists'''
397  def __init__(self, key, value = None, help = '', mustExist = 1, isTemporary = 0, deprecated = False):
398    self.mustExist = mustExist
399    Arg.__init__(self, key, value, help, isTemporary, deprecated)
400    return
401
402  def getEntryPrompt(self):
403    return 'Please enter file list for '+str(self.key)+': '
404
405  def getValue(self):
406    '''Returns the value. SHOULD MAKE THIS A PROPERTY'''
407    if not self.isValueSet():
408      checkInteractive(self.key)
409      return Arg.getValue(self)
410    return self.value
411
412  def setValue(self, value):
413    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
414    import os
415    self.checkKey()
416    if not isinstance(value, list):
417      value = value.split(':')
418    # Should check whether it is a well-formed path
419    nvalue = []
420    for file in value:
421      if file:
422        nvalue.append(os.path.expanduser(file))
423    value = nvalue
424    for file in value:
425      if self.mustExist and not os.path.isfile(file):
426        raise ValueError('Invalid file: '+str(file)+' for key '+str(self.key))
427    self.value = value
428    return
429
430class ArgLibrary(Arg):
431  '''Arguments that represent libraries'''
432  def __init__(self, key, value = None, help = '', mustExist = 1, isTemporary = 0, deprecated = False):
433    self.mustExist = mustExist
434    Arg.__init__(self, key, value, help, isTemporary, deprecated)
435    return
436
437  def getEntryPrompt(self):
438    return 'Please enter library for '+str(self.key)+': '
439
440  def getValue(self):
441    '''Returns the value. SHOULD MAKE THIS A PROPERTY'''
442    if not self.isValueSet():
443      checkInteractive(self.key)
444      return Arg.getValue(self)
445    return self.value
446
447  def setValue(self, value):
448    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
449    import os
450    self.checkKey()
451    # Should check whether it is a well-formed path and an archive or shared object
452    if self.mustExist:
453      if not isinstance(value, list):
454        value = value.split(' ')
455    self.value = value
456    return
457
458class ArgExecutable(Arg):
459  '''Arguments that represent executables'''
460  def __init__(self, key, value = None, help = '', mustExist = 1, isTemporary = 0, deprecated = False):
461    self.mustExist = mustExist
462    Arg.__init__(self, key, value, help, isTemporary, deprecated)
463    return
464
465  def getEntryPrompt(self):
466    return 'Please enter executable for '+str(self.key)+': '
467
468  def getValue(self):
469    '''Returns the value. SHOULD MAKE THIS A PROPERTY'''
470    if not self.isValueSet():
471      checkInteractive(self.key)
472      return Arg.getValue(self)
473    return self.value
474
475  def checkExecutable(self, dir, name):
476    import os
477    prog = os.path.join(dir, name)
478    return os.path.isfile(prog) and os.access(prog, os.X_OK)
479
480  def setValue(self, value):
481    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
482    import os
483    self.checkKey()
484    # Should check whether it is a well-formed path
485    if self.mustExist:
486      index = value.find(' ')
487      if index >= 0:
488        options = value[index:]
489        value   = value[:index]
490      else:
491        options = ''
492      found = self.checkExecutable('', value)
493      if not found:
494        for dir in os.environ['PATH'].split(os.path.pathsep):
495          if self.checkExecutable(dir, value):
496            found = 1
497            break
498      if not found:
499        raise ValueError('Invalid executable: '+str(value)+' for key '+str(self.key))
500    self.value = value+options
501    return
502
503class ArgString(Arg):
504  '''Arguments that represent strings satisfying a given regular expression'''
505  def __init__(self, key, value = None, help = '', regExp = None, isTemporary = 0, deprecated = False):
506    self.regExp = regExp
507    if self.regExp:
508      import re
509      self.re = re.compile(self.regExp)
510    Arg.__init__(self, key, value, help, isTemporary, deprecated)
511    return
512
513  def setValue(self, value):
514    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
515    self.checkKey()
516    if self.regExp and not self.re.match(value):
517      raise ValueError('Invalid string '+str(value)+'. You must give a string satisfying "'+str(self.regExp)+'"'+' for key '+str(self.key))
518    self.value = value
519    return
520
521class ArgDownload(Arg):
522  '''Arguments that represent software downloads'''
523  def __init__(self, key, value = None, help = '', isTemporary = 0, deprecated = False):
524    Arg.__init__(self, key, value, help, isTemporary, deprecated)
525    return
526
527  def valueName(self, value):
528    if value == 0:
529      return 'no'
530    elif value == 1:
531      return 'yes'
532    return str(value)
533
534  def __str__(self):
535    if not self.isValueSet():
536      return 'Empty '+str(self.__class__)
537    elif isinstance(self.value, list):
538      return str(map(self.valueName, self.value))
539    return self.valueName(self.value)
540
541  def getEntryPrompt(self):
542    return 'Please enter download value for '+str(self.key)+': '
543
544  def setValue(self, value):
545    '''Set the value. SHOULD MAKE THIS A PROPERTY'''
546    import os
547    self.checkKey()
548    try:
549      if   value == '0':        value = 0
550      elif value == '1':        value = 1
551      elif value == 'no':       value = 0
552      elif value == 'yes':      value = 1
553      elif value == 'false':    value = 0
554      elif value == 'true':     value = 1
555      elif not isinstance(value, int):
556        value = str(value)
557    except:
558      raise TypeError('Invalid download value: '+str(value)+' for key '+str(self.key))
559    if isinstance(value, str):
560      try:
561        import urlparse
562      except ImportError:
563        from urllib import parse as urlparse
564      if not urlparse.urlparse(value)[0]: # how do we check if the URL is invalid?
565        if os.path.isfile(value):
566          value = 'file://'+os.path.abspath(value)
567        elif os.path.isdir(value):
568          if os.path.isdir(os.path.join(value,'.git')):
569            value = 'git://'+os.path.abspath(value)
570          else:
571            value = 'dir://'+os.path.abspath(value)
572        else:
573          raise ValueError('Invalid download location: '+str(value)+' for key '+str(self.key))
574    self.value = value
575    return
576