Salome HOME
git: ignore VSCode cache files, sat cache file and add a pre-commit config
[tools/sat.git] / src / debug.py
1 #!/usr/bin/env python
2 #-*- coding:utf-8 -*-
3
4 #  Copyright (C) 2010-2018  CEA/DEN
5 #
6 #  This library is free software; you can redistribute it and/or
7 #  modify it under the terms of the GNU Lesser General Public
8 #  License as published by the Free Software Foundation; either
9 #  version 2.1 of the License.
10 #
11 #  This library is distributed in the hope that it will be useful,
12 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 #  Lesser General Public License for more details.
15 #
16 #  You should have received a copy of the GNU Lesser General Public
17 #  License along with this library; if not, write to the Free Software
18 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19
20 """
21 This file assume DEBUG functionalities use.
22 Print salomeTools debug messages in sys.stderr.
23 Show pretty print debug representation from instances of SAT classes 
24 (pretty print src.pyconf.Config)
25
26 | Warning: supposedly show messages in SAT development phase, not production
27
28 | Usage:
29 | >> import debug as DBG
30 | >> DBG.write("aTitle", aVariable)        # not shown in production 
31 | >> DBG.write("aTitle", aVariable, True)  # unconditionaly shown (as show=True)
32
33 | to set show message as development phase:
34 | >> DBG.push_debug(True)
35
36 | to set no show message as production phase:
37 | >> DBG.push_debug(False)
38
39 | to set show message temporary as development phase, only in a method:
40 | >> def aMethodToDebug(...):
41 | >>   DBG.push_debug(True)              #force show as appended status
42 | >>   etc. method code with some DBG.write()
43 | >>   DBG.pop_debug()                   #restore previous status (show or not show)
44 | >>   return
45
46 | to set a message for future fix, as temporary problem to not forget:
47 | DBG.tofix("aTitle", aVariable, True/False) #True/False in production shown, or not
48
49 | in command line interface you could redirect stderr to file 'myDebug.log':
50 | >> sat compile ... 2> myDebug.log   # only stderr
51 | >> sat compile ... &> myDebug.log   # stdout and stderr
52 """
53
54 import os
55 import sys
56 import traceback
57 import pprint as PP
58 import inspect
59 import src
60
61 # Compatibility python 2/3 for unicode
62 try:
63     _test = unicode
64 except:
65     unicode = str
66
67 # Compatibility python 2/3 for StringIO
68 try:
69     from StringIO import StringIO
70 except ImportError:
71     from io import StringIO
72
73 _debug = [False] #support push/pop for temporary activate debug outputs
74
75
76 def isDeveloper():
77     """
78     if you are a developer, sometimes you want verbose traces unconditionally
79     export SAT_DEVELOPER_MODE=1 before launch sat command to do that
80     """
81     res = os.getenv("SAT_DEVELOPER_MODE", "0")
82     if res in "YES yes 1 Y y".split():
83         return True
84     else:
85         return False
86
87 def indent(text, amount=2, ch=' '):
88     """indent multi lines message"""
89     padding = amount * ch
90     return ''.join(padding + line for line in text.splitlines(True))
91
92 def isTypeConfig(var):
93     """To know if var is instance from Config/pyconf"""
94     typ = str(type(var))
95     # print "isTypeConfig" ,type, dir(var)
96     if ".pyconf.Config" in typ: return True
97     if ".pyconf.Mapping" in typ: return True
98     if ".pyconf.Sequence" in typ: return True
99     # print "NOT isTypeConfig %s" % typ
100     return False
101     
102 def write(title, var="", force=None, fmt="  %s:\n%s\n####\n"):
103     """write sys.stderr a message if _debug[-1]==True or optionaly force=True"""
104     if _debug[-1] or force:
105       callerframerecord = inspect.stack()[1] # get info of the caller
106       frame = callerframerecord[0]
107       info = inspect.getframeinfo(frame)
108       sys.stderr.write("\n#### DEBUG - %s:%s (%s) ####\n" % (info.filename, info.lineno, info.function))
109       tvar = type(var)
110       typ = str(tvar)
111       if isTypeConfig(var):
112         sys.stderr.write(fmt % (title, indent(getStrConfigDbg(var))))
113         return
114       if 'UnittestStream' in typ:
115         sys.stderr.write(fmt % (title, indent(var.getLogs())))
116         return  
117       if tvar is not str and tvar is not unicode:
118         sys.stderr.write(fmt % (title, indent(PP.pformat(var))))
119         return
120       sys.stderr.write(fmt % (title, indent(var)))
121       return
122     return
123
124 def tofix(title, var="", force=None):
125     """
126     write sys.stderr a message if _debug[-1]==True or optionaly force=True
127     use this only if no logger accessible for classic logger.warning(message)
128     """
129     if _debug[-1] or isDeveloper():
130         callerframerecord = inspect.stack()[1] # get info of the caller
131         frame = callerframerecord[0]
132         info = inspect.getframeinfo(frame)
133         fmt = "#### TOFIX - " + str(info.filename) + ":" + str(info.lineno) +\
134               " (" + str(info.function) + ") ####\n   %s:\n%s\n"
135         write(title, var, force, fmt)
136
137 def push_debug(aBool):
138     """set debug outputs activated, or not"""
139     _debug.append(aBool)
140
141 def pop_debug():
142     """restore previous debug outputs status"""
143     if len(_debug) > 1:
144         return _debug.pop()
145     else:
146         sys.stderr.write("\nERROR: pop_debug: too much pop.")
147         return None
148
149
150 def format_exception(msg, limit=None, trace=None):
151   """
152   Format a stack trace and the exception information.
153   as traceback.format_exception(), without color
154   with traceback only if (_debug) or (DBG.isDeveloper())
155   """
156   etype, value, tb = sys.exc_info()
157   res = msg
158   if tb:
159     res += "\nTraceback (most recent call last):\n"
160     res += "".join(traceback.format_tb(tb, limit))  # [:-1])
161   res += "\n"
162   res += "\n".join(traceback.format_exception_only(etype, value))
163   return res
164
165 def format_color_exception(msg, limit=None, trace=None):
166   """
167   Format a stack trace and the exception information.
168   as traceback.format_exception(), with color
169   with traceback only if _debug or isDeveloper())
170   """
171   etype, value, tb = sys.exc_info()
172   if _debug[-1] or isDeveloper():
173     res = "<red>" + msg
174     if tb:
175       res += "<yellow>\nTraceback (most recent call last):\n"
176       res += "".join(traceback.format_tb(tb, limit))  # [:-1])
177     res += "\n<red>"
178     res += "\n".join(traceback.format_exception_only(etype, value))
179     return res + "<reset>"
180   else:
181     res = "<red>" + msg  # + "<bright>"
182     res += "".join(traceback.format_exception_only(etype, value))
183     return res + "<reset>"
184
185
186 ###############################################
187 # utilitaires divers pour debug
188 ###############################################
189
190 class OutStream(StringIO):
191     """
192     utility class for pyconf.Config output iostream
193     """
194     def close(self):
195       """
196       because Config.__save__ calls close() stream as file
197       keep value before lost as self.value
198       """
199       self.value = self.getvalue()
200       StringIO.close(self)
201     
202 class InStream(StringIO):
203     """utility class for pyconf.Config input iostream"""
204     pass
205
206 def getLocalEnv():
207     """get string for environment variables representation"""
208     res = ""
209     for i in sorted(os.environ):
210         res += "%s : %s\n" % (i, os.environ[i])
211     return res
212
213 # save as initial Config.save() moved as Config.__save__() 
214 def saveConfigStd(config, aStream):
215     """returns as file .pyconf"""
216     indent =  0
217     config.__save__(aStream, indent) 
218
219 def getStrConfigStd(config):
220     """set string as saveConfigStd, as file .pyconf"""
221     outStream = OutStream()
222     saveConfigStd(config, outStream)
223     return outStream.value
224
225 def getStrConfigDbg(config):
226     """
227     set string as saveConfigDbg, 
228     as (path expression evaluation) for debug
229     """
230     outStream = OutStream()
231     saveConfigDbg(config, outStream)
232     return outStream.value
233
234 def saveConfigDbg(config, aStream, indent=0, path=""):
235     """pyconf returns multilines (path expression evaluation) for debug"""
236     _saveConfigRecursiveDbg(config, aStream, indent, path, 0)
237     aStream.close() # as config.__save__()
238
239 def _saveConfigRecursiveDbg(config, aStream, indent, path, nb):
240     """pyconf inspired from Mapping.__save__"""
241     debug = False
242     nbp = nb + 1 # depth recursive
243     if indent <= 0: 
244       indentp = 0
245     else:
246       indentp = indent + 2
247       
248     if nbp > 10: # protection
249       # raise Exception("!!! ERROR: Circular reference after %s" % aStream.getvalue())
250       # raise Exception("!!! ERROR: Circular reference %s" % path)
251       aStream.write("<red>!!! ERROR: Circular reference after %s<reset>\n" % path)
252       return
253     
254     indstr = indent * ' ' # '':no indent, ' ':indent
255     strType = str(type(config))
256     if debug: print("saveDbg Type %s %s" % (path, strType))
257     
258     if "Sequence" in strType:
259       for i in range(len(config)):
260         _saveConfigRecursiveDbg(config[i], aStream, indentp, path+"[%i]" % i, nbp)
261       return
262     '''
263     if "Reference" in strType:
264       try:
265         #evaluate = value.resolve(config)
266         aStream.write("<header>%s%s<reset> : %s <yellow>--> '%s'<reset>\n" % (indstr, path, config, str(config)))
267       except Exception as e:  
268         aStream.write("<header>%s%s<reset> : <red>!!! ERROR: %s !!!<reset>\n" % (indstr, path, str(e)))     
269       return
270     '''
271     
272     try: #type config, mapping
273       order = object.__getattribute__(config, 'order')
274       data = object.__getattribute__(config, 'data')
275     except:
276       aStream.write("%s%s : '%s'\n" % (indstr, path, str(config)))
277       return     
278     for key in sorted(data): #order): # data as sort alphabetical, order as initial order
279       value = data[key]
280       strType = str(type(value))
281       if debug: print('strType %s %s %s' % (path, key, strType))
282       if "Config" in strType:
283         _saveConfigRecursiveDbg(value, aStream, indentp, path+"."+key, nbp)
284         continue
285       if "Mapping" in strType:
286         _saveConfigRecursiveDbg(value, aStream, indentp, path+"."+key, nbp)
287         continue
288       if "Sequence" in strType:
289         for i in range(len(value)):
290           _saveConfigRecursiveDbg(value.data[i], aStream, indentp, path+"."+key+"[%i]" % i, nbp)
291         continue
292       if "Expression" in strType:
293         try:
294           evaluate = value.evaluate(config)
295           aStream.write("%s%s.%s : %s --> '%s'\n" % (indstr, path, key, str(value), evaluate))
296         except Exception as e:      
297           aStream.write("%s%s.%s : !!! ERROR: %s !!!\n" % (indstr, path, key, str(e)))     
298         continue
299       if "Reference" in strType:
300         try:
301           evaluate = value.resolve(config)
302           aStream.write("%s%s.%s : %s --> '%s'\n" % (indstr, path, key, str(value), evaluate))
303         except Exception as e:  
304           aStream.write("%s%s.%s : !!! ERROR: %s !!!\n" % (indstr, path, key, str(e)))     
305         continue
306       if type(value) in [str, bool, int, type(None), unicode]:
307         aStream.write("%s%s.%s : '%s'\n" % (indstr, path, key, str(value)))
308         continue
309       try:
310         aStream.write("!!! TODO fix that %s %s%s.%s : %s\n" % (type(value), indstr, path, key, str(value)))
311       except Exception as e:      
312         aStream.write("%s%s.%s : !!! %s\n" % (indstr, path, key, str(e)))