Salome HOME
print stack for everybody, activate debug mode when sat -g option is used
[tools/sat.git] / src / salomeTools.py
1 #!/usr/bin/env python
2 #-*- coding:utf-8 -*-
3 #  Copyright (C) 2010-2012  CEA/DEN
4 #
5 #  This library is free software; you can redistribute it and/or
6 #  modify it under the terms of the GNU Lesser General Public
7 #  License as published by the Free Software Foundation; either
8 #  version 2.1 of the License.
9 #
10 #  This library is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 #  Lesser General Public License for more details.
14 #
15 #  You should have received a copy of the GNU Lesser General Public
16 #  License along with this library; if not, write to the Free Software
17 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18
19 """
20 This file is the main API file for salomeTools
21
22 | Warning: NO '__main__ ' call allowed,
23 |          Use 'sat' (in parent directory)
24 |
25 | Usage: see file ../sat
26 """
27
28 import sys
29
30 # exit OKSYS and KOSYS seems equal on linux or windows
31 _OKSYS = 0  # OK
32 _KOSYS = 1  # KO
33
34 ########################################################################
35 # NO __main__ entry allowed, use sat
36 ########################################################################
37 if __name__ == "__main__":
38     msg = """
39 ERROR: 'salomeTools.py' is not main command entry (CLI) for salomeTools.
40        Use 'sat' instead.\n\n"""
41     sys.stderr.write(msg)
42     sys.exit(_KOSYS)
43
44 # python imports
45 import os
46 import re
47 import tempfile
48 import imp
49 import types
50 import gettext
51 import traceback
52
53 import src
54 import src.debug as DBG # Easy print stderr (for DEBUG only)
55 import src.returnCode as RCO # Easy (ok/ko, why) return methods code
56 import src.utilsSat as UTS
57
58 # get path to salomeTools sources
59 satdir  = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
60 srcdir = os.path.join(satdir, 'src')
61 cmdsdir = os.path.join(satdir, 'commands')
62
63 import commands.config as CONFIG
64
65 # load resources for internationalization
66 gettext.install("salomeTools", os.path.join(srcdir, "i18n"))
67
68 try:
69   _LANG = os.environ["LANG"] # original locale
70 except:
71   _LANG = "en_US.utf8" #default
72
73 # The possible hooks : 
74 # pre is for hooks to be executed before commands
75 # post is for hooks to be executed after commands
76 C_PRE_HOOK = "pre"
77 C_POST_HOOK = "post"
78
79 # Define all possible option for salomeTools command :  sat <option> <args>
80 parser = src.options.Options()
81 parser.add_option('h', 'help', 'boolean', 'help', 
82                   _("shows global help or help on a specific command."))
83 parser.add_option('o', 'overwrite', 'list', "overwrite", 
84                   _("overwrites a configuration parameters."))
85 parser.add_option('g', 'debug', 'boolean', 'debug_mode', 
86                   _("run salomeTools in debug mode."))
87 parser.add_option('v', 'verbose', 'int', "output_verbose_level", 
88                   _("change output verbose level (default is 3)."))
89 parser.add_option('b', 'batch', 'boolean', "batch", 
90                   _("batch mode (no question)."))
91 parser.add_option('t', 'all_in_terminal', 'boolean', "all_in_terminal", 
92                   _("all traces in the terminal (for example compilation logs)."))
93 parser.add_option('l', 'logs_paths_in_file', 'string', "logs_paths_in_file", 
94                   _("put the command results and paths to log files."))
95
96
97 ########################################################################
98 # utility methods
99 ########################################################################
100 def find_command_list(dirPath):
101     ''' Parse files in dirPath that end with .py : it gives commands list
102     
103     :param dirPath str: The directory path where to search the commands
104     :return: cmd_list : the list containing the commands name 
105     :rtype: list
106     '''
107     cmd_list = []
108     for item in os.listdir(dirPath):
109         if "__init__" in item: continue # skip __init__.py
110         if item.endswith('.py'):
111             cmd_list.append(item[:-len('.py')])
112     return cmd_list
113
114
115 # The list of valid salomeTools commands from cmdsdir
116 # ['config', 'compile', 'prepare', ...]
117 _COMMANDS_NAMES = find_command_list(cmdsdir)
118 lCommand = find_command_list(cmdsdir) # obsolete
119
120 def getCommandsList():
121     """Gives commands list (as basename of files .py in directory commands""" 
122     return _COMMANDS_NAMES
123
124 def launchSat(command, logger=None):
125     """
126     launch sat as subprocess.Popen
127     command as string ('sat --help' for example)
128     used for unittest, or else...
129     
130     :return: RCO.ReturnCode with getValue as subprocess.Popen output
131     """
132     if "sat" not in command.split()[0]:
133       raise Exception(_("Not a valid command for launchSat: '%s'") % command)
134     env = dict(os.environ) # copy
135     # theorically useless, in user environ $PATH,
136     # on ne sait jamais
137     # https://docs.python.org/2/library/os.html
138     # On some platforms, including FreeBSD and Mac OS X, 
139     # setting environ may cause memory leaks.
140     # see test/initializeTest.py
141     if satdir not in env["PATH"].split(":"):
142       env["PATH"] = satdir + ":" + env["PATH"]
143     # TODO setLocale not 'fr' on subprocesses, why not?
144     # env["LANG"] == ''
145     res = UTS.Popen(command, env=env, logger=logger) # logger or not.
146     return res
147
148 def setNotLocale():
149     """force english at any moment"""
150     os.environ["LANG"] = ''
151     gettext.install("salomeTools", os.path.join(srcdir, "i18n"))
152     DBG.write("setNotLocale", os.environ["LANG"])
153     
154 def setLocale():
155     """
156     reset initial locale at any moment 
157     'fr' or else (TODO) from initial environment var '$LANG'
158     'i18n' as 'internationalization'
159     """
160     os.environ["LANG"] = _LANG
161     gettext.install("salomeTools", os.path.join(srcdir, "i18n"))
162     DBG.write("setLocale", os.environ["LANG"])
163     
164 def getVersion():
165     """get version number as string"""
166     return src.__version__
167  
168 def assumeAsList(strOrList):
169     """return a list as sys.argv if string"""
170     if type(strOrList) is list:
171       return list(strOrList) # copy
172     else:
173       res = strOrList.split(" ")
174       return [r for r in res if r != ""] # supposed string to split for convenience
175
176
177 ########################################################################
178 # Sat class
179 ########################################################################
180 class Sat(object):
181     """
182     The main class that stores all the commands of salomeTools
183     """
184     def __init__(self, logger=None):
185         """
186         Initialization
187
188         :param logger: The logger, if set from parent
189         """
190         # initialization of class attributes
191         self.__dict__ = dict()
192         # logger from parent
193         # future only one logger from src.loggingSimple at 2018/06
194         # to replace old loggers from src.logger
195         self.mainLogger = logger
196         src.logger.setCurrentLogger(logger)
197         self.cfg = None  # the config that will be read using pyconf module
198         self.arguments = None
199         self.remaindersArgs = None
200         self.options = None  # the options passed to salomeTools
201         self.datadir = None  # default value will be <salomeTools root>/data
202
203     def obsolete__init__(self, opt='', datadir=None):
204         '''Initialization
205
206         :param opt str: The sat options
207         :param: datadir str : the directory that contain all the external
208                               data (like software pyconf and software scripts)
209         '''
210         # Read the salomeTools options (the list of possible options is
211         # at the beginning of this file)
212         argList = self.assumeAsList(opt)
213         options, argus = parser.parse_args(argList)
214
215         # initialization of class attributes
216         self.__dict__ = dict()
217         self.cfg = None  # the config that will be read using pyconf module
218         self.arguments = argList
219         self.options = options  # the options passed to salomeTools
220         self.datadir = datadir  # default value will be <salomeTools root>/data
221         # set the commands by calling the dedicated function
222         self._setCommands(cmdsdir)
223
224         # if the help option has been called, print help and exit
225         if options.help:
226             try:
227                 self.print_help(argus)
228                 sys.exit(0)
229             except Exception as exc:
230                 write_exception(exc)
231                 sys.exit(1)
232
233     ##################################################################
234     def setInternals(self, opt=None, datadir=None):
235         """set the commands by calling the dedicated function etc..."""
236         options, remaindersArgs = parser.parse_args(opt)
237         if options.debug_mode:
238             DBG.push_debug(True)
239         self.arguments = opt
240         self.options = options # the generic options passed to salomeTools
241         self.remaindersArgs = remaindersArgs  # the command and their options
242         self.datadir = datadir # default value will be <salomeTools root>/data
243         self._setCommands(cmdsdir)
244         DBG.write("Sat.options", self.options, self.options.debug_mode)
245
246     def getConfig(self):
247         return self.cfg
248
249     ##################################################################
250     def execute_cli(self, args):
251         """
252         assume launch command from args, pyconf config known yet
253         """
254         argList = self.assumeAsList(args)
255         # no arguments : print general help
256         if len(argList) == 0:
257           self.mainLogger.info(get_help())
258           return RCO.ReturnCode("OK", "no args as sat --help")
259
260         self.setInternals(opt=argList, datadir=None)
261
262         # print general help on -h
263         if self.options.help and len(self.remaindersArgs) == 0:
264           self.mainLogger.info(get_help())
265           return RCO.ReturnCode("OK", "help done")
266
267         DBG.write("options", self.options)
268         DBG.write("remaindersArgs", self.remaindersArgs)
269
270         if len(self.remaindersArgs) == 0:
271           return RCO.ReturnCode("KO", "Nothing to do")
272
273         # print command help on -h --help after name command
274         if "-h" in self.remaindersArgs or "--help" in self.remaindersArgs:
275           self.mainLogger.info(self.get_help(self.remaindersArgs))
276           return RCO.ReturnCode("OK", "sat --help command")
277
278         # print command help on -h and continue if something do do more
279         if self.options.help and len(self.remaindersArgs) >= 1:
280           self.mainLogger.info(self.get_help(self.remaindersArgs))
281
282         command = self.remaindersArgs[0]
283         # get dynamically the command function to call
284         fun_command = self.__getattr__(command)
285         # Run the command using the arguments
286         code = fun_command(self.remaindersArgs[1:])
287
288         if code is None: code = 0 # what?! do not know why so respect history
289
290         # return salomeTools command with the right message
291         # code (0 if no errors, else 1)
292         if code == _KOSYS:
293           return RCO.ReturnCode("KO", "problem on execute_cli 'sat %s'" % " ".join(argList))
294         else:
295           return RCO.ReturnCode("OK", "execute_cli 'sat %s' done" % " ".join(argList))
296
297     '''
298     # OBSOLETE... see file ../sat
299     # ###############################
300     # MAIN : terminal command usage #
301     # ###############################
302     if __name__ == "__main__":  
303         # Initialize the code that will be returned by the terminal command 
304         code = 0
305         (options, args) = parser.parse_args(sys.argv[1:])
306
307         # no arguments : print general help
308         if len(args) == 0:
309             print_help()
310             sys.exit(0)
311
312         # instantiate the salomeTools class with correct options
313         sat = Sat(sys.argv[1:])
314         # the command called
315         command = args[0]
316         # get dynamically the command function to call
317         fun_command = sat.__getattr__(command)
318         # Run the command using the arguments
319         code = fun_command(args[1:])
320
321         # exit salomeTools with the right code (0 if no errors, else 1)
322         if code is None: code = 0
323         sys.exit(code)
324
325     '''
326
327     def __getattr__(self, name):
328         '''
329         overwrite of __getattr__ function in order to display
330         a customized message in case of a wrong call
331         
332         :param name str: The name of the attribute 
333         '''
334         if name in self.__dict__:
335             return self.__dict__[name]
336         else:
337             raise AttributeError(name + _(" is not a valid command"))
338
339     def assumeAsList(self, strOrList):
340         # DBG.write("Sat assumeAsList", strOrList, True)
341         return assumeAsList(strOrList)
342     
343     def _setCommands(self, dirPath):
344         '''set class attributes corresponding to all commands that are 
345            in the dirPath directory
346         
347         :param dirPath str: The directory path containing the commands 
348         '''
349         # loop on the commands name
350         for nameCmd in lCommand:
351             DBG.write("load module command '%s.py'" % nameCmd, "")
352             # Exception for the jobs command that requires the paramiko module
353             if nameCmd == "jobs":
354                 try:
355                     saveout = sys.stderr
356                     ff = tempfile.TemporaryFile()
357                     sys.stderr = ff
358                     import paramiko
359                     sys.stderr = saveout
360                 except:
361                     sys.stderr = saveout
362                     continue
363
364             # load the module that has name nameCmd in dirPath
365             (file_, pathname, description) = imp.find_module(nameCmd, [dirPath])
366             module = imp.load_module(nameCmd, file_, pathname, description)
367             
368             def run_command(args='',
369                             options=None,
370                             batch = False,
371                             verbose = -1,
372                             logger_add_link = None):
373                 '''
374                 The function that will load the configuration (all pyconf)
375                 and return the function run of the command corresponding to module
376                 
377                 :param args str: The arguments of the command 
378                 '''
379                 # Make sure the internationalization is available
380                 gettext.install('salomeTools', os.path.join(satdir, 'src', 'i18n'))
381                 
382                 # Get the arguments in a list and remove the empty elements
383                 if type(args) == type(''):
384                     # split by spaces without considering spaces in quotes
385                     argv_0 = re.findall(r'(?:"[^"]*"|[^\s"])+', args)
386                 else:
387                     argv_0 = args
388                 
389                 if argv_0 != ['']:
390                     while "" in argv_0: argv_0.remove("")
391                 
392                 # Format the argv list in order to prevent strings 
393                 # that contain a blank to be separated
394                 argv = []
395                 elem_old = ""
396                 for elem in argv_0:
397                     if argv == [] or elem_old.startswith("-") or elem.startswith("-"):
398                         argv.append(elem)
399                     else:
400                         argv[-1] += " " + elem
401                     elem_old = elem
402                            
403                 # if it is provided by the command line, get the application
404                 appliToLoad = None
405                 if argv not in [[''], []] and argv[0][0] != "-":
406                     appliToLoad = argv[0].rstrip('*')
407                     argv = argv[1:]
408                 
409                 # Check if the global options of salomeTools have to be changed
410                 if options:
411                     options_save = self.options
412                     self.options = options  
413
414                 # read the configuration from all the pyconf files    
415                 cfgManager = CONFIG.ConfigManager()
416                 self.cfg = cfgManager.get_config(datadir=self.datadir, 
417                                                  application=appliToLoad, 
418                                                  options=self.options, 
419                                                  command=__nameCmd__)
420                                
421                 # Set the verbose mode if called
422                 if verbose > -1:
423                     verbose_save = self.options.output_verbose_level
424                     self.options.__setattr__("output_verbose_level", verbose)    
425
426                 # Set batch mode if called
427                 if batch:
428                     batch_save = self.options.batch
429                     self.options.__setattr__("batch", True)
430
431                 # set output level
432                 if self.options.output_verbose_level is not None:
433                     self.cfg.USER.output_verbose_level = self.options.output_verbose_level
434                 if self.cfg.USER.output_verbose_level < 1:
435                     self.cfg.USER.output_verbose_level = 0
436                 silent = (self.cfg.USER.output_verbose_level == 0)
437
438                 # create log file
439                 micro_command = False
440                 if logger_add_link:
441                     micro_command = True
442                 logger_command = src.logger.Logger(self.cfg,
443                                    silent_sysstd=silent,
444                                    all_in_terminal=self.options.all_in_terminal,
445                                    micro_command=micro_command)
446                 
447                 # Check that the path given by the logs_paths_in_file option
448                 # is a file path that can be written
449                 if self.options.logs_paths_in_file and not micro_command:
450                     try:
451                         self.options.logs_paths_in_file = os.path.abspath(
452                                                 self.options.logs_paths_in_file)
453                         dir_file = os.path.dirname(self.options.logs_paths_in_file)
454                         if not os.path.exists(dir_file):
455                             os.makedirs(dir_file)
456                         if os.path.exists(self.options.logs_paths_in_file):
457                             os.remove(self.options.logs_paths_in_file)
458                         file_test = open(self.options.logs_paths_in_file, "w")
459                         file_test.close()
460                     except Exception as e:
461                         msg = _("WARNING: the logs_paths_in_file option will "
462                                 "not be taken into account.\nHere is the error:")
463                         logger_command.write("%s\n%s\n\n" % (
464                                              src.printcolors.printcWarning(msg),
465                                              str(e)))
466                         self.options.logs_paths_in_file = None
467
468
469                 # do nothing more if help is True
470                 if self.options.help:
471                   return 0
472
473                 options_launched = ""
474                 res = None
475                 try:
476                     # Execute the hooks (if there is any) 
477                     # and run method of the command
478                     self.run_hook(__nameCmd__, C_PRE_HOOK, logger_command)
479                     res = __module__.run(argv, self, logger_command)
480                     self.run_hook(__nameCmd__, C_POST_HOOK, logger_command)
481                     if res is None:
482                         res = 0
483                         
484                 except Exception as e:
485                     # Get error
486                     logger_command.write("\n***** ", 1)
487                     logger_command.write(src.printcolors.printcError(
488                             "salomeTools ERROR: sat %s" % __nameCmd__), 1)
489
490                     logger_command.write("\n" + DBG.format_exception("") + "\n", 1)
491
492
493                 finally:
494                     # set res if it is not set in the command
495                     if res is None:
496                         res = 1
497                                             
498                     # come back to the original global options
499                     if options:
500                         options_launched = get_text_from_options(self.options)
501                         self.options = options_save
502                     
503                     # come back in the original batch mode if 
504                     # batch argument was called
505                     if batch:
506                         self.options.__setattr__("batch", batch_save)
507
508                     # come back in the original verbose mode if 
509                     # verbose argument was called                        
510                     if verbose > -1:
511                         self.options.__setattr__("output_verbose_level", 
512                                                  verbose_save)
513                     # put final attributes in xml log file 
514                     # (end time, total time, ...) and write it
515                     launchedCommand = ' '.join([self.cfg.VARS.salometoolsway +
516                                                 os.path.sep +
517                                                 'sat',
518                                                 options_launched,
519                                                 __nameCmd__, 
520                                                 ' '.join(argv_0)])
521                     # TODO may be no need as call escapeSequence xml
522                     launchedCommand = launchedCommand.replace('"', "'")
523                     
524                     # Add a link to the parent command      
525                     if logger_add_link is not None:
526                         logger_add_link.add_link(logger_command.logFileName,
527                                                  __nameCmd__,
528                                                  res,
529                                                  launchedCommand)
530                         logger_add_link.l_logFiles += logger_command.l_logFiles
531                                             
532                     # Put the final attributes corresponding to end time and
533                     # Write the file to the hard drive
534                     logger_command.end_write(
535                                         {"launchedCommand" : launchedCommand})
536                     
537                     if res != 0:
538                         res = 1
539                         
540                     # print the log file path if 
541                     # the maximum verbose mode is invoked
542                     if not micro_command:
543                         logger_command.write("\nPath to the xml log file :\n",
544                                              5)
545                         logger_command.write("%s\n\n" % src.printcolors.printcInfo(
546                                                 logger_command.logFilePath), 5)
547
548                     # If the logs_paths_in_file was called, write the result
549                     # and log files in the given file path
550                     if self.options.logs_paths_in_file and not micro_command:
551                         file_res = open(self.options.logs_paths_in_file, "w")
552                         file_res.write(str(res) + "\n")
553                         for i, filepath in enumerate(logger_command.l_logFiles):
554                             file_res.write(filepath)
555                             if i < len(logger_command.l_logFiles):
556                                 file_res.write("\n")
557                                 file_res.flush()
558                 
559                 return res
560
561             # Make sure that run_command will be redefined 
562             # at each iteration of the loop
563             globals_up = {}
564             globals_up.update(run_command.__globals__)
565             globals_up.update({'__nameCmd__': nameCmd, '__module__' : module})
566             func = types.FunctionType(run_command.__code__,
567                                       globals_up,
568                                       run_command.__name__,
569                                       run_command.__defaults__,
570                                       run_command.__closure__)
571
572             # set the attribute corresponding to the command
573             self.__setattr__(nameCmd, func)
574
575     def run_hook(self, cmd_name, hook_type, logger):
576         '''Execute a hook file for a given command regarding the fact 
577            it is pre or post
578         
579         :param cmd_name str: The the command on which execute the hook
580         :param hook_type str: pre or post
581         :param logger Logger: the logging instance to use for the prints
582         '''
583         # The hooks must be defined in the application pyconf
584         # So, if there is no application, do not do anything
585         if not src.config_has_application(self.cfg):
586             return
587
588         # The hooks must be defined in the application pyconf in the
589         # APPLICATION section, hook : { command : 'script_path.py'}
590         if "hook" not in self.cfg.APPLICATION \
591                     or cmd_name not in self.cfg.APPLICATION.hook:
592             return
593
594         # Get the hook_script path and verify that it exists
595         hook_script_path = self.cfg.APPLICATION.hook[cmd_name]
596         if not os.path.exists(hook_script_path):
597             raise src.SatException(_("Hook script not found: %s") % 
598                                    hook_script_path)
599         
600         # Try to execute the script, catch the exception if it fails
601         try:
602             # import the module (in the sense of python)
603             pymodule = imp.load_source(cmd_name, hook_script_path)
604             
605             # format a message to be printed at hook execution
606             msg = src.printcolors.printcWarning(_("Run hook script"))
607             msg = "%s: %s\n" % (msg, 
608                                 src.printcolors.printcInfo(hook_script_path))
609             
610             # run the function run_pre_hook if this function is called 
611             # before the command, run_post_hook if it is called after
612             if hook_type == C_PRE_HOOK and "run_pre_hook" in dir(pymodule):
613                 logger.write(msg, 1)
614                 pymodule.run_pre_hook(self.cfg, logger)
615             elif hook_type == C_POST_HOOK and "run_post_hook" in dir(pymodule):
616                 logger.write(msg, 1)
617                 pymodule.run_post_hook(self.cfg, logger)
618
619         except Exception as exc:
620             msg = _("Unable to run hook script: %s") % hook_script_path
621             msg += "\n" + str(exc)
622             raise src.SatException(msg)
623
624     def get_help(self, opt):
625         '''Prints help for a command. Function called when "sat -h <command>"
626         
627         :param argv str: the options passed (to get the command name)
628         '''
629         # if no command as argument (sat -h)
630         if len(opt)==0:
631             return get_help()
632         # get command name
633         command = opt[0]
634         # read the configuration from all the pyconf files
635         cfgManager = CONFIG.ConfigManager()
636         self.cfg = cfgManager.get_config(datadir=self.datadir)
637
638         # Check if this command exists
639         if not hasattr(self, command):
640             raise src.SatException(_("Command '%s' does not exist") % command)
641         
642         # Print salomeTools version
643         msg = "\n" + get_version() + "\n\n"
644         
645         # load the module
646         module = self.get_module(command)
647
648         # print the description of the command that is done in the command file
649         if hasattr( module, "description" ) :
650             msg += src.printcolors.printcHeader( _("Description:") )
651             msg += '\n' + module.description() + '\n\n'
652
653         # print the description of the command options
654         if hasattr( module, "parser" ):
655             msg += module.parser.get_help()
656
657         msg += "\n -h, --help (boolean)\n          shows help on command.\n"
658         return msg
659
660     def get_module(self, module):
661         '''Loads a command. Function called only by print_help
662         
663         :param module str: the command to load
664         '''
665         # Check if this command exists
666         if not hasattr(self, module):
667             raise src.SatException(_("Command '%s' does not exist") % module)
668
669         # load the module
670         (file_, pathname, description) = imp.find_module(module, [cmdsdir])
671         module = imp.load_module(module, file_, pathname, description)
672         return module
673
674 ##################################################################
675 def get_text_from_options(options):
676     text_options = ""
677     for attr in dir(options):
678         if attr.startswith("__"):
679             continue
680         if options.__getattr__(attr) != None:
681             option_contain = options.__getattr__(attr)
682             if type(option_contain)==type([]):
683                 option_contain = ",".join(option_contain)
684             if type(option_contain)==type(True):
685                 option_contain = ""
686             text_options+= "--%s %s " % (attr, option_contain)
687     return text_options
688                 
689
690 def get_version():
691     '''
692     get colorized salomeTools version (in src/internal_config/salomeTools.pyconf).
693     returns string
694     '''
695     # read the config 
696     cfgManager = CONFIG.ConfigManager()
697     cfg = cfgManager.get_config()
698     # print the key corresponding to salomeTools version
699     msg = (src.printcolors.printcHeader( _("Version: ") ) + cfg.INTERNAL.sat_version)
700     return msg
701
702
703 def get_help():
704     '''
705     get salomeTools general help.
706     returns string
707     '''
708     msg = "\n" + get_version() + "\n\n"
709     msg += src.printcolors.printcHeader( _("Usage: ") ) + \
710           "sat [sat_options] <command> [product] [command_options]\n\n"
711
712     msg += parser.get_help() + "\n"
713
714     # display all the available commands.
715     msg += src.printcolors.printcHeader(_("Available commands are:")) + "\n"
716     for command in lCommand:
717         msg += " - %s\n" % (command)
718
719     msg += "\n"
720     # Explain how to get the help for a specific command
721     msg += src.printcolors.printcHeader(
722         _("Get help for a specific command:")) + \
723         "\n>> sat --help <command>\n"
724     return msg
725
726 def write_exception(exc):
727     '''write exception in case of error in a command
728     
729     :param exc exception: the exception to print
730     '''
731     sys.stderr.write("\n***** ")
732     sys.stderr.write(src.printcolors.printcError("salomeTools ERROR:"))
733     sys.stderr.write("\n" + str(exc) + "\n")
734
735