devtools.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. #!/usr/bin/env python3
  2. class MMGenObject(object):
  3. 'placeholder - overridden when testing'
  4. def immutable_attr_init_check(self): pass
  5. import os
  6. if os.getenv('MMGEN_DEBUG') or os.getenv('MMGEN_TEST_SUITE') or os.getenv('MMGEN_EXEC_WRAPPER'):
  7. import sys,re,traceback,json,pprint
  8. from decimal import Decimal
  9. from difflib import unified_diff,ndiff
  10. def pmsg(*args,out=sys.stderr):
  11. d = args if len(args) > 1 else '' if not args else args[0]
  12. out.write(pprint.PrettyPrinter(indent=4).pformat(d) + '\n')
  13. def pdie(*args,exit_val=1,out=sys.stderr):
  14. pmsg(*args,out=out)
  15. sys.exit(exit_val)
  16. def pexit(*args,out=sys.stderr):
  17. pdie(*args,exit_val=0,out=out)
  18. def Pmsg(*args):
  19. pmsg(*args,out=sys.stdout)
  20. def Pdie(*args):
  21. pdie(*args,out=sys.stdout)
  22. def Pexit(*args):
  23. pexit(*args,out=sys.stdout)
  24. def print_stack_trace(message=None,fh=[],nl='\n',sep='\n '):
  25. if not fh:
  26. fh.append(open(f'devtools.trace.{os.getpid()}','w'))
  27. nl = ''
  28. tb = [t for t in traceback.extract_stack() if t.filename[:1] != '<'][:-1]
  29. fs = '{}:{}: in {}:\n {}'
  30. out = [
  31. fs.format(
  32. re.sub(r'^\./','',os.path.relpath(t.filename)),
  33. t.lineno,
  34. (t.name+'()' if t.name[-1] != '>' else t.name),
  35. t.line or '(none)')
  36. for t in tb ]
  37. text = f'{nl}STACK TRACE {message or "[unnamed]"}:{sep}{sep.join(out)}\n'
  38. sys.stderr.write(text)
  39. fh[0].write(text)
  40. class MMGenObject(object):
  41. def print_stack_trace(self,*args,**kwargs):
  42. print_stack_trace(*args,**kwargs)
  43. # Pretty-print any object subclassed from MMGenObject, recursing into sub-objects - WIP
  44. def pmsg(self,*args):
  45. print(args[0] if len(args) == 1 else args if args else self.pfmt())
  46. def pdie(self,*args):
  47. self.pmsg(*args)
  48. sys.exit(1)
  49. def pexit(self,*args):
  50. self.pmsg(*args)
  51. sys.exit(0)
  52. def pfmt(self,lvl=0,id_list=[]):
  53. scalars = (str,int,float,Decimal)
  54. def do_list(out,e,lvl=0,is_dict=False):
  55. out.append('\n')
  56. for i in e:
  57. el = i if not is_dict else e[i]
  58. if is_dict:
  59. out.append('{s}{:<{l}}'.format(i,s=' '*(4*lvl+8),l=10,l2=8*(lvl+1)+8))
  60. if hasattr(el,'pfmt'):
  61. out.append('{:>{l}}{}'.format(
  62. '',
  63. el.pfmt( lvl=lvl+1, id_list=id_list+[id(self)] ),
  64. l = (lvl+1)*8 ))
  65. elif isinstance(el,scalars):
  66. if isList(e):
  67. out.append( '{:>{l}}{!r:16}\n'.format( '', el, l=lvl*8 ))
  68. else:
  69. out.append(f' {el!r}')
  70. elif isList(el) or isDict(el):
  71. indent = 1 if is_dict else lvl*8+4
  72. out.append('{:>{l}}{:16}'.format( '', f'<{type(el).__name__}>', l=indent ))
  73. if isList(el) and isinstance(el[0],scalars):
  74. out.append('\n')
  75. do_list(out,el,lvl=lvl+1,is_dict=isDict(el))
  76. else:
  77. out.append('{:>{l}}{:16} {!r}\n'.format( '', f'<{type(el).__name__}>', el, l=(lvl*8)+8 ))
  78. out.append('\n')
  79. if not e:
  80. out.append(f'{e!r}\n')
  81. def isDict(obj):
  82. return isinstance(obj,dict)
  83. def isList(obj):
  84. return isinstance(obj,list)
  85. def isScalar(obj):
  86. return isinstance(obj,scalars)
  87. out = [f'<{type(self).__name__}>{" "+repr(self) if isScalar(self) else ""}\n']
  88. if id(self) in id_list:
  89. return out[-1].rstrip() + ' [RECURSION]\n'
  90. if isList(self) or isDict(self):
  91. do_list(out,self,lvl=lvl,is_dict=isDict(self))
  92. for k in self.__dict__:
  93. e = getattr(self,k)
  94. if isList(e) or isDict(e):
  95. out.append('{:>{l}}{:<10} {:16}'.format( '', k, f'<{type(e).__name__}>', l=(lvl*8)+4 ))
  96. do_list(out,e,lvl=lvl,is_dict=isDict(e))
  97. elif hasattr(e,'pfmt') and type(e) != type:
  98. out.append('{:>{l}}{:10} {}'.format(
  99. '',
  100. k,
  101. e.pfmt( lvl=lvl+1, id_list=id_list+[id(self)] ),
  102. l = (lvl*8)+4 ))
  103. else:
  104. out.append('{:>{l}}{:<10} {:16} {}\n'.format(
  105. '',
  106. k,
  107. f'<{type(e).__name__}>',
  108. repr(e),
  109. l=(lvl*8)+4 ))
  110. import re
  111. return re.sub('\n+','\n',''.join(out))
  112. # Check that all immutables have been initialized. Expensive, so do only when testing.
  113. def immutable_attr_init_check(self):
  114. from .globalvars import g
  115. if g.test_suite:
  116. from .util import die
  117. cls = type(self)
  118. for attrname in sorted({a for a in self.valid_attrs if a[0] != '_'}):
  119. for o in (cls,cls.__bases__[0]): # assume there's only one base class
  120. if attrname in o.__dict__:
  121. attr = o.__dict__[attrname]
  122. break
  123. else:
  124. die(4,f'unable to find descriptor {cls.__name__}.{attrname}')
  125. if type(attr).__name__ == 'ImmutableAttr':
  126. if attrname not in self.__dict__:
  127. die(4,
  128. f'attribute {attrname!r} of {cls.__name__} has not been initialized in constructor!')
  129. def print_diff(a,b,from_file='',to_file='',from_json=True):
  130. if from_json:
  131. a = json.dumps(json.loads(a),indent=4).split('\n') if a else []
  132. b = json.dumps(json.loads(b),indent=4).split('\n') if b else []
  133. else:
  134. a = a.split('\n')
  135. b = b.split('\n')
  136. sys.stderr.write(' DIFF:\n {}\n'.format(
  137. '\n '.join(unified_diff(a,b,from_file,to_file)) ))
  138. def get_ndiff(a,b):
  139. a = a.split('\n')
  140. b = b.split('\n')
  141. return list(ndiff(a,b))