Salome HOME
integration derniers ajustements pour windows
[tools/sat.git] / commands / config.py
1 #!/usr/bin/env python
2 #-*- coding:utf-8 -*-
3 #  Copyright (C) 2010-2012  CEA/DEN
4 #
5 #  This library is free software; you can redistribute it and/or
6 #  modify it under the terms of the GNU Lesser General Public
7 #  License as published by the Free Software Foundation; either
8 #  version 2.1 of the License.
9 #
10 #  This library is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 #  Lesser General Public License for more details.
14 #
15 #  You should have received a copy of the GNU Lesser General Public
16 #  License along with this library; if not, write to the Free Software
17 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18
19 import os
20 import sys
21 import platform
22 import datetime
23 import shutil
24 import gettext
25 import pprint as PP
26
27 import src
28 import src.logger as LOG
29 import src.debug as DBG
30 import src.callerName as CALN
31
32 logger = LOG.getDefaultLogger()
33
34 verbose = False # True for debug
35
36 # internationalization
37 satdir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
38 gettext.install('salomeTools', os.path.join(satdir, 'src', 'i18n'))
39
40 # Define all possible option for config command :  sat config <options>
41 parser = src.options.Options()
42 parser.add_option('v', 'value', 'string', 'value',
43     _("Optional: print the value of CONFIG_VARIABLE."))
44 parser.add_option('g', 'debug', 'string', 'debug',
45     _("Optional: print the debugging mode value of CONFIG_VARIABLE."))
46 parser.add_option('e', 'edit', 'boolean', 'edit',
47     _("Optional: edit the product configuration file."))
48 parser.add_option('i', 'info', 'list2', 'info',
49     _("Optional: get information on product(s). This option accepts a comma separated list."))
50 parser.add_option('p', 'products', 'list2', 'products',
51     _("Optional: same as --info, for convenience."))
52 parser.add_option('l', 'list', 'boolean', 'list',
53     _("Optional: list all available applications."))
54 parser.add_option('', 'show_patchs', 'boolean', 'show_patchs',
55     _("Optional: synthetic list of all patches used in the application"))
56 parser.add_option('', 'show_install', 'boolean', 'show_install',
57     _("Optional: synthetic list of all install directories in the application"))
58 parser.add_option('', 'show_properties', 'boolean', 'show_properties',
59     _("Optional: synthetic list of all properties used in the application"))
60 parser.add_option('c', 'copy', 'boolean', 'copy',
61     _("""Optional: copy a config file to the personal config files directory.
62 WARNING: the included files are not copied.
63 If a name is given the new config file takes the given name."""))
64 parser.add_option('n', 'no_label', 'boolean', 'no_label',
65     _("Internal use: do not print labels, Works only with --value and --list."))
66 parser.add_option('', 'completion', 'boolean', 'completion',
67     _("Internal use: print only keys, works only with --value."))
68 parser.add_option('s', 'schema', 'boolean', 'schema',
69     _("Internal use."))
70
71 def osJoin(*args):
72   """
73   shortcut wrapper to os.path.join
74   plus optionaly print for debug
75   """
76   res = os.path.realpath(os.path.join(*args))
77   if verbose:
78     if True: # ".pyconf" in res:
79       logger.info("osJoin %-80s in %s" % (res, CALN.caller_name(1)))
80   return res
81
82 class ConfigOpener:
83     '''Class that helps to find an application pyconf 
84        in all the possible directories (pathList)
85     '''
86     def __init__(self, pathList):
87         '''Initialization
88         
89         :param pathList list: The list of paths where to search a pyconf.
90         '''
91         self.pathList = pathList
92         if verbose:
93           for path in pathList:
94             if not os.path.isdir(path):
95               logger.warning("ConfigOpener inexisting directory: %s" % path)
96
97     def __call__(self, name):
98         if os.path.isabs(name):
99             return src.pyconf.ConfigInputStream(open(name, 'rb'))
100         else:
101             return src.pyconf.ConfigInputStream(open(osJoin(self.get_path(name), name), 'rb'))
102         raise IOError(_("Configuration file '%s' not found") % name)
103
104     def get_path( self, name ):
105         '''The method that returns the entire path of the pyconf searched
106         returns first found in self.pathList directories
107
108         :param name str: The name of the searched pyconf.
109         '''
110         for path in self.pathList:
111             if os.path.exists(osJoin(path, name)):
112                 return path
113         raise IOError(_("Configuration file '%s' not found") % name)
114
115 class ConfigManager:
116     '''Class that manages the read of all the configuration files of salomeTools
117     '''
118     def __init__(self, datadir=None):
119         pass
120
121     def _create_vars(self, application=None, command=None, datadir=None):
122         '''Create a dictionary that stores all information about machine,
123            user, date, repositories, etc...
124         
125         :param application str: The application for which salomeTools is called.
126         :param command str: The command that is called.
127         :param datadir str: The repository that contain external data 
128                             for salomeTools.
129         :return: The dictionary that stores all information.
130         :rtype: dict
131         '''
132         var = {}      
133         var['user'] = src.architecture.get_user()
134         var['salometoolsway'] = os.path.dirname( os.path.dirname(os.path.abspath(__file__)))
135         var['srcDir'] =  osJoin(var['salometoolsway'], 'src')
136         var['internal_dir'] =  osJoin(var['srcDir'], 'internal_config')
137         var['sep']= os.path.sep
138         if src.architecture.is_windows():
139           var['scriptExtension'] = '.bat'
140         else:
141           var['scriptExtension'] = '.sh'
142         
143         # datadir has a default location
144         var['datadir'] =  osJoin(var['salometoolsway'], 'data')
145         if datadir is not None:
146             var['datadir'] = datadir
147
148         var['personalDir'] =  osJoin(os.path.expanduser('~'), '.salomeTools')
149         src.ensure_path_exists(var['personalDir'])
150
151         var['personal_applications_dir'] =  osJoin(var['personalDir'], "Applications")
152         src.ensure_path_exists(var['personal_applications_dir'])
153         
154         var['personal_products_dir'] =  osJoin(var['personalDir'], "products")
155         src.ensure_path_exists(var['personal_products_dir'])
156         
157         var['personal_archives_dir'] =  osJoin(var['personalDir'], "Archives")
158         src.ensure_path_exists(var['personal_archives_dir'])
159
160         var['personal_jobs_dir'] =  osJoin(var['personalDir'], "Jobs")
161         src.ensure_path_exists(var['personal_jobs_dir'])
162
163         var['personal_machines_dir'] =  osJoin(var['personalDir'], "Machines")
164         src.ensure_path_exists(var['personal_machines_dir'])
165
166         # read linux distributions dictionary
167         distrib_cfg = src.pyconf.Config( osJoin(var['srcDir'], 'internal_config', 'distrib.pyconf'))
168         
169         # set platform parameters
170         dist_name = src.architecture.get_distribution(codes=distrib_cfg.DISTRIBUTIONS)
171         dist_version = src.architecture.get_distrib_version(dist_name)
172         dist_version_full = src.architecture.get_version_XY()
173         dist = dist_name + dist_version
174         
175         var['dist_name'] = dist_name
176         var['dist_version'] = dist_version
177         var['dist'] = dist
178         var['dist_ref'] = dist_name + dist_version_full
179         var['python'] = src.architecture.get_python_version()
180
181         var['nb_proc'] = src.architecture.get_nb_proc()
182         node_name = platform.node()
183         var['node'] = node_name
184         var['hostname'] = node_name
185
186         # set date parameters
187         dt = datetime.datetime.now()
188         var['date'] = dt.strftime('%Y%m%d')
189         var['datehour'] = dt.strftime('%Y%m%d_%H%M%S')
190         var['hour'] = dt.strftime('%H%M%S')
191
192         var['command'] = str(command)
193         var['application'] = str(application)
194
195         # Root dir for temporary files 
196         var['tmp_root'] = os.sep + 'tmp' + os.sep + var['user']
197         # particular win case 
198         if src.architecture.is_windows() : 
199             var['tmp_root'] =  os.path.expanduser('~') + os.sep + 'tmp'
200         
201         return var
202
203     def get_command_line_overrides(self, options, sections):
204         '''get all the overwrites that are in the command line
205         
206         :param options: the options from salomeTools class 
207                         initialization (like -l5 or --overwrite)
208         :param sections str: The config section to overwrite.
209         :return: The list of all the overwrites to apply.
210         :rtype: list
211         '''
212         # when there are no options or not the overwrite option, 
213         # return an empty list
214         if options is None or options.overwrite is None:
215             return []
216         
217         over = []
218         for section in sections:
219             # only overwrite the sections that correspond to the option 
220             over.extend(filter(lambda l: l.startswith(section + "."), 
221                                options.overwrite))
222         return over
223
224     def get_config(self, application=None, options=None, command=None,
225                     datadir=None):
226         '''get the config from all the configuration files.
227         
228         :param application str: The application for which salomeTools is called.
229         :param options class Options: The general salomeToos
230                                       options (--overwrite or -l5, for example)
231         :param command str: The command that is called.
232         :param datadir str: The repository that contain 
233                             external data for salomeTools.
234         :return: The final config.
235         :rtype: class 'src.pyconf.Config'
236         '''        
237         
238         # create a ConfigMerger to handle merge
239         merger = src.pyconf.ConfigMerger()#MergeHandler())
240         
241         # create the configuration instance
242         cfg = src.pyconf.Config()
243         
244         # =====================================================================
245         # create VARS section
246         var = self._create_vars(application=application, command=command, datadir=datadir)
247         # DBG.write("create_vars", var, DBG.isDeveloper())
248
249         # add VARS to config
250         cfg.VARS = src.pyconf.Mapping(cfg)
251         for variable in var:
252             cfg.VARS[variable] = var[variable]
253         
254         # apply overwrite from command line if needed
255         for rule in self.get_command_line_overrides(options, ["VARS"]):
256             exec('cfg.' + rule) # this cannot be factorized because of the exec
257         
258         # =====================================================================
259         # Load INTERNAL config
260         # read src/internal_config/salomeTools.pyconf
261         src.pyconf.streamOpener = ConfigOpener([
262                              osJoin(cfg.VARS.srcDir, 'internal_config')])
263         try:
264             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   oneOrMore = False
814   for product in sorted(config.APPLICATION.products):
815     try:
816       product_info = src.product.get_product_config(config, product)
817       done = False
818       try:
819         for i in product_info.properties:
820           if not done:
821             logger.write("%s:\n" % product, 1)
822             done = True
823           oneOrMore = True
824           logger.write(src.printcolors.printcInfo("    %s\n" % i), 1)
825       except Exception as e:
826         pass
827     except Exception as e:
828       # logger.write(src.printcolors.printcInfo("    %s\n" % "no properties"), 1)
829       msg = "problem on product %s\n%s\n" % (product, e)
830       logger.error(msg)
831
832   if oneOrMore:
833     logger.write("\n", 1)
834   else:
835     logger.write("No properties found\n", 1)
836
837 def print_value(config, path, show_label, logger, level=0, show_full_path=False):
838     '''Prints a value from the configuration. Prints recursively the values 
839        under the initial path.
840     
841     :param config class 'src.pyconf.Config': The configuration 
842                                              from which the value is displayed.
843     :param path str : the path in the configuration of the value to print.
844     :param show_label boolean: if True, do a basic display. 
845                                (useful for bash completion)
846     :param logger Logger: the logger instance
847     :param level int: The number of spaces to add before display.
848     :param show_full_path :
849     '''            
850     
851     # Make sure that the path does not ends with a point
852     if path.endswith('.'):
853         path = path[:-1]
854     
855     # display all the path or not
856     if show_full_path:
857         vname = path
858     else:
859         vname = path.split('.')[-1]
860
861     # number of spaces before the display
862     tab_level = "  " * level
863     
864     # call to the function that gets the value of the path.
865     try:
866         val = config.getByPath(path)
867     except Exception as e:
868         logger.write(tab_level)
869         logger.write("%s: ERROR %s\n" % (src.printcolors.printcLabel(vname), 
870                                          src.printcolors.printcError(str(e))))
871         return
872
873     # in this case, display only the value
874     if show_label:
875         logger.write(tab_level)
876         logger.write("%s: " % src.printcolors.printcLabel(vname))
877
878     # The case where the value has under values, 
879     # do a recursive call to the function
880     if dir(val).__contains__('keys'):
881         if show_label: logger.write("\n")
882         for v in sorted(val.keys()):
883             print_value(config, path + '.' + v, show_label, logger, level + 1)
884     elif val.__class__ == src.pyconf.Sequence or isinstance(val, list): 
885         # in this case, value is a list (or a Sequence)
886         if show_label: logger.write("\n")
887         index = 0
888         for v in val:
889             print_value(config, path + "[" + str(index) + "]", 
890                         show_label, logger, level + 1)
891             index = index + 1
892     else: # case where val is just a str
893         logger.write("%s\n" % val)
894
895 def get_config_children(config, args):
896     '''Gets the names of the children of the given parameter.
897        Useful only for completion mechanism
898     
899     :param config Config: The configuration where to read the values
900     :param args: The path in the config from which get the keys
901     '''
902     vals = []
903     rootkeys = config.keys()
904     
905     if len(args) == 0:
906         # no parameter returns list of root keys
907         vals = rootkeys
908     else:
909         parent = args[0]
910         pos = parent.rfind('.')
911         if pos < 0:
912             # Case where there is only on key as parameter.
913             # For example VARS
914             vals = [m for m in rootkeys if m.startswith(parent)]
915         else:
916             # Case where there is a part from a key
917             # for example VARS.us  (for VARS.user)
918             head = parent[0:pos]
919             tail = parent[pos+1:]
920             try:
921                 a = config.getByPath(head)
922                 if dir(a).__contains__('keys'):
923                     vals = map(lambda x: head + '.' + x,
924                                [m for m in a.keys() if m.startswith(tail)])
925             except:
926                 pass
927
928     for v in sorted(vals):
929         sys.stdout.write("%s\n" % v)
930
931 def description():
932     '''method that is called when salomeTools is called with --help option.
933     
934     :return: The text to display for the config command description.
935     :rtype: str
936     '''
937     return _("The config command allows manipulation "
938              "and operation on config files.\n\nexample:\nsat config "
939              "SALOME-master --info ParaView")
940     
941
942 def run(args, runner, logger):
943     '''method that is called when salomeTools is called with config parameter.
944     '''
945     # Parse the options
946     (options, args) = parser.parse_args(args)
947
948     # Only useful for completion mechanism : print the keys of the config
949     if options.schema:
950         get_config_children(runner.cfg, args)
951         return
952
953     # case : print a value of the config
954     if options.value:
955         if options.value == ".":
956             # if argument is ".", print all the config
957             for val in sorted(runner.cfg.keys()):
958                 print_value(runner.cfg, val, not options.no_label, logger)
959         else:
960             print_value(runner.cfg, options.value, not options.no_label, logger, 
961                         level=0, show_full_path=False)
962     
963     # case : print a debug value of the config
964     if options.debug:
965         if options.debug == ".":
966             # if argument is ".", print all the config
967             res = DBG.indent(DBG.getStrConfigDbg(runner.cfg))
968             logger.write("\nConfig of application %s:\n\n%s\n" % (runner.cfg.VARS.application, res))
969         else:
970             if options.debug[0] == ".": # accept ".PRODUCT.etc" as "PRODUCT.etc"
971               od = options.debug[1:]
972             else:
973               od = options.debug
974             try:
975               aCode = "a = runner.cfg.%s" % od
976               # https://stackoverflow.com/questions/15086040/behavior-of-exec-function-in-python-2-and-python-3
977               aDict = {"runner": runner}
978               exec(aCode, globals(), aDict)
979               # DBG.write("globals()", globals(), True)
980               # DBG.write("aDict", aDict, True)
981               res = DBG.indent(DBG.getStrConfigDbg(aDict["a"]))
982               logger.write("\nConfig.%s of application %s:\n\n%s\n" % (od, runner.cfg.VARS.application, res))
983             except Exception as e:
984               msg = "\nConfig.%s of application %s: Unknown pyconf key\n" % (od, runner.cfg.VARS.application)
985               logger.write(src.printcolors.printcError(msg), 1)
986
987     
988     # case : edit user pyconf file or application file
989     if options.edit:
990         editor = runner.cfg.USER.editor
991         if ('APPLICATION' not in runner.cfg and
992                        'open_application' not in runner.cfg): # edit user pyconf
993             usercfg =  osJoin(runner.cfg.VARS.personalDir,
994                                    'SAT.pyconf')
995             logger.write(_("Opening %s\n" % usercfg), 3)
996             src.system.show_in_editor(editor, usercfg, logger)
997         else:
998             # search for file <application>.pyconf and open it
999             for path in runner.cfg.PATHS.APPLICATIONPATH:
1000                 pyconf_path =  osJoin(path,
1001                                     runner.cfg.VARS.application + ".pyconf")
1002                 if os.path.exists(pyconf_path):
1003                     logger.write(_("Opening %s\n" % pyconf_path), 3)
1004                     src.system.show_in_editor(editor, pyconf_path, logger)
1005                     break
1006     
1007     # case : give information about the product(s) in parameter
1008     if options.products:
1009       if options.info is not None:
1010         logger.warning('options.products %s overrides options.info %s' % (options.products, options.info))
1011       options.info = options.products
1012
1013     if options.info:
1014       # DBG.write("products", sorted(runner.cfg.APPLICATION.products.keys()), True)
1015       src.check_config_has_application(runner.cfg)
1016       taggedProducts = src.getProductNames(runner.cfg, options.info, logger)
1017       DBG.write("tagged products", sorted(taggedProducts))
1018       for prod in sorted(taggedProducts):
1019         if prod in runner.cfg.APPLICATION.products:
1020           try:
1021             if len(taggedProducts) > 1:
1022               logger.write("#################### ", 2)
1023             show_product_info(runner.cfg, prod, logger)
1024           except Exception as e:
1025             msg = "problem on product %s\n%s\n" % (prod, str(e))
1026             logger.error(msg)
1027           # return
1028         else:
1029           msg = _("%s is not a product of %s.\n") % \
1030                 (prod, runner.cfg.VARS.application)
1031           logger.warning(msg)
1032           #raise Exception(msg)
1033     
1034     # case : copy an existing <application>.pyconf 
1035     # to ~/.salomeTools/Applications/LOCAL_<application>.pyconf
1036     if options.copy:
1037         # product is required
1038         src.check_config_has_application( runner.cfg )
1039
1040         # get application file path 
1041         source = runner.cfg.VARS.application + '.pyconf'
1042         source_full_path = ""
1043         for path in runner.cfg.PATHS.APPLICATIONPATH:
1044             # ignore personal directory
1045             if path == runner.cfg.VARS.personalDir:
1046                 continue
1047             # loop on all directories that can have pyconf applications
1048             zz =  osJoin(path, source)
1049             if os.path.exists(zz):
1050                 source_full_path = zz
1051                 break
1052
1053         if len(source_full_path) == 0:
1054             raise src.SatException(_(
1055                         "Config file for product %s not found\n") % source)
1056         else:
1057             if len(args) > 0:
1058                 # a name is given as parameter, use it
1059                 dest = args[0]
1060             elif 'copy_prefix' in runner.cfg.INTERNAL.config:
1061                 # use prefix
1062                 dest = (runner.cfg.INTERNAL.config.copy_prefix 
1063                         + runner.cfg.VARS.application)
1064             else:
1065                 # use same name as source
1066                 dest = runner.cfg.VARS.application
1067                 
1068             # the full path
1069             dest_file =  osJoin(runner.cfg.VARS.personalDir,
1070                                      'Applications', dest + '.pyconf')
1071             if os.path.exists(dest_file):
1072                 raise src.SatException(_("A personal application"
1073                                          " '%s' already exists") % dest)
1074             
1075             # perform the copy
1076             shutil.copyfile(source_full_path, dest_file)
1077             logger.write(_("%s has been created.\n") % dest_file)
1078     
1079     # case : display all the available pyconf applications
1080     if options.list:
1081         lproduct = list()
1082         # search in all directories that can have pyconf applications
1083         for path in runner.cfg.PATHS.APPLICATIONPATH:
1084             # print a header
1085             if not options.no_label:
1086                 logger.write("------ %s\n" % src.printcolors.printcHeader(path))
1087
1088             if not os.path.exists(path):
1089                 logger.write(src.printcolors.printcError(_(
1090                                             "Directory not found")) + "\n")
1091             else:
1092                 for f in sorted(os.listdir(path)):
1093                     # ignore file that does not ends with .pyconf
1094                     if not f.endswith('.pyconf'):
1095                         continue
1096
1097                     appliname = f[:-len('.pyconf')]
1098                     if appliname not in lproduct:
1099                         lproduct.append(appliname)
1100                         if path.startswith(runner.cfg.VARS.personalDir) \
1101                                     and not options.no_label:
1102                             logger.write("%s*\n" % appliname)
1103                         else:
1104                             logger.write("%s\n" % appliname)
1105                             
1106             logger.write("\n")
1107
1108     # case: print all the products name of the application (internal use for completion)
1109     if options.completion:
1110         for product_name in runner.cfg.APPLICATION.products.keys():
1111             logger.write("%s\n" % product_name)
1112         
1113     # case : give a synthetic view of all patches used in the application
1114     if options.show_patchs:
1115         src.check_config_has_application(runner.cfg)
1116         # Print some informations
1117         logger.write(_('Patchs of application %s\n') %
1118                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1119         logger.write("\n", 2, False)
1120         show_patchs(runner.cfg, logger)
1121
1122     # case : give a synthetic view of all install directories used in the application
1123     if options.show_install:
1124         src.check_config_has_application(runner.cfg)
1125         # Print some informations
1126         logger.write(_('Installation directories of application %s\n') %
1127                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1128         logger.write("\n", 2, False)
1129         show_install_dir(runner.cfg, logger)
1130
1131     # case : give a synthetic view of all patches used in the application
1132     if options.show_properties:
1133         src.check_config_has_application(runner.cfg)
1134         # Print some informations
1135         logger.write(_('Properties of application %s\n') %
1136                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1137         logger.write("\n", 2, False)
1138         show_properties(runner.cfg, logger)
1139