Salome HOME
begin fix sat test
[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 \tWARNING the included files are not copied.
55 \tIf 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('~'),
122                                            '.salomeTools')
123         src.ensure_path_exists(var['personalDir'])
124
125         var['personal_applications_dir'] = os.path.join(var['personalDir'],
126                                                         "Applications")
127         src.ensure_path_exists(var['personal_applications_dir'])
128         
129         var['personal_products_dir'] = os.path.join(var['personalDir'],
130                                                     "products")
131         src.ensure_path_exists(var['personal_products_dir'])
132         
133         var['personal_archives_dir'] = os.path.join(var['personalDir'],
134                                                     "Archives")
135         src.ensure_path_exists(var['personal_archives_dir'])
136
137         var['personal_jobs_dir'] = os.path.join(var['personalDir'],
138                                                 "Jobs")
139         src.ensure_path_exists(var['personal_jobs_dir'])
140
141         var['personal_machines_dir'] = os.path.join(var['personalDir'],
142                                                     "Machines")
143         src.ensure_path_exists(var['personal_machines_dir'])
144
145         # read linux distributions dictionary
146         distrib_cfg = src.pyconf.Config(os.path.join(var['srcDir'],
147                                                       'internal_config',
148                                                       'distrib.pyconf'))
149         
150         # set platform parameters
151         dist_name = src.architecture.get_distribution(
152                                             codes=distrib_cfg.DISTRIBUTIONS)
153         dist_version = src.architecture.get_distrib_version(dist_name, 
154                                                     codes=distrib_cfg.VERSIONS)
155         dist = dist_name + dist_version
156         
157         var['dist_name'] = dist_name
158         var['dist_version'] = dist_version
159         var['dist'] = dist
160         var['python'] = src.architecture.get_python_version()
161
162         var['nb_proc'] = src.architecture.get_nb_proc()
163         node_name = platform.node()
164         var['node'] = node_name
165         var['hostname'] = node_name
166
167         # set date parameters
168         dt = datetime.datetime.now()
169         var['date'] = dt.strftime('%Y%m%d')
170         var['datehour'] = dt.strftime('%Y%m%d_%H%M%S')
171         var['hour'] = dt.strftime('%H%M%S')
172
173         var['command'] = str(command)
174         var['application'] = str(application)
175
176         # Root dir for temporary files 
177         var['tmp_root'] = os.sep + 'tmp' + os.sep + var['user']
178         # particular win case 
179         if src.architecture.is_windows() : 
180             var['tmp_root'] =  os.path.expanduser('~') + os.sep + 'tmp'
181         
182         return var
183
184     def get_command_line_overrides(self, options, sections):
185         '''get all the overwrites that are in the command line
186         
187         :param options: the options from salomeTools class 
188                         initialization (like -l5 or --overwrite)
189         :param sections str: The config section to overwrite.
190         :return: The list of all the overwrites to apply.
191         :rtype: list
192         '''
193         # when there are no options or not the overwrite option, 
194         # return an empty list
195         if options is None or options.overwrite is None:
196             return []
197         
198         over = []
199         for section in sections:
200             # only overwrite the sections that correspond to the option 
201             over.extend(filter(lambda l: l.startswith(section + "."), 
202                                options.overwrite))
203         return over
204
205     def get_config(self, application=None, options=None, command=None,
206                     datadir=None):
207         '''get the config from all the configuration files.
208         
209         :param application str: The application for which salomeTools is called.
210         :param options class Options: The general salomeToos
211                                       options (--overwrite or -l5, for example)
212         :param command str: The command that is called.
213         :param datadir str: The repository that contain 
214                             external data for salomeTools.
215         :return: The final config.
216         :rtype: class 'src.pyconf.Config'
217         '''        
218         
219         # create a ConfigMerger to handle merge
220         merger = src.pyconf.ConfigMerger()#MergeHandler())
221         
222         # create the configuration instance
223         cfg = src.pyconf.Config()
224         
225         # =====================================================================
226         # create VARS section
227         var = self._create_vars(application=application, command=command, datadir=datadir)
228         print("create_vars:\n%s" % PP.pformat(var))
229
230         # add VARS to config
231         cfg.VARS = src.pyconf.Mapping(cfg)
232         for variable in var:
233             cfg.VARS[variable] = var[variable]
234         
235         # apply overwrite from command line if needed
236         for rule in self.get_command_line_overrides(options, ["VARS"]):
237             exec('cfg.' + rule) # this cannot be factorized because of the exec
238         
239         # =====================================================================
240         # Load INTERNAL config
241         # read src/internal_config/salomeTools.pyconf
242         src.pyconf.streamOpener = ConfigOpener([
243                             os.path.join(cfg.VARS.srcDir, 'internal_config')])
244         try:
245             internal_cfg = src.pyconf.Config(open(os.path.join(cfg.VARS.srcDir, 
246                                     'internal_config', 'salomeTools.pyconf')))
247         except src.pyconf.ConfigError as e:
248             raise src.SatException(_("Error in configuration file:"
249                                      " salomeTools.pyconf\n  %(error)s") % \
250                                    {'error': str(e) })
251         
252         merger.merge(cfg, internal_cfg)
253
254         # apply overwrite from command line if needed
255         for rule in self.get_command_line_overrides(options, ["INTERNAL"]):
256             exec('cfg.' + rule) # this cannot be factorized because of the exec        
257                
258         # =====================================================================
259         # Load LOCAL config file
260         # search only in the data directory
261         src.pyconf.streamOpener = ConfigOpener([cfg.VARS.datadir])
262         try:
263             local_cfg = src.pyconf.Config(open(os.path.join(cfg.VARS.datadir, 
264                                                            'local.pyconf')),
265                                          PWD = ('LOCAL', cfg.VARS.datadir) )
266         except src.pyconf.ConfigError as e:
267             raise src.SatException(_("Error in configuration file: "
268                                      "local.pyconf\n  %(error)s") % \
269                 {'error': str(e) })
270         except IOError as error:
271             e = str(error)
272             raise src.SatException( e );
273         merger.merge(cfg, local_cfg)
274
275         # When the key is "default", put the default value
276         if cfg.LOCAL.base == "default":
277             cfg.LOCAL.base = os.path.abspath(
278                                         os.path.join(cfg.VARS.salometoolsway,
279                                                      "..",
280                                                      "BASE"))
281         if cfg.LOCAL.workdir == "default":
282             cfg.LOCAL.workdir = os.path.abspath(
283                                         os.path.join(cfg.VARS.salometoolsway,
284                                                      ".."))
285         if cfg.LOCAL.log_dir == "default":
286             cfg.LOCAL.log_dir = os.path.abspath(
287                                         os.path.join(cfg.VARS.salometoolsway,
288                                                      "..",
289                                                      "LOGS"))
290
291         if cfg.LOCAL.archive_dir == "default":
292             cfg.LOCAL.archive_dir = os.path.abspath(
293                                         os.path.join(cfg.VARS.salometoolsway,
294                                                      "..",
295                                                      "ARCHIVES"))
296
297         # apply overwrite from command line if needed
298         for rule in self.get_command_line_overrides(options, ["LOCAL"]):
299             exec('cfg.' + rule) # this cannot be factorized because of the exec
300         
301         # =====================================================================
302         # Load the PROJECTS
303         projects_cfg = src.pyconf.Config()
304         projects_cfg.addMapping("PROJECTS",
305                                 src.pyconf.Mapping(projects_cfg),
306                                 "The projects\n")
307         projects_cfg.PROJECTS.addMapping("projects",
308                                 src.pyconf.Mapping(cfg.PROJECTS),
309                                 "The projects definition\n")
310         
311         for project_pyconf_path in cfg.PROJECTS.project_file_paths:
312             if not os.path.exists(project_pyconf_path):
313                 msg = _("WARNING: The project file %s cannot be found. "
314                         "It will be ignored\n" % project_pyconf_path)
315                 sys.stdout.write(msg)
316                 continue
317             project_name = os.path.basename(
318                                     project_pyconf_path)[:-len(".pyconf")]
319             try:
320                 project_pyconf_dir = os.path.dirname(project_pyconf_path)
321                 project_cfg = src.pyconf.Config(open(project_pyconf_path),
322                                                 PWD=("", project_pyconf_dir))
323             except Exception as e:
324                 msg = _("ERROR: Error in configuration file: "
325                                  "%(file_path)s\n  %(error)s\n") % \
326                             {'file_path' : project_pyconf_path, 'error': str(e) }
327                 sys.stdout.write(msg)
328                 continue
329             projects_cfg.PROJECTS.projects.addMapping(project_name,
330                              src.pyconf.Mapping(projects_cfg.PROJECTS.projects),
331                              "The %s project\n" % project_name)
332             projects_cfg.PROJECTS.projects[project_name]=project_cfg
333             projects_cfg.PROJECTS.projects[project_name]["file_path"] = \
334                                                         project_pyconf_path
335                    
336         merger.merge(cfg, projects_cfg)
337
338         # apply overwrite from command line if needed
339         for rule in self.get_command_line_overrides(options, ["PROJECTS"]):
340             exec('cfg.' + rule) # this cannot be factorized because of the exec
341         
342         # =====================================================================
343         # Create the paths where to search the application configurations, 
344         # the product configurations, the products archives, 
345         # the jobs configurations and the machines configurations
346         cfg.addMapping("PATHS", src.pyconf.Mapping(cfg), "The paths\n")
347         cfg.PATHS["APPLICATIONPATH"] = src.pyconf.Sequence(cfg.PATHS)
348         cfg.PATHS.APPLICATIONPATH.append(cfg.VARS.personal_applications_dir, "")
349
350         
351         cfg.PATHS["PRODUCTPATH"] = src.pyconf.Sequence(cfg.PATHS)
352         cfg.PATHS.PRODUCTPATH.append(cfg.VARS.personal_products_dir, "")
353         cfg.PATHS["ARCHIVEPATH"] = src.pyconf.Sequence(cfg.PATHS)
354         cfg.PATHS.ARCHIVEPATH.append(cfg.VARS.personal_archives_dir, "")
355         cfg.PATHS["JOBPATH"] = src.pyconf.Sequence(cfg.PATHS)
356         cfg.PATHS.JOBPATH.append(cfg.VARS.personal_jobs_dir, "")
357         cfg.PATHS["MACHINEPATH"] = src.pyconf.Sequence(cfg.PATHS)
358         cfg.PATHS.MACHINEPATH.append(cfg.VARS.personal_machines_dir, "")
359
360         # initialise the path with local directory
361         cfg.PATHS["ARCHIVEPATH"].append(cfg.LOCAL.archive_dir, "")
362
363         # Loop over the projects in order to complete the PATHS variables
364         # as /data/tmpsalome/salome/prerequis/archives for example ARCHIVEPATH
365         for project in cfg.PROJECTS.projects:
366             for PATH in ["APPLICATIONPATH",
367                          "PRODUCTPATH",
368                          "ARCHIVEPATH", #comment this for default archive       #8646
369                          "JOBPATH",
370                          "MACHINEPATH"]:
371                 if PATH not in cfg.PROJECTS.projects[project]:
372                     continue
373                 cfg.PATHS[PATH].append(cfg.PROJECTS.projects[project][PATH], "")
374         
375         # apply overwrite from command line if needed
376         for rule in self.get_command_line_overrides(options, ["PATHS"]):
377             exec('cfg.' + rule) # this cannot be factorized because of the exec
378
379         # AT END append APPLI_TEST directory in APPLICATIONPATH, for unittest
380         appli_test_dir = os.path.join(satdir, "test", "APPLI_TEST")
381         if appli_test_dir not in cfg.PATHS.APPLICATIONPATH:
382           cfg.PATHS.APPLICATIONPATH.append(appli_test_dir, "unittest APPLI_TEST path")
383
384         # =====================================================================
385         # Load APPLICATION config file
386         if application is not None:
387             # search APPLICATION file in all directories in configPath
388             cp = cfg.PATHS.APPLICATIONPATH
389             src.pyconf.streamOpener = ConfigOpener(cp)
390             do_merge = True
391             try:
392                 application_cfg = src.pyconf.Config(application + '.pyconf')
393             except IOError as e:
394                 raise src.SatException(
395                    _("%s, use 'config --list' to get the list of available applications.") % e)
396             except src.pyconf.ConfigError as e:
397                 if (not ('-e' in parser.parse_args()[1]) 
398                                          or ('--edit' in parser.parse_args()[1]) 
399                                          and command == 'config'):
400                     raise src.SatException(_("Error in configuration file: "
401                                              "%(application)s.pyconf\n "
402                                              " %(error)s") % \
403                         { 'application': application, 'error': str(e) } )
404                 else:
405                     sys.stdout.write(src.printcolors.printcWarning(
406                                         "There is an error in the file"
407                                         " %s.pyconf.\n" % cfg.VARS.application))
408                     do_merge = False
409             except Exception as e:
410                 if (not ('-e' in parser.parse_args()[1]) 
411                                         or ('--edit' in parser.parse_args()[1]) 
412                                         and command == 'config'):
413                     sys.stdout.write(src.printcolors.printcWarning("%s\n" % str(e)))
414                     raise src.SatException(_("Error in configuration file:"
415                                              " %(application)s.pyconf\n") % \
416                         { 'application': application} )
417                 else:
418                     sys.stdout.write(src.printcolors.printcWarning(
419                                 "There is an error in the file"
420                                 " %s.pyconf. Opening the file with the"
421                                 " default viewer\n" % cfg.VARS.application))
422                     sys.stdout.write("The error:"
423                                  " %s\n" % src.printcolors.printcWarning(
424                                                                       str(e)))
425                     do_merge = False
426         
427             else:
428                 cfg['open_application'] = 'yes'
429
430         # =====================================================================
431         # Load product config files in PRODUCTS section
432         products_cfg = src.pyconf.Config()
433         products_cfg.addMapping("PRODUCTS",
434                                 src.pyconf.Mapping(products_cfg),
435                                 "The products\n")
436         if application is not None:
437             src.pyconf.streamOpener = ConfigOpener(cfg.PATHS.PRODUCTPATH)
438             for product_name in application_cfg.APPLICATION.products.keys():
439                 # Loop on all files that are in softsDir directory
440                 # and read their config
441                 product_file_name = product_name + ".pyconf"
442                 product_file_path = src.find_file_in_lpath(product_file_name, cfg.PATHS.PRODUCTPATH)
443                 if product_file_path:
444                     products_dir = os.path.dirname(product_file_path)
445                     try:
446                         prod_cfg = src.pyconf.Config(open(product_file_path),
447                                                      PWD=("", products_dir))
448                         prod_cfg.from_file = product_file_path
449                         products_cfg.PRODUCTS[product_name] = prod_cfg
450                     except Exception as e:
451                         msg = _(
452                             "WARNING: Error in configuration file"
453                             ": %(prod)s\n  %(error)s" % \
454                             {'prod' :  product_name, 'error': str(e) })
455                         sys.stdout.write(msg)
456             
457             merger.merge(cfg, products_cfg)
458             
459             # apply overwrite from command line if needed
460             for rule in self.get_command_line_overrides(options, ["PRODUCTS"]):
461                 exec('cfg.' + rule) # this cannot be factorized because of the exec
462             
463             if do_merge:
464                 merger.merge(cfg, application_cfg)
465
466                 # default launcher name ('salome')
467                 if ('profile' in cfg.APPLICATION and 
468                     'launcher_name' not in cfg.APPLICATION.profile):
469                     cfg.APPLICATION.profile.launcher_name = 'salome'
470
471                 # apply overwrite from command line if needed
472                 for rule in self.get_command_line_overrides(options,
473                                                              ["APPLICATION"]):
474                     # this cannot be factorized because of the exec
475                     exec('cfg.' + rule)
476             
477         # =====================================================================
478         # load USER config
479         self.set_user_config_file(cfg)
480         user_cfg_file = self.get_user_config_file()
481         user_cfg = src.pyconf.Config(open(user_cfg_file))
482         merger.merge(cfg, user_cfg)
483
484         # apply overwrite from command line if needed
485         for rule in self.get_command_line_overrides(options, ["USER"]):
486             exec('cfg.' + rule) # this cannot be factorize because of the exec
487         
488         return cfg
489
490     def set_user_config_file(self, config):
491         '''Set the user config file name and path.
492         If necessary, build it from another one or create it from scratch.
493         
494         :param config class 'src.pyconf.Config': The global config 
495                                                  (containing all pyconf).
496         '''
497         # get the expected name and path of the file
498         self.config_file_name = 'SAT.pyconf'
499         self.user_config_file_path = os.path.join(config.VARS.personalDir,
500                                                    self.config_file_name)
501         
502         # if pyconf does not exist, create it from scratch
503         if not os.path.isfile(self.user_config_file_path): 
504             self.create_config_file(config)
505     
506     def create_config_file(self, config):
507         '''This method is called when there are no user config file. 
508            It build it from scratch.
509         
510         :param config class 'src.pyconf.Config': The global config.
511         :return: the config corresponding to the file created.
512         :rtype: config class 'src.pyconf.Config'
513         '''
514         
515         cfg_name = self.get_user_config_file()
516
517         user_cfg = src.pyconf.Config()
518         #
519         user_cfg.addMapping('USER', src.pyconf.Mapping(user_cfg), "")
520
521         user_cfg.USER.addMapping('cvs_user', config.VARS.user,
522             "This is the user name used to access salome cvs base.\n")
523         user_cfg.USER.addMapping('svn_user', config.VARS.user,
524             "This is the user name used to access salome svn base.\n")
525         user_cfg.USER.addMapping('output_verbose_level', 3,
526             "This is the default output_verbose_level you want."
527             " 0=>no output, 5=>debug.\n")
528         user_cfg.USER.addMapping('publish_dir', 
529                                  os.path.join(os.path.expanduser('~'),
530                                  'websupport', 
531                                  'satreport'), 
532                                  "")
533         user_cfg.USER.addMapping('editor',
534                                  'vi', 
535                                  "This is the editor used to "
536                                  "modify configuration files\n")
537         user_cfg.USER.addMapping('browser', 
538                                  'firefox', 
539                                  "This is the browser used to "
540                                  "read html documentation\n")
541         user_cfg.USER.addMapping('pdf_viewer', 
542                                  'evince', 
543                                  "This is the pdf_viewer used "
544                                  "to read pdf documentation\n")
545 # CNC 25/10/17 : plus nécessaire a priori
546 #        user_cfg.USER.addMapping("base",
547 #                                 src.pyconf.Reference(
548 #                                            user_cfg,
549 #                                            src.pyconf.DOLLAR,
550 #                                            'workdir  + $VARS.sep + "BASE"'),
551 #                                 "The products installation base (could be "
552 #                                 "ignored if this key exists in the local.pyconf"
553 #                                 " file of salomTools).\n")
554                
555         # 
556         src.ensure_path_exists(config.VARS.personalDir)
557         src.ensure_path_exists(os.path.join(config.VARS.personalDir, 
558                                             'Applications'))
559
560         f = open(cfg_name, 'w')
561         user_cfg.__save__(f)
562         f.close()
563
564         return user_cfg   
565
566     def get_user_config_file(self):
567         '''Get the user config file
568         :return: path to the user config file.
569         :rtype: str
570         '''
571         if not self.user_config_file_path:
572             raise src.SatException(_("Error in get_user_config_file: "
573                                      "missing user config file path"))
574         return self.user_config_file_path     
575
576 def check_path(path, ext=[]):
577     '''Construct a text with the input path and "not found" if it does not
578        exist.
579     
580     :param path Str: the path to check.
581     :param ext List: An extension. Verify that the path extension 
582                      is in the list
583     :return: The string of the path with information
584     :rtype: Str
585     '''
586     # check if file exists
587     if not os.path.exists(path):
588         return "'%s'" % path + " " + src.printcolors.printcError(_(
589                                                             "** not found"))
590
591     # check extension
592     if len(ext) > 0:
593         fe = os.path.splitext(path)[1].lower()
594         if fe not in ext:
595             return "'%s'" % path + " " + src.printcolors.printcError(_(
596                                                         "** bad extension"))
597
598     return path
599
600 def show_product_info(config, name, logger):
601     '''Display on the terminal and logger information about a product.
602     
603     :param config Config: the global configuration.
604     :param name Str: The name of the product
605     :param logger Logger: The logger instance to use for the display
606     '''
607     
608     logger.write(_("%s is a product\n") % src.printcolors.printcLabel(name), 2)
609     pinfo = src.product.get_product_config(config, name)
610     
611     if "depend" in pinfo:
612         src.printcolors.print_value(logger, 
613                                     "depends on", 
614                                     ', '.join(pinfo.depend), 2)
615
616     if "opt_depend" in pinfo:
617         src.printcolors.print_value(logger, 
618                                     "optional", 
619                                     ', '.join(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     import src.architecture as ARCH
897     print("get_infosys %s " % PP.pformat(ARCH.get_infosys()))
898
899     # Parse the options
900     (options, args) = parser.parse_args(args)
901
902     # Only useful for completion mechanism : print the keys of the config
903     if options.schema:
904         get_config_children(runner.cfg, args)
905         return
906
907     # case : print a value of the config
908     if options.value:
909         if options.value == ".":
910             # if argument is ".", print all the config
911             for val in sorted(runner.cfg.keys()):
912                 print_value(runner.cfg, val, not options.no_label, logger)
913         else:
914             print_value(runner.cfg, options.value, not options.no_label, logger, 
915                         level=0, show_full_path=False)
916     
917     # case : print a debug value of the config
918     if options.debug:
919         if options.debug == ".":
920             # if argument is ".", print all the config
921             res = DBG.indent(DBG.getStrConfigDbg(runner.cfg))
922             logger.write("\nConfig of application %s:\n\n%s\n" % (runner.cfg.VARS.application, res))
923         else:
924             if options.debug[0] == ".": # accept ".PRODUCT.etc" as "PRODUCT.etc"
925               od = options.debug[1:]
926             else:
927               od = options.debug
928             exec("a = runner.cfg.%s" % od)
929             res = DBG.indent(DBG.getStrConfigDbg(a))
930             logger.write("\nConfig.%s of application %s:\n\n%s\n" % (od, runner.cfg.VARS.application, res))
931
932     
933     # case : edit user pyconf file or application file
934     if options.edit:
935         editor = runner.cfg.USER.editor
936         if ('APPLICATION' not in runner.cfg and
937                        'open_application' not in runner.cfg): # edit user pyconf
938             usercfg = os.path.join(runner.cfg.VARS.personalDir, 
939                                    'SAT.pyconf')
940             logger.write(_("Opening %s\n" % usercfg), 3)
941             src.system.show_in_editor(editor, usercfg, logger)
942         else:
943             # search for file <application>.pyconf and open it
944             for path in runner.cfg.PATHS.APPLICATIONPATH:
945                 pyconf_path = os.path.join(path, 
946                                     runner.cfg.VARS.application + ".pyconf")
947                 if os.path.exists(pyconf_path):
948                     logger.write(_("Opening %s\n" % pyconf_path), 3)
949                     src.system.show_in_editor(editor, pyconf_path, logger)
950                     break
951     
952     # case : give information about the product(s) in parameter
953     if options.products:
954       if options.info is not None:
955         logger.warning('options.products %s overrides options.info %s' % (options.products, options.info))
956       options.info = options.products
957
958     if options.info:
959       # DBG.write("products", sorted(runner.cfg.APPLICATION.products.keys()), True)
960       src.check_config_has_application(runner.cfg)
961       taggedProducts = src.getProductNames(runner.cfg, options.info, logger)
962       DBG.write("tagged products", sorted(taggedProducts))
963       for prod in sorted(taggedProducts):
964         if prod in runner.cfg.APPLICATION.products:
965           try:
966             if len(taggedProducts) > 1:
967               logger.write("#################### ", 2)
968             show_product_info(runner.cfg, prod, logger)
969           except Exception as e:
970             msg = "problem on product %s\n%s\n" % (prod, str(e))
971             logger.error(msg)
972           # return
973         else:
974           msg = _("%s is not a product of %s.\n") % \
975                 (prod, runner.cfg.VARS.application)
976           logger.warning(msg)
977           #raise Exception(msg)
978     
979     # case : copy an existing <application>.pyconf 
980     # to ~/.salomeTools/Applications/LOCAL_<application>.pyconf
981     if options.copy:
982         # product is required
983         src.check_config_has_application( runner.cfg )
984
985         # get application file path 
986         source = runner.cfg.VARS.application + '.pyconf'
987         source_full_path = ""
988         for path in runner.cfg.PATHS.APPLICATIONPATH:
989             # ignore personal directory
990             if path == runner.cfg.VARS.personalDir:
991                 continue
992             # loop on all directories that can have pyconf applications
993             zz = os.path.join(path, source)
994             if os.path.exists(zz):
995                 source_full_path = zz
996                 break
997
998         if len(source_full_path) == 0:
999             raise src.SatException(_(
1000                         "Config file for product %s not found\n") % source)
1001         else:
1002             if len(args) > 0:
1003                 # a name is given as parameter, use it
1004                 dest = args[0]
1005             elif 'copy_prefix' in runner.cfg.INTERNAL.config:
1006                 # use prefix
1007                 dest = (runner.cfg.INTERNAL.config.copy_prefix 
1008                         + runner.cfg.VARS.application)
1009             else:
1010                 # use same name as source
1011                 dest = runner.cfg.VARS.application
1012                 
1013             # the full path
1014             dest_file = os.path.join(runner.cfg.VARS.personalDir, 
1015                                      'Applications', dest + '.pyconf')
1016             if os.path.exists(dest_file):
1017                 raise src.SatException(_("A personal application"
1018                                          " '%s' already exists") % dest)
1019             
1020             # perform the copy
1021             shutil.copyfile(source_full_path, dest_file)
1022             logger.write(_("%s has been created.\n") % dest_file)
1023     
1024     # case : display all the available pyconf applications
1025     if options.list:
1026         lproduct = list()
1027         # search in all directories that can have pyconf applications
1028         for path in runner.cfg.PATHS.APPLICATIONPATH:
1029             # print a header
1030             if not options.no_label:
1031                 logger.write("------ %s\n" % src.printcolors.printcHeader(path))
1032
1033             if not os.path.exists(path):
1034                 logger.write(src.printcolors.printcError(_(
1035                                             "Directory not found")) + "\n")
1036             else:
1037                 for f in sorted(os.listdir(path)):
1038                     # ignore file that does not ends with .pyconf
1039                     if not f.endswith('.pyconf'):
1040                         continue
1041
1042                     appliname = f[:-len('.pyconf')]
1043                     if appliname not in lproduct:
1044                         lproduct.append(appliname)
1045                         if path.startswith(runner.cfg.VARS.personalDir) \
1046                                     and not options.no_label:
1047                             logger.write("%s*\n" % appliname)
1048                         else:
1049                             logger.write("%s\n" % appliname)
1050                             
1051             logger.write("\n")
1052
1053     # case: print all the products name of the application (internal use for completion)
1054     if options.completion:
1055         for product_name in runner.cfg.APPLICATION.products.keys():
1056             logger.write("%s\n" % product_name)
1057         
1058     # case : give a synthetic view of all patches used in the application
1059     if options.show_patchs:
1060         src.check_config_has_application(runner.cfg)
1061         # Print some informations
1062         logger.write(_('Patchs of application %s\n') %
1063                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1064         logger.write("\n", 2, False)
1065         show_patchs(runner.cfg, logger)
1066
1067     # case : give a synthetic view of all patches used in the application
1068     if options.show_properties:
1069         src.check_config_has_application(runner.cfg)
1070         # Print some informations
1071         logger.write(_('Properties of application %s\n') %
1072                     src.printcolors.printcLabel(runner.cfg.VARS.application), 3)
1073         logger.write("\n", 2, False)
1074         show_properties(runner.cfg, logger)
1075