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