]> SALOME platform Git repositories - tools/sat.git/blob - commands/config.py
Salome HOME
set VARS.user to Unknown is USER var not set (no more exception), extend printings...
[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             if src.architecture.is_windows(): # special internal config for windows
265                 internal_cfg = src.pyconf.Config(open( osJoin(cfg.VARS.srcDir,
266                                         'internal_config', 'salomeTools_win.pyconf')))
267             else:
268                 internal_cfg = src.pyconf.Config(open( osJoin(cfg.VARS.srcDir,
269                                         'internal_config', 'salomeTools.pyconf')))
270         except src.pyconf.ConfigError as e:
271             raise src.SatException(_("Error in configuration file:"
272                                      " salomeTools.pyconf\n  %(error)s") % \
273                                    {'error': str(e) })
274         
275         merger.merge(cfg, internal_cfg)
276
277         # apply overwrite from command line if needed
278         for rule in self.get_command_line_overrides(options, ["INTERNAL"]):
279             exec('cfg.' + rule) # this cannot be factorized because of the exec        
280                
281         # =====================================================================
282         # Load LOCAL config file
283         # search only in the data directory
284         src.pyconf.streamOpener = ConfigOpener([cfg.VARS.datadir])
285         try:
286             local_cfg = src.pyconf.Config(open( osJoin(cfg.VARS.datadir,
287                                                            'local.pyconf')),
288                                          PWD = ('LOCAL', cfg.VARS.datadir) )
289         except src.pyconf.ConfigError as e:
290             raise src.SatException(_("Error in configuration file: "
291                                      "local.pyconf\n  %(error)s") % \
292                 {'error': str(e) })
293         except IOError as error:
294             e = str(error)
295             raise src.SatException( e );
296         merger.merge(cfg, local_cfg)
297
298         # When the key is "default", put the default value
299         if cfg.LOCAL.base == "default":
300             cfg.LOCAL.base = os.path.abspath(osJoin(cfg.VARS.salometoolsway, "..", "BASE"))
301         if cfg.LOCAL.workdir == "default":
302             cfg.LOCAL.workdir = os.path.abspath(osJoin(cfg.VARS.salometoolsway, ".."))
303         if cfg.LOCAL.log_dir == "default":
304             cfg.LOCAL.log_dir = os.path.abspath(osJoin(cfg.VARS.salometoolsway, "..", "LOGS"))
305
306         if cfg.LOCAL.archive_dir == "default":
307             cfg.LOCAL.archive_dir = os.path.abspath( osJoin(cfg.VARS.salometoolsway, "..", "ARCHIVES"))
308
309         # if the sat tag was not set permanently by user
310         if cfg.LOCAL.tag == None:
311             # get the tag with git, and store it
312             sat_version=src.system.git_describe(cfg.VARS.salometoolsway) 
313             if sat_version == False:
314                 sat_version=cfg.INTERNAL.sat_version
315             cfg.LOCAL.tag=sat_version
316                 
317
318         # apply overwrite from command line if needed
319         for rule in self.get_command_line_overrides(options, ["LOCAL"]):
320             exec('cfg.' + rule) # this cannot be factorized because of the exec
321         
322         # =====================================================================
323         # Load the PROJECTS
324         projects_cfg = src.pyconf.Config()
325         projects_cfg.addMapping("PROJECTS",
326                                 src.pyconf.Mapping(projects_cfg),
327                                 "The projects\n")
328         projects_cfg.PROJECTS.addMapping("projects",
329                                 src.pyconf.Mapping(cfg.PROJECTS),
330                                 "The projects definition\n")
331         
332         for project_pyconf_path in cfg.PROJECTS.project_file_paths:
333             if not os.path.isabs(project_pyconf_path):
334                 # for a relative path (archive case) we complete with sat path
335                 project_pyconf_path = os.path.join(cfg.VARS.salometoolsway,
336                                                   project_pyconf_path)
337             if not os.path.exists(project_pyconf_path):
338                 msg = _("WARNING: The project file %s cannot be found. "
339                         "It will be ignored\n" % project_pyconf_path)
340                 sys.stdout.write(msg)
341                 continue
342             project_name = os.path.basename(
343                                     project_pyconf_path)[:-len(".pyconf")]
344             try:
345                 project_pyconf_dir = os.path.dirname(project_pyconf_path)
346                 project_cfg = src.pyconf.Config(open(project_pyconf_path),
347                                                 PWD=("", project_pyconf_dir))
348             except Exception as e:
349                 msg = _("ERROR: Error in configuration file: "
350                                  "%(file_path)s\n  %(error)s\n") % \
351                             {'file_path' : project_pyconf_path, 'error': str(e) }
352                 sys.stdout.write(msg)
353                 continue
354             projects_cfg.PROJECTS.projects.addMapping(project_name,
355                              src.pyconf.Mapping(projects_cfg.PROJECTS.projects),
356                              "The %s project\n" % project_name)
357             projects_cfg.PROJECTS.projects[project_name]=project_cfg
358             projects_cfg.PROJECTS.projects[project_name]["file_path"] = \
359                                                         project_pyconf_path
360             # store the project tag if any
361             product_project_git_tag = src.system.git_describe(os.path.dirname(project_pyconf_path))
362             if product_project_git_tag:
363                 projects_cfg.PROJECTS.projects[project_name]["git_tag"] = product_project_git_tag
364             else:
365                 projects_cfg.PROJECTS.projects[project_name]["git_tag"] = "unknown"
366                    
367         merger.merge(cfg, projects_cfg)
368
369         # apply overwrite from command line if needed
370         for rule in self.get_command_line_overrides(options, ["PROJECTS"]):
371             exec('cfg.' + rule) # this cannot be factorized because of the exec
372         
373         # =====================================================================
374         # Create the paths where to search the application configurations, 
375         # the product configurations, the products archives, 
376         # the jobs configurations and the machines configurations
377         cfg.addMapping("PATHS", src.pyconf.Mapping(cfg), "The paths\n")
378         cfg.PATHS["APPLICATIONPATH"] = src.pyconf.Sequence(cfg.PATHS)
379         cfg.PATHS.APPLICATIONPATH.append(cfg.VARS.personal_applications_dir, "")
380
381         
382         cfg.PATHS["PRODUCTPATH"] = src.pyconf.Sequence(cfg.PATHS)
383         cfg.PATHS.PRODUCTPATH.append(cfg.VARS.personal_products_dir, "")
384         cfg.PATHS["ARCHIVEPATH"] = src.pyconf.Sequence(cfg.PATHS)
385         cfg.PATHS.ARCHIVEPATH.append(cfg.VARS.personal_archives_dir, "")
386         cfg.PATHS["ARCHIVEFTP"] = src.pyconf.Sequence(cfg.PATHS)
387         cfg.PATHS["JOBPATH"] = src.pyconf.Sequence(cfg.PATHS)
388         cfg.PATHS.JOBPATH.append(cfg.VARS.personal_jobs_dir, "")
389         cfg.PATHS["MACHINEPATH"] = src.pyconf.Sequence(cfg.PATHS)
390         cfg.PATHS.MACHINEPATH.append(cfg.VARS.personal_machines_dir, "")
391         cfg.PATHS["LICENCEPATH"] = src.pyconf.Sequence(cfg.PATHS)
392
393         # initialise the path with local directory
394         cfg.PATHS["ARCHIVEPATH"].append(cfg.LOCAL.archive_dir, "")
395
396         # Loop over the projects in order to complete the PATHS variables
397         # as /data/tmpsalome/salome/prerequis/archives for example ARCHIVEPATH
398         for project in cfg.PROJECTS.projects:
399             for PATH in ["APPLICATIONPATH",
400                          "PRODUCTPATH",
401                          "ARCHIVEPATH", #comment this for default archive       #8646
402                          "ARCHIVEFTP",
403                          "JOBPATH",
404                          "MACHINEPATH",
405                          "LICENCEPATH"]:
406                 if PATH not in cfg.PROJECTS.projects[project]:
407                     continue
408                 cfg.PATHS[PATH].append(cfg.PROJECTS.projects[project][PATH], "")
409         
410         # apply overwrite from command line if needed
411         for rule in self.get_command_line_overrides(options, ["PATHS"]):
412             exec('cfg.' + rule) # this cannot be factorized because of the exec
413
414         # AT END append APPLI_TEST directory in APPLICATIONPATH, for unittest
415         appli_test_dir =  osJoin(satdir, "test", "APPLI_TEST")
416         if appli_test_dir not in cfg.PATHS.APPLICATIONPATH:
417           cfg.PATHS.APPLICATIONPATH.append(appli_test_dir, "unittest APPLI_TEST path")
418
419         # =====================================================================
420         # Load APPLICATION config file
421         if application is not None:
422             # search APPLICATION file in all directories in configPath
423             cp = cfg.PATHS.APPLICATIONPATH
424             src.pyconf.streamOpener = ConfigOpener(cp)
425             do_merge = True
426             try:
427                 application_cfg = src.pyconf.Config(application + '.pyconf')
428             except IOError as e:
429                 raise src.SatException(
430                    _("%s, use 'config --list' to get the list of available applications.") % e)
431             except src.pyconf.ConfigError as e:
432                 if (not ('-e' in parser.parse_args()[1]) 
433                                          or ('--edit' in parser.parse_args()[1]) 
434                                          and command == 'config'):
435                     raise src.SatException(_("Error in configuration file: "
436                                              "%(application)s.pyconf\n "
437                                              " %(error)s") % \
438                         { 'application': application, 'error': str(e) } )
439                 else:
440                     sys.stdout.write(src.printcolors.printcWarning(
441                                         "There is an error in the file"
442                                         " %s.pyconf.\n" % cfg.VARS.application))
443                     do_merge = False
444             except Exception as e:
445                 if (not ('-e' in parser.parse_args()[1]) 
446                                         or ('--edit' in parser.parse_args()[1]) 
447                                         and command == 'config'):
448                     sys.stdout.write(src.printcolors.printcWarning("%s\n" % str(e)))
449                     raise src.SatException(_("Error in configuration file:"
450                                              " %(application)s.pyconf\n") % \
451                         { 'application': application} )
452                 else:
453                     sys.stdout.write(src.printcolors.printcWarning(
454                                 "There is an error in the file"
455                                 " %s.pyconf. Opening the file with the"
456                                 " default viewer\n" % cfg.VARS.application))
457                     sys.stdout.write("The error:"
458                                  " %s\n" % src.printcolors.printcWarning(
459                                                                       str(e)))
460                     do_merge = False
461         
462             else:
463                 cfg['open_application'] = 'yes'
464         # =====================================================================
465         # Load product config files in PRODUCTS section
466         products_cfg = src.pyconf.Config()
467         products_cfg.addMapping("PRODUCTS",
468                                 src.pyconf.Mapping(products_cfg),
469                                 "The products\n")
470         if application is not None:
471             src.pyconf.streamOpener = ConfigOpener(cfg.PATHS.PRODUCTPATH)
472             for product_name in application_cfg.APPLICATION.products.keys():
473                 # Loop on all files that are in softsDir directory
474                 # and read their config
475                 product_file_name = product_name + ".pyconf"
476                 product_file_path = src.find_file_in_lpath(product_file_name, cfg.PATHS.PRODUCTPATH)
477                 if product_file_path:
478                     products_dir = os.path.dirname(product_file_path)
479                     # for a relative path (archive case) we complete with sat path
480                     if not os.path.isabs(products_dir):
481                         products_dir = os.path.join(cfg.VARS.salometoolsway,
482                                                     products_dir)
483                     try:
484                         prod_cfg = src.pyconf.Config(open(product_file_path),
485                                                      PWD=("", products_dir))
486                         prod_cfg.from_file = product_file_path
487                         products_cfg.PRODUCTS[product_name] = prod_cfg
488                     except Exception as e:
489                         msg = _(
490                             "WARNING: Error in configuration file"
491                             ": %(prod)s\n  %(error)s" % \
492                             {'prod' :  product_name, 'error': str(e) })
493                         sys.stdout.write(msg)
494             
495             merger.merge(cfg, products_cfg)
496             
497             # apply overwrite from command line if needed
498             for rule in self.get_command_line_overrides(options, ["PRODUCTS"]):
499                 exec('cfg.' + rule) # this cannot be factorized because of the exec
500             
501             if do_merge:
502                 merger.merge(cfg, application_cfg)
503
504                 # default launcher name ('salome')
505                 if ('profile' in cfg.APPLICATION and 
506                     'launcher_name' not in cfg.APPLICATION.profile):
507                     cfg.APPLICATION.profile.launcher_name = 'salome'
508
509                 # apply overwrite from command line if needed
510                 for rule in self.get_command_line_overrides(options,
511                                                              ["APPLICATION"]):
512                     # this cannot be factorized because of the exec
513                     exec('cfg.' + rule)
514             
515         # =====================================================================
516         # load USER config
517         self.set_user_config_file(cfg)
518         user_cfg_file = self.get_user_config_file()
519         user_cfg = src.pyconf.Config(open(user_cfg_file))
520         merger.merge(cfg, user_cfg)
521
522         # apply overwrite from command line if needed
523         for rule in self.get_command_line_overrides(options, ["USER"]):
524             exec('cfg.' + rule) # this cannot be factorize because of the exec
525         
526         # remove application products "blacklisted" in rm_products field
527         if "APPLICATION" in cfg and "rm_products" in cfg.APPLICATION:
528             for prod_to_remove in cfg.APPLICATION.rm_products:
529                 cfg.APPLICATION.products.__delitem__(prod_to_remove)
530         return cfg
531
532     def set_user_config_file(self, config):
533         '''Set the user config file name and path.
534         If necessary, build it from another one or create it from scratch.
535         
536         :param config class 'src.pyconf.Config': The global config 
537                                                  (containing all pyconf).
538         '''
539         # get the expected name and path of the file
540         self.config_file_name = 'SAT.pyconf'
541         self.user_config_file_path =  osJoin(config.VARS.personalDir, self.config_file_name)
542         
543         # if pyconf does not exist, create it from scratch
544         if not os.path.isfile(self.user_config_file_path): 
545             self.create_config_file(config)
546     
547     def create_config_file(self, config):
548         '''This method is called when there are no user config file. 
549            It build it from scratch.
550         
551         :param config class 'src.pyconf.Config': The global config.
552         :return: the config corresponding to the file created.
553         :rtype: config class 'src.pyconf.Config'
554         '''
555         
556         cfg_name = self.get_user_config_file()
557
558         user_cfg = src.pyconf.Config()
559         #
560         user_cfg.addMapping('USER', src.pyconf.Mapping(user_cfg), "")
561
562         user_cfg.USER.addMapping('cvs_user', config.VARS.user,
563             "This is the user name used to access salome cvs base.\n")
564         user_cfg.USER.addMapping('svn_user', config.VARS.user,
565             "This is the user name used to access salome svn base.\n")
566         user_cfg.USER.addMapping('output_verbose_level', 3,
567             "This is the default output_verbose_level you want."
568             " 0=>no output, 5=>debug.\n")
569         user_cfg.USER.addMapping('publish_dir', 
570                                   osJoin(os.path.expanduser('~'),
571                                  'websupport', 
572                                  'satreport'), 
573                                  "")
574         user_cfg.USER.addMapping('editor',
575                                  'vi', 
576                                  "This is the editor used to "
577                                  "modify configuration files\n")
578         user_cfg.USER.addMapping('browser', 
579                                  'firefox', 
580                                  "This is the browser used to "
581                                  "read html documentation\n")
582         user_cfg.USER.addMapping('pdf_viewer', 
583                                  'evince', 
584                                  "This is the pdf_viewer used "
585                                  "to read pdf documentation\n")
586
587         src.ensure_path_exists(config.VARS.personalDir)
588         src.ensure_path_exists( osJoin(config.VARS.personalDir,
589                                             'Applications'))
590
591         f = open(cfg_name, 'w')
592         user_cfg.__save__(f)
593         f.close()
594
595         return user_cfg   
596
597     def get_user_config_file(self):
598         '''Get the user config file
599         :return: path to the user config file.
600         :rtype: str
601         '''
602         if not self.user_config_file_path:
603             raise src.SatException(_("Error in get_user_config_file: "
604                                      "missing user config file path"))
605         return self.user_config_file_path     
606
607 def check_path(path, ext=[]):
608     '''Construct a text with the input path and "not found" if it does not
609        exist.
610     
611     :param path Str: the path to check.
612     :param ext List: An extension. Verify that the path extension 
613                      is in the list
614     :return: The string of the path with information
615     :rtype: Str
616     '''
617     # check if file exists
618     if not os.path.exists(path):
619         return "'%s'" % path + " " + src.printcolors.printcError(_(
620                                                             "** not found"))
621
622     # check extension
623     if len(ext) > 0:
624         fe = os.path.splitext(path)[1].lower()
625         if fe not in ext:
626             return "'%s'" % path + " " + src.printcolors.printcError(_(
627                                                         "** bad extension"))
628
629     return path
630
631 def show_product_info(config, name, logger):
632     '''Display on the terminal and logger information about a product.
633     
634     :param config Config: the global configuration.
635     :param name Str: The name of the product
636     :param logger Logger: The logger instance to use for the display
637     '''
638     
639     logger.write(_("%s is a product\n") % src.printcolors.printcLabel(name), 2)
640     pinfo = src.product.get_product_config(config, name)
641     
642     if "depend" in pinfo:
643         src.printcolors.print_value(logger, "depends on", sorted(pinfo.depend), 2)
644
645     if "opt_depend" in pinfo:
646         src.printcolors.print_value(logger, "optional", sorted(pinfo.opt_depend), 2)
647
648     # information on pyconf
649     logger.write("\n", 2)
650     logger.write(src.printcolors.printcLabel("configuration:") + "\n", 2)
651     if "from_file" in pinfo:
652         src.printcolors.print_value(logger,
653                                     "pyconf file path",
654                                     pinfo.from_file,
655                                     2)
656     if "section" in pinfo:
657         src.printcolors.print_value(logger,
658                                     "section",
659                                     pinfo.section,
660                                     2)
661
662     # information on prepare
663     logger.write("\n", 2)
664     logger.write(src.printcolors.printcLabel("prepare:") + "\n", 2)
665
666     is_dev = src.product.product_is_dev(pinfo)
667     method = pinfo.get_source
668     if is_dev:
669         method += " (dev)"
670     src.printcolors.print_value(logger, "get method", method, 2)
671
672     if method == 'cvs':
673         src.printcolors.print_value(logger, "server", pinfo.cvs_info.server, 2)
674         src.printcolors.print_value(logger, "base module",
675                                     pinfo.cvs_info.module_base, 2)
676         src.printcolors.print_value(logger, "source", pinfo.cvs_info.source, 2)
677         src.printcolors.print_value(logger, "tag", pinfo.cvs_info.tag, 2)
678
679     elif method == 'svn':
680         src.printcolors.print_value(logger, "repo", pinfo.svn_info.repo, 2)
681
682     elif method == 'git':
683         src.printcolors.print_value(logger, "repo", pinfo.git_info.repo, 2)
684         src.printcolors.print_value(logger, "tag", pinfo.git_info.tag, 2)
685
686     elif method == 'archive':
687         src.printcolors.print_value(logger,
688                                     "get from",
689                                     check_path(pinfo.archive_info.archive_name),
690                                     2)
691
692     if 'patches' in pinfo:
693         for patch in pinfo.patches:
694             src.printcolors.print_value(logger, "patch", check_path(patch), 2)
695
696     if src.product.product_is_fixed(pinfo):
697         src.printcolors.print_value(logger, "install_dir",
698                                     check_path(pinfo.install_dir), 2)
699
700     if src.product.product_is_native(pinfo) or src.product.product_is_fixed(pinfo):
701         return
702     
703     # information on compilation
704     if src.product.product_compiles(pinfo):
705         logger.write("\n", 2)
706         logger.write(src.printcolors.printcLabel("compile:") + "\n", 2)
707         src.printcolors.print_value(logger,
708                                     "compilation method",
709                                     pinfo.build_source,
710                                     2)
711         
712         if pinfo.build_source == "script" and "compil_script" in pinfo:
713             src.printcolors.print_value(logger, 
714                                         "Compilation script", 
715                                         pinfo.compil_script, 
716                                         2)
717         
718         if 'nb_proc' in pinfo:
719             src.printcolors.print_value(logger, "make -j", pinfo.nb_proc, 2)
720     
721         src.printcolors.print_value(logger, 
722                                     "source dir", 
723                                     check_path(pinfo.source_dir), 
724                                     2)
725         if 'install_dir' in pinfo:
726             src.printcolors.print_value(logger, 
727                                         "build dir", 
728                                         check_path(pinfo.build_dir), 
729                                         2)
730             src.printcolors.print_value(logger, 
731                                         "install dir", 
732                                         check_path(pinfo.install_dir), 
733                                         2)
734         else:
735             logger.write("  " + 
736                          src.printcolors.printcWarning(_("no install dir")) + 
737                          "\n", 2)
738     else:
739         logger.write("\n", 2)
740         msg = _("This product does not compile")
741         logger.write("%s\n" % msg, 2)
742
743     # information on environment
744     logger.write("\n", 2)
745     logger.write(src.printcolors.printcLabel("environ :") + "\n", 2)
746     if "environ" in pinfo and "env_script" in pinfo.environ:
747         src.printcolors.print_value(logger, 
748                                     "script", 
749                                     check_path(pinfo.environ.env_script), 
750                                     2)
751
752     # display run-time environment
753     zz = src.environment.SalomeEnviron(config,
754                                        src.fileEnviron.ScreenEnviron(logger), 
755                                        False)
756     zz.set_python_libdirs()
757     zz.set_a_product(name, logger)
758     logger.write("\n", 2)
759
760
761 def show_patchs(config, logger):
762   '''Prints all the used patchs in the application.
763
764   :param config Config: the global configuration.
765   :param logger Logger: The logger instance to use for the display
766   '''
767   oneOrMore = False
768   for product in sorted(config.APPLICATION.products):
769     try:
770       product_info = src.product.get_product_config(config, product)
771       if src.product.product_has_patches(product_info):
772         oneOrMore = True
773         logger.write("%s:\n" % product, 1)
774         for i in product_info.patches:
775           logger.write(src.printcolors.printcInfo("    %s\n" % i), 1)
776     except Exception as e:
777       msg = "problem on product %s\n%s\n" % (product, str(e))
778       logger.error(msg)
779
780   if oneOrMore:
781     logger.write("\n", 1)
782   else:
783     logger.write("No patchs found\n", 1)
784
785
786 def show_install_dir(config, logger):
787   '''Prints all the used installed directories in the application.
788
789   :param config Config: the global configuration.
790   :param logger Logger: The logger instance to use for the display
791   '''
792   for product in sorted(config.APPLICATION.products):
793     try:
794       product_info = src.product.get_product_config(config, product)
795       install_path=src.Path(product_info.install_dir)
796       if (src.product.product_is_native(product_info)):
797           install_path="Native"
798       elif (src.product.product_is_fixed(product_info)):
799           install_path+=" (Fixed)"
800       logger.write("%s : %s\n" % (product, install_path) , 1)
801     except Exception as e:
802       msg = "problem on product %s\n%s\n" % (product, str(e))
803       logger.error(msg)
804   logger.write("\n", 1)
805
806
807 def show_properties(config, logger):
808   '''Prints all the used properties in the application.
809
810   :param config Config: the global configuration.
811   :param logger Logger: The logger instance to use for the display
812   '''
813   if "properties" in config.APPLICATION:
814       # some properties are defined at application level, we display them
815       logger.write("Application properties:\n", 1)
816       for prop in config.APPLICATION.properties:
817           logger.write(src.printcolors.printcInfo("    %s : %s\n" % (prop, config.APPLICATION.properties[prop])), 1)
818   oneOrMore = False
819   for product in sorted(config.APPLICATION.products):
820     try:
821       product_info = src.product.get_product_config(config, product)
822       done = False
823       try:
824         for prop in product_info.properties:
825           if not done:
826             logger.write("%s:\n" % product, 1)
827             done = True
828           oneOrMore = True
829           logger.write(src.printcolors.printcInfo("    %s : %s\n" % (prop, product_info.properties[prop])), 1)
830       except Exception as e:
831         pass
832     except Exception as e:
833       # logger.write(src.printcolors.printcInfo("    %s\n" % "no properties"), 1)
834       msg = "problem on product %s\n%s\n" % (product, e)
835       logger.error(msg)
836
837   if oneOrMore:
838     logger.write("\n", 1)
839   else:
840     logger.write("No properties found\n", 1)
841
842 def print_value(config, path, show_label, logger, level=0, show_full_path=False):
843     '''Prints a value from the configuration. Prints recursively the values 
844        under the initial path.
845     
846     :param config class 'src.pyconf.Config': The configuration 
847                                              from which the value is displayed.
848     :param path str : the path in the configuration of the value to print.
849     :param show_label boolean: if True, do a basic display. 
850                                (useful for bash completion)
851     :param logger Logger: the logger instance
852     :param level int: The number of spaces to add before display.
853     :param show_full_path :
854     '''            
855     
856     # Make sure that the path does not ends with a point
857     if path.endswith('.'):
858         path = path[:-1]
859     
860     # display all the path or not
861     if show_full_path:
862         vname = path
863     else:
864         vname = path.split('.')[-1]
865
866     # number of spaces before the display
867     tab_level = "  " * level
868     
869     # call to the function that gets the value of the path.
870     try:
871         val = config.getByPath(path)
872     except Exception as e:
873         logger.write(tab_level)
874         logger.write("%s: ERROR %s\n" % (src.printcolors.printcLabel(vname), 
875                                          src.printcolors.printcError(str(e))))
876         return
877
878     # in this case, display only the value
879     if show_label:
880         logger.write(tab_level)
881         logger.write("%s: " % src.printcolors.printcLabel(vname))
882
883     # The case where the value has under values, 
884     # do a recursive call to the function
885     if dir(val).__contains__('keys'):
886         if show_label: logger.write("\n")
887         for v in sorted(val.keys()):
888             print_value(config, path + '.' + v, show_label, logger, level + 1)
889     elif val.__class__ == src.pyconf.Sequence or isinstance(val, list): 
890         # in this case, value is a list (or a Sequence)
891         if show_label: logger.write("\n")
892         index = 0
893         for v in val:
894             print_value(config, path + "[" + str(index) + "]", 
895                         show_label, logger, level + 1)
896             index = index + 1
897     else: # case where val is just a str
898         logger.write("%s\n" % val)
899
900 def get_config_children(config, args):
901     '''Gets the names of the children of the given parameter.
902        Useful only for completion mechanism
903     
904     :param config Config: The configuration where to read the values
905     :param args: The path in the config from which get the keys
906     '''
907     vals = []
908     rootkeys = config.keys()
909     
910     if len(args) == 0:
911         # no parameter returns list of root keys
912         vals = rootkeys
913     else:
914         parent = args[0]
915         pos = parent.rfind('.')
916         if pos < 0:
917             # Case where there is only on key as parameter.
918             # For example VARS
919             vals = [m for m in rootkeys if m.startswith(parent)]
920         else:
921             # Case where there is a part from a key
922             # for example VARS.us  (for VARS.user)
923             head = parent[0:pos]
924             tail = parent[pos+1:]
925             try:
926                 a = config.getByPath(head)
927                 if dir(a).__contains__('keys'):
928                     vals = map(lambda x: head + '.' + x,
929                                [m for m in a.keys() if m.startswith(tail)])
930             except:
931                 pass
932
933     for v in sorted(vals):
934         sys.stdout.write("%s\n" % v)
935
936 def description():
937     '''method that is called when salomeTools is called with --help option.
938     
939     :return: The text to display for the config command description.
940     :rtype: str
941     '''
942     return _("The config command allows manipulation "
943              "and operation on config files.\n\nexample:\nsat config "
944              "SALOME-master --info ParaView")
945     
946
947 def run(args, runner, logger):
948     '''method that is called when salomeTools is called with config parameter.
949     '''
950     # Parse the options
951     (options, args) = parser.parse_args(args)
952
953     # Only useful for completion mechanism : print the keys of the config
954     if options.schema:
955         get_config_children(runner.cfg, args)
956         return
957
958     # case : print a value of the config
959     if options.value:
960         if options.value == ".":
961             # if argument is ".", print all the config
962             for val in sorted(runner.cfg.keys()):
963                 print_value(runner.cfg, val, not options.no_label, logger)
964         else:
965             print_value(runner.cfg, options.value, not options.no_label, logger, 
966                         level=0, show_full_path=False)
967     
968     # case : print a debug value of the config
969     if options.debug:
970         if options.debug == ".":
971             # if argument is ".", print all the config
972             res = DBG.indent(DBG.getStrConfigDbg(runner.cfg))
973             logger.write("\nConfig of application %s:\n\n%s\n" % (runner.cfg.VARS.application, res))
974         else:
975             if options.debug[0] == ".": # accept ".PRODUCT.etc" as "PRODUCT.etc"
976               od = options.debug[1:]
977             else:
978               od = options.debug
979             try:
980               aCode = "a = runner.cfg.%s" % od
981               # https://stackoverflow.com/questions/15086040/behavior-of-exec-function-in-python-2-and-python-3
982               aDict = {"runner": runner}
983               exec(aCode, globals(), aDict)
984               # DBG.write("globals()", globals(), True)
985               # DBG.write("aDict", aDict, True)
986               res = DBG.indent(DBG.getStrConfigDbg(aDict["a"]))
987               logger.write("\nConfig.%s of application %s:\n\n%s\n" % (od, runner.cfg.VARS.application, res))
988             except Exception as e:
989               msg = "\nConfig.%s of application %s: Unknown pyconf key\n" % (od, runner.cfg.VARS.application)
990               logger.write(src.printcolors.printcError(msg), 1)
991
992     
993     # case : edit user pyconf file or application file
994     if options.edit:
995         editor = runner.cfg.USER.editor
996         if ('APPLICATION' not in runner.cfg and
997                        'open_application' not in runner.cfg): # edit user pyconf
998             usercfg =  osJoin(runner.cfg.VARS.personalDir,
999                                    'SAT.pyconf')
1000             logger.write(_("Opening %s\n" % usercfg), 3)
1001             src.system.show_in_editor(editor, usercfg, logger)
1002         else:
1003             # search for file <application>.pyconf and open it
1004             for path in runner.cfg.PATHS.APPLICATIONPATH:
1005                 pyconf_path =  osJoin(path,
1006                                     runner.cfg.VARS.application + ".pyconf")
1007                 if os.path.exists(pyconf_path):
1008                     logger.write(_("Opening %s\n" % pyconf_path), 3)
1009                     src.system.show_in_editor(editor, pyconf_path, logger)
1010                     break
1011     
1012     # case : give information about the product(s) in parameter
1013     if options.products:
1014       if options.info is not None:
1015         logger.warning('options.products %s overrides options.info %s' % (options.products, options.info))
1016       options.info = options.products
1017
1018     if options.info:
1019       # DBG.write("products", sorted(runner.cfg.APPLICATION.products.keys()), True)
1020       src.check_config_has_application(runner.cfg)
1021       taggedProducts = src.getProductNames(runner.cfg, options.info, logger)
1022       DBG.write("tagged products", sorted(taggedProducts))
1023       for prod in sorted(taggedProducts):
1024         if prod in runner.cfg.APPLICATION.products:
1025           try:
1026             if len(taggedProducts) > 1:
1027               logger.write("#################### ", 2)
1028             show_product_info(runner.cfg, prod, logger)
1029           except Exception as e:
1030             msg = "problem on product %s\n%s\n" % (prod, str(e))
1031             logger.error(msg)
1032           # return
1033         else:
1034           msg = _("%s is not a product of %s.\n") % \
1035                 (prod, runner.cfg.VARS.application)
1036           logger.warning(msg)
1037           #raise Exception(msg)
1038     
1039     # case : copy an existing <application>.pyconf 
1040     # to ~/.salomeTools/Applications/LOCAL_<application>.pyconf
1041     if options.copy:
1042         # product is required
1043         src.check_config_has_application( runner.cfg )
1044
1045         # get application file path 
1046         source = runner.cfg.VARS.application + '.pyconf'
1047         source_full_path = ""
1048         for path in runner.cfg.PATHS.APPLICATIONPATH:
1049             # ignore personal directory
1050             if path == runner.cfg.VARS.personalDir:
1051                 continue
1052             # loop on all directories that can have pyconf applications
1053             zz =  osJoin(path, source)
1054             if os.path.exists(zz):
1055                 source_full_path = zz
1056                 break
1057
1058         if len(source_full_path) == 0:
1059             raise src.SatException(_(
1060                         "Config file for product %s not found\n") % source)
1061         else:
1062             if len(args) > 0:
1063                 # a name is given as parameter, use it
1064                 dest = args[0]
1065             elif 'copy_prefix' in runner.cfg.INTERNAL.config:
1066                 # use prefix
1067                 dest = (runner.cfg.INTERNAL.config.copy_prefix 
1068                         + runner.cfg.VARS.application)
1069             else:
1070                 # use same name as source
1071                 dest = runner.cfg.VARS.application
1072                 
1073             # the full path
1074             dest_file =  osJoin(runner.cfg.VARS.personalDir,
1075                                      'Applications', dest + '.pyconf')
1076             if os.path.exists(dest_file):
1077                 raise src.SatException(_("A personal application"
1078                                          " '%s' already exists") % dest)
1079             
1080             # perform the copy
1081             shutil.copyfile(source_full_path, dest_file)
1082             logger.write(_("%s has been created.\n") % dest_file)
1083     
1084     # case : display all the available pyconf applications
1085     if options.list:
1086         lproduct = list()
1087         # search in all directories that can have pyconf applications
1088         for path in runner.cfg.PATHS.APPLICATIONPATH:
1089             # print a header
1090             if not options.no_label:
1091                 logger.write("------ %s\n" % src.printcolors.printcHeader(path))
1092
1093             if not os.path.exists(path):
1094                 logger.write(src.printcolors.printcError(_(
1095                                             "Directory not found")) + "\n")
1096             else:
1097                 for f in sorted(os.listdir(path)):
1098                     # ignore file that does not ends with .pyconf
1099                     if not f.endswith('.pyconf'):
1100                         continue
1101
1102                     appliname = f[:-len('.pyconf')]
1103                     if appliname not in lproduct:
1104                         lproduct.append(appliname)
1105                         if path.startswith(runner.cfg.VARS.personalDir) \
1106                                     and not options.no_label:
1107                             logger.write("%s*\n" % appliname)
1108                         else:
1109                             logger.write("%s\n" % appliname)
1110                             
1111             logger.write("\n")
1112
1113     # case: print all the products name of the application (internal use for completion)
1114     if options.completion:
1115         for product_name in runner.cfg.APPLICATION.products.keys():
1116             logger.write("%s\n" % product_name)
1117         
1118     # case : give a synthetic view of all patches used in the application
1119     if options.show_patchs:
1120         src.check_config_has_application(runner.cfg)
1121         # Print some informations
1122         logger.write(_('Patchs of application %s\n') %
1123                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1124         logger.write("\n", 2, False)
1125         show_patchs(runner.cfg, logger)
1126
1127     # case : give a synthetic view of all install directories used in the application
1128     if options.show_install:
1129         src.check_config_has_application(runner.cfg)
1130         # Print some informations
1131         logger.write(_('Installation directories of application %s\n') %
1132                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1133         logger.write("\n", 2, False)
1134         show_install_dir(runner.cfg, logger)
1135
1136     # case : give a synthetic view of all patches used in the application
1137     if options.show_properties:
1138         src.check_config_has_application(runner.cfg)
1139         # Print some informations
1140         logger.write(_('Properties of application %s\n') %
1141                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1142         logger.write("\n", 2, False)
1143         show_properties(runner.cfg, logger)
1144