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