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