Salome HOME
scs #13189: report des modifications pour que SAT fonctionne sous windows
[tools/sat.git] / commands / config.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 import os
20 import sys
21 import platform
22 import datetime
23 import shutil
24 import gettext
25 import pprint as PP
26
27 import src
28 import src.logger as LOG
29 import src.debug as DBG
30 import src.callerName as CALN
31
32 logger = LOG.getDefaultLogger()
33
34 verbose = False # True for debug
35
36 # internationalization
37 satdir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
38 gettext.install('salomeTools', os.path.join(satdir, 'src', 'i18n'))
39
40 # Define all possible option for config command :  sat config <options>
41 parser = src.options.Options()
42 parser.add_option('v', 'value', 'string', 'value',
43     _("Optional: print the value of CONFIG_VARIABLE."))
44 parser.add_option('g', 'debug', 'string', 'debug',
45     _("Optional: print the debugging mode value of CONFIG_VARIABLE."))
46 parser.add_option('e', 'edit', 'boolean', 'edit',
47     _("Optional: edit the product configuration file."))
48 parser.add_option('i', 'info', 'list2', 'info',
49     _("Optional: get information on product(s). This option accepts a comma separated list."))
50 parser.add_option('p', 'products', 'list2', 'products',
51     _("Optional: same as --info, for convenience."))
52 parser.add_option('l', 'list', 'boolean', 'list',
53     _("Optional: list all available applications."))
54 parser.add_option('', 'show_patchs', 'boolean', 'show_patchs',
55     _("Optional: synthetic list of all patches used in the application"))
56 parser.add_option('', 'show_install', 'boolean', 'show_install',
57     _("Optional: synthetic list of all install directories in the application"))
58 parser.add_option('', 'show_properties', 'boolean', 'show_properties',
59     _("Optional: synthetic list of all properties used in the application"))
60 parser.add_option('c', 'copy', 'boolean', 'copy',
61     _("""Optional: copy a config file to the personal config files directory.
62 WARNING: the included files are not copied.
63 If a name is given the new config file takes the given name."""))
64 parser.add_option('n', 'no_label', 'boolean', 'no_label',
65     _("Internal use: do not print labels, Works only with --value and --list."))
66 parser.add_option('', 'completion', 'boolean', 'completion',
67     _("Internal use: print only keys, works only with --value."))
68 parser.add_option('s', 'schema', 'boolean', 'schema',
69     _("Internal use."))
70
71 def osJoin(*args):
72   """
73   shortcut wrapper to os.path.join
74   plus optionaly print for debug
75   """
76   res = os.path.realpath(os.path.join(*args))
77   if verbose:
78     if True: # ".pyconf" in res:
79       logger.info("osJoin %-80s in %s" % (res, CALN.caller_name(1)))
80   return res
81
82 class ConfigOpener:
83     '''Class that helps to find an application pyconf 
84        in all the possible directories (pathList)
85     '''
86     def __init__(self, pathList):
87         '''Initialization
88         
89         :param pathList list: The list of paths where to search a pyconf.
90         '''
91         self.pathList = pathList
92         if verbose:
93           for path in pathList:
94             if not os.path.isdir(path):
95               logger.warning("ConfigOpener inexisting directory: %s" % path)
96
97     def __call__(self, name):
98         if os.path.isabs(name):
99             return src.pyconf.ConfigInputStream(open(name, 'rb'))
100         else:
101             return src.pyconf.ConfigInputStream(open(osJoin(self.get_path(name), name), 'rb'))
102         raise IOError(_("Configuration file '%s' not found") % name)
103
104     def get_path( self, name ):
105         '''The method that returns the entire path of the pyconf searched
106         returns first found in self.pathList directories
107
108         :param name str: The name of the searched pyconf.
109         '''
110         for path in self.pathList:
111             if os.path.exists(osJoin(path, name)):
112                 return path
113         raise IOError(_("Configuration file '%s' not found") % name)
114
115 class ConfigManager:
116     '''Class that manages the read of all the configuration files of salomeTools
117     '''
118     def __init__(self, datadir=None):
119         pass
120
121     def _create_vars(self, application=None, command=None, datadir=None):
122         '''Create a dictionary that stores all information about machine,
123            user, date, repositories, etc...
124         
125         :param application str: The application for which salomeTools is called.
126         :param command str: The command that is called.
127         :param datadir str: The repository that contain external data 
128                             for salomeTools.
129         :return: The dictionary that stores all information.
130         :rtype: dict
131         '''
132         var = {}      
133         var['user'] = src.architecture.get_user()
134         var['salometoolsway'] = os.path.dirname( os.path.dirname(os.path.abspath(__file__)))
135         var['srcDir'] =  osJoin(var['salometoolsway'], 'src')
136         var['internal_dir'] =  osJoin(var['srcDir'], 'internal_config')
137         var['sep']= os.path.sep
138         if src.architecture.is_windows():
139           var['scriptExtension'] = '.bat'
140         else:
141           var['scriptExtension'] = '.sh'
142         
143         # datadir has a default location
144         var['datadir'] =  osJoin(var['salometoolsway'], 'data')
145         if datadir is not None:
146             var['datadir'] = datadir
147
148         var['personalDir'] =  osJoin(os.path.expanduser('~'), '.salomeTools')
149         src.ensure_path_exists(var['personalDir'])
150
151         var['personal_applications_dir'] =  osJoin(var['personalDir'], "Applications")
152         src.ensure_path_exists(var['personal_applications_dir'])
153         
154         var['personal_products_dir'] =  osJoin(var['personalDir'], "products")
155         src.ensure_path_exists(var['personal_products_dir'])
156         
157         var['personal_archives_dir'] =  osJoin(var['personalDir'], "Archives")
158         src.ensure_path_exists(var['personal_archives_dir'])
159
160         var['personal_jobs_dir'] =  osJoin(var['personalDir'], "Jobs")
161         src.ensure_path_exists(var['personal_jobs_dir'])
162
163         var['personal_machines_dir'] =  osJoin(var['personalDir'], "Machines")
164         src.ensure_path_exists(var['personal_machines_dir'])
165
166         # read linux distributions dictionary
167         distrib_cfg = src.pyconf.Config( osJoin(var['srcDir'], 'internal_config', 'distrib.pyconf'))
168         
169         # set platform parameters
170         dist_name = src.architecture.get_distribution(codes=distrib_cfg.DISTRIBUTIONS)
171         dist_version = src.architecture.get_distrib_version(dist_name)
172         dist_version_full = src.architecture.get_version_XY()
173         dist = dist_name + dist_version
174         
175         var['dist_name'] = dist_name
176         var['dist_version'] = dist_version
177         var['dist'] = dist
178         var['dist_ref'] = dist_name + dist_version_full
179         var['python'] = src.architecture.get_python_version()
180
181         var['nb_proc'] = src.architecture.get_nb_proc()
182         node_name = platform.node()
183         var['node'] = node_name
184         var['hostname'] = node_name
185
186         # set date parameters
187         dt = datetime.datetime.now()
188         var['date'] = dt.strftime('%Y%m%d')
189         var['datehour'] = dt.strftime('%Y%m%d_%H%M%S')
190         var['hour'] = dt.strftime('%H%M%S')
191
192         var['command'] = str(command)
193         var['application'] = str(application)
194
195         # Root dir for temporary files 
196         var['tmp_root'] = os.sep + 'tmp' + os.sep + var['user']
197         # particular win case 
198         if src.architecture.is_windows() : 
199             var['tmp_root'] =  os.path.expanduser('~') + os.sep + 'tmp'
200         
201         return var
202
203     def get_command_line_overrides(self, options, sections):
204         '''get all the overwrites that are in the command line
205         
206         :param options: the options from salomeTools class 
207                         initialization (like -l5 or --overwrite)
208         :param sections str: The config section to overwrite.
209         :return: The list of all the overwrites to apply.
210         :rtype: list
211         '''
212         # when there are no options or not the overwrite option, 
213         # return an empty list
214         if options is None or options.overwrite is None:
215             return []
216         
217         over = []
218         for section in sections:
219             # only overwrite the sections that correspond to the option 
220             over.extend(filter(lambda l: l.startswith(section + "."), 
221                                options.overwrite))
222         return over
223
224     def get_config(self, application=None, options=None, command=None,
225                     datadir=None):
226         '''get the config from all the configuration files.
227         
228         :param application str: The application for which salomeTools is called.
229         :param options class Options: The general salomeToos
230                                       options (--overwrite or -l5, for example)
231         :param command str: The command that is called.
232         :param datadir str: The repository that contain 
233                             external data for salomeTools.
234         :return: The final config.
235         :rtype: class 'src.pyconf.Config'
236         '''        
237         
238         # create a ConfigMerger to handle merge
239         merger = src.pyconf.ConfigMerger()#MergeHandler())
240         
241         # create the configuration instance
242         cfg = src.pyconf.Config()
243         
244         # =====================================================================
245         # create VARS section
246         var = self._create_vars(application=application, command=command, datadir=datadir)
247         # DBG.write("create_vars", var, DBG.isDeveloper())
248
249         # add VARS to config
250         cfg.VARS = src.pyconf.Mapping(cfg)
251         for variable in var:
252             cfg.VARS[variable] = var[variable]
253         
254         # apply overwrite from command line if needed
255         for rule in self.get_command_line_overrides(options, ["VARS"]):
256             exec('cfg.' + rule) # this cannot be factorized because of the exec
257         
258         # =====================================================================
259         # Load INTERNAL config
260         # read src/internal_config/salomeTools.pyconf
261         src.pyconf.streamOpener = ConfigOpener([
262                              osJoin(cfg.VARS.srcDir, 'internal_config')])
263         try:
264             internal_cfg = src.pyconf.Config(open( osJoin(cfg.VARS.srcDir,
265                                     'internal_config', 'salomeTools.pyconf')))
266         except src.pyconf.ConfigError as e:
267             raise src.SatException(_("Error in configuration file:"
268                                      " salomeTools.pyconf\n  %(error)s") % \
269                                    {'error': str(e) })
270         
271         merger.merge(cfg, internal_cfg)
272
273         # apply overwrite from command line if needed
274         for rule in self.get_command_line_overrides(options, ["INTERNAL"]):
275             exec('cfg.' + rule) # this cannot be factorized because of the exec        
276                
277         # =====================================================================
278         # Load LOCAL config file
279         # search only in the data directory
280         src.pyconf.streamOpener = ConfigOpener([cfg.VARS.datadir])
281         try:
282             local_cfg = src.pyconf.Config(open( osJoin(cfg.VARS.datadir,
283                                                            'local.pyconf')),
284                                          PWD = ('LOCAL', cfg.VARS.datadir) )
285         except src.pyconf.ConfigError as e:
286             raise src.SatException(_("Error in configuration file: "
287                                      "local.pyconf\n  %(error)s") % \
288                 {'error': str(e) })
289         except IOError as error:
290             e = str(error)
291             raise src.SatException( e );
292         merger.merge(cfg, local_cfg)
293
294         # When the key is "default", put the default value
295         if cfg.LOCAL.base == "default":
296             cfg.LOCAL.base = os.path.abspath(osJoin(cfg.VARS.salometoolsway, "..", "BASE"))
297         if cfg.LOCAL.workdir == "default":
298             cfg.LOCAL.workdir = os.path.abspath(osJoin(cfg.VARS.salometoolsway, ".."))
299         if cfg.LOCAL.log_dir == "default":
300             cfg.LOCAL.log_dir = os.path.abspath(osJoin(cfg.VARS.salometoolsway, "..", "LOGS"))
301
302         if cfg.LOCAL.archive_dir == "default":
303             cfg.LOCAL.archive_dir = os.path.abspath( osJoin(cfg.VARS.salometoolsway, "..", "ARCHIVES"))
304
305         # if the sat tag was not set permanently by user
306         if cfg.LOCAL.tag == None:
307             # get the tag with git, and store it
308             sat_version=src.system.git_describe(cfg.VARS.salometoolsway) 
309             if sat_version == False:
310                 sat_version=cfg.INTERNAL.sat_version
311             cfg.LOCAL.tag=sat_version
312                 
313
314         # apply overwrite from command line if needed
315         for rule in self.get_command_line_overrides(options, ["LOCAL"]):
316             exec('cfg.' + rule) # this cannot be factorized because of the exec
317         
318         # =====================================================================
319         # Load the PROJECTS
320         projects_cfg = src.pyconf.Config()
321         projects_cfg.addMapping("PROJECTS",
322                                 src.pyconf.Mapping(projects_cfg),
323                                 "The projects\n")
324         projects_cfg.PROJECTS.addMapping("projects",
325                                 src.pyconf.Mapping(cfg.PROJECTS),
326                                 "The projects definition\n")
327         
328         for project_pyconf_path in cfg.PROJECTS.project_file_paths:
329             if not os.path.isabs(project_pyconf_path):
330                 # for a relative path (archive case) we complete with sat path
331                 project_pyconf_path = os.path.join(cfg.VARS.salometoolsway,
332                                                   project_pyconf_path)
333             if not os.path.exists(project_pyconf_path):
334                 msg = _("WARNING: The project file %s cannot be found. "
335                         "It will be ignored\n" % project_pyconf_path)
336                 sys.stdout.write(msg)
337                 continue
338             project_name = os.path.basename(
339                                     project_pyconf_path)[:-len(".pyconf")]
340             try:
341                 project_pyconf_dir = os.path.dirname(project_pyconf_path)
342                 project_cfg = src.pyconf.Config(open(project_pyconf_path),
343                                                 PWD=("", project_pyconf_dir))
344             except Exception as e:
345                 msg = _("ERROR: Error in configuration file: "
346                                  "%(file_path)s\n  %(error)s\n") % \
347                             {'file_path' : project_pyconf_path, 'error': str(e) }
348                 sys.stdout.write(msg)
349                 continue
350             projects_cfg.PROJECTS.projects.addMapping(project_name,
351                              src.pyconf.Mapping(projects_cfg.PROJECTS.projects),
352                              "The %s project\n" % project_name)
353             projects_cfg.PROJECTS.projects[project_name]=project_cfg
354             projects_cfg.PROJECTS.projects[project_name]["file_path"] = \
355                                                         project_pyconf_path
356             # store the project tag if any
357             product_project_git_tag = src.system.git_describe(os.path.dirname(project_pyconf_path))
358             if product_project_git_tag:
359                 projects_cfg.PROJECTS.projects[project_name]["git_tag"] = product_project_git_tag
360             else:
361                 projects_cfg.PROJECTS.projects[project_name]["git_tag"] = "unknown"
362                    
363         merger.merge(cfg, projects_cfg)
364
365         # apply overwrite from command line if needed
366         for rule in self.get_command_line_overrides(options, ["PROJECTS"]):
367             exec('cfg.' + rule) # this cannot be factorized because of the exec
368         
369         # =====================================================================
370         # Create the paths where to search the application configurations, 
371         # the product configurations, the products archives, 
372         # the jobs configurations and the machines configurations
373         cfg.addMapping("PATHS", src.pyconf.Mapping(cfg), "The paths\n")
374         cfg.PATHS["APPLICATIONPATH"] = src.pyconf.Sequence(cfg.PATHS)
375         cfg.PATHS.APPLICATIONPATH.append(cfg.VARS.personal_applications_dir, "")
376
377         
378         cfg.PATHS["PRODUCTPATH"] = src.pyconf.Sequence(cfg.PATHS)
379         cfg.PATHS.PRODUCTPATH.append(cfg.VARS.personal_products_dir, "")
380         cfg.PATHS["ARCHIVEPATH"] = src.pyconf.Sequence(cfg.PATHS)
381         cfg.PATHS.ARCHIVEPATH.append(cfg.VARS.personal_archives_dir, "")
382         cfg.PATHS["ARCHIVEFTP"] = src.pyconf.Sequence(cfg.PATHS)
383         cfg.PATHS["JOBPATH"] = src.pyconf.Sequence(cfg.PATHS)
384         cfg.PATHS.JOBPATH.append(cfg.VARS.personal_jobs_dir, "")
385         cfg.PATHS["MACHINEPATH"] = src.pyconf.Sequence(cfg.PATHS)
386         cfg.PATHS.MACHINEPATH.append(cfg.VARS.personal_machines_dir, "")
387         cfg.PATHS["LICENCEPATH"] = src.pyconf.Sequence(cfg.PATHS)
388
389         # initialise the path with local directory
390         cfg.PATHS["ARCHIVEPATH"].append(cfg.LOCAL.archive_dir, "")
391
392         # Loop over the projects in order to complete the PATHS variables
393         # as /data/tmpsalome/salome/prerequis/archives for example ARCHIVEPATH
394         for project in cfg.PROJECTS.projects:
395             for PATH in ["APPLICATIONPATH",
396                          "PRODUCTPATH",
397                          "ARCHIVEPATH", #comment this for default archive       #8646
398                          "ARCHIVEFTP",
399                          "JOBPATH",
400                          "MACHINEPATH",
401                          "LICENCEPATH"]:
402                 if PATH not in cfg.PROJECTS.projects[project]:
403                     continue
404                 cfg.PATHS[PATH].append(cfg.PROJECTS.projects[project][PATH], "")
405         
406         # apply overwrite from command line if needed
407         for rule in self.get_command_line_overrides(options, ["PATHS"]):
408             exec('cfg.' + rule) # this cannot be factorized because of the exec
409
410         # AT END append APPLI_TEST directory in APPLICATIONPATH, for unittest
411         appli_test_dir =  osJoin(satdir, "test", "APPLI_TEST")
412         if appli_test_dir not in cfg.PATHS.APPLICATIONPATH:
413           cfg.PATHS.APPLICATIONPATH.append(appli_test_dir, "unittest APPLI_TEST path")
414
415         # =====================================================================
416         # Load APPLICATION config file
417         if application is not None:
418             # search APPLICATION file in all directories in configPath
419             cp = cfg.PATHS.APPLICATIONPATH
420             src.pyconf.streamOpener = ConfigOpener(cp)
421             do_merge = True
422             try:
423                 application_cfg = src.pyconf.Config(application + '.pyconf')
424             except IOError as e:
425                 raise src.SatException(
426                    _("%s, use 'config --list' to get the list of available applications.") % e)
427             except src.pyconf.ConfigError as e:
428                 if (not ('-e' in parser.parse_args()[1]) 
429                                          or ('--edit' in parser.parse_args()[1]) 
430                                          and command == 'config'):
431                     raise src.SatException(_("Error in configuration file: "
432                                              "%(application)s.pyconf\n "
433                                              " %(error)s") % \
434                         { 'application': application, 'error': str(e) } )
435                 else:
436                     sys.stdout.write(src.printcolors.printcWarning(
437                                         "There is an error in the file"
438                                         " %s.pyconf.\n" % cfg.VARS.application))
439                     do_merge = False
440             except Exception as e:
441                 if (not ('-e' in parser.parse_args()[1]) 
442                                         or ('--edit' in parser.parse_args()[1]) 
443                                         and command == 'config'):
444                     sys.stdout.write(src.printcolors.printcWarning("%s\n" % str(e)))
445                     raise src.SatException(_("Error in configuration file:"
446                                              " %(application)s.pyconf\n") % \
447                         { 'application': application} )
448                 else:
449                     sys.stdout.write(src.printcolors.printcWarning(
450                                 "There is an error in the file"
451                                 " %s.pyconf. Opening the file with the"
452                                 " default viewer\n" % cfg.VARS.application))
453                     sys.stdout.write("The error:"
454                                  " %s\n" % src.printcolors.printcWarning(
455                                                                       str(e)))
456                     do_merge = False
457         
458             else:
459                 cfg['open_application'] = 'yes'
460
461         # =====================================================================
462         # Load product config files in PRODUCTS section
463         products_cfg = src.pyconf.Config()
464         products_cfg.addMapping("PRODUCTS",
465                                 src.pyconf.Mapping(products_cfg),
466                                 "The products\n")
467         if application is not None:
468             src.pyconf.streamOpener = ConfigOpener(cfg.PATHS.PRODUCTPATH)
469             for product_name in application_cfg.APPLICATION.products.keys():
470                 # Loop on all files that are in softsDir directory
471                 # and read their config
472                 product_file_name = product_name + ".pyconf"
473                 product_file_path = src.find_file_in_lpath(product_file_name, cfg.PATHS.PRODUCTPATH)
474                 if product_file_path:
475                     products_dir = os.path.dirname(product_file_path)
476                     # for a relative path (archive case) we complete with sat path
477                     if not os.path.isabs(products_dir):
478                         products_dir = os.path.join(cfg.VARS.salometoolsway,
479                                                     products_dir)
480                     try:
481                         prod_cfg = src.pyconf.Config(open(product_file_path),
482                                                      PWD=("", products_dir))
483                         prod_cfg.from_file = product_file_path
484                         products_cfg.PRODUCTS[product_name] = prod_cfg
485                     except Exception as e:
486                         msg = _(
487                             "WARNING: Error in configuration file"
488                             ": %(prod)s\n  %(error)s" % \
489                             {'prod' :  product_name, 'error': str(e) })
490                         sys.stdout.write(msg)
491             
492             merger.merge(cfg, products_cfg)
493             
494             # apply overwrite from command line if needed
495             for rule in self.get_command_line_overrides(options, ["PRODUCTS"]):
496                 exec('cfg.' + rule) # this cannot be factorized because of the exec
497             
498             if do_merge:
499                 merger.merge(cfg, application_cfg)
500
501                 # default launcher name ('salome')
502                 if ('profile' in cfg.APPLICATION and 
503                     'launcher_name' not in cfg.APPLICATION.profile):
504                     cfg.APPLICATION.profile.launcher_name = 'salome'
505
506                 # apply overwrite from command line if needed
507                 for rule in self.get_command_line_overrides(options,
508                                                              ["APPLICATION"]):
509                     # this cannot be factorized because of the exec
510                     exec('cfg.' + rule)
511             
512         # =====================================================================
513         # load USER config
514         self.set_user_config_file(cfg)
515         user_cfg_file = self.get_user_config_file()
516         user_cfg = src.pyconf.Config(open(user_cfg_file))
517         merger.merge(cfg, user_cfg)
518
519         # apply overwrite from command line if needed
520         for rule in self.get_command_line_overrides(options, ["USER"]):
521             exec('cfg.' + rule) # this cannot be factorize because of the exec
522         
523         return cfg
524
525     def set_user_config_file(self, config):
526         '''Set the user config file name and path.
527         If necessary, build it from another one or create it from scratch.
528         
529         :param config class 'src.pyconf.Config': The global config 
530                                                  (containing all pyconf).
531         '''
532         # get the expected name and path of the file
533         self.config_file_name = 'SAT.pyconf'
534         self.user_config_file_path =  osJoin(config.VARS.personalDir, self.config_file_name)
535         
536         # if pyconf does not exist, create it from scratch
537         if not os.path.isfile(self.user_config_file_path): 
538             self.create_config_file(config)
539     
540     def create_config_file(self, config):
541         '''This method is called when there are no user config file. 
542            It build it from scratch.
543         
544         :param config class 'src.pyconf.Config': The global config.
545         :return: the config corresponding to the file created.
546         :rtype: config class 'src.pyconf.Config'
547         '''
548         
549         cfg_name = self.get_user_config_file()
550
551         user_cfg = src.pyconf.Config()
552         #
553         user_cfg.addMapping('USER', src.pyconf.Mapping(user_cfg), "")
554
555         user_cfg.USER.addMapping('cvs_user', config.VARS.user,
556             "This is the user name used to access salome cvs base.\n")
557         user_cfg.USER.addMapping('svn_user', config.VARS.user,
558             "This is the user name used to access salome svn base.\n")
559         user_cfg.USER.addMapping('output_verbose_level', 3,
560             "This is the default output_verbose_level you want."
561             " 0=>no output, 5=>debug.\n")
562         user_cfg.USER.addMapping('publish_dir', 
563                                   osJoin(os.path.expanduser('~'),
564                                  'websupport', 
565                                  'satreport'), 
566                                  "")
567         user_cfg.USER.addMapping('editor',
568                                  'vi', 
569                                  "This is the editor used to "
570                                  "modify configuration files\n")
571         user_cfg.USER.addMapping('browser', 
572                                  'firefox', 
573                                  "This is the browser used to "
574                                  "read html documentation\n")
575         user_cfg.USER.addMapping('pdf_viewer', 
576                                  'evince', 
577                                  "This is the pdf_viewer used "
578                                  "to read pdf documentation\n")
579 # CNC 25/10/17 : plus nécessaire a priori
580 #        user_cfg.USER.addMapping("base",
581 #                                 src.pyconf.Reference(
582 #                                            user_cfg,
583 #                                            src.pyconf.DOLLAR,
584 #                                            'workdir  + $VARS.sep + "BASE"'),
585 #                                 "The products installation base (could be "
586 #                                 "ignored if this key exists in the local.pyconf"
587 #                                 " file of salomTools).\n")
588
589         src.ensure_path_exists(config.VARS.personalDir)
590         src.ensure_path_exists( osJoin(config.VARS.personalDir,
591                                             'Applications'))
592
593         f = open(cfg_name, 'w')
594         user_cfg.__save__(f)
595         f.close()
596
597         return user_cfg   
598
599     def get_user_config_file(self):
600         '''Get the user config file
601         :return: path to the user config file.
602         :rtype: str
603         '''
604         if not self.user_config_file_path:
605             raise src.SatException(_("Error in get_user_config_file: "
606                                      "missing user config file path"))
607         return self.user_config_file_path     
608
609 def check_path(path, ext=[]):
610     '''Construct a text with the input path and "not found" if it does not
611        exist.
612     
613     :param path Str: the path to check.
614     :param ext List: An extension. Verify that the path extension 
615                      is in the list
616     :return: The string of the path with information
617     :rtype: Str
618     '''
619     # check if file exists
620     if not os.path.exists(path):
621         return "'%s'" % path + " " + src.printcolors.printcError(_(
622                                                             "** not found"))
623
624     # check extension
625     if len(ext) > 0:
626         fe = os.path.splitext(path)[1].lower()
627         if fe not in ext:
628             return "'%s'" % path + " " + src.printcolors.printcError(_(
629                                                         "** bad extension"))
630
631     return path
632
633 def show_product_info(config, name, logger):
634     '''Display on the terminal and logger information about a product.
635     
636     :param config Config: the global configuration.
637     :param name Str: The name of the product
638     :param logger Logger: The logger instance to use for the display
639     '''
640     
641     logger.write(_("%s is a product\n") % src.printcolors.printcLabel(name), 2)
642     pinfo = src.product.get_product_config(config, name)
643     
644     if "depend" in pinfo:
645         src.printcolors.print_value(logger, "depends on", sorted(pinfo.depend), 2)
646
647     if "opt_depend" in pinfo:
648         src.printcolors.print_value(logger, "optional", sorted(pinfo.opt_depend), 2)
649
650     # information on pyconf
651     logger.write("\n", 2)
652     logger.write(src.printcolors.printcLabel("configuration:") + "\n", 2)
653     if "from_file" in pinfo:
654         src.printcolors.print_value(logger,
655                                     "pyconf file path",
656                                     pinfo.from_file,
657                                     2)
658     if "section" in pinfo:
659         src.printcolors.print_value(logger,
660                                     "section",
661                                     pinfo.section,
662                                     2)
663
664     # information on prepare
665     logger.write("\n", 2)
666     logger.write(src.printcolors.printcLabel("prepare:") + "\n", 2)
667
668     is_dev = src.product.product_is_dev(pinfo)
669     method = pinfo.get_source
670     if is_dev:
671         method += " (dev)"
672     src.printcolors.print_value(logger, "get method", method, 2)
673
674     if method == 'cvs':
675         src.printcolors.print_value(logger, "server", pinfo.cvs_info.server, 2)
676         src.printcolors.print_value(logger, "base module",
677                                     pinfo.cvs_info.module_base, 2)
678         src.printcolors.print_value(logger, "source", pinfo.cvs_info.source, 2)
679         src.printcolors.print_value(logger, "tag", pinfo.cvs_info.tag, 2)
680
681     elif method == 'svn':
682         src.printcolors.print_value(logger, "repo", pinfo.svn_info.repo, 2)
683
684     elif method == 'git':
685         src.printcolors.print_value(logger, "repo", pinfo.git_info.repo, 2)
686         src.printcolors.print_value(logger, "tag", pinfo.git_info.tag, 2)
687
688     elif method == 'archive':
689         src.printcolors.print_value(logger,
690                                     "get from",
691                                     check_path(pinfo.archive_info.archive_name),
692                                     2)
693
694     if 'patches' in pinfo:
695         for patch in pinfo.patches:
696             src.printcolors.print_value(logger, "patch", check_path(patch), 2)
697
698     if src.product.product_is_fixed(pinfo):
699         src.printcolors.print_value(logger, "install_dir",
700                                     check_path(pinfo.install_dir), 2)
701
702     if src.product.product_is_native(pinfo) or src.product.product_is_fixed(pinfo):
703         return
704     
705     # information on compilation
706     if src.product.product_compiles(pinfo):
707         logger.write("\n", 2)
708         logger.write(src.printcolors.printcLabel("compile:") + "\n", 2)
709         src.printcolors.print_value(logger,
710                                     "compilation method",
711                                     pinfo.build_source,
712                                     2)
713         
714         if pinfo.build_source == "script" and "compil_script" in pinfo:
715             src.printcolors.print_value(logger, 
716                                         "Compilation script", 
717                                         pinfo.compil_script, 
718                                         2)
719         
720         if 'nb_proc' in pinfo:
721             src.printcolors.print_value(logger, "make -j", pinfo.nb_proc, 2)
722     
723         src.printcolors.print_value(logger, 
724                                     "source dir", 
725                                     check_path(pinfo.source_dir), 
726                                     2)
727         if 'install_dir' in pinfo:
728             src.printcolors.print_value(logger, 
729                                         "build dir", 
730                                         check_path(pinfo.build_dir), 
731                                         2)
732             src.printcolors.print_value(logger, 
733                                         "install dir", 
734                                         check_path(pinfo.install_dir), 
735                                         2)
736         else:
737             logger.write("  " + 
738                          src.printcolors.printcWarning(_("no install dir")) + 
739                          "\n", 2)
740     else:
741         logger.write("\n", 2)
742         msg = _("This product does not compile")
743         logger.write("%s\n" % msg, 2)
744
745     # information on environment
746     logger.write("\n", 2)
747     logger.write(src.printcolors.printcLabel("environ :") + "\n", 2)
748     if "environ" in pinfo and "env_script" in pinfo.environ:
749         src.printcolors.print_value(logger, 
750                                     "script", 
751                                     check_path(pinfo.environ.env_script), 
752                                     2)
753
754     # display run-time environment
755     zz = src.environment.SalomeEnviron(config,
756                                        src.fileEnviron.ScreenEnviron(logger), 
757                                        False)
758     zz.set_python_libdirs()
759     zz.set_a_product(name, logger)
760     logger.write("\n", 2)
761
762
763 def show_patchs(config, logger):
764   '''Prints all the used patchs in the application.
765
766   :param config Config: the global configuration.
767   :param logger Logger: The logger instance to use for the display
768   '''
769   oneOrMore = False
770   for product in sorted(config.APPLICATION.products):
771     try:
772       product_info = src.product.get_product_config(config, product)
773       if src.product.product_has_patches(product_info):
774         oneOrMore = True
775         logger.write("%s:\n" % product, 1)
776         for i in product_info.patches:
777           logger.write(src.printcolors.printcInfo("    %s\n" % i), 1)
778     except Exception as e:
779       msg = "problem on product %s\n%s\n" % (product, str(e))
780       logger.error(msg)
781
782   if oneOrMore:
783     logger.write("\n", 1)
784   else:
785     logger.write("No patchs found\n", 1)
786
787
788 def show_install_dir(config, logger):
789   '''Prints all the used installed directories in the application.
790
791   :param config Config: the global configuration.
792   :param logger Logger: The logger instance to use for the display
793   '''
794   for product in sorted(config.APPLICATION.products):
795     try:
796       product_info = src.product.get_product_config(config, product)
797       install_path=src.Path(product_info.install_dir)
798       if (src.product.product_is_native(product_info)):
799           install_path="Native"
800       elif (src.product.product_is_fixed(product_info)):
801           install_path+=" (Fixed)"
802       logger.write("%s : %s\n" % (product, install_path) , 1)
803     except Exception as e:
804       msg = "problem on product %s\n%s\n" % (product, str(e))
805       logger.error(msg)
806   logger.write("\n", 1)
807
808
809 def show_properties(config, logger):
810   '''Prints all the used properties in the application.
811
812   :param config Config: the global configuration.
813   :param logger Logger: The logger instance to use for the display
814   '''
815   oneOrMore = False
816   for product in sorted(config.APPLICATION.products):
817     try:
818       product_info = src.product.get_product_config(config, product)
819       done = False
820       try:
821         for i in product_info.properties:
822           if not done:
823             logger.write("%s:\n" % product, 1)
824             done = True
825           oneOrMore = True
826           logger.write(src.printcolors.printcInfo("    %s\n" % i), 1)
827       except Exception as e:
828         pass
829     except Exception as e:
830       # logger.write(src.printcolors.printcInfo("    %s\n" % "no properties"), 1)
831       msg = "problem on product %s\n%s\n" % (product, e)
832       logger.error(msg)
833
834   if oneOrMore:
835     logger.write("\n", 1)
836   else:
837     logger.write("No properties found\n", 1)
838
839 def print_value(config, path, show_label, logger, level=0, show_full_path=False):
840     '''Prints a value from the configuration. Prints recursively the values 
841        under the initial path.
842     
843     :param config class 'src.pyconf.Config': The configuration 
844                                              from which the value is displayed.
845     :param path str : the path in the configuration of the value to print.
846     :param show_label boolean: if True, do a basic display. 
847                                (useful for bash completion)
848     :param logger Logger: the logger instance
849     :param level int: The number of spaces to add before display.
850     :param show_full_path :
851     '''            
852     
853     # Make sure that the path does not ends with a point
854     if path.endswith('.'):
855         path = path[:-1]
856     
857     # display all the path or not
858     if show_full_path:
859         vname = path
860     else:
861         vname = path.split('.')[-1]
862
863     # number of spaces before the display
864     tab_level = "  " * level
865     
866     # call to the function that gets the value of the path.
867     try:
868         val = config.getByPath(path)
869     except Exception as e:
870         logger.write(tab_level)
871         logger.write("%s: ERROR %s\n" % (src.printcolors.printcLabel(vname), 
872                                          src.printcolors.printcError(str(e))))
873         return
874
875     # in this case, display only the value
876     if show_label:
877         logger.write(tab_level)
878         logger.write("%s: " % src.printcolors.printcLabel(vname))
879
880     # The case where the value has under values, 
881     # do a recursive call to the function
882     if dir(val).__contains__('keys'):
883         if show_label: logger.write("\n")
884         for v in sorted(val.keys()):
885             print_value(config, path + '.' + v, show_label, logger, level + 1)
886     elif val.__class__ == src.pyconf.Sequence or isinstance(val, list): 
887         # in this case, value is a list (or a Sequence)
888         if show_label: logger.write("\n")
889         index = 0
890         for v in val:
891             print_value(config, path + "[" + str(index) + "]", 
892                         show_label, logger, level + 1)
893             index = index + 1
894     else: # case where val is just a str
895         logger.write("%s\n" % val)
896
897 def get_config_children(config, args):
898     '''Gets the names of the children of the given parameter.
899        Useful only for completion mechanism
900     
901     :param config Config: The configuration where to read the values
902     :param args: The path in the config from which get the keys
903     '''
904     vals = []
905     rootkeys = config.keys()
906     
907     if len(args) == 0:
908         # no parameter returns list of root keys
909         vals = rootkeys
910     else:
911         parent = args[0]
912         pos = parent.rfind('.')
913         if pos < 0:
914             # Case where there is only on key as parameter.
915             # For example VARS
916             vals = [m for m in rootkeys if m.startswith(parent)]
917         else:
918             # Case where there is a part from a key
919             # for example VARS.us  (for VARS.user)
920             head = parent[0:pos]
921             tail = parent[pos+1:]
922             try:
923                 a = config.getByPath(head)
924                 if dir(a).__contains__('keys'):
925                     vals = map(lambda x: head + '.' + x,
926                                [m for m in a.keys() if m.startswith(tail)])
927             except:
928                 pass
929
930     for v in sorted(vals):
931         sys.stdout.write("%s\n" % v)
932
933 def description():
934     '''method that is called when salomeTools is called with --help option.
935     
936     :return: The text to display for the config command description.
937     :rtype: str
938     '''
939     return _("The config command allows manipulation "
940              "and operation on config files.\n\nexample:\nsat config "
941              "SALOME-master --info ParaView")
942     
943
944 def run(args, runner, logger):
945     '''method that is called when salomeTools is called with config parameter.
946     '''
947     # Parse the options
948     (options, args) = parser.parse_args(args)
949
950     # Only useful for completion mechanism : print the keys of the config
951     if options.schema:
952         get_config_children(runner.cfg, args)
953         return
954
955     # case : print a value of the config
956     if options.value:
957         if options.value == ".":
958             # if argument is ".", print all the config
959             for val in sorted(runner.cfg.keys()):
960                 print_value(runner.cfg, val, not options.no_label, logger)
961         else:
962             print_value(runner.cfg, options.value, not options.no_label, logger, 
963                         level=0, show_full_path=False)
964     
965     # case : print a debug value of the config
966     if options.debug:
967         if options.debug == ".":
968             # if argument is ".", print all the config
969             res = DBG.indent(DBG.getStrConfigDbg(runner.cfg))
970             logger.write("\nConfig of application %s:\n\n%s\n" % (runner.cfg.VARS.application, res))
971         else:
972             if options.debug[0] == ".": # accept ".PRODUCT.etc" as "PRODUCT.etc"
973               od = options.debug[1:]
974             else:
975               od = options.debug
976             try:
977               aCode = "a = runner.cfg.%s" % od
978               # https://stackoverflow.com/questions/15086040/behavior-of-exec-function-in-python-2-and-python-3
979               aDict = {"runner": runner}
980               exec(aCode, globals(), aDict)
981               # DBG.write("globals()", globals(), True)
982               # DBG.write("aDict", aDict, True)
983               res = DBG.indent(DBG.getStrConfigDbg(aDict["a"]))
984               logger.write("\nConfig.%s of application %s:\n\n%s\n" % (od, runner.cfg.VARS.application, res))
985             except Exception as e:
986               msg = "\nConfig.%s of application %s: Unknown pyconf key\n" % (od, runner.cfg.VARS.application)
987               logger.write(src.printcolors.printcError(msg), 1)
988
989     
990     # case : edit user pyconf file or application file
991     if options.edit:
992         editor = runner.cfg.USER.editor
993         if ('APPLICATION' not in runner.cfg and
994                        'open_application' not in runner.cfg): # edit user pyconf
995             usercfg =  osJoin(runner.cfg.VARS.personalDir,
996                                    'SAT.pyconf')
997             logger.write(_("Opening %s\n" % usercfg), 3)
998             src.system.show_in_editor(editor, usercfg, logger)
999         else:
1000             # search for file <application>.pyconf and open it
1001             for path in runner.cfg.PATHS.APPLICATIONPATH:
1002                 pyconf_path =  osJoin(path,
1003                                     runner.cfg.VARS.application + ".pyconf")
1004                 if os.path.exists(pyconf_path):
1005                     logger.write(_("Opening %s\n" % pyconf_path), 3)
1006                     src.system.show_in_editor(editor, pyconf_path, logger)
1007                     break
1008     
1009     # case : give information about the product(s) in parameter
1010     if options.products:
1011       if options.info is not None:
1012         logger.warning('options.products %s overrides options.info %s' % (options.products, options.info))
1013       options.info = options.products
1014
1015     if options.info:
1016       # DBG.write("products", sorted(runner.cfg.APPLICATION.products.keys()), True)
1017       src.check_config_has_application(runner.cfg)
1018       taggedProducts = src.getProductNames(runner.cfg, options.info, logger)
1019       DBG.write("tagged products", sorted(taggedProducts))
1020       for prod in sorted(taggedProducts):
1021         if prod in runner.cfg.APPLICATION.products:
1022           try:
1023             if len(taggedProducts) > 1:
1024               logger.write("#################### ", 2)
1025             show_product_info(runner.cfg, prod, logger)
1026           except Exception as e:
1027             msg = "problem on product %s\n%s\n" % (prod, str(e))
1028             logger.error(msg)
1029           # return
1030         else:
1031           msg = _("%s is not a product of %s.\n") % \
1032                 (prod, runner.cfg.VARS.application)
1033           logger.warning(msg)
1034           #raise Exception(msg)
1035     
1036     # case : copy an existing <application>.pyconf 
1037     # to ~/.salomeTools/Applications/LOCAL_<application>.pyconf
1038     if options.copy:
1039         # product is required
1040         src.check_config_has_application( runner.cfg )
1041
1042         # get application file path 
1043         source = runner.cfg.VARS.application + '.pyconf'
1044         source_full_path = ""
1045         for path in runner.cfg.PATHS.APPLICATIONPATH:
1046             # ignore personal directory
1047             if path == runner.cfg.VARS.personalDir:
1048                 continue
1049             # loop on all directories that can have pyconf applications
1050             zz =  osJoin(path, source)
1051             if os.path.exists(zz):
1052                 source_full_path = zz
1053                 break
1054
1055         if len(source_full_path) == 0:
1056             raise src.SatException(_(
1057                         "Config file for product %s not found\n") % source)
1058         else:
1059             if len(args) > 0:
1060                 # a name is given as parameter, use it
1061                 dest = args[0]
1062             elif 'copy_prefix' in runner.cfg.INTERNAL.config:
1063                 # use prefix
1064                 dest = (runner.cfg.INTERNAL.config.copy_prefix 
1065                         + runner.cfg.VARS.application)
1066             else:
1067                 # use same name as source
1068                 dest = runner.cfg.VARS.application
1069                 
1070             # the full path
1071             dest_file =  osJoin(runner.cfg.VARS.personalDir,
1072                                      'Applications', dest + '.pyconf')
1073             if os.path.exists(dest_file):
1074                 raise src.SatException(_("A personal application"
1075                                          " '%s' already exists") % dest)
1076             
1077             # perform the copy
1078             shutil.copyfile(source_full_path, dest_file)
1079             logger.write(_("%s has been created.\n") % dest_file)
1080     
1081     # case : display all the available pyconf applications
1082     if options.list:
1083         lproduct = list()
1084         # search in all directories that can have pyconf applications
1085         for path in runner.cfg.PATHS.APPLICATIONPATH:
1086             # print a header
1087             if not options.no_label:
1088                 logger.write("------ %s\n" % src.printcolors.printcHeader(path))
1089
1090             if not os.path.exists(path):
1091                 logger.write(src.printcolors.printcError(_(
1092                                             "Directory not found")) + "\n")
1093             else:
1094                 for f in sorted(os.listdir(path)):
1095                     # ignore file that does not ends with .pyconf
1096                     if not f.endswith('.pyconf'):
1097                         continue
1098
1099                     appliname = f[:-len('.pyconf')]
1100                     if appliname not in lproduct:
1101                         lproduct.append(appliname)
1102                         if path.startswith(runner.cfg.VARS.personalDir) \
1103                                     and not options.no_label:
1104                             logger.write("%s*\n" % appliname)
1105                         else:
1106                             logger.write("%s\n" % appliname)
1107                             
1108             logger.write("\n")
1109
1110     # case: print all the products name of the application (internal use for completion)
1111     if options.completion:
1112         for product_name in runner.cfg.APPLICATION.products.keys():
1113             logger.write("%s\n" % product_name)
1114         
1115     # case : give a synthetic view of all patches used in the application
1116     if options.show_patchs:
1117         src.check_config_has_application(runner.cfg)
1118         # Print some informations
1119         logger.write(_('Patchs of application %s\n') %
1120                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1121         logger.write("\n", 2, False)
1122         show_patchs(runner.cfg, logger)
1123
1124     # case : give a synthetic view of all install directories used in the application
1125     if options.show_install:
1126         src.check_config_has_application(runner.cfg)
1127         # Print some informations
1128         logger.write(_('Installation directories of application %s\n') %
1129                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1130         logger.write("\n", 2, False)
1131         show_install_dir(runner.cfg, logger)
1132
1133     # case : give a synthetic view of all patches used in the application
1134     if options.show_properties:
1135         src.check_config_has_application(runner.cfg)
1136         # Print some informations
1137         logger.write(_('Properties of application %s\n') %
1138                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1139         logger.write("\n", 2, False)
1140         show_properties(runner.cfg, logger)
1141