Salome HOME
spns #40790: on Debian distributions, use dpkg-query instead of apt to check system...
[tools/sat.git] / src / debug.py
old mode 100644 (file)
new mode 100755 (executable)
index 1bc2618..330fb6d
 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 
 """
-This file assume DEBUG functionalities use
-- print debug messages in sys.stderr for salomeTools
-- show pretty print debug representation from instances of SAT classes
-  (pretty print src.pyconf.Config), and python dict/list etc. (as 'aVariable')
+This file assume DEBUG functionalities use.
+Print salomeTools debug messages in sys.stderr.
+Show pretty print debug representation from instances of SAT classes 
+(pretty print src.pyconf.Config)
 
-WARNING: obviously supposedly show messages in SAT development phase, not production
-
-usage:
->> import debug as DBG
->> DBG.write("aTitle", aVariable)        # not shown in production 
->> DBG.write("aTitle", aVariable, True)  # unconditionaly shown (as show=True)
-
-to set show message as development phase:
->> DBG.push_debug(True)
-
-to set no show message as production phase:
->> DBG.push_debug(False)
-
-to set show message temporary as development phase, only in a method:
->> def aMethodToDebug(...):
->>   DBG.push_debug(True)              #force show as appended status
->>   etc. method code with some DBG.write()
->>   DBG.pop_debug()                   #restore previous status (show or not show)
->>   return
-
-to set a message for future fix, as temporary problem to not forget:
-DBG.tofix("aTitle", aVariable, True/False) #True/False in production shown, or not
-
-in command line interface you could redirect stderr to file 'myDebug.log':
->> sat compile ... 2> myDebug.log   # only stderr
->> sat compile ... &> myDebug.log   # stdout and stderr
+| Warning: supposedly show messages in SAT development phase, not production
+| 
+| Usage:
+>> import debug as DBG
+>> DBG.write("aTitle", aVariable)        # not shown in production 
+>> DBG.write("aTitle", aVariable, True)  # unconditionaly shown (as show=True)
+| 
+to set show message as development phase:
+>> DBG.push_debug(True)
+| 
+to set no show message as production phase:
+>> DBG.push_debug(False)
+| 
+to set show message temporary as development phase, only in a method:
+>> def aMethodToDebug(...):
+>>   DBG.push_debug(True)              #force show as appended status
+>>   etc. method code with some DBG.write()
+>>   DBG.pop_debug()                   #restore previous status (show or not show)
+>>   return
+| 
+to set a message for future fix, as temporary problem to not forget:
+DBG.tofix("aTitle", aVariable, True/False) #True/False in production shown, or not
+| 
+in command line interface you could redirect stderr to file 'myDebug.log':
+>> sat compile ... 2> myDebug.log   # only stderr
+>> sat compile ... &> myDebug.log   # stdout and stderr
 """
 
 import os
 import sys
-import StringIO as SIO
+import traceback
 import pprint as PP
+import inspect
+import src
+
+# Compatibility python 2/3 for unicode
+try:
+    _test = unicode
+except:
+    unicode = str
+
+# Compatibility python 2/3 for StringIO
+try:
+    from StringIO import StringIO
+except ImportError:
+    from io import StringIO
 
 _debug = [False] #support push/pop for temporary activate debug outputs
 
+
+def isDeveloper():
+    """
+    if you are a developer, sometimes you want verbose traces unconditionally
+    export SAT_DEVELOPER_MODE=1 before launch sat command to do that
+    """
+    res = os.getenv("SAT_DEVELOPER_MODE", "0")
+    if res in "YES yes 1 Y y".split():
+        return True
+    else:
+        return False
+
 def indent(text, amount=2, ch=' '):
     """indent multi lines message"""
     padding = amount * ch
     return ''.join(padding + line for line in text.splitlines(True))
 
-def write(title, var="", force=None, fmt="\n#### DEBUG: %s:\n%s\n"):
+def isTypeConfig(var):
+    """To know if var is instance from Config/pyconf"""
+    typ = str(type(var))
+    # print "isTypeConfig" ,type, dir(var)
+    if ".pyconf.Config" in typ: return True
+    if ".pyconf.Mapping" in typ: return True
+    if ".pyconf.Sequence" in typ: return True
+    # print "NOT isTypeConfig %s" % typ
+    return False
+    
+def write(title, var="", force=None, fmt="  %s:\n%s\n####\n"):
     """write sys.stderr a message if _debug[-1]==True or optionaly force=True"""
     if _debug[-1] or force:
