Salome HOME
Change log directory. It is now set in site.pyconf
[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
25 import src
26
27 # internationalization
28 satdir  = os.path.dirname(os.path.realpath(__file__))
29 gettext.install('salomeTools', os.path.join(satdir, 'src', 'i18n'))
30
31 # Define all possible option for config command :  sat config <options>
32 parser = src.options.Options()
33 parser.add_option('v', 'value', 'string', 'value', _("print the value of CONFIG_VARIABLE."))
34 parser.add_option('e', 'edit', 'boolean', 'edit', _("edit the product configuration file."))
35 parser.add_option('l', 'list', 'boolean', 'list',_("list all available applications."))
36 parser.add_option('c', 'copy', 'boolean', 'copy',
37     _("""copy a config file to the personnal config files directory.
38 \tWARNING the included files are not copied.
39 \tIf a name is given the new config file takes the given name."""))
40
41 class ConfigOpener:
42     ''' Class that helps to find an application pyconf in all the possible directories (pathList)
43     '''
44     def __init__(self, pathList):
45         '''Initialization
46         
47         :param pathList list: The list of paths where to serach a pyconf.
48         '''
49         self.pathList = pathList
50
51     def __call__(self, name):
52         if os.path.isabs(name):
53             return src.pyconf.ConfigInputStream(open(name, 'rb'))
54         else:
55             return src.pyconf.ConfigInputStream( open(os.path.join( self.getPath(name), name ), 'rb') )
56         raise IOError(_("Configuration file '%s' not found") % name)
57
58     def getPath( self, name ):
59         '''The method that returns the entire path of the pyconf searched
60         :param name str: The name of the searched pyconf.
61         '''
62         for path in self.pathList:
63             if os.path.exists(os.path.join(path, name)):
64                 return path
65         raise IOError(_("Configuration file '%s' not found") % name)
66
67 class ConfigManager:
68     '''Class that manages the read of all the configuration files of salomeTools
69     '''
70     def __init__(self, dataDir=None):
71         pass
72
73     def _create_vars(self, application=None, command=None, dataDir=None):
74         '''Create a dictionary that stores all information about machine, user, date, repositories, etc...
75         
76         :param application str: The application for which salomeTools is called.
77         :param command str: The command that is called.
78         :param dataDir str: The repository that contain external data for salomeTools.
79         :return: The dictionary that stores all information.
80         :rtype: dict
81         '''
82         var = {}      
83         var['user'] = src.architecture.get_user()
84         var['salometoolsway'] = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
85         var['srcDir'] = os.path.join(var['salometoolsway'], 'src')
86         var['sep']= os.path.sep
87         
88         # dataDir has a default location
89         var['dataDir'] = os.path.join(var['salometoolsway'], 'data')
90         if dataDir is not None:
91             var['dataDir'] = dataDir
92
93         var['personalDir'] = os.path.join(os.path.expanduser('~'), '.salomeTools')
94
95         # read linux distributions dictionary
96         distrib_cfg = src.pyconf.Config(os.path.join(var['srcDir'], 'internal_config', 'distrib.pyconf'))
97         
98         # set platform parameters
99         dist_name = src.architecture.get_distribution(codes=distrib_cfg.DISTRIBUTIONS)
100         dist_version = src.architecture.get_distrib_version(dist_name, codes=distrib_cfg.VERSIONS)
101         dist = dist_name + dist_version
102         
103         var['dist_name'] = dist_name
104         var['dist_version'] = dist_version
105         var['dist'] = dist
106         var['python'] = src.architecture.get_python_version()
107
108         var['nb_proc'] = src.architecture.get_nb_proc()
109         node_name = platform.node()
110         var['node'] = node_name
111         var['hostname'] = node_name
112
113         # set date parameters
114         dt = datetime.datetime.now()
115         var['date'] = dt.strftime('%Y%m%d')
116         var['datehour'] = dt.strftime('%Y%m%d_%H%M%S')
117         var['hour'] = dt.strftime('%H%M%S')
118
119         var['command'] = str(command)
120         var['application'] = str(application)
121
122         # Root dir for temporary files 
123         var['tmp_root'] = os.sep + 'tmp' + os.sep + var['user']
124         # particular win case 
125         if src.architecture.is_windows() : 
126             var['tmp_root'] =  os.path.expanduser('~') + os.sep + 'tmp'
127         
128         return var
129
130     def get_command_line_overrides(self, options, sections):
131         '''get all the overwrites that are in the command line
132         
133         :param options: the options from salomeTools class initialization (like -l5 or --overwrite)
134         :param sections str: The config section to overwrite.
135         :return: The list of all the overwrites to apply.
136         :rtype: list
137         '''
138         # when there are no options or not the overwrite option, return an empty list
139         if options is None or options.overwrite is None:
140             return []
141         
142         over = []
143         for section in sections:
144             # only overwrite the sections that correspond to the option 
145             over.extend(filter(lambda l: l.startswith(section + "."), options.overwrite))
146         return over
147
148     def getConfig(self, application=None, options=None, command=None, dataDir=None):
149         '''get the config from all the configuration files.
150         
151         :param application str: The application for which salomeTools is called.
152         :param options calss Options: The general salomeToosl options (--overwrite or -l5, for example)
153         :param command str: The command that is called.
154         :param dataDir str: The repository that contain external data for salomeTools.
155         :return: The final config.
156         :rtype: class 'src.pyconf.Config'
157         '''        
158         
159         # create a ConfigMerger to handle merge
160         merger = src.pyconf.ConfigMerger()#MergeHandler())
161         
162         # create the configuration instance
163         cfg = src.pyconf.Config()
164         
165         # =======================================================================================
166         # create VARS section
167         var = self._create_vars(application=application, command=command, dataDir=dataDir)
168         # add VARS to config
169         cfg.VARS = src.pyconf.Mapping(cfg)
170         for variable in var:
171             cfg.VARS[variable] = var[variable]
172         
173         # apply overwrite from command line if needed
174         for rule in self.get_command_line_overrides(options, ["VARS"]):
175             exec('cfg.' + rule) # this cannot be factorized because of the exec
176         
177         # =======================================================================================
178         # Load INTERNAL config
179         # read src/internal_config/salomeTools.pyconf
180         src.pyconf.streamOpener = ConfigOpener([os.path.join(cfg.VARS.srcDir, 'internal_config')])
181         try:
182             internal_cfg = src.pyconf.Config(open(os.path.join(cfg.VARS.srcDir, 'internal_config', 'salomeTools.pyconf')))
183         except src.pyconf.ConfigError as e:
184             raise src.SatException(_("Error in configuration file: salomeTools.pyconf\n  %(error)s") % \
185                 {'error': str(e) })
186         
187         merger.merge(cfg, internal_cfg)
188
189         # apply overwrite from command line if needed
190         for rule in self.get_command_line_overrides(options, ["INTERNAL"]):
191             exec('cfg.' + rule) # this cannot be factorized because of the exec        
192         
193         # =======================================================================================
194         # Load SITE config file
195         # search only in the data directory
196         src.pyconf.streamOpener = ConfigOpener([cfg.VARS.dataDir])
197         try:
198             site_cfg = src.pyconf.Config(open(os.path.join(cfg.VARS.dataDir, 'site.pyconf')))
199         except src.pyconf.ConfigError as e:
200             raise src.SatException(_("Error in configuration file: site.pyconf\n  %(error)s") % \
201                 {'error': str(e) })
202         except IOError as error:
203             e = str(error)
204             if "site.pyconf" in e :
205                 e += "\nYou can copy data" + cfg.VARS.sep + "site.template.pyconf to data" + cfg.VARS.sep + "site.pyconf and edit the file"
206             raise src.SatException( e );
207         
208         # add user local path for configPath
209         site_cfg.SITE.config.configPath.append(os.path.join(cfg.VARS.personalDir, 'Applications'), "User applications path")
210         
211         merger.merge(cfg, site_cfg)
212
213         # apply overwrite from command line if needed
214         for rule in self.get_command_line_overrides(options, ["SITE"]):
215             exec('cfg.' + rule) # this cannot be factorized because of the exec
216   
217         
218         # =======================================================================================
219         # Load APPLICATION config file
220         if application is not None:
221             # search APPLICATION file in all directories in configPath
222             cp = cfg.SITE.config.configPath
223             src.pyconf.streamOpener = ConfigOpener(cp)
224             try:
225                 application_cfg = src.pyconf.Config(application + '.pyconf')
226             except IOError as e:
227                 raise src.SatException(_("%s, use 'config --list' to get the list of available applications.") %e)
228             except src.pyconf.ConfigError as e:
229                 raise src.SatException(_("Error in configuration file: %(application)s.pyconf\n  %(error)s") % \
230                     { 'application': application, 'error': str(e) } )
231
232             merger.merge(cfg, application_cfg)
233
234             # apply overwrite from command line if needed
235             for rule in self.get_command_line_overrides(options, ["APPLICATION"]):
236                 exec('cfg.' + rule) # this cannot be factorized because of the exec
237         
238         # =======================================================================================
239         # Load softwares config files in SOFTWARE section
240        
241         # The directory containing the softwares definition
242         softsDir = os.path.join(cfg.VARS.dataDir, 'softwares')
243         
244         # Loop on all files that are in softsDir directory and read their config
245         for fName in os.listdir(softsDir):
246             if fName.endswith(".pyconf"):
247                 src.pyconf.streamOpener = ConfigOpener([softsDir])
248                 try:
249                     soft_cfg = src.pyconf.Config(open(os.path.join(softsDir, fName)))
250                 except src.pyconf.ConfigError as e:
251                     raise src.SatException(_("Error in configuration file: %(soft)s\n  %(error)s") % \
252                         {'soft' :  fName, 'error': str(e) })
253                 except IOError as error:
254                     e = str(error)
255                     raise src.SatException( e );
256                 
257                 merger.merge(cfg, soft_cfg)
258
259         # apply overwrite from command line if needed
260         for rule in self.get_command_line_overrides(options, ["SOFTWARE"]):
261             exec('cfg.' + rule) # this cannot be factorized because of the exec
262
263         
264         # =======================================================================================
265         # load USER config
266         self.setUserConfigFile(cfg)
267         user_cfg_file = self.getUserConfigFile()
268         user_cfg = src.pyconf.Config(open(user_cfg_file))
269         merger.merge(cfg, user_cfg)
270
271         # apply overwrite from command line if needed
272         for rule in self.get_command_line_overrides(options, ["USER"]):
273             exec('cfg.' + rule) # this cannot be factorize because of the exec
274
275         return cfg
276
277     def setUserConfigFile(self, config):
278         '''Set the user config file name and path.
279         If necessary, build it from another one or create it from scratch.
280         
281         :param config class 'src.pyconf.Config': The global config (containing all pyconf).
282         '''
283         # get the expected name and path of the file
284         self.config_file_name = 'salomeTools.pyconf'
285         self.user_config_file_path = os.path.join(config.VARS.personalDir, self.config_file_name)
286         
287         # if pyconf does not exist, create it from scratch
288         if not os.path.isfile(self.user_config_file_path): 
289             self.createConfigFile(config)
290     
291     def createConfigFile(self, config):
292         '''This method is called when there are no user config file. It build it from scratch.
293         
294         :param config class 'src.pyconf.Config': The global config.
295         :return: the config corresponding to the file created.
296         :rtype: config class 'src.pyconf.Config'
297         '''
298         
299         cfg_name = self.getUserConfigFile()
300
301         user_cfg = src.pyconf.Config()
302         #
303         user_cfg.addMapping('USER', src.pyconf.Mapping(user_cfg), "")
304
305         #
306         user_cfg.USER.addMapping('workDir', os.path.expanduser('~'),
307             "This is where salomeTools will work. You may (and probably do) change it.\n")
308         user_cfg.USER.addMapping('cvs_user', config.VARS.user,
309             "This is the user name used to access salome cvs base.\n")
310         user_cfg.USER.addMapping('svn_user', config.VARS.user,
311             "This is the user name used to access salome svn base.\n")
312         user_cfg.USER.addMapping('output_level', 3,
313             "This is the default output_level you want. 0=>no output, 5=>debug.\n")
314         user_cfg.USER.addMapping('publish_dir', os.path.join(os.path.expanduser('~'), 'websupport', 'satreport'), "")
315         user_cfg.USER.addMapping('editor', 'vi', "This is the editor used to modify configuration files\n")
316         user_cfg.USER.addMapping('browser', 'firefox', "This is the browser used to read html documentation\n")
317         user_cfg.USER.addMapping('pdf_viewer', 'evince', "This is the pdf_viewer used to read pdf documentation\n")
318         # 
319         src.ensure_path_exists(config.VARS.personalDir)
320         src.ensure_path_exists(os.path.join(config.VARS.personalDir, 'Applications'))
321
322         f = open(cfg_name, 'w')
323         user_cfg.__save__(f)
324         f.close()
325         print(_("You can edit it to configure salomeTools (use: sat config --edit).\n"))
326
327         return user_cfg   
328
329     def getUserConfigFile(self):
330         '''Get the user config file
331         :return: path to the user config file.
332         :rtype: str
333         '''
334         if not self.user_config_file_path:
335             raise src.SatException(_("Error in getUserConfigFile: missing user config file path"))
336         return self.user_config_file_path     
337         
338     
339 def print_value(config, path, show_label, logger, level=0, show_full_path=False):
340     '''Prints a value from the configuration. Prints recursively the values under the initial path.
341     
342     :param config class 'src.pyconf.Config': The configuration from which the value is displayed.
343     :param path str : the path in the configuration of the value to print.
344     :param show_label boolean: if True, do a basic display. (useful for bash completion)
345     :param logger Logger: the logger instance
346     :param level int: The number of spaces to add before display.
347     :param show_full_path :
348     '''            
349     
350     # display all the path or not
351     if show_full_path:
352         vname = path
353     else:
354         vname = path.split('.')[-1]
355
356     # number of spaces before the display
357     tab_level = "  " * level
358     
359     # call to the function that gets the value of the path.
360     try:
361         val = config.getByPath(path)
362     except Exception as e:
363         logger.write(tab_level)
364         logger.write("%s: ERROR %s\n" % (src.printcolors.printcLabel(vname), src.printcolors.printcError(str(e))))
365         return
366
367     # in this case, display only the value
368     if show_label:
369         logger.write(tab_level)
370         logger.write("%s: " % src.printcolors.printcLabel(vname))
371
372     # The case where the value has under values, do a recursive call to the function
373     if dir(val).__contains__('keys'):
374         if show_label: logger.write("\n")
375         for v in sorted(val.keys()):
376             print_value(config, path + '.' + v, show_label, logger, level + 1)
377     elif val.__class__ == src.pyconf.Sequence or isinstance(val, list): # in this case, value is a list (or a Sequence)
378         if show_label: logger.write("\n")
379         index = 0
380         for v in val:
381             print_value(config, path + "[" + str(index) + "]", show_label, logger, level + 1)
382             index = index + 1
383     else: # case where val is just a str
384         logger.write("%s\n" % val)
385
386 def description():
387     '''method that is called when salomeTools is called with --help option.
388     
389     :return: The text to display for the config command description.
390     :rtype: str
391     '''
392     return _("The config command allows manipulation and operation on config files.")
393     
394
395 def run(args, runner, logger):
396     '''method that is called when salomeTools is called with config parameter.
397     '''
398     # Parse the options
399     (options, args) = parser.parse_args(args)
400     
401     # case : print a value of the config
402     if options.value:
403         if options.value == ".":
404             # if argument is ".", print all the config
405             for val in sorted(runner.cfg.keys()):
406                 print_value(runner.cfg, val, True, logger)
407         else:
408             print_value(runner.cfg, options.value, True, logger, level=0, show_full_path=False)
409     
410     # case : edit user pyconf file or application file
411     elif options.edit:
412         editor = runner.cfg.USER.editor
413         if 'APPLICATION' not in runner.cfg: # edit user pyconf
414             usercfg = os.path.join(runner.cfg.VARS.personalDir, 'salomeTools.pyconf')
415             src.system.show_in_editor(editor, usercfg, logger)
416         else:
417             # search for file <application>.pyconf and open it
418             for path in runner.cfg.SITE.config.configPath:
419                 pyconf_path = os.path.join(path, runner.cfg.VARS.application + ".pyconf")
420                 if os.path.exists(pyconf_path):
421                     src.system.show_in_editor(editor, pyconf_path, logger)
422                     break
423     
424     # case : copy an existing <application>.pyconf to ~/.salomeTools/Applications/LOCAL_<application>.pyconf
425     elif options.copy:
426         # product is required
427         src.check_config_has_application( runner.cfg )
428
429         # get application file path 
430         source = runner.cfg.VARS.application + '.pyconf'
431         source_full_path = ""
432         for path in runner.cfg.SITE.config.configPath:
433             # ignore personal directory
434             if path == runner.cfg.VARS.personalDir:
435                 continue
436             # loop on all directories that can have pyconf applications
437             zz = os.path.join(path, source)
438             if os.path.exists(zz):
439                 source_full_path = zz
440                 break
441
442         if len(source_full_path) == 0:
443             raise src.SatException(_("Config file for product %s not found\n") % source)
444         else:
445             if len(args) > 0:
446                 # a name is given as parameter, use it
447                 dest = args[0]
448             elif 'copy_prefix' in runner.cfg.SITE.config:
449                 # use prefix
450                 dest = runner.cfg.SITE.config.copy_prefix + runner.cfg.VARS.application
451             else:
452                 # use same name as source
453                 dest = runner.cfg.VARS.application
454                 
455             # the full path
456             dest_file = os.path.join(runner.cfg.VARS.personalDir, 'Applications', dest + '.pyconf')
457             if os.path.exists(dest_file):
458                 raise src.SatException(_("A personal application '%s' already exists") % dest)
459             
460             # perform the copy
461             shutil.copyfile(source_full_path, dest_file)
462             logger.write(_("%s has been created.\n") % dest_file)
463     
464     # case : display all the available pyconf applications
465     elif options.list:
466         lproduct = list()
467         # search in all directories that can have pyconf applications
468         for path in runner.cfg.SITE.config.configPath:
469             # print a header
470             logger.write("------ %s\n" % src.printcolors.printcHeader(path))
471
472             if not os.path.exists(path):
473                 logger.write(src.printcolors.printcError(_("Directory not found")) + "\n")
474             else:
475                 for f in sorted(os.listdir(path)):
476                     # ignore file that does not ends with .pyconf
477                     if not f.endswith('.pyconf'):
478                         continue
479
480                     appliname = f[:-len('.pyconf')]
481                     if appliname not in lproduct:
482                         lproduct.append(appliname)
483                         if path.startswith(runner.cfg.VARS.personalDir):
484                             logger.write("%s*\n" % appliname)
485                         else:
486                             logger.write("%s\n" % appliname)
487                             
488             logger.write("\n")
489     
490