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