-        if 'src.pyconf.' in str(type(var)): 
-            sys.stderr.write(fmt % (title, indent(getStrConfigDbg(var))))
-        elif type(var) is not str:
-            sys.stderr.write(fmt % (title, indent(PP.pformat(var))))
-        else:
-            sys.stderr.write(fmt % (title, indent(var)))
+      callerframerecord = inspect.stack()[1] # get info of the caller
+      frame = callerframerecord[0]
+      info = inspect.getframeinfo(frame)
+      sys.stderr.write("\n#### DEBUG - %s:%s (%s) ####\n" % (info.filename, info.lineno, info.function))
+      tvar = type(var)
+      typ = str(tvar)
+      if isTypeConfig(var):
+        sys.stderr.write(fmt % (title, indent(getStrConfigDbg(var))))
+        return
+      if 'UnittestStream' in typ:
+        sys.stderr.write(fmt % (title, indent(var.getLogs())))
+        return  
+      if tvar is not str and tvar is not unicode:
+        sys.stderr.write(fmt % (title, indent(PP.pformat(var))))
+        return
+      sys.stderr.write(fmt % (title, indent(var)))
+      return
     return
 
 def tofix(title, var="", force=None):
     """
     write sys.stderr a message if _debug[-1]==True or optionaly force=True
-    use this only if no logger accessible for classic 
-    logger.warning(message) or logger.debug(message)
+    use this only if no logger accessible for classic logger.warning(message)
     """
-    fmt = "\n#### TOFIX: %s:\n%s\n"
-    write(title, var, force, fmt)
+    if _debug[-1] or isDeveloper():
+        callerframerecord = inspect.stack()[1] # get info of the caller
+        frame = callerframerecord[0]
+        info = inspect.getframeinfo(frame)
+        fmt = "#### TOFIX - " + str(info.filename) + ":" + str(info.lineno) +\
+              " (" + str(info.function) + ") ####\n   %s:\n%s\n"
+        write(title, var, force, fmt)
 
 def push_debug(aBool):
     """set debug outputs activated, or not"""
@@ -95,20 +146,60 @@ def pop_debug():
         sys.stderr.write("\nERROR: pop_debug: too much pop.")
         return None
 
+
+def format_exception(msg, limit=None, trace=None):
+  """
+  Format a stack trace and the exception information.
+  as traceback.format_exception(), without color
+  with traceback only if (_debug) or (DBG.isDeveloper())
+  """
+  etype, value, tb = sys.exc_info()
+  res = msg
+  if tb:
+    res += "\nTraceback (most recent call last):\n"
+    res += "".join(traceback.format_tb(tb, limit))  # [:-1])
+  res += "\n"
+  res += "\n".join(traceback.format_exception_only(etype, value))
+  return res
+
+def format_color_exception(msg, limit=None, trace=None):
+  """
+  Format a stack trace and the exception information.
+  as traceback.format_exception(), with color
+  with traceback only if _debug or isDeveloper())
+  """
+  etype, value, tb = sys.exc_info()
+  if _debug[-1] or isDeveloper():
+    res = "<red>" + msg
+    if tb:
+      res += "<yellow>\nTraceback (most recent call last):\n"
+      res += "".join(traceback.format_tb(tb, limit))  # [:-1])
+    res += "\n<red>"
+    res += "\n".join(traceback.format_exception_only(etype, value))
+    return res + "<reset>"
+  else:
+    res = "<red>" + msg  # + "<bright>"
+    res += "".join(traceback.format_exception_only(etype, value))
+    return res + "<reset>"
+
+
 ###############################################
 # utilitaires divers pour debug
 ###############################################
 
-class OutStream(SIO.StringIO):
-    """utility class for pyconf.Config output iostream"""
+class OutStream(StringIO):
+    """
+    utility class for pyconf.Config output iostream
+    """
     def close(self):
