From 772e86c1c13a8e0e4b0dfd6f68fead7231320625 Mon Sep 17 00:00:00 2001 From: Christian Van Wambeke Date: Sun, 1 Apr 2018 20:25:09 +0200 Subject: [PATCH] sat --help and logingSat for unittest and begin fix ./test/test_xxx_zzz.py --- .gitignore | 2 + AllTestLauncherSat.py | 19 +- commands/config.py | 848 +--------------- sat | 46 +- src/__init__.py | 4 +- src/coloringSat.py | 22 +- src/configManager.py | 935 ++++++++++++++++++ src/debug.py | 8 +- src/i18n/fr/LC_MESSAGES/salomeTools.po | 44 +- src/loggingSat.py | 64 +- src/options.py | 42 +- src/salomeTools.py | 594 ++++------- src/test/satHelpTest.py | 85 -- .../APPLI_TEST/APPLI_TEST.pyconf | 0 {src/test => test}/README_config_0_3_9.txt | 0 test/__init__.py | 0 {src/test => test}/config_0_3_9/LICENSE | 0 {src/test => test}/config_0_3_9/PKG-INFO | 0 {src/test => test}/config_0_3_9/README.txt | 0 {src/test => test/config_0_3_9}/__init__.py | 0 {src/test => test}/config_0_3_9/config.py | 0 {src/test => test}/config_0_3_9/logconfig.cfg | 0 {src/test => test}/config_0_3_9/logconfig.py | 0 {src/test => test}/config_0_3_9/setup.py | 0 {src/test => test}/config_0_3_9/styles.json | 0 .../test => test}/config_0_3_9/test_config.py | 0 .../debugTest.py => test/test_020_debug.py | 3 +- .../pyconfTest.py => test/test_025_pyconf.py | 3 +- .../test_030_pyconf_0_3_9.py | 11 +- test/test_100_satHelp.py | 120 +++ .../test_500_APPLI_TEST.py | 39 +- .../test_sat5_0}/__init__.py | 0 .../compilation/test_compilation.py | 0 .../compilation/test_configure.py | 0 .../compilation/test_make.py | 0 .../compilation/test_makeinstall.py | 0 .../config/test_create_user_pyconf.py | 0 .../config/test_option_copy.py | 0 .../config/test_option_edit.py | 0 .../config/test_option_value.py | 0 .../config/test_option_value_2.py | 0 .../{ => test_sat5_0}/environ/test_environ.py | 0 test/{ => test_sat5_0}/job/test_job.py | 0 test/{ => test_sat5_0}/jobs/test_jobs.py | 0 .../log/test_launch_browser.py | 0 .../log/test_launch_browser2.py | 0 test/{ => test_sat5_0}/prepare/test_clean.py | 0 test/{ => test_sat5_0}/prepare/test_patch.py | 0 .../{ => test_sat5_0}/prepare/test_prepare.py | 0 test/{ => test_sat5_0}/prepare/test_source.py | 0 test/{ => test_sat5_0}/run_all.sh | 0 test/{ => test_sat5_0}/shell/test_shell.py | 0 test/{ => test_sat5_0}/test/test_command.py | 0 53 files changed, 1494 insertions(+), 1395 deletions(-) create mode 100644 src/configManager.py delete mode 100755 src/test/satHelpTest.py rename {src/test => test}/APPLI_TEST/APPLI_TEST.pyconf (100%) rename {src/test => test}/README_config_0_3_9.txt (100%) mode change 100644 => 100755 test/__init__.py rename {src/test => test}/config_0_3_9/LICENSE (100%) rename {src/test => test}/config_0_3_9/PKG-INFO (100%) rename {src/test => test}/config_0_3_9/README.txt (100%) rename {src/test => test/config_0_3_9}/__init__.py (100%) mode change 100755 => 100644 rename {src/test => test}/config_0_3_9/config.py (100%) rename {src/test => test}/config_0_3_9/logconfig.cfg (100%) rename {src/test => test}/config_0_3_9/logconfig.py (100%) rename {src/test => test}/config_0_3_9/setup.py (100%) rename {src/test => test}/config_0_3_9/styles.json (100%) rename {src/test => test}/config_0_3_9/test_config.py (100%) rename src/test/debugTest.py => test/test_020_debug.py (97%) rename src/test/pyconfTest.py => test/test_025_pyconf.py (98%) rename src/test/test_pyconf.py => test/test_030_pyconf_0_3_9.py (97%) create mode 100755 test/test_100_satHelp.py rename src/test/APPLI_TEST_Test.py => test/test_500_APPLI_TEST.py (70%) rename {src/test/config_0_3_9 => test/test_sat5_0}/__init__.py (100%) rename test/{ => test_sat5_0}/compilation/test_compilation.py (100%) rename test/{ => test_sat5_0}/compilation/test_configure.py (100%) rename test/{ => test_sat5_0}/compilation/test_make.py (100%) rename test/{ => test_sat5_0}/compilation/test_makeinstall.py (100%) rename test/{ => test_sat5_0}/config/test_create_user_pyconf.py (100%) rename test/{ => test_sat5_0}/config/test_option_copy.py (100%) rename test/{ => test_sat5_0}/config/test_option_edit.py (100%) rename test/{ => test_sat5_0}/config/test_option_value.py (100%) rename test/{ => test_sat5_0}/config/test_option_value_2.py (100%) rename test/{ => test_sat5_0}/environ/test_environ.py (100%) rename test/{ => test_sat5_0}/job/test_job.py (100%) rename test/{ => test_sat5_0}/jobs/test_jobs.py (100%) rename test/{ => test_sat5_0}/log/test_launch_browser.py (100%) rename test/{ => test_sat5_0}/log/test_launch_browser2.py (100%) rename test/{ => test_sat5_0}/prepare/test_clean.py (100%) rename test/{ => test_sat5_0}/prepare/test_patch.py (100%) rename test/{ => test_sat5_0}/prepare/test_prepare.py (100%) rename test/{ => test_sat5_0}/prepare/test_source.py (100%) rename test/{ => test_sat5_0}/run_all.sh (100%) rename test/{ => test_sat5_0}/shell/test_shell.py (100%) rename test/{ => test_sat5_0}/test/test_command.py (100%) diff --git a/.gitignore b/.gitignore index 86431c4..6bab5df 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ test/htmlcov test/test_res.html doc/src/commands/apidoc* .idea +.spyderproject + diff --git a/AllTestLauncherSat.py b/AllTestLauncherSat.py index 59f4fbd..45f2a59 100755 --- a/AllTestLauncherSat.py +++ b/AllTestLauncherSat.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # %% LICENSE_SALOME_CEA_BEGIN -# Copyright (C) 2008-2016 CEA/DEN +# Copyright (C) 2008-2018 CEA/DEN # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -69,7 +69,7 @@ verboseImport = True satdir = os.path.dirname(os.path.realpath(__file__)) def errPrint(aStr): - """to avoid write in html or xml file log message""" + """stderr to avoid write in html or xml file log message""" sys.stderr.write(aStr + '\n') try: @@ -87,7 +87,7 @@ try: except: XTST = None errPrint(""" -WARNING: no XML output available. +WARNING: no XML output available for unittest. try 'pip install unittest-xml-reporting'. """) @@ -180,8 +180,9 @@ def runOnArgs(args): if done: del sys.path[0] #attention of sys.path appends done = False - errPrint("ERROR: AllTestLauncher: import '%s':" % aFile) - errPrint(" %s\n" % e) + errPrint("""\ +ERROR: AllTestLauncher: import '%s': + %s\n""" % (aFile, e)) continue listfilesForTest = sorted(filesForTest.keys()) @@ -255,7 +256,7 @@ def runFromEnvVar(envVar, fileTestPattern="*Test.py"): ################################################################### def getParser(): - parser = AP.ArgumentParser(description='launch All SAT python tests', argument_default=None) + parser = AP.ArgumentParser(description='launch All salomeTools python tests', argument_default=None) parser.add_argument( '-d', '--debug', @@ -280,7 +281,7 @@ of recursive searching unittest python files parser.add_argument( '-p', '--pattern', help="file pattern for unittest files ['test_*.py'|'*Test.py'...]", - default="test_*.py", + default="test_???_*.py", # as alphabetical ordered test site metavar='filePattern' ) parser.add_argument( @@ -302,12 +303,14 @@ If name = 'stdout' then all-in-one xml output at 'sys.stdout'. For pipe redirect ) return parser -#export PATH=/volatile/wambeke/SAT5/SAT5_S840_MATIX24/salomeTools-5-cvw:${PATH} +#export PATH=satdir:${PATH} ################################################################### if __name__ == '__main__': # Make the src & command package accessible from all code + # as export PATH=satdir:${PATH} sys.path.insert(0, satdir) + errPrint("WARNING: export PATH=%s:${PATH}\n" % satdir) args = getParser().parse_args(sys.argv[1:]) debug = args.debug diff --git a/commands/config.py b/commands/config.py index 515c2a6..07c2cf2 100644 --- a/commands/config.py +++ b/commands/config.py @@ -16,813 +16,45 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -import os +"""import os import platform import datetime import shutil -import sys +import sys""" import src import src.debug as DBG +import src.loggingSat as LOG import src.returnCode as RCO from src.salomeTools import _BaseCommand +import src.configManager as CFGMGR -class ConfigOpener: - '''Class that helps to find an application pyconf - in all the possible directories (pathList) - ''' - def __init__(self, pathList): - '''Initialization - - :param pathList list: The list of paths where to search a pyconf. - ''' - self.pathList = pathList - - def __call__(self, name): - if os.path.isabs(name): - return src.pyconf.ConfigInputStream(open(name, 'rb')) - else: - return src.pyconf.ConfigInputStream( - open(os.path.join( self.get_path(name), name ), 'rb') ) - raise IOError(_("Configuration file '%s' not found") % name) - - def get_path( self, name ): - '''The method that returns the entire path of the pyconf searched - :param name str: The name of the searched pyconf. - ''' - for path in self.pathList: - if os.path.exists(os.path.join(path, name)): - return path - raise IOError(_("Configuration file '%s' not found") % name) - -class ConfigManager: - '''Class that manages the read of all the configuration files of salomeTools - ''' - def __init__(self, datadir=None): - pass - - def _create_vars(self, application=None, command=None, datadir=None): - '''Create a dictionary that stores all information about machine, - user, date, repositories, etc... - - :param application str: The application for which salomeTools is called. - :param command str: The command that is called. - :param datadir str: The repository that contain external data - for salomeTools. - :return: The dictionary that stores all information. - :rtype: dict - ''' - var = {} - var['user'] = src.architecture.get_user() - var['salometoolsway'] = os.path.dirname( - os.path.dirname(os.path.abspath(__file__))) - var['srcDir'] = os.path.join(var['salometoolsway'], 'src') - var['internal_dir'] = os.path.join(var['srcDir'], 'internal_config') - var['sep']= os.path.sep - - # datadir has a default location - var['datadir'] = os.path.join(var['salometoolsway'], 'data') - if datadir is not None: - var['datadir'] = datadir - - var['personalDir'] = os.path.join(os.path.expanduser('~'), - '.salomeTools') - src.ensure_path_exists(var['personalDir']) - - var['personal_applications_dir'] = os.path.join(var['personalDir'], - "Applications") - src.ensure_path_exists(var['personal_applications_dir']) - - var['personal_products_dir'] = os.path.join(var['personalDir'], - "products") - src.ensure_path_exists(var['personal_products_dir']) - - var['personal_archives_dir'] = os.path.join(var['personalDir'], - "Archives") - src.ensure_path_exists(var['personal_archives_dir']) - - var['personal_jobs_dir'] = os.path.join(var['personalDir'], - "Jobs") - src.ensure_path_exists(var['personal_jobs_dir']) - - var['personal_machines_dir'] = os.path.join(var['personalDir'], - "Machines") - src.ensure_path_exists(var['personal_machines_dir']) - - # read linux distributions dictionary - distrib_cfg = src.pyconf.Config(os.path.join(var['srcDir'], - 'internal_config', - 'distrib.pyconf')) - - # set platform parameters - dist_name = src.architecture.get_distribution( - codes=distrib_cfg.DISTRIBUTIONS) - dist_version = src.architecture.get_distrib_version(dist_name, - codes=distrib_cfg.VERSIONS) - dist = dist_name + dist_version - - var['dist_name'] = dist_name - var['dist_version'] = dist_version - var['dist'] = dist - var['python'] = src.architecture.get_python_version() - - var['nb_proc'] = src.architecture.get_nb_proc() - node_name = platform.node() - var['node'] = node_name - var['hostname'] = node_name - - # set date parameters - dt = datetime.datetime.now() - var['date'] = dt.strftime('%Y%m%d') - var['datehour'] = dt.strftime('%Y%m%d_%H%M%S') - var['hour'] = dt.strftime('%H%M%S') - - var['command'] = str(command) - var['application'] = str(application) - - # Root dir for temporary files - var['tmp_root'] = os.sep + 'tmp' + os.sep + var['user'] - # particular win case - if src.architecture.is_windows() : - var['tmp_root'] = os.path.expanduser('~') + os.sep + 'tmp' - - return var - - def get_command_line_overrides(self, options, sections): - '''get all the overwrites that are in the command line - - :param options: the options from salomeTools class - initialization (like -l5 or --overwrite) - :param sections str: The config section to overwrite. - :return: The list of all the overwrites to apply. - :rtype: list - ''' - # when there are no options or not the overwrite option, - # return an empty list - if options is None or options.overwrite is None: - return [] - - over = [] - for section in sections: - # only overwrite the sections that correspond to the option - over.extend(filter(lambda l: l.startswith(section + "."), - options.overwrite)) - return over - - def get_config(self, application=None, options=None, command=None, - datadir=None): - '''get the config from all the configuration files. - - :param application str: The application for which salomeTools is called. - :param options class Options: The general salomeTools - options (--overwrite or -v5, for example) - :param command str: The command that is called. - :param datadir str: The repository that contain - external data for salomeTools. - :return: The final config. - :rtype: class 'src.pyconf.Config' - ''' - - # create a ConfigMerger to handle merge - merger = src.pyconf.ConfigMerger() #MergeHandler()) - - # create the configuration instance - cfg = src.pyconf.Config() - - # ===================================================================== - # create VARS section - var = self._create_vars(application=application, command=command, - datadir=datadir) - # add VARS to config - cfg.VARS = src.pyconf.Mapping(cfg) - for variable in var: - cfg.VARS[variable] = var[variable] - - # apply overwrite from command line if needed - for rule in self.get_command_line_overrides(options, ["VARS"]): - exec('cfg.' + rule) # this cannot be factorized because of the exec - - # ===================================================================== - # Load INTERNAL config - # read src/internal_config/salomeTools.pyconf - src.pyconf.streamOpener = ConfigOpener([ - os.path.join(cfg.VARS.srcDir, 'internal_config')]) - try: - internal_cfg = src.pyconf.Config(open(os.path.join(cfg.VARS.srcDir, - 'internal_config', 'salomeTools.pyconf'))) - except src.pyconf.ConfigError as e: - raise src.SatException(_("Error in configuration file:" - " salomeTools.pyconf\n %(error)s") % \ - {'error': str(e) }) - - merger.merge(cfg, internal_cfg) - - # apply overwrite from command line if needed - for rule in self.get_command_line_overrides(options, ["INTERNAL"]): - exec('cfg.' + rule) # this cannot be factorized because of the exec - - # ===================================================================== - # Load LOCAL config file - # search only in the data directory - src.pyconf.streamOpener = ConfigOpener([cfg.VARS.datadir]) - try: - local_cfg = src.pyconf.Config(open(os.path.join(cfg.VARS.datadir, - 'local.pyconf')), - PWD = ('LOCAL', cfg.VARS.datadir) ) - except src.pyconf.ConfigError as e: - raise src.SatException(_("Error in configuration file: " - "local.pyconf\n %(error)s") % \ - {'error': str(e) }) - except IOError as error: - e = str(error) - raise src.SatException( e ); - merger.merge(cfg, local_cfg) - - # When the key is "default", put the default value - if cfg.LOCAL.base == "default": - cfg.LOCAL.base = os.path.abspath( - os.path.join(cfg.VARS.salometoolsway, - "..", - "BASE")) - if cfg.LOCAL.workdir == "default": - cfg.LOCAL.workdir = os.path.abspath( - os.path.join(cfg.VARS.salometoolsway, - "..")) - if cfg.LOCAL.log_dir == "default": - cfg.LOCAL.log_dir = os.path.abspath( - os.path.join(cfg.VARS.salometoolsway, - "..", - "LOGS")) - - if cfg.LOCAL.archive_dir == "default": - cfg.LOCAL.archive_dir = os.path.abspath( - os.path.join(cfg.VARS.salometoolsway, - "..", - "ARCHIVES")) - - # apply overwrite from command line if needed - for rule in self.get_command_line_overrides(options, ["LOCAL"]): - exec('cfg.' + rule) # this cannot be factorized because of the exec - - # ===================================================================== - # Load the PROJECTS - projects_cfg = src.pyconf.Config() - projects_cfg.addMapping("PROJECTS", - src.pyconf.Mapping(projects_cfg), - "The projects\n") - projects_cfg.PROJECTS.addMapping("projects", - src.pyconf.Mapping(cfg.PROJECTS), - "The projects definition\n") - - for project_pyconf_path in cfg.PROJECTS.project_file_paths: - if not os.path.exists(project_pyconf_path): - msg = _("WARNING: The project file %s cannot be found. " - "It will be ignored\n") % project_pyconf_path - sys.stdout.write(msg) - continue - project_name = os.path.basename(project_pyconf_path)[:-len(".pyconf")] - try: - project_pyconf_dir = os.path.dirname(project_pyconf_path) - project_cfg = src.pyconf.Config(open(project_pyconf_path), - PWD=("", project_pyconf_dir)) - except Exception as e: - msg = _("ERROR: Error in configuration file: %(file_path)s\n %(error)s\n") % \ - {'file_path' : project_pyconf_path, 'error': str(e) } - sys.stdout.write(msg) - continue - projects_cfg.PROJECTS.projects.addMapping(project_name, - src.pyconf.Mapping(projects_cfg.PROJECTS.projects), - "The %s project\n" % project_name) - projects_cfg.PROJECTS.projects[project_name]=project_cfg - projects_cfg.PROJECTS.projects[project_name]["file_path"] = \ - project_pyconf_path - - merger.merge(cfg, projects_cfg) - - # apply overwrite from command line if needed - for rule in self.get_command_line_overrides(options, ["PROJECTS"]): - exec('cfg.' + rule) # this cannot be factorized because of the exec - - # ===================================================================== - # Create the paths where to search the application configurations, - # the product configurations, the products archives, - # the jobs configurations and the machines configurations - cfg.addMapping("PATHS", src.pyconf.Mapping(cfg), "The paths\n") - cfg.PATHS["APPLICATIONPATH"] = src.pyconf.Sequence(cfg.PATHS) - cfg.PATHS.APPLICATIONPATH.append(cfg.VARS.personal_applications_dir, "") - - cfg.PATHS["PRODUCTPATH"] = src.pyconf.Sequence(cfg.PATHS) - cfg.PATHS.PRODUCTPATH.append(cfg.VARS.personal_products_dir, "") - cfg.PATHS["ARCHIVEPATH"] = src.pyconf.Sequence(cfg.PATHS) - cfg.PATHS.ARCHIVEPATH.append(cfg.VARS.personal_archives_dir, "") - cfg.PATHS["JOBPATH"] = src.pyconf.Sequence(cfg.PATHS) - cfg.PATHS.JOBPATH.append(cfg.VARS.personal_jobs_dir, "") - cfg.PATHS["MACHINEPATH"] = src.pyconf.Sequence(cfg.PATHS) - cfg.PATHS.MACHINEPATH.append(cfg.VARS.personal_machines_dir, "") - - # initialise the path with local directory - cfg.PATHS["ARCHIVEPATH"].append(cfg.LOCAL.archive_dir, "") - - # Loop over the projects in order to complete the PATHS variables - for project in cfg.PROJECTS.projects: - for PATH in ["APPLICATIONPATH", - "PRODUCTPATH", - "ARCHIVEPATH", - "JOBPATH", - "MACHINEPATH"]: - if PATH not in cfg.PROJECTS.projects[project]: - continue - cfg.PATHS[PATH].append(cfg.PROJECTS.projects[project][PATH], "") - - # apply overwrite from command line if needed - for rule in self.get_command_line_overrides(options, ["PATHS"]): - exec('cfg.' + rule) # this cannot be factorized because of the exec - - # ===================================================================== - # Load APPLICATION config file - if application is not None: - # search APPLICATION file in all directories in configPath - cp = cfg.PATHS.APPLICATIONPATH - src.pyconf.streamOpener = ConfigOpener(cp) - do_merge = True - try: - application_cfg = src.pyconf.Config(application + '.pyconf') - except IOError as e: - raise src.SatException(_("%s, use 'config --list' to get the" - " list of available applications.") % e) - except src.pyconf.ConfigError as e: - if (not ('-e' in parser.parse_args()[1]) - or ('--edit' in parser.parse_args()[1]) - and command == 'config'): - raise src.SatException( - _("Error in configuration file: (1)s.pyconf\n %(2)s") % \ - { 'application': application, 'error': str(e) } ) - else: - sys.stdout.write(src.printcolors.printcWarning( - "There is an error in the file %s.pyconf.\n" % \ - cfg.VARS.application)) - do_merge = False - except Exception as e: - if ( not('-e' in parser.parse_args()[1]) or - ('--edit' in parser.parse_args()[1]) and - command == 'config' ): - sys.stdout.write(src.printcolors.printcWarning("%s\n" % str(e))) - raise src.SatException( - _("Error in configuration file: %s.pyconf\n") % application ) - else: - sys.stdout.write(src.printcolors.printcWarning( - "ERROR: in file %s.pyconf. Opening the file with the default viewer\n" % \ - cfg.VARS.application)) - sys.stdout.write("\n%s\n" % src.printcolors.printcWarning(str(e))) - do_merge = False - - else: - cfg['open_application'] = 'yes' - - # ===================================================================== - # Load product config files in PRODUCTS section - products_cfg = src.pyconf.Config() - products_cfg.addMapping("PRODUCTS", - src.pyconf.Mapping(products_cfg), - "The products\n") - if application is not None: - src.pyconf.streamOpener = ConfigOpener(cfg.PATHS.PRODUCTPATH) - for product_name in application_cfg.APPLICATION.products.keys(): - # Loop on all files that are in softsDir directory - # and read their config - product_file_name = product_name + ".pyconf" - product_file_path = src.find_file_in_lpath(product_file_name, cfg.PATHS.PRODUCTPATH) - if product_file_path: - products_dir = os.path.dirname(product_file_path) - try: - prod_cfg = src.pyconf.Config(open(product_file_path), - PWD=("", products_dir)) - prod_cfg.from_file = product_file_path - products_cfg.PRODUCTS[product_name] = prod_cfg - except Exception as e: - msg = _( - "WARNING: Error in configuration file: %(prod)s\n %(error)s" % \ - {'prod' : product_name, 'error': str(e) }) - sys.stdout.write(msg) - - merger.merge(cfg, products_cfg) - - # apply overwrite from command line if needed - for rule in self.get_command_line_overrides(options, ["PRODUCTS"]): - exec('cfg.' + rule) # this cannot be factorized because of the exec - - if do_merge: - merger.merge(cfg, application_cfg) - - # default launcher name ('salome') - if ('profile' in cfg.APPLICATION and - 'launcher_name' not in cfg.APPLICATION.profile): - cfg.APPLICATION.profile.launcher_name = 'salome' - - # apply overwrite from command line if needed - for rule in self.get_command_line_overrides(options, - ["APPLICATION"]): - # this cannot be factorized because of the exec - exec('cfg.' + rule) - - # ===================================================================== - # load USER config - self.set_user_config_file(cfg) - user_cfg_file = self.get_user_config_file() - user_cfg = src.pyconf.Config(open(user_cfg_file)) - merger.merge(cfg, user_cfg) - - # apply overwrite from command line if needed - for rule in self.get_command_line_overrides(options, ["USER"]): - exec('cfg.' + rule) # this cannot be factorize because of the exec - - return cfg - - def set_user_config_file(self, config): - '''Set the user config file name and path. - If necessary, build it from another one or create it from scratch. - - :param config class 'src.pyconf.Config': The global config - (containing all pyconf). - ''' - # get the expected name and path of the file - self.config_file_name = 'SAT.pyconf' - self.user_config_file_path = os.path.join(config.VARS.personalDir, - self.config_file_name) - - # if pyconf does not exist, create it from scratch - if not os.path.isfile(self.user_config_file_path): - self.create_config_file(config) - - def create_config_file(self, config): - '''This method is called when there are no user config file. - It build it from scratch. - - :param config class 'src.pyconf.Config': The global config. - :return: the config corresponding to the file created. - :rtype: config class 'src.pyconf.Config' - ''' - - cfg_name = self.get_user_config_file() - - user_cfg = src.pyconf.Config() - # - user_cfg.addMapping('USER', src.pyconf.Mapping(user_cfg), "") - - user_cfg.USER.addMapping('cvs_user', config.VARS.user, - "This is the user name used to access salome cvs base.\n") - user_cfg.USER.addMapping('svn_user', config.VARS.user, - "This is the user name used to access salome svn base.\n") - user_cfg.USER.addMapping('output_verbose_level', 3, - "This is the default output_verbose_level you want." - " 0=>no output, 5=>debug.\n") - user_cfg.USER.addMapping('publish_dir', - os.path.join(os.path.expanduser('~'), - 'websupport', - 'satreport'), - "") - user_cfg.USER.addMapping('editor', - 'vi', - "This is the editor used to " - "modify configuration files\n") - user_cfg.USER.addMapping('browser', - 'firefox', - "This is the browser used to " - "read html documentation\n") - user_cfg.USER.addMapping('pdf_viewer', - 'evince', - "This is the pdf_viewer used " - "to read pdf documentation\n") -# CNC 25/10/17 : plus nécessaire a priori -# user_cfg.USER.addMapping("base", -# src.pyconf.Reference( -# user_cfg, -# src.pyconf.DOLLAR, -# 'workdir + $VARS.sep + "BASE"'), -# "The products installation base (could be " -# "ignored if this key exists in the local.pyconf" -# " file of salomTools).\n") - - # - src.ensure_path_exists(config.VARS.personalDir) - src.ensure_path_exists(os.path.join(config.VARS.personalDir, - 'Applications')) - - f = open(cfg_name, 'w') - user_cfg.__save__(f) - f.close() - - return user_cfg - - def get_user_config_file(self): - '''Get the user config file - :return: path to the user config file. - :rtype: str - ''' - if not self.user_config_file_path: - raise src.SatException( - _("Error in get_user_config_file: missing user config file path") ) - return self.user_config_file_path - -def check_path(path, ext=[]): - '''Construct a text with the input path and "not found" if it does not - exist. - - :param path Str: the path to check. - :param ext List: An extension. Verify that the path extension - is in the list - :return: The string of the path with information - :rtype: Str - ''' - # check if file exists - if not os.path.exists(path): - return "'%s' %s" % (path, src.printcolors.printcError(_("** not found"))) - - # check extension - if len(ext) > 0: - fe = os.path.splitext(path)[1].lower() - if fe not in ext: - return "'%s' %s" % (path, src.printcolors.printcError(_("** bad extension"))) - - return path - -def show_product_info(config, name, logger): - '''Display on the terminal and logger information about a product. - - :param config Config: the global configuration. - :param name Str: The name of the product - :param logger Logger: The logger instance to use for the display - ''' - - logger.write(_("%s is a product\n") % src.printcolors.printcLabel(name), 2) - pinfo = src.product.get_product_config(config, name) - - if "depend" in pinfo: - src.printcolors.print_value(logger, - "depends on", - ', '.join(pinfo.depend), 2) - - if "opt_depend" in pinfo: - src.printcolors.print_value(logger, - "optional", - ', '.join(pinfo.opt_depend), 2) - - # information on pyconf - logger.write("\n", 2) - logger.write(src.printcolors.printcLabel("configuration:") + "\n", 2) - if "from_file" in pinfo: - src.printcolors.print_value(logger, - "pyconf file path", - pinfo.from_file, - 2) - if "section" in pinfo: - src.printcolors.print_value(logger, - "section", - pinfo.section, - 2) - - # information on prepare - logger.write("\n", 2) - logger.write(src.printcolors.printcLabel("prepare:") + "\n", 2) - - is_dev = src.product.product_is_dev(pinfo) - method = pinfo.get_source - if is_dev: - method += " (dev)" - src.printcolors.print_value(logger, "get method", method, 2) - - if method == 'cvs': - src.printcolors.print_value(logger, "server", pinfo.cvs_info.server, 2) - src.printcolors.print_value(logger, "base module", - pinfo.cvs_info.module_base, 2) - src.printcolors.print_value(logger, "source", pinfo.cvs_info.source, 2) - src.printcolors.print_value(logger, "tag", pinfo.cvs_info.tag, 2) - - elif method == 'svn': - src.printcolors.print_value(logger, "repo", pinfo.svn_info.repo, 2) - - elif method == 'git': - src.printcolors.print_value(logger, "repo", pinfo.git_info.repo, 2) - src.printcolors.print_value(logger, "tag", pinfo.git_info.tag, 2) - - elif method == 'archive': - src.printcolors.print_value(logger, - "get from", - check_path(pinfo.archive_info.archive_name), - 2) - - if 'patches' in pinfo: - for patch in pinfo.patches: - src.printcolors.print_value(logger, "patch", check_path(patch), 2) - - if src.product.product_is_fixed(pinfo): - src.printcolors.print_value(logger, "install_dir", - check_path(pinfo.install_dir), 2) - - if src.product.product_is_native(pinfo) or src.product.product_is_fixed(pinfo): - return - - # information on compilation - if src.product.product_compiles(pinfo): - logger.write("\n", 2) - logger.write(src.printcolors.printcLabel("compile:") + "\n", 2) - src.printcolors.print_value(logger, - "compilation method", - pinfo.build_source, - 2) - - if pinfo.build_source == "script" and "compil_script" in pinfo: - src.printcolors.print_value(logger, - "Compilation script", - pinfo.compil_script, - 2) - - if 'nb_proc' in pinfo: - src.printcolors.print_value(logger, "make -j", pinfo.nb_proc, 2) - - src.printcolors.print_value(logger, - "source dir", - check_path(pinfo.source_dir), - 2) - if 'install_dir' in pinfo: - src.printcolors.print_value(logger, - "build dir", - check_path(pinfo.build_dir), - 2) - src.printcolors.print_value(logger, - "install dir", - check_path(pinfo.install_dir), - 2) - else: - logger.write(" %s\n" % src.printcolors.printcWarning(_("no install dir")) , 2) - else: - logger.write("\n", 2) - msg = _("This product does not compile") - logger.write("%s\n" % msg, 2) - - # information on environment - logger.write("\n", 2) - logger.write(src.printcolors.printcLabel("environ :") + "\n", 2) - if "environ" in pinfo and "env_script" in pinfo.environ: - src.printcolors.print_value(logger, - "script", - check_path(pinfo.environ.env_script), - 2) - - zz = src.environment.SalomeEnviron(config, - src.fileEnviron.ScreenEnviron(logger), - False) - zz.set_python_libdirs() - zz.set_a_product(name, logger) - -def show_patchs(config, logger): - '''Prints all the used patchs in the application. - - :param config Config: the global configuration. - :param logger Logger: The logger instance to use for the display - ''' - len_max = max([len(p) for p in config.APPLICATION.products]) + 2 - for product in config.APPLICATION.products: - product_info = src.product.get_product_config(config, product) - if src.product.product_has_patches(product_info): - logger.write("%s: " % product, 1) - logger.write(src.printcolors.printcInfo( - " " * (len_max - len(product) -2) + - "%s\n" % product_info.patches[0]), - 1) - if len(product_info.patches) > 1: - for patch in product_info.patches[1:]: - logger.write(src.printcolors.printcInfo(len_max*" " + - "%s\n" % patch), 1) - logger.write("\n", 1) - -def print_value(config, path, show_label, logger, level=0, show_full_path=False): - '''Prints a value from the configuration. Prints recursively the values - under the initial path. - - :param config class 'src.pyconf.Config': The configuration - from which the value is displayed. - :param path str : the path in the configuration of the value to print. - :param show_label boolean: if True, do a basic display. - (useful for bash completion) - :param logger Logger: the logger instance - :param level int: The number of spaces to add before display. - :param show_full_path : - ''' - - # Make sure that the path does not ends with a point - if path.endswith('.'): - path = path[:-1] - - # display all the path or not - if show_full_path: - vname = path - else: - vname = path.split('.')[-1] - - # number of spaces before the display - tab_level = " " * level - - # call to the function that gets the value of the path. - try: - val = config.getByPath(path) - except Exception as e: - logger.write(tab_level) - logger.write("%s: ERROR %s\n" % (src.printcolors.printcLabel(vname), - src.printcolors.printcError(str(e)))) - return - - # in this case, display only the value - if show_label: - logger.write(tab_level) - logger.write("%s: " % src.printcolors.printcLabel(vname)) - - # The case where the value has under values, - # do a recursive call to the function - if dir(val).__contains__('keys'): - if show_label: logger.write("\n") - for v in sorted(val.keys()): - print_value(config, path + '.' + v, show_label, logger, level + 1) - elif val.__class__ == src.pyconf.Sequence or isinstance(val, list): - # in this case, value is a list (or a Sequence) - if show_label: logger.write("\n") - index = 0 - for v in val: - print_value(config, path + "[" + str(index) + "]", - show_label, logger, level + 1) - index = index + 1 - else: # case where val is just a str - logger.write("%s\n" % val) - -def print_debug(config, aPath, show_label, logger, level=0, show_full_path=False): - """ - logger output for debugging a config/pyconf - lines contains: - path : expression --> 'evaluation' - example: - .PROJECTS.projects.salome.project_path : $PWD --> '/volatile/wambeke/SAT5/SAT5_S840_MATIX24/SAT_SALOME' - """ - path = str(aPath) - if path == "." : - val = config - path = "" - else: - if path.endswith('.'): # Make sure that the path does not ends with a point - path = path[:-1] - val = config.getByPath(path) - - outStream = DBG.OutStream() - DBG.saveConfigDbg(val, outStream, path=path) - res = outStream.value - logger.write(res) - return - -def get_config_children(config, args): - '''Gets the names of the children of the given parameter. - Useful only for completion mechanism - - :param config Config: The configuration where to read the values - :param args: The path in the config from which get the keys - ''' - vals = [] - rootkeys = config.keys() - - if len(args) == 0: - # no parameter returns list of root keys - vals = rootkeys - else: - parent = args[0] - pos = parent.rfind('.') - if pos < 0: - # Case where there is only on key as parameter. - # For example VARS - vals = [m for m in rootkeys if m.startswith(parent)] - else: - # Case where there is a part from a key - # for example VARS.us (for VARS.user) - head = parent[0:pos] - tail = parent[pos+1:] - try: - a = config.getByPath(head) - if dir(a).__contains__('keys'): - vals = map(lambda x: head + '.' + x, - [m for m in a.keys() if m.startswith(tail)]) - except: - pass - - for v in sorted(vals): - sys.stdout.write("%s\n" % v) - ######################################################################## # Command class for command 'sat config etc.' ######################################################################## class Command(_BaseCommand): + """\ + The config command allows manipulation and operation on config '.pyconf' files. + + examples: + >> sat config --list + >> sat config SALOME --edit + >> sat config SALOME --copy SALOME-new + >> sat config SALOME --value VARS + >> sat config SALOME --debug VARS + >> sat config SALOME --info ParaView + >> sat config SALOME --show_patchs + """ + + name = "config" def getParser(self): # Define all possible option for config command : sat config parser = src.options.Options() + parser.add_option('h', 'help', 'boolean', 'help', + _("shows help on command.")) parser.add_option('v', 'value', 'string', 'value', _("Optional: print the value of CONFIG_VARIABLE.")) parser.add_option('d', 'debug', 'string', 'debug', @@ -833,43 +65,41 @@ class Command(_BaseCommand): _("Optional: get information on a product.")) parser.add_option('l', 'list', 'boolean', 'list', _("Optional: list all available applications.")) - parser.add_option('', 'show_patchs', 'boolean', 'show_patchs', + parser.add_option('p', 'show_patchs', 'boolean', 'show_patchs', _("Optional: synthetic view of all patches used in the application")) parser.add_option('c', 'copy', 'boolean', 'copy', _("""Optional: copy a config file (.pyconf) to the personal config files directory. - \tWarning: the included files are not copied. - \tIf a name is given the new config file takes the given name.""")) + Warning: the included files are not copied. + If a name is given the new config file takes the given name.""")) parser.add_option('n', 'no_label', 'boolean', 'no_label', _("Internal use: do not print labels, Works only with --value and --list.")) - parser.add_option('', 'completion', 'boolean', 'completion', + parser.add_option('o', 'completion', 'boolean', 'completion', _("Internal use: print only keys, works only with --value.")) parser.add_option('s', 'schema', 'boolean', 'schema', _("Internal use.")) return parser - def description(self): - '''method that is called when salomeTools is called with --help option. - - :return: The text to display for the config command description. - :rtype: str - ''' - return _("""\ -The config command allows manipulation and operation on config files. + def run(self, cmd_arguments): + """method that is called when salomeTools is called with config parameter.""" + argList = self.assumeAsList(cmd_arguments) -example: ->> sat config SALOME-master --info ParaView""") + # print general help and returns + if len(argList) == 0: + self.print_help() + return RCO.ReturnCode("OK", "No arguments, as 'sat %s --help'" % self.name) - - def run(self, args): - '''method that is called when salomeTools is called with config parameter. - ''' + self._options, remaindersArgs = self.parseArguments(argList) + + if self._options.help: + self.print_help() + return RCO.ReturnCode("OK", "Done 'sat %s --help'" % self.name) + + # shortcuts runner = self.getRunner() config = self.getConfig() logger = self.getLogger() + options = self.getOptions() - # Parse the options - (options, argsc) = self.parse_args(args) - # Only useful for completion mechanism : print the keys of the config if options.schema: get_config_children(config, args) diff --git a/sat b/sat index d374a93..eb1cc10 100755 --- a/sat +++ b/sat @@ -17,15 +17,15 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -'''\ +"""\ This file is the main entry file to use salomeTools, in mode Command Line Argument(s) (CLI) -''' +""" import os import sys -# OKSYS and KOSYS seems equal on linux or windows +# exit OKSYS and KOSYS seems equal on linux or windows OKSYS = 0 # OK KOSYS = 1 # KO @@ -35,27 +35,41 @@ satdir = os.path.dirname(os.path.realpath(__file__)) # Make the src & commands package accessible from all code sys.path.insert(0, satdir) +import src.loggingSat as LOG +import src.debug as DBG # Easy print stderr (for DEBUG only) +logger = LOG.getDefaultLogger() + + ################################# # MAIN ################################# if __name__ == "__main__": - import src.loggingSat as LOG - import src.debug as DBG # Easy print stderr (for DEBUG only)from src.salomeTools import Sat - from src.salomeTools import Sat + from src.salomeTools import Sat # it is time... - _debug = True # Have to be False in production (for DEBUG only) - DBG.push_debug(_debug) - - logger = LOG.getDefaultLogger() - # instantiate the salomeTools class - sat = Sat(logger) - returnCode = sat.execute_cli(sys.argv[1:]) - logger.debug("sat exit code: %s" % returnCode) + _debug = False # Have to be False in production (for programmers DEBUG only) + DBG.push_debug(_debug) # as __main__ with sys.exit so no need pop_debug - sys.exit(returnCode.toSys()) + sat = Sat(logger) # instantiate the salomeTools class + args = sys.argv[1:] # skip useless "sat' + + try: + returnCode = sat.execute_cli(args) + logger.debug("sat exit code: %s" % returnCode) + sys.exit(returnCode.toSys()) + + except Exception as e: + msg = "Exception raised for execute_cli('%s'):\n\n%s" + if (_debug) or (DBG._user in DBG._developpers): + # verbose debug message with traceback + import traceback + logger.critical( msg % (args, traceback.format_exc()) ) + else: + # short production message + logger.critical( msg % (args, e) ) + sys.exit(KOSYS) else: - sys.stderr.write("\nERROR: unexpected mode __name__ '%s'" % __name__) + logger.critical("forbidden/unexpected mode for __name__ '%s'" % __name__) sys.exit(KOSYS) diff --git a/src/__init__.py b/src/__init__.py index 5ac9804..3cdf701 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,2 +1,4 @@ -import src.pyconf \ No newline at end of file +# https://www.python.org/dev/peps/pep-0396/ +__version__ = "5.1.0" +__name__ = "SalomeTools" \ No newline at end of file diff --git a/src/coloringSat.py b/src/coloringSat.py index 8fc13c6..7d88a9e 100755 --- a/src/coloringSat.py +++ b/src/coloringSat.py @@ -51,11 +51,10 @@ import colorama as CLRM from colorama import Fore as FG from colorama import Style as ST #from colorama import AnsiToWin32 -from ansitowin32 import AnsiToWin32 # debug is os.name == 'nt' ? +from colorama import AnsiToWin32 # debug is os.name == 'nt' ? CLRM.init(wrap=False) # choose NO wrapping - """ from colorama: Available formatting constants are: @@ -65,8 +64,10 @@ Style: DIM, NORMAL, BRIGHT, RESET_ALL n.b. DIM is not assumed in win32 """ -dir(ST) -# order matters for replace + +# dir(ST) + +# order matters for items replaces forward to color _tags = ( ("", FG.BLACK), ("", FG.RED), @@ -79,12 +80,14 @@ _tags = ( ("", ST.BRIGHT), ("", ST.NORMAL), ("", ST.RESET_ALL), + ("
", FG.BLUE), ("", FG.GREEN + ST.BRIGHT + "OK" + ST.RESET_ALL), ("", FG.RED + ST.BRIGHT + "KO" + ST.RESET_ALL), ) # _tagsNone = ((i, "") for i,j in _tags) # to clean tags when log not tty -_tagsNone = ( +# reversed order matters for item replaces backward to no color +_tagsNone = reversed( ( ("", ""), ("", ""), ("", ""), @@ -96,9 +99,10 @@ _tagsNone = ( ("", ""), ("", ""), ("", ""), + ("
", ""), ("", "OK"), ("", "KO"), -) +) ) def indent(msg, nb, car=" "): """indent nb car (spaces) multi lines message except first one""" @@ -176,7 +180,7 @@ if __name__ == "__main__": log("import colorama in %s" % __file__) log("set green and not reset...") log("...and here is not green because appended reset at end of message") - log("dir(FG):\n%s" % dir(FG)) - log("dir(ST):\n%s" % dir(ST)) + log("dir(FG):\n%s ... or ??" % dir(FG)) + log("dir(ST):\n%s ... or ??" % dir(ST)) - \ No newline at end of file + diff --git a/src/configManager.py b/src/configManager.py new file mode 100644 index 0000000..e05eed5 --- /dev/null +++ b/src/configManager.py @@ -0,0 +1,935 @@ +#!/usr/bin/env python +#-*- coding:utf-8 -*- +# Copyright (C) 2010-2012 CEA/DEN +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import os +import platform +import datetime +import shutil +import sys + +import src.debug as DBG +import src.loggingSat as LOG +import src.returnCode as RCO +import src.architecture as ARCH +import src.utilsSat as UTS + + +class ConfigOpener: + '''Class that helps to find an application pyconf + in all the possible directories (pathList) + ''' + def __init__(self, pathList): + '''Initialization + + :param pathList list: The list of paths where to search a pyconf. + ''' + self.pathList = pathList + + def __call__(self, name): + if os.path.isabs(name): + return src.pyconf.ConfigInputStream(open(name, 'rb')) + else: + return src.pyconf.ConfigInputStream( + open(os.path.join( self.get_path(name), name ), 'rb') ) + raise IOError(_("Configuration file '%s' not found") % name) + + def get_path( self, name ): + '''The method that returns the entire path of the pyconf searched + :param name str: The name of the searched pyconf. + ''' + for path in self.pathList: + if os.path.exists(os.path.join(path, name)): + return path + raise IOError(_("Configuration file '%s' not found") % name) + +class ConfigManager: + '''Class that manages the read of all the configuration files of salomeTools + ''' + def __init__(self, datadir=None): + self.logger = LOG.getDefaultLogger() + + + def _create_vars(self, application=None, command=None, datadir=None): + '''Create a dictionary that stores all information about machine, + user, date, repositories, etc... + + :param application str: The application for which salomeTools is called. + :param command str: The command that is called. + :param datadir str: The repository that contain external data + for salomeTools. + :return: The dictionary that stores all information. + :rtype: dict + ''' + var = {} + var['user'] = ARCH.get_user() + var['salometoolsway'] = os.path.dirname( + os.path.dirname(os.path.abspath(__file__))) + var['srcDir'] = os.path.join(var['salometoolsway'], 'src') + var['internal_dir'] = os.path.join(var['srcDir'], 'internal_config') + var['sep']= os.path.sep + + # datadir has a default location + var['datadir'] = os.path.join(var['salometoolsway'], 'data') + if datadir is not None: + var['datadir'] = datadir + + var['personalDir'] = os.path.join(os.path.expanduser('~'), + '.salomeTools') + UTS.ensure_path_exists(var['personalDir']) + + var['personal_applications_dir'] = os.path.join(var['personalDir'], + "Applications") + UTS.ensure_path_exists(var['personal_applications_dir']) + + var['personal_products_dir'] = os.path.join(var['personalDir'], + "products") + UTS.ensure_path_exists(var['personal_products_dir']) + + var['personal_archives_dir'] = os.path.join(var['personalDir'], + "Archives") + UTS.ensure_path_exists(var['personal_archives_dir']) + + var['personal_jobs_dir'] = os.path.join(var['personalDir'], + "Jobs") + UTS.ensure_path_exists(var['personal_jobs_dir']) + + var['personal_machines_dir'] = os.path.join(var['personalDir'], + "Machines") + UTS.ensure_path_exists(var['personal_machines_dir']) + + # read linux distributions dictionary + distrib_cfg = src.pyconf.Config(os.path.join(var['srcDir'], + 'internal_config', + 'distrib.pyconf')) + + # set platform parameters + dist_name = ARCH.get_distribution(codes=distrib_cfg.DISTRIBUTIONS) + dist_version = ARCH.get_distrib_version(dist_name, + codes=distrib_cfg.VERSIONS) + dist = dist_name + dist_version + + var['dist_name'] = dist_name + var['dist_version'] = dist_version + var['dist'] = dist + var['python'] = ARCH.get_python_version() + + var['nb_proc'] = ARCH.get_nb_proc() + node_name = platform.node() + var['node'] = node_name + var['hostname'] = node_name + + # set date parameters + dt = datetime.datetime.now() + var['date'] = dt.strftime('%Y%m%d') + var['datehour'] = dt.strftime('%Y%m%d_%H%M%S') + var['hour'] = dt.strftime('%H%M%S') + + var['command'] = str(command) + var['application'] = str(application) + + # Root dir for temporary files + var['tmp_root'] = os.sep + 'tmp' + os.sep + var['user'] + # particular win case + if ARCH.is_windows() : + var['tmp_root'] = os.path.expanduser('~') + os.sep + 'tmp' + + return var + + def get_command_line_overrides(self, options, sections): + '''get all the overwrites that are in the command line + + :param options: the options from salomeTools class + initialization (like -l5 or --overwrite) + :param sections str: The config section to overwrite. + :return: The list of all the overwrites to apply. + :rtype: list + ''' + # when there are no options or not the overwrite option, + # return an empty list + if options is None or options.overwrite is None: + return [] + + over = [] + for section in sections: + # only overwrite the sections that correspond to the option + over.extend(filter(lambda l: l.startswith(section + "."), + options.overwrite)) + return over + + def get_config(self, application=None, options=None, command=None, + datadir=None): + '''get the config from all the configuration files. + + :param application str: The application for which salomeTools is called. + :param options class Options: The general salomeTools + options (--overwrite or -v5, for example) + :param command str: The command that is called. + :param datadir str: The repository that contain + external data for salomeTools. + :return: The final config. + :rtype: class 'src.pyconf.Config' + ''' + + # create a ConfigMerger to handle merge + merger = src.pyconf.ConfigMerger() #MergeHandler()) + + # create the configuration instance + cfg = src.pyconf.Config() + + # ===================================================================== + # create VARS section + var = self._create_vars(application=application, command=command, + datadir=datadir) + # add VARS to config + cfg.VARS = src.pyconf.Mapping(cfg) + for variable in var: + cfg.VARS[variable] = var[variable] + + # apply overwrite from command line if needed + for rule in self.get_command_line_overrides(options, ["VARS"]): + exec('cfg.' + rule) # this cannot be factorized because of the exec + + # ===================================================================== + # Load INTERNAL config + # read src/internal_config/salomeTools.pyconf + src.pyconf.streamOpener = ConfigOpener([ + os.path.join(cfg.VARS.srcDir, 'internal_config')]) + try: + internal_cfg = src.pyconf.Config(open(os.path.join(cfg.VARS.srcDir, + 'internal_config', 'salomeTools.pyconf'))) + except src.pyconf.ConfigError as e: + raise src.SatException(_("Error in configuration file:" + " salomeTools.pyconf\n %(error)s") % \ + {'error': str(e) }) + + merger.merge(cfg, internal_cfg) + + # apply overwrite from command line if needed + for rule in self.get_command_line_overrides(options, ["INTERNAL"]): + exec('cfg.' + rule) # this cannot be factorized because of the exec + + # ===================================================================== + # Load LOCAL config file + # search only in the data directory + src.pyconf.streamOpener = ConfigOpener([cfg.VARS.datadir]) + try: + local_cfg = src.pyconf.Config(open(os.path.join(cfg.VARS.datadir, + 'local.pyconf')), + PWD = ('LOCAL', cfg.VARS.datadir) ) + except src.pyconf.ConfigError as e: + raise src.SatException(_("Error in configuration file: " + "local.pyconf\n %(error)s") % \ + {'error': str(e) }) + except IOError as error: + e = str(error) + raise src.SatException( e ); + merger.merge(cfg, local_cfg) + + # When the key is "default", put the default value + if cfg.LOCAL.base == "default": + cfg.LOCAL.base = os.path.abspath( + os.path.join(cfg.VARS.salometoolsway, + "..", + "BASE")) + if cfg.LOCAL.workdir == "default": + cfg.LOCAL.workdir = os.path.abspath( + os.path.join(cfg.VARS.salometoolsway, + "..")) + if cfg.LOCAL.log_dir == "default": + cfg.LOCAL.log_dir = os.path.abspath( + os.path.join(cfg.VARS.salometoolsway, + "..", + "LOGS")) + + if cfg.LOCAL.archive_dir == "default": + cfg.LOCAL.archive_dir = os.path.abspath( + os.path.join(cfg.VARS.salometoolsway, + "..", + "ARCHIVES")) + + # apply overwrite from command line if needed + for rule in self.get_command_line_overrides(options, ["LOCAL"]): + exec('cfg.' + rule) # this cannot be factorized because of the exec + + # ===================================================================== + # Load the PROJECTS + projects_cfg = src.pyconf.Config() + projects_cfg.addMapping("PROJECTS", + src.pyconf.Mapping(projects_cfg), + "The projects\n") + projects_cfg.PROJECTS.addMapping("projects", + src.pyconf.Mapping(cfg.PROJECTS), + "The projects definition\n") + + for project_pyconf_path in cfg.PROJECTS.project_file_paths: + if not os.path.exists(project_pyconf_path): + msg = _("Cannot find project file %s, Ignored.") % project_pyconf_path + self.logger.warning(msg) + continue + project_name = os.path.basename(project_pyconf_path)[:-len(".pyconf")] + try: + project_pyconf_dir = os.path.dirname(project_pyconf_path) + project_cfg = src.pyconf.Config(open(project_pyconf_path), + PWD=("", project_pyconf_dir)) + except Exception as e: + msg = _("ERROR: Error in configuration file: %(file_path)s\n %(error)s\n") % \ + {'file_path' : project_pyconf_path, 'error': str(e) } + sys.stdout.write(msg) + continue + projects_cfg.PROJECTS.projects.addMapping(project_name, + src.pyconf.Mapping(projects_cfg.PROJECTS.projects), + "The %s project\n" % project_name) + projects_cfg.PROJECTS.projects[project_name]=project_cfg + projects_cfg.PROJECTS.projects[project_name]["file_path"] = \ + project_pyconf_path + + merger.merge(cfg, projects_cfg) + + # apply overwrite from command line if needed + for rule in self.get_command_line_overrides(options, ["PROJECTS"]): + exec('cfg.' + rule) # this cannot be factorized because of the exec + + # ===================================================================== + # Create the paths where to search the application configurations, + # the product configurations, the products archives, + # the jobs configurations and the machines configurations + cfg.addMapping("PATHS", src.pyconf.Mapping(cfg), "The paths\n") + cfg.PATHS["APPLICATIONPATH"] = src.pyconf.Sequence(cfg.PATHS) + cfg.PATHS.APPLICATIONPATH.append(cfg.VARS.personal_applications_dir, "") + + cfg.PATHS["PRODUCTPATH"] = src.pyconf.Sequence(cfg.PATHS) + cfg.PATHS.PRODUCTPATH.append(cfg.VARS.personal_products_dir, "") + cfg.PATHS["ARCHIVEPATH"] = src.pyconf.Sequence(cfg.PATHS) + cfg.PATHS.ARCHIVEPATH.append(cfg.VARS.personal_archives_dir, "") + cfg.PATHS["JOBPATH"] = src.pyconf.Sequence(cfg.PATHS) + cfg.PATHS.JOBPATH.append(cfg.VARS.personal_jobs_dir, "") + cfg.PATHS["MACHINEPATH"] = src.pyconf.Sequence(cfg.PATHS) + cfg.PATHS.MACHINEPATH.append(cfg.VARS.personal_machines_dir, "") + + # initialise the path with local directory + cfg.PATHS["ARCHIVEPATH"].append(cfg.LOCAL.archive_dir, "") + + # Loop over the projects in order to complete the PATHS variables + for project in cfg.PROJECTS.projects: + for PATH in ["APPLICATIONPATH", + "PRODUCTPATH", + "ARCHIVEPATH", + "JOBPATH", + "MACHINEPATH"]: + if PATH not in cfg.PROJECTS.projects[project]: + continue + cfg.PATHS[PATH].append(cfg.PROJECTS.projects[project][PATH], "") + + # apply overwrite from command line if needed + for rule in self.get_command_line_overrides(options, ["PATHS"]): + exec('cfg.' + rule) # this cannot be factorized because of the exec + + # ===================================================================== + # Load APPLICATION config file + if application is not None: + # search APPLICATION file in all directories in configPath + cp = cfg.PATHS.APPLICATIONPATH + src.pyconf.streamOpener = ConfigOpener(cp) + do_merge = True + try: + application_cfg = src.pyconf.Config(application + '.pyconf') + except IOError as e: + raise src.SatException(_("%s, use 'config --list' to get the" + " list of available applications.") % e) + except src.pyconf.ConfigError as e: + if (not ('-e' in parser.parse_args()[1]) + or ('--edit' in parser.parse_args()[1]) + and command == 'config'): + raise src.SatException( + _("Error in configuration file: (1)s.pyconf\n %(2)s") % \ + { 'application': application, 'error': str(e) } ) + else: + sys.stdout.write(src.printcolors.printcWarning( + "There is an error in the file %s.pyconf.\n" % \ + cfg.VARS.application)) + do_merge = False + except Exception as e: + if ( not('-e' in parser.parse_args()[1]) or + ('--edit' in parser.parse_args()[1]) and + command == 'config' ): + sys.stdout.write(src.printcolors.printcWarning("%s\n" % str(e))) + raise src.SatException( + _("Error in configuration file: %s.pyconf\n") % application ) + else: + sys.stdout.write(src.printcolors.printcWarning( + "ERROR: in file %s.pyconf. Opening the file with the default viewer\n" % \ + cfg.VARS.application)) + sys.stdout.write("\n%s\n" % src.printcolors.printcWarning(str(e))) + do_merge = False + + else: + cfg['open_application'] = 'yes' + + # ===================================================================== + # Load product config files in PRODUCTS section + products_cfg = src.pyconf.Config() + products_cfg.addMapping("PRODUCTS", + src.pyconf.Mapping(products_cfg), + "The products\n") + if application is not None: + src.pyconf.streamOpener = ConfigOpener(cfg.PATHS.PRODUCTPATH) + for product_name in application_cfg.APPLICATION.products.keys(): + # Loop on all files that are in softsDir directory + # and read their config + product_file_name = product_name + ".pyconf" + product_file_path = src.find_file_in_lpath(product_file_name, cfg.PATHS.PRODUCTPATH) + if product_file_path: + products_dir = os.path.dirname(product_file_path) + try: + prod_cfg = src.pyconf.Config(open(product_file_path), + PWD=("", products_dir)) + prod_cfg.from_file = product_file_path + products_cfg.PRODUCTS[product_name] = prod_cfg + except Exception as e: + msg = _( + "WARNING: Error in configuration file: %(prod)s\n %(error)s" % \ + {'prod' : product_name, 'error': str(e) }) + sys.stdout.write(msg) + + merger.merge(cfg, products_cfg) + + # apply overwrite from command line if needed + for rule in self.get_command_line_overrides(options, ["PRODUCTS"]): + exec('cfg.' + rule) # this cannot be factorized because of the exec + + if do_merge: + merger.merge(cfg, application_cfg) + + # default launcher name ('salome') + if ('profile' in cfg.APPLICATION and + 'launcher_name' not in cfg.APPLICATION.profile): + cfg.APPLICATION.profile.launcher_name = 'salome' + + # apply overwrite from command line if needed + for rule in self.get_command_line_overrides(options, + ["APPLICATION"]): + # this cannot be factorized because of the exec + exec('cfg.' + rule) + + # ===================================================================== + # load USER config + self.set_user_config_file(cfg) + user_cfg_file = self.get_user_config_file() + user_cfg = src.pyconf.Config(open(user_cfg_file)) + merger.merge(cfg, user_cfg) + + # apply overwrite from command line if needed + for rule in self.get_command_line_overrides(options, ["USER"]): + exec('cfg.' + rule) # this cannot be factorize because of the exec + + return cfg + + def set_user_config_file(self, config): + '''Set the user config file name and path. + If necessary, build it from another one or create it from scratch. + + :param config class 'src.pyconf.Config': The global config + (containing all pyconf). + ''' + # get the expected name and path of the file + self.config_file_name = 'SAT.pyconf' + self.user_config_file_path = os.path.join(config.VARS.personalDir, + self.config_file_name) + + # if pyconf does not exist, create it from scratch + if not os.path.isfile(self.user_config_file_path): + self.create_config_file(config) + + def create_config_file(self, config): + '''This method is called when there are no user config file. + It build it from scratch. + + :param config class 'src.pyconf.Config': The global config. + :return: the config corresponding to the file created. + :rtype: config class 'src.pyconf.Config' + ''' + + cfg_name = self.get_user_config_file() + + user_cfg = src.pyconf.Config() + # + user_cfg.addMapping('USER', src.pyconf.Mapping(user_cfg), "") + + user_cfg.USER.addMapping('cvs_user', config.VARS.user, + "This is the user name used to access salome cvs base.\n") + user_cfg.USER.addMapping('svn_user', config.VARS.user, + "This is the user name used to access salome svn base.\n") + user_cfg.USER.addMapping('output_verbose_level', 3, + "This is the default output_verbose_level you want." + " 0=>no output, 5=>debug.\n") + user_cfg.USER.addMapping('publish_dir', + os.path.join(os.path.expanduser('~'), + 'websupport', + 'satreport'), + "") + user_cfg.USER.addMapping('editor', + 'vi', + "This is the editor used to " + "modify configuration files\n") + user_cfg.USER.addMapping('browser', + 'firefox', + "This is the browser used to " + "read html documentation\n") + user_cfg.USER.addMapping('pdf_viewer', + 'evince', + "This is the pdf_viewer used " + "to read pdf documentation\n") +# CNC 25/10/17 : plus nécessaire a priori +# user_cfg.USER.addMapping("base", +# src.pyconf.Reference( +# user_cfg, +# src.pyconf.DOLLAR, +# 'workdir + $VARS.sep + "BASE"'), +# "The products installation base (could be " +# "ignored if this key exists in the local.pyconf" +# " file of salomTools).\n") + + # + src.ensure_path_exists(config.VARS.personalDir) + src.ensure_path_exists(os.path.join(config.VARS.personalDir, + 'Applications')) + + f = open(cfg_name, 'w') + user_cfg.__save__(f) + f.close() + + return user_cfg + + def get_user_config_file(self): + '''Get the user config file + :return: path to the user config file. + :rtype: str + ''' + if not self.user_config_file_path: + raise src.SatException( + _("Error in get_user_config_file: missing user config file path") ) + return self.user_config_file_path + +def check_path(path, ext=[]): + '''Construct a text with the input path and "not found" if it does not + exist. + + :param path Str: the path to check. + :param ext List: An extension. Verify that the path extension + is in the list + :return: The string of the path with information + :rtype: Str + ''' + # check if file exists + if not os.path.exists(path): + return "'%s' %s" % (path, src.printcolors.printcError(_("** not found"))) + + # check extension + if len(ext) > 0: + fe = os.path.splitext(path)[1].lower() + if fe not in ext: + return "'%s' %s" % (path, src.printcolors.printcError(_("** bad extension"))) + + return path + +def show_product_info(config, name, logger): + '''Display on the terminal and logger information about a product. + + :param config Config: the global configuration. + :param name Str: The name of the product + :param logger Logger: The logger instance to use for the display + ''' + + logger.write(_("%s is a product\n") % src.printcolors.printcLabel(name), 2) + pinfo = src.product.get_product_config(config, name) + + if "depend" in pinfo: + src.printcolors.print_value(logger, + "depends on", + ', '.join(pinfo.depend), 2) + + if "opt_depend" in pinfo: + src.printcolors.print_value(logger, + "optional", + ', '.join(pinfo.opt_depend), 2) + + # information on pyconf + logger.write("\n", 2) + logger.write(src.printcolors.printcLabel("configuration:") + "\n", 2) + if "from_file" in pinfo: + src.printcolors.print_value(logger, + "pyconf file path", + pinfo.from_file, + 2) + if "section" in pinfo: + src.printcolors.print_value(logger, + "section", + pinfo.section, + 2) + + # information on prepare + logger.write("\n", 2) + logger.write(src.printcolors.printcLabel("prepare:") + "\n", 2) + + is_dev = src.product.product_is_dev(pinfo) + method = pinfo.get_source + if is_dev: + method += " (dev)" + src.printcolors.print_value(logger, "get method", method, 2) + + if method == 'cvs': + src.printcolors.print_value(logger, "server", pinfo.cvs_info.server, 2) + src.printcolors.print_value(logger, "base module", + pinfo.cvs_info.module_base, 2) + src.printcolors.print_value(logger, "source", pinfo.cvs_info.source, 2) + src.printcolors.print_value(logger, "tag", pinfo.cvs_info.tag, 2) + + elif method == 'svn': + src.printcolors.print_value(logger, "repo", pinfo.svn_info.repo, 2) + + elif method == 'git': + src.printcolors.print_value(logger, "repo", pinfo.git_info.repo, 2) + src.printcolors.print_value(logger, "tag", pinfo.git_info.tag, 2) + + elif method == 'archive': + src.printcolors.print_value(logger, + "get from", + check_path(pinfo.archive_info.archive_name), + 2) + + if 'patches' in pinfo: + for patch in pinfo.patches: + src.printcolors.print_value(logger, "patch", check_path(patch), 2) + + if src.product.product_is_fixed(pinfo): + src.printcolors.print_value(logger, "install_dir", + check_path(pinfo.install_dir), 2) + + if src.product.product_is_native(pinfo) or src.product.product_is_fixed(pinfo): + return + + # information on compilation + if src.product.product_compiles(pinfo): + logger.write("\n", 2) + logger.write(src.printcolors.printcLabel("compile:") + "\n", 2) + src.printcolors.print_value(logger, + "compilation method", + pinfo.build_source, + 2) + + if pinfo.build_source == "script" and "compil_script" in pinfo: + src.printcolors.print_value(logger, + "Compilation script", + pinfo.compil_script, + 2) + + if 'nb_proc' in pinfo: + src.printcolors.print_value(logger, "make -j", pinfo.nb_proc, 2) + + src.printcolors.print_value(logger, + "source dir", + check_path(pinfo.source_dir), + 2) + if 'install_dir' in pinfo: + src.printcolors.print_value(logger, + "build dir", + check_path(pinfo.build_dir), + 2) + src.printcolors.print_value(logger, + "install dir", + check_path(pinfo.install_dir), + 2) + else: + logger.write(" %s\n" % src.printcolors.printcWarning(_("no install dir")) , 2) + else: + logger.write("\n", 2) + msg = _("This product does not compile") + logger.write("%s\n" % msg, 2) + + # information on environment + logger.write("\n", 2) + logger.write(src.printcolors.printcLabel("environ :") + "\n", 2) + if "environ" in pinfo and "env_script" in pinfo.environ: + src.printcolors.print_value(logger, + "script", + check_path(pinfo.environ.env_script), + 2) + + zz = src.environment.SalomeEnviron(config, + src.fileEnviron.ScreenEnviron(logger), + False) + zz.set_python_libdirs() + zz.set_a_product(name, logger) + +def show_patchs(config, logger): + """ + Prints all the used patchs in the application. + + :param config Config: the global configuration. + :param logger Logger: The logger instance to use for the display + """ + len_max = max([len(p) for p in config.APPLICATION.products]) + 2 + msg = "" + for product in config.APPLICATION.products: + nb = len_max-len(product)-2 + product_info = src.product.get_product_config(config, product) + if src.product.product_has_patches(product_info): + msg += "
%s: " % product + msg += " "*nb + "%s\n" % product_info.patches[0] + if len(product_info.patches) > 1: + for patch in product_info.patches[1:]: + msg += " "*nb + "%s\n" % patch + msg += "\n" + logger.info(msg) + return + +def getConfigColored(config, path, stream, show_label=False, level=0, show_full_path=False): + """ + get a colored representation value from a config pyconf instance. + used recursively from the initial path. + + :param config class 'src.pyconf.Config': The configuration + from which the value is displayed. + :param path str : the path in the configuration of the value to print. + :param show_label boolean: if True, do a basic display. + (useful for bash completion) + :param stream: the output stream used + :param level int: The number of spaces to add before display. + :param show_full_path: display full path, else relative + """ + + # Make sure that the path does not ends with a point + if path.endswith('.'): + path = path[:-1] + + # display all the path or not + if show_full_path: + vname = path + else: + vname = path.split('.')[-1] + + # number of spaces before the display + tab_level = " " * level + + # call to the function that gets the value of the path. + try: + val = config.getByPath(path) + except Exception as e: + stream.write(tab_level) + stream.write("
%s: ERROR %s\n" % (vname, str(e))) + return + + # in this case, display only the value + if show_label: + stream.write(tab_level) + stream.write("" % vname) + + # The case where the value has under values, + # do a recursive call to the function + if dir(val).__contains__('keys'): + if show_label: strean.write("\n") + for v in sorted(val.keys()): + print_value(config, path + '.' + v, stream, show_label, level + 1) + elif val.__class__ == src.pyconf.Sequence or isinstance(val, list): + # in this case, value is a list (or a Sequence) + if show_label: stream.write("\n") + index = 0 + for v in val: + p = path + "[" + str(index) + "]" + print_value(config, p, stream, show_label, level + 1) + index += 1 + else: # case where val is just a str + stream.write("%s\n" % val) + +def print_value(config, path, logger, show_label=False, level=0, show_full_path=False): + """ + print a colored representation value from a config pyconf instance. + used recursively from the initial path. + + :param see getConfigColored + """ + outStream = DBG.OutStream() + getConfigColored(config, path, outStream, show_label, level, show_full_path) + res = outStream.value + logger.info(res) + return + + +def print_debug(config, path, logger, show_label=False, level=0, show_full_path=False): + """ + logger output for debugging a config/pyconf + lines contains: + path : expression --> 'evaluation' + example: + .PROJECTS.projects.salome.project_path : $PWD --> '/tmp/SALOME' + """ + path = str(aPath) + if path == "." : + val = config + path = "" + else: + if path.endswith('.'): # Make sure that the path does not ends with a point + path = path[:-1] + val = config.getByPath(path) + + outStream = DBG.OutStream() + DBG.saveConfigDbg(val, outStream, path=path) + res = outStream.value + logger.info(res) + return + + +def get_config_children(config, args): + """ + Gets the names of the children of the given parameter. + Useful only for completion mechanism + + :param config Config: The configuration where to read the values + :param args: The path in the config from which get the keys + """ + vals = [] + rootkeys = config.keys() + + if len(args) == 0: + # no parameter returns list of root keys + vals = rootkeys + else: + parent = args[0] + pos = parent.rfind('.') + if pos < 0: + # Case where there is only on key as parameter. + # For example VARS + vals = [m for m in rootkeys if m.startswith(parent)] + else: + # Case where there is a part from a key + # for example VARS.us (for VARS.user) + head = parent[0:pos] + tail = parent[pos+1:] + try: + a = config.getByPath(head) + if dir(a).__contains__('keys'): + vals = map(lambda x: head + '.' + x, + [m for m in a.keys() if m.startswith(tail)]) + except: + pass + + for v in sorted(vals): + sys.stdout.write("%s\n" % v) + + +def _getConfig(self, appliToLoad): + '''The function that will load the configuration (all pyconf) + and returns the config from some files .pyconf + ''' + if self.runner.config is not None: + raise Exception("config existing yet in '%s' instance" % self.runner.getClassName()) + + + # read the configuration from all the pyconf files + cfgManager = getConfigManager() # commands.config.ConfigManager() + DBG.write("appli to load", appliToLoad, True) + config = cfgManager.get_config(datadir=self.runner.datadir, + application=appliToLoad, + options=self.runner.options, + command=self.name) + self.runner.nameAppliLoaded = appliToLoad + # DBG.write("appli loaded", config, True) + + # Set the verbose mode if called + DBG.tofix("verbose/batch/logger_add_link -1/False/None", True) + verbose = -1 + batch = False + logger_add_link = None + if verbose > -1: + verbose_save = self.options.output_verbose_level + self.options.__setattr__("output_verbose_level", verbose) + + # Set batch mode if called + if batch: + batch_save = self.options.batch + self.options.__setattr__("batch", True) + + # set output level + if self.runner.options.output_verbose_level is not None: + config.USER.output_verbose_level = self.runner.options.output_verbose_level + if config.USER.output_verbose_level < 1: + config.USER.output_verbose_level = 0 + silent = (config.USER.output_verbose_level == 0) + + # create log file + micro_command = False + if logger_add_link: + micro_command = True + logger_command = src.logger.Logger(config, + silent_sysstd=silent, + all_in_terminal=self.runner.options.all_in_terminal, + micro_command=micro_command) + + # Check that the path given by the logs_paths_in_file option + # is a file path that can be written + if self.runner.options.logs_paths_in_file and not micro_command: + try: + self.options.logs_paths_in_file = os.path.abspath( + self.options.logs_paths_in_file) + dir_file = os.path.dirname(self.options.logs_paths_in_file) + if not os.path.exists(dir_file): + os.makedirs(dir_file) + if os.path.exists(self.options.logs_paths_in_file): + os.remove(self.options.logs_paths_in_file) + file_test = open(self.options.logs_paths_in_file, "w") + file_test.close() + except Exception as e: + msg = _("WARNING: the logs_paths_in_file option will " + "not be taken into account.\nHere is the error:") + logger_command.write("%s\n%s\n\n" % ( + src.printcolors.printcWarning(msg), + str(e))) + self.options.logs_paths_in_file = None + + return config + +def get_products_list(self, options, cfg, logger): + '''method that gives the product list with their informations from + configuration regarding the passed options. + + :param options Options: The Options instance that stores the commands + arguments + :param config Config: The global configuration + :param logger Logger: The logger instance to use for the display and logging + :return: The list of (product name, product_informations). + :rtype: List + ''' + # Get the products to be prepared, regarding the options + if options.products is None: + # No options, get all products sources + products = cfg.APPLICATION.products + else: + # if option --products, check that all products of the command line + # are present in the application. + products = options.products + for p in products: + if p not in cfg.APPLICATION.products: + raise Exception(_("Product %(product)s " + "not defined in application %(application)s") % + { 'product': p, 'application': cfg.VARS.application} ) + + # Construct the list of tuple containing + # the products name and their definition + products_infos = src.product.get_products_infos(products, cfg) + + return products_infos \ No newline at end of file diff --git a/src/debug.py b/src/debug.py index dbf7e67..9a9056d 100644 --- a/src/debug.py +++ b/src/debug.py @@ -38,6 +38,10 @@ import pprint as PP _debug = [False] #support push/pop for temporary activate debug outputs +_user = os.environ['USER'] +_developpers = ["christian", "wambeke", ] # crouzet, kloss ... + + def indent(text, amount=2, ch=' '): """indent multi lines message""" padding = amount * ch @@ -46,8 +50,10 @@ def indent(text, amount=2, ch=' '): def write(title, var="", force=None, fmt="\n#### DEBUG: %s:\n%s\n"): """write sys.stderr a message if _debug[-1]==True or optionaly force=True""" if _debug[-1] or force: - if 'src.pyconf.Config' in str(type(var)): + if 'pyconf.Config' in str(type(var)): sys.stderr.write(fmt % (title, indent(getStrConfigDbg(var)))) + if 'loggingSat.UnittestStream' in str(type(var)): + sys.stderr.write(fmt % (title, indent(var.getLogs()))) elif type(var) is not str: sys.stderr.write(fmt % (title, indent(PP.pformat(var)))) else: diff --git a/src/i18n/fr/LC_MESSAGES/salomeTools.po b/src/i18n/fr/LC_MESSAGES/salomeTools.po index 1101bdf..692522d 100644 --- a/src/i18n/fr/LC_MESSAGES/salomeTools.po +++ b/src/i18n/fr/LC_MESSAGES/salomeTools.po @@ -42,46 +42,38 @@ msgid "batch mode (no question)." msgstr "mode batch (pas de question)." msgid "all traces in the terminal (for example compilation logs)." -msgstr "toutes traces dans le terminal (par exemple: log de compilation)" +msgstr "toutes traces dans le terminal (par exemple: log de compilation)." -msgid "put the command result and paths to log files." +msgid "put the command result and paths to log files" msgstr "redirige resultats de commandes vers log files" msgid " is not a valid command" msgstr " n'est pas une commande valide" -msgid "" -"WARNING: the logs_paths_in_file option will not be taken into account.\n" -"Here is the error:" -msgstr "" +msgid "the 'logs_paths_in_file' option will not be taken into account:" +msgstr "l'option 'logs_paths_in_file' ne sera pas prise en compte:" -msgid "Usage: " -msgstr "Utilisation : " +msgid "Usage:" +msgstr "Utilisation:" -msgid "Available commands are:\n" -msgstr "Les commandes disponibles sont:\n" +msgid "Available commands are:" +msgstr "Les commandes disponibles sont:" -msgid "" -"\n" -"Getting the help for a specific command: " -msgstr "" -"\n" -"Obtenir l'aide d'une commande spécifique : " +msgid "Getting help for a specific command: " +msgstr "Obtenir l'aide d'une commande spécifique: " -msgid "lsb_release not installed\n" -msgstr "lsb_release n'est pas installé\n" +msgid "'lsb_release' is not installed" +msgstr "'lsb_release' n'est pas installé" -msgid "You can define $LSB_PATH to give the path to lsb_release\n" -msgstr "Vous pouvez définir $LSB_PATH pour donner le chemin vers lsb_release\n" +msgid "You can define $LSB_PATH to give the path to lsb_release" +msgstr "Vous pouvez définir $LSB_PATH pour donner le chemin vers lsb_release" #, python-format -msgid "Unknown distribution: '%s'\n" -msgstr "Distribution inconnue : '%s'\n" +msgid "Unknown distribution: '%s'" +msgstr "Distribution inconnue : '%s'" -msgid "Please add your distribution to src/internal_config/distrib.pyconf\n" -msgstr "" -"SVP ajoutez votre distribution au fichier src/internal_config/distrib." -"pyconf\n" +msgid "Add your distribution to src/internal_config/distrib.pyconf" +msgstr "Ajoutez votre distribution au fichier src/internal_config/distrib.pyconf" msgid " is not a valid option" msgstr " n'est pas une option valide" diff --git a/src/loggingSat.py b/src/loggingSat.py index 2ac44a0..30f1a5e 100755 --- a/src/loggingSat.py +++ b/src/loggingSat.py @@ -16,6 +16,7 @@ import os import sys import logging import pprint as PP +import src.coloringSat as COLS _verbose = False _name = "loggingSat" @@ -37,6 +38,7 @@ log("import logging on %s" % logging.__file__) _loggerDefaultName = 'SatDefaultLogger' _loggerUnittestName = 'SatUnittestLogger' + def getDefaultLogger(): log("getDefaultLogger %s" % _loggerDefaultName) return logging.getLogger(_loggerDefaultName) @@ -53,18 +55,34 @@ _loggerUnittest = getUnittestLogger() class DefaultFormatter(logging.Formatter): + + # to set color prefix, problem with indent format + _ColorLevelname = { + "DEBUG": "", + "INFO": "", + "WARNING": "", + "ERROR": "", + "CRITICAL": "", + } + def format(self, record): - # print "", record.levelname #type(record), dir(record) - if record.levelname == "INFO": - return str(record.msg) + if record.levelname == "INFO": + res = str(record.msg) else: - return indent(super(DefaultFormatter, self).format(record), 12) + #record.levelname = self.setColorLevelname(record.levelname) + res = indent(super(DefaultFormatter, self).format(record), 12) + return COLS.toColor(res) + + def setColorLevelname(self, levelname): + return self._ColorLevelname[levelname] + levelname + "" + class UnittestFormatter(logging.Formatter): def format(self, record): # print "", record.levelname #type(record), dir(record) nb = len("2018-03-17 12:15:41 :: INFO :: ") - return indent(super(UnittestFormatter, self).format(record), nb) + res = indent(super(UnittestFormatter, self).format(record), nb) + return COLS.toColor(res) class UnittestStream(object): @@ -75,17 +93,25 @@ class UnittestStream(object): https://stackoverflow.com/questions/31999627/storing-logger-messages-in-a-string """ def __init__(self): - self.logs = '' + self._logs = '' + + def getLogs(self): + return self._logs + + def getLogsAndClear(self): + res = self._logs + self._logs = '' + return res def write(self, astr): # log("UnittestStream.write('%s')" % astr) - self.logs += astr + self._logs += astr def flush(self): pass def __str__(self): - return self.logs + return self._logs def initLoggerAsDefault(logger, fmt=None, level=None): @@ -94,7 +120,7 @@ def initLoggerAsDefault(logger, fmt=None, level=None): exept info() outed 'as it' without any format """ log("initLoggerAsDefault name=%s\nfmt='%s' level='%s'" % (logger.name, fmt, level)) - handler = logging.StreamHandler() # Logging vers console + handler = logging.StreamHandler(sys.stdout) # Logging vers console if fmt is not None: # formatter = logging.Formatter(fmt, "%Y-%m-%d %H:%M:%S") formatter = DefaultFormatter(fmt, "%y-%m-%d %H:%M:%S") @@ -120,7 +146,9 @@ def initLoggerAsUnittest(logger, fmt=None, level=None): formatter = UnittestFormatter(fmt, "%Y-%m-%d %H:%M:%S") handler.setFormatter(formatter) logger.addHandler(handler) - logger.streamUnittest = stream + logger.stream = stream + logger.getLogs = stream.getLogs + logger.getLogsAndClear = stream.getLogsAndClear if level is not None: logger.setLevel(level) else: @@ -135,16 +163,17 @@ def testLogger_1(logger): logger.warning('test logger warning') logger.error('test logger error') logger.critical('test logger critical') - logger.info('test logger info:\n- second line\n- third line') + logger.info('\ntest logger info: no indent\n- second line\n- third line\n') logger.warning('test logger warning:\n- second line\n- third line') if __name__ == "__main__": - print("\n**** DEFAULT") + print("\n**** DEFAULT logger") logdef = getDefaultLogger() + # problem if add +2? if append 2 setColorLevelname , not fixed initLoggerAsDefault(logdef, '%(levelname)-8s :: %(message)s', level=logging.INFO) testLogger_1(logdef) - print("\n**** UNITTEST") + print("\n**** UNITTEST logger") loguni = getUnittestLogger() initLoggerAsUnittest(loguni, '%(asctime)s :: %(levelname)-8s :: %(message)s', level=logging.DEBUG) testLogger_1(loguni) # is silent @@ -153,5 +182,10 @@ if __name__ == "__main__": from colorama import Fore as FG from colorama import Style as ST - print("this is %scolored%s!" % (FG.G)) - \ No newline at end of file + print("this is %scolored in green%s !!!" % (FG.GREEN, ST.RESET_ALL)) + +else: + _loggerDefault = getDefaultLogger() + _loggerUnittest = getUnittestLogger() + initLoggerAsDefault(_loggerDefault, '%(levelname)-8s :: %(message)s', level=logging.INFO) + initLoggerAsUnittest(_loggerUnittest, '%(asctime)s :: %(levelname)-8s :: %(message)s', level=logging.DEBUG) diff --git a/src/options.py b/src/options.py index ac7270e..714fcc4 100644 --- a/src/options.py +++ b/src/options.py @@ -15,26 +15,29 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -'''The Options class that manages the access to all options passed as - parameters in salomeTools command lines -''' + +"""\ +The Options class that manages the access to all options passed as +parameters in salomeTools command lines +""" + import getopt import sys import pprint as PP import src import src.debug as DBG # Easy print stderr (for DEBUG only) -import src.printcolors class OptResult(object): - '''An instance of this class will be the object manipulated - in code of all salomeTools commands - The aim of this class is to have an elegant syntax - to manipulate the options. - ex: - print(options.level) - 5 - ''' + """ + An instance of this class will be the object manipulated + in code of all salomeTools commands + The aim of this class is to have an elegant syntax to manipulate the options. + + example: + >> print(options.level) + >> 5 + """ def __init__(self): '''Initialization ''' @@ -99,6 +102,13 @@ class Options(object): :return: Nothing. :rtype: N\A ''' + tmp = [o['shortName'] for o in self.options if o['shortName'] != ''] + if shortName in tmp: + raise Exception("option '-%s' existing yet" % shortName) + tmp = [o['longName'] for o in self.options if o['longName'] != ''] + if longName in tmp: + raise Exception("option '--%s' existing yet" % longName) + option = dict() option['shortName'] = shortName option['longName'] = longName @@ -111,6 +121,7 @@ class Options(object): option['destName'] = destName option['helpString'] = helpString option['result'] = default + self.options.append(option) def get_help(self): @@ -127,7 +138,7 @@ class Options(object): # for all options, gets its values. # "shortname" is an optional field of the options - msg += src.printcolors.printcHeader(_("Available options are:")) + "\n" + msg += "
" + _("Available options are:") + "\n" for option in self.options: if 'shortName' in option and len(option['shortName']) > 0: msg += "\n -%(shortName)1s, --%(longName)s" \ @@ -137,9 +148,6 @@ class Options(object): % option return msg - def print_help(self): - print(self.get_help()) - def parse_args(self, argList=None): '''Method that instantiates the class OptResult @@ -177,7 +185,7 @@ class Options(object): try: optlist, args = getopt.getopt(argList, shortNameOption, longNameOption) except Exception as e: - msg = str(e) + ", expected:\n\n%s" % str(self) + msg = str(e) + "\n\n" + self.get_help() raise Exception(msg) # instantiate and completing the optResult that will be returned diff --git a/src/salomeTools.py b/src/salomeTools.py index a009fdb..12e5101 100755 --- a/src/salomeTools.py +++ b/src/salomeTools.py @@ -18,7 +18,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA '''This file is the main entry file to salomeTools -NO __main__ entry allowed, use sat +NO __main__ entry allowed, use 'sat' (in parent directory) ''' ######################################################################## @@ -39,9 +39,9 @@ import traceback import subprocess as SP import pprint as PP +import src # for __version__ import src.debug as DBG # Easy print stderr (for DEBUG only) import src.returnCode as RCO # Easy (ok/ko, why) return methods code -from src.exceptionSat import ExceptionSat # get path to src rootdir = os.path.realpath( os.path.join(os.path.dirname(__file__), "..") ) @@ -50,7 +50,7 @@ srcdir = os.path.join(rootdir, "src") cmdsdir = os.path.join(rootdir, "commands") # load resources for internationalization -gettext.install('salomeTools', os.path.join(srcdir, 'i18n')) +gettext.install("salomeTools", os.path.join(srcdir, "i18n")) # The possible hooks : # pre is for hooks to be executed before commands @@ -64,69 +64,91 @@ _LANG = os.environ["LANG"] # original locale # utility methods ######################################################################## def find_command_list(dirPath): - '''Parse files in dirPath that end with .py : it gives commands list + """ + Parse files in dirPath that end with '.py' : it gives commands list :param dirPath str: The directory path where to search the commands :return: cmd_list : the list containing the commands name :rtype: list - ''' + """ cmd_list = [] for item in os.listdir(dirPath): if item in ["__init__.py"]: #avoid theses files continue - if item.endswith('.py'): - cmd_list.append(item[:-len('.py')]) - return cmd_list + if item.endswith(".py"): + cmd_list.append(item[:-len(".py")]) + return sorted(cmd_list) # The list of valid salomeTools commands from cmdsdir #_COMMANDS_NAMES = ['config', 'compile', 'prepare',...] _COMMANDS_NAMES = find_command_list(cmdsdir) def getCommandsList(): - """Gives commands list (as basename of files cmdsdir/*.py) - """ + """Gives commands list (as basename of files cmdsdir/*.py)""" return _COMMANDS_NAMES def launchSat(command): - """launch sat as subprocess popen + """ + launch sat as subprocess popen command as string ('sat --help' for example) used for unittest, or else... returns tuple (stdout, stderr) """ if "sat" not in command.split()[0]: raise Exception(_("Not a valid command for launchSat: '%s'") % command) - env = dict(os.environ) - env["PATH"] = rootdir + ":" + env["PATH"] + env = dict(os.environ) #copy + # theorically useless + # env["PATH"] = rootdir + ":" + env["PATH"] res =SP.Popen(command, shell=True, env=env, stdout=SP.PIPE, stderr=SP.PIPE).communicate() return res def setNotLocale(): """force english at any moment""" os.environ["LANG"] = '' - gettext.install('salomeTools', os.path.join(srcdir, 'i18n')) + gettext.install("salomeTools", os.path.join(srcdir, "i18n")) DBG.write("setNotLocale", os.environ["LANG"]) def setLocale(): """reset initial locale at any moment - 'fr' or else 'TODO' from initial environment var '$LANG' + 'fr' or else (TODO) from initial environment var '$LANG' + 'i18n' as 'internationalization' """ os.environ["LANG"] = _LANG - gettext.install('salomeTools', os.path.join(srcdir, 'i18n')) + gettext.install("salomeTools", os.path.join(srcdir, "i18n")) DBG.write("setLocale", os.environ["LANG"]) +def getVersion(): + """get version number as string""" + return src.__version__ + +def assumeAsList(strOrList): + """return a list as sys.argv if string""" + if type(strOrList) is list: + return list(strOrList) # copy + else: + res = strOrList.split(" ") + return [r for r in res if r != ""] # supposed string to split for convenience ######################################################################## # _BaseCmd class ######################################################################## class _BaseCommand(object): - """_BaseCmd is the base class for all inherited commands of salomeTools + """ + _BaseCmd is the base class for all inherited commands of salomeTools instancied as classes 'Command' in files '.../commands/*.py' """ - def __init__(self, name): - self.name = name - self.runner = None # runner (as caller) usually as Sat instance - # self.config = None # config pyconf usually loaded with _getConfig method - self.logger = None # logger (from caller) usually as Sat instance logger + + # supposed never seen, set "config", "prepare"... in inherited commands + name = "salomeTools" + + def __init__(self, runner): + # runner (as caller) usually as Sat instance + self._runner = runner + # config pyconf usually loaded in runner, but sometimes local value + self._config = None + # logger (from caller) usually as Sat instance logger, but sometimes local value + self._logger = runner.logger + self._options = None def getClassName(self): """ @@ -135,230 +157,117 @@ class _BaseCommand(object): '.../commands/config.py' or '.../commands/prepare.py' """ return "%s.%s" % (self.name, self.__class__.__name__) - + def __repr__(self): tmp = PP.pformat(self.__dict__) res = "%s(\n %s)\n" % (self.getClassName(), tmp[1:-1]) return res def run(self, args): - return RCO.ReturnCode("KO", "_BaseCommand.run() have to be inherited") - - def setRunner(self, runner): - """set who owns me, and use me whith method run()""" - self.runner = runner - + """ + virtual example method for Command instance + have to return RCO.ReturnCode() + """ + return RCO.ReturnCode("KO", "_BaseCommand.run() have not to be instancied and called") + def setLogger(self, logger): """set logger for run command""" - self.logger = logger - + if self._logger is not None: + # supposed logger.debug for future + self._logger.warning("change logger for %s, are you sure" % self.getClassName()) + self._logger = logger + def getLogger(self): - if self.logger is None: # could use runner Sat instance logger - return self.runner.getLogger() - else: # could use local logger - return self.logger - + if self._logger is None: + raise Exception("%s instance needs self._logger not None, fix it." % self.getClassName()) + else: + return self._logger + + def getOptions(self): + if self._options is None: + raise Exception("%s instance needs self._option not None, fix it." % self.getClassName()) + else: + return self._options + def getRunner(self): - if self.runner is None: - raise Exception("have to set runner attribute, fix it.") + if self._runner is None: + raise Exception("%s instance needs self.runner not None, fix it." % self.getClassName()) else: - return self.runner + return self._runner + + def getConfig(self): + """ + supposedly (for now) no multiply local config(s) + only one config in runner.config + may be some for future... + """ + if self._runner.config is None: + self._logger.error("%s instance have runner.config None, fix it." % self.getClassName()) + return self._runner.config + + def assumeAsList(self, strOrList): + return assumeAsList(strOrList) def getParser(self): raise Exception("_BaseCmd class have not to be instancied, only for inheritage") - def parse_args(self, args): - """smart parse command arguments skipping first argument name appli to load if present""" - parser = self.getParser() - if type(args) is list: - argList = args - else: - argList = args.split(' ') - DBG.write("%s args" % self.name, args, True) - # or if args[0][0] == "-": #as argument name appli without "--" - if self.runner.nameAppliLoaded is None: - (options, argsc) = parser.parse_args(args) - else: - (options, argsc) = parser.parse_args(args[1:]) # skip name appli - DBG.write("%s options" % self.name, options) - DBG.write("%s remainders args" % self.name, argsc) - if argsc != []: - self.getLogger().error("\n\ncommand '%s' remainders args %s\n\n" % (self.name, argsc)) - return (options, argsc) + def parseArguments(self, cmd_arguments): + """ + smart parse command arguments skip + first argument name appli to load, if present + """ + argList = self.assumeAsList(cmd_arguments) + DBG.write("%s.Command arguments" % self.name, argList) + commandOptions, remaindersArgs = self.getParser().parse_args(argList) + DBG.write("%s.Command options" % self.name, commandOptions) + DBG.write("%s.Command remainders arguments" % self.name, remaindersArgs) + if remaindersArgs != []: + self.getLogger().error("%s.Command have unknown remainders arguments:\n%s\n" % \ + (self.name, remaindersArgs)) + return commandOptions, remaindersArgs def description(self): - '''method that is called when salomeTools is called with --help option. - - :return: The text to display for the config command description. - :rtype: str - ''' - raise Exception("_BaseCmd class have not to be instancied, only for inheritage") + """ + method called when salomeTools have '--help' argument. + returns The text to display for the command description + which is current Command class docstring 'self.__doc__', + with traduction + """ + return _(self.__doc__) - def run(self, args, runner, logger): - '''method that is called when salomeTools is called with self.name parameter. - ''' - raise Exception("_BaseCmd class have not to be instancied, only for inheritage") - - def getConfig(self): - if self.runner.config is None: - self.runner.config = self._getConfig() - #DBG.write("_baseCommand runner", self.runner) - DBG.write("_baseCommand runner.config", self.runner.config) - return self.runner.config - - def _getConfig(self): - '''The function that will load the configuration (all pyconf) - and returns the config from files .pyconf - ''' - if self.runner.config is not None: - raise Exception("config existing yet in 's' instance" % self.runner.getClassName()) - - # Get the arguments in a list and remove the empty elements - # DBG.write("%s.runner.arguments" % self.name, self.runner.arguments) - - self.parser = self.getParser() - try: - options, args = self.parser.parse_args(self.runner.arguments[1:]) - DBG.write("%s args" % self.name, args) - DBG.write("%s options" % self.name, options) - except Exception as exc: - write_exception(exc) - sys.exit(RCO.KOSYS) - - self.arguments = args # args are postfixes options: args[0] is the 'commands' command - self.options = options # the options passed to salomeTools - - if type(args) == type(''): - # split by spaces without considering spaces in quotes - argv_0 = re.findall(r'(?:"[^"]*"|[^\s"])+', args) - else: - argv_0 = args - - if argv_0 != ['']: - while "" in argv_0: argv_0.remove("") - - # Format the argv list in order to prevent strings - # that contain a blank to be separated - argv = [] - elem_old = "" - for elem in argv_0: - if argv == [] or elem_old.startswith("-") or elem.startswith("-"): - argv.append(elem) - else: - argv[-1] += " " + elem - elem_old = elem - - # if it is provided by the command line, get the application - appliToLoad = None - if argv not in [[''], []] and argv[0][0] != "-": - appliToLoad = argv[0].rstrip('*') - argv = argv[1:] - - # Check if the global options of salomeTools have to be changed - if options: - options_save = self.options - self.options = options - - # read the configuration from all the pyconf files - cfgManager = getConfigManager() # commands.config.ConfigManager() - DBG.write("appli to load", appliToLoad, True) - config = cfgManager.get_config(datadir=self.runner.datadir, - application=appliToLoad, - options=self.runner.options, - command=self.name) # command=__nameCmd__) - self.runner.nameAppliLoaded = appliToLoad - # DBG.write("appli loaded", config, True) - - # Set the verbose mode if called - DBG.tofix("verbose/batch/logger_add_link -1/False/None", True) - verbose = -1 - batch = False - logger_add_link = None - if verbose > -1: - verbose_save = self.options.output_verbose_level - self.options.__setattr__("output_verbose_level", verbose) - - # Set batch mode if called - if batch: - batch_save = self.options.batch - self.options.__setattr__("batch", True) - - # set output level - if self.runner.options.output_verbose_level is not None: - config.USER.output_verbose_level = self.runner.options.output_verbose_level - if config.USER.output_verbose_level < 1: - config.USER.output_verbose_level = 0 - silent = (config.USER.output_verbose_level == 0) - - # create log file - micro_command = False - if logger_add_link: - micro_command = True - logger_command = src.logger.Logger(config, - silent_sysstd=silent, - all_in_terminal=self.runner.options.all_in_terminal, - micro_command=micro_command) - - # Check that the path given by the logs_paths_in_file option - # is a file path that can be written - if self.runner.options.logs_paths_in_file and not micro_command: - try: - self.options.logs_paths_in_file = os.path.abspath( - self.options.logs_paths_in_file) - dir_file = os.path.dirname(self.options.logs_paths_in_file) - if not os.path.exists(dir_file): - os.makedirs(dir_file) - if os.path.exists(self.options.logs_paths_in_file): - os.remove(self.options.logs_paths_in_file) - file_test = open(self.options.logs_paths_in_file, "w") - file_test.close() - except Exception as e: - msg = _("WARNING: the logs_paths_in_file option will " - "not be taken into account.\nHere is the error:") - logger_command.write("%s\n%s\n\n" % ( - src.printcolors.printcWarning(msg), - str(e))) - self.options.logs_paths_in_file = None - - return config + def run(self, cmd_arguments): + """ + method called when salomeTools processes command(s) parameters""" + raise Exception("_BaseCmd class have not to be instancied, useful only for inheritage") - def get_products_list(self, options, cfg, logger): - '''method that gives the product list with their informations from - configuration regarding the passed options. - - :param options Options: The Options instance that stores the commands - arguments - :param config Config: The global configuration - :param logger Logger: The logger instance to use for the display and logging - :return: The list of (product name, product_informations). - :rtype: List - ''' - # Get the products to be prepared, regarding the options - if options.products is None: - # No options, get all products sources - products = cfg.APPLICATION.products - else: - # if option --products, check that all products of the command line - # are present in the application. - products = options.products - for p in products: - if p not in cfg.APPLICATION.products: - raise src.SatException(_("Product %(product)s " - "not defined in application %(application)s") % - { 'product': p, 'application': cfg.VARS.application} ) - - # Construct the list of tuple containing - # the products name and their definition - products_infos = src.product.get_products_infos(products, cfg) + def print_help(self): + """ + Prints help for a command. Function called with + 'sat --help ' or + 'sat --help --help' or + 'sat ' without any trailing arguments + """ + msg = self.get_help() + self._logger.info(msg) + + def get_help(self): + """get string help for inherited Command classes""" + version = getVersion() + "\n\n" # salomeTools version + msg = "
Version: " + version + # description of the command that is done in the command.py file + msg += "
Description:\n" + msg += self.description() + "\n\n" - return products_infos - + # description of the command options + msg += self.getParser().get_help() + "\n" + return msg ######################################################################## # Sat class ######################################################################## class Sat(object): """The main class that stores all the commands of salomeTools + (usually known as 'runner' argument in Command classes) """ def __init__(self, logger): """Initialization @@ -373,8 +282,9 @@ class Sat(object): self.CONFIG_FILENAME = "sat-config.pyconf" + self.configManager = None # the config Manager that will be used to set self.config self.config = None # the config that will be read using pyconf module - self.logger = None # the logger that will be use + self.logger = logger # the logger that will be use self.arguments = None # args are postfixes options: args[0] is the 'commands' command self.options = None # the options passed to salomeTools @@ -404,19 +314,16 @@ class Sat(object): if self.logger is None: # could use owner Sat instance logger import src.loggingSat as LOG self.logger=LOG.getDefaultLogger() - self.logger.error("Sat logger not set, fixed as default") + self.logger.critical("Sat logger not set, unexpected situation, fixed as default") return self.logger else: # could use local logger return self.logger + def getConfig(self): + return self.config + def assumeAsList(self, strOrList): - """return a list as sys.argv if string - """ - if type(strOrList) is list: - return list(strOrList) # copy - else: - return strOrList.split(" ") # supposed string to split for convenience - + return assumeAsList(strOrList) def _getParser(self): """ @@ -442,47 +349,53 @@ class Sat(object): return parser - def parseGenericArguments(self, arguments): + def parseArguments(self, arguments): args = self.assumeAsList(arguments) genericOptions, remaindersArgs = self.parser.parse_args(args) - DBG.write("Sat generic arguments", genericArgs) + DBG.write("Sat generic options", genericOptions) DBG.write("Sat remainders arguments", remaindersArgs) return genericOptions, remaindersArgs - def _getCommand(self, name): + def _getModule(self, name): """ - create and add Command 'name' as instance of class in dict self.commands - create only one instance + load and add module Command 'name' in dict self.commands + have to be called only one time maximum for each module Command """ if name not in _COMMANDS_NAMES: - raise AttributeError(_("command not valid: '%s'") % name) + raise Exception(_("module command not valid: '%s'") % name) if name in self.commands.keys(): - raise AttributeError(_("command existing yet: '%s', use getCommand") % name) + raise Exception(_("module command existing yet: '%s', use getModule") % name) file_, pathname, description = imp.find_module(name, [cmdsdir]) + # could raise Exception in load (catched in sat, logger.critical) module = imp.load_module(name, file_, pathname, description) - try: - cmdInstance = module.Command(name) - except: - DBG.tofix("no Command() class in %s" % pathname, dir(module), True) - raise Exception("no Command() class in %s" % pathname) - - cmdInstance.setRunner(self) # self is runner, owns cmdInstance - DBG.write("Sat load new command", cmdInstance) - return cmdInstance - - def getCommand(self, name): + self.commands[name] = module # store module loaded (only one time) + self.logger.debug("Sat load module command %s" % name) + return module + + def getModule(self, name): + """ + returns only-one-time loaded module Command 'name' + assume load if not done yet """ - returns inherited instance of _BaseCmd for command 'name' - if not existing as self.commands[name], create it. + if name in self.commands.keys(): + return self.commands[name] + else: + return self._getModule(name) - example: - returns Command() from command.config + def getCommandInstance(self, name): """ - if name not in self.commands.keys(): - self.commands[name] = self._getCommand(name) - return self.commands[name] - + returns inherited instance of Command(_BaseCmd) for command 'name' + if module not loaded yet, load it. + """ + module = self.getModule(name) + try: + commandInstance = module.Command(self) # set runner as 'parent' (and logger...) + except Exception as e: + raise Exception("Problem instance %s.Command(): %s" % (name, e)) + self.logger.debug("Sat new instance %s.Command()" % name) + return commandInstance + def execute_cli(self, cli_arguments): """select first argument as a command in directory 'commands', and launch on arguments @@ -492,146 +405,57 @@ class Sat(object): # print general help and returns if len(args) == 0: - print_help() - return RCO.ReturnCode("OK", "No arguments as --help") - + self.print_help() + return RCO.ReturnCode("OK", "No arguments, as 'sat --help'") + + self.options, remaindersArgs = self.parseArguments(args) + # if the help option has been called, print command help and returns if self.options.help: - self.print_help(self.arguments) + self.print_help() return RCO.ReturnCode("OK", "Option --help") # the command called - cmdName = args[0] + cmdName = remaindersArgs[0] # create/get dynamically the command instance to call its 'run' method - cmdInstance = self.getCommand(cmdName) + cmdInstance = self.getCommandInstance(cmdName) # Run the command using the arguments - returnCode = cmdInstance.run(args[1:]) + returnCode = cmdInstance.run(remaindersArgs[1:]) return returnCode - - def print_help(self, opt): - '''Prints help for a command. Function called when "sat -h " - - :param argv str: the options passed (to get the command name) - ''' - # if no command as argument (sat -h) - if len(opt)==0: - print_help() - return - - # get command name - cmdName = opt[0] - # read the configuration from all the pyconf files - cfgManager = getConfigManager() - self.cfg = cfgManager.get_config(datadir=self.datadir) - - cmdInstance = self.getCommand(cmdName) - - msg = self.get_command_help(cmdInstance) - - if isStdoutPipe(): - # clean color if the terminal is redirected by user - # ex: sat compile appli > log.txt - msg = src.printcolors.cleancolor(msg) - print(msg) - return - - def get_command_help(self, module): - """get help for a command - as 'sat --help config' for example - """ - # get salomeTools version - msg = get_version() + "\n\n" - - # print the description of the command that is done in the command file - try: - msg += src.printcolors.printcHeader( _("Description:") ) + "\n" - msg += module.description() + "\n\n" - except: - DBG.tofix("no description() for", module.name, True) - - # print the description of the command options - try: - msg += module.getParser().get_help() + "\n" - except: - DBG.tofix("no getParser() for", module.name, True) - return msg - -################################################################### -def getConfigManager(): - import commands.config - return commands.config.ConfigManager() - -def get_text_from_options(options): - text_options = "" - for attr in dir(options): - if attr.startswith("__"): - continue - if options.__getattr__(attr) != None: - option_contain = options.__getattr__(attr) - if type(option_contain)==type([]): - option_contain = ",".join(option_contain) - if type(option_contain)==type(True): - option_contain = "" - text_options+= "--%s %s " % (attr, option_contain) - return text_options - - -def isStdoutPipe(): - """check if the terminal is redirected by user (elsewhere a tty) - example: - >> sat compile appli > log.txt - """ - return not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()) - -def get_version(): - """get version colored string - """ - cfgManager = getConfigManager() - cfg = cfgManager.get_config() - # print the key corresponding to salomeTools version - msg = src.printcolors.printcHeader( _("Version: ") ) + \ - cfg.INTERNAL.sat_version - return msg - -def get_help(): - """get general help colored string - """ - # read the config - msg = get_version() + "\n\n" - msg += src.printcolors.printcHeader(_("Usage: ")) + \ - "sat [generic_options] [product] [command_options]\n\n" - msg += Sat()._getParser().get_help() + "\n" - msg += src.printcolors.printcHeader(_("Available commands are:")) + "\n\n" - for command in _COMMANDS_NAMES: - msg += " - %s\n" % (command) - msg += "\n" - # Explain how to get the help for a specific command - msg += src.printcolors.printcHeader( - _("Getting the help for a specific command: ")) + \ - "sat --help \n" - return msg - -def print_help(): - """prints salomeTools general help - """ - msg = get_help() - if isStdoutPipe(): - # clean color if the terminal is redirected by user - # ex: sat compile appli > log.txt - msg = src.printcolors.cleancolor(msg) - print(msg) - return - -def write_exception(exc): - '''write in stderr exception in case of error in a command + def get_help(self): + """get general help colored string""" + msg = self.getColoredVersion() + "\n\n" + msg += "
Usage: sat [generic_options] [product] [command_options]\n\n" + msg += self._getParser().get_help() + "\n" + msg += "
" + _("Available commands are:") + "\n\n" + for command in _COMMANDS_NAMES: + msg += " - %s\n" % (command) + msg += "\n" + # how to get the help for a specific command + msg += "
" + _("Getting the help for a specific command: ") + \ + "sat --help\n" + return msg - :param exc exception: the exception to print - ''' - sys.stderr.write("\n***** ") - sys.stderr.write(src.printcolors.printcError("salomeTools ERROR:")) - sys.stderr.write("\n" + str(exc) + "\n") - + def print_help(self): + """prints salomeTools general help""" + self.logger.info(self.get_help()) + + def getConfigManager(self): + import src.configManager as CFGMGR + return CFGMGR.ConfigManager(self.logger) + + def getColoredVersion(self): + """get colored salomeTools version message""" + version = getVersion() + if self.config is not None: + # verify coherency with config.INTERNAL.sat_version + if config.INTERNAL.sat_version != version: + self.logger.warning("verify version with INTERNAL.sat_version") + msg = "
Version: " + version + return msg + + diff --git a/src/test/satHelpTest.py b/src/test/satHelpTest.py deleted file mode 100755 index 23d8ba2..0000000 --- a/src/test/satHelpTest.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python -#-*- coding:utf-8 -*- - -# Copyright (C) 2010-2018 CEA/DEN -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -import os -import sys -import unittest - -import src.salomeTools as SAT -import src -import src.debug as DBG # Easy print stderr (for DEBUG only) - -class TestCase(unittest.TestCase): - "Test the sat --help commands""" - - def test_000(self): - # one shot setUp() for this TestCase - # DBG.push_debug(True) - SAT.setNotLocale() # test english - return - - def test_010(self): - cmd = "sat --help" - stdout, stderr = SAT.launchSat(cmd) - print stdout, stderr - self.assertEqual(stderr, "") - self.assertTrue(" - config" in stdout) - - def xtest_011(self): - cmd = "--help" - s = SAT.Sat(cmd) - exitCode = s.execute_command() - self.assertEqual(src.okToStr(exitCode), "OK") - - def xtest_030(self): - cmd = "sat --help config" - stdout, stderr = SAT.launchSat(cmd) - self.assertEqual(stderr, "") - self.assertTrue("--value" in stdout) - - def xtest_031(self): - cmd = "--help config" - s = SAT.Sat(cmd) - exitCode = s.execute_command() - self.assertEqual(src.okToStr(exitCode), "OK") - - def xtest_032(self): - cmd = "config -l" - s = SAT.Sat(cmd) - exitCode = s.execute_command() - self.assertEqual(src.okToStr(exitCode), "OK") - - def xtest_040(self): - cmds = SAT.getCommandsList() - for c in cmds: - cmd = "sat --help %s" % c - stdout, stderr = SAT.launchSat(cmd) - self.assertEqual(stderr, "") - # DBG.write("stdout '%s'" % cmd, stdout) - self.assertTrue("vailable options" in stdout) - - def test_999(self): - # one shot tearDown() for this TestCase - SAT.setLocale() # end test english - # DBG.pop_debug() - return - -if __name__ == '__main__': - unittest.main(exit=False) - pass diff --git a/src/test/APPLI_TEST/APPLI_TEST.pyconf b/test/APPLI_TEST/APPLI_TEST.pyconf similarity index 100% rename from src/test/APPLI_TEST/APPLI_TEST.pyconf rename to test/APPLI_TEST/APPLI_TEST.pyconf diff --git a/src/test/README_config_0_3_9.txt b/test/README_config_0_3_9.txt similarity index 100% rename from src/test/README_config_0_3_9.txt rename to test/README_config_0_3_9.txt diff --git a/test/__init__.py b/test/__init__.py old mode 100644 new mode 100755 diff --git a/src/test/config_0_3_9/LICENSE b/test/config_0_3_9/LICENSE similarity index 100% rename from src/test/config_0_3_9/LICENSE rename to test/config_0_3_9/LICENSE diff --git a/src/test/config_0_3_9/PKG-INFO b/test/config_0_3_9/PKG-INFO similarity index 100% rename from src/test/config_0_3_9/PKG-INFO rename to test/config_0_3_9/PKG-INFO diff --git a/src/test/config_0_3_9/README.txt b/test/config_0_3_9/README.txt similarity index 100% rename from src/test/config_0_3_9/README.txt rename to test/config_0_3_9/README.txt diff --git a/src/test/__init__.py b/test/config_0_3_9/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from src/test/__init__.py rename to test/config_0_3_9/__init__.py diff --git a/src/test/config_0_3_9/config.py b/test/config_0_3_9/config.py similarity index 100% rename from src/test/config_0_3_9/config.py rename to test/config_0_3_9/config.py diff --git a/src/test/config_0_3_9/logconfig.cfg b/test/config_0_3_9/logconfig.cfg similarity index 100% rename from src/test/config_0_3_9/logconfig.cfg rename to test/config_0_3_9/logconfig.cfg diff --git a/src/test/config_0_3_9/logconfig.py b/test/config_0_3_9/logconfig.py similarity index 100% rename from src/test/config_0_3_9/logconfig.py rename to test/config_0_3_9/logconfig.py diff --git a/src/test/config_0_3_9/setup.py b/test/config_0_3_9/setup.py similarity index 100% rename from src/test/config_0_3_9/setup.py rename to test/config_0_3_9/setup.py diff --git a/src/test/config_0_3_9/styles.json b/test/config_0_3_9/styles.json similarity index 100% rename from src/test/config_0_3_9/styles.json rename to test/config_0_3_9/styles.json diff --git a/src/test/config_0_3_9/test_config.py b/test/config_0_3_9/test_config.py similarity index 100% rename from src/test/config_0_3_9/test_config.py rename to test/config_0_3_9/test_config.py diff --git a/src/test/debugTest.py b/test/test_020_debug.py similarity index 97% rename from src/test/debugTest.py rename to test/test_020_debug.py index bc84f53..27e46ef 100755 --- a/src/test/debugTest.py +++ b/test/test_020_debug.py @@ -21,10 +21,9 @@ import os import sys import unittest -import src import src.debug as DBG # Easy print stderr (for DEBUG only) import src.pyconf as PYF # 0.3.7 -import src.test.config_0_3_9.config as PYF9 # TODO 0.3.9 +import config_0_3_9.config as PYF9 # TODO 0.3.9 _EXAMPLES = { 1 : """\ diff --git a/src/test/pyconfTest.py b/test/test_025_pyconf.py similarity index 98% rename from src/test/pyconfTest.py rename to test/test_025_pyconf.py index 2adaeb8..8d1107f 100755 --- a/src/test/pyconfTest.py +++ b/test/test_025_pyconf.py @@ -21,10 +21,9 @@ import os import sys import unittest -import src import src.debug as DBG # Easy print stderr (for DEBUG only) import src.pyconf as PYF # 0.3.7 -import src.test.config_0_3_9.config as PYF9 # TODO 0.3.9 +import config_0_3_9.config as PYF9 # TODO 0.3.9 _EXAMPLES = { 1 : """\ diff --git a/src/test/test_pyconf.py b/test/test_030_pyconf_0_3_9.py similarity index 97% rename from src/test/test_pyconf.py rename to test/test_030_pyconf_0_3_9.py index 7f884de..209d622 100755 --- a/src/test/test_pyconf.py +++ b/test/test_030_pyconf_0_3_9.py @@ -21,7 +21,8 @@ Test harness for the configuration module 'config' for Python. from test_config 0.3.9 modified to test 0.3.7.1 -this test obviously have FAILED (errors=6), TODO, fix upgrading 0.3.9, or not. +this test obviously have FAILED (errors=6), +TODO, fix upgrading 0.3.9, or not. """ import unittest @@ -444,7 +445,11 @@ if __name__ == "__main__": unittest.main(exit=False) import sys sys.stderr.write(""" -WARNING: this test obviously have FAILED (errors=6), -TODO: fix upgrading 0.3.9, (or not).\n\n""") + +########################################################### +WARNING: this test obviously have 'FAILED (errors=6)', +TODO: fix upgrading 0.3.9, (or not). +########################################################### +""") pass diff --git a/test/test_100_satHelp.py b/test/test_100_satHelp.py new file mode 100755 index 0000000..0ffef0e --- /dev/null +++ b/test/test_100_satHelp.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +#-*- coding:utf-8 -*- + +# Copyright (C) 2010-2018 CEA/DEN +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import os +import sys +import unittest + +import src.salomeTools as SAT +import src.debug as DBG # Easy print stderr (for DEBUG only) +import src.loggingSat as LOG + +class TestCase(unittest.TestCase): + "Test the sat --help commands""" + + logger = LOG.getUnittestLogger() + debug = False + + def tearDown(self): + # print "tearDown", __file__ + # assure self.logger clear for next test + logs = self.logger.getLogsAndClear() + # using assertNotIn() is too much verbose + self.assertFalse("ERROR" in logs) + self.assertFalse("CRITICAL" in logs) + + def test_000(self): + # one shot setUp() for this TestCase + if self.debug: DBG.push_debug(True) + SAT.setNotLocale() # test english + + def test_999(self): + # one shot tearDown() for this TestCase + SAT.setLocale() # end test english + if self.debug: DBG.pop_debug() + + def test_010(self): + cmd = "sat --help" + stdout, stderr = SAT.launchSat(cmd) + DBG.write("test_010 stdout", stdout) + DBG.write("test_010 stderr", stderr) + self.assertEqual(stderr, "") + self.assertTrue(" - config" in stdout) + self.assertTrue(" - prepare" in stdout) + self.assertTrue(" - compile" in stdout) + + def test_011(self): + cmd = "--help" + s = SAT.Sat(self.logger) + returnCode = s.execute_cli(cmd) + self.assertTrue(returnCode.isOk()) + logs = self.logger.getLogs() + DBG.write("test_011 logger", logs) + self.assertTrue(" - config" in logs) + self.assertTrue(" - prepare" in logs) + self.assertTrue(" - compile" in logs) + + def test_030(self): + cmd = "sat config --help" + stdout, stderr = SAT.launchSat(cmd) + DBG.write("test_030 stdout", stdout) + self.assertEqual(stderr, "") + self.assertTrue("--value" in stdout) + + def test_031(self): + cmd = "config --help" + s = SAT.Sat(self.logger) + returnCode = s.execute_cli(cmd) + self.assertTrue(returnCode.isOk()) + logs = self.logger.getLogs() + DBG.write("test_031 logger", logs) + self.assertTrue("--value" in logs) + + def xxtest_040(self): + cmd = "config --list" + s = SAT.Sat(self.logger) + returnCode = s.execute_cli(cmd) + self.assertTrue(returnCode.isOk()) + logs = self.logger.getLogs() + self.assertTrue("--value" in logs) + + def test_050(self): + cmds = SAT.getCommandsList() + DBG.write("test_050 getCommandsList", cmds) + for c in cmds: + cmd = "sat %s --help" % c + stdout, stderr = SAT.launchSat(cmd) + self.assertEqual(stderr, "") + self.assertTrue(c in stdout) + self.assertTrue("Available options" in stdout) + + def test_051(self): + cmds = SAT.getCommandsList() + for c in cmds: + cmd = "%s --help" % c + s = SAT.Sat(self.logger) + returnCode = s.execute_cli(cmd) + self.assertTrue(returnCode.isOk()) + logs = self.logger.getLogsAndClear() + DBG.write(cmd, logs, True) + self.assertTrue("Available options" in logs) + +if __name__ == '__main__': + unittest.main(exit=False) + pass diff --git a/src/test/APPLI_TEST_Test.py b/test/test_500_APPLI_TEST.py similarity index 70% rename from src/test/APPLI_TEST_Test.py rename to test/test_500_APPLI_TEST.py index e29e0a7..dcb58c7 100755 --- a/src/test/APPLI_TEST_Test.py +++ b/test/test_500_APPLI_TEST.py @@ -22,35 +22,42 @@ import sys import unittest import src.salomeTools as SAT -import src import src.debug as DBG # Easy print stderr (for DEBUG only) +import src.loggingSat as LOG class TestCase(unittest.TestCase): "Test the sat commands on APPLI_TEST configuration pyconf etc. files""" + logger = LOG.getUnittestLogger() + debug = False + + def tearDown(self): + # print "tearDown", __file__ + # assure self.logger clear for next test + logs = self.logger.getLogsAndClear() + # using assertNotIn() is too much verbose + self.assertFalse("ERROR" in logs) + self.assertFalse("CRITICAL" in logs) + def test_000(self): # one shot setUp() for this TestCase - # DBG.push_debug(True) + if self.debug: DBG.push_debug(True) SAT.setNotLocale() # test english return - def test_010(self): - cmd = "-v 5 config -l" - s = SAT.Sat(cmd) - # DBG.push_debug(True) - DBG.write("s.cfg", s.cfg) #none - DBG.write("s.__dict__", s.__dict__) # have - exitCode = s.execute_command() - # DBG.write("s.cfg", s.cfg) - self.assertEqual(src.okToStr(exitCode), "OK") - DBG.pop_debug() - def test_999(self): # one shot tearDown() for this TestCase SAT.setLocale() # end test english - # DBG.pop_debug() - return - + if self.debug: DBG.pop_debug() + + def test_010(self): + cmd = "-v 5 config -l" + s = SAT.Sat(self.logger) + DBG.write("s.getConfig()", s.getConfig()) #none + DBG.write("s.__dict__", s.__dict__) # have + returnCode = s.execute_cli(cmd) + self.assertTrue(returnCode.isOk()) + if __name__ == '__main__': unittest.main(exit=False) pass diff --git a/src/test/config_0_3_9/__init__.py b/test/test_sat5_0/__init__.py similarity index 100% rename from src/test/config_0_3_9/__init__.py rename to test/test_sat5_0/__init__.py diff --git a/test/compilation/test_compilation.py b/test/test_sat5_0/compilation/test_compilation.py similarity index 100% rename from test/compilation/test_compilation.py rename to test/test_sat5_0/compilation/test_compilation.py diff --git a/test/compilation/test_configure.py b/test/test_sat5_0/compilation/test_configure.py similarity index 100% rename from test/compilation/test_configure.py rename to test/test_sat5_0/compilation/test_configure.py diff --git a/test/compilation/test_make.py b/test/test_sat5_0/compilation/test_make.py similarity index 100% rename from test/compilation/test_make.py rename to test/test_sat5_0/compilation/test_make.py diff --git a/test/compilation/test_makeinstall.py b/test/test_sat5_0/compilation/test_makeinstall.py similarity index 100% rename from test/compilation/test_makeinstall.py rename to test/test_sat5_0/compilation/test_makeinstall.py diff --git a/test/config/test_create_user_pyconf.py b/test/test_sat5_0/config/test_create_user_pyconf.py similarity index 100% rename from test/config/test_create_user_pyconf.py rename to test/test_sat5_0/config/test_create_user_pyconf.py diff --git a/test/config/test_option_copy.py b/test/test_sat5_0/config/test_option_copy.py similarity index 100% rename from test/config/test_option_copy.py rename to test/test_sat5_0/config/test_option_copy.py diff --git a/test/config/test_option_edit.py b/test/test_sat5_0/config/test_option_edit.py similarity index 100% rename from test/config/test_option_edit.py rename to test/test_sat5_0/config/test_option_edit.py diff --git a/test/config/test_option_value.py b/test/test_sat5_0/config/test_option_value.py similarity index 100% rename from test/config/test_option_value.py rename to test/test_sat5_0/config/test_option_value.py diff --git a/test/config/test_option_value_2.py b/test/test_sat5_0/config/test_option_value_2.py similarity index 100% rename from test/config/test_option_value_2.py rename to test/test_sat5_0/config/test_option_value_2.py diff --git a/test/environ/test_environ.py b/test/test_sat5_0/environ/test_environ.py similarity index 100% rename from test/environ/test_environ.py rename to test/test_sat5_0/environ/test_environ.py diff --git a/test/job/test_job.py b/test/test_sat5_0/job/test_job.py similarity index 100% rename from test/job/test_job.py rename to test/test_sat5_0/job/test_job.py diff --git a/test/jobs/test_jobs.py b/test/test_sat5_0/jobs/test_jobs.py similarity index 100% rename from test/jobs/test_jobs.py rename to test/test_sat5_0/jobs/test_jobs.py diff --git a/test/log/test_launch_browser.py b/test/test_sat5_0/log/test_launch_browser.py similarity index 100% rename from test/log/test_launch_browser.py rename to test/test_sat5_0/log/test_launch_browser.py diff --git a/test/log/test_launch_browser2.py b/test/test_sat5_0/log/test_launch_browser2.py similarity index 100% rename from test/log/test_launch_browser2.py rename to test/test_sat5_0/log/test_launch_browser2.py diff --git a/test/prepare/test_clean.py b/test/test_sat5_0/prepare/test_clean.py similarity index 100% rename from test/prepare/test_clean.py rename to test/test_sat5_0/prepare/test_clean.py diff --git a/test/prepare/test_patch.py b/test/test_sat5_0/prepare/test_patch.py similarity index 100% rename from test/prepare/test_patch.py rename to test/test_sat5_0/prepare/test_patch.py diff --git a/test/prepare/test_prepare.py b/test/test_sat5_0/prepare/test_prepare.py similarity index 100% rename from test/prepare/test_prepare.py rename to test/test_sat5_0/prepare/test_prepare.py diff --git a/test/prepare/test_source.py b/test/test_sat5_0/prepare/test_source.py similarity index 100% rename from test/prepare/test_source.py rename to test/test_sat5_0/prepare/test_source.py diff --git a/test/run_all.sh b/test/test_sat5_0/run_all.sh similarity index 100% rename from test/run_all.sh rename to test/test_sat5_0/run_all.sh diff --git a/test/shell/test_shell.py b/test/test_sat5_0/shell/test_shell.py similarity index 100% rename from test/shell/test_shell.py rename to test/test_sat5_0/shell/test_shell.py diff --git a/test/test/test_command.py b/test/test_sat5_0/test/test_command.py similarity index 100% rename from test/test/test_command.py rename to test/test_sat5_0/test/test_command.py -- 2.39.2