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