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