devtools.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
  4. # Copyright (C)2013-2023 The MMGen Project <mmgen@tuta.io>
  5. # Licensed under the GNU General Public License, Version 3:
  6. # https://www.gnu.org/licenses
  7. # Public project repositories:
  8. # https://github.com/mmgen/mmgen
  9. # https://gitlab.com/mmgen/mmgen
  10. """
  11. devtools: Developer tools for the MMGen suite
  12. """
  13. import sys
  14. def pfmt(*args):
  15. import pprint
  16. return pprint.PrettyPrinter(indent=4).pformat(
  17. args if len(args) > 1 else '' if not args else args[0] )
  18. def pmsg(*args):
  19. sys.stderr.write(pfmt(*args) + '\n')
  20. def pmsg_r(*args):
  21. sys.stderr.write(pfmt(*args))
  22. def pdie(*args,exit_val=1):
  23. pmsg(*args)
  24. sys.exit(exit_val)
  25. def pexit(*args):
  26. pdie(*args,exit_val=0)
  27. def Pmsg(*args):
  28. sys.stdout.write(pfmt(*args) + '\n')
  29. def Pdie(*args,exit_val=1):
  30. Pmsg(*args)
  31. sys.exit(exit_val)
  32. def Pexit(*args):
  33. Pdie(*args,exit_val=0)
  34. def print_stack_trace(message=None,fh_list=[],nl='\n',sep='\n ',trim=4):
  35. import os
  36. if not fh_list:
  37. fh_list.append(open(f'devtools.trace.{os.getpid()}','w'))
  38. nl = ''
  39. res = get_stack_trace(message,nl,sep,trim)
  40. sys.stderr.write(res)
  41. fh_list[0].write(res)
  42. def get_stack_trace(message=None,nl='\n',sep='\n ',trim=3):
  43. import os,re,traceback
  44. tb = [t for t in traceback.extract_stack() if t.filename[:1] != '<']
  45. fs = '{}:{}: in {}:\n {}'
  46. out = [
  47. fs.format(
  48. re.sub(r'^\./','',os.path.relpath(t.filename)),
  49. t.lineno,
  50. (t.name+'()' if t.name[-1] != '>' else t.name),
  51. t.line or '(none)')
  52. for t in (tb[:-trim] if trim else tb) ]
  53. return f'{nl}STACK TRACE {message or "[unnamed]"}:{sep}{sep.join(out)}\n'
  54. def print_diff(*args,**kwargs):
  55. sys.stderr.write(get_diff(*args,**kwargs))
  56. def get_diff(a,b,a_fn='',b_fn='',from_json=True):
  57. if from_json:
  58. import json
  59. a = json.dumps(json.loads(a),indent=4)
  60. b = json.dumps(json.loads(b),indent=4)
  61. from difflib import unified_diff
  62. # chunk headers have trailing newlines, hence the rstrip()
  63. return ' DIFF:\n {}\n'.format(
  64. '\n '.join(a.rstrip('\n') for a in unified_diff(
  65. a.split('\n'),
  66. b.split('\n'),
  67. a_fn,
  68. b_fn )))
  69. def print_ndiff(*args,**kwargs):
  70. sys.stderr.write(get_ndiff(*args,**kwargs))
  71. def get_ndiff(a,b):
  72. from difflib import ndiff
  73. return list(ndiff(
  74. a.split('\n'),
  75. b.split('\n') ))
  76. class MMGenObjectMethods: # mixin class for MMGenObject
  77. # Pretty-print an MMGenObject instance, recursing into sub-objects - WIP
  78. def pmsg(self):
  79. sys.stdout.write('\n'+self.pfmt())
  80. def pdie(self,exit_val=1):
  81. self.pmsg()
  82. sys.exit(exit_val)
  83. def pexit(self):
  84. self.pdie(exit_val=0)
  85. def pfmt(self,lvl=0,id_list=[]):
  86. from decimal import Decimal
  87. scalars = (str,int,float,Decimal)
  88. def do_list(out,e,lvl=0,is_dict=False):
  89. out.append('\n')
  90. for i in e:
  91. el = i if not is_dict else e[i]
  92. if is_dict:
  93. # out.append('{s}{:<{l}}'.format(i,s=' '*(4*lvl+8),l=10,l2=8*(lvl+1)+8))
  94. out.append('{s1}{i}{s2}'.format(
  95. i = i,
  96. s1 = ' ' * (4*lvl+8),
  97. s2 = ' ' * 10 ))
  98. if hasattr(el,'pfmt'):
  99. out.append('{:>{l}}{}'.format(
  100. '',
  101. el.pfmt( lvl=lvl+1, id_list=id_list+[id(self)] ),
  102. l = (lvl+1)*8 ))
  103. elif isinstance(el,scalars):
  104. if isList(e):
  105. out.append( '{:>{l}}{!r:16}\n'.format( '', el, l=lvl*8 ))
  106. else:
  107. out.append(f' {el!r}')
  108. elif isList(el) or isDict(el):
  109. indent = 1 if is_dict else lvl*8+4
  110. out.append('{:>{l}}{:16}'.format( '', f'<{type(el).__name__}>', l=indent ))
  111. if isList(el) and isinstance(el[0],scalars):
  112. out.append('\n')
  113. do_list(out,el,lvl=lvl+1,is_dict=isDict(el))
  114. else:
  115. out.append('{:>{l}}{:16} {!r}\n'.format( '', f'<{type(el).__name__}>', el, l=(lvl*8)+8 ))
  116. out.append('\n')
  117. if not e:
  118. out.append(f'{e!r}\n')
  119. def isDict(obj):
  120. return isinstance(obj,dict)
  121. def isList(obj):
  122. return isinstance(obj,list)
  123. def isScalar(obj):
  124. return isinstance(obj,scalars)
  125. out = [f'<{type(self).__name__}>{" "+repr(self) if isScalar(self) else ""}\n']
  126. if id(self) in id_list:
  127. return out[-1].rstrip() + ' [RECURSION]\n'
  128. if isList(self) or isDict(self):
  129. do_list(out,self,lvl=lvl,is_dict=isDict(self))
  130. for k in self.__dict__:
  131. e = getattr(self,k)
  132. if isList(e) or isDict(e):
  133. out.append('{:>{l}}{:<10} {:16}'.format( '', k, f'<{type(e).__name__}>', l=(lvl*8)+4 ))
  134. do_list(out,e,lvl=lvl,is_dict=isDict(e))
  135. elif hasattr(e,'pfmt') and callable(e.pfmt) and type(e) != type:
  136. out.append('{:>{l}}{:10} {}'.format(
  137. '',
  138. k,
  139. e.pfmt( lvl=lvl+1, id_list=id_list+[id(self)] ),
  140. l = (lvl*8)+4 ))
  141. else:
  142. out.append('{:>{l}}{:<10} {:16} {}\n'.format(
  143. '',
  144. k,
  145. f'<{type(e).__name__}>',
  146. repr(e),
  147. l=(lvl*8)+4 ))
  148. import re
  149. return re.sub('\n+','\n',''.join(out))