Salome HOME
0e1c1a3f70ce3deebb738973cf82e619c278ba4e
[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         # Load product config files in PRODUCTS section
462         products_cfg = src.pyconf.Config()
463         products_cfg.addMapping("PRODUCTS",
464                                 src.pyconf.Mapping(products_cfg),
465                                 "The products\n")
466         if application is not None:
467             src.pyconf.streamOpener = ConfigOpener(cfg.PATHS.PRODUCTPATH)
468             for product_name in application_cfg.APPLICATION.products.keys():
469                 # Loop on all files that are in softsDir directory
470                 # and read their config
471                 product_file_name = product_name + ".pyconf"
472                 product_file_path = src.find_file_in_lpath(product_file_name, cfg.PATHS.PRODUCTPATH)
473                 if product_file_path:
474                     products_dir = os.path.dirname(product_file_path)
475                     # for a relative path (archive case) we complete with sat path
476                     if not os.path.isabs(products_dir):
477                         products_dir = os.path.join(cfg.VARS.salometoolsway,
478                                                     products_dir)
479                     try:
480                         prod_cfg = src.pyconf.Config(open(product_file_path),
481                                                      PWD=("", products_dir))
482                         prod_cfg.from_file = product_file_path
483                         products_cfg.PRODUCTS[product_name] = prod_cfg
484                     except Exception as e:
485                         msg = _(
486                             "WARNING: Error in configuration file"
487                             ": %(prod)s\n  %(error)s" % \
488                             {'prod' :  product_name, 'error': str(e) })
489                         sys.stdout.write(msg)
490             
491             merger.merge(cfg, products_cfg)
492             
493             # apply overwrite from command line if needed
494             for rule in self.get_command_line_overrides(options, ["PRODUCTS"]):
495                 exec('cfg.' + rule) # this cannot be factorized because of the exec
496             
497             if do_merge:
498                 merger.merge(cfg, application_cfg)
499
500                 # default launcher name ('salome')
501                 if ('profile' in cfg.APPLICATION and 
502                     'launcher_name' not in cfg.APPLICATION.profile):
503                     cfg.APPLICATION.profile.launcher_name = 'salome'
504
505                 # apply overwrite from command line if needed
506                 for rule in self.get_command_line_overrides(options,
507                                                              ["APPLICATION"]):
508                     # this cannot be factorized because of the exec
509                     exec('cfg.' + rule)
510             
511         # =====================================================================
512         # load USER config
513         self.set_user_config_file(cfg)
514         user_cfg_file = self.get_user_config_file()
515         user_cfg = src.pyconf.Config(open(user_cfg_file))
516         merger.merge(cfg, user_cfg)
517
518         # apply overwrite from command line if needed
519         for rule in self.get_command_line_overrides(options, ["USER"]):
520             exec('cfg.' + rule) # this cannot be factorize because of the exec
521         
522         # remove application products "blacklisted" in rm_products field
523         if "APPLICATION" in cfg and "rm_products" in cfg.APPLICATION:
524             for prod_to_remove in cfg.APPLICATION.rm_products:
525                 cfg.APPLICATION.products.__delitem__(prod_to_remove)
526         return cfg
527
528     def set_user_config_file(self, config):
529         '''Set the user config file name and path.
530         If necessary, build it from another one or create it from scratch.
531         
532         :param config class 'src.pyconf.Config': The global config 
533                                                  (containing all pyconf).
534         '''
535         # get the expected name and path of the file
536         self.config_file_name = 'SAT.pyconf'
537         self.user_config_file_path =  osJoin(config.VARS.personalDir, self.config_file_name)
538         
539         # if pyconf does not exist, create it from scratch
540         if not os.path.isfile(self.user_config_file_path): 
541             self.create_config_file(config)
542     
543     def create_config_file(self, config):
544         '''This method is called when there are no user config file. 
545            It build it from scratch.
546         
547         :param config class 'src.pyconf.Config': The global config.
548         :return: the config corresponding to the file created.
549         :rtype: config class 'src.pyconf.Config'
550         '''
551         
552         cfg_name = self.get_user_config_file()
553
554         user_cfg = src.pyconf.Config()
555         #
556         user_cfg.addMapping('USER', src.pyconf.Mapping(user_cfg), "")
557
558         user_cfg.USER.addMapping('cvs_user', config.VARS.user,
559             "This is the user name used to access salome cvs base.\n")
560         user_cfg.USER.addMapping('svn_user', config.VARS.user,
561             "This is the user name used to access salome svn base.\n")
562         user_cfg.USER.addMapping('output_verbose_level', 3,
563             "This is the default output_verbose_level you want."
564             " 0=>no output, 5=>debug.\n")
565         user_cfg.USER.addMapping('publish_dir', 
566                                   osJoin(os.path.expanduser('~'),
567                                  'websupport', 
568                                  'satreport'), 
569                                  "")
570         user_cfg.USER.addMapping('editor',
571                                  'vi', 
572                                  "This is the editor used to "
573                                  "modify configuration files\n")
574         user_cfg.USER.addMapping('browser', 
575                                  'firefox', 
576                                  "This is the browser used to "
577                                  "read html documentation\n")
578         user_cfg.USER.addMapping('pdf_viewer', 
579                                  'evince', 
580                                  "This is the pdf_viewer used "
581                                  "to read pdf documentation\n")
582
583         src.ensure_path_exists(config.VARS.personalDir)
584         src.ensure_path_exists( osJoin(config.VARS.personalDir,
585                                             'Applications'))
586
587         f = open(cfg_name, 'w')
588         user_cfg.__save__(f)
589         f.close()
590
591         return user_cfg   
592
593     def get_user_config_file(self):
594         '''Get the user config file
595         :return: path to the user config file.
596         :rtype: str
597         '''
598         if not self.user_config_file_path:
599             raise src.SatException(_("Error in get_user_config_file: "
600                                      "missing user config file path"))
601         return self.user_config_file_path     
602
603 def check_path(path, ext=[]):
604     '''Construct a text with the input path and "not found" if it does not
605        exist.
606     
607     :param path Str: the path to check.
608     :param ext List: An extension. Verify that the path extension 
609                      is in the list
610     :return: The string of the path with information
611     :rtype: Str
612     '''
613     # check if file exists
614     if not os.path.exists(path):
615         return "'%s'" % path + " " + src.printcolors.printcError(_(
616                                                             "** not found"))
617
618     # check extension
619     if len(ext) > 0:
620         fe = os.path.splitext(path)[1].lower()
621         if fe not in ext:
622             return "'%s'" % path + " " + src.printcolors.printcError(_(
623                                                         "** bad extension"))
624
625     return path
626
627 def show_product_info(config, name, logger):
628     '''Display on the terminal and logger information about a product.
629     
630     :param config Config: the global configuration.
631     :param name Str: The name of the product
632     :param logger Logger: The logger instance to use for the display
633     '''
634     
635     logger.write(_("%s is a product\n") % src.printcolors.printcLabel(name), 2)
636     pinfo = src.product.get_product_config(config, name)
637     
638     if "depend" in pinfo:
639         src.printcolors.print_value(logger, "depends on", sorted(pinfo.depend), 2)
640
641     if "opt_depend" in pinfo:
642         src.printcolors.print_value(logger, "optional", sorted(pinfo.opt_depend), 2)
643
644     # information on pyconf
645     logger.write("\n", 2)
646     logger.write(src.printcolors.printcLabel("configuration:") + "\n", 2)
647     if "from_file" in pinfo:
648         src.printcolors.print_value(logger,
649                                     "pyconf file path",
650                                     pinfo.from_file,
651                                     2)
652     if "section" in pinfo:
653         src.printcolors.print_value(logger,
654                                     "section",
655                                     pinfo.section,
656                                     2)
657
658     # information on prepare
659     logger.write("\n", 2)
660     logger.write(src.printcolors.printcLabel("prepare:") + "\n", 2)
661
662     is_dev = src.product.product_is_dev(pinfo)
663     method = pinfo.get_source
664     if is_dev:
665         method += " (dev)"
666     src.printcolors.print_value(logger, "get method", method, 2)
667
668     if method == 'cvs':
669         src.printcolors.print_value(logger, "server", pinfo.cvs_info.server, 2)
670         src.printcolors.print_value(logger, "base module",
671                                     pinfo.cvs_info.module_base, 2)
672         src.printcolors.print_value(logger, "source", pinfo.cvs_info.source, 2)
673         src.printcolors.print_value(logger, "tag", pinfo.cvs_info.tag, 2)
674
675     elif method == 'svn':
676         src.printcolors.print_value(logger, "repo", pinfo.svn_info.repo, 2)
677
678     elif method == 'git':
679         src.printcolors.print_value(logger, "repo", pinfo.git_info.repo, 2)
680         src.printcolors.print_value(logger, "tag", pinfo.git_info.tag, 2)
681
682     elif method == 'archive':
683         src.printcolors.print_value(logger,
684                                     "get from",
685                                     check_path(pinfo.archive_info.archive_name),
686                                     2)
687
688     if 'patches' in pinfo:
689         for patch in pinfo.patches:
690             src.printcolors.print_value(logger, "patch", check_path(patch), 2)
691
692     if src.product.product_is_fixed(pinfo):
693         src.printcolors.print_value(logger, "install_dir",
694                                     check_path(pinfo.install_dir), 2)
695
696     if src.product.product_is_native(pinfo) or src.product.product_is_fixed(pinfo):
697         return
698     
699     # information on compilation
700     if src.product.product_compiles(pinfo):
701         logger.write("\n", 2)
702         logger.write(src.printcolors.printcLabel("compile:") + "\n", 2)
703         src.printcolors.print_value(logger,
704                                     "compilation method",
705                                     pinfo.build_source,
706                                     2)
707         
708         if pinfo.build_source == "script" and "compil_script" in pinfo:
709             src.printcolors.print_value(logger, 
710                                         "Compilation script", 
711                                         pinfo.compil_script, 
712                                         2)
713         
714         if 'nb_proc' in pinfo:
715             src.printcolors.print_value(logger, "make -j", pinfo.nb_proc, 2)
716     
717         src.printcolors.print_value(logger, 
718                                     "source dir", 
719                                     check_path(pinfo.source_dir), 
720                                     2)
721         if 'install_dir' in pinfo:
722             src.printcolors.print_value(logger, 
723                                         "build dir", 
724                                         check_path(pinfo.build_dir), 
725                                         2)
726             src.printcolors.print_value(logger, 
727                                         "install dir", 
728                                         check_path(pinfo.install_dir), 
729                                         2)
730         else:
731             logger.write("  " + 
732                          src.printcolors.printcWarning(_("no install dir")) + 
733                          "\n", 2)
734     else:
735         logger.write("\n", 2)
736         msg = _("This product does not compile")
737         logger.write("%s\n" % msg, 2)
738
739     # information on environment
740     logger.write("\n", 2)
741     logger.write(src.printcolors.printcLabel("environ :") + "\n", 2)
742     if "environ" in pinfo and "env_script" in pinfo.environ:
743         src.printcolors.print_value(logger, 
744                                     "script", 
745                                     check_path(pinfo.environ.env_script), 
746                                     2)
747
748     # display run-time environment
749     zz = src.environment.SalomeEnviron(config,
750                                        src.fileEnviron.ScreenEnviron(logger), 
751                                        False)
752     zz.set_python_libdirs()
753     zz.set_a_product(name, logger)
754     logger.write("\n", 2)
755
756
757 def show_patchs(config, logger):
758   '''Prints all the used patchs in the application.
759
760   :param config Config: the global configuration.
761   :param logger Logger: The logger instance to use for the display
762   '''
763   oneOrMore = False
764   for product in sorted(config.APPLICATION.products):
765     try:
766       product_info = src.product.get_product_config(config, product)
767       if src.product.product_has_patches(product_info):
768         oneOrMore = True
769         logger.write("%s:\n" % product, 1)
770         for i in product_info.patches:
771           logger.write(src.printcolors.printcInfo("    %s\n" % i), 1)
772     except Exception as e:
773       msg = "problem on product %s\n%s\n" % (product, str(e))
774       logger.error(msg)
775
776   if oneOrMore:
777     logger.write("\n", 1)
778   else:
779     logger.write("No patchs found\n", 1)
780
781
782 def show_install_dir(config, logger):
783   '''Prints all the used installed directories in the application.
784
785   :param config Config: the global configuration.
786   :param logger Logger: The logger instance to use for the display
787   '''
788   for product in sorted(config.APPLICATION.products):
789     try:
790       product_info = src.product.get_product_config(config, product)
791       install_path=src.Path(product_info.install_dir)
792       if (src.product.product_is_native(product_info)):
793           install_path="Native"
794       elif (src.product.product_is_fixed(product_info)):
795           install_path+=" (Fixed)"
796       logger.write("%s : %s\n" % (product, install_path) , 1)
797     except Exception as e:
798       msg = "problem on product %s\n%s\n" % (product, str(e))
799       logger.error(msg)
800   logger.write("\n", 1)
801
802
803 def show_properties(config, logger):
804   '''Prints all the used properties in the application.
805
806   :param config Config: the global configuration.
807   :param logger Logger: The logger instance to use for the display
808   '''
809   oneOrMore = False
810   for product in sorted(config.APPLICATION.products):
811     try:
812       product_info = src.product.get_product_config(config, product)
813       done = False
814       try:
815         for i in product_info.properties:
816           if not done:
817             logger.write("%s:\n" % product, 1)
818             done = True
819           oneOrMore = True
820           logger.write(src.printcolors.printcInfo("    %s\n" % i), 1)
821       except Exception as e:
822         pass
823     except Exception as e:
824       # logger.write(src.printcolors.printcInfo("    %s\n" % "no properties"), 1)
825       msg = "problem on product %s\n%s\n" % (product, e)
826       logger.error(msg)
827
828   if oneOrMore:
829     logger.write("\n", 1)
830   else:
831     logger.write("No properties found\n", 1)
832
833 def print_value(config, path, show_label, logger, level=0, show_full_path=False):
834     '''Prints a value from the configuration. Prints recursively the values 
835        under the initial path.
836     
837     :param config class 'src.pyconf.Config': The configuration 
838                                              from which the value is displayed.
839     :param path str : the path in the configuration of the value to print.
840     :param show_label boolean: if True, do a basic display. 
841                                (useful for bash completion)
842     :param logger Logger: the logger instance
843     :param level int: The number of spaces to add before display.
844     :param show_full_path :
845     '''            
846     
847     # Make sure that the path does not ends with a point
848     if path.endswith('.'):
849         path = path[:-1]
850     
851     # display all the path or not
852     if show_full_path:
853         vname = path
854     else:
855         vname = path.split('.')[-1]
856
857     # number of spaces before the display
858     tab_level = "  " * level
859     
860     # call to the function that gets the value of the path.
861     try:
862         val = config.getByPath(path)
863     except Exception as e:
864         logger.write(tab_level)
865         logger.write("%s: ERROR %s\n" % (src.printcolors.printcLabel(vname), 
866                                          src.printcolors.printcError(str(e))))
867         return
868
869     # in this case, display only the value
870     if show_label:
871         logger.write(tab_level)
872         logger.write("%s: " % src.printcolors.printcLabel(vname))
873
874     # The case where the value has under values, 
875     # do a recursive call to the function
876     if dir(val).__contains__('keys'):
877         if show_label: logger.write("\n")
878         for v in sorted(val.keys()):
879             print_value(config, path + '.' + v, show_label, logger, level + 1)
880     elif val.__class__ == src.pyconf.Sequence or isinstance(val, list): 
881         # in this case, value is a list (or a Sequence)
882         if show_label: logger.write("\n")
883         index = 0
884         for v in val:
885             print_value(config, path + "[" + str(index) + "]", 
886                         show_label, logger, level + 1)
887             index = index + 1
888     else: # case where val is just a str
889         logger.write("%s\n" % val)
890
891 def get_config_children(config, args):
892     '''Gets the names of the children of the given parameter.
893        Useful only for completion mechanism
894     
895     :param config Config: The configuration where to read the values
896     :param args: The path in the config from which get the keys
897     '''
898     vals = []
899     rootkeys = config.keys()
900     
901     if len(args) == 0:
902         # no parameter returns list of root keys
903         vals = rootkeys
904     else:
905         parent = args[0]
906         pos = parent.rfind('.')
907         if pos < 0:
908             # Case where there is only on key as parameter.
909             # For example VARS
910             vals = [m for m in rootkeys if m.startswith(parent)]
911         else:
912             # Case where there is a part from a key
913             # for example VARS.us  (for VARS.user)
914             head = parent[0:pos]
915             tail = parent[pos+1:]
916             try:
917                 a = config.getByPath(head)
918                 if dir(a).__contains__('keys'):
919                     vals = map(lambda x: head + '.' + x,
920                                [m for m in a.keys() if m.startswith(tail)])
921             except:
922                 pass
923
924     for v in sorted(vals):
925         sys.stdout.write("%s\n" % v)
926
927 def description():
928     '''method that is called when salomeTools is called with --help option.
929     
930     :return: The text to display for the config command description.
931     :rtype: str
932     '''
933     return _("The config command allows manipulation "
934              "and operation on config files.\n\nexample:\nsat config "
935              "SALOME-master --info ParaView")
936     
937
938 def run(args, runner, logger):
939     '''method that is called when salomeTools is called with config parameter.
940     '''
941     # Parse the options
942     (options, args) = parser.parse_args(args)
943
944     # Only useful for completion mechanism : print the keys of the config
945     if options.schema:
946         get_config_children(runner.cfg, args)
947         return
948
949     # case : print a value of the config
950     if options.value:
951         if options.value == ".":
952             # if argument is ".", print all the config
953             for val in sorted(runner.cfg.keys()):
954                 print_value(runner.cfg, val, not options.no_label, logger)
955         else:
956             print_value(runner.cfg, options.value, not options.no_label, logger, 
957                         level=0, show_full_path=False)
958     
959     # case : print a debug value of the config
960     if options.debug:
961         if options.debug == ".":
962             # if argument is ".", print all the config
963             res = DBG.indent(DBG.getStrConfigDbg(runner.cfg))
964             logger.write("\nConfig of application %s:\n\n%s\n" % (runner.cfg.VARS.application, res))
965         else:
966             if options.debug[0] == ".": # accept ".PRODUCT.etc" as "PRODUCT.etc"
967               od = options.debug[1:]
968             else:
969               od = options.debug
970             try:
971               aCode = "a = runner.cfg.%s" % od
972               # https://stackoverflow.com/questions/15086040/behavior-of-exec-function-in-python-2-and-python-3
973               aDict = {"runner": runner}
974               exec(aCode, globals(), aDict)
975               # DBG.write("globals()", globals(), True)
976               # DBG.write("aDict", aDict, True)
977               res = DBG.indent(DBG.getStrConfigDbg(aDict["a"]))
978               logger.write("\nConfig.%s of application %s:\n\n%s\n" % (od, runner.cfg.VARS.application, res))
979             except Exception as e:
980               msg = "\nConfig.%s of application %s: Unknown pyconf key\n" % (od, runner.cfg.VARS.application)
981               logger.write(src.printcolors.printcError(msg), 1)
982
983     
984     # case : edit user pyconf file or application file
985     if options.edit:
986         editor = runner.cfg.USER.editor
987         if ('APPLICATION' not in runner.cfg and
988                        'open_application' not in runner.cfg): # edit user pyconf
989             usercfg =  osJoin(runner.cfg.VARS.personalDir,
990                                    'SAT.pyconf')
991             logger.write(_("Opening %s\n" % usercfg), 3)
992             src.system.show_in_editor(editor, usercfg, logger)
993         else:
994             # search for file <application>.pyconf and open it
995             for path in runner.cfg.PATHS.APPLICATIONPATH:
996                 pyconf_path =  osJoin(path,
997                                     runner.cfg.VARS.application + ".pyconf")
998                 if os.path.exists(pyconf_path):
999                     logger.write(_("Opening %s\n" % pyconf_path), 3)
1000                     src.system.show_in_editor(editor, pyconf_path, logger)
1001                     break
1002     
1003     # case : give information about the product(s) in parameter
1004     if options.products:
1005       if options.info is not None:
1006         logger.warning('options.products %s overrides options.info %s' % (options.products, options.info))
1007       options.info = options.products
1008
1009     if options.info:
1010       # DBG.write("products", sorted(runner.cfg.APPLICATION.products.keys()), True)
1011       src.check_config_has_application(runner.cfg)
1012       taggedProducts = src.getProductNames(runner.cfg, options.info, logger)
1013       DBG.write("tagged products", sorted(taggedProducts))
1014       for prod in sorted(taggedProducts):
1015         if prod in runner.cfg.APPLICATION.products:
1016           try:
1017             if len(taggedProducts) > 1:
1018               logger.write("#################### ", 2)
1019             show_product_info(runner.cfg, prod, logger)
1020           except Exception as e:
1021             msg = "problem on product %s\n%s\n" % (prod, str(e))
1022             logger.error(msg)
1023           # return
1024         else:
1025           msg = _("%s is not a product of %s.\n") % \
1026                 (prod, runner.cfg.VARS.application)
1027           logger.warning(msg)
1028           #raise Exception(msg)
1029     
1030     # case : copy an existing <application>.pyconf 
1031     # to ~/.salomeTools/Applications/LOCAL_<application>.pyconf
1032     if options.copy:
1033         # product is required
1034         src.check_config_has_application( runner.cfg )
1035
1036         # get application file path 
1037         source = runner.cfg.VARS.application + '.pyconf'
1038         source_full_path = ""
1039         for path in runner.cfg.PATHS.APPLICATIONPATH:
1040             # ignore personal directory
1041             if path == runner.cfg.VARS.personalDir:
1042                 continue
1043             # loop on all directories that can have pyconf applications
1044             zz =  osJoin(path, source)
1045             if os.path.exists(zz):
1046                 source_full_path = zz
1047                 break
1048
1049         if len(source_full_path) == 0:
1050             raise src.SatException(_(
1051                         "Config file for product %s not found\n") % source)
1052         else:
1053             if len(args) > 0:
1054                 # a name is given as parameter, use it
1055                 dest = args[0]
1056             elif 'copy_prefix' in runner.cfg.INTERNAL.config:
1057                 # use prefix
1058                 dest = (runner.cfg.INTERNAL.config.copy_prefix 
1059                         + runner.cfg.VARS.application)
1060             else:
1061                 # use same name as source
1062                 dest = runner.cfg.VARS.application
1063                 
1064             # the full path
1065             dest_file =  osJoin(runner.cfg.VARS.personalDir,
1066                                      'Applications', dest + '.pyconf')
1067             if os.path.exists(dest_file):
1068                 raise src.SatException(_("A personal application"
1069                                          " '%s' already exists") % dest)
1070             
1071             # perform the copy
1072             shutil.copyfile(source_full_path, dest_file)
1073             logger.write(_("%s has been created.\n") % dest_file)
1074     
1075     # case : display all the available pyconf applications
1076     if options.list:
1077         lproduct = list()
1078         # search in all directories that can have pyconf applications
1079         for path in runner.cfg.PATHS.APPLICATIONPATH:
1080             # print a header
1081             if not options.no_label:
1082                 logger.write("------ %s\n" % src.printcolors.printcHeader(path))
1083
1084             if not os.path.exists(path):
1085                 logger.write(src.printcolors.printcError(_(
1086                                             "Directory not found")) + "\n")
1087             else:
1088                 for f in sorted(os.listdir(path)):
1089                     # ignore file that does not ends with .pyconf
1090                     if not f.endswith('.pyconf'):
1091                         continue
1092
1093                     appliname = f[:-len('.pyconf')]
1094                     if appliname not in lproduct:
1095                         lproduct.append(appliname)
1096                         if path.startswith(runner.cfg.VARS.personalDir) \
1097                                     and not options.no_label:
1098                             logger.write("%s*\n" % appliname)
1099                         else:
1100                             logger.write("%s\n" % appliname)
1101                             
1102             logger.write("\n")
1103
1104     # case: print all the products name of the application (internal use for completion)
1105     if options.completion:
1106         for product_name in runner.cfg.APPLICATION.products.keys():
1107             logger.write("%s\n" % product_name)
1108         
1109     # case : give a synthetic view of all patches used in the application
1110     if options.show_patchs:
1111         src.check_config_has_application(runner.cfg)
1112         # Print some informations
1113         logger.write(_('Patchs of application %s\n') %
1114                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1115         logger.write("\n", 2, False)
1116         show_patchs(runner.cfg, logger)
1117
1118     # case : give a synthetic view of all install directories used in the application
1119     if options.show_install:
1120         src.check_config_has_application(runner.cfg)
1121         # Print some informations
1122         logger.write(_('Installation directories of application %s\n') %
1123                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1124         logger.write("\n", 2, False)
1125         show_install_dir(runner.cfg, logger)
1126
1127     # case : give a synthetic view of all patches used in the application
1128     if options.show_properties:
1129         src.check_config_has_application(runner.cfg)
1130         # Print some informations
1131         logger.write(_('Properties of application %s\n') %
1132                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1133         logger.write("\n", 2, False)
1134         show_properties(runner.cfg, logger)
1135