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