Package PyFoam :: Package Basics :: Module FoamOptionParser
[hide private]
[frames] | no frames]

Source Code for Module PyFoam.Basics.FoamOptionParser

  1  #  ICE Revision: $Id$ 
  2  """Parse options for the PyFoam-Scripts""" 
  3   
  4  from optparse import OptionParser,TitledHelpFormatter 
  5  import textwrap 
  6   
  7  from PyFoam import versionString 
  8   
  9  from PyFoam.FoamInformation import changeFoamVersion 
 10  from PyFoam.FoamInformation import oldAppConvention as oldApp 
 11   
 12  from PyFoam.Error import error,warning 
 13  from PyFoam.ThirdParty.six import iteritems 
 14  from PyFoam.ThirdParty.six import string_types,integer_types 
 15   
 16  from os import path,environ 
 17  from copy import deepcopy 
 18   
19 -class FoamOptionParser(OptionParser):
20 """Wrapper to the usual OptionParser to honor the conventions of OpenFOAM-utilities 21 22 Options that are not used by the script are passed to the OpenFOAM-application""" 23
24 - def __init__(self, 25 args=None, 26 usage=None, 27 version=None, 28 description=None, 29 interspersed=False):
30 """ 31 @param usage: usage string. If missing a default is used 32 @param version: if missing the PyFoam-version is used 33 @param description: description of the utility 34 @param interspersed: needs to be false if options should be passed to an OpenFOAM-utility 35 @param args: Command line arguments. If unset sys.argv[1:] is used. 36 Can be a string: it will be splitted then unsing the spaces (very primitive), or a list of strings (prefered) 37 """ 38 if usage==None: 39 if oldApp(): 40 usage="%prog [options] <foamApplication> <caseDir> <caseName> [foamOptions]" 41 else: 42 usage="%prog [options] <foamApplication> [foamOptions]" 43 44 if version==None: 45 version="%prog "+versionString() 46 47 if args==None: 48 self.argLine=None 49 elif type(args)==str: 50 self.argLine=args.split() 51 else: 52 self.argLine=map(str,args) 53 54 OptionParser.__init__(self, 55 usage=usage, 56 # prog=self.__type__.__name__, 57 version=version, 58 description=description, 59 formatter=TitledHelpFormatter()) 60 61 if interspersed: 62 self.enable_interspersed_args() 63 else: 64 self.disable_interspersed_args() 65 66 self.options=None 67 self.args=None 68 69 self.__foamVersionChanged=False 70 self.__oldEnvironment=None
71
72 - def restoreEnvironment(self):
73 """Restore the environment to its old glory... if it was changed""" 74 if self.__foamVersionChanged: 75 # print "Restoring the environment" 76 environ.update(self.__oldEnvironment)
77
78 - def parse(self,nr=None,exactNr=True):
79 """ 80 parse the options 81 @param nr: minimum number of arguments that are to be passed to the application 82 3 is default for pre-1.5 versions of OpenFOAM 83 """ 84 (self.options,self.args)=self.parse_args(args=self.argLine) 85 86 if "foamVersion" in dir(self.options): 87 if self.options.foamVersion!=None: 88 if self.options.force32 and self.options.force64: 89 error("A version can't be 32 and 64 bit at the same time") 90 91 self.__foamVersionChanged=True 92 self.__oldEnvironment=deepcopy(environ) 93 94 changeFoamVersion(self.options.foamVersion, 95 force64=self.options.force64, 96 force32=self.options.force32, 97 compileOption=self.options.compileOption, 98 foamCompiler=self.options.foamCompiler, 99 wmCompiler=self.options.wmCompiler) 100 elif self.options.force32 or self.options.force64: 101 warning("Forcing version to be 32 or 64 bit, but no version chosen. Doing nothing") 102 elif self.options.compileOption: 103 warning("No OpenFOAM-version chosen. Can't set compile-option to",self.options.compileOption) 104 105 if nr==None: 106 if oldApp(): 107 nr=3 108 else: 109 nr=1 110 111 if len(self.args)<nr: 112 self.error("Too few arguments (%d needed, %d given)" %(nr,len(self.args))) 113 114 maxNr=nr 115 if not oldApp(): 116 if "-case" in self.args: 117 maxNr+=2 118 119 if exactNr and len(self.args)>maxNr: 120 self.error("Too many arguments (%d needed, %d given)" %(nr,len(self.args))) 121 122 tmp=self.args 123 self.args=[] 124 for a in tmp: 125 if a.find(" ")>=0 or a.find("(")>=0: 126 a="\""+a+"\"" 127 self.args.append(a)
128
129 - def getArgs(self):
130 """Return the arguments left after parsing""" 131 if self.args!=None: 132 return self.args 133 else: 134 return []
135
136 - def getApplication(self):
137 """Return the OpenFOAM-Application to be run""" 138 if self.args!=None: 139 return self.args[0] 140 else: 141 return None
142
143 - def getOptions(self):
144 """Return the options""" 145 if self.options==None: 146 self.error("options have not been parsed yet") 147 148 return self.options
149
150 - def casePath(self):
151 """Returns the path to the case (if applicable)""" 152 if oldApp(): 153 return path.join(self.getArgs()[1],self.getArgs()[2]) 154 else: 155 if "-case" in self.getArgs(): 156 return path.normpath(self.getArgs()[self.getArgs().index("-case")+1]) 157 else: 158 return path.abspath(path.curdir)
159
160 - def _buildKeyordArgumentList(self):
161 """Go through the lists of options and build a dictionary of keyword 162 arguments (in CamelCase)""" 163 kwArgs={} 164 for og in self.option_groups: 165 for o in og.option_list: 166 raw=o.get_opt_string().strip("-") 167 pos=raw.find("-") 168 if pos<0: 169 name=raw.lower() 170 raw="" 171 else: 172 name=raw[:pos].lower() 173 raw=raw[pos:].strip("-") 174 while len(raw)>0: 175 pos=raw.find("-") 176 if pos<0: 177 name+=raw.capitalize() 178 raw="" 179 else: 180 name+=raw[:pos].capitalize() 181 raw=raw[pos:].strip("-") 182 if not name[0].isalpha() and name[0]!="_": 183 error("Option",o.get_opt_string(),"reduces to",name, 184 "with invalid first character") 185 # Remove all characters that do not belong in a valid Python-name 186 name="".join([c for c in name if c=="_" or c.isalnum]) 187 if name in kwArgs: 188 error("Keyword arguement",name,"appears at least twice") 189 kwArgs[name]=o 190 191 return kwArgs
192
193 - def processKeywordArguments(self,kw):
194 kwArgs=self._buildKeyordArgumentList() 195 for k,v in iteritems(kw): 196 if k not in kwArgs: 197 raise TypeError("Unknown keyword argument",k,"in", 198 sorted(kwArgs.keys())) 199 o=kwArgs[k] 200 if o.action=="store_true": 201 if type(v)!=bool: 202 raise TypeError("Keyword argument",k,"needs a bool") 203 setattr(self.values,o.dest,v) 204 elif o.action=="store_false": 205 if type(v)!=bool: 206 raise TypeError("Keyword argument",k,"needs a bool") 207 setattr(self.values,o.dest,not v) 208 elif o.action=="store": 209 if o.type: 210 if o.type=="string": 211 if not isinstance(v,string_types) and v!=o.default and o.default!=None: 212 raise TypeError("Keyword argument",k,"must be string or",o.default,". Is a ",type(v)) 213 elif o.type in ("int","long"): 214 if not isinstance(v,integer_types): 215 raise TypeError("Keyword argument",k,"must be an integer. Is a ",type(v)) 216 elif o.type=="float": 217 if not isinstance(v,integer_types+(float,)): 218 raise TypeError("Keyword argument",k,"must be float. Is a ",type(v)) 219 elif o.type=="choice": 220 if v not in o.choices: 221 raise TypeError("Keyword argument",k,"must be one of",o.choices) 222 else: 223 raise RuntimeError("Type",o.type,"not implemented") 224 setattr(self.values,o.dest,v) 225 elif o.action=="append": 226 oldVal=getattr(self.values,o.dest) 227 if type(oldVal) not in (list,tuple): 228 if not type(v) in (list,tuple): 229 raise TypeError("Keyword argument",k,"must be a list or a tuple") 230 setattr(self.values,o.dest,v) 231 else: 232 if type(v) in (list,tuple): 233 setattr(self.values,o.dest,oldVal+v) 234 else: 235 oldVal.append(v) 236 elif o.action=="store_const": 237 setattr(self.values,o.dest,o.const) 238 elif o.action=="append_const": 239 getattr(self.values,o.dest).append(o.const) 240 elif o.action=="count": 241 oldVal=getattr(self.values,o.dest) 242 setattr(self.values,o.dest,oldVal+1) 243 else: 244 raise RuntimeError("Action",o.action,"not implemented")
245
246 -class Subcommand(object):
247 """A subcommand of a root command-line application that may be 248 invoked by a SubcommandOptionParser. 249 Taken from https://gist.github.com/sampsyo/462717 250 """
251 - def __init__(self, name, parser=None, help='', aliases=(),nr=None,exactNr=None):
252 """Creates a new subcommand. name is the primary way to invoke 253 the subcommand; aliases are alternate names. parser is an 254 OptionParser responsible for parsing the subcommand's options. 255 help is a short description of the command. If no parser is 256 given, it defaults to a new, empty OptionParser. 257 """ 258 self.name = name 259 self.parser = parser or OptionParser() 260 self.aliases = aliases 261 self.help = help 262 self.nr=nr 263 self.exactNr=exactNr
264
265 -class SubcommandFoamOptionParser(FoamOptionParser):
266 """Subclass of the regular option parser that allows setting subcommands 267 Inspired by https://gist.github.com/sampsyo/462717 268 """ 269 270 # A singleton command used to give help on other subcommands. 271 _HelpSubcommand = Subcommand('help', OptionParser(), 272 help='give detailed help on a specific sub-command', 273 aliases=('?',)) 274
275 - def __init__(self, 276 args=None, 277 usage=None, 278 version=None, 279 description=None, 280 subcommands=[]):
281 """ 282 @param usage: usage string. If missing a default is used 283 @param version: if missing the PyFoam-version is used 284 @param description: description of the utility 285 @param subcommands: list with subcommands to prepopulate the parser 286 @param args: Command line arguments. If unset sys.argv[1:] is used. 287 Can be a string: it will be splitted then unsing the spaces (very primitive), or a list of strings (prefered) 288 """ 289 if usage==None: 290 usage=""" 291 %prog [general options ...] COMMAND [ARGS ...] 292 %prog help COMMAND""" 293 294 FoamOptionParser.__init__(self, 295 args, 296 usage, 297 version, 298 description, 299 interspersed=False) 300 301 self.subcommands=subcommands[:] 302 self.addSubcommand(self._HelpSubcommand) 303 304 # Adjust the help-visible name of each subcommand. 305 for subcommand in self.subcommands: 306 subcommand.parser.prog = '%s %s' % \ 307 (self.get_prog_name(), subcommand.name) 308 309 self.cmdname=None 310 self.__subopts=None 311 self.subargs=None
312
313 - def addSubcommand(self,cmd,usage=None):
314 if usage==None: 315 cmd.parser.usage=self.usage 316 else: 317 cmd.parser.usage=usage 318 cmd.parser.formatter=TitledHelpFormatter() 319 self.subcommands.append(cmd)
320 321 # Add the list of subcommands to the help message.
322 - def format_help(self, formatter=None):
323 """Taken from https://gist.github.com/sampsyo/462717""" 324 # Get the original help message, to which we will append. 325 out = OptionParser.format_help(self, formatter) 326 if formatter is None: 327 formatter = self.formatter 328 329 # Subcommands header. 330 result = ["\n"] 331 result.append(formatter.format_heading('Commands')) 332 formatter.indent() 333 334 # Generate the display names (including aliases). 335 # Also determine the help position. 336 disp_names = [] 337 help_position = 0 338 for subcommand in self.subcommands: 339 name = subcommand.name 340 if subcommand.aliases: 341 name += ' (%s)' % ', '.join(subcommand.aliases) 342 disp_names.append(name) 343 344 # Set the help position based on the max width. 345 proposed_help_position = len(name) + formatter.current_indent + 2 346 if proposed_help_position <= formatter.max_help_position: 347 help_position = max(help_position, proposed_help_position) 348 349 # Add each subcommand to the output. 350 for subcommand, name in zip(self.subcommands, disp_names): 351 # Lifted directly from optparse.py. 352 name_width = help_position - formatter.current_indent - 2 353 if len(name) > name_width: 354 name = "%*s%s\n" % (formatter.current_indent, "", name) 355 indent_first = help_position 356 else: 357 name = "%*s%-*s " % (formatter.current_indent, "", 358 name_width, name) 359 indent_first = 0 360 result.append(name) 361 help_width = formatter.width - help_position 362 help_lines = textwrap.wrap(subcommand.help, help_width) 363 result.append("%*s%s\n" % (indent_first, "", help_lines[0])) 364 result.extend(["%*s%s\n" % (help_position, "", line) 365 for line in help_lines[1:]]) 366 formatter.dedent() 367 368 # Concatenate the original help message with the subcommand 369 # list. 370 return out + "".join(result)
371
372 - def parse(self,nr=None,exactNr=None):
373 """Do the parsing of a subcommand""" 374 if nr or exactNr: 375 self.error("For calling this implementention no setting of nr and exactNr is valid") 376 377 FoamOptionParser.parse(self,nr=1,exactNr=False) 378 379 if not self.args: 380 # no command 381 self.print_help() 382 self.exit() 383 else: 384 cmdname=self.args.pop(0) 385 subcommand = self._subcommand_for_name(cmdname) 386 if not subcommand: 387 self.error('unknown command ' + cmdname) 388 389 # make sure we get the canonical name 390 self.cmdname=subcommand.name 391 392 nr=subcommand.nr 393 exactNr=subcommand.exactNr 394 395 if subcommand is not self._HelpSubcommand: 396 subcommand.parser.usage=subcommand.parser.usage.replace("COMMAND",cmdname) 397 398 self.__subopts,self.subargs=subcommand.parser.parse_args(self.args) 399 if nr!=None: 400 if len(self.subargs)<nr: 401 self.error("Too few arguments for %s (%d needed, %d given)" %(cmdname,nr,len(self.subargs))) 402 403 maxNr=nr 404 if exactNr and len(self.subargs)>maxNr: 405 self.error("Too many arguments for %s (%d needed, %d given)" %(cmdname,nr,len(self.subargs))) 406 407 if subcommand is self._HelpSubcommand: 408 if self.subargs: 409 # help on a specific command 410 cmdname=self.subargs[0] 411 helpcommand = self._subcommand_for_name(cmdname) 412 if helpcommand!=None: 413 helpcommand.parser.usage=helpcommand.parser.usage.replace("COMMAND",cmdname) 414 helpcommand.parser.print_help() 415 else: 416 self.print_help() 417 self.exit() 418 else: 419 # general 420 self.print_help() 421 self.exit() 422 self.options._update_loose(self.__subopts.__dict__)
423
424 - def getArgs(self):
425 """Return the arguments left after parsing""" 426 if self.subargs!=None: 427 return self.subargs 428 else: 429 return []
430
431 - def _subcommand_for_name(self, name):
432 """Return the subcommand in self.subcommands matching the 433 given name. The name may either be the name of a subcommand or 434 an alias. If no subcommand matches, returns None. 435 """ 436 for subcommand in self.subcommands: 437 if name == subcommand.name or \ 438 name in subcommand.aliases: 439 return subcommand 440 441 return None
442