1
2 """Base class for pyFoam-applications
3
4 Classes can also be called with a command-line string"""
5
6 from optparse import OptionGroup
7 from PyFoam.Basics.FoamOptionParser import FoamOptionParser,SubcommandFoamOptionParser
8 from PyFoam.Error import error,warning,FatalErrorPyFoamException,PyFoamException,isatty
9 from PyFoam.RunDictionary.SolutionDirectory import NoTouchSolutionDirectory
10
11 from PyFoam.Basics.TerminalFormatter import TerminalFormatter
12 from PyFoam import configuration
13
14 format=TerminalFormatter()
15 format.getConfigFormat("error")
16 format.getConfigFormat("warn")
17
18 import sys
19 from os import path,getcwd,environ
20 from copy import deepcopy
21
22 from PyFoam.ThirdParty.six import print_
23 from PyFoam.ThirdParty.six import iteritems
24
32
33
35 if hasattr(sys,'ps1'):
36 warning("Interactive mode. No debugger")
37 sys.__excepthook__(type,value,tb)
38 elif not (isatty(sys.stderr) and isatty(sys.stdin) and isatty(sys.stdout)):
39 warning("Not on a terminal. No debugger")
40 sys.__excepthook__(type,value,tb)
41 elif issubclass(type,SyntaxError) and not debugOnSyntaxError:
42 warning("Syntax error. No debugger")
43 sys.__excepthook__(type,value,tb)
44 else:
45 import traceback
46 try:
47 import ipdb as pdb
48 except ImportError:
49 import pdb
50 traceback.print_exception(type,value,tb)
51 print_()
52 pdb.pm()
53
57
59 """This class is the base for all pyFoam-utilities"""
61 "This class is a quick and dirty wrapper to use a dictionary like a struct"
63 try:
64 return self[key]
65 except KeyError:
66 raise AttributeError(key)
67
68 - def __init__(self,
69 args=None,
70 description=None,
71 epilog=None,
72 examples=None,
73 usage=None,
74 interspersed=False,
75 nr=None,
76 changeVersion=True,
77 exactNr=True,
78 subcommands=None,
79 inputApp=None,
80 **kwArgs):
81 """
82 @param description: description of the command
83 @param epilog: text to be printed after the options-help
84 @param examples: usage examples to be printed after the epilog
85 @param usage: Usage
86 @param interspersed: Is the command line allowed to be interspersed (options after the arguments)
87 @param args: Command line arguments when using the Application as a 'class' from a script
88 @param nr: Number of required arguments
89 @param changeVersion: May this application change the version of OF used?
90 @param exactNr: Must not have more than the required number of arguments
91 @param subcommands: parse and use subcommands from the command line. Either True or a list with subcommands
92 @param inputApp: Application with input data. Used to allow a 'pipe-like' behaviour if the class is used from a Script
93 """
94 if subcommands:
95 self.subs=True
96 if interspersed:
97 self.error("Subcommand parser does not work with 'interspersed'")
98 if subcommands==True:
99 subcommands=[]
100 self.parser=SubcommandFoamOptionParser(args=args,
101 description=description,
102 epilog=epilog,
103 examples=examples,
104 usage=usage,
105 subcommands=subcommands)
106 nr=None
107 exactNr=False
108 else:
109 self.subs=False
110 self.parser=FoamOptionParser(args=args,
111 description=description,
112 epilog=epilog,
113 examples=examples,
114 usage=usage,
115 interspersed=interspersed)
116
117 self.calledName=sys.argv[0]
118 self.calledAsClass=(args!=None)
119 if self.calledAsClass:
120 self.calledName=self.__class__.__name__+" used by "+sys.argv[0]
121 self.parser.prog=self.calledName
122
123 self.generalOpts=None
124
125 self.__appData=self.iDict()
126 if inputApp:
127 self.__appData["inputData"]=inputApp.getData()
128
129 grp=OptionGroup(self.parser,
130 "Default",
131 "Options common to all PyFoam-applications")
132
133 if changeVersion:
134
135 grp.add_option("--foamVersion",
136 dest="foamVersion",
137 default=None,
138 help="Change the OpenFOAM-version that is to be used. To get a list of know Foam-versions use the pyFoamVersion.py-utility")
139 if "WM_PROJECT_VERSION" in environ:
140 grp.add_option("--currentFoamVersion",
141 dest="foamVersion",
142 const=environ["WM_PROJECT_VERSION"],
143 default=None,
144 action="store_const",
145 help="Use the current OpenFOAM-version "+environ["WM_PROJECT_VERSION"])
146
147 grp.add_option("--force-32bit",
148 dest="force32",
149 default=False,
150 action="store_true",
151 help="Forces the usage of a 32-bit-version if that version exists as 32 and 64 bit. Only used when --foamVersion is used")
152 grp.add_option("--force-64bit",
153 dest="force64",
154 default=False,
155 action="store_true",
156 help="Forces the usage of a 64-bit-version if that version exists as 32 and 64 bit. Only used when --foamVersion is used")
157 grp.add_option("--force-debug",
158 dest="compileOption",
159 const="Debug",
160 default=None,
161 action="store_const",
162 help="Forces the value Debug for the WM_COMPILE_OPTION. Only used when --foamVersion is used")
163 grp.add_option("--force-opt",
164 dest="compileOption",
165 const="Opt",
166 default=None,
167 action="store_const",
168 help="Forces the value Opt for the WM_COMPILE_OPTION. Only used when --foamVersion is used")
169 grp.add_option("--force-system-compiler",
170 dest="foamCompiler",
171 const="system",
172 default=None,
173 action="store_const",
174 help="Force using a 'system' compiler (compiler installed in the system)")
175 grp.add_option("--force-openfoam-compiler",
176 dest="foamCompiler",
177 const="OpenFOAM",
178 default=None,
179 action="store_const",
180 help="Force using a 'OpenFOAM' compiler (compiler installed in ThirdParty)")
181 grp.add_option("--force-compiler",
182 dest="wmCompiler",
183 default=None,
184 action="store",
185 help="Overwrite value for WM_COMPILER (for instance Gcc47 ...)")
186
187 grp.add_option("--psyco-accelerated",
188 dest="psyco",
189 default=False,
190 action="store_true",
191 help="Accelerate the script using the psyco-library (EXPERIMENTAL and requires a separatly installed psyco)")
192 grp.add_option("--profile-python",
193 dest="profilePython",
194 default=False,
195 action="store_true",
196 help="Profile the python-script (not the OpenFOAM-program) - mostly of use for developers")
197 grp.add_option("--profile-cpython",
198 dest="profileCPython",
199 default=False,
200 action="store_true",
201 help="Profile the python-script (not the OpenFOAM-program) using the better cProfile library - mostly of use for developers")
202 grp.add_option("--profile-hotshot",
203 dest="profileHotshot",
204 default=False,
205 action="store_true",
206 help="Profile the python-script using the hotshot-library (not the OpenFOAM-program) - mostly of use for developers - EXPERIMENTAL")
207
208 dbg=OptionGroup(self.parser,
209 "Debugging",
210 "Options mainly used for debugging PyFoam-Utilities")
211
212 dbg.add_option("--traceback-on-error",
213 dest="traceback",
214 default=False,
215 action="store_true",
216 help="Prints a traceback when an error is encountered (for debugging)")
217 dbg.add_option("--interactive-debugger",
218 dest="interactiveDebug",
219 default=False,
220 action="store_true",
221 help="In case of an exception start the interactive debugger PDB. Also implies --traceback-on-error")
222 dbg.add_option("--catch-USR1-signal",
223 dest="catchUSR1Signal",
224 default=False,
225 action="store_true",
226 help="If the USR1-signal is sent to the application with 'kill -USR1 <pid>' the application ens and prints a traceback. If interactive debugging is enabled then the debugger is entered. Use to investigate hangups")
227 dbg.add_option("--also-catch-TERM-signal",
228 dest="alsoCatchTERMsignal",
229 default=False,
230 action="store_true",
231 help="In addition to USR1 catch the regular TERM-kill")
232 dbg.add_option("--keyboard-interrupt-trace",
233 dest="keyboardInterrupTrace",
234 default=False,
235 action="store_true",
236 help="Make the application behave like with --catch-USR1-signal if <Ctrl>-C is pressed")
237 dbg.add_option("--syntax-error-debugger",
238 dest="syntaxErrorDebugger",
239 default=False,
240 action="store_true",
241 help="Only makes sense with --interactive-debugger: Do interactive debugging even when a syntax error was encountered")
242 dbg.add_option("--i-am-a-developer",
243 dest="developerMode",
244 default=False,
245 action="store_true",
246 help="Switch on all of the above options. Usually this makes only sense if you're developing PyFoam'")
247 dbg.add_option("--interactive-after-execution",
248 dest="interacticeAfterExecution",
249 default=False,
250 action="store_true",
251 help="Instead of ending execution drop to an interactive shell (which is IPython if possible)")
252
253 grp.add_option("--dump-application-data",
254 dest="dumpAppData",
255 default=False,
256 action="store_true",
257 help="Print the dictionary with the generated application data after running")
258 grp.add_option("--pickle-application-data",
259 dest="pickleApplicationData",
260 default=None,
261 action="store",
262 type="string",
263 help="""\
264 Write a pickled version of the application data to a file. If the
265 filename given is 'stdout' then the pickled data is written to
266 stdout. The usual standard output is then captured and added to the
267 application data as an entry 'stdout' (same for 'stderr'). Be careful
268 with these option for commands that generate a lot of output""")
269
270 self.parser.add_option_group(grp)
271 self.parser.add_option_group(dbg)
272
273 self.addOptions()
274 self.parser.parse(nr=nr,exactNr=exactNr)
275 if len(kwArgs)>0:
276 self.parser.processKeywordArguments(kwArgs)
277 self.opts=self.parser.getOptions()
278 if self.subs:
279 self.cmdname=self.parser.cmdname
280
281 if "WM_PROJECT_VERSION" not in environ:
282 warning("$WM_PROJECT_VERSION unset. PyFoam will not be able to determine the OpenFOAM-version and behave strangely")
283 if self.opts.developerMode:
284 self.opts.syntaxErrorDebugger=True
285 self.opts.keyboardInterrupTrace=True
286 self.opts.alsoCatchTERMsignal=True
287 self.opts.catchUSR1Signal=True
288 self.opts.interactiveDebug=True
289 self.opts.traceback=True
290
291 if self.opts.interactiveDebug:
292 sys.excepthook=lambda a1,a2,a3:pyFoamExceptionHook(a1,
293 a2,
294 a3,
295 debugOnSyntaxError=self.opts.syntaxErrorDebugger)
296 self.opts.traceback=True
297 if self.opts.catchUSR1Signal:
298 import signal
299 signal.signal(signal.SIGUSR1,pyFoamSIG1HandlerPrintStack)
300 if self.opts.alsoCatchTERMsignal:
301 signal.signal(signal.SIGTERM,pyFoamSIG1HandlerPrintStack)
302 self.opts.traceback=True
303
304 if self.opts.keyboardInterrupTrace:
305 import signal
306 signal.signal(signal.SIGINT,pyFoamSIG1HandlerPrintStack)
307 self.opts.traceback=True
308
309 if self.opts.psyco:
310 try:
311 import psyco
312 psyco.full()
313 except ImportError:
314 warning("No psyco installed. Continuing without acceleration")
315
316 if self.opts.profilePython or self.opts.profileCPython or self.opts.profileHotshot:
317 if sum([self.opts.profilePython,self.opts.profileCPython,self.opts.profileHotshot])>1:
318 self.error("Profiling with hotshot and regular profiling are mutual exclusive")
319 print_("Running profiled")
320 if self.opts.profilePython:
321 import profile
322 elif self.opts.profileCPython:
323 import cProfile as profile
324 else:
325 import hotshot
326 profileData=path.basename(sys.argv[0])+".profile"
327 if self.opts.profilePython or self.opts.profileCPython:
328 profile.runctx('self.run()',None,{'self':self},profileData)
329 print_("Reading python profile")
330 import pstats
331 stats=pstats.Stats(profileData)
332 else:
333 profileData+=".hotshot"
334 prof=hotshot.Profile(profileData)
335 prof.runctx('self.run()',{},{'self':self})
336 print_("Writing and reading hotshot profile")
337 prof.close()
338 import hotshot.stats
339 stats=hotshot.stats.load(profileData)
340 stats.strip_dirs()
341 stats.sort_stats('time','calls')
342 stats.print_stats(20)
343
344 self.parser.restoreEnvironment()
345 else:
346 try:
347 if self.opts.pickleApplicationData=="stdout":
348
349 from PyFoam.ThirdParty.six.moves import StringIO
350
351 oldStdout=sys.stdout
352 oldStderr=sys.stderr
353 sys.stdout=StringIO()
354 sys.stderr=StringIO()
355
356 result=self.run()
357
358
359 self.parser.restoreEnvironment()
360
361 if self.opts.pickleApplicationData=="stdout":
362
363 self.__appData["stdout"]=sys.stdout.getvalue()
364 self.__appData["stderr"]=sys.stderr.getvalue()
365 sys.stdout=oldStdout
366 sys.stderr=oldStderr
367
368 if self.opts.pickleApplicationData:
369 from PyFoam.ThirdParty.six.moves import cPickle as pickle
370 if self.opts.pickleApplicationData=="stdout":
371 pick=pickle.Pickler(sys.stdout)
372 else:
373 pick=pickle.Pickler(open(self.opts.pickleApplicationData,'wb'))
374 pick.dump(self.__appData)
375 del pick
376 if self.opts.dumpAppData:
377 import pprint
378 print_("Application data:")
379 printer=pprint.PrettyPrinter()
380 printer.pprint(self.__appData)
381
382 if self.opts.interacticeAfterExecution:
383 print_("\nDropping to interactive shell ... ",end="")
384 ns={}
385 ns.update(locals())
386 ns.update(globals())
387 try:
388 import IPython
389 print_("found IPython ...",end="")
390 if "embed" in dir(IPython):
391 print_("up-to-date IPython\n")
392 IPython.embed(user_ns=ns)
393 else:
394 print_("old-school IPython\n")
395 IPython.Shell.IPythonShellEmbed(argv="",user_ns=ns)()
396
397 except ImportError:
398 print_("no IPython -> regular shell\n")
399 from code import InteractiveConsole
400 c=InteractiveConsole(ns)
401 c.interact()
402 print_("\nEnding interactive shell\n")
403 return result
404 except PyFoamException:
405 e=sys.exc_info()[1]
406 if self.opts.traceback or self.calledAsClass:
407 raise
408 else:
409 self.errorPrint(str(e))
410
412 """Get application data"""
413 try:
414 return self.__appData[key]
415 except KeyError:
416 print_("available keys:",list(self.__appData.keys()))
417 raise
418
420 """Set data. Only if key does not exist (data can only be set once)"""
421 if key in self.__appData:
422 self.error(key,"does already exist in app-data")
423 self.__appData[key]=deepcopy(value)
424
426 """Iterate over the application data"""
427 for k in self.__appData:
428 yield k
429
431 return iter(list(self.__appData.keys()))
432
434 return iter(list(self.__appData.items()))
435
437 try:
438 return self.__appData[key]
439 except KeyError:
440 raise AttributeError(key)
441
443 """Get the application data"""
444 return deepcopy(self.__appData)
445
447 """Set the application data
448
449 @param data: dictionary whose entries will be added to the
450 application data (possibly overwriting old entries of the same name)"""
451 for k,v in iteritems(data):
452 self.__appData[k]=deepcopy(v)
453
455 if self.generalOpts==None:
456 self.generalOpts=OptionGroup(self.parser,
457 "General",
458 "General options for the control of OpenFOAM-runs")
459 self.parser.add_option_group(self.generalOpts)
460
462 """
463 Add options to the parser
464 """
465 pass
466
468 """
469 Run the real application
470 """
471 error("Not a valid application")
472
473
475 """Raise a error exception. How it will be handled is a different story
476 @param args: Arguments to the exception
477 """
478 raise PyFoamApplicationException(self,*args)
479
495
510
512 """
513 Don't print a warning message
514 @param args: Arguments that are to be printed
515 """
516 pass
517
518 - def checkCase(self,name,fatal=True,verbose=True):
519 """
520 Check whether this is a valid OpenFOAM-case
521 @param name: the directory-bame that is supposed to be the case
522 @param fatal: If this is not a case then the application ends
523 @param verbose: If this is not a case no warning is issued
524 """
525 if fatal:
526 func=self.error
527 elif verbose:
528 func=self.warning
529 else:
530 func=self.silent
531
532 if not path.exists(name):
533 func("Case",name,"does not exist")
534 return False
535 if not path.isdir(name):
536 func("Case",name,"is not a directory")
537 return False
538 if not path.exists(path.join(name,"system")):
539 func("Case",name,"does not have a 'system' directory")
540 return False
541 if not path.exists(path.join(name,"constant")):
542 func("Case",name,"does not have a 'constant' directory")
543 return False
544
545 return True
546
561
568
569
570