From e8439611e5438bbaa1a87d483219202811019d54 Mon Sep 17 00:00:00 2001 From: Christian Van Wambeke Date: Sun, 25 Mar 2018 21:03:17 +0200 Subject: [PATCH] rm src/logger.py src/printcolors.py --- .spyderproject | Bin 92 -> 0 bytes commands/config.py | 2 +- sat | 32 +- src/__init__.py | 418 +-------------------- src/coloringSat.py | 182 ++++++++++ src/example/__init__.py | 0 src/example/essai_logging_1.py | 71 ++++ src/example/essai_logging_2.py | 68 ++++ src/exceptionSat.py | 25 ++ src/logger.py | 414 --------------------- src/loggingSat.py | 157 ++++++++ src/printcolors.py | 185 ---------- src/salomeTools.py | 106 +++--- src/salomeTools_old.py | 637 --------------------------------- src/utilsSat.py | 398 ++++++++++++++++++++ 15 files changed, 979 insertions(+), 1716 deletions(-) delete mode 100644 .spyderproject create mode 100755 src/coloringSat.py create mode 100644 src/example/__init__.py create mode 100755 src/example/essai_logging_1.py create mode 100755 src/example/essai_logging_2.py create mode 100644 src/exceptionSat.py delete mode 100644 src/logger.py create mode 100755 src/loggingSat.py delete mode 100644 src/printcolors.py delete mode 100755 src/salomeTools_old.py create mode 100644 src/utilsSat.py diff --git a/.spyderproject b/.spyderproject deleted file mode 100644 index cfa47ec2125ef3d2de96629121e4e877c8c211c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 92 zcmZo*sx4&H2o)$w%}FdtO^Gik%FjwoE-5Z#iY;Ug6$T4umZioQRF-7q=M^NDWE8T* shO*=(=B5_1MsPDQFcc@2n8p_uRHmdB6|#l0 by in file - ''' - shutil.move(filein, filein + "_old") - fileout= filein - filein = filein + "_old" - fin = open(filein, "r") - fout = open(fileout, "w") - for line in fin: - fout.write(line.replace(strin, strout)) - -def get_property_in_product_cfg(product_cfg, pprty): - if not "properties" in product_cfg: - return None - if not pprty in product_cfg.properties: - return None - return product_cfg.properties[pprty] +import src.pyconf \ No newline at end of file diff --git a/src/coloringSat.py b/src/coloringSat.py new file mode 100755 index 0000000..8fc13c6 --- /dev/null +++ b/src/coloringSat.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +simple tagging as '' for simple coloring log messages on terminal(s) +window or unix or ios using backend colorama + +using '' because EZ human readable +so '' are not supposed existing in log message +"{}".format() is not choosen because "{}" are present +in log messages of contents of python dict (as JSON) etc. + +example: +>> log("this is in color green, OK is in blue: OK?") +""" + +import os +import sys +import pprint as PP + +_verbose = True +_name = "coloringSat" + +""" +https://github.com/tartley/colorama +init(wrap=True): + +On Windows, colorama works by replacing sys.stdout and sys.stderr +with proxy objects, which override the .write() method to do their work. +If this wrapping causes you problems, +then this can be disabled by passing init(wrap=False). +The default behaviour is to wrap if autoreset or strip or convert are True. + +When wrapping is disabled, colored printing on non-Windows platforms +will continue to work as normal. +To do cross-platform colored output, +you can use Colorama's AnsiToWin32 proxy directly: + +example: + import sys + from colorama import init, AnsiToWin32, Fore + init(wrap=False) + stream = AnsiToWin32(sys.stderr).stream + # Python 2 + print >>stream, Fore.BLUE + 'blue text on stderr' + # Python 3 + print(Fore.BLUE + 'blue text on stderr', file=stream) +""" + +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' ? + +CLRM.init(wrap=False) # choose NO wrapping + + +""" +from colorama: +Available formatting constants are: +Fore: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. +Back: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. +Style: DIM, NORMAL, BRIGHT, RESET_ALL + +n.b. DIM is not assumed in win32 +""" +dir(ST) +# order matters for replace +_tags = ( + ("", FG.BLACK), + ("", FG.RED), + ("", FG.GREEN), + ("", FG.YELLOW), + ("", FG.BLUE), + ("", FG.MAGENTA), + ("", FG.CYAN), + ("", FG.WHITE), + ("", ST.BRIGHT), + ("", ST.NORMAL), + ("", ST.RESET_ALL), + ("", 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 = ( + ("", ""), + ("", ""), + ("", ""), + ("", ""), + ("", ""), + ("", ""), + ("", ""), + ("", ""), + ("", ""), + ("", ""), + ("", ""), + ("", "OK"), + ("", "KO"), +) + +def indent(msg, nb, car=" "): + """indent nb car (spaces) multi lines message except first one""" + s = msg.split("\n") + res = ("\n"+car*nb).join(s) + return res + +def log(msg): + """elementary log stdout for debug if _verbose""" + prefix = "%s.log: " % _name + nb = len(prefix) + if _verbose: + ini = prefix + indent(msg, nb) + res = toColor(ini) + if res != ini: + res = res + toColor("") + print(res) + +class ColoringStream(object): + """ + write my stream class + only write and flush are used for the streaming + https://docs.python.org/2/library/logging.handlers.html + https://stackoverflow.com/questions/31999627/storing-logger-messages-in-a-string + """ + def __init__(self): + self.logs = '' + + def write(self, astr): + # log("UnittestStream.write('%s')" % astr) + self.logs += astr + + def flush(self): + pass + + def __str__(self): + return self.logs + +def toColor(msg): + if not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()): + # clean the message color if the terminal is redirected by user + # ex: sat compile appli > log.txt + return replace(msg, _tagsNone) + else: + return replace(msg, _tags) + +def toColor_AnsiToWin32(msg): + """for test debug no wrapping""" + if not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()): + # clean the message color if the terminal is redirected by user + # ex: sat compile appli > log.txt + return replace(msg, _tagsNone) + else: + msgAnsi = replace(msg, _tags) + streamOut = ColoringStream() + atw = AnsiToWin32(streamOut, convert=True) + streamIn = atw.stream + print "should_wrap",atw.should_wrap(),atw.convert,atw.strip,atw.autoreset + streamIn.write(msgAnsi) + #AnsiToWin32(streamOut).write_and_convert(msgAnsi) + # print "streamOut",str(streamOut) + return str(streamOut) + +def replace(msg, tags): + s = msg + for r in tags: + s = s.replace(*r) + return s + +if __name__ == "__main__": + #log(FG.BLUE + 'blue text on stdout'+ ST.RESET_ALL) + log("import colorama at %s" % CLRM.__file__) + log("import colorama in %s: " % __file__) + log("import colorama in %s: " % __file__) + 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)) + + \ No newline at end of file diff --git a/src/example/__init__.py b/src/example/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/example/essai_logging_1.py b/src/example/essai_logging_1.py new file mode 100755 index 0000000..a8c8f88 --- /dev/null +++ b/src/example/essai_logging_1.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +http://sametmax.com/ecrire-des-logs-en-python/ +https://docs.python.org/3/library/time.html#time.strftime + +essai utilisation logger plusieurs handler format different + + /usr/lib/python2.7/logging/__init__.pyc + + init MyLogger, fmt='%(asctime)s :: %(levelname)-8s :: %(message)s', level='20' + + 2018-03-11 18:51:21 :: INFO :: test logger info + 2018-03-11 18:51:21 :: WARNING :: test logger warning + 2018-03-11 18:51:21 :: ERROR :: test logger error + 2018-03-11 18:51:21 :: CRITICAL :: test logger critical + + init MyLogger, fmt='None', level='10' + + 2018-03-11 18:51:21 :: DEBUG :: test logger debug + test logger debug + 2018-03-11 18:51:21 :: INFO :: test logger info + test logger info + 2018-03-11 18:51:21 :: WARNING :: test logger warning + test logger warning + 2018-03-11 18:51:21 :: ERROR :: test logger error + test logger error + 2018-03-11 18:51:21 :: CRITICAL :: test logger critical + test logger critical +""" + +import os +import sys +import logging +import pprint as PP + +print logging.__file__ + +def initMyLogger(fmt=None, level=None): + # http://sametmax.com/ecrire-des-logs-en-python/ + # https://docs.python.org/3/library/time.html#time.strftime + print "\ninit MyLogger, fmt='%s', level='%s'\n" % (fmt, level) + logger = getMyLogger() + handler = logging.StreamHandler() # Logging vers console + if fmt is not None: + formatter = logging.Formatter(fmt, "%Y-%m-%d %H:%M:%S") + handler.setFormatter(formatter) + logger.addHandler(handler) + if level is not None: + logger.setLevel(level) + else: + logger.setLevel(logger.INFO) # ou DEBUG + # logger.info('\n' + PP.pformat(dir(logger))) + +def getMyLogger(): + return logging.getLogger('MyLogger') + +def testLogger1(): + logger = getMyLogger() + logger.debug('test logger debug') + logger.info('test logger info') + logger.warning('test logger warning') + logger.error('test logger error') + logger.critical('test logger critical') + +if __name__ == "__main__": + initMyLogger('%(asctime)s :: %(levelname)-8s :: %(message)s', level=logging.INFO) + testLogger1() + initMyLogger(level=logging.DEBUG) + testLogger1() diff --git a/src/example/essai_logging_2.py b/src/example/essai_logging_2.py new file mode 100755 index 0000000..ba5da19 --- /dev/null +++ b/src/example/essai_logging_2.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +http://sametmax.com/ecrire-des-logs-en-python/ +https://docs.python.org/3/library/time.html#time.strftime + +essai utilisation logger un handler format different +sur info() pas de format et su other format + + /usr/lib/python2.7/logging/__init__.pyc + + init MyLogger, fmt='%(asctime)s :: %(levelname)-8s :: %(message)s', level='20' + + test logger info + 2018-03-11 18:51:51 :: WARNING :: test logger warning + 2018-03-11 18:51:51 :: ERROR :: test logger error + 2018-03-11 18:51:51 :: CRITICAL :: test logger critical + +""" + +import os +import sys +import logging +import pprint as PP + +print logging.__file__ + +class MyFormatter(logging.Formatter): + def format(self, record): + # print "", record.levelname #type(record), dir(record) + if record.levelname == "INFO": + return str(record.msg) + else: + return super(MyFormatter, self).format(record) + +def initMyLogger(fmt=None, level=None): + # http://sametmax.com/ecrire-des-logs-en-python/ + # https://docs.python.org/3/library/time.html#time.strftime + print "\ninit MyLogger, fmt='%s', level='%s'\n" % (fmt, level) + logger = getMyLogger() + handler = logging.StreamHandler() # Logging vers console + if fmt is not None: + # formatter = logging.Formatter(fmt, "%Y-%m-%d %H:%M:%S") + formatter =MyFormatter(fmt, "%Y-%m-%d %H:%M:%S") + handler.setFormatter(formatter) + logger.addHandler(handler) + if level is not None: + logger.setLevel(level) + else: + logger.setLevel(logger.INFO) # ou DEBUG + # logger.info('\n' + PP.pformat(dir(logger))) + +def getMyLogger(): + return logging.getLogger('MyLogger') + +def testLogger1(): + logger = getMyLogger() + logger.debug('test logger debug') + logger.info('test logger info') + logger.warning('test logger warning') + logger.error('test logger error') + logger.critical('test logger critical') + +if __name__ == "__main__": + initMyLogger('%(asctime)s :: %(levelname)-8s :: %(message)s', level=logging.INFO) + testLogger1() + diff --git a/src/exceptionSat.py b/src/exceptionSat.py new file mode 100644 index 0000000..dba733f --- /dev/null +++ b/src/exceptionSat.py @@ -0,0 +1,25 @@ +#!/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 + + +class ExceptionSat(Exception): + '''rename Exception Class for sat convenience (for future...) + ''' + pass + diff --git a/src/logger.py b/src/logger.py deleted file mode 100644 index 0a78a7b..0000000 --- a/src/logger.py +++ /dev/null @@ -1,414 +0,0 @@ -#!/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 -'''In this file are implemented the classes and method relative to the logging -''' - -import sys -import os -import datetime -import re -import tempfile - -import src - -from src import printcolors -from src import xmlManager - -log_macro_command_file_expression = "^[0-9]{8}_+[0-9]{6}_+.*\.xml$" -log_all_command_file_expression = "^.*[0-9]{8}_+[0-9]{6}_+.*\.xml$" - - -class Logger(object): - '''Class to handle log mechanism. - ''' - def __init__(self, - config, - silent_sysstd=False, - all_in_terminal=False, - micro_command = False): - '''Initialization - - :param config pyconf.Config: The global configuration. - :param silent_sysstd boolean: if True, do not write anything - in terminal. - ''' - self.config = config - self.default_level = 3 - self.silentSysStd = silent_sysstd - - # Construct xml log file location for sat prints. - prefix = "" - if micro_command: - prefix = "micro_" - hour_command_host = (config.VARS.datehour + "_" + - config.VARS.command + "_" + - config.VARS.hostname) - logFileName = prefix + hour_command_host + ".xml" - log_dir = src.get_log_path(config) - logFilePath = os.path.join(log_dir, logFileName) - # Construct txt file location in order to log - # the external commands calls (cmake, make, git clone, etc...) - txtFileName = prefix + hour_command_host + ".txt" - txtFilePath = os.path.join(log_dir, "OUT", txtFileName) - - src.ensure_path_exists(os.path.dirname(logFilePath)) - src.ensure_path_exists(os.path.dirname(txtFilePath)) - - # The path of the log files (one for sat traces, and the other for - # the system commands traces) - self.logFileName = logFileName - self.logFilePath = logFilePath - self.txtFileName = txtFileName - self.txtFilePath = txtFilePath - - # The list of all log files corresponding to the current command and - # the commands called by the current command - self.l_logFiles = [logFilePath, txtFilePath] - - # Initialize xml instance and put first fields - # like beginTime, user, command, etc... - self.xmlFile = xmlManager.XmlLogFile(logFilePath, "SATcommand", - attrib = {"application" : config.VARS.application}) - self.put_initial_xml_fields() - # Initialize the txt file for reading - try: - self.logTxtFile = open(str(self.txtFilePath), 'w') - except IOError: - #msg1 = _("WARNING! Trying to write to a file that is not accessible:") - #msg2 = _("The logs won't be written.") - #print("%s\n%s\n%s\n" % (src.printcolors.printcWarning(msg1), - # src.printcolors.printcLabel(str(self.txtFilePath)), - # src.printcolors.printcWarning(msg2) )) - self.logTxtFile = tempfile.TemporaryFile() - - # If the option all_in_terminal was called, all the system commands - # are redirected to the terminal - if all_in_terminal: - self.logTxtFile = sys.__stdout__ - - def put_initial_xml_fields(self): - '''Method called at class initialization : Put all fields - corresponding to the command context (user, time, ...) - ''' - # command name - self.xmlFile.add_simple_node("Site", attrib={"command" : - self.config.VARS.command}) - # version of salomeTools - self.xmlFile.append_node_attrib("Site", attrib={"satversion" : - self.config.INTERNAL.sat_version}) - # machine name on which the command has been launched - self.xmlFile.append_node_attrib("Site", attrib={"hostname" : - self.config.VARS.hostname}) - # Distribution of the machine - self.xmlFile.append_node_attrib("Site", attrib={"OS" : - self.config.VARS.dist}) - # The user that have launched the command - self.xmlFile.append_node_attrib("Site", attrib={"user" : - self.config.VARS.user}) - # The time when command was launched - Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour) - date_hour = "%2s/%2s/%4s %2sh%2sm%2ss" % (dd, m, Y, H, M, S) - self.xmlFile.append_node_attrib("Site", attrib={"beginTime" : - date_hour}) - # The application if any - if "APPLICATION" in self.config: - self.xmlFile.append_node_attrib("Site", - attrib={"application" : self.config.VARS.application}) - # The initialization of the trace node - self.xmlFile.add_simple_node("Log",text="") - # The system commands logs - self.xmlFile.add_simple_node("OutLog", - text=os.path.join("OUT", self.txtFileName)) - # The initialization of the node where - # to put the links to the other sat commands that can be called by any - # command - self.xmlFile.add_simple_node("Links") - - def add_link(self, - log_file_name, - command_name, - command_res, - full_launched_command): - '''Add a link to another log file. - - :param log_file_name str: The file name of the link. - :param command_name str: The name of the command linked. - :param command_res str: The result of the command linked. "0" or "1" - :parma full_launched_command str: The full lanch command - ("sat command ...") - ''' - xmlLinks = self.xmlFile.xmlroot.find("Links") - src.xmlManager.add_simple_node(xmlLinks, - "link", - text = log_file_name, - attrib = {"command" : command_name, - "passed" : command_res, - "launchedCommand" : full_launched_command}) - - def write(self, message, level=None, screenOnly=False): - '''the function used in the commands - that will print in the terminal and the log file. - - :param message str: The message to print. - :param level int: The output level corresponding - to the message 0 < level < 6. - :param screenOnly boolean: if True, do not write in log file. - ''' - # do not write message starting with \r to log file - if not message.startswith("\r") and not screenOnly: - self.xmlFile.append_node_text("Log", - printcolors.cleancolor(message)) - - # get user or option output level - current_output_verbose_level = self.config.USER.output_verbose_level - if not ('isatty' in dir(sys.stdout) and sys.stdout.isatty()): - # clean the message color if the terminal is redirected by user - # ex: sat compile appli > log.txt - message = printcolors.cleancolor(message) - - # Print message regarding the output level value - if level: - if level <= current_output_verbose_level and not self.silentSysStd: - sys.stdout.write(message) - else: - if self.default_level <= current_output_verbose_level and not self.silentSysStd: - sys.stdout.write(message) - self.flush() - - def error(self, message): - '''Print an error. - - :param message str: The message to print. - ''' - # Print in the log file - self.xmlFile.append_node_text("traces", _('ERROR:') + message) - - # Print in the terminal and clean colors if the terminal - # is redirected by user - if not ('isatty' in dir(sys.stderr) and sys.stderr.isatty()): - sys.stderr.write(printcolors.printcError(_('ERROR:') + message)) - else: - sys.stderr.write(_('ERROR:') + message) - - def flush(self): - '''Flush terminal - ''' - sys.stdout.flush() - self.logTxtFile.flush() - - def end_write(self, attribute): - '''Method called just after command end : Put all fields - corresponding to the command end context (time). - Write the log xml file on the hard drive. - And display the command to launch to get the log - - :param attribute dict: the attribute to add to the node "Site". - ''' - # Get current time (end of command) and format it - dt = datetime.datetime.now() - Y, m, dd, H, M, S = date_to_datetime(self.config.VARS.datehour) - t0 = datetime.datetime(int(Y), int(m), int(dd), int(H), int(M), int(S)) - tf = dt - delta = tf - t0 - total_time = timedelta_total_seconds(delta) - hours = int(total_time / 3600) - minutes = int((total_time - hours*3600) / 60) - seconds = total_time - hours*3600 - minutes*60 - # Add the fields corresponding to the end time - # and the total time of command - endtime = dt.strftime('%Y/%m/%d %Hh%Mm%Ss') - self.xmlFile.append_node_attrib("Site", attrib={"endTime" : endtime}) - self.xmlFile.append_node_attrib("Site", - attrib={"TotalTime" : "%ih%im%is" % (hours, minutes, seconds)}) - - # Add the attribute passed to the method - self.xmlFile.append_node_attrib("Site", attrib=attribute) - - # Call the method to write the xml file on the hard drive - self.xmlFile.write_tree(stylesheet = "command.xsl") - - # Dump the config in a pyconf file in the log directory - logDir = src.get_log_path(self.config) - dumpedPyconfFileName = (self.config.VARS.datehour - + "_" - + self.config.VARS.command - + ".pyconf") - dumpedPyconfFilePath = os.path.join(logDir, 'OUT', dumpedPyconfFileName) - try: - f = open(dumpedPyconfFilePath, 'w') - self.config.__save__(f) - f.close() - except IOError: - pass - -_DefaultLogger = [] # o,nly one default logger as common logger on one config - -def getDefaultLogger(config): - if len(_DefaultLogger) == 1: - if config != _DefaultLogger[0].config: - raise Exception("config is not unique, unexpected, have to fix that") - return _DefaultLogger[0] - if config == None: - raise Exception("config have to be set for default logger, not None") - _DefaultLogger.append(Logger(config)) - return _DefaultLogger[0] - -def date_to_datetime(date): - '''Little method that gets year, mon, day, hour , - minutes and seconds from a str in format YYYYMMDD_HHMMSS - - :param date str: The date in format YYYYMMDD_HHMMSS - :return: the same date and time in separate variables. - :rtype: (str,str,str,str,str,str) - ''' - Y = date[:4] - m = date[4:6] - dd = date[6:8] - H = date[9:11] - M = date[11:13] - S = date[13:15] - return Y, m, dd, H, M, S - -def timedelta_total_seconds(timedelta): - '''Little method to replace total_seconds from - datetime module in order to be compatible with old python versions - - :param timedelta datetime.timedelta: The delta between two dates - :return: The number of seconds corresponding to timedelta. - :rtype: float - ''' - return ( - timedelta.microseconds + 0.0 + - (timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6 - -def show_command_log(logFilePath, cmd, application, notShownCommands): - '''Used in updateHatXml. Determine if the log xml file logFilePath - has to be shown or not in the hat log. - - :param logFilePath str: the path to the command xml log file - :param cmd str: the command of the log file - :param application str: the application passed as parameter - to the salomeTools command - :param notShownCommands list: the list of commands - that are not shown by default - - :return: True if cmd is not in notShownCommands and the application - in the log file corresponds to application - :rtype: boolean - ''' - # When the command is not in notShownCommands, no need to go further : - # Do not show - if cmd in notShownCommands: - return False, None, None - - # Get the application of the log file - try: - logFileXml = src.xmlManager.ReadXmlFile(logFilePath) - except Exception as e: - msg = _("WARNING: the log file %s cannot be read:") % logFilePath - sys.stdout.write(printcolors.printcWarning("%s\n%s\n" % (msg, e))) - return False, None, None - - if 'application' in logFileXml.xmlroot.keys(): - appliLog = logFileXml.xmlroot.get('application') - launched_cmd = logFileXml.xmlroot.find('Site').attrib['launchedCommand'] - # if it corresponds, then the log has to be shown - if appliLog == application: - return True, appliLog, launched_cmd - elif application != 'None': - return False, appliLog, launched_cmd - - return True, appliLog, launched_cmd - - if application == 'None': - return True, None, None - - return False, None, None - -def list_log_file(dirPath, expression): - '''Find all files corresponding to expression in dirPath - - :param dirPath str: the directory where to search the files - :param expression str: the regular expression of files to find - :return: the list of files path and informations about it - :rtype: list - ''' - lRes = [] - for fileName in os.listdir(dirPath): - # YYYYMMDD_HHMMSS_namecmd.xml - sExpr = expression - oExpr = re.compile(sExpr) - if oExpr.search(fileName): - file_name = fileName - if fileName.startswith("micro_"): - file_name = fileName[len("micro_"):] - # get date and hour and format it - date_hour_cmd_host = file_name.split('_') - date_not_formated = date_hour_cmd_host[0] - date = "%s/%s/%s" % (date_not_formated[6:8], - date_not_formated[4:6], - date_not_formated[0:4]) - hour_not_formated = date_hour_cmd_host[1] - hour = "%s:%s:%s" % (hour_not_formated[0:2], - hour_not_formated[2:4], - hour_not_formated[4:6]) - if len(date_hour_cmd_host) < 4: - cmd = date_hour_cmd_host[2][:-len('.xml')] - host = "" - else: - cmd = date_hour_cmd_host[2] - host = date_hour_cmd_host[3][:-len('.xml')] - lRes.append((os.path.join(dirPath, fileName), - date_not_formated, - date, - hour_not_formated, - hour, - cmd, - host)) - return lRes - -def update_hat_xml(logDir, application=None, notShownCommands = []): - '''Create the xml file in logDir that contain all the xml file - and have a name like YYYYMMDD_HHMMSS_namecmd.xml - - :param logDir str: the directory to parse - :param application str: the name of the application if there is any - ''' - # Create an instance of XmlLogFile class to create hat.xml file - xmlHatFilePath = os.path.join(logDir, 'hat.xml') - xmlHat = src.xmlManager.XmlLogFile(xmlHatFilePath, - "LOGlist", {"application" : application}) - # parse the log directory to find all the command logs, - # then add it to the xml file - lLogFile = list_log_file(logDir, log_macro_command_file_expression) - for filePath, __, date, __, hour, cmd, __ in lLogFile: - showLog, cmdAppli, full_cmd = show_command_log(filePath, cmd, - application, notShownCommands) - #if cmd not in notShownCommands: - if showLog: - # add a node to the hat.xml file - xmlHat.add_simple_node("LogCommand", - text=os.path.basename(filePath), - attrib = {"date" : date, - "hour" : hour, - "cmd" : cmd, - "application" : cmdAppli, - "full_command" : full_cmd}) - - # Write the file on the hard drive - xmlHat.write_tree('hat.xsl') diff --git a/src/loggingSat.py b/src/loggingSat.py new file mode 100755 index 0000000..2ac44a0 --- /dev/null +++ b/src/loggingSat.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +http://sametmax.com/ecrire-des-logs-en-python/ +https://docs.python.org/3/library/time.html#time.strftime + +use logging package for salometools + +handler: + on info() no format + on other formatted indented on multi lines messages +""" + +import os +import sys +import logging +import pprint as PP + +_verbose = False +_name = "loggingSat" + +def indent(msg, nb, car=" "): + """indent nb car (spaces) multi lines message except first one""" + s = msg.split("\n") + res = ("\n"+car*nb).join(s) + return res + +def log(msg): + """elementary log when no logger yet""" + prefix = "%s.log: " % _name + nb = len(prefix) + if _verbose: print(prefix + indent(msg, nb)) + +log("import logging on %s" % logging.__file__) + +_loggerDefaultName = 'SatDefaultLogger' +_loggerUnittestName = 'SatUnittestLogger' + +def getDefaultLogger(): + log("getDefaultLogger %s" % _loggerDefaultName) + return logging.getLogger(_loggerDefaultName) + +def getUnittestLogger(): + log("getUnittestLogger %s" % _loggerUnittestName) + return logging.getLogger(_loggerUnittestName) + +def dirLogger(logger): + logger.info('dir(logger name=%s):\n' % logger.name + PP.pformat(dir(logger))) + +_loggerDefault = getDefaultLogger() +_loggerUnittest = getUnittestLogger() + + +class DefaultFormatter(logging.Formatter): + def format(self, record): + # print "", record.levelname #type(record), dir(record) + if record.levelname == "INFO": + return str(record.msg) + else: + return indent(super(DefaultFormatter, self).format(record), 12) + +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) + + +class UnittestStream(object): + """ + write my stream class + only write and flush are used for the streaming + https://docs.python.org/2/library/logging.handlers.html + https://stackoverflow.com/questions/31999627/storing-logger-messages-in-a-string + """ + def __init__(self): + self.logs = '' + + def write(self, astr): + # log("UnittestStream.write('%s')" % astr) + self.logs += astr + + def flush(self): + pass + + def __str__(self): + return self.logs + + +def initLoggerAsDefault(logger, fmt=None, level=None): + """ + init logger as prefixed message and indented message if multi line + 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 + if fmt is not None: + # formatter = logging.Formatter(fmt, "%Y-%m-%d %H:%M:%S") + formatter = DefaultFormatter(fmt, "%y-%m-%d %H:%M:%S") + handler.setFormatter(formatter) + logger.addHandler(handler) + if level is not None: + logger.setLevel(level) + else: + logger.setLevel(logger.INFO) + + +def initLoggerAsUnittest(logger, fmt=None, level=None): + """ + init logger as silent on stdout/stderr + used for retrieve messages in memory for post execution unittest + https://docs.python.org/2/library/logging.handlers.html + """ + log("initLoggerAsUnittest name=%s\nfmt='%s' level='%s'" % (logger.name, fmt, level)) + stream = UnittestStream() + handler = logging.StreamHandler(stream) # Logging vers stream + if fmt is not None: + # formatter = logging.Formatter(fmt, "%Y-%m-%d %H:%M:%S") + formatter = UnittestFormatter(fmt, "%Y-%m-%d %H:%M:%S") + handler.setFormatter(formatter) + logger.addHandler(handler) + logger.streamUnittest = stream + if level is not None: + logger.setLevel(level) + else: + logger.setLevel(logger.DEBUG) + + +def testLogger_1(logger): + """small test""" + # dirLogger(logger) + logger.debug('test logger debug') + logger.info('test logger info') + 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.warning('test logger warning:\n- second line\n- third line') + + +if __name__ == "__main__": + print("\n**** DEFAULT") + logdef = getDefaultLogger() + initLoggerAsDefault(logdef, '%(levelname)-8s :: %(message)s', level=logging.INFO) + testLogger_1(logdef) + print("\n**** UNITTEST") + loguni = getUnittestLogger() + initLoggerAsUnittest(loguni, '%(asctime)s :: %(levelname)-8s :: %(message)s', level=logging.DEBUG) + testLogger_1(loguni) # is silent + # log("loguni.streamUnittest:\n%s" % loguni.streamUnittest) + print("loguni.streamUnittest:\n%s" % loguni.streamUnittest) + + 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 diff --git a/src/printcolors.py b/src/printcolors.py deleted file mode 100644 index 52d41f6..0000000 --- a/src/printcolors.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env python -#-*- coding:utf-8 -*- -# Copyright (C) 2010-2013 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 -'''In this file is stored the mechanism that manage color prints in the terminal -''' - -# define constant to use in scripts -COLOR_ERROR = 'ERROR' -COLOR_WARNING = 'WARNING' -COLOR_SUCCESS = 'SUCCESS' -COLOR_LABEL = 'LABEL' -COLOR_HEADER = 'HEADER' -COLOR_INFO = 'INFO' -COLOR_HIGLIGHT = 'HIGHLIGHT' - -# the color map to use to print the colors -__colormap__ = { - COLOR_ERROR: '\033[1m\033[31m', - COLOR_SUCCESS: '\033[1m\033[32m', - COLOR_WARNING: '\033[33m', - COLOR_HEADER: '\033[34m', - COLOR_INFO: '\033[35m', - COLOR_LABEL: '\033[36m', - COLOR_HIGLIGHT: '\033[97m\033[43m' -} - -# list of available codes -__code_range__ = ([1, 4] + list(range(30, 38)) + list(range(40, 48)) - + list(range(90, 98)) + list(range(100, 108))) - -def printc(txt, code=''): - '''print a text with colors - - :param txt str: The text to be printed. - :param code str: The color to use. - :return: The colored text. - :rtype: str - ''' - # no code means 'auto mode' (works only for OK, KO, NO and ERR*) - if code == '': - striptxt = txt.strip().upper() - if striptxt == "OK": - code = COLOR_SUCCESS - elif striptxt in ["KO", "NO"] or striptxt.startswith("ERR"): - code = COLOR_ERROR - else: - return txt - - # no code => output the originial text - if code not in __colormap__.keys() or __colormap__[code] == '': - return txt - - return __colormap__[code] + txt + '\033[0m' - -def printcInfo(txt): - '''print a text info color - - :param txt str: The text to be printed. - :return: The colored text. - :rtype: str - ''' - return printc(txt, COLOR_INFO) - -def printcError(txt): - '''print a text error color - - :param txt str: The text to be printed. - :return: The colored text. - :rtype: str - ''' - return printc(txt, COLOR_ERROR) - -def printcWarning(txt): - '''print a text warning color - - :param txt str: The text to be printed. - :return: The colored text. - :rtype: str - ''' - return printc(txt, COLOR_WARNING) - -def printcHeader(txt): - '''print a text header color - - :param txt str: The text to be printed. - :return: The colored text. - :rtype: str - ''' - return printc(txt, COLOR_HEADER) - -def printcLabel(txt): - '''print a text label color - - :param txt str: The text to be printed. - :return: The colored text. - :rtype: str - ''' - return printc(txt, COLOR_LABEL) - -def printcSuccess(txt): - '''print a text success color - - :param txt str: The text to be printed. - :return: The colored text. - :rtype: str - ''' - return printc(txt, COLOR_SUCCESS) - -def printcHighlight(txt): - '''print a text highlight color - - :param txt str: The text to be printed. - :return: The colored text. - :rtype: str - ''' - return printc(txt, COLOR_HIGLIGHT) - -def cleancolor(message): - '''remove color from a colored text. - - :param message str: The text to be cleaned. - :return: The cleaned text. - :rtype: str - ''' - if message == None: - return message - - message = message.replace('\033[0m', '') - for i in __code_range__: - message = message.replace('\033[%dm' % i, '') - return message - -def print_value(logger, label, value, level=1, suffix=""): - '''shortcut method to print a label and a value with the info color - - :param logger class logger: the logger instance. - :param label int: the label to print. - :param value str: the value to print. - :param level int: the level of verboseness. - :param suffix str: the suffix to add at the end. - ''' - if logger is None: - print(" %s = %s %s" % (label, printcInfo(str(value)), suffix)) - else: - logger.write(" %s = %s %s\n" % (label, printcInfo(str(value)), - suffix), level) - -def print_color_range(start, end): - '''print possible range values for colors - - :param start int: The smaller value. - :param end int: The bigger value. - ''' - for k in range(start, end+1): - print("\033[%dm%3d\033[0m" % (k, k),) - print - -# This method prints the color map -def print_color_map(): - '''This method prints the color map - ''' - print("colormap:") - print("{") - for k in sorted(__colormap__.keys()): - codes = __colormap__[k].split('\033[') - codes = filter(lambda l: len(l) > 0, codes) - codes = map(lambda l: l[:-1], codes) - print(printc(" %s: '%s', " % (k, ';'.join(codes)), k)) - print("}") - - diff --git a/src/salomeTools.py b/src/salomeTools.py index 6d2d033..a009fdb 100755 --- a/src/salomeTools.py +++ b/src/salomeTools.py @@ -18,8 +18,17 @@ # 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 +######################################################################## +if __name__ == "__main__": + sys.stderr.write("\nERROR: 'salomeTools.py' is not main command entry for sat: use 'sat' instead.\n\n") + KOSYS = 1 # avoid import src + sys.exit(KOSYS) + import os import sys import re @@ -30,19 +39,9 @@ import traceback import subprocess as SP import pprint as PP - -######################################################################## -# NOT MAIN entry allowed, use sat -######################################################################## -if __name__ == "__main__": - sys.stderr.write("\nERROR: 'salomeTools.py' is not main command entry for sat: use 'sat' instead.\n\n") - KOSYS = 1 # avoid import src - sys.exit(KOSYS) - - import src.debug as DBG # Easy print stderr (for DEBUG only) import src.returnCode as RCO # Easy (ok/ko, why) return methods code -import src +from src.exceptionSat import ExceptionSat # get path to src rootdir = os.path.realpath( os.path.join(os.path.dirname(__file__), "..") ) @@ -120,9 +119,9 @@ def setLocale(): # _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 @@ -359,45 +358,37 @@ class _BaseCommand(object): # Sat class ######################################################################## class Sat(object): - '''The main class that stores all the commands of salomeTools - ''' - def __init__(self, opt='', datadir=None): - '''Initialization + """The main class that stores all the commands of salomeTools + """ + def __init__(self, logger): + """Initialization - :param opt str or list: The sat options - :param: datadir str : the directory that contain all the external - data (like software pyconf and software scripts) - ''' + :param logger: The logger to use + """ + # Read the salomeTools prefixes options before the 'commands' tag # sat - # (the list of possible options is at the beginning of this file) + # (the list of possible options is at the beginning of this file) - # DBG.push_debug(True) - - self.parser = self._getParser() - try: - if type(opt) is not list: # as string 'sat --help' for example' - opts = opt.split() - else: - opts = opt - options, args = self.parser.parse_args(opts) - DBG.write("Sat options", options) - DBG.write("Sat remainders args", args) - - except Exception as exc: - write_exception(exc) - sys.exit(RCO.KOSYS) + self.CONFIG_FILENAME = "sat-config.pyconf" + self.config = None # the config that will be read using pyconf module self.logger = None # the logger that will be use - self.arguments = args # args are postfixes options: args[0] is the 'commands' command - self.options = options # the options passed to salomeTools - self.datadir = datadir # default value will be /data + self.arguments = None # args are postfixes options: args[0] is the 'commands' command + self.options = None # the options passed to salomeTools + + # the directory that contain all the external + # data (like software pyconf and software scripts) + self.datadir = None # default value will be /data + # contains commands classes needed (think micro commands) # if useful 'a la demande' self.commands = {} self.nameAppliLoaded = None + self.parser = self._getParser() + def __repr__(self): aDict = { "arguments": self.arguments, @@ -408,15 +399,23 @@ class Sat(object): tmp = PP.pformat(aDict) res = "Sat(\n %s\n)\n" % tmp[1:-1] return res - def getLogger(self): if self.logger is None: # could use owner Sat instance logger - import src.logger as LOG - self.logger=LOG.getDefaultLogger(self.config) + import src.loggingSat as LOG + self.logger=LOG.getDefaultLogger() + self.logger.error("Sat logger not set, fixed as default") return self.logger else: # could use local logger return self.logger + + 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 def _getParser(self): @@ -441,8 +440,16 @@ class Sat(object): parser.add_option('l', 'logs_paths_in_file', 'string', "logs_paths_in_file", _("put the command result and paths to log files.")) return parser - - + + + def parseGenericArguments(self, arguments): + args = self.assumeAsList(arguments) + genericOptions, remaindersArgs = self.parser.parse_args(args) + DBG.write("Sat generic arguments", genericArgs) + DBG.write("Sat remainders arguments", remaindersArgs) + return genericOptions, remaindersArgs + + def _getCommand(self, name): """ create and add Command 'name' as instance of class in dict self.commands @@ -476,15 +483,12 @@ class Sat(object): self.commands[name] = self._getCommand(name) return self.commands[name] - def execute_command(self, opt=None): + def execute_cli(self, cli_arguments): """select first argument as a command in directory 'commands', and launch on arguments - :param opt str, optionnal: The sat options (as sys.argv) + :param args str or list, The sat cli arguments (as sys.argv) """ - if opt is not None: - args = opt - else: - args = self.arguments + args = self.assumeAsList(cli_arguments) # print general help and returns if len(args) == 0: diff --git a/src/salomeTools_old.py b/src/salomeTools_old.py deleted file mode 100755 index 4036061..0000000 --- a/src/salomeTools_old.py +++ /dev/null @@ -1,637 +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 - -'''This file is the main entry file to salomeTools -''' - -import os -import sys -import re -import tempfile -import imp -import types -import gettext -import traceback -import subprocess as SP - -################################# -# NOT MAIN allowed -################################# -if __name__ == "__main__": - sys.stderr.write("\nERROR: 'salomeTools.py' is not main command entry for sat: use 'sat' instead.\n\n") - KOSYS = 1 # avoid import src - sys.exit(KOSYS) - - -import src.debug as DBG # Easy print stderr (for DEBUG only) - -# get path to src -rootdir = os.path.realpath( os.path.join(os.path.dirname(__file__), "..") ) -DBG.write("sat rootdir", rootdir) -srcdir = os.path.join(rootdir, "src") -cmdsdir = os.path.join(rootdir, "commands") - -# load resources for internationalization -gettext.install('salomeTools', os.path.join(srcdir, 'i18n')) - -# salomeTools imports -import src -import commands.config - -# The possible hooks : -# pre is for hooks to be executed before commands -# post is for hooks to be executed after commands -C_PRE_HOOK = "pre" -C_POST_HOOK = "post" - -_LANG = os.environ["LANG"] # original locale - -def find_command_list(dirPath): - '''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 - -# The list of valid salomeTools commands -#lCommand = ['config', 'compile', 'prepare',...] -lCommand = find_command_list(cmdsdir) - -def getCommandsList(): - """Gives commands list (as basename of files commands/*.py) - """ - return lCommand - -def launchSat(command): - """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"] - 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')) - DBG.write("setNotLocale", os.environ["LANG"]) - -def setLocale(): - """reset initial locale at any moment - 'fr' or else 'TODO' from initial environment var '$LANG' - """ - os.environ["LANG"] = _LANG - gettext.install('salomeTools', os.path.join(srcdir, 'i18n')) - DBG.write("setLocale", os.environ["LANG"]) - -# Define all possible option for salomeTools command : sat -parser = src.options.Options() -parser.add_option('h', 'help', 'boolean', 'help', - _("shows global help or help on a specific command.")) -parser.add_option('o', 'overwrite', 'list', "overwrite", - _("overwrites a configuration parameters.")) -parser.add_option('g', 'debug', 'boolean', 'debug_mode', - _("run salomeTools in debug mode.")) -parser.add_option('v', 'verbose', 'int', "output_verbose_level", - _("change output verbose level (default is 3).")) -parser.add_option('b', 'batch', 'boolean', "batch", - _("batch mode (no question).")) -parser.add_option('t', 'all_in_terminal', 'boolean', "all_in_terminal", - _("all traces in the terminal (for example compilation logs).")) -parser.add_option('l', 'logs_paths_in_file', 'string', "logs_paths_in_file", - _("put the command result and paths to log files.")) - -class Sat(object): - '''The main class that stores all the commands of salomeTools - ''' - def __init__(self, opt='', datadir=None): - '''Initialization - - :param opt str or list: The sat options - :param: datadir str : the directory that contain all the external - data (like software pyconf and software scripts) - ''' - # Read the salomeTools prefixes options before the 'commands' tag - # sat - # (the list of possible options is at the beginning of this file) - try: - if type(opt) is not list: # as string 'sat --help' for example' - opts = opt.split() - else: - opts = opt - options, args = parser.parse_args(opts) - # DBG.write("Sat args", args) - # DBG.write("Sat options", options) - except Exception as exc: - write_exception(exc) - sys.exit(src.KOSYS) - - # initialization of class attributes - self.__dict__ = dict() - self.cfg = None # the config that will be read using pyconf module - self.arguments = args # args are postfixes options: args[0] is the 'commands' command - self.options = options # the options passed to salomeTools - self.datadir = datadir # default value will be /data - # set the commands by calling the dedicated function - self._setCommands(cmdsdir) - - ''' - # done with execute_command, to avoid sys.exit - # if the help option has been called, print help and exit - if options.help: - try: - self.print_help(args) - sys.exit(src.OKSYS) - except Exception as exc: - write_exception(exc) - DBG.write("args", args, True) - sys.exit(src.KOSYS) - ''' - - def __getattr__(self, name): - '''overwrite of __getattr__ function in order to display - a customized message in case of a wrong call - - :param name str: The name of the attribute - ''' - if name in self.__dict__: - return self.__dict__[name] - else: - raise AttributeError("'%s'" % name + _(" is not a valid command")) - - def execute_command(self, opt=None): - """select first argument as a command in directory 'commands', and launch on arguments - - :param opt str, optionnal: The sat options (as sys.argv) - """ - if opt is not None: - args = opt - else: - args = self.arguments - - # print general help and returns - if len(args) == 0: - print_help() - return src.OKSYS - - # if the help option has been called, print command help and returns - if self.options.help: - self.print_help(self.arguments) - return src.OKSYS - - # the command called - command = args[0] - # get dynamically the command function to call - fun_command = self.__getattr__(command) - # Run the command using the arguments - exitCode = fun_command(args[1:]) - return exitCode - - def _setCommands(self, dirPath): - '''set class attributes corresponding to all commands that are - in the dirPath directory - - :param dirPath str: The directory path containing the commands - ''' - # loop on the commands name - for nameCmd in lCommand: - - # Exception for the jobs command that requires the paramiko module - if nameCmd == "jobs": - try: - saveout = sys.stderr - ff = tempfile.TemporaryFile() - sys.stderr = ff - import paramiko - sys.stderr = saveout - except: - sys.stderr = saveout - continue - - # load the module that has name nameCmd in dirPath - (file_, pathname, description) = imp.find_module(nameCmd, [dirPath]) - module = imp.load_module(nameCmd, file_, pathname, description) - - def run_command(args='', - options=None, - batch = False, - verbose = -1, - logger_add_link = None): - '''The function that will load the configuration (all pyconf) - and return the function run of the command corresponding to module - - :param args str: The arguments of the command - ''' - # Make sure the internationalization is available - # gettext.install('salomeTools', os.path.join(srcdir, 'i18n')) - - # Get the arguments in a list and remove the empty elements - 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 = commands.config.ConfigManager() - self.cfg = cfgManager.get_config(datadir=self.datadir, - application=appliToLoad, - options=self.options, - command=__nameCmd__) - - # Set the verbose mode if called - 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.options.output_verbose_level is not None: - self.cfg.USER.output_verbose_level = self.options.output_verbose_level - if self.cfg.USER.output_verbose_level < 1: - self.cfg.USER.output_verbose_level = 0 - silent = (self.cfg.USER.output_verbose_level == 0) - - # create log file - micro_command = False - if logger_add_link: - micro_command = True - logger_command = src.logger.Logger(self.cfg, - silent_sysstd=silent, - all_in_terminal=self.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.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 - - options_launched = "" - res = None - try: - # Execute the hooks (if there is any) - # and run method of the command - self.run_hook(__nameCmd__, C_PRE_HOOK, logger_command) - res = __module__.run(argv, self, logger_command) - self.run_hook(__nameCmd__, C_POST_HOOK, logger_command) - if res is None: - res = 0 - - except Exception as e: - # Get error - logger_command.write("\n***** ", 1) - logger_command.write( - src.printcolors.printcError("salomeTools ERROR:"), 1) - logger_command.write("\n" + str(e) + "\n\n", 1) - # get stack - __, __, exc_traceback = sys.exc_info() - fp = tempfile.TemporaryFile() - traceback.print_tb(exc_traceback, file=fp) - fp.seek(0) - stack = fp.read() - verbosity = 5 - if self.options.debug_mode: - verbosity = 1 - logger_command.write("TRACEBACK:\n%s" % stack.replace('"',"'"), - verbosity) - finally: - # set res if it is not set in the command - if res is None: - res = 1 - - # come back to the original global options - if options: - options_launched = get_text_from_options(self.options) - self.options = options_save - - # come back in the original batch mode if - # batch argument was called - if batch: - self.options.__setattr__("batch", batch_save) - - # come back in the original verbose mode if - # verbose argument was called - if verbose > -1: - self.options.__setattr__("output_verbose_level", - verbose_save) - # put final attributes in xml log file - # (end time, total time, ...) and write it - launchedCommand = ' '.join([self.cfg.VARS.salometoolsway + - os.path.sep + - 'sat', - options_launched, - __nameCmd__, - ' '.join(argv_0)]) - launchedCommand = launchedCommand.replace('"', "'") - - # Add a link to the parent command - if logger_add_link is not None: - logger_add_link.add_link(logger_command.logFileName, - __nameCmd__, - res, - launchedCommand) - logger_add_link.l_logFiles += logger_command.l_logFiles - - # Put the final attributes corresponding to end time and - # Write the file to the hard drive - logger_command.end_write( - {"launchedCommand" : launchedCommand}) - - if res != 0: - res = 1 - - # print the log file path if - # the maximum verbose mode is invoked - if not micro_command: - logger_command.write("\nPath to the xml log file:\n", 5) - logger_command.write("%s\n\n" % \ - src.printcolors.printcInfo(logger_command.logFilePath), 5) - - # If the logs_paths_in_file was called, write the result - # and log files in the given file path - if self.options.logs_paths_in_file and not micro_command: - file_res = open(self.options.logs_paths_in_file, "w") - file_res.write(str(res) + "\n") - for i, filepath in enumerate(logger_command.l_logFiles): - file_res.write(filepath) - if i < len(logger_command.l_logFiles): - file_res.write("\n") - file_res.flush() - - return res - - # Make sure that run_command will be redefined - # at each iteration of the loop - globals_up = {} - globals_up.update(run_command.__globals__) - globals_up.update({'__nameCmd__': nameCmd, '__module__' : module}) - func = types.FunctionType(run_command.__code__, - globals_up, - run_command.__name__, - run_command.__defaults__, - run_command.__closure__) - - # set the attribute corresponding to the command - self.__setattr__(nameCmd, func) - - def run_hook(self, cmd_name, hook_type, logger): - '''Execute a hook file for a given command regarding the fact - it is pre or post - - :param cmd_name str: The the command on which execute the hook - :param hook_type str: pre or post - :param logger Logger: the logging instance to use for the prints - ''' - # The hooks must be defined in the application pyconf - # So, if there is no application, do not do anything - if not src.config_has_application(self.cfg): - return - - # The hooks must be defined in the application pyconf in the - # APPLICATION section, hook : { command : 'script_path.py'} - if "hook" not in self.cfg.APPLICATION \ - or cmd_name not in self.cfg.APPLICATION.hook: - return - - # Get the hook_script path and verify that it exists - hook_script_path = self.cfg.APPLICATION.hook[cmd_name] - if not os.path.exists(hook_script_path): - raise src.SatException(_("Hook script not found: %s") % - hook_script_path) - - # Try to execute the script, catch the exception if it fails - try: - # import the module (in the sense of python) - pymodule = imp.load_source(cmd_name, hook_script_path) - - # format a message to be printed at hook execution - msg = src.printcolors.printcWarning(_("Run hook script")) - msg = "%s: %s\n" % (msg, - src.printcolors.printcInfo(hook_script_path)) - - # run the function run_pre_hook if this function is called - # before the command, run_post_hook if it is called after - if hook_type == C_PRE_HOOK and "run_pre_hook" in dir(pymodule): - logger.write(msg, 1) - pymodule.run_pre_hook(self.cfg, logger) - elif hook_type == C_POST_HOOK and "run_post_hook" in dir(pymodule): - logger.write(msg, 1) - pymodule.run_post_hook(self.cfg, logger) - - except Exception as exc: - msg = _("Unable to run hook script: %s") % hook_script_path - msg += "\n" + str(exc) - raise src.SatException(msg) - - 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 - command = opt[0] - # read the configuration from all the pyconf files - cfgManager = commands.config.ConfigManager() - self.cfg = cfgManager.get_config(datadir=self.datadir) - - # Check if this command exists - if not hasattr(self, command): - raise src.SatException(_("Command '%s' does not exist") % command) - - # load the module - module = self.get_module(command) - - msg = self.get_module_help(module) - - 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_module_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 - if hasattr( module, "description" ): - msg += src.printcolors.printcHeader( _("Description:") ) + "\n" - msg += module.description() + "\n\n" - - # print the description of the command options - if hasattr( module, "parser" ) : - msg += module.parser.get_help() + "\n" - return msg - - - def get_module(self, module): - '''Loads a command. Function called only by print_help - - :param module str: the command to load - ''' - # Check if this command exists - if not hasattr(self, module): - raise src.SatException(_("Command '%s' does not exist") % module) - - # load the module - (file_, pathname, description) = imp.find_module(module, [cmdsdir]) - module = imp.load_module(module, file_, pathname, description) - return module - - -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 = commands.config.ConfigManager() - 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 [sat_options] [product] [command_options]\n\n" - msg += parser.get_help() + "\n" - msg += src.printcolors.printcHeader(_("Available commands are:")) + "\n\n" - for command in lCommand: - 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 - - :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") - - - - diff --git a/src/utilsSat.py b/src/utilsSat.py new file mode 100644 index 0000000..07f3995 --- /dev/null +++ b/src/utilsSat.py @@ -0,0 +1,398 @@ +#!/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 + +""" +utilities for sat +general useful simple methods +""" + +import os +import shutil +import errno +import stat + + + +def ensure_path_exists(p): + '''Create a path if not existing + + :param p str: The path. + ''' + if not os.path.exists(p): + os.makedirs(p) + +def check_config_has_application( config, details = None ): + '''check that the config has the key APPLICATION. Else raise an exception. + + :param config class 'common.pyconf.Config': The config. + ''' + if 'APPLICATION' not in config: + message = _("An APPLICATION is required. Use 'config --list' to get" + " the list of available applications.\n") + if details : + details.append(message) + raise SatException( message ) + +def check_config_has_profile( config, details = None ): + '''check that the config has the key APPLICATION.profile. + Else, raise an exception. + + :param config class 'common.pyconf.Config': The config. + ''' + check_config_has_application(config) + if 'profile' not in config.APPLICATION: + message = _("A profile section is required in your application.\n") + if details : + details.append(message) + raise SatException( message ) + +def config_has_application( config ): + return 'APPLICATION' in config + +def get_cfg_param(config, param_name, default): + '''Search for param_name value in config. + If param_name is not in config, then return default, + else, return the found value + + :param config class 'common.pyconf.Config': The config. + :param param_name str: the name of the parameter to get the value + :param default str: The value to return if param_name is not in config + :return: see initial description of the function + :rtype: str + ''' + if param_name in config: + return config[param_name] + return default + +def print_info(logger, info): + '''Prints the tuples that are in info variable in a formatted way. + + :param logger Logger: The logging instance to use for the prints. + :param info list: The list of tuples to display + ''' + # find the maximum length of the first value of the tuples in info + smax = max(map(lambda l: len(l[0]), info)) + # Print each item of info with good indentation + for i in info: + sp = " " * (smax - len(i[0])) + printcolors.print_value(logger, sp + i[0], i[1], 2) + logger.write("\n", 2) + +def get_base_path(config): + '''Returns the path of the products base. + + :param config Config: The global Config instance. + :return: The path of the products base. + :rtype: str + ''' + if "base" not in config.LOCAL: + local_file_path = os.path.join(config.VARS.salometoolsway, + "data", + "local.pyconf") + msg = _("Please define a base path in the file %s") % local_file_path + raise SatException(msg) + + base_path = os.path.abspath(config.LOCAL.base) + + return base_path + +def get_launcher_name(config): + '''Returns the name of salome launcher. + + :param config Config: The global Config instance. + :return: The name of salome launcher. + :rtype: str + ''' + check_config_has_application(config) + if 'profile' in config.APPLICATION and 'launcher_name' in config.APPLICATION.profile: + launcher_name = config.APPLICATION.profile.launcher_name + else: + launcher_name = 'salome' + + return launcher_name + +def get_log_path(config): + '''Returns the path of the logs. + + :param config Config: The global Config instance. + :return: The path of the logs. + :rtype: str + ''' + if "log_dir" not in config.LOCAL: + local_file_path = os.path.join(config.VARS.salometoolsway, + "data", + "local.pyconf") + msg = _("Please define a log_dir in the file %s") % local_file_path + raise SatException(msg) + + log_dir_path = os.path.abspath(config.LOCAL.log_dir) + + return log_dir_path + +def get_salome_version(config): + if hasattr(config.APPLICATION, 'version_salome'): + Version = config.APPLICATION.version_salome + else: + KERNEL_info = product.get_product_config(config, "KERNEL") + VERSION = os.path.join( + KERNEL_info.install_dir, + "bin", + "salome", + "VERSION") + if not os.path.isfile(VERSION): + return None + + fVERSION = open(VERSION) + Version = fVERSION.readline() + fVERSION.close() + + VersionSalome = int(only_numbers(Version)) + return VersionSalome + +def only_numbers(str_num): + return ''.join([nb for nb in str_num if nb in '0123456789'] or '0') + +def read_config_from_a_file(filePath): + try: + cfg_file = pyconf.Config(filePath) + except pyconf.ConfigError as e: + raise SatException(_("Error in configuration file: %(file)s\n %(error)s") % + { 'file': filePath, 'error': str(e) } ) + return cfg_file + +def get_tmp_filename(cfg, name): + if not os.path.exists(cfg.VARS.tmp_root): + os.makedirs(cfg.VARS.tmp_root) + + return os.path.join(cfg.VARS.tmp_root, name) + +## +# Utils class to simplify path manipulations. +class Path: + def __init__(self, path): + self.path = str(path) + + def __add__(self, other): + return Path(os.path.join(self.path, str(other))) + + def __abs__(self): + return Path(os.path.abspath(self.path)) + + def __str__(self): + return self.path + + def __eq__(self, other): + return self.path == other.path + + def exists(self): + return self.islink() or os.path.exists(self.path) + + def islink(self): + return os.path.islink(self.path) + + def isdir(self): + return os.path.isdir(self.path) + + def isfile(self): + return os.path.isfile(self.path) + + def list(self): + return [Path(p) for p in os.listdir(self.path)] + + def dir(self): + return Path(os.path.dirname(self.path)) + + def base(self): + return Path(os.path.basename(self.path)) + + def make(self, mode=None): + os.makedirs(self.path) + if mode: + os.chmod(self.path, mode) + + def chmod(self, mode): + os.chmod(self.path, mode) + + def rm(self): + if self.islink(): + os.remove(self.path) + else: + shutil.rmtree( self.path, onerror = handleRemoveReadonly ) + + def copy(self, path, smart=False): + if not isinstance(path, Path): + path = Path(path) + + if os.path.islink(self.path): + return self.copylink(path) + elif os.path.isdir(self.path): + return self.copydir(path, smart) + else: + return self.copyfile(path) + + def smartcopy(self, path): + return self.copy(path, True) + + def readlink(self): + if self.islink(): + return os.readlink(self.path) + else: + return False + + def symlink(self, path): + try: + os.symlink(str(path), self.path) + return True + except: + return False + + def copylink(self, path): + try: + os.symlink(os.readlink(self.path), str(path)) + return True + except: + return False + + def copydir(self, dst, smart=False): + try: + names = self.list() + + if not dst.exists(): + dst.make() + + for name in names: + if name == dst: + continue + if smart and (str(name) in [".git", "CVS", ".svn"]): + continue + srcname = self + name + dstname = dst + name + srcname.copy(dstname, smart) + return True + except: + return False + + def copyfile(self, path): + try: + shutil.copy2(self.path, str(path)) + return True + except: + return False + +def find_file_in_lpath(file_name, lpath, additional_dir = ""): + """Find in all the directories in lpath list the file that has the same name + as file_name. If it is found, return the full path of the file, else, + return False. + The additional_dir (optional) is the name of the directory to add to all + paths in lpath. + + :param file_name str: The file name to search + :param lpath List: The list of directories where to search + :param additional_dir str: The name of the additional directory + :return: the full path of the file or False if not found + :rtype: str + """ + for directory in lpath: + dir_complete = os.path.join(directory, additional_dir) + if not os.path.isdir(directory) or not os.path.isdir(dir_complete): + continue + l_files = os.listdir(dir_complete) + for file_n in l_files: + if file_n == file_name: + return os.path.join(dir_complete, file_name) + return False + +def handleRemoveReadonly(func, path, exc): + excvalue = exc[1] + if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES: + os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777 + func(path) + else: + raise + +def deepcopy_list(input_list): + """ Do a deep copy of a list + + :param input_list List: The list to copy + :return: The copy of the list + :rtype: List + """ + res = [] + for elem in input_list: + res.append(elem) + return res + +def remove_item_from_list(input_list, item): + """ Remove all occurences of item from input_list + + :param input_list List: The list to modify + :return: The without any item + :rtype: List + """ + res = [] + for elem in input_list: + if elem == item: + continue + res.append(elem) + return res + +def parse_date(date): + """Transform YYYYMMDD_hhmmss into YYYY-MM-DD hh:mm:ss. + + :param date str: The date to transform + :return: The date in the new format + :rtype: str + """ + if len(date) != 15: + return date + res = "%s-%s-%s %s:%s:%s" % (date[0:4], + date[4:6], + date[6:8], + date[9:11], + date[11:13], + date[13:]) + return res + +def merge_dicts(*dict_args): + ''' + Given any number of dicts, shallow copy and merge into a new dict, + precedence goes to key value pairs in latter dicts. + ''' + result = {} + for dictionary in dict_args: + result.update(dictionary) + return result + +def replace_in_file(filein, strin, strout): + '''Replace by in file + ''' + shutil.move(filein, filein + "_old") + fileout= filein + filein = filein + "_old" + fin = open(filein, "r") + fout = open(fileout, "w") + for line in fin: + fout.write(line.replace(strin, strout)) + +def get_property_in_product_cfg(product_cfg, pprty): + if not "properties" in product_cfg: + return None + if not pprty in product_cfg.properties: + return None + return product_cfg.properties[pprty] -- 2.39.2