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