-      """because Config.__save__ calls close() stream as file
+      """
+      because Config.__save__ calls close() stream as file
       keep value before lost as self.value
       """
       self.value = self.getvalue()
-      SIO.StringIO.close(self)
+      StringIO.close(self)
     
-class InStream(SIO.StringIO):
+class InStream(StringIO):
     """utility class for pyconf.Config input iostream"""
     pass
 
@@ -126,70 +217,91 @@ def saveConfigStd(config, aStream):
     config.__save__(aStream, indent) 
 
 def getStrConfigStd(config):
-    """set string as saveConfigStd, 
-    as file .pyconf"""
+    """set string as saveConfigStd, as file .pyconf"""
     outStream = OutStream()
     saveConfigStd(config, outStream)
     return outStream.value
 
 def getStrConfigDbg(config):
-    """set string as saveConfigDbg, 
-    as (path expression evaluation) for debug"""
+    """
+    set string as saveConfigDbg, 
+    as (path expression evaluation) for debug
+    """
     outStream = OutStream()
     saveConfigDbg(config, outStream)
     return outStream.value
 
 def saveConfigDbg(config, aStream, indent=0, path=""):
     """pyconf returns multilines (path expression evaluation) for debug"""
-    _saveConfigRecursiveDbg(config, aStream, indent, path)
+    _saveConfigRecursiveDbg(config, aStream, indent, path, 0)
     aStream.close() # as config.__save__()
 
-def _saveConfigRecursiveDbg(config, aStream, indent, path):
+def _saveConfigRecursiveDbg(config, aStream, indent, path, nb):
     """pyconf inspired from Mapping.__save__"""
     debug = False
+    nbp = nb + 1 # depth recursive
     if indent <= 0: 
       indentp = 0
     else:
-      indentp = indentp + 2
+      indentp = indent + 2
+      
+    if nbp > 10: # protection
+      # raise Exception("!!! ERROR: Circular reference after %s" % aStream.getvalue())
+      # raise Exception("!!! ERROR: Circular reference %s" % path)
+      aStream.write("<red>!!! ERROR: Circular reference after %s<reset>\n" % path)
+      return
+    
     indstr = indent * ' ' # '':no indent, ' ':indent
     strType = str(type(config))
+    if debug: print("saveDbg Type %s %s" % (path, strType))
+    
     if "Sequence" in strType:
       for i in range(len(config)):
-        _saveConfigRecursiveDbg(config[i], aStream, indentp, path+"[%i]" % i)
+        _saveConfigRecursiveDbg(config[i], aStream, indentp, path+"[%i]" % i, nbp)
       return
-    try: 
+    '''
+    if "Reference" in strType:
+      try:
+        #evaluate = value.resolve(config)
+        aStream.write("<header>%s%s<reset> : %s <yellow>--> '%s'<reset>\n" % (indstr, path, config, str(config)))
+      except Exception as e:  
+        aStream.write("<header>%s%s<reset> : <red>!!! ERROR: %s !!!<reset>\n" % (indstr, path, str(e)))     
+      return
+    '''
+    
+    try: #type config, mapping
       order = object.__getattribute__(config, 'order')
       data = object.__getattribute__(config, 'data')
     except:
       aStream.write("%s%s : '%s'\n" % (indstr, path, str(config)))
       return     
-    for key in sorted(order):
+    for key in sorted(data): #order): # data as sort alphabetical, order as initial order
       value = data[key]
       strType = str(type(value))
-      if debug: print indstr + 'strType = %s' % strType, key
+      if debug: print('strType %s %s %s' % (path, key, strType))
       if "Config" in strType:
-        _saveConfigRecursiveDbg(value, aStream, indentp, path+"."+key)
+        _saveConfigRecursiveDbg(value, aStream, indentp, path+"."+key, nbp)
         continue
       if "Mapping" in strType:
-        _saveConfigRecursiveDbg(value, aStream, indentp, path+"."+key)
+        _saveConfigRecursiveDbg(value, aStream, indentp, path+"."+key, nbp)
         continue
       if "Sequence" in strType:
         for i in range(len(value)):
-          _saveConfigRecursiveDbg(value[i], aStream, indentp, path+"."+key+"[%i]" % i)
+          _saveConfigRecursiveDbg(value.data[i], aStream, indentp, path+"."+key+"[%i]" % i, nbp)
         continue
       if "Expression" in strType:
         try:
           evaluate = value.evaluate(config)
           aStream.write("%s%s.%s : %s --> '%s'\n" % (indstr, path, key, str(value), evaluate))
         except Exception as e:      
-          aStream.write("%s%s.%s : !!! ERROR: %s !!!\n" % (indstr, path, key, e.message))     
+          aStream.write("%s%s.%s : !!! ERROR: %s !!!\n" % (indstr, path, key, str(e)))     
         continue
       if "Reference" in strType:
         try:
           evaluate = value.resolve(config)
           aStream.write("%s%s.%s : %s --> '%s'\n" % (indstr, path, key, str(value), evaluate))
         except Exception as e:  
-          aStream.write("%s%s.%s : !!! ERROR: %s !!!\n" % (indstr, path, key, e.message))     
+          aStream.write("%s%s.%s : !!! ERROR: %s !!!\n" % (indstr, path, key, str(e)))     
         continue
       if type(value) in [str, bool, int, type(None), unicode]:
         aStream.write("%s%s.%s : '%s'\n" % (indstr, path, key, str(value)))
@@ -197,4 +309,4 @@ def _saveConfigRecursiveDbg(config, aStream, indent, path):
       try:
         aStream.write("!!! TODO fix that %s %s%s.%s : %s\n" % (type(value), indstr, path, key, str(value)))
       except Exception as e:      
-        aStream.write("%s%s.%s : !!! %s\n" % (indstr, path, key, e.message))
+        aStream.write("%s%s.%s : !!! %s\n" % (indstr, path, key, str(e)))