Salome HOME
fix sat#13081 'sat package -t'
[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         self.arguments = opt
238         self.options = options # the generic options passed to salomeTools
239         self.remaindersArgs = remaindersArgs  # the command and their options
240         self.datadir = datadir # default value will be <salomeTools root>/data
241         self._setCommands(cmdsdir)
242
243     def getConfig(self):
244         return self.cfg
245
246     ##################################################################
247     def execute_cli(self, args):
248         """
249         assume launch command from args, pyconf config known yet
250         """
251         argList = self.assumeAsList(args)
252         # no arguments : print general help
253         if len(argList) == 0:
254           self.mainLogger.info(get_help())
255           return RCO.ReturnCode("OK", "no args as sat --help")
256
257         self.setInternals(opt=argList, datadir=None)
258
259         # print general help on -h
260         if self.options.help and len(self.remaindersArgs) == 0:
261           self.mainLogger.info(get_help())
262           return RCO.ReturnCode("OK", "help done")
263
264         DBG.write("options", self.options)
265         DBG.write("remaindersArgs", self.remaindersArgs)
266
267         if len(self.remaindersArgs) == 0:
268           return RCO.ReturnCode("KO", "Nothing to do")
269
270         # print command help on -h --help after name command
271         if "-h" in self.remaindersArgs or "--help" in self.remaindersArgs:
272           self.mainLogger.info(self.get_help(self.remaindersArgs))
273           return RCO.ReturnCode("OK", "sat --help command")
274
275         # print command help on -h and continue if something do do more
276         if self.options.help and len(self.remaindersArgs) >= 1:
277           self.mainLogger.info(self.get_help(self.remaindersArgs))
278
279         command = self.remaindersArgs[0]
280         # get dynamically the command function to call
281         fun_command = self.__getattr__(command)
282         # Run the command using the arguments
283         code = fun_command(self.remaindersArgs[1:])
284
285         if code is None: code = 0 # what?! do not know why so respect history
286
287         # return salomeTools command with the right message
288         # code (0 if no errors, else 1)
289         if code == _KOSYS:
290           return RCO.ReturnCode("KO", "problem on execute_cli 'sat %s'" % " ".join(argList))
291         else:
292           return RCO.ReturnCode("OK", "execute_cli 'sat %s' done" % " ".join(argList))
293
294     '''
295     # OBSOLETE... see file ../sat
296     # ###############################
297     # MAIN : terminal command usage #
298     # ###############################
299     if __name__ == "__main__":  
300         # Initialize the code that will be returned by the terminal command 
301         code = 0
302         (options, args) = parser.parse_args(sys.argv[1:])
303
304         # no arguments : print general help
305         if len(args) == 0:
306             print_help()
307             sys.exit(0)
308
309         # instantiate the salomeTools class with correct options
310         sat = Sat(sys.argv[1:])
311         # the command called
312         command = args[0]
313         # get dynamically the command function to call
314         fun_command = sat.__getattr__(command)
315         # Run the command using the arguments
316         code = fun_command(args[1:])
317
318         # exit salomeTools with the right code (0 if no errors, else 1)
319         if code is None: code = 0
320         sys.exit(code)
321
322     '''
323
324     def __getattr__(self, name):
325         '''
326         overwrite of __getattr__ function in order to display
327         a customized message in case of a wrong call
328         
329         :param name str: The name of the attribute 
330         '''
331         if name in self.__dict__:
332             return self.__dict__[name]
333         else:
334             raise AttributeError(name + _(" is not a valid command"))
335
336     def assumeAsList(self, strOrList):
337         # DBG.write("Sat assumeAsList", strOrList, True)
338         return assumeAsList(strOrList)
339     
340     def _setCommands(self, dirPath):
341         '''set class attributes corresponding to all commands that are 
342            in the dirPath directory
343         
344         :param dirPath str: The directory path containing the commands 
345         '''
346         # loop on the commands name
347         for nameCmd in lCommand:
348             DBG.write("load module command '%s.py'" % nameCmd, "")
349             # Exception for the jobs command that requires the paramiko module
350             if nameCmd == "jobs":
351                 try:
352                     saveout = sys.stderr
353                     ff = tempfile.TemporaryFile()
354                     sys.stderr = ff
355                     import paramiko
356                     sys.stderr = saveout
357                 except:
358                     sys.stderr = saveout
359                     continue
360
361             # load the module that has name nameCmd in dirPath
362             (file_, pathname, description) = imp.find_module(nameCmd, [dirPath])
363             module = imp.load_module(nameCmd, file_, pathname, description)
364             
365             def run_command(args='',
366                             options=None,
367                             batch = False,
368                             verbose = -1,
369                             logger_add_link = None):
370                 '''
371                 The function that will load the configuration (all pyconf)
372                 and return the function run of the command corresponding to module
373                 
374                 :param args str: The arguments of the command 
375                 '''
376                 # Make sure the internationalization is available
377                 gettext.install('salomeTools', os.path.join(satdir, 'src', 'i18n'))
378                 
379                 # Get the arguments in a list and remove the empty elements
380                 if type(args) == type(''):
381                     # split by spaces without considering spaces in quotes
382                     argv_0 = re.findall(r'(?:"[^"]*"|[^\s"])+', args)
383                 else:
384                     argv_0 = args
385                 
386                 if argv_0 != ['']:
387                     while "" in argv_0: argv_0.remove("")
388                 
389                 # Format the argv list in order to prevent strings 
390                 # that contain a blank to be separated
391                 argv = []
392                 elem_old = ""
393                 for elem in argv_0:
394                     if argv == [] or elem_old.startswith("-") or elem.startswith("-"):
395                         argv.append(elem)
396                     else:
397                         argv[-1] += " " + elem
398                     elem_old = elem
399                            
400                 # if it is provided by the command line, get the application
401                 appliToLoad = None
402                 if argv not in [[''], []] and argv[0][0] != "-":
403                     appliToLoad = argv[0].rstrip('*')
404                     argv = argv[1:]
405                 
406                 # Check if the global options of salomeTools have to be changed
407                 if options:
408                     options_save = self.options
409                     self.options = options  
410
411                 # read the configuration from all the pyconf files    
412                 cfgManager = CONFIG.ConfigManager()
413                 self.cfg = cfgManager.get_config(datadir=self.datadir, 
414                                                  application=appliToLoad, 
415                                                  options=self.options, 
416                                                  command=__nameCmd__)
417                                
418                 # Set the verbose mode if called
419                 if verbose > -1:
420                     verbose_save = self.options.output_verbose_level
421                     self.options.__setattr__("output_verbose_level", verbose)    
422
423                 # Set batch mode if called
424                 if batch:
425                     batch_save = self.options.batch
426                     self.options.__setattr__("batch", True)
427
428                 # set output level
429                 if self.options.output_verbose_level is not None:
430                     self.cfg.USER.output_verbose_level = self.options.output_verbose_level
431                 if self.cfg.USER.output_verbose_level < 1:
432                     self.cfg.USER.output_verbose_level = 0
433                 silent = (self.cfg.USER.output_verbose_level == 0)
434
435                 # create log file
436                 micro_command = False
437                 if logger_add_link:
438                     micro_command = True
439                 logger_command = src.logger.Logger(self.cfg,
440                                    silent_sysstd=silent,
441                                    all_in_terminal=self.options.all_in_terminal,
442                                    micro_command=micro_command)
443                 
444                 # Check that the path given by the logs_paths_in_file option
445                 # is a file path that can be written
446                 if self.options.logs_paths_in_file and not micro_command:
447                     try:
448                         self.options.logs_paths_in_file = os.path.abspath(
449                                                 self.options.logs_paths_in_file)
450                         dir_file = os.path.dirname(self.options.logs_paths_in_file)
451                         if not os.path.exists(dir_file):
452                             os.makedirs(dir_file)
453                         if os.path.exists(self.options.logs_paths_in_file):
454                             os.remove(self.options.logs_paths_in_file)
455                         file_test = open(self.options.logs_paths_in_file, "w")
456                         file_test.close()
457                     except Exception as e:
458                         msg = _("WARNING: the logs_paths_in_file option will "
459                                 "not be taken into account.\nHere is the error:")
460                         logger_command.write("%s\n%s\n\n" % (
461                                              src.printcolors.printcWarning(msg),
462                                              str(e)))
463                         self.options.logs_paths_in_file = None
464
465
466                 # do nothing more if help is True
467                 if self.options.help:
468                   return 0
469
470                 options_launched = ""
471                 res = None
472                 try:
473                     # Execute the hooks (if there is any) 
474                     # and run method of the command
475                     self.run_hook(__nameCmd__, C_PRE_HOOK, logger_command)
476                     res = __module__.run(argv, self, logger_command)
477                     self.run_hook(__nameCmd__, C_POST_HOOK, logger_command)
478                     if res is None:
479                         res = 0
480                         
481                 except Exception as e:
482                     # Get error
483                     logger_command.write("\n***** ", 1)
484                     logger_command.write(src.printcolors.printcError( "salomeTools ERROR: sat %s" % __nameCmd__), 1)
485                     logger_command.write("\n" + str(e) + "\n\n", 1)
486                     # get stack
487                     __, __, exc_traceback = sys.exc_info()
488                     fp = tempfile.TemporaryFile()
489                     traceback.print_tb(exc_traceback, file=fp)
490                     fp.seek(0)
491                     stack = fp.read()
492                     verbosity = 5
493                     if self.options.debug_mode:
494                         verbosity = 1
495                     logger_command.write("TRACEBACK: %s" % stack.replace('"',"'"), verbosity)
496                 finally:
497                     # set res if it is not set in the command
498                     if res is None:
499                         res = 1
500                                             
501                     # come back to the original global options
502                     if options:
503                         options_launched = get_text_from_options(self.options)
504                         self.options = options_save
505                     
506                     # come back in the original batch mode if 
507                     # batch argument was called
508                     if batch:
509                         self.options.__setattr__("batch", batch_save)
510
511                     # come back in the original verbose mode if 
512                     # verbose argument was called                        
513                     if verbose > -1:
514                         self.options.__setattr__("output_verbose_level", 
515                                                  verbose_save)
516                     # put final attributes in xml log file 
517                     # (end time, total time, ...) and write it
518                     launchedCommand = ' '.join([self.cfg.VARS.salometoolsway +
519                                                 os.path.sep +
520                                                 'sat',
521                                                 options_launched,
522                                                 __nameCmd__, 
523                                                 ' '.join(argv_0)])
524                     # TODO may be no need as call escapeSequence xml
525                     launchedCommand = launchedCommand.replace('"', "'")
526                     
527                     # Add a link to the parent command      
528                     if logger_add_link is not None:
529                         logger_add_link.add_link(logger_command.logFileName,
530                                                  __nameCmd__,
531                                                  res,
532                                                  launchedCommand)
533                         logger_add_link.l_logFiles += logger_command.l_logFiles
534                                             
535                     # Put the final attributes corresponding to end time and
536                     # Write the file to the hard drive
537                     logger_command.end_write(
538                                         {"launchedCommand" : launchedCommand})
539                     
540                     if res != 0:
541                         res = 1
542                         
543                     # print the log file path if 
544                     # the maximum verbose mode is invoked
545                     if not micro_command:
546                         logger_command.write("\nPath to the xml log file :\n",
547                                              5)
548                         logger_command.write("%s\n\n" % src.printcolors.printcInfo(
549                                                 logger_command.logFilePath), 5)
550
551                     # If the logs_paths_in_file was called, write the result
552                     # and log files in the given file path
553                     if self.options.logs_paths_in_file and not micro_command:
554                         file_res = open(self.options.logs_paths_in_file, "w")
555                         file_res.write(str(res) + "\n")
556                         for i, filepath in enumerate(logger_command.l_logFiles):
557                             file_res.write(filepath)
558                             if i < len(logger_command.l_logFiles):
559                                 file_res.write("\n")
560                                 file_res.flush()
561                 
562                 return res
563
564             # Make sure that run_command will be redefined 
565             # at each iteration of the loop
566             globals_up = {}
567             globals_up.update(run_command.__globals__)
568             globals_up.update({'__nameCmd__': nameCmd, '__module__' : module})
569             func = types.FunctionType(run_command.__code__,
570                                       globals_up,
571                                       run_command.__name__,
572                                       run_command.__defaults__,
573                                       run_command.__closure__)
574
575             # set the attribute corresponding to the command
576             self.__setattr__(nameCmd, func)
577
578     def run_hook(self, cmd_name, hook_type, logger):
579         '''Execute a hook file for a given command regarding the fact 
580            it is pre or post
581         
582         :param cmd_name str: The the command on which execute the hook
583         :param hook_type str: pre or post
584         :param logger Logger: the logging instance to use for the prints
585         '''
586         # The hooks must be defined in the application pyconf
587         # So, if there is no application, do not do anything
588         if not src.config_has_application(self.cfg):
589             return
590
591         # The hooks must be defined in the application pyconf in the
592         # APPLICATION section, hook : { command : 'script_path.py'}
593         if "hook" not in self.cfg.APPLICATION \
594                     or cmd_name not in self.cfg.APPLICATION.hook:
595             return
596
597         # Get the hook_script path and verify that it exists
598         hook_script_path = self.cfg.APPLICATION.hook[cmd_name]
599         if not os.path.exists(hook_script_path):
600             raise src.SatException(_("Hook script not found: %s") % 
601                                    hook_script_path)
602         
603         # Try to execute the script, catch the exception if it fails
604         try:
605             # import the module (in the sense of python)
606             pymodule = imp.load_source(cmd_name, hook_script_path)
607             
608             # format a message to be printed at hook execution
609             msg = src.printcolors.printcWarning(_("Run hook script"))
610             msg = "%s: %s\n" % (msg, 
611                                 src.printcolors.printcInfo(hook_script_path))
612             
613             # run the function run_pre_hook if this function is called 
614             # before the command, run_post_hook if it is called after
615             if hook_type == C_PRE_HOOK and "run_pre_hook" in dir(pymodule):
616                 logger.write(msg, 1)
617                 pymodule.run_pre_hook(self.cfg, logger)
618             elif hook_type == C_POST_HOOK and "run_post_hook" in dir(pymodule):
619                 logger.write(msg, 1)
620                 pymodule.run_post_hook(self.cfg, logger)
621
622         except Exception as exc:
623             msg = _("Unable to run hook script: %s") % hook_script_path
624             msg += "\n" + str(exc)
625             raise src.SatException(msg)
626
627     def get_help(self, opt):
628         '''Prints help for a command. Function called when "sat -h <command>"
629         
630         :param argv str: the options passed (to get the command name)
631         '''
632         # if no command as argument (sat -h)
633         if len(opt)==0:
634             return get_help()
635         # get command name
636         command = opt[0]
637         # read the configuration from all the pyconf files
638         cfgManager = CONFIG.ConfigManager()
639         self.cfg = cfgManager.get_config(datadir=self.datadir)
640
641         # Check if this command exists
642         if not hasattr(self, command):
643             raise src.SatException(_("Command '%s' does not exist") % command)
644         
645         # Print salomeTools version
646         msg = "\n" + get_version() + "\n\n"
647         
648         # load the module
649         module = self.get_module(command)
650
651         # print the description of the command that is done in the command file
652         if hasattr( module, "description" ) :
653             msg += src.printcolors.printcHeader( _("Description:") )
654             msg += '\n' + module.description() + '\n\n'
655
656         # print the description of the command options
657         if hasattr( module, "parser" ):
658             msg += module.parser.get_help()
659
660         msg += "\n -h, --help (boolean)\n          shows help on command.\n"
661         return msg
662
663     def get_module(self, module):
664         '''Loads a command. Function called only by print_help
665         
666         :param module str: the command to load
667         '''
668         # Check if this command exists
669         if not hasattr(self, module):
670             raise src.SatException(_("Command '%s' does not exist") % module)
671
672         # load the module
673         (file_, pathname, description) = imp.find_module(module, [cmdsdir])
674         module = imp.load_module(module, file_, pathname, description)
675         return module
676
677 ##################################################################
678 def get_text_from_options(options):
679     text_options = ""
680     for attr in dir(options):
681         if attr.startswith("__"):
682             continue
683         if options.__getattr__(attr) != None:
684             option_contain = options.__getattr__(attr)
685             if type(option_contain)==type([]):
686                 option_contain = ",".join(option_contain)
687             if type(option_contain)==type(True):
688                 option_contain = ""
689             text_options+= "--%s %s " % (attr, option_contain)
690     return text_options
691                 
692
693 def get_version():
694     '''
695     get colorized salomeTools version (in src/internal_config/salomeTools.pyconf).
696     returns string
697     '''
698     # read the config 
699     cfgManager = CONFIG.ConfigManager()
700     cfg = cfgManager.get_config()
701     # print the key corresponding to salomeTools version
702     msg = (src.printcolors.printcHeader( _("Version: ") ) + cfg.INTERNAL.sat_version)
703     return msg
704
705
706 def get_help():
707     '''
708     get salomeTools general help.
709     returns string
710     '''
711     msg = "\n" + get_version() + "\n\n"
712     msg += src.printcolors.printcHeader( _("Usage: ") ) + \
713           "sat [sat_options] <command> [product] [command_options]\n\n"
714
715     msg += parser.get_help() + "\n"
716
717     # display all the available commands.
718     msg += src.printcolors.printcHeader(_("Available commands are:")) + "\n"
719     for command in lCommand:
720         msg += " - %s\n" % (command)
721
722     msg += "\n"
723     # Explain how to get the help for a specific command
724     msg += src.printcolors.printcHeader(
725         _("Get help for a specific command:")) + \
726         "\n>> sat --help <command>\n"
727     return msg
728
729 def write_exception(exc):
730     '''write exception in case of error in a command
731     
732     :param exc exception: the exception to print
733     '''
734     sys.stderr.write("\n***** ")
735     sys.stderr.write(src.printcolors.printcError("salomeTools ERROR:"))
736     sys.stderr.write("\n" + str(exc) + "\n")
737
738