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