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