xref: /petsc/config/BuildSystem/help.py (revision 0619917b5a674bb687c64e7daba2ab22be99af31)
1'''This module is meant to provide support for information and help systems based upon RDict.'''
2from __future__ import print_function
3from __future__ import absolute_import
4import logger
5import sys
6
7class Info(logger.Logger):
8  '''This basic class provides information independent of RDict'''
9  def __init__(self, argDB = None):
10    '''Creates a dictionary "sections" whose keys are section names, and values are a tuple of (ordinal, nameList)'''
11    logger.Logger.__init__(self, None, argDB)
12    self.sections = {}
13    return
14
15  def getTitle(self):
16    return self._title
17
18  def setTitle(self, title):
19    self._title = str(title)
20  title = property(getTitle, setTitle, None, 'Title of the Information Menu')
21
22  def getDescription(self, section, name):
23    return self._desc[(section, name)]
24
25  def setDescription(self, section, name, desc):
26    if not hasattr(self, '_desc'):
27      self._desc = {}
28    self._desc[(section, name)] = desc
29    return
30
31  def addArgument(self, section, name, desc):
32    '''Add an argument with given name and string to an information section'''
33    if not section in self.sections:
34      self.sections[section] = (len(self.sections), [])
35    if name in self.sections[section][1]:
36      name += '@'+str(len([n for n in self.sections[section][1] if name == n.split('@')[0]])+1)
37    self.sections[section][1].append(name)
38    self.setDescription(section, name, desc)
39    return
40
41  def printBanner(self, f):
42    '''Print a banner for the information screen'''
43    title   = self.title
44    divider = '-' * logger.get_global_divider_length()
45    f.write('{}\n{}\n'.format(title, divider))
46    return
47
48  def getTextSizes(self):
49    '''Returns the maximum name and description lengths'''
50    nameLen = 1
51    descLen = 1
52    for section in self.sections:
53      nameLen = max([nameLen, max(map(lambda n: len(n.split('@')[0]), self.sections[section][1]))+1])
54      descLen = max([descLen, max(map(lambda name: len(self.getDescription(section, name)), self.sections[section][1]))+1])
55    return (nameLen, descLen)
56
57  def output(self, f = None):
58    '''Print a help screen with all the argument information.'''
59    if f is  None:
60      f = sys.stdout
61    self.printBanner(f)
62    (nameLen, descLen) = self.getTextSizes()
63    format = '  %-'+str(nameLen)+'s: %s\n'
64    items  = sorted(self.sections.items(), key=lambda a: a[1][0])
65    for section, names in items:
66      f.write(section+':\n')
67      for name in names[1]:
68        f.write(format % (name.split('@')[0], self.getDescription(section, name)))
69    return
70
71# I don't know how to not have this stupid global variable
72_outputDownloadDone = 0
73
74class Help(Info):
75  '''Help provides a simple help system for RDict'''
76  def __init__(self, argDB):
77    '''Creates a dictionary "sections" whose keys are section names, and values are a tuple of (ordinal, nameList). Also provide the RDict upon which this will be based.'''
78    Info.__init__(self, argDB)
79    self.title = 'Help'
80    return
81
82  def getDescription(self, section, name):
83    return self.argDB.getType(self.getArgName(name)).help
84
85  def setDescription(self, section, name, desc):
86    return
87
88  def getArgName(self, name):
89    '''Return the RDict key corresponding to a more verbose help name. Right now, this means discard everything after "=".'''
90    #return name.split('=')[0].strip('-')
91    argName = name.split('=')[0]
92    while argName[0] == '-': argName = argName[1:]
93    return argName
94
95  def addArgument(self, section, name, argType, ignoreDuplicates = 0):
96    '''Add an argument with given name and type to a help section. The type, which can also have an initializer and help string, will be put into RDict.'''
97##  super(Info, self).addArgument(section, name, None)
98    if section in self.sections:
99      if name in self.sections[section][1]:
100        if ignoreDuplicates:
101          return
102        raise RuntimeError('Duplicate configure option '+name+' in section '+section)
103    else:
104      self.sections[section] = (len(self.sections), [])
105    if not argType.deprecated:
106      self.sections[section][1].append(name)
107
108    self.argDB.setType(self.getArgName(name), argType, forceLocal = 1)
109    return
110
111  def addDownload(self,name,dlist):
112    if not hasattr(self.argDB,'dlist'):
113      self.argDB.dlist = {}
114    else:
115      self.argDB.dlist[name] = dlist
116
117  def output(self, f = None, sections = None):
118    '''Print a help screen with all the argument information.'''
119    def output_items(section_title, items):
120      f.write(
121        logger.build_multiline_message(
122          '***** {} *****'.format(section_title), '', divider_char='-'
123        ) + '\n'
124      )
125
126      for section, names in items:
127        if sections and not section.casefold() in sections:
128          continue
129
130        f.write(section + ':\n')
131        for name in names[1]:
132          arg_name = self.getArgName(name)
133          arg_type = self.argDB.getType(arg_name)
134          arg_help = arg_type.help
135          if arg_name in self.argDB:
136            f.write('  -{}\n       {}  current: {}\n'.format(name, arg_help, arg_type))
137          else:
138            f.write('  -{}\n       {}\n'.format(name, arg_help))
139      return
140
141    if f is None:
142      f = sys.stdout
143    if sections:
144      sections = {s.casefold() for s in sections}
145
146    packages = []
147    modules  = []
148    for item in self.sections.items():
149      # Packages all have -- for whatever reason -- an uppercase section name, so use this
150      # to distinguish them. This is a vile hack.
151      if item[0].isupper():
152        packages.append(item)
153      else:
154        modules.append(item)
155
156    self.printBanner(f)
157    # sort the primary modules by their ordering, this happens to be nice and logical
158    output_items('CORE OPTIONS', sorted(modules, key=lambda a: a[1][0]))
159    # self.printBanner() will automatically append a '----' so we don't have to print a
160    # divider above, but we do have to here
161    f.write('-' * logger.get_global_divider_length() + '\n')
162    # sort packages by name
163    output_items('PACKAGE OPTIONS', sorted(packages, key=lambda a: a[0]))
164    return
165
166
167  def outputDownload(self):
168    ''' Looks for downloaded packages in --with-packages-download-dir
169        For any it finds it updates the --download-xxx= argument to point to this local copy
170        If it does not find some needed packages then prints the packages that need to be downloaded and exits'''
171    import nargs
172    import os
173    global _outputDownloadDone
174    if _outputDownloadDone: return
175    _outputDownloadDone = 1
176    pkgdir = os.path.abspath(os.path.expanduser(nargs.Arg.findArgument('with-packages-download-dir', self.clArgs)))
177    missing = 0
178    for i in self.argDB.dlist.keys():
179      if not nargs.Arg.findArgument('download-'+i, self.clArgs) == None and not nargs.Arg.findArgument('download-'+i, self.clArgs) == '0':
180        dlist = self.argDB.dlist[i]
181        found = 0
182        for k in range(0,len(dlist)):
183          fd = os.path.join(pkgdir,(os.path.basename(dlist[k])))
184          if fd.endswith('.git'):
185            fd = fd[:-4]
186          if os.path.isdir(fd) or os.path.isfile(fd):
187            found = 1
188            break
189        if not found:
190          missing = 1
191    if missing:
192      print('Download the following packages to '+pkgdir+' \n')
193    for i in self.argDB.dlist.keys():
194      if not nargs.Arg.findArgument('download-'+i, self.clArgs) == None and not nargs.Arg.findArgument('download-'+i, self.clArgs) == '0':
195        dlist = self.argDB.dlist[i]
196        found = 0
197        for k in range(0,len(dlist)):
198          fd = os.path.join(pkgdir,(os.path.basename(dlist[k])))
199          if fd.endswith('.git'):
200            fd = fd[:-4]
201          if os.path.isdir(fd) or os.path.isfile(fd):
202            found = 1
203            for k in range(0,len(self.clArgs)):
204              if self.clArgs[k].startswith('--download-'+i):
205                self.clArgs[k] = 'download-'+i+'='+fd
206                self.argDB.insertArgs([self.clArgs[k]])
207            break
208        if not found:
209          print(i + ' ' + str(self.argDB.dlist[i]).replace("git://","git clone "))
210    if missing:
211      print('\nThen run the script again\n')
212      sys.exit(10)
213
214