Salome HOME
architecture finalization and commentaries for main executable file
[tools/sat.git] / src / 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 glob
24 import re
25 import shutil
26
27 import common
28
29 # Define all possible option for config command :  sat config <options>
30 parser = common.options.Options()
31 parser.add_option('v', 'value', 'string', 'value', "print the value of CONFIG_VARIABLE.")
32
33 '''
34 class MergeHandler:
35     def __init__(self):
36         pass
37
38     def __call__(self, map1, map2, key):
39         if '__overwrite__' in map2 and key in map2.__overwrite__:
40             return "overwrite"
41         else:
42             return common.config_pyconf.overwriteMergeResolve(map1, map2, key)
43 '''
44
45 class ConfigOpener:
46     def __init__(self, pathList):
47         self.pathList = pathList
48
49     def __call__(self, name):
50         if os.path.isabs(name):
51             return common.config_pyconf.ConfigInputStream(open(name, 'rb'))
52         else:
53             return common.config_pyconf.ConfigInputStream( open(os.path.join( self.getPath(name), name ), 'rb') )
54         raise IOError(_("Configuration file '%s' not found") % name)
55
56     def getPath( self, name ):
57         for path in self.pathList:
58             if os.path.exists(os.path.join(path, name)):
59                 return path
60         raise IOError(_("Configuration file '%s' not found") % name)
61
62 class ConfigManager:
63     '''Class that manages the read of all the configuration files of salomeTools
64     '''
65     def __init__(self, dataDir=None):
66         pass
67
68     def _create_vars(self, application=None, command=None, dataDir=None):
69         '''Create a dictionary that stores all information about machine, user, date, repositories, etc...
70         :param application str: The application for which salomeTools is called.
71         :param command str: The command that is called.
72         :param dataDir str: The repository that contain external data for salomeTools.
73         :return: The dictionary that stores all information.
74         :rtype: dict
75         '''
76         var = {}      
77         var['user'] = common.architecture.get_user()
78         var['salometoolsway'] = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
79         var['srcDir'] = os.path.join(var['salometoolsway'], 'src')
80         var['sep']= os.path.sep
81         
82         # dataDir has a default location
83         var['dataDir'] = os.path.join(var['salometoolsway'], 'data')
84         if dataDir is not None:
85             var['dataDir'] = dataDir
86
87         var['personalDir'] = os.path.join(os.path.expanduser('~'), '.salomeTools')
88
89         # read linux distributions dictionary
90         distrib_cfg = common.config_pyconf.Config(os.path.join(var['dataDir'], "distrib.pyconf"))
91
92         # set platform parameters
93         dist_name = common.architecture.get_distribution(codes=distrib_cfg.DISTRIBUTIONS)
94         dist_version = common.architecture.get_distrib_version(dist_name, codes=distrib_cfg.VERSIONS)
95         dist = dist_name + dist_version
96         
97         # Forcing architecture with env variable ARCH on Windows
98         if common.architecture.is_windows() and "ARCH" in os.environ :
99             bitsdict={"Win32":"32","Win64":"64"}
100             nb_bits = bitsdict[os.environ["ARCH"]]
101         else :
102             nb_bits = common.architecture.get_nb_bit()
103
104         var['dist_name'] = dist_name
105         var['dist_version'] = dist_version
106         var['dist'] = dist
107         var['arch'] = dist + '_' + nb_bits
108         var['bits'] = nb_bits
109         var['python'] = common.architecture.get_python_version()
110
111         var['nb_proc'] = common.architecture.get_nb_proc()
112         node_name = platform.node()
113         var['node'] = node_name
114         var['hostname'] = node_name
115         # particular win case 
116         if common.architecture.is_windows() :
117             var['hostname'] = node_name+'-'+nb_bits
118
119         # set date parameters
120         dt = datetime.datetime.now()
121         var['date'] = dt.strftime('%Y%m%d')
122         var['datehour'] = dt.strftime('%Y%m%d_%H%M%S')
123         var['hour'] = dt.strftime('%H%M%S')
124
125         var['command'] = str(command)
126         var['application'] = str(application)
127
128         # Root dir for temporary files 
129         var['tmp_root'] = os.sep + 'tmp' + os.sep + var['user']
130         # particular win case 
131         if common.architecture.is_windows() : 
132             var['tmp_root'] =  os.path.expanduser('~') + os.sep + 'tmp'
133         
134         return var
135
136     def get_command_line_overrides(self, options, sections):
137         '''get all the overwrites that are in the command line
138         :param options : the options from salomeTools class initialization (like -l5 or --overwrite)
139         :param sections str: The config section to overwrite.
140         :return: The list of all the overwrites to apply.
141         :rtype: list
142         '''
143         # when there are no options or not the overwrite option, return an empty list
144         if options is None or options.overwrite is None:
145             return []
146         
147         over = []
148         for section in sections:
149             # only overwrite the sections that correspond to the option 
150             over.extend(filter(lambda l: l.startswith(section + "."), options.overwrite))
151         return over
152
153     def getConfig(self, application=None, options=None, command=None, dataDir=None):
154         '''get the config from all the configuration files.
155         :param application str: The application for which salomeTools is called.
156         :param options TODO
157         :param command str: The command that is called.
158         :param dataDir str: The repository that contain external data for salomeTools.
159         :return: The final config.
160         :rtype: class 'common.config_pyconf.Config'
161         '''        
162         
163         # create a ConfigMerger to handle merge
164         merger = common.config_pyconf.ConfigMerger()#MergeHandler())
165         
166         # create the configuration instance
167         cfg = common.config_pyconf.Config()
168         
169         # =======================================================================================
170         # create VARS section
171         var = self._create_vars(application=application, command=command, dataDir=dataDir)
172         # add VARS to config
173         cfg.VARS = common.config_pyconf.Mapping(cfg)
174         for variable in var:
175             cfg.VARS[variable] = var[variable]
176
177         for rule in self.get_command_line_overrides(options, ["VARS"]):
178             exec('cfg.' + rule) # this cannot be factorized because of the exec
179         
180         # =======================================================================================
181         # Load INTERNAL config
182         # read src/common/internal_config/salomeTools.pyconf
183         common.config_pyconf.streamOpener = ConfigOpener([os.path.join(cfg.VARS.srcDir, 'common', 'internal_config')])
184         try:
185             internal_cfg = common.config_pyconf.Config(open(os.path.join(cfg.VARS.srcDir, 'common', 'internal_config', 'salomeTools.pyconf')))
186         except common.config_pyconf.ConfigError as e:
187             raise common.SatException(_("Error in configuration file: salomeTools.pyconf\n  %(error)s") % \
188                 {'error': str(e) })
189
190         merger.merge(cfg, internal_cfg)
191
192         for rule in self.get_command_line_overrides(options, ["INTERNAL"]):
193             exec('cfg.' + rule) # this cannot be factorized because of the exec        
194         
195         # =======================================================================================
196         # Load SITE config file
197         # search only in the data directory
198         common.config_pyconf.streamOpener = ConfigOpener([cfg.VARS.dataDir])
199         try:
200             site_cfg = common.config_pyconf.Config(open(os.path.join(cfg.VARS.dataDir, 'site.pyconf')))
201         except common.config_pyconf.ConfigError as e:
202             raise common.SatException(_("Error in configuration file: site.pyconf\n  %(error)s") % \
203                 {'error': str(e) })
204         except IOError as error:
205             e = str(error)
206             if "site.pyconf" in e :
207                 e += "\nYou can copy data" + cfg.VARS.sep + "site.template.pyconf to data" + cfg.VARS.sep + "site.pyconf and edit the file"
208             raise common.SatException( e );
209
210         merger.merge(cfg, site_cfg)
211
212         for rule in self.get_command_line_overrides(options, ["SITE"]):
213             exec('cfg.' + rule) # this cannot be factorized because of the exec
214         
215         # =======================================================================================
216         # Load APPLICATION config file
217         if application is not None:
218             # search APPLICATION file in all directories in configPath
219             cp = cfg.SITE.config.configPath
220             common.config_pyconf.streamOpener = ConfigOpener(cp)
221             try:
222                 application_cfg = common.config_pyconf.Config(application + '.pyconf')
223             except IOError as e:
224                 raise common.SatException(_("%s, use 'config --list' to get the list of available applications.") %e)
225             except common.config_pyconf.ConfigError as e:
226                 raise common.SatException(_("Error in configuration file: %(application)s.pyconf\n  %(error)s") % \
227                     { 'application': application, 'error': str(e) } )
228
229             merger.merge(cfg, application_cfg)
230
231             for rule in self.get_command_line_overrides(options, ["APPLICATION"]):
232                 exec('cfg.' + rule) # this cannot be factorized because of the exec
233         
234         # =======================================================================================
235         # load USER config
236         self.setUserConfigFile(cfg)
237         user_cfg_file = self.getUserConfigFile()
238         user_cfg = common.config_pyconf.Config(open(user_cfg_file))
239         merger.merge(cfg, user_cfg)
240
241         for rule in self.get_command_line_overrides(options, ["USER"]):
242             exec('cfg.' + rule) # this cannot be factorize because of the exec
243
244         return cfg
245
246     def setUserConfigFile(self, config):
247         '''Set the user config file name and path.
248         If necessary, build it from another one or create it from scratch.
249         '''
250         if not config:
251             raise common.SatException(_("Error in setUserConfigFile: config is None"))
252         sat_version = config.INTERNAL.sat_version
253         self.config_file_name = 'salomeTools-%s.pyconf'%sat_version
254         self.user_config_file_path = os.path.join(config.VARS.personalDir, self.config_file_name)
255         if not os.path.isfile(self.user_config_file_path):
256             # if pyconf does not exist, 
257             # Make a copy of an existing  salomeTools-<sat_version>.pyconf
258             # or at least a copy of salomeTools.pyconf
259             # If there is no pyconf file at all, create it from scratch 
260             already_exisiting_pyconf_file = self.getAlreadyExistingUserPyconf( config.VARS.personalDir, sat_version )
261             if already_exisiting_pyconf_file:  
262                 # copy
263                 shutil.copyfile( already_exisiting_pyconf_file, self.user_config_file_path )
264                 cfg = common.config_pyconf.Config(open(self.user_config_file_path))
265             else: # create from scratch
266                 self.createConfigFile(config)
267     
268     def getAlreadyExistingUserPyconf(self, dir, sat_version ):
269         '''Get a pyconf file younger than the given sat version in the given directory
270         The file basename can be one of salometools-<younger version>.pyconf or salomeTools.pyconf
271         Returns the file path or None if no file has been found.
272         '''
273         file_path = None  
274         # Get a younger pyconf version   
275         pyconfFiles = glob.glob( os.path.join(dir, 'salomeTools-*.pyconf') )
276         sExpr = "^salomeTools-(.*)\.pyconf$"
277         oExpr = re.compile(sExpr)
278         younger_version = None
279         for s in pyconfFiles:
280             oSreMatch = oExpr.search( os.path.basename(s) )
281             if oSreMatch:
282                 pyconf_version = oSreMatch.group(1)
283                 if pyconf_version < sat_version: 
284                     younger_version = pyconf_version 
285
286         # Build the pyconf filepath
287         if younger_version :   
288             file_path = os.path.join( dir, 'salomeTools-%s.pyconf'%younger_version )
289         elif os.path.isfile( os.path.join(dir, 'salomeTools.pyconf') ):
290             file_path = os.path.join( dir, 'salomeTools.pyconf' )
291         
292         return file_path 
293     
294     def createConfigFile(self, config):
295         
296         cfg_name = self.getUserConfigFile()
297
298         user_cfg = common.config_pyconf.Config()
299         #
300         user_cfg.addMapping('USER', common.config_pyconf.Mapping(user_cfg), "")
301
302         #
303         user_cfg.USER.addMapping('workDir', os.path.expanduser('~'),
304             "This is where salomeTools will work. You may (and probably do) change it.\n")
305         user_cfg.USER.addMapping('cvs_user', config.VARS.user,
306             "This is the user name used to access salome cvs base.\n")
307         user_cfg.USER.addMapping('svn_user', config.VARS.user,
308             "This is the user name used to access salome svn base.\n")
309         user_cfg.USER.addMapping('output_level', 3,
310             "This is the default output_level you want. 0=>no output, 5=>debug.\n")
311         user_cfg.USER.addMapping('publish_dir', os.path.join(os.path.expanduser('~'), 'websupport', 'satreport'), "")
312         user_cfg.USER.addMapping('editor', 'vi', "This is the editor used to modify configuration files\n")
313         user_cfg.USER.addMapping('browser', 'firefox', "This is the browser used to read html documentation\n")
314         user_cfg.USER.addMapping('pdf_viewer', 'evince', "This is the pdf_viewer used to read pdf documentation\n")
315         # 
316         common.ensure_path_exists(config.VARS.personalDir)
317         common.ensure_path_exists(os.path.join(config.VARS.personalDir, 'Applications'))
318
319         f = open(cfg_name, 'w')
320         user_cfg.__save__(f)
321         f.close()
322         print(_("You can edit it to configure salomeTools (use: sat config --edit).\n"))
323
324         return user_cfg   
325
326     def getUserConfigFile(self):
327         '''Get the user config file
328         '''
329         if not self.user_config_file_path:
330             raise common.SatException(_("Error in getUserConfigFile: missing user config file path"))
331         return self.user_config_file_path     
332         
333     
334 def print_value(config, path, show_label, level=0, show_full_path=False):
335     '''Prints a value from the configuration. Prints recursively the values under the initial path.
336     :param config class 'common.config_pyconf.Config': The configuration from which the value is displayed.
337     :param path str : the path in the configuration of the value to print.
338     :param show_label boolean: if True, do a basic display. (useful for bash completion)
339     :param level int: The number of spaces to add before display.
340     :param show_full_path :
341     :return: The final config.
342     :rtype: class 'common.config_pyconf.Config'
343     '''            
344     
345     # display all the path or not
346     if show_full_path:
347         vname = path
348     else:
349         vname = path.split('.')[-1]
350
351     # number of spaces before the display
352     tab_level = "  " * level
353     
354     # call to the function that gets the value of the path.
355     try:
356         val = config.getByPath(path)
357     except Exception as e:
358         sys.stdout.write(tab_level)
359         sys.stdout.write("%s: ERROR %s\n" % (common.printcolors.printcLabel(vname), common.printcolors.printcError(str(e))))
360         return
361
362     # in this case, display only the value
363     if show_label:
364         sys.stdout.write(tab_level)
365         sys.stdout.write("%s: " % common.printcolors.printcLabel(vname))
366
367     # The case where the value has under values, do a recursive call to the function
368     if dir(val).__contains__('keys'):
369         if show_label: sys.stdout.write("\n")
370         for v in sorted(val.keys()):
371             print_value(config, path + '.' + v, show_label, level + 1)
372     elif val.__class__ == common.config_pyconf.Sequence or isinstance(val, list): # in this case, value is a list (or a Sequence)
373         if show_label: sys.stdout.write("\n")
374         index = 0
375         for v in val:
376             print_value(config, path + "[" + str(index) + "]", show_label, level + 1)
377             index = index + 1
378     else: # case where val is just a str
379         sys.stdout.write("%s\n" % val)
380
381 def description():
382     return _("The config command allows manipulation and operation on config files.")
383     
384
385 def run(args, runner):
386     (options, args) = parser.parse_args(args)
387     if options.value:
388         print_value(runner.cfg, options.value, True, level=0, show_full_path=False)
389