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